Python
Python rules and best practices for Claude Code.
24.4k
15 TEMPLATES
Modern Python Rules
# CLAUDE.md — Modern Python ## Tooling - **uv** for environments and dependency management. No pip, poetry, or pipenv. - **ruff** for linting and import sorting. Replaces flake8, isort, pyupgrade, pylint. - **ruff format** for code formatting. No black. - **mypy** or **pyright** in strict mode for type checking. Pick one and enforce in CI. - **pytest** for tests. No unittest unless you're maintaining a legacy suite. ## Python version - Target Python 3.12+ unless you have a hard reason not to. - Use `match`/`case` for exhaustive dispatch where it improves clarity. - Use `pathlib.Path` for filesystem work — never `os.path.join` in new code. ## Type hints - Type every function parameter and return. `-> None` is required for procedures. - Modern syntax: `list[int]` not `List[int]`, `dict[str, int]` not `Dict`, `X | Y` not `Union[X, Y]`, `X | None` not `Optional[X]`. - `from __future__ import annotations` at the top of every module so types are lazily evaluated. - Use `TypedDict` or `dataclass` for structured dicts. `Protocol` for structural interfaces. ## Project layout ``` src/your_package/ __init__.py domain/ services/ api/ tests/ pyproject.toml README.md ``` - `src/` layout, not flat. Stops accidental imports of in-tree code over installed code. - `__init__.py` is empty unless re-exporting a small public surface. ## Dataclasses & validation - `@dataclass(frozen=True, slots=True)` for value objects. - Pydantic for I/O boundary types (request bodies, config, deserialization). - Don't use Pydantic for internal-only domain types — it's heavier than needed. ## Errors - Custom exceptions inherit from a base `<App>Error`. Never raise bare `Exception`. - Catch the narrowest exception possible. Bare `except:` is a bug. - Don't use exceptions for control flow (`StopIteration` is the rare exception). ## Logging - `logging.getLogger(__name__)` per module. Never `print` in library code. - Structured logs in JSON for services. Plain text for CLIs. - Don't log secrets. Add a redaction filter if there's any risk. ## Don't - Don't shadow built-ins (`list`, `dict`, `id`, `type`). - Don't mutate default arguments. Use `None` and a fresh value inside the function. - Don't rely on dict ordering for correctness — it's stable but a code smell. - Don't ship code with `# type: ignore` unless the line above explains why.FastAPI + Pydantic + SQLAlchemy
# CLAUDE.md — FastAPI + Pydantic + SQLAlchemy ## Project layout ``` src/myapp/ api/ routers/ # one file per resource deps.py # shared FastAPI dependencies core/ config.py # pydantic-settings security.py db/ base.py # declarative_base session.py # engine + sessionmaker models/ # SQLAlchemy ORM models schemas/ # Pydantic models for I/O services/ # business logic main.py # app = FastAPI(); include_router(...) ``` - Models, schemas, and services have separate files. Don't smush them into one `models.py`. ## Pydantic v2 - Use Pydantic v2. `BaseModel` only — no v1 syntax. - One schema per direction: `UserCreate`, `UserUpdate`, `UserRead`. Never reuse a "User" schema across all three. - `model_config = ConfigDict(from_attributes=True)` on read schemas to read from ORM objects. - Validate at the schema. `@field_validator` for single-field rules; `@model_validator` for cross-field. ## SQLAlchemy 2.0 - Use the `Mapped[...]` typed style. No legacy `Column(...)` declarations. ```python class User(Base): __tablename__ = "users" id: Mapped[int] = mapped_column(primary_key=True) email: Mapped[str] = mapped_column(unique=True) ``` - Use `select(...)` and the modern execute API. No `session.query(...)`. - Sessions are scoped to a request via a FastAPI dependency: ```python def get_db() -> Iterator[Session]: with SessionLocal() as session: yield session ``` - Commit at the boundary (the endpoint or service), never inside a repository helper. ## Dependency injection - FastAPI `Depends` is your DI. Use it for db sessions, auth users, settings, feature flags. - One `deps.py` per app for cross-cutting deps. Per-router deps stay in the router. - Don't create dependencies that have side effects (logging a metric is fine; mutating state is not). ## Routers - One router per resource. `prefix="/users"`, `tags=["users"]`. - Endpoints are thin: parse → call service → return. No business logic in the route function. - `response_model=UserRead` on every endpoint that returns data. FastAPI strips fields not in the schema. ## Errors - Raise `HTTPException` for known failure modes with the right status code. - Custom exception classes inherit from a base; register handlers via `@app.exception_handler(...)`. - Don't return error dicts from endpoints. Raise. ## Don't - Don't share a Session across requests. Use `Depends(get_db)`. - Don't run schema migrations in app startup. Use Alembic and a separate migration step. - Don't put SQL queries in routers. Wrap them in a repository or service. - Don't expose ORM models directly from endpoints. Always go through a `*Read` Pydantic schema.Django + DRF Rules
# CLAUDE.md — Django + DRF ## Apps - One Django app per bounded context. Don't make a single `core` app that owns everything. - App layout: ``` myapp/ models.py serializers.py views.py # ViewSets / APIViews urls.py permissions.py services.py # business logic selectors.py # read-only queries tests/ ``` - Business logic lives in `services.py` (writes) and `selectors.py` (reads). Views call services. Models stay thin. ## Serializers - One serializer per output shape. Don't reuse the same serializer for list and detail when fields differ — add a `ListSerializer` and `DetailSerializer`. - Validate at the serializer (`validate_<field>`, `validate`). Don't validate inside views. - Use `source=` to map field names; don't rename in the model just to please an API contract. - For nested writes, override `create`/`update` explicitly. Don't rely on `WritableNestedSerializer` magic. ## ViewSets - `ModelViewSet` for full CRUD. `ReadOnlyModelViewSet` for read-only resources. Don't use generic mixins by hand when a ViewSet does it. - Override `get_queryset` to scope by user/tenant — the base `queryset` attribute should be the unscoped default. - Override `get_serializer_class` to switch between list/detail/write serializers. - Custom actions with `@action(detail=True/False, methods=[...])` — never overload PATCH for "do this thing". ## Permissions - Default to `[IsAuthenticated]` globally in `REST_FRAMEWORK['DEFAULT_PERMISSION_CLASSES']`. - Per-view permissions are subclasses of `BasePermission`. Combine with `&`, `|`, `~`. - Object-level checks go in `has_object_permission`. Don't duplicate them in `get_queryset` and pretend it's enough — it isn't for direct lookups. ## Pagination & filtering - Set `DEFAULT_PAGINATION_CLASS` and `PAGE_SIZE`. Never return unpaginated lists from production endpoints. - Use `django-filter` for query-string filters. Don't write `if request.GET.get(...)` chains. - For sort, use DRF's `OrderingFilter` with an explicit `ordering_fields` allowlist. ## Errors - Raise DRF exceptions (`NotFound`, `PermissionDenied`, `ValidationError`). Don't return `Response({'error': ...}, status=...)` from view code. - Custom exceptions inherit from `APIException` with `status_code` and `default_detail`. ## Don't - Don't put business logic in the serializer. Serializers shape and validate; services act. - Don't use `select_related` / `prefetch_related` only on the detail endpoint. Profile the list endpoint. - Don't expose internal field names (`is_deleted`, `internal_notes`) without filtering with serializer `fields` or `exclude`. - Don't disable CSRF on session-authenticated endpoints.Python Data Science (pandas + numpy)
# CLAUDE.md — Python Data Science (pandas + numpy) ## Notebook discipline - Notebooks are for exploration. Anything reusable lives in `.py` files importable from the notebook. - Cells run top-to-bottom. Restart and run all before claiming a result. - Clear outputs before committing notebooks (`jupyter nbconvert --clear-output --inplace`) — or use `nbstripout`. - One question per notebook. When a notebook gets to "and also...", split it. ## pandas - Always read with explicit dtypes (`dtype={...}`) when you know the schema. Auto-detection is slow and wrong on edge rows. - Prefer column operations over `.iterrows()`. If you reach for a row loop, you're losing the vectorization win. - Use `.loc[]` for label-based indexing and `.iloc[]` for positional. Never chain `[col][row]`. - `df.assign(new_col=...)` instead of `df['new_col'] = ...` when chaining. It returns a new frame and reads top-to-bottom. - `df.merge(...)` over `pd.merge(df, other)` — it's the same call but reads better. - Set `pd.options.mode.copy_on_write = True` on new code to avoid the SettingWithCopyWarning class of bugs. ## Schemas - Validate input frames with `pandera` (or assertions) at the boundary. A 5-line schema catches an hour of debugging. - Document expected columns in a docstring or a constant — don't make readers grep. ## numpy - Vectorize. Loops over numpy arrays are almost always wrong. - Be explicit about dtype on creation: `np.zeros(n, dtype=np.float32)`. Defaults vary across platforms. - `np.einsum` for unusual reductions — once you learn it, it replaces dozens of one-off helpers. ## Reproducibility - Pin random seeds in any non-deterministic step. Use `np.random.default_rng(seed)` — never the legacy `np.random.seed`. - Save intermediate frames to `parquet`, not CSV — preserves dtypes and is much faster. - Track versions: a small `pyproject.toml` or `requirements.txt` next to the notebook is enough. ## Plotting - Matplotlib for everything reproducible. Plotly for interactive dashboards. - Always set `figsize`, `xlabel`, `ylabel`, `title`. A plot without labels is debug output, not a result. ## Don't - Don't store secrets, API keys, or DB credentials in notebooks. Use a `.env` and `python-dotenv`. - Don't pickle dataframes for long-term storage. Use parquet. - Don't run analysis on `head()` and ship without re-running on the full data. The shape of small samples lies. - Don't trust auto-inferred datetime parsing. Specify `format=` or use `pd.to_datetime(..., utc=True)` explicitly.FastAPI Async + Postgres
# CLAUDE.md — FastAPI Async + Postgres ## Async stack - Driver: **asyncpg** (fastest, async-native). - ORM: SQLAlchemy 2.0 with `AsyncEngine` + `AsyncSession`. Or **SQLModel** if you want Pydantic-flavored models. - Migrations: **Alembic** in async mode (`async def run_migrations_online`). ## Engine & sessions ```python engine = create_async_engine( settings.DATABASE_URL, pool_size=10, max_overflow=10, pool_pre_ping=True, ) SessionLocal = async_sessionmaker(engine, expire_on_commit=False) ``` - `expire_on_commit=False` so attributes stay accessible after commit. - Pool sizing: start with `pool_size = workers × concurrent_requests`. Adjust under load. - One async session per request, via a FastAPI dependency: ```python async def get_db() -> AsyncIterator[AsyncSession]: async with SessionLocal() as session: yield session ``` ## Async patterns - Every endpoint is `async def`. Sync defs run in a thread pool — usually wrong here. - Every DB call: `await session.execute(stmt)`. Forgetting `await` returns a coroutine and a confusing error. - Use `asyncio.gather` to run independent reads in parallel: ```python user, orders = await asyncio.gather(get_user(id), get_orders(id)) ``` ## Connection pooling - For serverless / many short-lived containers, use **pgbouncer** in transaction mode in front of Postgres. Configure SQLAlchemy with `pool_pre_ping=True` and `connect_args={"prepared_statement_cache_size": 0}` for asyncpg. - For long-lived processes (k8s deploy), pool inside the app with `pool_size`. Don't double-pool. - `DATABASE_URL` for the pooled URL; `DIRECT_URL` for migrations (Alembic needs direct). ## Transactions - Begin a transaction at the boundary, not per-statement: ```python async with session.begin(): ... # one atomic unit ``` - Read-only endpoints don't need an explicit transaction. - Don't catch and swallow `IntegrityError` — let it propagate, map to `HTTPException(409)` at the boundary. ## Migrations - Alembic with `revision --autogenerate` then **always review** the diff. Autogenerate misses constraints, indexes, and enum changes. - Long migrations run as a separate job, not at app boot. - Backward-compatible deploys: add nullable column → deploy code → backfill → make NOT NULL. ## Performance - Add indexes for every WHERE/ORDER column you actually query. - Use `selectinload` / `joinedload` to avoid N+1 — pick one per relationship and stick with it. - For bulk inserts, use `session.execute(insert(Model), [...])`. Don't loop `session.add`. ## Don't - Don't mix sync and async sessions. Pick one path; convert legacy code, don't bridge. - Don't share an `AsyncSession` across coroutines. One per task. - Don't use the default psycopg2 sync driver in an async app. It blocks. - Don't run `await` calls in `__init__`. Use an async factory or lifespan.Django Models + ORM Discipline
# CLAUDE.md — Django Models + ORM ## Model design - One model per concept. Don't pile unrelated fields onto a single `User` or `Order`. - Required fields have no default. `null=False, blank=False` is the default — keep it that way unless you mean otherwise. - `null=True` only on database NULL columns. For string fields, use `blank=True` and `default=''` instead. - Use `models.UUIDField(primary_key=True, default=uuid.uuid4)` when IDs leak to clients. Sequential IDs invite enumeration. - Always set `verbose_name` for fields users will see in admin or generated docs. ## Migrations - One migration per logical change. Never edit a migration after it's merged — add a new one. - `makemigrations --dry-run` before committing. Confirm the diff is what you expected. - Long-running data migrations go in a `RunPython` op with `atomic=False` and batched updates. Don't lock production for an hour. - Always provide a reverse migration where possible. `RunPython.noop` is fine when the change is one-way. ## Querysets - Use `select_related('fk_field')` for forward FKs and one-to-ones. `prefetch_related('reverse_set')` for reverse FKs and M2M. - Profile every list endpoint with `django-debug-toolbar` or `django-silk`. N+1 queries are the most common Django bug. - `only()` and `defer()` to fetch partial rows when the table is wide. - `iterator()` for batch reads that don't fit in memory. `bulk_create` and `bulk_update` for batch writes. ## Custom managers - Custom `Manager` for table-wide concerns (soft delete, tenant filtering). - Custom `QuerySet` for chainable filters: `objects = MyQuerySet.as_manager()`. - Encode invariants here, not in views. `Order.objects.unpaid()` is clearer than `Order.objects.filter(paid=False)` scattered everywhere. ## Indexes & constraints - Add `models.Index` for fields you filter or order by frequently. Define them in `Meta.indexes`. - `UniqueConstraint` for compound uniqueness — `unique_together` is deprecated. - `CheckConstraint` for invariants (e.g., `quantity__gte=0`). Better than a `clean()` method that runs sometimes. ## Soft delete - If you need it, override the default manager with `objects = SoftDeleteManager()` and add `all_objects = models.Manager()` for admin. - Add a `deleted_at: DateTimeField(null=True)`. Don't add a boolean — timestamps tell you when. - Don't soft-delete by default for everything. Most data should be hard-deleted. ## Don't - Don't raise inside `save()`. Validate at the form/serializer. - Don't put business logic in `pre_save` signals. They run in spooky places. Use explicit service functions. - Don't fetch a row, mutate it, and `save()` in a loop. Use `update()` or `bulk_update()`. - Don't disable `atomic_requests` to "fix" a deadlock. Find the actual lock contention.FastAPI JWT Authentication
# CLAUDE.md — FastAPI JWT Authentication ## Tokens - Two tokens: **access token** (short-lived, ~15 min) + **refresh token** (longer, ~7–30 days). - Sign with HS256 if you have one service. Switch to RS256 if you have multiple services that need to verify without sharing the signing key. - Library: **python-jose** or **PyJWT**. Either is fine — pick one. - Claims: `sub` (user id), `iat`, `exp`, `jti` (for revocation), and a `type` claim distinguishing access vs refresh. ## Storage on the client - Web: HTTP-only, Secure, SameSite=Lax cookies for the refresh token. Access token in memory only. - Mobile / native: secure storage (Keychain, EncryptedSharedPreferences). Never plain disk. - Don't put tokens in `localStorage`. XSS reads it. ## Password hashing - **bcrypt** via `passlib` or **argon2** via `argon2-cffi`. Argon2 if starting fresh. - Cost factor: bcrypt 12+, argon2 default. Re-evaluate yearly. - Never log passwords. Filter them out of error reporting. ## FastAPI integration - Auth dependency: ```python async def get_current_user(token: str = Depends(oauth2_scheme), db: AsyncSession = Depends(get_db)) -> User: try: payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"]) except JWTError: raise HTTPException(401, "Invalid token") user = await db.get(User, payload["sub"]) if not user: raise HTTPException(401, "Invalid token") return user ``` - For optional auth, write a separate dependency that returns `Optional[User]`. Don't make `get_current_user` accept `None`. ## Refresh flow - Refresh endpoint: takes the refresh token, issues new access + refresh, **rotates** the refresh token. - Track refresh tokens in a `refresh_tokens` table with `jti`, `user_id`, `expires_at`, `revoked_at`. Lookup on each refresh. - On suspicious activity (token reuse), revoke the entire user session family. ## Roles & permissions - Role on the user record, claim copied to the access token. Don't re-fetch the user just for role checks. - Per-endpoint authorization: factory dependencies like `require_role("admin")`. - For object-level permissions, check ownership inside the service — JWT only proves identity, not authorization. ## Logout - Stateless JWTs can't truly be invalidated. For real logout: - Delete the refresh-token row (the user can't refresh again) - Optionally maintain a revocation list keyed by `jti` for unexpired access tokens - Or shorten access-token TTL so revocation is "soon enough" ## Don't - Don't use long-lived access tokens to avoid refresh logic. The refresh flow exists for revocation. - Don't put sensitive data (email, role, plan) you'd hate to leak in the JWT payload — anyone can read it. - Don't trust `iss` and `aud` without configuring them. Set them in encode and verify in decode. - Don't roll your own crypto. Use library-supported algorithms only.Python Clean Architecture
# CLAUDE.md — Python Clean Architecture ## Layers (innermost → outermost) 1. **Domain** — entities, value objects, domain events. Pure Python, no I/O. 2. **Use cases** (application services) — orchestrate domain objects. Depend on interfaces (ports), not implementations. 3. **Interface adapters** — repositories, gateways, controllers. Implement the ports. 4. **Frameworks & drivers** — FastAPI, SQLAlchemy, Celery. The thinnest layer. The dependency rule: outer layers depend on inner. Never the reverse. ## Folder layout ``` src/your_app/ domain/ entities.py # @dataclass(frozen=True, slots=True) value_objects.py events.py use_cases/ create_invoice.py # one file per use case adapters/ repositories/ http/ queue/ infrastructure/ db/ config.py ``` ## Domain - Entities are dataclasses. Behavior lives on the entity (methods), not in services. - Value objects are `frozen=True`. They have equality by value, not identity. - Domain events are dataclasses. Use cases emit them; adapters dispatch them. - No imports from outer layers. The domain doesn't know SQLAlchemy exists. ## Use cases - One class or callable per use case. The name is a verb in the imperative: `CreateInvoice`, `ApprovePayment`. - Constructor takes injected dependencies as `Protocol` types — never concrete implementations. - The `__call__` (or `execute`) method takes a request DTO and returns a result DTO. Everything serializable. - No I/O directly inside use cases. They orchestrate; adapters do the work. ## Repositories - Interface (`Protocol`) lives in `domain/` or `use_cases/`. Implementation lives in `adapters/repositories/`. - Method names match domain language: `find_by_email`, not `select_user_by_email`. - Return domain entities, never ORM rows. Map at the boundary. ## Dependency injection - Wire dependencies in `infrastructure/composition_root.py` (or `main.py`). Use cases never construct their dependencies. - `dependency-injector` or just plain factory functions both work. Don't reinvent a DI container. ## Testing - Unit-test use cases with fake repositories — no DB, no network. - Integration-test repositories against a real database (Docker container, ephemeral). - End-to-end tests hit the HTTP layer. Keep them few and fast. ## Don't - Don't import SQLAlchemy or FastAPI from `domain/` or `use_cases/`. - Don't pass HTTP request objects into use cases. Map to a DTO at the controller layer. - Don't share database sessions across use cases. Each use case opens and commits its own unit of work. - Don't reach for clean architecture on a 200-line script. It's overkill below a certain scale.Django Testing with pytest
# CLAUDE.md — Django Testing with pytest ## Setup - Use **pytest** with **pytest-django**. Don't write `unittest`-style `TestCase` subclasses for new tests. - Config in `pyproject.toml`: ```toml [tool.pytest.ini_options] DJANGO_SETTINGS_MODULE = "myproject.settings.test" python_files = ["tests.py", "test_*.py", "*_tests.py"] addopts = "--reuse-db --nomigrations" ``` - `--reuse-db` keeps the test DB across runs. `--nomigrations` skips migrations and uses syncdb-style schema creation — much faster. ## Fixtures - One `conftest.py` per app for app-local fixtures. One root `conftest.py` for project-wide fixtures. - Don't use Django's `fixtures` JSON. Build state with **factory_boy** (`factory.django.DjangoModelFactory`). - Factories live in `<app>/tests/factories.py`. One factory per model. Use `Faker` for realistic strings. ## Database access - Mark tests that hit the DB with `@pytest.mark.django_db`. Untouched code stays fast. - For complex transactional tests, use `@pytest.mark.django_db(transaction=True)` — but it's slower, so reach for it only when needed. - Each test gets a clean DB via transaction rollback. Don't write tests that depend on order. ## API tests - DRF tests use `APIClient` from a fixture. Pass `format='json'` explicitly. - Authentication: `client.force_authenticate(user=user)`. Don't fake auth via session middleware in tests. - Assert on status code, response shape, and side effects. A passing 200 with a wrong body is still a bug. ## Mocks - Use `pytest-mock` (`mocker.patch`) — cleaner than the bare `unittest.mock`. - Patch where the symbol is **used**, not where it's defined. - Don't mock the framework. If you find yourself mocking `Model.objects.filter`, the test is at the wrong level. ## Speed - Run tests in parallel with `pytest-xdist`: `pytest -n auto`. - Profile with `pytest --durations=10`. Anything over a second is suspicious. - Don't sleep in tests. Use `freezegun` or pass time as a parameter. ## Coverage - `pytest-cov` for coverage. Aim for high coverage on services and models; tests of view scaffolding rarely matter. - Don't chase 100% by testing trivial code. Cover the branches that hide bugs. ## Don't - Don't load fixtures from JSON files in 2025+. Use factories. - Don't share state between tests via module-level mutable variables. - Don't catch and swallow exceptions in tests to "make them pass". Fix the cause. - Don't run tests against a real external API. Mock at the boundary, integration-test in a separate stage.Python asyncio Patterns
# CLAUDE.md — Python asyncio Patterns ## Fundamentals - Async functions are `async def`. They return coroutines. A coroutine doesn't run until awaited or scheduled as a task. - One event loop per thread. Use `asyncio.run(main())` as the program's entry point. - Don't mix sync blocking calls into async code. They block the loop and stall every other task. ## Tasks - `asyncio.create_task(coro)` schedules a coroutine to run concurrently. Hold the reference — orphaned tasks can be garbage-collected mid-flight. - For groups of related tasks, use `asyncio.TaskGroup` (3.11+): ```python async with asyncio.TaskGroup() as tg: tg.create_task(fetch_a()) tg.create_task(fetch_b()) ``` TaskGroup propagates exceptions and cancels siblings on first failure. Prefer it over `gather` for new code. - `asyncio.gather(*aws)` for parallel awaits when you need a list of results. Set `return_exceptions=True` only if you handle them. ## Cancellation - Cancellation is cooperative. A task is cancelled by raising `CancelledError` at the next await point. - Always handle `asyncio.CancelledError` in long-running tasks if you have cleanup to do, then re-raise. - `try/finally` for cleanup. `finally` blocks run during cancellation. - Don't catch `CancelledError` to "ignore" cancellation. That breaks every shutdown path. ## Timeouts - Use `asyncio.timeout(seconds)` (3.11+): ```python async with asyncio.timeout(5): await slow_call() ``` Older code uses `asyncio.wait_for`. Both raise `TimeoutError` on expiry. - Always wrap network and IPC calls in a timeout. Hang-forever bugs are unrecoverable. ## Sync ↔ async bridges - Run blocking code in a thread: `await asyncio.to_thread(blocking_func, arg)`. Pool managed by the loop. - Don't call `asyncio.run` inside an async function — it creates a new loop and conflicts with the running one. - Library code that *might* run in async contexts: prefer async-friendly libraries (`httpx` over `requests`, `aiosqlite` over `sqlite3`). ## Structured concurrency - Prefer `TaskGroup` over standalone tasks. Lifetimes are visible from one place. - Don't fire-and-forget. If a coroutine needs to outlive its caller, log that decision and hold the task somewhere reachable. ## Don't - Don't sleep with `time.sleep(n)` in async code. Use `await asyncio.sleep(n)`. - Don't share a single `aiohttp.ClientSession` (or any pooled resource) across `asyncio.run()` calls — it's tied to a loop. - Don't use `asyncio.run()` more than once in a process if you can help it. - Don't test async code with `asyncio.run` inside the test body. Use `pytest-asyncio` with `@pytest.mark.asyncio`.Django + Celery Background Jobs
# CLAUDE.md — Django + Celery Background Jobs ## Setup - One Celery app per Django project, defined in `<project>/celery.py`. Auto-discover tasks from installed apps. - Use **Redis** as broker for most workloads; RabbitMQ if you need priority queues, dead-letter handling out of the box, or strict ordering. - Result backend: only enable if you actually consume results. Otherwise skip — it costs writes. ## Task design - Tasks live in `<app>/tasks.py`. Decorate with `@shared_task` (not `@app.task`) so apps stay decoupled. - Tasks are **idempotent**. Retrying must be safe. If it can't be safe, the task is wrong. - Pass IDs, not objects. `process_invoice(invoice_id)` — never `process_invoice(invoice)`. Pickled model instances go stale. - Each task fetches what it needs via `Model.objects.get(...)`. If the row is gone, exit cleanly (`Model.DoesNotExist`). ## Retries - `@shared_task(bind=True, autoretry_for=(SomeError,), retry_backoff=True, max_retries=5)`. - Don't retry on validation failures — they'll never succeed. Retry on transient infra errors. - Set `acks_late=True` for tasks where work loss is unacceptable. Combine with `task_reject_on_worker_lost=True`. ## Scheduled jobs - Celery Beat owns the schedule. Define it in `CELERY_BEAT_SCHEDULE` in settings. - Don't run Beat in multiple places — pick one host and lock it. - For DB-backed schedules, use `django-celery-beat`. The admin UI is worth it on teams of more than two. ## Concurrency - One queue per workload class (`fast`, `slow`, `email`, `report`). A long task should never block a fast queue. - Set `worker_prefetch_multiplier=1` for slow tasks. The default of 4 starves long jobs. - Workers are stateless. Don't share global state across tasks. ## Observability - Send failure notifications via Sentry or your error tracker (`celery.signals.task_failure`). - Tag every log line with `task_id` so you can grep one execution end-to-end. - Health check the broker — if Redis dies, your jobs silently pile up. ## Local dev - `eager_mode = True` in tests. Tasks run inline in the calling thread. - For local dev, run `celery -A myproject worker -l info` in a separate terminal. Don't share the prod Redis. ## Don't - Don't store large payloads in task arguments. Push to S3/DB and pass the key. - Don't call tasks synchronously (`task.apply()`) from a request handler when you mean to queue. Use `task.delay()` or `task.apply_async()`. - Don't put long-running work in HTTP handlers, even if it "usually finishes in time". - Don't catch broad `Exception` and swallow it. Let Celery retry or fail the task.FastAPI Testing (pytest + httpx)
# CLAUDE.md — FastAPI Testing (pytest + httpx) ## Stack - **pytest** + **pytest-asyncio** + **httpx**. - Use `httpx.AsyncClient` with the FastAPI app as transport — not the legacy `TestClient` synchronously, unless you have purely sync code. - `anyio` is fine too if you prefer the trio-compatible style. ## Async client fixture ```python @pytest.fixture async def client(app) -> AsyncIterator[AsyncClient]: async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as c: yield c ``` - Use `ASGITransport` so the request never touches a real socket. - Keep `base_url` consistent so logs are readable. ## Database isolation - Spin up a Postgres container for tests (Docker Compose, Testcontainers, or a local `pg_tmp`). SQLite isn't equivalent. - Each test runs in a transaction that rolls back at the end: ```python @pytest.fixture async def db(engine): async with engine.connect() as conn: trans = await conn.begin() session = AsyncSession(bind=conn, expire_on_commit=False) yield session await session.close() await trans.rollback() ``` - Override `get_db` via `app.dependency_overrides` so the endpoint uses the test session. ## Factories - **factory_boy** with async-friendly post-generation hooks, or just plain async factory functions. - Factories return persisted objects unless you ask for `build`-only. - Don't share factory state across tests — every test creates what it needs. ## Auth in tests - Override `get_current_user` to return a test user. Don't hit the real JWT issue endpoint in every test. - Have a separate test category that *does* exercise the real auth flow — keep it small and slow. ## What to test - **Endpoint contract**: status codes, response shape, validation errors. Use Pydantic schemas to assert on shape. - **Side effects**: row was created, email task was enqueued, cache was invalidated. - **Edge cases**: missing fields, unauthorized users, conflicting state. ## Speed - `--reuse-db` equivalent: reuse the test DB across runs by skipping migrations on every test. - `pytest -n auto` with `pytest-xdist` parallelism. Make sure factories don't collide on unique constraints. - Profile with `pytest --durations=10`. Fix the worst offenders before they multiply. ## Don't - Don't make HTTP calls to a running dev server in tests. Use the app directly via ASGI. - Don't mock `httpx.AsyncClient` — test against the real client with a stubbed transport when calling external services. - Don't rely on insertion order. Use deterministic ordering in queries when asserting. - Don't share authenticated user state across tests via session middleware.Python CLI Tools (Typer)
# CLAUDE.md — Python CLI Tools (Typer) ## Project layout ``` src/yourcli/ __init__.py __main__.py # python -m yourcli cli.py # the Typer app commands/ init.py deploy.py core/ # business logic, no Typer imports pyproject.toml ``` - Entry point in `pyproject.toml`: ```toml [project.scripts] yourcli = "yourcli.cli:app" ``` - `cli.py` is thin — it parses input and calls into `core/`. All real work lives in `core/`. ## Typer basics - One `Typer()` app per command tree. Sub-commands attach via `app.add_typer(sub_app, name="sub")`. - Annotated types for parameters: `name: Annotated[str, typer.Option(help="...")]`. Use `typer.Argument` for positional, `typer.Option` for flags. - Default values come from the parameter default, not from `typer.Option(default=...)`. - Boolean flags get `--flag/--no-flag` automatically. Don't write your own. ## Output - Use **Rich** for formatted output (tables, trees, syntax highlighting). It's already a Typer dep. - Errors go to stderr (`typer.echo(msg, err=True)` or a `console = Console(stderr=True)`). - Exit codes matter: `raise typer.Exit(code=1)` on failure. Don't `sys.exit` directly. - Default to non-color when `stdout` isn't a TTY, or when `NO_COLOR` env var is set. Rich handles both automatically. ## Confirmation & prompts - Destructive actions confirm: `typer.confirm("Proceed?", abort=True)`. - Add `--yes` flags to skip confirmation in scripts. Default `False`. - Don't prompt for required values silently — fail fast with a clear error if the flag is missing in non-interactive mode. ## Configuration - Config order of precedence (highest to lowest): CLI flag → env var → config file → default. - Use `pydantic-settings` for env-var/config loading. Validate types at the boundary. - Config file location: `$XDG_CONFIG_HOME/<yourcli>/config.toml` (Linux/macOS), `%APPDATA%\<yourcli>\config.toml` (Windows). Use `platformdirs`. ## Logging - `--verbose` / `-v` sets log level to DEBUG. `--quiet` / `-q` suppresses non-error output. - Use `RichHandler` from `rich.logging`. It prints log entries pretty. - Log file is optional; keep stdout clean for piping. ## Testing - `from typer.testing import CliRunner`. Test commands with `runner.invoke(app, ["sub", "--flag"])`. - Snapshot the output with `result.stdout` for golden-file tests. - Don't import the CLI module inside `core/` tests — they should run without Typer. ## Don't - Don't put business logic inside command functions. They're parsers, not orchestrators. - Don't `print()` from library code that the CLI imports. Use `logging` and let the CLI configure handlers. - Don't rely on the user's `cwd`. Take paths as arguments and resolve them explicitly. - Don't add a flag for everything. Every flag is forever.Django Deployment (Docker + gunicorn)
# CLAUDE.md — Django Deployment (Docker + gunicorn) ## Image layout - Two-stage Dockerfile: a builder stage that installs deps + collects static, and a runtime stage that copies the artifact. - Pin the Python image: `python:3.12-slim-bookworm`. Don't use `latest`. - Run as a non-root user: `RUN useradd -m app && USER app`. - One process per container. Beat, worker, and web run in separate containers. ## Dependencies - Lock with `uv` or `pip-tools`. Commit the lock file. - Install with `--no-cache-dir`. Cached wheels in the image bloat it. - System deps (libpq, libjpeg) installed in the builder stage only when needed for compilation. Strip from runtime. ## gunicorn - Workers: `2 × CPU + 1` for sync workloads. Override per-service after profiling. - Worker class: `gthread` for I/O-bound apps with reasonable thread counts. `sync` for pure CPU. Don't reach for `gevent` without measuring. - Bind to `0.0.0.0:8000` inside the container. Let the orchestrator handle external ports. - `--access-logfile -` and `--error-logfile -` to send logs to stdout/stderr. - Set `--timeout 30` and `--graceful-timeout 30`. Long-running work belongs in Celery, not the request thread. ## Static files - `python manage.py collectstatic --noinput` runs at build time. - Serve static via Nginx, S3, or Cloudfront — never gunicorn directly. - Use **WhiteNoise** if you have no CDN: `STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"`. ## Environment - 12-factor: every config value comes from env vars. No file-based secrets in the image. - Use `django-environ` or `pydantic-settings` to parse env in one place. - `DEBUG = False` in production. The default settings module is for prod; dev imports from it and overrides. ## Migrations - Migrations run as a separate one-shot container before the web container starts. Never on app boot — race condition with multi-replica deploys. - For zero-downtime deploys, schema changes are backward-compatible: add columns nullable first, deploy, then make NOT NULL in a follow-up release. ## Observability - Logs to stdout, JSON-formatted, captured by the orchestrator. - Health check endpoint that hits the DB and cache. Don't return 200 from a static route. - `SENTRY_DSN` env var; initialize Sentry in `settings.py` only if the DSN is set. ## Don't - Don't bake secrets into the image (DATABASE_URL, SECRET_KEY). - Don't use `runserver` in production. It's not a production server. - Don't run migrations from inside the web container at startup. Use a one-shot job. - Don't ignore `ALLOWED_HOSTS`. An empty list with `DEBUG=False` is a 500 generator.FastAPI Production Deploy
# CLAUDE.md — FastAPI Production Deploy ## Image - Two-stage Dockerfile. Builder installs deps, runtime copies the `/app` and the venv. - Base: `python:3.12-slim-bookworm`. Pin minor version. - Non-root user: `RUN adduser --disabled-password app && USER app`. - One process per container. The image runs `uvicorn`; orchestration handles replicas. ## ASGI server - **uvicorn** behind a real ASGI gateway (`uvicorn[standard]` for HTTP/2 and websockets): ```sh uvicorn myapp.main:app --host 0.0.0.0 --port 8000 --workers 1 ``` - Use **gunicorn with uvicorn workers** when you want process-level concurrency: `gunicorn -k uvicorn.workers.UvicornWorker -w 4`. - For pure CPU-bound work, you don't want async — split into a worker queue. - `--workers` count: `2 × CPU` is a starting point. Profile. ## Lifespan & startup - Use the modern `lifespan` context manager, not `@app.on_event("startup")`: ```python @asynccontextmanager async def lifespan(app: FastAPI): await connect_db() yield await disconnect_db() ``` - Open connections in `lifespan`, not at import time. Otherwise multi-worker setups create N pools. ## Health checks - `/health/live` — process is alive. Returns 200 unconditionally. - `/health/ready` — process can serve traffic (DB reachable, cache reachable, migrations applied). Returns 503 on failure. - Don't gate liveness on the DB. A dead DB shouldn't kill the pod that's mid-graceful-shutdown. ## Kubernetes - Resource requests and limits set on every deployment. CPU `request` ≈ steady-state; `limit` 2× request or unset. - HPA on CPU + custom metrics (request rate). Pure CPU isn't enough for async workloads. - Probes: - `livenessProbe`: `/health/live`, period 10s - `readinessProbe`: `/health/ready`, period 5s - `startupProbe` for slow boots, period 5s, `failureThreshold` 30 - `terminationGracePeriodSeconds` ≥ uvicorn's graceful shutdown timeout. ## Observability - **OpenTelemetry** instrumentation: `opentelemetry-instrumentation-fastapi`. Auto-traces every request. - Logs: JSON to stdout, fields for `trace_id`, `span_id`, `user_id` where available. - Metrics: Prometheus exposed at `/metrics` (use `prometheus-fastapi-instrumentator`). - Sentry for exceptions, gated on `SENTRY_DSN` env var. ## Configuration - `pydantic-settings` reads env into a typed `Settings` object. Single instance, imported once. - 12-factor: every config from env, no config files in the image. - Secrets injected by the orchestrator (Kubernetes Secrets, AWS Secrets Manager, etc.). Never baked. ## Don't - Don't run uvicorn in `--reload` mode in production. - Don't put migrations in `lifespan`. Run them as a one-shot Job before rolling out the new ReplicaSet. - Don't ship without limits — a runaway worker can exhaust the node. - Don't rely on `sys.exit` for shutdown. Cleanly close connections and let uvicorn drain.
Have a CLAUDE.md template that works for you?
Send it in — we’ll credit you and publish it under the right tags.