Python rules for code and reviews, covering style, idioms, types, errors, async, testing, packaging, security and the Ruff rules that check them. Use when writing or reviewing Python, or a diff that touches .py or .pyi files.
Resources
1Install
npx skillscat add pragmatic-engineer/playbook/engineering-standards-python Install via the SkillsCat registry.
Engineering Standards: Python
Language-specific engineering standards for Python, derived from the team's base standards. RFC 2119 keywords (MUST, SHOULD) carry their standard meanings.
REQUIRED BACKGROUND: engineering-standards owns the general rules (PR readiness, size, test types and requirements, the mocking philosophy, deployment). Read it first. This skill adds only the Python specifics and does not restate the base.
How to use this skill
- The index below holds one line per rule. Each topic names a reference file in this skill's
references/folder and says when to load it. Load only the files the work touches. - When you review a diff that touches
.pyor.pyifiles, load the reference for each topic the diff touches. Cite the rule and the Ruff rule, type checker flag or command that checks it. - The project's own setup wins. When
pyproject.toml,ruff.toml, the type checker config, CI or the contributing guide sets a rule, follow it over this skill. - Set
lint.selectin the Ruff config and list each group or code you rely on. The default set depends on the version: up to Ruff 0.15 it isE4,E7,E9andF, and Ruff 0.16 grew it to about 400 rules. A rule the references mark (preview) also needspreview = true. - Snippets target Python 3.12 and later unless a rule says otherwise.
Rules index
Style and naming
- Let the formatter own layout. Names follow PEP 8:
snake_case,CapWords,UPPER_CASEconstants, and a_prefix for internal names. - Group imports (standard library, third party, local), import modules by absolute path, and no
import *. - Public modules, classes and functions have docstrings in one style per project.
Load references/style-and-naming.md when you name things, write imports, docstrings or comments, or review a diff for readability.
Idioms
- Use comprehensions, generators,
enumerate,zip(strict=True), unpacking and f-strings over index loops and manual counters. - Open every resource in a
withblock. Usepathlibfor paths. - Ask forgiveness for one narrow operation, and check first when the check is cheap and race free.
Load references/idioms.md when you write a loop, a comprehension, a with block, string formatting, path or datetime handling, a match or long if chain, a script entry point, or choose between try and a check first.
Data modeling
- Model a record with a
dataclass(frozen when you can), a closed set of values with anEnum, and an interface with aProtocol. - Validate untrusted data at the boundary with Pydantic (or the project's validator), and pass plain typed objects inside.
Load references/data-modeling.md when you add a class that holds data, an enum, a protocol, a TypedDict, or code that parses input into objects.
Types and typing
- Annotate every public function. Run mypy or pyright in strict mode in CI.
- No
Anyacross a boundary,X | NoneoverOptional[X], and a runtime check overcast.
Load references/types-and-typing.md when you add or change annotations, a generic, a cast, a # type: ignore, or the type checker config.
Error handling
- Catch the narrowest exception. No bare
except:, and noexceptthat passes in silence. - Raise a custom exception from one project base class, and chain with
raise ... from err. - Log with
logger.exceptionand lazy%arguments. Noprintin library or service code.
Load references/error-handling.md when you write try, except, raise, a custom exception, or a logging call.
Functions and classes
- Small functions with one job. No mutable default arguments. Keyword-only parameters after the first one or two.
- A class has state and behavior, and holds collaborators as attributes. A module of functions replaces a class with only static methods.
Load references/functions-and-classes.md when you add a function, a class, a property, a dunder method or a decorator.
Async and concurrency
- Do not block the event loop. Run related tasks in a
TaskGroup, keep a reference to every task, put a timeout on each network call, and let cancellation through. - Threads overlap waits, processes run CPU work in parallel. Use
concurrent.futuresexecutors in awithblock.
Load references/async-and-concurrency.md when you write async def, create a task, use threads or processes, or add a lock, queue or semaphore.
Performance
- Profile first. Pick the data structure for the access pattern, stream large data, and cache pure functions.
Load references/performance.md when a change targets speed or memory, or touches a hot loop.
Testing
- pytest with strict markers,
importlibimport mode and warnings as errors. Parametrize cases, assert exceptions withmatch. - Inject fakes before you patch. Patch where the name is looked up, with
autospec. No sleeps and no real network.
Load references/testing.md when you write or review a test, a fixture or a mock, or reproduce a bug as a failing test.
Packaging and tooling
- Metadata and tool config in
pyproject.toml. uv for new projects, a committed lock file for applications, version ranges for libraries. - Ruff for lint and format, a type checker and tests in CI, fast checks only in pre-commit, and supported CPython versions only.
Load references/packaging-and-tooling.md when you change pyproject.toml, dependencies, the lock file, CI, pre-commit or the supported Python versions.
Security and input
- Bind SQL parameters, run processes with an argument list, and do not
eval, unpickle oryaml.loaduntrusted data. - Secrets from the environment, tokens from
secrets, a timeout on every HTTP call, andpip-auditin CI.
Load references/security-and-input.md when code reads input from a network, a file or a user, runs a process, builds SQL, handles a path or a secret, or adds a dependency.
Project structure
- src layout for packages, modules grouped by domain, one-way imports, and a thin entry point.
Load references/project-structure.md when you add a package or module, move code between modules, or add a CLI or config loading.
Antipatterns
Load references/antipatterns.md when you review a Python diff. It is a checklist of the most common smells, each with a fix, and a check where one exists.
Sources
The rule list started from the Real Python best practices reference and the pages it links. Real Python content is copyrighted. The rules here are ideas restated in our own words. No text or code is copied from any source below, and every snippet is our own.
- Real Python entries used: classes, coding style, code formatting, comments, comprehensions, conditionals, constants, docstrings, exception handling, public API surface, Pythonic code, type checking, variables, concurrency, code testing, dependency management, distribution, resource management, security, virtual environments, third-party libraries, and the tutorial on LBYL and EAFP
- PEP 8, PEP 20, PEP 257 and PEP 484 (public domain)
- The Python documentation (PSF License): the data model,
typing,dataclasses,enum, the logging HOWTO and cookbook,asyncio,concurrent.futures,unittest.mockand the security notes ofsubprocess,pickleandtarfile - Typing best practices from the Python typing project
- mypy and pyright docs (MIT)
- Pydantic docs (MIT)
- pytest docs (MIT) and the Hypothesis quickstart (MPL 2.0)
- Python Packaging User Guide (CC BY-SA 3.0, ideas restated) and the uv docs (MIT or Apache-2.0)
- Python version status (CC0) and SPEC 0 from Scientific Python
- OWASP Cheat Sheet Series (CC BY-SA 4.0, ideas restated), Bandit (Apache-2.0) and pip-audit (Apache-2.0)
- Ruff rules (MIT). Rule codes and preview status were checked against Ruff 0.16.10 with
ruff rule --all. - Google Python Style Guide (CC BY 3.0)
- The Hitchhiker's Guide to Python (CC BY-NC-SA 3.0, ideas only)
- Modern Good Practices for Python Development by Stuart Ellis (CC BY-NC-SA 4.0, ideas only)