Skip to content

Commit 3f4f1ef

Browse files
authored
Merge pull request #197 from grillazz/195-switch-project-to-uv
2 parents e88f68e + b594dee commit 3f4f1ef

30 files changed

+750
-288
lines changed

.github/workflows/build-and-test.yml

+4
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ jobs:
5454
with:
5555
python-version: ${{ matrix.python-version }}
5656

57+
- name: Lint with ruff
58+
run: uv run --frozen ruff check .
59+
5760
- name: Test with python ${{ matrix.python-version }}
5861
run: uv run --frozen pytest
5962

63+

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ safety: ## Check project and dependencies with safety https://github.com/pyupio/
3939

4040
.PHONY: py-upgrade
4141
py-upgrade: ## Upgrade project py files with pyupgrade library for python version 3.10
42-
pyupgrade --py312-plus `find app -name "*.py"`
42+
pyupgrade --py313-plus `find app -name "*.py"`
4343

4444
.PHONY: lint
4545
lint: ## Lint project code.

app/api/health.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import logging
22
from typing import Annotated
33

4-
from fastapi import APIRouter, status, Request, Depends, Query
4+
from fastapi import APIRouter, Depends, Query, Request, status
55
from pydantic import EmailStr
66
from starlette.concurrency import run_in_threadpool
77

88
from app.services.smtp import SMTPEmailService
9-
109
from app.utils.logging import AppLogger
1110

1211
logger = AppLogger().get_logger()

app/api/nonsense.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import io
2-
from fastapi import APIRouter, Depends, status, UploadFile, HTTPException
3-
from sqlalchemy.exc import SQLAlchemyError
2+
43
import polars as pl
4+
from fastapi import APIRouter, Depends, HTTPException, UploadFile, status
5+
from sqlalchemy.exc import SQLAlchemyError
56
from sqlalchemy.ext.asyncio import AsyncSession
67

78
from app.database import get_db

app/api/shakespeare.py

-3
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
from fastapi import APIRouter, Depends, Query
44
from sqlalchemy.ext.asyncio import AsyncSession
55

6-
from fastapi_cache.decorator import cache
7-
86
from app.database import get_db
97
from app.models.shakespeare import Paragraph
108

