diff --git a/app/api/audit_logs.py b/app/api/audit_logs.py index a4ed9d10..3f6fa9a7 100644 --- a/app/api/audit_logs.py +++ b/app/api/audit_logs.py @@ -7,7 +7,7 @@ Events are append-only — there are no update or delete endpoints. import logging from datetime import datetime -from typing import Any +from typing import Annotated, Any from fastapi import APIRouter, Depends, Query, Request from sqlalchemy.orm import Session @@ -20,20 +20,24 @@ logger = logging.getLogger(__name__) router = APIRouter() +# Module-level dependency singleton to satisfy Ruff B008 while maintaining default values for manual calls (e.g. in decorators). +_db_dep = Depends(get_db) +DbSession = Annotated[Session, _db_dep] + @router.get("/audit-logs") @require_login async def list_audit_logs( request: Request, - db: Session = Depends(get_db), - action: str | None = Query(None, description="Filter by action (exact match)"), - user: str | None = Query(None, description="Filter by username"), - resource_type: str | None = Query(None, description="Filter by resource type"), - severity: str | None = Query(None, description="Filter by severity level"), - since: datetime | None = Query(None, description="Only events at or after this ISO-8601 timestamp"), - until: datetime | None = Query(None, description="Only events at or before this ISO-8601 timestamp"), - limit: int = Query(50, ge=1, le=500, description="Max rows to return"), - offset: int = Query(0, ge=0, description="Rows to skip for pagination"), + db: DbSession = _db_dep, + action: Annotated[str | None, Query(description="Filter by action (exact match)")] = None, + user: Annotated[str | None, Query(description="Filter by username")] = None, + resource_type: Annotated[str | None, Query(description="Filter by resource type")] = None, + severity: Annotated[str | None, Query(description="Filter by severity level")] = None, + since: Annotated[datetime | None, Query(description="Only events at or after this ISO-8601 timestamp")] = None, + until: Annotated[datetime | None, Query(description="Only events at or before this ISO-8601 timestamp")] = None, + limit: Annotated[int, Query(ge=1, le=500, description="Max rows to return")] = 50, + offset: Annotated[int, Query(ge=0, description="Rows to skip for pagination")] = 0, ) -> dict[str, Any]: """Return audit log entries with optional filtering and pagination. @@ -71,7 +75,7 @@ async def list_audit_logs( @require_login async def list_distinct_actions( request: Request, - db: Session = Depends(get_db), + db: DbSession = _db_dep, ) -> list[str]: """Return the distinct action values present in the audit log.""" from app.models import AuditLog @@ -84,7 +88,7 @@ async def list_distinct_actions( @require_login async def list_distinct_users( request: Request, - db: Session = Depends(get_db), + db: DbSession = _db_dep, ) -> list[str]: """Return the distinct user values present in the audit log.""" from app.models import AuditLog diff --git a/test_b008.py b/test_b008.py new file mode 100644 index 00000000..654838dc --- /dev/null +++ b/test_b008.py @@ -0,0 +1,7 @@ +from typing import Annotated + +def Query(default, **kwargs): + return default + +def test_func(action: Annotated[str | None, Query(None, description="test")] = None): + pass diff --git a/test_b008_annotated.py b/test_b008_annotated.py new file mode 100644 index 00000000..bd153a44 --- /dev/null +++ b/test_b008_annotated.py @@ -0,0 +1,7 @@ +from typing import Annotated + +def Query(default=None, **kwargs): + return default + +def test_func(limit: Annotated[int, Query(50, ge=1)] = 50): + pass diff --git a/test_b008_annotated_default.py b/test_b008_annotated_default.py new file mode 100644 index 00000000..b7f185d4 --- /dev/null +++ b/test_b008_annotated_default.py @@ -0,0 +1,4 @@ +from typing import Annotated +def Query(default=None, **kwargs): return default +def test_func(limit: Annotated[int, Query(50, ge=1)] = 50): + pass diff --git a/test_b008_depends.py b/test_b008_depends.py new file mode 100644 index 00000000..fdb486ce --- /dev/null +++ b/test_b008_depends.py @@ -0,0 +1,6 @@ +def Depends(arg=None): + return arg +def get_db(): + pass +def test_func(db=Depends(get_db)): + pass diff --git a/test_b008_standard_fastapi.py b/test_b008_standard_fastapi.py new file mode 100644 index 00000000..159b0ccb --- /dev/null +++ b/test_b008_standard_fastapi.py @@ -0,0 +1,18 @@ +from typing import Annotated + +class Depends: + def __init__(self, dependency=None): + pass + +def get_db(): + pass + +DbSession = Annotated[int, Depends(get_db)] + +# This is what I want to use +def test_func_ok(db: DbSession = Depends()): + pass + +# This is what Ruff should flag +def test_func_bad(db: int = Depends(get_db)): + pass diff --git a/test_b008_standard_fastapi_2.py b/test_b008_standard_fastapi_2.py new file mode 100644 index 00000000..f4bb8c27 --- /dev/null +++ b/test_b008_standard_fastapi_2.py @@ -0,0 +1,14 @@ +from typing import Annotated + +class Depends: + def __init__(self, dependency=None): + pass + +def get_db(): + pass + +DbSession = Annotated[int, Depends(get_db)] + +# Cleanest Annotated pattern +def test_func_clean(db: DbSession): + pass diff --git a/test_ruff_annotated.py b/test_ruff_annotated.py new file mode 100644 index 00000000..d1dda02c --- /dev/null +++ b/test_ruff_annotated.py @@ -0,0 +1,16 @@ +from typing import Annotated + +def Query(default=None, **kwargs): + return default + +def Depends(dependency=None): + return dependency + +def get_db(): + return None + +def test_func( + db: Annotated[int, Depends(get_db)] = Depends(), + action: Annotated[str | None, Query(None, description="test")] = None +): + pass diff --git a/test_ruff_annotated_fastapi.py b/test_ruff_annotated_fastapi.py new file mode 100644 index 00000000..b19d42b1 --- /dev/null +++ b/test_ruff_annotated_fastapi.py @@ -0,0 +1,11 @@ +from typing import Annotated + +class Depends: + def __init__(self, dependency=None): + pass + +def get_db(): + pass + +def test_func(db: Annotated[int, Depends(get_db)] = Depends()): + pass diff --git a/test_ruff_annotated_metadata.py b/test_ruff_annotated_metadata.py new file mode 100644 index 00000000..e26b1068 --- /dev/null +++ b/test_ruff_annotated_metadata.py @@ -0,0 +1,4 @@ +from typing import Annotated +def Query(x=None, **kwargs): return x +def test_func(x: Annotated[str, Query(None, description="test")] = None): + pass diff --git a/test_ruff_annotated_none.py b/test_ruff_annotated_none.py new file mode 100644 index 00000000..f2b21fc1 --- /dev/null +++ b/test_ruff_annotated_none.py @@ -0,0 +1,6 @@ +from typing import Annotated +def Depends(x): return x +def get_db(): return "db" +DbSession = Annotated[str, Depends(get_db)] +def test_func(db: DbSession = None): + pass