Instruction file imported from jalvespinto/menu-planning-public (
.cursor/rules/src-code-standards.mdc). Copyright stays with the author.
0. Principles
- KISS: keep solutions simple and boring; avoid premature abstraction/optimization.
- Single-developer maintainability: default to designs one developer can understand, test, debug, and change locally without needing to learn new frameworks or hidden orchestration.
- Complexity budget: if a solution introduces more than one new moving part, name the concrete requirement for each part. Prefer slicing work over landing a large, interconnected design.
- DRY: eliminate duplication with shared functions/modules.
- SOLID (esp. SRP): each unit has one reason to change.
- Readability > cleverness: optimize for the next reader.
- Prefer small functions that compose.
Structure & Organization
- One clear purpose per file/module; avoid "misc/utils" dumping grounds.
- Keep public API small; hide internals by default.
- Group code by domain (feature) over technical layer when possible.
Naming & Style
- Descriptive, unambiguous names; avoid cryptic abbreviations.
- Consistent casing per language ecosystem (snake_case, camelCase, PascalCase).
- Avoid boolean-negation names (prefer
is_enabledoverdisable_flag).
Security & Privacy
- Principle of least privilege (tokens, roles, network).
- Never log secrets. Use a secret manager; rotate regularly.
- Validate and sanitize all external inputs.
- Comply with data retention & encryption at rest/in transit.
Performance
- Measure before optimizing; add benchmarks for hotspots.
- Prefer simple O(1)/O(n) solutions; beware N+1 calls.
- Backpressure and timeouts on all I/O. Use retries with jitter.
Dependencies
- Prefer standard library first; add third-party only with clear benefit.
- Pin versions; record changelogs and upgrade deliberately.
Reviews & Collaboration
- Small PRs (≈200–400 LOC). One reviewer is enough if checklist passes.
- PR checklist: style, security, migrations, logs, revert plan, tests.
- Clear commit messages:
<scope>: <change>; link issue/ticket.
Delivery
- One Dockerfile per service; image version = tag or SHA.
- Blue/green or rolling deploys; keep a revert path ready.
- Post-release checks; write a one-line changelog per release.
I. Descriptive Naming
All files, classes, methods, and variables MUST have carefully chosen descriptive names following the project's naming conventions.
General Guidelines
- Prefer small functions; call helpers from main functions.
- Place helpers after the main functions; prefix helper names with
_(private intent).
Command Naming
- Command classes may use suffixed form (e.g.,
ScrapeReceiptCommand) or non-suffixed form (e.g.,CreateMenu,RefineRecipe); both are common in this codebase. - Consistency within each bounded context is more important than global consistency.
Handler Function Naming
- Use
_handlersuffix for exported command entrypoints underapplication/command_handlers/(e.g.,copy_recipe_to_meal_handler,create_products_handler). This marks application-layer callables that handle commands, distinct from similarly named domain methods. - The suffix is especially important when the name could be confused with a domain model method.
- Module location (in
command_handlers/folder) already indicates the handler role.
Soft Delete vs Hard Delete
- Soft Delete (
discard): Usediscard()on aggregates and entities that participate in soft delete. This setsdiscarded=Trueanddiscarded_atand preserves the row for audit and typical "hide from default queries" behavior. undiscard(): Not part of the shared mixin API. Implement only where the domain models undo; today that isMealandRecipeinrecipes_catalog. Do not assume Menu, Client, or other aggregates exposeundiscard().- Hard Delete (
delete()): Usedelete()for permanent removal (SQL DELETE via UoW, object storage, cache entries, test data cleanup).
Protected vs Private Methods
- Use a single underscore
_method()for both protected and private methods. - Document in the docstring whether the method is "protected" (subclasses may use) or "private" (internal only) where it matters; thoroughness varies across the codebase.
Async Naming
- Do NOT use an
_asyncsuffix. Theasync defsignature is sufficient. - Exception: Use a suffix only when wrapping both sync and async versions of the same function for disambiguation.
For naming examples: Search the existing codebase in src/contexts/ to find established patterns. Follow the conventions already in use within each bounded context.
II. Documentation Guidelines
All code MUST have well-written documentation following the established standards.
Documentation Philosophy
- Document the "why" and contracts, not the "how".
- Code is the primary source of truth; well-structured code with clear naming is better than comments explaining bad code.
- Document decisions and business rules, not implementations.
- Keep documentation close to the code (in-file docstrings) whenever possible.
Documentation Structure
Organize documentation to serve different user needs (Diátaxis framework):
- Tutorials (Learning-oriented): "Walkthroughs" for new developers (e.g., "Creating a Menu from Scratch"). Focus on learning by doing.
- How-to Guides (Problem-oriented): Steps to solve specific problems (e.g., "How to run local tests", "How to add a migration").
- Reference (Information-oriented): Technical descriptions of machinery (e.g., API Reference, Command List, Env Vars). Auto-generate where possible.
- Explanation (Understanding-oriented): Background knowledge, high-level context, and design choices (e.g., System Overview, Domain Dictionary, Architecture Decisions).
Maintenance & Lifecycle
Documentation is a living part of the codebase. It MUST be updated as part of the development lifecycle:
- Code Changes: Update docstrings in the same task that modifies the code.
- New Features: Add "How-to" guides or update "Tutorials" if the feature changes the primary workflow.
- Architectural Changes: Update "Explanation" docs (e.g., Architecture Decision Records) when patterns change.
- Infrastructure Changes: Update setup guides and environment configuration docs immediately.
MUST Document
- Modules: Module-level purpose in
__init__.pyor the main file. - Public APIs: Commands, API schemas, and service interfaces (Class/function docstrings).
- Entry Points: Workers, CLI commands, and FastAPI endpoints.
- Configuration: Environment variables and their effects.
- Architectural Decisions: Major design choices affecting multiple components.
- Business Rules: Domain-specific rules that aren't obvious from the code.
Docstring Format
Use Google-style docstrings throughout the codebase. Include the following sections when applicable:
- Summary: Always (first line).
- Extended Description: When the summary isn't sufficient.
- Args: When the function has parameters.
- Returns: When the function returns a value.
- Raises: When the function raises exceptions.
- Example: For public APIs and complex usage.
- Side Effects: For functions that modify state.
File and Module Documentation
- Module Docstrings: Every
*.pyfile MUST have a docstring explaining its purpose. - Package
__init__.py: Use to document the package purpose and public API.
Anti-patterns (What NOT to Document)
- Do NOT document implementation details (how a loop works).
- Do NOT document obvious code (e.g.,
# Increment counter). - Do NOT document type information that is already in type annotations.
For documentation examples: Examine existing modules in src/contexts/ to match the established documentation style. Pay attention to how commands, handlers, and domain entities are documented.
III. Testing
All code changes MUST include tests inside tests/ following these guidelines.
Key Principles
| Principle | Description |
|---|---|
| Test intent from interface | Never analyze implementation code to determine expected outcomes |
| Fakes over mocks | Use in-memory implementations (FakeRepo, FakeUoW) to assert end-state |
| Black-box approach | Focus on inputs and observable outputs, not internal calls |
| No mirror tests | Test what code should do based on names/signatures, not what it does |
Additional Testing Standards
- Use
pytestfor tests; test names describe behavior (e.g.,test_saves_order_on_retry()). - Use
hypothesiswhere property-based testing adds value. - Isolate time, randomness, and I/O with fakes/fixtures; freeze time when needed.
- Golden tests for prompts/templating if applicable.
Test Infrastructure
Global Fixtures (tests/conftest.py)
| Fixture | Scope | Purpose |
|---|---|---|
| suppress_logs | function (auto) | Suppresses log output during tests |
| anyio_backend | session | Configures async backend as "asyncio" |
Integration/E2E Fixtures (tests/integration_conftest.py)
| Fixture | Scope | Purpose |
|---|---|---|
| wait_for_postgres_to_come_up | session | Waits for DB readiness with retry logic |
| database_setup | session | Runs Alembic downgrade base + upgrade head |
| db_session | function | Isolated DB transaction - auto-rollback after each test |
| test_client | function | httpx.AsyncClient with app lifespan + DI overrides |
Important: The db_session fixture provides transaction isolation - changes are rolled back automatically, ensuring test independence.
Running Tests
Step 1: Load environment variables (REQUIRED)
source ./tools/env/setenvs.sh -e .env.test
Step 2: Run tests with appropriate flags
| Command | Description |
|---|---|
uv run python -m pytest |
Unit tests only (default) |
uv run python -m pytest --integration |
Unit + integration tests |
uv run python -m pytest --e2e |
Unit + E2E tests |
uv run python -m pytest --skip-unit --integration |
Integration tests only |
uv run python -m pytest tests/contexts/iam/ |
Run specific context |
CLI Options
--integration- Run integration tests--e2e- Run E2E tests--slow- Run slow tests--skip-unit- Skip unit tests--llm- Run LLM API tests
Reference Implementation
If you encounter unexpected issues during implementation, consult the existing tests in tests/.
There you will find comprehensive test coverage and proven patterns and workarounds for common issues such as:
- SQLAlchemy session handling and transaction boundaries
- Foreign key constraint setup in integration tests
- E2E test authentication/authorization patterns
- Complex relationship mapping tests
- Factory patterns for test data
For test examples: Always examine existing tests in tests/contexts/ before writing new tests. Match the established patterns for unit, integration, and E2E tests within the same bounded context.
IV. Analysis and Research Process
When working on tasks, you MUST analyze the codebase in a structured way:
- Search for existing patterns - Before implementing anything new, search the codebase for similar implementations.
- Follow established conventions - Match the style and patterns already in use within the same bounded context.
- Develop competing hypotheses - When investigating issues, consider multiple explanations and gather evidence.
- Track confidence levels - Note your certainty about findings to improve calibration.
- Self-critique regularly - Question your assumptions and approach throughout the task.
IMPORTANT: When the user's intent is ambiguous, or it seems that the codebase has a bug, default to providing information, doing research, and providing recommendations rather than taking action.
How to Find Examples
Instead of relying on documentation examples, search the existing codebase:
- For naming patterns:
src/contexts/*/core/domain/commands/andsrc/contexts/*/core/services/command_handlers/ - For documentation style: Any well-documented module in
src/contexts/ - For test patterns:
tests/contexts/recipes_catalog/(reference implementation) - For architectural patterns:
src/contexts/seedwork/(shared infrastructure)
This ensures consistency with the actual codebase rather than potentially outdated examples.
V. Coding Style & Patterns
Style & Layout
- Follow PEP 8 + PEP 257. 4-space indent; ≤ 88–100 cols depending on formatter.
- Import order: stdlib → third-party → local; explicit
__all__for public APIs. - Prefer
pathlib.Pathoveros.path. - Use absolute imports (no relative
from .foo import ...).
Types & Datamodels
- Always type-annotate parameters and return values.
- Use modern annotations (
list[int],dict[str, Any]). - Prefer
attrsor Pydantic for structured data models over ad-hoc dicts/strings. - Use
Enum/StrEnumfor closed sets. - Add
TypedDict/Protocolwhere structural typing helps.
Functions & APIs
- No mutable defaults (
def f(x: list[int] = [])❌). UseNoneand set inside. - Keep function arity small; group related params into objects/models.
- Return rich results (
attrs/Pydantic model) instead of tuple soup.
Strings & Formatting
- Prefer f-strings; use
!rfor debug representations. - Centralize user-facing messages; make them testable and localizable if needed.
Iteration & Collections
- Prefer comprehensions and generator expressions for clarity and laziness.
- Use
itertools,functools,collections(e.g.,Counter,deque,defaultdict).
Modern Features
- Use pattern matching (
match/case) when it clarifies branching. - Prefer
typing.Self,Literal,Annotatedwhere it improves intent. - Consider
@functools.cache/lru_cachefor pure function memoization.
VI. Error Handling & Logging
- Raise specific exceptions; avoid broad
except Exception. - Keep tracebacks; add context but never secrets.
- Use structured logging; log exceptions with
exc_info=True.
VII. Resource Management & Concurrency
Resource Management
- Always use context managers (
with) for files, DB conns, locks, sessions. - Provide custom context managers (
contextlib.contextmanager) when helpful.
Concurrency
- Use AnyIO for async code; avoid direct
asyncio. - In async code, never call blocking I/O; delegate to a thread pool (
anyio.to_thread.run_sync). - Always set timeouts (
fail_after,move_on_after). - Use cancel scopes correctly; prefer shielded scopes only when strictly needed.
VIII. Data & Performance
Data & I/O
- Validate all external inputs (HTTP/CLI/files) with Pydantic or
attrsvalidators. - Serialize with explicit schemas; avoid pickle for untrusted data.
- Use
json/orjsonand explicit encodings (encoding="utf-8").
Performance Footguns to Avoid
- N+1 network/DB calls; batch or pipeline.
- Building huge intermediate lists—stream with generators.
- Excessive regex; precompile with
re.compilewhen reused.
IX. Tooling
- One
pyproject.toml; lock and pin versions. - Format with Ruff (
ruff format .; CI usesruff format --check .). - Lint with Ruff (
ruff check .). - Pre-commit hooks:
ruff,ruff-format, and basic safety checks. - Expose a minimal public surface in
__init__.py; avoid import side-effects.