---
name: code-python-guidelines
description: >
  Apply Pysae Python coding guidelines when writing or reviewing Python code.
  Triggered automatically when working on any Pysae Python project, or manually
  via /code-python-guidelines. Covers typing, imports, logging, data safety,
  Pydantic usage, and code-quality tooling conventions.
allowed-tools:
  - Read
  - Grep
  - Glob
---

# Pysae Python Guidelines

These guidelines apply to **all** Pysae Python projects (API, workers, tooling, ai-tools, etc.).
Follow them whenever you write, review, or refactor Python code in a Pysae repository.

---

## 1. Python version & typing

- **Target: Python 3.14** — use all modern typing syntax natively.
- **Full type annotations required** — all functions, methods, variables, and parameters must be typed. `mypy --strict` must pass.
- **Never use `from __future__ import annotations`** — Python 3.14 does not need it.
- **Never use quoted/string type annotations** (e.g. `-> "MyClass"`) — use `Self`, `TYPE_CHECKING`, or other `typing` utilities instead. Forward references are resolved natively.

## 2. Imports

- **Prefer top-level imports** — avoid local/deferred imports inside functions or methods. Use them only when strictly necessary to break circular imports or when the import is very heavy.
- **Prefer relative imports within packages** — use `from .module import X` or `from ..common.utils import X` instead of absolute `from pysae_something.common.utils import X`. Absolute imports are fine for script entry points (`if __name__ == "__main__"`).
- **Never use `__all__`** — do not define `__all__` in `__init__.py` or any other file. Import symbols explicitly where they are needed.

## 3. Pydantic & data structures

- **Always use Pydantic models for FastAPI** — all request bodies, response payloads, and structured data must be Pydantic `BaseModel` subclasses. Never pass raw `dict` to/from endpoints or between layers.
- **Prefer typed dataclasses over raw dicts** — use `@dataclass` with explicit field types for internal data structures. Reserve `dict` for truly dynamic/schemaless data (e.g. JSON from external APIs before parsing).

## 4. Static files & resources

- **Use `importlib.resources`** for loading static files (HTML templates, CSS, JS) bundled in the package — store them in a `templates/` subdirectory and load with `importlib.resources.files(...)`. Never use `__file__`-relative paths.

## 5. Async-first

- **Prefer async code with `asyncio`** — all I/O-bound code (HTTP calls, database queries, file operations, subprocess calls) should be async. This applies to FastAPI services, workers, CLI tools, and data pipelines alike.
- **CLI tools must use `typer`** — use `asyncio.run()` to bootstrap async code from typer commands.
- **Use `async for` and `async with`** — for iterators and context managers that perform I/O (database cursors, HTTP streams, file handles).
- **Use `asyncio.gather` / `asyncio.TaskGroup`** for concurrent I/O — don't run awaitable calls sequentially when they are independent.

## 6. Logging & error handling

- **Don't log exceptions you re-raise** — if a `try/except` block re-raises the exception (or wraps it with `raise NewError(...) from e`), do not also call `logger.exception()`. The caller or the global exception handler will log it. Double-logging creates noise in monitoring.

## 7. Data migration safety

When code migrates data between stores (e.g. MongoDB → Redis, old collection → new collection):

- **Never delete from the source store during migration reads.** A migration triggered by a read should copy data to the new store but leave the source intact.
- **Source cleanup should be a separate, explicit operation** — a dedicated migration script or a later phase.
- **Both stores must coexist safely** during the migration period.
- **Kubernetes rolling update compatibility** — old pods (pre-migration) and new pods (post-migration) run simultaneously. The migration must not break old pods.
- Flag any code path where a read/get operation has a destructive side-effect — this is almost always a bug.

## 8. Code quality tooling

Every Pysae Python project uses these tools (configured in `pyproject.toml`):

| Tool       | Purpose                     | Command example |
|------------|-----------------------------|-----------------|
| **ruff**   | Linter + import sorting     | `ruff check`    |
| **black**  | Formatter (line-length=120) | `black`         |
| **mypy**   | Type checking (strict)      | `mypy`          |
| **pytest** | Tests                       | `pytest`        |

- Pre-commit hooks run ruff, black, and mypy automatically.
- **Pre-commit hooks must invoke tools with no arguments** — all checked packages, paths, and rules must be configured in `pyproject.toml` so that a bare `ruff check`, `black`, `mypy` works correctly.
- All four must pass before merging.
- **Read `pyproject.toml` before generating code** — check the ruff rules (`select`, `ignore`), black `line-length`, mypy `strict` settings, and any project-specific overrides. Generated code must comply with these settings out of the box, without requiring a format/lint fix pass afterwards.

$ARGUMENTS
