Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
version: 2
updates:
- package-ecosystem: "uv"
directory: "/"
schedule:
interval: "weekly"
groups:
python-minor-patch:
update-types:
- "minor"
- "patch"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
groups:
actions:
update-types:
- "minor"
- "patch"
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: astral-sh/setup-uv@v7
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v9.0.0
- run: uv sync
- run: uv run ruff check .
- run: uv run ruff format --check .

test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: astral-sh/setup-uv@v7
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v9.0.0
- run: uv sync
- run: uv run pytest
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
fail_fast: true
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.6
rev: v0.16.0
hooks:
- id: ruff
- id: ruff-check
args: [--fix]
- id: ruff-format
- repo: local
Expand Down
2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.12
3.14
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
FROM python:3.12-slim
FROM python:3.14-slim

WORKDIR /app

# Install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
COPY --from=ghcr.io/astral-sh/uv:0.11.32 /uv /usr/local/bin/uv

# Copy dependency files
COPY pyproject.toml uv.lock* ./
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,9 @@ Superusers (`is_superuser=True`) bypass all role checks. Roles are read-only via
```python
from app.auth import require_role


@router.get("/admin-only")
async def admin_only(user: User = Depends(require_role("admin"))):
...
async def admin_only(user: User = Depends(require_role("admin"))): ...
```

### Security Features
Expand Down
4 changes: 2 additions & 2 deletions app/auth/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

async def get_user_db(
session: AsyncSession = Depends(get_async_session),
) -> AsyncGenerator[SQLAlchemyUserDatabase, None]:
) -> AsyncGenerator[SQLAlchemyUserDatabase]:
yield SQLAlchemyUserDatabase(session, User)


Expand Down Expand Up @@ -75,7 +75,7 @@ async def on_after_request_verify(self, user: User, token: str, request=None):

async def get_user_manager(
user_db: SQLAlchemyUserDatabase = Depends(get_user_db),
) -> AsyncGenerator[UserManager, None]:
) -> AsyncGenerator[UserManager]:
yield UserManager(user_db)


Expand Down
2 changes: 1 addition & 1 deletion app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def cors_origin_list(self) -> list[str]:
return [o.strip() for o in self.cors_origins.split(",") if o.strip()]

@model_validator(mode="after")
def validate_production_settings(self) -> "Settings":
def validate_production_settings(self) -> Settings:
if not self.is_development:
weak_secrets = {"change-me-in-production", "dev-secret-key-change-in-production", ""}
if self.secret_key in weak_secrets or len(self.secret_key) < 32:
Expand Down
2 changes: 1 addition & 1 deletion app/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class Base(DeclarativeBase):
)


async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
async def get_async_session() -> AsyncGenerator[AsyncSession]:
"""Dependency that provides an async database session."""
async with async_session_maker() as session:
yield session
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "api-template"
version = "0.1.0"
description = "FastAPI template with async PostgreSQL and JWT auth"
readme = "README.md"
requires-python = ">=3.12"
requires-python = ">=3.14"
dependencies = [
"fastapi>=0.135.0",
"uvicorn[standard]>=0.41.0",
Expand Down Expand Up @@ -41,7 +41,7 @@ testpaths = ["tests"]
asyncio_default_fixture_loop_scope = "function"

[tool.ruff]
target-version = "py312"
target-version = "py314"
line-length = 100

[tool.ruff.lint]
Expand Down
10 changes: 5 additions & 5 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ async def setup_database():
await conn.run_sync(Base.metadata.drop_all)


async def override_get_async_session() -> AsyncGenerator[AsyncSession, None]:
async def override_get_async_session() -> AsyncGenerator[AsyncSession]:
async with async_session_maker() as session:
yield session

Expand All @@ -36,7 +36,7 @@ async def override_get_async_session() -> AsyncGenerator[AsyncSession, None]:


@pytest.fixture
async def client() -> AsyncGenerator[AsyncClient, None]:
async def client() -> AsyncGenerator[AsyncClient]:
"""Async HTTP client for testing."""
async with AsyncClient(
transport=ASGITransport(app=app),
Expand All @@ -46,7 +46,7 @@ async def client() -> AsyncGenerator[AsyncClient, None]:


@pytest.fixture
async def session() -> AsyncGenerator[AsyncSession, None]:
async def session() -> AsyncGenerator[AsyncSession]:
"""Direct database session for test setup."""
async with async_session_maker() as session:
yield session
Expand Down Expand Up @@ -77,7 +77,7 @@ def superuser() -> User:


@pytest.fixture
async def auth_client(client: AsyncClient, test_user: User) -> AsyncGenerator[AsyncClient, None]:
async def auth_client(client: AsyncClient, test_user: User) -> AsyncGenerator[AsyncClient]:
"""Client that authenticates as test_user via dependency override."""
app.dependency_overrides[current_active_user] = lambda: test_user
try:
Expand All @@ -87,7 +87,7 @@ async def auth_client(client: AsyncClient, test_user: User) -> AsyncGenerator[As


@pytest.fixture
async def admin_client(client: AsyncClient, admin_user: User) -> AsyncGenerator[AsyncClient, None]:
async def admin_client(client: AsyncClient, admin_user: User) -> AsyncGenerator[AsyncClient]:
"""Client that authenticates as admin_user via dependency override."""
app.dependency_overrides[current_active_user] = lambda: admin_user
try:
Expand Down
Loading
Loading