@@ -14,7 +12,6 @@
1412
@router.get(
1513
"/",
1614
)
17-
@cache(namespace="test-2", expire=60)
1815
async def find_paragraph(
1916
character: Annotated[str, Query(description="Character name")],
2017
db_session: AsyncSession = Depends(get_db),

app/api/stuff.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from fastapi import APIRouter, Depends, HTTPException, status, Request
1+
from fastapi import APIRouter, Depends, HTTPException, Request, status
22
from sqlalchemy.exc import SQLAlchemyError
33
from sqlalchemy.ext.asyncio import AsyncSession
44

app/api/user.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
from typing import Annotated
22

3-
from fastapi import APIRouter, Depends, status, Request, HTTPException, Form
3+
from fastapi import APIRouter, Depends, Form, HTTPException, Request, status
44
from sqlalchemy.ext.asyncio import AsyncSession
55

66
from app.database import get_db
77
from app.models.user import User
8-
from app.schemas.user import UserSchema, UserResponse, UserLogin, TokenResponse
8+
from app.schemas.user import TokenResponse, UserLogin, UserResponse, UserSchema
99
from app.services.auth import create_access_token
1010
from app.utils.logging import AppLogger
1111

app/config.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import os
22

3-
from pydantic import PostgresDsn, RedisDsn, computed_field, BaseModel
3+
from pydantic import BaseModel, PostgresDsn, RedisDsn, computed_field
44
from pydantic_core import MultiHostUrl
55
from pydantic_settings import BaseSettings, SettingsConfigDict
66

app/database.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from collections.abc import AsyncGenerator
22

3-
from sqlalchemy.ext.asyncio import create_async_engine
4-
from sqlalchemy.ext.asyncio import async_sessionmaker
3+
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
54

65
from app.config import settings as global_settings
76
from app.utils.logging import AppLogger

app/main.py

+10-16
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,22 @@
1+
from contextlib import asynccontextmanager
2+
13
import asyncpg
2-
from apscheduler.eventbrokers.redis import RedisEventBroker
4+
from apscheduler import AsyncScheduler
35
from apscheduler.datastores.sqlalchemy import SQLAlchemyDataStore
4-
from fastapi import FastAPI, Depends
5-
from fastapi_cache import FastAPICache
6-
from fastapi_cache.backends.redis import RedisBackend
6+
from apscheduler.eventbrokers.redis import RedisEventBroker
7+
from fastapi import Depends, FastAPI
78

9+
from app.api.health import router as health_router
810
from app.api.nonsense import router as nonsense_router
911
from app.api.shakespeare import router as shakespeare_router
1012
from app.api.stuff import router as stuff_router
13+
from app.api.user import router as user_router
1114
from app.config import settings as global_settings
1215
from app.database import engine
13-
from app.utils.logging import AppLogger
14-
from app.api.user import router as user_router
15-
from app.api.health import router as health_router
16-
from app.redis import get_redis, get_cache
16+
from app.redis import get_redis
1717
from app.services.auth import AuthBearer
1818
from app.services.scheduler import SchedulerMiddleware
19-
20-
from contextlib import asynccontextmanager
21-
22-
from apscheduler import AsyncScheduler
19+
from app.utils.logging import AppLogger
2320

2421
logger = AppLogger().get_logger()
2522

@@ -32,10 +29,7 @@ async def lifespan(_app: FastAPI):
3229
_postgres_dsn = global_settings.postgres_url.unicode_string()
3330

3431
try:
35-
# Initialize the cache with the redis connection
36-
redis_cache = await get_cache()
37-
FastAPICache.init(RedisBackend(redis_cache), prefix="fastapi-cache")
38-
# logger.info(FastAPICache.get_cache_status_header())
32+
# TODO: cache with the redis connection
3933
# Initialize the postgres connection pool
4034
_app.postgres_pool = await asyncpg.create_pool(
4135
dsn=_postgres_dsn,

app/models/base.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
from asyncpg import UniqueViolationError
44
from fastapi import HTTPException, status
5-
from sqlalchemy.exc import SQLAlchemyError, IntegrityError
5+
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
66
from sqlalchemy.ext.asyncio import AsyncSession
7-
from sqlalchemy.orm import declared_attr, DeclarativeBase
7+
from sqlalchemy.orm import DeclarativeBase, declared_attr
8+
89
from app.utils.logging import AppLogger
910

1011
logger = AppLogger().get_logger()

app/models/nonsense.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from sqlalchemy import String, select
55
from sqlalchemy.dialects.postgresql import UUID
66
from sqlalchemy.ext.asyncio import AsyncSession
7-
from sqlalchemy.orm import mapped_column, Mapped
7+
from sqlalchemy.orm import Mapped, mapped_column
88

99
from app.models.base import Base
1010

app/models/shakespeare.py

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
)
1212
from sqlalchemy.ext.asyncio import AsyncSession
1313
from sqlalchemy.orm import Mapped, mapped_column, relationship
14+
1415
from app.models.base import Base
1516

1617

app/models/stuff.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import uuid
22

3-
from sqlalchemy import String, select, ForeignKey
3+
from sqlalchemy import ForeignKey, String, select
44
from sqlalchemy.dialects.postgresql import UUID
55
from sqlalchemy.ext.asyncio import AsyncSession
6-
from sqlalchemy.orm import mapped_column, Mapped, relationship, joinedload
6+
from sqlalchemy.orm import Mapped, joinedload, mapped_column, relationship
77

88
from app.models.base import Base
99
from app.models.nonsense import Nonsense

app/models/user.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33

44
import bcrypt
55
from pydantic import SecretStr
6-
from sqlalchemy import String, LargeBinary, select, Column
6+
from sqlalchemy import Column, LargeBinary, String, select
77
from sqlalchemy.dialects.postgresql import UUID
88
from sqlalchemy.ext.asyncio import AsyncSession
9-
from sqlalchemy.orm import mapped_column, Mapped
9+
from sqlalchemy.orm import Mapped, mapped_column
1010

1111
from app.models.base import Base
1212

app/schemas/nnonsense.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from uuid import UUID
22

3-
from pydantic import BaseModel, Field, ConfigDict
3+
from pydantic import BaseModel, ConfigDict, Field
44

55
config = ConfigDict(from_attributes=True)
66

app/schemas/stuff.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from uuid import UUID
22

3-
from pydantic import BaseModel, Field, ConfigDict
3+
from pydantic import BaseModel, ConfigDict, Field
44

55
config = ConfigDict(from_attributes=True)
66

app/schemas/user.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from uuid import UUID
22

3-
from pydantic import BaseModel, Field, EmailStr, ConfigDict, SecretStr
3+
from pydantic import BaseModel, ConfigDict, EmailStr, Field, SecretStr
44

55
config = ConfigDict(from_attributes=True)
66

app/services/auth.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import time
2+
23
import jwt
4+
from fastapi import HTTPException, Request
5+
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
36

47
from app.config import settings as global_settings
58
from app.models.user import User
6-
7-
from fastapi import Request, HTTPException
8-
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
99
from app.utils.logging import AppLogger
1010

1111
logger = AppLogger().get_logger()

app/services/scheduler.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
from datetime import datetime
22

3+
from apscheduler import AsyncScheduler
4+
from apscheduler.triggers.interval import IntervalTrigger
35
from attrs import define
4-
56
from sqlalchemy import text
67
from starlette.types import ASGIApp, Receive, Scope, Send
7-
from apscheduler import AsyncScheduler
8-
from apscheduler.triggers.interval import IntervalTrigger
98

109
from app.database import AsyncSessionFactory
1110
from app.utils.logging import AppLogger

app/services/smtp.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
1-
from attrs import define, field
21
import smtplib
32
from email.mime.multipart import MIMEMultipart
43
from email.mime.text import MIMEText
54

6-
from app.config import settings as global_settings
7-
5+
from attrs import define, field
86
from fastapi.templating import Jinja2Templates
9-
107
from pydantic import EmailStr
118

9+
from app.config import settings as global_settings
1210
from app.utils.logging import AppLogger
1311
from app.utils.singleton import SingletonMetaNoArgs
1412

15-
1613
logger = AppLogger().get_logger()
1714

1815

app/utils/decorators.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
from sqlalchemy.dialects import postgresql
2-
31
from functools import wraps
42

3+
from sqlalchemy.dialects import postgresql
4+
55

66
def compile_sql_or_scalar(func):
77
"""

app/utils/logging.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from rich.console import Console
44
from rich.logging import RichHandler
55

6-
76
from app.utils.singleton import SingletonMeta
87

98

@@ -21,5 +20,5 @@ class RichConsoleHandler(RichHandler):
2120
def __init__(self, width=200, style=None, **kwargs):
2221
super().__init__(
2322
console=Console(color_system="256", width=width, style=style, stderr=True),
24-
**kwargs
23+
**kwargs,
2524
)

performance/locustfile.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from locust import HttpUser, task, between
1+
from locust import HttpUser, between, task
22

33

44
class Stuff(HttpUser):

pyproject.toml

+54-13
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,71 @@ description = "A modern FastAPI application with SQLAlchemy 2.0 and AsyncPG for
55
readme = "README.md"
66
requires-python = ">=3.13"
77
dependencies = [
8-
"fastapi[all]>=0.115.6",
9-
"pydantic[email]>=2.10.3",
10-
"pydantic-settings>=2.7.0",
11-
"sqlalchemy>=2.0.36",
8+
"fastapi[all]>=0.115.11",
9+
"pydantic[email]>=2.10.6",
10+
"pydantic-settings>=2.8.1",
11+
"sqlalchemy>=2.0.38",
1212
"uvicorn[standard]>=0.34.0",
1313
"asyncpg>=0.30.0",
14-
"alembic>=1.14.0",
14+
"alembic>=1.15.1",
1515
"httpx>=0.28.1",
16-
"pytest>=8.3.4",
16+
"pytest>=8.3.5",
1717
"pytest-cov>=6.0.0",
1818
"uvloop>=0.21.0",
1919
"httptools>=0.6.4",
2020
"rich>=13.9.4",
21-
"pyjwt[cryptography]>=2.10.1",
21+
"pyjwt>=2.10.1",
2222
"redis>=5.2.1",
23-
"bcrypt>=4.2.1",
24-
"polars>=1.17.1",
23+
"bcrypt>=4.3.0",
24+
"polars>=1.24.0",
2525
"python-multipart>=0.0.20",
26-
"fastexcel>=0.12.0",
27-
"fastapi-cache2>=0.2.1",
26+
"fastexcel>=0.13.0",
2827
"inline-snapshot>=0.17.0",
2928
"dirty-equals>=0.8.0",
3029
"polyfactory>=2.18.1",
3130
"granian>=1.7.0",
3231
"apscheduler[redis,sqlalchemy]>=4.0.0a5",
33-
"pendulum @ git+https://github.com/sdispater/pendulum.git@develop"
34-
]
32+
]
33+
34+
[tool.uv]
35+
dev-dependencies = [
36+
"ruff>=0.9.10",
37+
"devtools[pygments]>=0.12.2",
38+
"pyupgrade>=3.19.1",
39+
"ipython>=9.0.2",
40+
"sqlacodegen>=3.0.0",
41+
"tryceratops>=2.4.1",
42+
"locust>=2.33.0"
43+
44+
]
45+
46+
47+
[tool.mypy]
48+
strict = true
49+
exclude = ["venv", ".venv", "alembic"]
50+
51+
[tool.ruff]
52+
target-version = "py313"
53+
exclude = ["alembic"]
54+
55+
[tool.ruff.lint]
56+
select = [
57+
"E", # pycodestyle errors
58+
"W", # pycodestyle warnings
59+
"F", # pyflakes
60+
"I", # isort
61+
"B", # flake8-bugbear
62+
"C4", # flake8-comprehensions
63+
"UP", # pyupgrade
64+
"ARG001", # unused arguments in functions
65+
]
66+
ignore = [
67+
"E501", # line too long, handled by black
68+
"B008", # do not perform function calls in argument defaults
69+
"W191", # indentation contains tabs
70+
"B904", # Allow raising exceptions without from e, for HTTPException
71+
]
72+
73+
[tool.ruff.lint.pyupgrade]
74+
# Preserve types, even if a file imports `from __future__ import annotations`.
75+
keep-runtime-typing = true

0 commit comments

Comments
 (0)