Files
skill-python-style-preferences/python-project-defaults/SKILL.md
T

5.0 KiB

name, description
name description
python-project-defaults Apply repo-aligned Python defaults for greenfield modules, scaffolding, CLIs, libraries, async work, and typed implementation work in projects that already use or are explicitly standardizing on uv, anyio, click, comprehensive typing, basedpyright, and beartype. Do not use for general code review or to migrate existing projects away from their current tooling unless requested.

Python Project Defaults

Use this skill to apply the user's preferred Python defaults without turning an implementation task into a tooling migration.

Priority Order

  1. Explicit user instructions
  2. Existing repository conventions and verification workflow
  3. This skill

Before Applying This Skill

Check the local project first:

  • packaging and environment: pyproject.toml, requirements*, lockfiles, existing env tooling
  • task runner and verification flow: Makefile, justfile, tox.ini, noxfile.py, CI commands
  • CLI framework: click, typer, argparse, or no CLI
  • async model: anyio, asyncio, trio, or sync-only code
  • type checker: basedpyright, pyright, mypy, or no configured checker
  • runtime type checking: beartype or another established validator
  • data modeling patterns: TypedDict, dataclass, pydantic, attrs, ORM models

If the repository already uses an alternative, keep it. Mirror the repo before introducing new tooling.

Core Defaults

Use these defaults when the repository is already aligned with them or the task is greenfield.

  1. Start with typed domain shapes.
  • Prefer TypedDict for reusable mapping-shaped payloads when the repo does not already favor another model layer.
  • Prefer @dataclass(slots=True) for reusable owned records and value objects when that matches local conventions.
  • Avoid reusable bare dict[str, object] structures; keep ad hoc dicts local and short-lived.
  1. Keep entrypoints thin.
  • Put parsing and CLI wiring in the repo's existing CLI framework.
  • Put business logic in typed functions and classes.
  • If you are creating a new CLI with no established framework, default to click.
from pathlib import Path

import click


@click.command()
@click.argument("config_path", type=click.Path(path_type=Path))
def main(config_path: Path) -> None:
    run(config_path)


def run(config_path: Path) -> None:
    ...
  1. Prefer anyio for new async work.
  • If the repository already uses anyio, stay inside its task groups, cancellation scopes, timeout helpers, and streams.
  • If the task is greenfield async work with no established async model, prefer anyio over raw asyncio.
  • Do not rewrite established asyncio, trio, or synchronous code solely to match this preference.
  • For async CLIs that use anyio, keep a synchronous entrypoint that calls anyio.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:
    ...
  1. Treat typing as enforcement, not decoration.
  • Annotate public functions, methods, return values, and reusable internal helpers.
  • Use the repository's configured type checker. If the repo has no established checker and the task is greenfield, default to basedpyright.
  • 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]
  1. Use beartype to catch type errors early at stable boundaries.
  • Prefer @beartype on adapters, parsers, deserializers, service boundaries, and test-targeted helpers when the repository already uses it or the task is greenfield.
  • Use it by default when runtime validation will catch bad inputs earlier than static typing alone.
  • Avoid decorating the hottest inner loops unless the runtime cost is acceptable.
  • Only avoid from __future__ import annotations in modules that rely on runtime annotation inspection.
from beartype import beartype


@beartype
def parse_port(value: str) -> int:
    return int(value)

Anti-Goals

  • Do not change a repository's package manager or lockfile format unless asked.
  • Do not replace an existing async model with anyio unless asked.
  • Do not replace an existing CLI framework with click unless asked.
  • Do not add beartype to hot paths blindly without considering cost.
  • Do not let style preferences override correctness, bug fixing, or the user's explicit task.
  • Do not treat this skill as a mandate to rewrite working code that already matches local conventions.