115 lines
4.9 KiB
Markdown
115 lines
4.9 KiB
Markdown
---
|
|
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.
|