4.0 KiB
4.0 KiB
name, description
| name | description |
|---|---|
| python-style-preferences | 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
uvfor dependency management, virtualenv creation, locking, and command execution. - Prefer
anyioover rawasyncioAPIs. - Build CLIs with
click. - Keep type annotations comprehensive enough for
basedpyrightto be useful by default.
Working Style
- Start with typed domain shapes.
- Prefer
TypedDictfor 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.
- Keep entrypoints thin.
- Put parsing and CLI wiring in
clickcommands. - Put business logic in typed functions and classes.
- For async CLIs, use a synchronous
clickentrypoint that callsanyio.run(...).
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:
...
- Use
anyioprimitives 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 intoanyiocode unless an external integration forces it. - When an
asyncioboundary is unavoidable, isolate it and leave a short note.
- Treat typing as enforcement, not decoration.
- Annotate public functions, methods, return values, and reusable internal helpers.
- Run
basedpyrightas 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.
# 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]
- Use
jaxtypingfor arrays and tensors.
Short summary:
- Use
DType[ArrayType, "shape names"], for exampleFloat[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
ArrayLikeonly at loose input boundaries.
See references/jaxtyping-summary.md for the fuller cheat sheet.
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:
...
- Use runtime type checking where it pays for itself.
- Prefer
beartypetogether 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 annotationsor stringized annotations when runtime checking must inspect annotations directly.
Verification
Use this order unless the project already has a stricter local workflow:
uv sync --devuv run basedpyrightuv run pytestuv run python -m <package_or_module>or a CLI smoke test when relevant
Reference Files
- Read
references/jaxtyping-summary.mdwhen writing or reviewing array/tensor annotations.