Merge remote-tracking branch 'origin/switch-logger-to-rotoger' into switch-logger-to-rotoger

# Conflicts:
#	pyproject.toml
#	uv.lock
This commit is contained in:
grillazz
2025-11-16 15:18:11 +01:00
17 changed files with 43 additions and 140 deletions

View File

@@ -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

View File

@@ -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
@@ -56,8 +51,4 @@ COPY /templates/ templates/
COPY .env app/ 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'

View File

@@ -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

View File

@@ -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>

View File

@@ -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()

View File

@@ -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()

View File

@@ -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")

View File

@@ -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")

View File

@@ -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(),

View File

@@ -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)

View File

@@ -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")

View File

@@ -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):

View File

@@ -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):

View File

@@ -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():

View File

@@ -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

View File

@@ -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

View File

@@ -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"