Files
skills-public/code-languages/python.md
T
BluscreamandClaude Opus 5 5d96e69d2b refactor(skills): inline cross-links, drop Related sections, fix conventions
- all nine files: trailing "Related" sections removed, their unique links
  inlined where the topic actually arises (FFI boundary -> c-cpp, embedded
  frontend -> js-typescript, build scripts -> bash, etc.)
- README conventions: rule 2 mandated a Related Skills section, which now
  contradicts practice — replaced with inline-linking and one-home-per-fact
  rules. The <repo>/<rel path> rule was appended after the wrong section
  last commit; moved into the list where it belongs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 12:49:30 +02:00

6.0 KiB

name, description
name description
python Use this skill when writing, linting, type-checking, testing, or packaging Python — including async code, CLI tools, bot cogs, and library packages. Covers strict typing with mypy, ruff configuration, virtualenv discipline, async patterns, and packaging.

Python — Rules & Preferences

Extends coding-dev.md. Everything there applies; this adds Python specifics. For the surrounding build and deploy scripts, see bash.md.


1. The Gate

ruff format --check .
ruff check .
mypy --strict .
pytest

All four clean before anything is called done. Configure them in pyproject.toml so the editor, the CLI, and CI all read the same settings.


2. Typing — Strict

Every function gets annotated, including -> None. Type hints are not optional documentation; they are the checker's input.

[tool.mypy]
strict = true
warn_unreachable = true
warn_return_any = true
disallow_untyped_defs = true
  • Never bare Any. Use a Protocol, a TypeVar, or object + narrowing.
  • X | None over Optional[X]; list[str] over List[str] (modern syntax, no typing import).
  • TypedDict or a dataclass for structured dicts — not dict[str, Any].
  • Literal and Enum for closed sets of values, not bare strings.
  • @dataclass(frozen=True, slots=True) for value objects.
  • Third-party library missing stubs: install types-* if it exists, otherwise a narrowly scoped # type: ignore[import-untyped] with a comment — never a blanket module-level ignore.

3. Linting

ruff is the linter and formatter (it replaces flake8, isort, black, pyupgrade, and most plugins). Enable a broad rule set rather than the default minimum:

[tool.ruff.lint]
select = ["E", "F", "W", "I", "N", "UP", "B", "A", "C4", "SIM", "RUF", "ASYNC", "S", "PTH"]

S (bandit security rules) and ASYNC are worth the noise. As always: fix, don't suppress — and when you must suppress, use a specific code (# noqa: S603) with a reason.


4. Environments

  • Always a virtualenv or project-managed environment (uv, poetry, pdm, venv). Never install into the system interpreter, and never pip install --user as a workaround for an immutable OS — use a container or a venv.
  • uv is the preferred installer/resolver where free choice exists; it is dramatically faster and lockfile-native.
  • Commit the lockfile. Pin the Python version in pyproject.toml (requires-python).
  • pathlib.Path over os.path everywhere (ruff's PTH rules enforce this).

5. Errors & Logging

  • Specific exceptions only. except Exception: needs a reason; bare except: is never acceptable (it catches KeyboardInterrupt and SystemExit).
  • Never swallow silently. If you catch and continue, log at warning or above.
  • raise ... from err to preserve the chain.
  • Custom exception types deriving from a single package-level base, so callers can catch the whole family.
  • logging module, never print, in anything that isn't a CLI writing to stdout as its actual output. One logger = logging.getLogger(__name__) per module, at module scope.
  • Never log secrets — tokens, auth headers, message content, personal data.

6. Async

  • asyncio with async/await. Never block the event loop — no time.sleep, no synchronous requests, no blocking file I/O in a coroutine. Use asyncio.to_thread for genuinely blocking calls.
  • httpx (or the framework's own client) over requests in async code.
  • asyncio.TaskGroup (3.11+) over bare create_task — it propagates exceptions and cancels siblings instead of losing them to an unawaited task.
  • Every create_task result must be held in a reference, or the garbage collector can cancel it mid-flight.
  • Cancellation is cooperative: clean up in finally, and never swallow CancelledError.
  • Events over polling. Subscribe to the library's event/callback API; do not loop on a timer. Where polling is genuinely unavoidable (no event source exists), use a staggered backoff rather than a fixed interval, and say why polling was necessary.

7. Rate Limiting & External Services

When talking to a rate-limited API (a chat platform, a store, a tracker):

  • Funnel outbound calls through a single queue/worker rather than calling from arbitrary call sites. It is the only place a limit can actually be enforced.
  • Backoff with jitter on retry; never a tight retry loop.
  • Respect Retry-After and documented limits; assume undocumented ones exist.
  • Be extremely careful scraping real consumer services. Conservative delays, realistic headers, plenty of failsafes, and abort fast when a response looks unexpected. Assume you are one mistake away from a ban.

8. Structure

  • Package layout: src/<package>/, tests in tests/.
  • Shared behavior goes in a real shared module, imported by each consumer — not copy-pasted between sibling packages. When three components grow the same queue/retry/ formatting logic, that is the signal to extract it.
  • Reusable, generic functionality (a format parser, a protocol client) becomes its own installable package with its own repo and version, consumed as a dependency.
  • Keep persistent state in the framework's own config/storage system so it survives restarts; do not invent a parallel JSON file beside it.
  • File and function size limits from the general rules apply.

9. Testing

  • pytest, with pytest-asyncio for coroutines.
  • Fixtures over setup/teardown methods; tmp_path for anything touching the filesystem.
  • Regression test every fixed bug.
  • Mock at the network boundary (respx, responses), not by monkeypatching your own internals — a test that patches the code under test proves nothing.
  • Never exercise destructive operations against real accounts or real data. Temporary fixtures only.
  • pytest -p no:randomly only when order genuinely matters, and note why.