mirror of
https://github.com/grillazz/fastapi-sqlalchemy-asyncpg.git
synced 2025-08-26 16:40:40 +03:00
Merge pull request #197 from grillazz/195-switch-project-to-uv
This commit is contained in:
commit
3f4f1efb35
4
.github/workflows/build-and-test.yml
vendored
4
.github/workflows/build-and-test.yml
vendored
@ -54,6 +54,10 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Lint with ruff
|
||||
run: uv run --frozen ruff check .
|
||||
|
||||
- name: Test with python ${{ matrix.python-version }}
|
||||
run: uv run --frozen pytest
|
||||
|
||||
|
||||
|
2
Makefile
2
Makefile
@ -39,7 +39,7 @@ safety: ## Check project and dependencies with safety https://github.com/pyupio/
|
||||
|
||||
.PHONY: py-upgrade
|
||||
py-upgrade: ## Upgrade project py files with pyupgrade library for python version 3.10
|
||||
pyupgrade --py312-plus `find app -name "*.py"`
|
||||
pyupgrade --py313-plus `find app -name "*.py"`
|
||||
|
||||
.PHONY: lint
|
||||
lint: ## Lint project code.
|
||||
|
@ -1,12 +1,11 @@
|
||||
import logging
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, status, Request, Depends, Query
|
||||
from fastapi import APIRouter, Depends, Query, Request, status
|
||||
from pydantic import EmailStr
|
||||
from starlette.concurrency import run_in_threadpool
|
||||
|
||||
from app.services.smtp import SMTPEmailService
|
||||
|
||||
from app.utils.logging import AppLogger
|
||||
|
||||
logger = AppLogger().get_logger()
|
||||
|
@ -1,7 +1,8 @@
|
||||
import io
|
||||
from fastapi import APIRouter, Depends, status, UploadFile, HTTPException
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
|
||||
import polars as pl
|
||||
from fastapi import APIRouter, Depends, HTTPException, UploadFile, status
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.database import get_db
|
||||
|
@ -3,8 +3,6 @@ from typing import Annotated
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from fastapi_cache.decorator import cache
|
||||
|
||||
from app.database import get_db
|
||||
from app.models.shakespeare import Paragraph
|
||||
|
||||
@ -14,7 +12,6 @@ router = APIRouter(prefix="/v1/shakespeare")
|
||||
@router.get(
|
||||
"/",
|
||||
)
|
||||
@cache(namespace="test-2", expire=60)
|
||||
async def find_paragraph(
|
||||
character: Annotated[str, Query(description="Character name")],
|
||||
db_session: AsyncSession = Depends(get_db),
|
||||
|
@ -1,4 +1,4 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Request
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends, status, Request, HTTPException, Form
|
||||
from fastapi import APIRouter, Depends, Form, HTTPException, Request, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.database import get_db
|
||||
from app.models.user import User
|
||||
from app.schemas.user import UserSchema, UserResponse, UserLogin, TokenResponse
|
||||
from app.schemas.user import TokenResponse, UserLogin, UserResponse, UserSchema
|
||||
from app.services.auth import create_access_token
|
||||
from app.utils.logging import AppLogger
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import os
|
||||
|
||||
from pydantic import PostgresDsn, RedisDsn, computed_field, BaseModel
|
||||
from pydantic import BaseModel, PostgresDsn, RedisDsn, computed_field
|
||||
from pydantic_core import MultiHostUrl
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
from collections.abc import AsyncGenerator
|
||||
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
from sqlalchemy.ext.asyncio import async_sessionmaker
|
||||
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
||||
|
||||
from app.config import settings as global_settings
|
||||
from app.utils.logging import AppLogger
|
||||
|
30
app/main.py
30
app/main.py
@ -1,25 +1,22 @@
|
||||
import asyncpg
|
||||
from apscheduler.eventbrokers.redis import RedisEventBroker
|
||||
from apscheduler.datastores.sqlalchemy import SQLAlchemyDataStore
|
||||
from fastapi import FastAPI, Depends
|
||||
from fastapi_cache import FastAPICache
|
||||
from fastapi_cache.backends.redis import RedisBackend
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
import asyncpg
|
||||
from apscheduler import AsyncScheduler
|
||||
from apscheduler.datastores.sqlalchemy import SQLAlchemyDataStore
|
||||
from apscheduler.eventbrokers.redis import RedisEventBroker
|
||||
from fastapi import Depends, FastAPI
|
||||
|
||||
from app.api.health import router as health_router
|
||||
from app.api.nonsense import router as nonsense_router
|
||||
from app.api.shakespeare import router as shakespeare_router
|
||||
from app.api.stuff import router as stuff_router
|
||||
from app.api.user import router as user_router
|
||||
from app.config import settings as global_settings
|
||||
from app.database import engine
|
||||
from app.utils.logging import AppLogger
|
||||
from app.api.user import router as user_router
|
||||
from app.api.health import router as health_router
|
||||
from app.redis import get_redis, get_cache
|
||||
from app.redis import get_redis
|
||||
from app.services.auth import AuthBearer
|
||||
from app.services.scheduler import SchedulerMiddleware
|
||||
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from apscheduler import AsyncScheduler
|
||||
from app.utils.logging import AppLogger
|
||||
|
||||
logger = AppLogger().get_logger()
|
||||
|
||||
@ -32,10 +29,7 @@ async def lifespan(_app: FastAPI):
|
||||
_postgres_dsn = global_settings.postgres_url.unicode_string()
|
||||
|
||||
try:
|
||||
# Initialize the cache with the redis connection
|
||||
redis_cache = await get_cache()
|
||||
FastAPICache.init(RedisBackend(redis_cache), prefix="fastapi-cache")
|
||||
# logger.info(FastAPICache.get_cache_status_header())
|
||||
# TODO: cache with the redis connection
|
||||
# Initialize the postgres connection pool
|
||||
_app.postgres_pool = await asyncpg.create_pool(
|
||||
dsn=_postgres_dsn,
|
||||
|
@ -2,9 +2,10 @@ from typing import Any
|
||||
|
||||
from asyncpg import UniqueViolationError
|
||||
from fastapi import HTTPException, status
|
||||
from sqlalchemy.exc import SQLAlchemyError, IntegrityError
|
||||
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import declared_attr, DeclarativeBase
|
||||
from sqlalchemy.orm import DeclarativeBase, declared_attr
|
||||
|
||||
from app.utils.logging import AppLogger
|
||||
|
||||
logger = AppLogger().get_logger()
|
||||
|
@ -4,7 +4,7 @@ from fastapi import HTTPException, status
|
||||
from sqlalchemy import String, select
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import mapped_column, Mapped
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.models.base import Base
|
||||
|
||||
|
@ -11,6 +11,7 @@ from sqlalchemy import (
|
||||
)
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.models.base import Base
|
||||
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
import uuid
|
||||
|
||||
from sqlalchemy import String, select, ForeignKey
|
||||
from sqlalchemy import ForeignKey, String, select
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import mapped_column, Mapped, relationship, joinedload
|
||||
from sqlalchemy.orm import Mapped, joinedload, mapped_column, relationship
|
||||
|
||||
from app.models.base import Base
|
||||
from app.models.nonsense import Nonsense
|
||||
|
@ -3,10 +3,10 @@ from typing import Any
|
||||
|
||||
import bcrypt
|
||||
from pydantic import SecretStr
|
||||
from sqlalchemy import String, LargeBinary, select, Column
|
||||
from sqlalchemy import Column, LargeBinary, String, select
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import mapped_column, Mapped
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.models.base import Base
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field, EmailStr, ConfigDict, SecretStr
|
||||
from pydantic import BaseModel, ConfigDict, EmailStr, Field, SecretStr
|
||||
|
||||
config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
import time
|
||||
|
||||
import jwt
|
||||
from fastapi import HTTPException, Request
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
|
||||
from app.config import settings as global_settings
|
||||
from app.models.user import User
|
||||
|
||||
from fastapi import Request, HTTPException
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from app.utils.logging import AppLogger
|
||||
|
||||
logger = AppLogger().get_logger()
|
||||
|
@ -1,11 +1,10 @@
|
||||
from datetime import datetime
|
||||
|
||||
from attrs import define
|
||||
|
||||
from sqlalchemy import text
|
||||
from starlette.types import ASGIApp, Receive, Scope, Send
|
||||
from apscheduler import AsyncScheduler
|
||||
from apscheduler.triggers.interval import IntervalTrigger
|
||||
from attrs import define
|
||||
from sqlalchemy import text
|
||||
from starlette.types import ASGIApp, Receive, Scope, Send
|
||||
|
||||
from app.database import AsyncSessionFactory
|
||||
from app.utils.logging import AppLogger
|
||||
|
@ -1,18 +1,15 @@
|
||||
from attrs import define, field
|
||||
import smtplib
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
|
||||
from app.config import settings as global_settings
|
||||
|
||||
from attrs import define, field
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
from pydantic import EmailStr
|
||||
|
||||
from app.config import settings as global_settings
|
||||
from app.utils.logging import AppLogger
|
||||
from app.utils.singleton import SingletonMetaNoArgs
|
||||
|
||||
|
||||
logger = AppLogger().get_logger()
|
||||
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
from functools import wraps
|
||||
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
|
||||
def compile_sql_or_scalar(func):
|
||||
"""
|
||||
|
@ -3,7 +3,6 @@ import logging
|
||||
from rich.console import Console
|
||||
from rich.logging import RichHandler
|
||||
|
||||
|
||||
from app.utils.singleton import SingletonMeta
|
||||
|
||||
|
||||
@ -21,5 +20,5 @@ class RichConsoleHandler(RichHandler):
|
||||
def __init__(self, width=200, style=None, **kwargs):
|
||||
super().__init__(
|
||||
console=Console(color_system="256", width=width, style=style, stderr=True),
|
||||
**kwargs
|
||||
**kwargs,
|
||||
)
|
||||
|
@ -1,4 +1,4 @@
|
||||
from locust import HttpUser, task, between
|
||||
from locust import HttpUser, between, task
|
||||
|
||||
|
||||
class Stuff(HttpUser):
|
||||
|
@ -5,30 +5,71 @@ description = "A modern FastAPI application with SQLAlchemy 2.0 and AsyncPG for
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"fastapi[all]>=0.115.6",
|
||||
"pydantic[email]>=2.10.3",
|
||||
"pydantic-settings>=2.7.0",
|
||||
"sqlalchemy>=2.0.36",
|
||||
"fastapi[all]>=0.115.11",
|
||||
"pydantic[email]>=2.10.6",
|
||||
"pydantic-settings>=2.8.1",
|
||||
"sqlalchemy>=2.0.38",
|
||||
"uvicorn[standard]>=0.34.0",
|
||||
"asyncpg>=0.30.0",
|
||||
"alembic>=1.14.0",
|
||||
"alembic>=1.15.1",
|
||||
"httpx>=0.28.1",
|
||||
"pytest>=8.3.4",
|
||||
"pytest>=8.3.5",
|
||||
"pytest-cov>=6.0.0",
|
||||
"uvloop>=0.21.0",
|
||||
"httptools>=0.6.4",
|
||||
"rich>=13.9.4",
|
||||
"pyjwt[cryptography]>=2.10.1",
|
||||
"pyjwt>=2.10.1",
|
||||
"redis>=5.2.1",
|
||||
"bcrypt>=4.2.1",
|
||||
"polars>=1.17.1",
|
||||
"bcrypt>=4.3.0",
|
||||
"polars>=1.24.0",
|
||||
"python-multipart>=0.0.20",
|
||||
"fastexcel>=0.12.0",
|
||||
"fastapi-cache2>=0.2.1",
|
||||
"fastexcel>=0.13.0",
|
||||
"inline-snapshot>=0.17.0",
|
||||
"dirty-equals>=0.8.0",
|
||||
"polyfactory>=2.18.1",
|
||||
"granian>=1.7.0",
|
||||
"apscheduler[redis,sqlalchemy]>=4.0.0a5",
|
||||
"pendulum @ git+https://github.com/sdispater/pendulum.git@develop"
|
||||
]
|
||||
|
||||
[tool.uv]
|
||||
dev-dependencies = [
|
||||
"ruff>=0.9.10",
|
||||
"devtools[pygments]>=0.12.2",
|
||||
"pyupgrade>=3.19.1",
|
||||
"ipython>=9.0.2",
|
||||
"sqlacodegen>=3.0.0",
|
||||
"tryceratops>=2.4.1",
|
||||
"locust>=2.33.0"
|
||||
|
||||
]
|
||||
|
||||
|
||||
[tool.mypy]
|
||||
strict = true
|
||||
exclude = ["venv", ".venv", "alembic"]
|
||||
|
||||
[tool.ruff]
|
||||
target-version = "py313"
|
||||
exclude = ["alembic"]
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
"E", # pycodestyle errors
|
||||
"W", # pycodestyle warnings
|
||||
"F", # pyflakes
|
||||
"I", # isort
|
||||
"B", # flake8-bugbear
|
||||
"C4", # flake8-comprehensions
|
||||
"UP", # pyupgrade
|
||||
"ARG001", # unused arguments in functions
|
||||
]
|
||||
ignore = [
|
||||
"E501", # line too long, handled by black
|
||||
"B008", # do not perform function calls in argument defaults
|
||||
"W191", # indentation contains tabs
|
||||
"B904", # Allow raising exceptions without from e, for HTTPException
|
||||
]
|
||||
|
||||
[tool.ruff.lint.pyupgrade]
|
||||
# Preserve types, even if a file imports `from __future__ import annotations`.
|
||||
keep-runtime-typing = true
|
@ -1,9 +1,9 @@
|
||||
import pytest
|
||||
from httpx import AsyncClient
|
||||
from starlette import status
|
||||
import jwt
|
||||
import pytest
|
||||
from dirty_equals import IsPositiveFloat, IsStr, IsUUID
|
||||
from httpx import AsyncClient
|
||||
from inline_snapshot import snapshot
|
||||
from dirty_equals import IsStr, IsUUID, IsPositiveFloat
|
||||
from starlette import status
|
||||
|
||||
pytestmark = pytest.mark.anyio
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
from anyio import Path
|
||||
|
||||
import pytest
|
||||
from anyio import Path
|
||||
from fastapi import status
|
||||
from httpx import AsyncClient
|
||||
|
||||
|
@ -1,9 +1,8 @@
|
||||
import pytest
|
||||
from dirty_equals import IsUUID
|
||||
from fastapi import status
|
||||
from httpx import AsyncClient
|
||||
from inline_snapshot import snapshot
|
||||
from dirty_equals import IsUUID
|
||||
|
||||
from polyfactory.factories.pydantic_factory import ModelFactory
|
||||
|
||||
from app.schemas.stuff import StuffSchema
|
||||
|
@ -1,5 +1,8 @@
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from httpx import AsyncClient, ASGITransport
|
||||
from httpx import ASGITransport, AsyncClient
|
||||
|
||||
from app.database import engine
|
||||
from app.main import app
|
||||
@ -28,13 +31,11 @@ async def start_db():
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
async def client(start_db) -> AsyncClient:
|
||||
|
||||
async def client(start_db) -> AsyncGenerator[AsyncClient, Any]: # noqa: ARG001
|
||||
transport = ASGITransport(
|
||||
app=app,
|
||||
)
|
||||
async with AsyncClient(
|
||||
# app=app,
|
||||
base_url="http://testserver/v1",
|
||||
headers={"Content-Type": "application/json"},
|
||||
transport=transport,
|
||||
|
Loading…
x
Reference in New Issue
Block a user