--- name: python-style-preferences description: Apply the user's preferred Python engineering style for new modules, refactors, reviews, and project scaffolding. Use when working on Python CLIs, async services, libraries, typed data pipelines, or tensor-heavy code that should standardize on uv, anyio, click, full type annotations, basedpyright, jaxtyping, and beartype. --- # Python Style Preferences ## Overview Follow these conventions whenever the task is primarily Python: - Use `uv` for dependency management, virtualenv creation, locking, and command execution. - Prefer `anyio` over raw `asyncio` APIs. - Build CLIs with `click`. - Keep type annotations comprehensive enough for `basedpyright` to be useful by default. ## Working Style 1. Start with typed domain shapes. - Prefer `TypedDict` for reusable mapping-shaped payloads. - Prefer `@dataclass(slots=True)` for reusable owned records and value objects. - Avoid reusable bare `dict[str, object]` structures; keep ad hoc dicts local and short-lived. 2. Keep entrypoints thin. - Put parsing and CLI wiring in `click` commands. - Put business logic in typed functions and classes. - For async CLIs, use a synchronous `click` entrypoint that calls `anyio.run(...)`. ```python from pathlib import Path import anyio import click @click.command() @click.argument("config_path", type=click.Path(path_type=Path)) def main(config_path: Path) -> None: anyio.run(run, config_path) async def run(config_path: Path) -> None: ... ``` 3. Use `anyio` primitives consistently. - Prefer `anyio.create_task_group()`, `anyio.fail_after()`, streams, and cancellation scopes. - Do not mix `asyncio.create_task`, `asyncio.gather`, or manual event-loop management into `anyio` code unless an external integration forces it. - When an `asyncio` boundary is unavoidable, isolate it and leave a short note. 4. Treat typing as enforcement, not decoration. - Annotate public functions, methods, return values, and reusable internal helpers. - Run `basedpyright` as the default type-checking pass and keep new code clean. - Allow `cast(...)` only at trusted boundaries where runtime guarantees exist but inference cannot express them. Leave a short reason comment immediately above it. - Allow `# type: ignore[...]` only when it is narrow and justified. Leave a short reason comment immediately above it. ```python # basedpyright cannot infer the validated plugin registry value type here. runner = cast(Runner, registry[name]) # The vendor stub is wrong for this overload; runtime input is validated above. value = vendor_api.load(path) # type: ignore[call-overload] ``` 5. Use `jaxtyping` for arrays and tensors. Short summary: - Use `DType[ArrayType, "shape names"]`, for example `Float[np.ndarray, "batch channels"]`. - Reuse axis names to express shared dimensions across arguments and returns. - Use `...` for arbitrary extra axes and fixed integers for exact sizes. - Prefer reusable aliases for common tensor shapes. - Prefer concrete array types after normalization; use `ArrayLike` only at loose input boundaries. See `references/jaxtyping-summary.md` for the fuller cheat sheet. ```python import numpy as np from beartype import beartype from jaxtyping import Float, jaxtyped Batch = Float[np.ndarray, "batch channels"] @jaxtyped(typechecker=beartype) def normalize(x: Batch) -> Batch: ... ``` 6. Use runtime type checking where it pays for itself. - Prefer `beartype` together with `@jaxtyped(typechecker=beartype)` on stable boundaries, data adapters, numerical helpers, and test-targeted functions. - Avoid decorating the hottest inner loops unless the cost is clearly acceptable. - Avoid `from __future__ import annotations` or stringized annotations when runtime checking must inspect annotations directly. ## Verification Use this order unless the project already has a stricter local workflow: 1. `uv sync --dev` 2. `uv run basedpyright` 3. `uv run pytest` 4. `uv run python -m ` or a CLI smoke test when relevant ## Reference Files - Read `references/jaxtyping-summary.md` when writing or reviewing array/tensor annotations.