mirror of
https://github.com/grillazz/fastapi-sqlalchemy-asyncpg.git
synced 2026-01-17 11:40:39 +03:00
Merge remote-tracking branch 'origin/switch-logger-to-rotoger' into switch-logger-to-rotoger
# Conflicts: # pyproject.toml # uv.lock
This commit is contained in:
2
.github/workflows/build-and-test.yml
vendored
2
.github/workflows/build-and-test.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [ "3.13" ]
|
python-version: [ "3.14" ]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
PYTHONDONTWRITEBYTECODE: 1
|
PYTHONDONTWRITEBYTECODE: 1
|
||||||
|
|||||||
35
Dockerfile
35
Dockerfile
@@ -1,32 +1,28 @@
|
|||||||
FROM ubuntu:oracular AS build
|
FROM ubuntu:25.10 AS base
|
||||||
|
|
||||||
RUN apt-get update -qy && apt-get install -qyy \
|
RUN apt-get update -qy \
|
||||||
|
&& apt-get install -qyy \
|
||||||
-o APT::Install-Recommends=false \
|
-o APT::Install-Recommends=false \
|
||||||
-o APT::Install-Suggests=false \
|
-o APT::Install-Suggests=false \
|
||||||
build-essential \
|
build-essential \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
python3-setuptools \
|
python3-setuptools \
|
||||||
python3.13-dev \
|
python3.14-dev
|
||||||
git
|
|
||||||
|
|
||||||
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
|
|
||||||
|
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
|
||||||
|
|
||||||
ENV UV_LINK_MODE=copy \
|
ENV UV_LINK_MODE=copy \
|
||||||
UV_COMPILE_BYTECODE=1 \
|
UV_COMPILE_BYTECODE=1 \
|
||||||
UV_PYTHON_DOWNLOADS=never \
|
UV_PYTHON=python3.14 \
|
||||||
UV_PYTHON=python3.13 \
|
|
||||||
UV_PROJECT_ENVIRONMENT=/panettone
|
UV_PROJECT_ENVIRONMENT=/panettone
|
||||||
|
|
||||||
COPY pyproject.toml /_lock/
|
COPY pyproject.toml /_lock/
|
||||||
COPY uv.lock /_lock/
|
COPY uv.lock /_lock/
|
||||||
|
|
||||||
RUN --mount=type=cache,target=/root/.cache
|
RUN cd /_lock && uv sync --locked --no-install-project
|
||||||
RUN cd /_lock && uv sync \
|
|
||||||
--locked \
|
|
||||||
--no-dev \
|
|
||||||
--no-install-project
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
FROM ubuntu:oracular
|
FROM ubuntu:25.10
|
||||||
|
|
||||||
ENV PATH=/panettone/bin:$PATH
|
ENV PATH=/panettone/bin:$PATH
|
||||||
|
|
||||||
@@ -38,15 +34,14 @@ STOPSIGNAL SIGINT
|
|||||||
RUN apt-get update -qy && apt-get install -qyy \
|
RUN apt-get update -qy && apt-get install -qyy \
|
||||||
-o APT::Install-Recommends=false \
|
-o APT::Install-Recommends=false \
|
||||||
-o APT::Install-Suggests=false \
|
-o APT::Install-Suggests=false \
|
||||||
python3.13 \
|
python3.14 \
|
||||||
libpython3.13 \
|
libpython3.14 \
|
||||||
libpcre3 \
|
libpcre3
|
||||||
libxml2
|
|
||||||
|
|
||||||
RUN apt-get clean
|
RUN apt-get clean
|
||||||
RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||||
|
|
||||||
COPY --from=build --chown=panettone:panettone /panettone /panettone
|
COPY --from=base --chown=panettone:panettone /panettone /panettone
|
||||||
|
|
||||||
USER panettone
|
USER panettone
|
||||||
WORKDIR /panettone
|
WORKDIR /panettone
|
||||||
@@ -57,7 +52,3 @@ COPY .env app/
|
|||||||
COPY alembic.ini /panettone/alembic.ini
|
COPY alembic.ini /panettone/alembic.ini
|
||||||
COPY /alembic/ /panettone/alembic/
|
COPY /alembic/ /panettone/alembic/
|
||||||
COPY pyproject.toml /panettone/pyproject.toml
|
COPY pyproject.toml /panettone/pyproject.toml
|
||||||
|
|
||||||
RUN python -V
|
|
||||||
RUN python -Im site
|
|
||||||
RUN python -Ic 'import uvicorn'
|
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -26,7 +26,7 @@ docker-up: ## Run project with compose
|
|||||||
docker-clean: ## Clean and reset project containers and volumes
|
docker-clean: ## Clean and reset project containers and volumes
|
||||||
docker compose down -v --remove-orphans | true
|
docker compose down -v --remove-orphans | true
|
||||||
docker compose rm -f | true
|
docker compose rm -f | true
|
||||||
docker volume rm panettone_postgres_data | true
|
docker volume ls -q | grep panettone_postgres_data | xargs -r docker volume rm | true
|
||||||
|
|
||||||
# ====================================================================================
|
# ====================================================================================
|
||||||
# DATABASE MIGRATIONS
|
# DATABASE MIGRATIONS
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<li><a href="#make-will-help-you">Make will help you</a></li>
|
<li><a href="#make-will-help-you">Make will help you</a></li>
|
||||||
<li><a href="#how-to-feed-database">How to feed database</a></li>
|
<li><a href="#how-to-feed-database">How to feed database</a></li>
|
||||||
<li><a href="#structured-&-asynchronous-logging-with-rotoger">Structured & Asynchronous Logging with Rotoger</a></li>
|
<li><a href="#structured-asynchronous-logging-with-rotoger">Structured & Asynchronous Logging with Rotoger</a></li>
|
||||||
<li><a href="#setup-user-auth">Setup user auth</a></li>
|
<li><a href="#setup-user-auth">Setup user auth</a></li>
|
||||||
<li><a href="#setup-local-env-with-uv">Setup local development with uv</a></li>
|
<li><a href="#setup-local-env-with-uv">Setup local development with uv</a></li>
|
||||||
<li><a href="#import-xlsx-files-with-polars-and-calamine">Import xlsx files with polars and calamine</a></li>
|
<li><a href="#import-xlsx-files-with-polars-and-calamine">Import xlsx files with polars and calamine</a></li>
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ from typing import Annotated
|
|||||||
|
|
||||||
from fastapi import APIRouter, Depends, Query, Request, status
|
from fastapi import APIRouter, Depends, Query, Request, status
|
||||||
from pydantic import EmailStr
|
from pydantic import EmailStr
|
||||||
from rotoger import Rotoger
|
from rotoger import get_logger
|
||||||
from starlette.concurrency import run_in_threadpool
|
from starlette.concurrency import run_in_threadpool
|
||||||
|
|
||||||
from app.services.smtp import SMTPEmailService
|
from app.services.smtp import SMTPEmailService
|
||||||
|
|
||||||
logger = Rotoger().get_logger()
|
logger = get_logger()
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ from typing import Annotated
|
|||||||
|
|
||||||
from fastapi import APIRouter, Depends, Form
|
from fastapi import APIRouter, Depends, Form
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from rotoger import Rotoger
|
from rotoger import get_logger
|
||||||
|
|
||||||
from app.services.llm import get_llm_service
|
from app.services.llm import get_llm_service
|
||||||
|
|
||||||
logger = Rotoger().get_logger()
|
logger = get_logger()
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||||
from rotoger import Rotoger
|
from rotoger import get_logger
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ from app.models.stuff import RandomStuff, Stuff
|
|||||||
from app.schemas.stuff import RandomStuff as RandomStuffSchema
|
from app.schemas.stuff import RandomStuff as RandomStuffSchema
|
||||||
from app.schemas.stuff import StuffResponse, StuffSchema
|
from app.schemas.stuff import StuffResponse, StuffSchema
|
||||||
|
|
||||||
logger = Rotoger().get_logger()
|
logger = get_logger()
|
||||||
|
|
||||||
router = APIRouter(prefix="/v1/stuff")
|
router = APIRouter(prefix="/v1/stuff")
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Form, HTTPException, Request, status
|
from fastapi import APIRouter, Depends, Form, HTTPException, Request, status
|
||||||
from rotoger import Rotoger
|
from rotoger import get_logger
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.database import get_db
|
from app.database import get_db
|
||||||
@@ -9,7 +9,7 @@ from app.models.user import User
|
|||||||
from app.schemas.user import TokenResponse, UserLogin, UserResponse, UserSchema
|
from app.schemas.user import TokenResponse, UserLogin, UserResponse, UserSchema
|
||||||
from app.services.auth import create_access_token
|
from app.services.auth import create_access_token
|
||||||
|
|
||||||
logger = Rotoger().get_logger()
|
logger = get_logger()
|
||||||
|
|
||||||
router = APIRouter(prefix="/v1/user")
|
router = APIRouter(prefix="/v1/user")
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
|
|
||||||
from fastapi.exceptions import ResponseValidationError
|
from fastapi.exceptions import ResponseValidationError
|
||||||
from rotoger import Rotoger
|
from rotoger import get_logger
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
||||||
|
|
||||||
from app.config import settings as global_settings
|
from app.config import settings as global_settings
|
||||||
|
|
||||||
logger = Rotoger().get_logger()
|
logger = get_logger()
|
||||||
|
|
||||||
engine = create_async_engine(
|
engine = create_async_engine(
|
||||||
global_settings.asyncpg_url.unicode_string(),
|
global_settings.asyncpg_url.unicode_string(),
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import orjson
|
import orjson
|
||||||
from attrs import define, field
|
from attrs import define, field
|
||||||
from fastapi import Request
|
from fastapi import Request
|
||||||
from rotoger import Rotoger
|
from rotoger import get_logger
|
||||||
|
|
||||||
logger = Rotoger().get_logger()
|
logger = get_logger()
|
||||||
|
|
||||||
|
|
||||||
@define(slots=True)
|
@define(slots=True)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import asyncpg
|
|||||||
from fastapi import Depends, FastAPI, Request
|
from fastapi import Depends, FastAPI, Request
|
||||||
from fastapi.responses import HTMLResponse
|
from fastapi.responses import HTMLResponse
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
from rotoger import Rotoger
|
from rotoger import get_logger
|
||||||
|
|
||||||
from app.api.health import router as health_router
|
from app.api.health import router as health_router
|
||||||
from app.api.ml import router as ml_router
|
from app.api.ml import router as ml_router
|
||||||
@@ -18,7 +18,7 @@ from app.exception_handlers import register_exception_handlers
|
|||||||
from app.redis import get_redis
|
from app.redis import get_redis
|
||||||
from app.services.auth import AuthBearer
|
from app.services.auth import AuthBearer
|
||||||
|
|
||||||
logger = Rotoger().get_logger()
|
logger = get_logger()
|
||||||
templates = Jinja2Templates(directory=Path(__file__).parent.parent / "templates")
|
templates = Jinja2Templates(directory=Path(__file__).parent.parent / "templates")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ from typing import Any
|
|||||||
|
|
||||||
from asyncpg import UniqueViolationError
|
from asyncpg import UniqueViolationError
|
||||||
from fastapi import HTTPException, status
|
from fastapi import HTTPException, status
|
||||||
from rotoger import Rotoger
|
from rotoger import get_logger
|
||||||
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
|
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm import DeclarativeBase, declared_attr
|
from sqlalchemy.orm import DeclarativeBase, declared_attr
|
||||||
|
|
||||||
logger = Rotoger().get_logger()
|
logger = get_logger()
|
||||||
|
|
||||||
|
|
||||||
class Base(DeclarativeBase):
|
class Base(DeclarativeBase):
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ import time
|
|||||||
import jwt
|
import jwt
|
||||||
from fastapi import HTTPException, Request
|
from fastapi import HTTPException, Request
|
||||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||||
from rotoger import Rotoger
|
from rotoger import get_logger
|
||||||
|
|
||||||
from app.config import settings as global_settings
|
from app.config import settings as global_settings
|
||||||
from app.models.user import User
|
from app.models.user import User
|
||||||
|
|
||||||
logger = Rotoger().get_logger()
|
logger = get_logger()
|
||||||
|
|
||||||
|
|
||||||
async def get_from_redis(request: Request, key: str):
|
async def get_from_redis(request: Request, key: str):
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ from datetime import datetime
|
|||||||
from apscheduler import AsyncScheduler
|
from apscheduler import AsyncScheduler
|
||||||
from apscheduler.triggers.interval import IntervalTrigger
|
from apscheduler.triggers.interval import IntervalTrigger
|
||||||
from attrs import define
|
from attrs import define
|
||||||
|
from rotoger import get_logger
|
||||||
from sqlalchemy import text
|
from sqlalchemy import text
|
||||||
from starlette.types import ASGIApp, Receive, Scope, Send
|
from starlette.types import ASGIApp, Receive, Scope, Send
|
||||||
|
|
||||||
from app.database import AsyncSessionFactory
|
from app.database import AsyncSessionFactory
|
||||||
from app.utils.logging import AppLogger
|
|
||||||
|
|
||||||
logger = AppLogger().get_logger()
|
logger = get_logger()
|
||||||
|
|
||||||
|
|
||||||
async def tick():
|
async def tick():
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ from email.mime.text import MIMEText
|
|||||||
from attrs import define, field
|
from attrs import define, field
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
from pydantic import EmailStr
|
from pydantic import EmailStr
|
||||||
from rotoger import Rotoger
|
from rotoger import get_logger
|
||||||
|
|
||||||
from app.config import settings as global_settings
|
from app.config import settings as global_settings
|
||||||
from app.utils.singleton import SingletonMetaNoArgs
|
from app.utils.singleton import SingletonMetaNoArgs
|
||||||
|
|
||||||
logger = Rotoger().get_logger()
|
logger = get_logger()
|
||||||
|
|
||||||
|
|
||||||
@define
|
@define
|
||||||
|
|||||||
@@ -20,8 +20,10 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
postgres:
|
||||||
- redis
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_started
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
container_name: panettone_postgres
|
container_name: panettone_postgres
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
[tool.poetry]
|
|
||||||
name = "fastapi-sqlalchemy-asyncpg"
|
|
||||||
version = "0.0.17"
|
|
||||||
description = ""
|
|
||||||
authors = ["Jakub Miazek <the@grillazz.com>"]
|
|
||||||
packages = []
|
|
||||||
license = "MIT"
|
|
||||||
package-mode = false
|
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
|
||||||
python = "^3.13"
|
|
||||||
fastapi = {version = "^0.115.6", extras = ["all"]}
|
|
||||||
pydantic = {version = "^2.10.3", extras = ["email"]}
|
|
||||||
pydantic-settings = "^2.7.0"
|
|
||||||
sqlalchemy = "^2.0.36"
|
|
||||||
uvicorn = { version = "^0.34.0", extras = ["standard"]}
|
|
||||||
asyncpg = "^0.30.0"
|
|
||||||
alembic = "^1.14.0"
|
|
||||||
httpx = "^0.28.1"
|
|
||||||
pytest = "^8.3.4"
|
|
||||||
pytest-cov = "^6.0.0"
|
|
||||||
uvloop = "^0.21.0"
|
|
||||||
httptools = "^0.6.4"
|
|
||||||
rich = "^13.9.4"
|
|
||||||
pyjwt = {version = "^2.10.1", extras = ["cryptography"]}
|
|
||||||
redis = "^5.2.1"
|
|
||||||
bcrypt = "^4.2.1"
|
|
||||||
polars = "^1.17.1"
|
|
||||||
python-multipart = "^0.0.20"
|
|
||||||
fastexcel = "^0.12.0"
|
|
||||||
fastapi-cache2 = "^0.2.1"
|
|
||||||
inline-snapshot = "^0.17.0"
|
|
||||||
dirty-equals = "^0.8.0"
|
|
||||||
polyfactory = "^2.18.1"
|
|
||||||
granian = "^1.7.0"
|
|
||||||
apscheduler = {version = "^4.0.0a5", extras = ["redis,sqlalchemy"]}
|
|
||||||
pendulum = {git = "https://github.com/sdispater/pendulum.git", rev="develop"}
|
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
|
||||||
devtools = { extras = ["pygments"], version = "^0.12.2" }
|
|
||||||
safety = "*"
|
|
||||||
pyupgrade = "*"
|
|
||||||
ipython = "^8.26.0"
|
|
||||||
ruff = "^0.6.1"
|
|
||||||
sqlacodegen = "^3.0.0rc5"
|
|
||||||
tryceratops = "^2.3.3"
|
|
||||||
locust = "^2.31.3"
|
|
||||||
|
|
||||||
[build-system]
|
|
||||||
requires = ["poetry-core>=1.0.0"]
|
|
||||||
build-backend = "poetry.core.masonry.api"
|
|
||||||
|
|
||||||
[tool.ruff]
|
|
||||||
line-length = 120
|
|
||||||
indent-width = 4
|
|
||||||
|
|
||||||
lint.select = ["E", "F", "UP", "N", "C", "B"]
|
|
||||||
lint.ignore = ["E501"]
|
|
||||||
|
|
||||||
# Exclude a variety of commonly ignored directories.
|
|
||||||
exclude = ["alembic",]
|
|
||||||
# Assume Python 3.13
|
|
||||||
target-version = "py313"
|
|
||||||
|
|
||||||
[tool.ruff.lint.flake8-quotes]
|
|
||||||
docstring-quotes = "double"
|
|
||||||
|
|
||||||
[tool.ruff.lint.flake8-bugbear]
|
|
||||||
extend-immutable-calls = ["fastapi.Depends",]
|
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
|
||||||
addopts = "-v --doctest-modules --doctest-glob=*.md --ignore=alembic"
|
|
||||||
asyncio_mode = "strict"
|
|
||||||
env_files = [".env"]
|
|
||||||
|
|
||||||
[tool.tryceratops]
|
|
||||||
exclude = ["alembic",]
|
|
||||||
|
|
||||||
[tool.ruff.format]
|
|
||||||
# Like Black, use double quotes for strings.
|
|
||||||
quote-style = "double"
|
|
||||||
|
|
||||||
# Like Black, indent with spaces, rather than tabs.
|
|
||||||
indent-style = "space"
|
|
||||||
|
|
||||||
# Like Black, respect magic trailing commas.
|
|
||||||
skip-magic-trailing-comma = false
|
|
||||||
|
|
||||||
# Like Black, automatically detect the appropriate line ending.
|
|
||||||
line-ending = "auto"
|
|
||||||
Reference in New Issue
Block a user