--- name: python-practices description: Use when writing, reviewing, refactoring, or debugging Python code in this repository, including style, typing, error handling, async work, tests, packaging, security, and verification. --- # Python Practices ## Core Rule Write Python that is boring to maintain: explicit, typed, small, tested, and easy to inspect in production. Prefer this repository's existing conventions over generic advice. When a library, framework, SDK, CLI, or cloud-service detail matters, fetch current docs with Context7; use Tavily for broader ecosystem practice research. ## Source Priority 1. Project files: `AGENTS.md`, `pyproject.toml`, existing `cpv3/` code, tests. 2. Current official docs and PEPs. 3. High-quality community research for trade-offs, checked against project needs. 4. Memory or intuition, only after the above. ## Readability - Keep functions small enough to read in one pass. Extract helpers when branching, parsing, or side effects start competing for attention. - Use intention-revealing names: `snake_case` for functions and variables, `CapWords` for classes, `UPPER_SNAKE_CASE` for constants. - Prefer direct control flow, early returns, and clear condition names over nested cleverness. - Avoid ad hoc string parsing when Python, Pydantic, SQLAlchemy, JSON, pathlib, or another structured API can express the operation. - Comments should explain non-obvious reasons, not repeat the code. ## Types And Data - Annotate public functions, service/repository boundaries, async helpers, and values that cross module boundaries. - Prefer precise built-in generics such as `list[str]`, `dict[str, Any]`, and `tuple[int, int]`; avoid bare `dict`, `list`, or `Any` unless the shape is genuinely unknown. - Use Pydantic schemas for API and validation boundaries. Use dataclasses or small typed objects for internal value bundles when Pydantic validation is not needed. - Do not mutate default containers. Use `None` plus initialization or a factory. - Convert external data at the boundary, then keep internal code working with typed values instead of raw payload dictionaries. ## Errors And Logging - Catch specific exceptions. Avoid bare `except`, silent fallbacks, and swallowing errors without observability. - Preserve context with exception chaining: `raise DomainError(...) from exc`. - Define reusable user-facing or domain error strings as `ERROR_...` constants. - Use `logging.getLogger(__name__)` for diagnostics. Do not use `print` in application code. - Error messages should help the next maintainer find the failing input, dependency, or state without leaking secrets. ## Async And Resources - Do not block the event loop. Use async APIs, subprocess APIs, or `anyio.to_thread.run_sync` for sync I/O and CPU-bound library calls. - Use context managers for files, sessions, locks, temporary resources, and network clients. Cleanup must be visible in normal and exceptional paths. - Add timeouts around network, subprocess, and external-service operations when the called API supports them. - Do not share mutable state across concurrent tasks unless access is synchronized. - For this backend, do not share one SQLAlchemy `AsyncSession` across concurrent tasks; use a session per task/request. ## Tests - Test behavior, not implementation details. Name tests after the observable rule. - Prefer deterministic fixtures, explicit inputs, and parametrized edge cases. - Cover failure paths for parsing, permissions, missing data, external services, and persistence boundaries. - Use unit tests for pure helpers and service/repository rules. Use integration tests for endpoint contracts and dependency overrides. - For bug fixes, write or update the failing test first when practical, then make the smallest code change that proves the fix. ## Security And Configuration - Keep secrets in environment-backed settings, never source files, tests, fixtures, generated docs, or logs. - Validate paths, object keys, URLs, filenames, and user-controlled identifiers at the boundary. - Prefer allowlists for modes, providers, file types, and status transitions. - Treat serialization as a boundary: expose only fields that should leave the module or API. ## Project Tooling Use the repository's Python 3.11+ and `uv` workflow. Keep Ruff formatting with the configured 100-character line length. ```bash uv run ruff format cpv3 tests uv run ruff check cpv3 tests uv run pytest ``` Run narrower checks while iterating, then broaden when the change touches shared behavior. If verification cannot run, record the exact blocker. ## Project Overlay - FastAPI HTTP concerns belong in `router.py`. - Pydantic DTOs belong in `schemas.py` and usually inherit `cpv3.common.schemas.Schema`. - Business orchestration belongs in `service.py`. - SQLAlchemy statements belong in `repository.py`. - Database shape changes need Alembic migrations and tests. - Do not rely on `.claude/` directory contents.