Python Backend Expert
alembic database migrations
When reviewing or writing code, apply these guidelines:
- Use alembic for database migrations.
django class based views for htmx
When reviewing or writing code, apply these guidelines:
- Use Django's class-based views for HTMX responses
django form handling
When reviewing or writing code, apply these guidelines:
-
Implement Django forms for form handling
-
Use Django's form validation for HTMX requests
django forms
When reviewing or writing code, apply these guidelines:
-
Utilize Django's form and model form classes for form handling and validation.
-
Use Django's validation framework to validate form and model data.
-
Keep business logic in models and forms; keep views light and focused on request handling.
django framework rules
When reviewing or writing code, apply these guidelines:
- You always use the latest stable version of Django, and you are familiar with the latest features and best practices.
django middleware
When reviewing or writing code, apply these guidelines:
-
Use middleware judiciously to handle cross-cutting concerns like authentication, logging, and caching.
-
Use Django’s middleware for common tasks such as authentication, logging, and security.
django middleware for request response
When reviewing or writing code, apply these guidelines:
- Utilize Django's middleware for request/response processing
django models
When reviewing or writing code, apply these guidelines:
-
Leverage Django’s ORM for database interactions; avoid raw SQL queries unless necessary for performance.
-
Keep business logic in models and forms; keep views light and focused on request handling.
django orm for database operations
When reviewing or writing code, apply these guidelines:
- Implement Django ORM for database operations
django rest framework
When reviewing or writing code, apply these guidelines:
- Use Django templates for rendering HTML and DRF serializers for JSON responses
django 5.x features (2025+)
When reviewing or writing code, apply these guidelines:
-
Django 5.2 is the current LTS (Long-Term Support) release; target it for new projects (supported until 2028)
-
Use database-computed default values via db_default on model fields (e.g., db_default=Now() ) instead of Python-side defaults where the database should own the value
-
Use facet filters in the Django admin (ModelAdmin.show_facets ) to get counts alongside filter options
-
Leverage improved async ORM support: Django 5.x expands async -native queryset methods — prefer await qs.acount() , await qs.afirst() , async for obj in qs in async views
-
Use declarative middleware configuration with MIDDLEWARE list; async-capable middleware is preferred for high-throughput ASGI deployments
-
Use LoginRequiredMiddleware (Django 5.1+) instead of decorating every view when all views require authentication
-
Use GeneratedField for database-generated columns (computed from other columns at the DB level)
fastapi patterns (2025+)
When reviewing or writing code, apply these guidelines:
Use the lifespan context manager (not deprecated @app.on_event ) for startup/shutdown resource management:
from contextlib import asynccontextmanager from fastapi import FastAPI
@asynccontextmanager async def lifespan(app: FastAPI): # startup: initialize DB pool, HTTP clients, caches app.state.db_pool = await create_pool() yield # shutdown: close resources await app.state.db_pool.close()
app = FastAPI(lifespan=lifespan)
Use Pydantic v2 models for all request/response schemas; Pydantic v2 is the default in FastAPI 0.100+. Use model_config = ConfigDict(...) instead of the inner class Config
Use pydantic-settings (BaseSettings ) with lru_cache for config management:
from functools import lru_cache from pydantic_settings import BaseSettings
class Settings(BaseSettings): database_url: str model_config = ConfigDict(env_prefix="APP_")
@lru_cache def get_settings() -> Settings: return Settings()
Scope dependencies correctly: per-request (DB sessions, auth), router-level (audit logging, namespace caches), application lifespan (Kafka producers, feature flag SDKs, tracing exporters)
Use Annotated type hints with Depends for cleaner dependency signatures:
from typing import Annotated from fastapi import Depends
DbSession = Annotated[AsyncSession, Depends(get_db)] CurrentUser = Annotated[User, Depends(get_current_user)]
Structure projects by domain: routers/ , services/ , repositories/ , schemas/ , models/ — avoid flat single-file apps beyond prototypes
Prefer async def path operations for I/O-bound routes; use def (sync) only for CPU-bound work that should run in a thread pool
Use APIRouter with prefix , tags , and dependencies to group related routes and apply shared middleware
sqlalchemy 2.0 async patterns (2025+)
When reviewing or writing code, apply these guidelines:
Use create_async_engine
- async_sessionmaker (not the deprecated AsyncSession factory directly); create one engine per service at application startup
Use the new Mapped
- mapped_column declarative style (SQLAlchemy 2.0+) instead of the legacy Column style:
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column from sqlalchemy import String
class Base(DeclarativeBase): pass
class User(Base): tablename = "users" id: Mapped[int] = mapped_column(primary_key=True) email: Mapped[str] = mapped_column(String(255), unique=True) is_active: Mapped[bool] = mapped_column(default=True)
Provide the DB session via FastAPI dependency injection using async with session scope:
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
async_session = async_sessionmaker(engine, expire_on_commit=False)
async def get_db() -> AsyncGenerator[AsyncSession, None]: async with async_session() as session: yield session
Use select() (not the legacy session.query() ) for all queries in SQLAlchemy 2.0+
Use selectinload / joinedload explicitly to avoid implicit lazy-load I/O in async contexts (lazy loading raises MissingGreenlet in async)
For upserts, use insert().on_conflict_do_update() (PostgreSQL) or the dialect-specific equivalent rather than separate select + update round trips
Use connection pool sizing appropriate for async: async drivers (asyncpg, aiomysql) need smaller pools than sync drivers; pool_size=5, max_overflow=10 is a safe default for moderate load
python 3.13 / 3.14 features (2025+)
When reviewing or writing code, apply these guidelines:
-
Python 3.13 (released Oct 2024) is the current stable release for production use; Python 3.14 (released Oct 2025) is also stable
-
Free-threaded mode (PEP 703, experimental in 3.13, maturing in 3.14): The GIL can be disabled with python3.13t (free-threaded build). Avoid assuming GIL protection for shared mutable state in new code targeting 3.13+; use explicit locks or thread-safe data structures. Do not enable free-threaded mode in production without thorough testing of all C extensions
-
Experimental JIT compiler (PEP 744, 3.13+): Opt-in with PYTHON_JIT=1 . Provides measurable speedups for tight loops and numeric code. No code changes needed; just be aware it exists for performance-sensitive services
-
Improved error messages (3.13+): Tracebacks are now syntax-highlighted in color by default. Error messages for common mistakes (typos in attribute names, missing imports) are significantly more descriptive — rely on them during debugging
-
Python 3.14 — Template strings / T-strings (PEP 750): New t"..." string literals that defer interpolation, useful for safe SQL/HTML construction without injection risk. Prefer T-strings over f-strings when building dynamic queries or HTML fragments
-
Python 3.14 — Deferred annotation evaluation (PEP 649): Annotations are now lazily evaluated by default (no more from future import annotations needed). This resolves forward-reference issues in type hints at zero runtime cost
-
Python 3.14 — Parallel subinterpreters: The interpreters stdlib module enables true parallelism via subinterpreters without disabling the GIL. Useful for CPU-bound workloads that previously required multiprocessing
-
Python 3.14 — Incremental garbage collector: Reduces GC pause times, improving latency consistency in long-running async services
-
Use pyproject.toml (not setup.py / requirements.txt alone) for all new projects; use uv or pip with pyproject.toml for reproducible dependency management
-
Always specify the minimum Python version in pyproject.toml requires-python field
Consolidated Skills
This expert skill consolidates 1 individual skills:
- python-backend-expert
Iron Laws
-
ALWAYS use the lifespan context manager for FastAPI startup/shutdown resource management — @app.on_event is deprecated and will be removed in a future release.
-
NEVER use session.query() in SQLAlchemy 2.0+ — use select() with the 2.0-style API; legacy query API will be removed.
-
ALWAYS use parameterized queries or the ORM for all database operations — never construct SQL with string interpolation or f-strings (SQL injection vector).
-
NEVER perform blocking I/O in async FastAPI routes — use async def with awaitable drivers or run_in_executor for blocking operations to avoid event loop starvation.
-
ALWAYS validate all request data at the boundary using Pydantic v2 models — never pass raw request dicts into business logic layers.
Anti-Patterns
Anti-Pattern Why It Fails Correct Approach
Using @app.on_event for startup/shutdown Deprecated in FastAPI; will break on version upgrade Use @asynccontextmanager with lifespan parameter
Using session.query() in SQLAlchemy 2.0+ Legacy query API is deprecated and will be removed Use select() statements with session.execute()
Building SQL strings with f-strings or % formatting SQL injection vulnerability; critical security flaw Use parameterized queries via ORM or text() with bound params
Calling blocking I/O directly in async def routes Blocks the entire event loop; causes cascading latency Use awaitable async drivers; loop.run_in_executor() for sync code
Putting business logic in FastAPI path functions Couples routing to logic; makes unit testing impossible Extract logic to service/repository layer; inject via Depends()
Memory Protocol (MANDATORY)
Before starting:
cat .claude/context/memory/learnings.md
After completing: Record any new patterns or exceptions discovered.
ASSUME INTERRUPTION: Your context may reset. If it's not in memory, it didn't happen.