python
Skill1 starsChanged 3 months agoMITView on GitHub
Python standard and patterns. ALWAYS load for .py files, .ipynb notebooks, Python code, uv, pip, pyproject.toml, pytest, ruff, pyright, mypy, asyncio, FastAPI, pydantic, dataclasses, type hints, data scripts/ETL, PyTorch (pytorch-skill complements this skill — load both), or any Python-related task — even when the user doesn't say Python explicitly.
44 other projects use a version of this.
Adopted by
Most recently taken up by
- niksavis/handilytheir own version, first seen Nov 8, 2026
- testdino-hq/google-styleguides-skillstheir own version, first seen Nov 8, 2026
- niksavis/basiclytheir own version, first seen Nov 7, 2026
- its-ash/skillstheir own version, first seen Nov 1, 2026
- shubh3794/claude-code-portabletheir own version, first seen Nov 1, 2026
- rogue-dev-studio/ai-agents-roguetheir own version, first seen Nov 1, 2026
Used with
Projects with it also use android-kotlin, csharp and 8 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
alinaqiSkillandroid-kotlin
Android Kotlin development with Coroutines, Jetpack Compose, Hilt, and MockK testing
Also in 4 projects that use this
testdino-hqSkillcsharp
Google's official C# style guide. Covers naming conventions, formatting, LINQ, async/await, XML documentation, and .NET best practices.
Also in 4 projects that use this
ffsshhttiikkSkillflutter
Flutter cross-platform framework
Also in 4 projects that use this
mcollinaSkillnode
Provides domain-specific best practices for Node.js development with TypeScript, covering type stripping, async patterns, error handling, streams, modules, testing, performance, caching, logging, and more. Use when setting up Node.js projects with native TypeScript support, configuring type stripping (--experimental-strip-types), writing Node 22+ TypeScript without a build step, or when the user mentions 'native TypeScript in Node', 'strip types', 'Node 22 TypeScript', '.ts files without compilation', 'ts-node alternative', or needs guidance on error handling, graceful shutdown, flaky tests, profiling, or environment configuration in Node.js. Helps configure tsconfig.json for type stripping, set up package.json scripts, handle module resolution and import extensions, and apply robust patterns across the full Node.js stack.
Also in 4 projects that use this
testdino-hqSkillcpp
Google's official C++ style guide. Covers headers, naming conventions, formatting, classes, memory management, RAII, smart pointers, and modern C++ features.
Also in 3 projects that use this
dylantarreSkillangular
Builds token-driven Angular components with signals and standalone components. Use when creating Angular component libraries, integrating design tokens, or building design system components with new control flow syntax.
Also in 3 projects that use this
Other versions44 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 44
- alinaqi/maggy★ 711same name
- alinaqi/claude-bootstrap★ 708same name
- Mindrally/skills★ 275same name
- kid-sid/claude-spellbook★ 190same name
- hass-energy/haeo★ 77same name
- nimadorostkar/Claude-Skills-collection★ 33same name
- maroffo/claude-forge★ 17same name
- G1Joshi/Agent-Skills★ 15same name
- kissgyorgy/coding-agents★ 15same name
- testdino-hq/google-styleguides-skills★ 15same name
34 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository18 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- clickhouseSkill
- distributed-systemsSkill
- flink-javaSkill
- flink-pythonSkill
- flink-sqlSkill
- gitSkill
- grokSkill
- java-prod-skillSkill
- javaSkill
- mermaid-skillSkill
- openapi_docSkill
- postgresSkill
- python-numericsSkill
- pytorchSkill
- reactSkill
- rustSkill
- sqlSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
13 rules. 0 are common, 4 are distinctive, and 9 are very specific to this project.
0
Common
Most of the 40 edited versions we read keep these.
4
Distinctive
This author's own choices. Worth a look.
9
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 8
Use Python 3.14+. Verify feature availability when uncertain.
- Very specific: line 24
No global variables or shared mutable state.
- Distinctive: line 94
No variable name reuse across scopes: when the types differ — pyright may accept it, but mypy catches it and humans misread it.
- Distinctive: line 121
Editing counts too: When you change a function, keep the comments already there, fix any your change made wrong, and add the missing …
- Distinctive: line 128
When code review flags long-function, deep-nesting, or too-many-args, these are the canonical shapes:
- Very specific: line 165
Add case _ as unreachable: assert_never(unreachable) to EVERY match over a union type — describe() under Enums and ADTs below is the …
- Very specific: line 429
No blocking calls in async context (time.sleep, blocking I/O) — freezes the event loop
- Very specific: line 435
Load
references/concurrency.md: when writing async/await, using TaskGroup/Semaphore, choosing an executor, or debugging a hang. - Very specific: line 437
Load
references/runtime.md: when configuring uvicorn, implementing graceful shutdown, writing retry-with-backoff, setting timeouts, or … - Very specific: line 578
Before committing or completing a review, load references/checklist.md for the full checklist.
- Very specific: line 581
Every variable has a type annotation.
- Very specific: line 584
Every match over a union has case _ as unreachable: assert_never(unreachable)
- Very specific: line 591
Trust but verify the formatter: After ruff format, run python -m py_compile on the touched files — formatters have shipped syntax-breaking …
“Common” is measured: kept by at least 6 in 10 of the 40 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Version Policy
Use Python 3.14+. Verify feature availability when uncertain.
Philosophy
- Modular, clear, readable code with care for performance and efficiency
- Obvious over clever — if it needs a what-comment, simplify it; complexity (big-O) and WHY comments are mandatory, not a smell
- PEP-585 types EVERYWHERE — all variables, parameters, returns, constants
Result[T]is the default — every fallible operation returnsResult. Validating construction uses.create()factory methods that returnResult. Raising exceptions is a narrow escape hatch (dataclass__post_init__defensive guards, framework integration), NOT a peer ofResult.err.- Pattern match over
isinstancechains — sealed unions +match/case+assert_neverfor exhaustiveness - No dynamic magic — no runtime attribute lookup, no metaclasses, no monkey-patching (full list in "No Dynamic Magic" section below)
- Explicit over implicit, flat over nested (early returns, guard clauses)
- Guard clauses over
.unwrap()/.expect()— returnErr(), don't raise - Each module does ONE thing well; depend on abstractions (Protocol), not implementations; compose small pieces into larger systems
- Solve the actual problem, not a generalized version; delete aggressively — git preserves history
assertfor DEBUG-only invariants — stripped bypython -O, never use for runtime validation ("Where to Assert" below has the full translation;S101is globally ignored to make this legal)- No
cast()without prior validation —castlies to pyright - No global variables or shared mutable state. For stateful lifetimes (caches, pools, install guards): encapsulate in a class, expose one stable module-level instance, mutate
self— never rebind the module-level name (rebinding breaks everyfrom module import instancealias)
Rust → Python Rosetta
The house idioms, keyed by the Rust reflex they replace. Each row's full treatment lives in the named section.
| Rust | Here | Section |
Result<T, E> + ? | Result[T]; pre-declare, match, early return e | Result[T] Pattern |
enum + match | frozen dataclass per variant + type union + assert_never | Enums and ADTs |
| trait | Protocol — structural, no inheritance | Protocol |
| newtype | @dataclass(frozen=True, slots=True) + .create() | Make Invalid States Unrepresentable |
Option<T> | T | None, converted to Result at lookup boundaries | None and JSON at Boundaries |
let (no mut) | annotated binding, never rebound; Final for constants | Type Annotations |
unsafe | cast / # type: ignore — narrow, justified, commented | No Dynamic Magic |
| cargo / clippy / rustc | uv / ruff / basedpyright --strict | Tooling Enforcement |
No Dynamic Magic
The goal is code that pyright --strict can verify end to end — that rules out features which defeat static analysis and make refactors unsafe:
- NO
getattr/setattr/hasattrwith string literals for field access. Use explicit attributes; for a genuine dispatch table, a typeddict[str, Callable[..., T]]. - NO
__getattr__/__setattr__/__getattribute__overrides on domain classes — the runtime diverges from the annotations. Same spirit: neverobject.__setattr__(frozen_model, field, value)to bypassfrozen=True— see Data Modeling andreferences/frozen_models.md. - NO metaclasses outside test-framework internals. Use a decorator, a
Protocol, or an explicit registry. - NO
eval/exec/compileof user or configuration input; NO runtime class generation (type("Foo", (Base,), {...})). - NO monkey-patching (
module.func = other_func) outside of test setup. - NO
**kwargs: Anypass-through without documenting the expected keys. Prefer typed parameters, aTypedDict, or a dataclass carrying the options. - NO
cast(T, x)without a preceding runtime check that actually narrows — useisinstance, a guard clause, orResult-based validation first. - PREFER
Protocoloverabc.ABCfor interfaces — structural typing plus pyright strict beats runtimeisinstancechecks. - CAUTION with
@runtime_checkable— see the Protocol section.
Performance
- Correct first, then clear, then fast
- Measure before optimizing — intuition is often wrong
- Optimize hot paths only — 90% of code doesn't matter
- Simple code is often fast code (cache-friendly, predictable); complexity for performance is a last resort, with comments explaining WHY (which benchmark, what gain)
Type Annotations (Mandatory)
Every variable assignment carries a type annotation — locals included —
the house delta from common style, which leans on inference. PEP-585 built-in
generics, | unions, PEP-695 type aliases.
def process_items(items: Sequence[str]) -> dict[str, int]:
result: dict[str, int] = {}
for item in items:
count: int = len(item) # yes, even loop locals
result[item] = count
return result
left: TreeNode | None = None # no quotes on forward refs — 3.14 deferred annotations (PEP 649)
type Handler = Callable[[Request], Response] # PEP-695 — typing.TypeVar/Generic are lint-bannedAccept broad, return narrow: parameters take read-only ABCs
(Sequence[T] / Mapping[K, V] / Iterable[T]); returns are concrete
(list[T] / dict[K, V]).
Immutable by default: module constants are Final[T]
(MAX_RETRIES: Final[int] = 3); a local is never rebound to a new meaning —
rename instead. Rust's let without mut is the default here too.
Function Discipline
- Max ~40 lines per function, 3-4 parameters
- 2-3 nesting levels — the Python translation of the global 2-level default (
with/try/matcheach burn a level); the lintable ceiling is 3 (PLR1702,max-nested-blocks = 3) - Single responsibility: one reason to change; early returns over nested conditionals
- No variable name reuse across scopes when the types differ — pyright may accept it, but mypy catches it and humans misread it. Rename the inner variable.
Non-trivial functions carry a big-O comment for compute AND memory, derived DFS through the call tree: callees count toward the bound, and recursion counts its activation frames as memory. Trivially O(1)/O(n) glue (single-pass comprehensions, dict passes) does not get annotated.
Where the big-O lives — the header comment. Any function that does real
work gets a header comment above its def: one line saying what it is for
(this matters most for public APIs), plus the O(...) for compute and memory.
The big-O lives in the header, never on a line inside the body. Tiny obvious
helpers are exempt.
# Group orders by customer for one-pass lookup.
# compute O(n), memory O(n) — one dict entry per order.
def index_orders(orders: Sequence[Order]) -> dict[CustomerId, list[Order]]:
...Comment the code, not the work plan. Comments are for the person reading
and reviewing: the public API, anything not obvious, and the reason behind the
code as a plain sentence (no WHY: label). Keep the work plan out — no phases,
plan steps, ticket numbers, or words like "for now", "temporary", or "later".
Those go stale and someone has to find and delete them. Unfinished work belongs
in plan.md, not in a comment.
Editing counts too. When you change a function, keep the comments already there, fix any your change made wrong, and add the missing header, big-O, or reason. Do this only for code you add or change, not for nearby code you did not touch.
Three Refactor Shapes (north star)
When code review flags long-function, deep-nesting, or too-many-args, these are the canonical shapes:
| Pattern | Trigger | Fix |
| 1 — Phase Extraction | Function exceeds 30-40 lines | Extract validation / side-effect / verification phases into self-naming helpers; the entry fn reads as a 6-line narrative |
| 2 — Params Dataclass + Newtypes | Same 4+ args in 3+ signatures (rule of three — don't preemptively bag) | Merge cohesive params into a reused @dataclass (13 args → 3) |
| 3 — Hoist Invariant Work + dict Indexing | Loop-invariant work recomputed per iteration, or next(... for ...) scans inside loops | Hoist invariants out of the loop; pre-build dict indexes — O(N²) → O(N) |
The three patterns cascade: too-many-args produces long functions; long functions produce deep nesting; deep nesting hides quadratic work. Fix the signature first and the rest unravels. Full before/after pairs (with real codebase examples) live in references/decomposition.md.
Comprehensions Over for-append
Single filter + single-expression map → comprehension (also set/dict). Feed
generator expressions straight into sinks (sum, any, all, "".join, list.extend).
out: list[str] = [] # BAD — accumulator mechanics, three control statements
for item in raw:
if isinstance(item, str):
out.append(item)
out: list[str] = [item for item in raw if isinstance(item, str)] # GOOD — same runtimeUse a for loop when: the body is multi-statement; it needs
try/except; it exists for side effects (file writes, API calls); or it
would nest two deep (extract the inner loop to a named function). A
comprehension is an expression of result; for is a statement of process.
Pattern Matching First
match/case over isinstance chains, over if/elif trees branching on a
discriminant, over dict-dispatch by type. Destructuring is built in, guards stay
typed, and — unlike isinstance chains — exhaustiveness is mechanically checkable.
Exhaustiveness via assert_never
Add case _ as unreachable: assert_never(unreachable) to EVERY match over
a union type — describe() under Enums and ADTs below is the canonical form.
Two guarantees, layered:
- Compile-time. pyright
--strict+reportMatchNotExhaustive: addCancelledtoPaymentStatewithout updating a match site and CI fails — drift never reaches production. - Runtime. An out-of-band value (malformed deserialization, an object built outside the type system) raises immediately instead of silently falling through to
None— the worst failure mode.
Dead Code After Exhaustive Match
A return/raise after a match covering all union members is unreachable (mypy warn_unreachable flags it). Fold the failure path into the match:
match provider.type:
case LlmProviderType.OLLAMA:
return ok(OllamaClient(provider.endpoint))
case LlmProviderType.OPENAI | LlmProviderType.ANTHROPIC:
return ok(OpenAiClient(provider.endpoint, provider.api_key))
case _ as unreachable:
assert_never(unreachable) # not `return err(...)` after the match — dead codeOpen Types
For matches over open types (str, object, dict) where not every value is meaningful, add case _: pass to make "skip unknown" explicit — reportMatchNotExhaustive flags the missing wildcard:
match key:
case "orchestration":
if isinstance(val, str):
clean[key] = val
case _:
pass # skip unknown settings keys — explicit, not accidentalResult[T] Pattern (No Exceptions)
Exceptions only at system boundaries (try_call, async_try_call). Internal code returns Result[T]. When creating result.py in a project, you MUST read references/result.md in this skill's directory for the canonical implementation — do not recreate from memory.
Key API: ok(v), err(code, msg), try_call(fn), async_try_call(fn), .map(), .and_then(), .unwrap_or(), .is_ok().
Pre-declare Variables Used After match
Python lets variables bound inside case branches leak into the enclosing
scope. Don't rely on this — it's fragile and invisible. If a variable will
be used after a match, declare it with a type annotation before the match.
Scope becomes explicit, naked pass disappears, and reportPossiblyUnbound
catches missing assignments at CI time — for ALL match patterns.
# GOOD: ADT extraction — pre-declare, assign in each branch
target_name: str
match strategy:
case Single():
target_name = agent.name
case Supervisor(children):
target_name = children[0].name
case _ as unreachable:
assert_never(unreachable)
log.info("routing", target=target_name) # clearly in scope
# GOOD: Result[T] guard — pre-declare, assign in Ok, return in Err
user: User
match fetch_user(user_id):
case Ok(u):
user = u
case Err() as e:
return e
process(user) # clearly in scope, type declared above
# GOOD: tuple destructuring from Result
conversation: Conversation
agent: Agent
match await service.resolve(account_id, conversation_id):
case Ok((conv, ag)):
conversation, agent = conv, ag
case Err(failure):
yield error_event(failure)
return
# BAD: match-case-pass — variable bound in pattern, naked pass
match fetch_user(user_id):
case Ok(user):
pass # ← user bound HERE, used BELOW
case Err() as e:
return e # ← remove this return → UnboundLocalError
do_something(user) # ← fragile.unwrap()/.expect() is acceptable only in: tests; right after an
.is_ok() check you just did; process startup where crash = correct behavior.
None and JSON at Boundaries
Return empty collections over None. Convert None to Result at lookup
boundaries — if user is None: return err("NOT_FOUND", ...) — so callers never receive T | None from a fallible lookup.
resp.json() returns Any — in pyright strict every downstream access becomes
Unknown. Funnel each external response shape through ONE typed parse function
(_parse_models_response(body: bytes) -> Result[list[str]]): isinstance
narrowing inside (not cast), parse failure is Err not an exception, and the
Any never escapes. More than 3 # type: ignore # JSON boundary lines in a file → extract the parse function.
Protocol (Structural Typing)
Python's equivalent of Rust traits — structural matching verified statically by pyright, no inheritance, no decorator. Narrow protocols for narrow needs (interface segregation).
from typing import Protocol
class UserRepo(Protocol):
async def find(self, id: int) -> Result[User]: ...
async def save(self, user: User) -> Result[None]: ...
class PgUserRepo: # satisfies UserRepo structurally — no `implements`, no base class
async def find(self, id: int) -> Result[User]: ...
async def save(self, user: User) -> Result[None]: ...No @runtime_checkable by default: it makes isinstance(obj, UserRepo)
work, but verifies method names only, not signatures — a find taking
completely different arguments still passes. Add it only when something
genuinely requires runtime isinstance (e.g., plugin feature detection); for
DI and typed interfaces, leave protocols purely structural.
When you do inherit (framework base classes — the exception, not the
rule), mark every overriding method with @override (typing.override).
The shipped config sets reportImplicitOverride = "error", so a silent
shadow after a parent-class refactor is a CI failure, not a latent bug.
Make Invalid States Unrepresentable
Wrap primitives in newtypes whose constructors guarantee the invariant. Two
construction paths: .create() returns Result[T] — the validating path
at every untrusted boundary (API body, config, DB row, user input);
__post_init__ is a defensive guard raising AppException when someone
bypasses the factory — never assert there: python -O strips it and the
invariant silently disappears.
@dataclass(frozen=True, slots=True)
class PositiveInt:
value: int
def __post_init__(self) -> None:
if self.value <= 0: # defensive guard — survives `python -O`, unlike assert
raise AppException("VALIDATION", f"must be positive: {self.value}")
@staticmethod
def create(value: int) -> Result[PositiveInt]:
"""Validating factory. Use this at every boundary."""
if value <= 0:
return err("VALIDATION", f"must be positive: {value}")
return Ok(PositiveInt(value=value))
# Boundary handler: .create() + pre-declare-then-match (see Result[T] section);
# EmailAddress is the same newtype shape over str.
def send_invoice_handler(to_raw: str, amount_raw: int) -> Result[None]:
to: EmailAddress
match EmailAddress.create(to_raw):
case Ok(addr):
to = addr
case Err() as e:
return e
amount: PositiveInt
match PositiveInt.create(amount_raw):
case Ok(amt):
amount = amt
case Err() as e:
return e
return send_invoice(to, amount)
# Internal code receives validated types — the type system holds the invariant:
# def send_invoice(to: EmailAddress, amount: PositiveInt) -> Result[None]Full AppException implementation and the raise-vs-Result rationale: references/app_exception.md.
Where to Assert
The Python rendering of CLAUDE.md's "cheap asserts for invariants":
| Job | Tool |
| Runtime validation (user input, API payloads, config) | Result / AppException — never assert |
Exhaustiveness at a match wildcard | assert_never (a real function — survives -O, S101-clean) |
__post_init__ defensive guard | raise AppException |
| Test assertions | assert freely |
| Cheap debug-only invariants in src | assert — S101 is globally ignored for exactly this (see references/tooling.md) |
Enums and Algebraic Data Types
StrEnum for flat string-valued tags — serializes cleanly, compares with ==,
JSON round-trips by default. ADTs (frozen dataclass per variant + type alias
union) for variants carrying different data — consumed via exhaustive match + assert_never, serialized with a "kind" discriminator.
@dataclass(frozen=True, slots=True)
class Pending:
created_at: datetime
@dataclass(frozen=True, slots=True)
class Shipped:
tracking: str
shipped_at: datetime
type PaymentState = Pending | Shipped
def describe(state: PaymentState) -> str:
match state:
case Pending(created_at):
return f"pending since {created_at}"
case Shipped(tracking, shipped_at):
return f"shipped {tracking} at {shipped_at}"
case _ as unreachable:
assert_never(unreachable) # exhaustive: pyright + runtime guardThe payoff: add Cancelled to PaymentState and every forgetful match
site lights up in pyright. A StrEnum + if/elif chain drifts silently.
Data Modeling
Pydantic at boundaries (external data). Dataclasses internally (already validated).
# Boundary form of the PositiveInt invariant — internal code uses the dataclass newtype with .create().
PositiveInt = Annotated[int, Field(gt=0)]
class UserInput(BaseModel):
model_config = {"frozen": True}
id: PositiveInt
email: str
# Internal — already validated
@dataclass(frozen=True, slots=True)
class User:
id: int
email: strFrozen models + derived fields: derived state is structural, not stored.
A derived field on a frozen sub-model lives on the wrong class — never fill
it by mutating in model_post_init / an after-validator via
object.__setattr__; compute it where it's used, as a method on the model
that owns the inputs (usually the root). Derived/computed fields, validator
modes, or frozen-bypass temptation → read references/frozen_models.md.
Template Strings (PEP 750)
t"..." returns a Template, not a str — the consuming framework
sanitizes each interpolation. f-strings for display; t-strings wherever
interpolation must be sanitized (SQL, HTML, log-injection boundaries):
query: Template = t"select * from users where id = {id}".
Concurrency & Runtime
Principles (full details in the references below):
asyncio.wait_for(coro, timeout=...)on every external call — no hanging- Bounded queues only (
asyncio.Queue(maxsize=n)— unbounded leaks memory);asyncio.Semaphore(n)caps concurrent operations - Structured concurrency via
asyncio.TaskGroup— exceptions propagate, cleanup is guaranteed - No blocking calls in async context (
time.sleep, blocking I/O) — freezes the event loop - Event loop:
uvicorn[standard]auto-selects uvloop where wheels exist — never depend onuvloopdirectly or calluvloop.install()(deprecated since 3.12; no cp314 wheels as of mid-2026). asyncio's default loop is the supported path otherwise. - Graceful shutdown: catch
SIGTERM/SIGINT, finish in-flight requests, then exit - Retries have bounded attempts, exponential backoff, and only for idempotent or retriable failures
- Primitive choice:
asynciofor I/O-bound,InterpreterPoolExecutorfor CPU-bound pure Python (PEP 734),ProcessPoolExecutoronly for C-extension heavy work
Load references/concurrency.md when writing async/await, using
TaskGroup/Semaphore, choosing an executor, or debugging a hang.
Load references/runtime.md when configuring uvicorn, implementing
graceful shutdown, writing retry-with-backoff, setting timeouts, or reasoning
about request-path resilience (circuit breakers, load shedding, idempotency).
Project Structure (src layout)
project-name/
├── pyproject.toml
├── uv.lock
├── src/project_name/
│ ├── __init__.py
│ ├── py.typed # PEP-561 marker
│ ├── main.py
│ ├── core/ # Result, domain models, protocols
│ ├── services/ # Business logic
│ └── infra/ # DB, HTTP, external
└── tests/ # conftest.py, unit/, integration/The full pyproject.toml — every lint rule explained, every strict pyright flag wired — lives in references/tooling.md.
Package Management (uv)
uv init project-name && cd project-name
uv python pin 3.14
# Before adding: check if already transitive — uv tree | grep <package>
uv add fastapi uvicorn[standard] httpx orjson asyncpg structlog
uv add --dev pytest pytest-asyncio hypothesis testcontainers ruff basedpyright mypy pre-commit
uv sync && uv run pytest && uv run ruff check . && uv run basedpyrightuv replaces pip + pip-tools + virtualenv + pyenv + poetry + pipenv with one
Rust binary; deterministic lockfile; install never executes arbitrary Python.
A conda-only package → conda for the scientific stack, uv for the service
layer; don't mix.
Single-file Scripts (PEP 723)
A 50-200 line ETL or data script gets inline metadata, not a project scaffold — reserve src layout for packages and services:
# /// script
# requires-python = ">=3.14"
# dependencies = ["httpx", "polars"]
# ///uv add --script etl.py httpx manages the block; uv run etl.py resolves
and runs it in an ephemeral environment — no pyproject, no venv ceremony.
Terminate Result-style programs at the top level: match the final Result
— Ok → sys.exit(0); Err(f) → log f.code and sys.exit(1).
Tooling Enforcement
The discipline is mostly enforceable via config — don't rely on review
discipline alone. references/tooling.md is the canonical config: full
pyproject.toml, complete select list with per-rule justification, targeted
ignores, per-file overrides, pyright flag-by-flag, pre-commit and CI YAML. Load
it when setting up a project, auditing a pyproject.toml, or resolving a
lint-rule conflict. Lint config asset: copy assets/ruff.toml to the
project root (standalone config), or merge its tables into pyproject.toml
under [tool.ruff.*] — it is generated from references/tooling.md, which is canonical when they disagree.
| Tool | Enforces |
uv | Deterministic lockfile, no setup.py arbitrary code at install |
ruff | Function shape (PLR0915 statements, PLR0913 args, PLR0912 branching, PLR1702 nesting), complexity (C901), mandatory annotations (ANN001/201/204), security (S family — S101 globally ignored, house assert rule), modern syntax (UP), plus SIM/PERF/TRY/RUF |
basedpyright (strict, primary) | End-to-end static types on pyright's engine, pip-installable (no Node). reportMatchNotExhaustive = "error" turns missing match cases into CI failures; reportAny = "error" turns Any leaks into CI failures. Prose references to "pyright" behavior elsewhere in this skill mean this checker. |
mypy (strict, secondary) | Belt-and-suspenders second opinion in CI. |
pytest + pytest-asyncio + hypothesis | asyncio_mode = "auto"; --strict-markers --strict-config turn typos into failures; property tests for pure functions |
coverage (branch) | fail_under = 80; excludes assert_never and if TYPE_CHECKING: |
pre-commit | Runs ruff + pyright before each commit |
Any discipline is machine-enforced: basedpyright's reportAny = "error"
flags every expression that resolves to Any. The narrow, justified uses
(JSON parse funnels, kwargs forwarding) carry
# pyright: ignore[reportAny] # reason — localized by the funnel pattern,
so a file collecting more than 3 is telling you to extract a typed boundary.
Ruff's ANN401 stays ignored: redundant once expressions are checked.
The highest-leverage settings (mirroring references/tooling.md — the
numbers live there, don't fork them):
[tool.basedpyright]
typeCheckingMode = "strict"
reportMatchNotExhaustive = "error" # non-exhaustive match → CI failure
reportAny = "error" # Any leak → CI failure
[tool.ruff.lint.pylint]
max-args = 5 # "3-4 params" with slack for constructors
max-statements = 40 # matches "40 lines per function"Ecosystem Libraries
Each topic has its own reference with full patterns, Result[T] boundary conversions, and anti-patterns. Load only what the current task needs.
| Topic | Reference | Load when |
| HTTP client | references/httpx.md | Writing httpx code, pooling + timeouts, wrapping HTTP into Result[T], debugging pool exhaustion, MockTransport testing |
| Web framework + DI | references/fastapi.md | FastAPI routes, Depends wiring, Result[T] → HTTP responses, Protocol-based DI, lifespan hooks |
| Database (PostgreSQL) | references/asyncpg.md | asyncpg queries, transactions, pool config, Result[T] wrapping, parameterized SQL, bulk inserts |
| Structured logging | references/structlog.md | Configuring structlog, trace-ID via contextvars, @traced decorator, guarding expensive debug args |
| Performance hot paths | references/performance.md | Optimizing after profiling, container choice (list/array/bytearray/numpy), orjson default= discipline, profiling workflow |
| Testing | references/testing.md | pytest tests, testcontainers fixtures, hypothesis property tests, fake-over-mock, async testing |
| Jupyter notebooks | references/notebooks.md | Writing or editing .ipynb teaching notebooks, cell vs sibling-module decisions, exercise cells, restart-and-run-all and output hygiene before commit |
| Pydantic frozen models | references/frozen_models.md | Derived/computed fields on frozen models, validator modes, BaseSettings derivation, tempted by object.__setattr__ |
Sibling skills own adjacent territory — route, don't improvise:
- numpy / polars / pandas / pyarrow / jaxtyping / parquet / tensor shapes →
python-numerics-skill - torch /
nn.Module/ training loops / architectures →pytorch-skill(complements this skill — both load) - SQL style inside inline strings (lowercase keywords, snake_case) →
sql-skill
Observability (logs + metrics + traces, zero-overhead)
Three pillars joined by one correlation id: every log line, metric window, and trace of the same request must pivot to each other through that id.
- Logs: structlog with the correlation id bound via
contextvars(references/structlog.md) — no handler logs before binding. - Metrics:
prometheus_clientcounters/histograms at seams (request boundaries, external calls, error arms), exposed on its own listener port. Bounded label values only (channel, model, status) — per-user/per-request ids never become labels; identity lives in traces and logs. - LLM/agent traces: Langfuse SDK (
@observeor explicit spans) around model calls, STT/TTS, tool runs. Adopt the service correlation id as the trace id by zero-padding 64→128 bit with a shared pad function — nevercreate_trace_id(seed), which hashes and splits cross-service traces. Payload capture (prompts/completions) sits behind a config flag, default off. - Zero-overhead posture: exporters batch in background threads; unset env ⇒ a no-op stub (zero network, zero branching at call sites); observability misconfig warns and is skipped, never blocks startup or a request.
- A feature is done when its seams carry spans + metrics — instrumented on landing, not retrofitted.
Before Committing (Self-Review)
Before committing or completing a review, load references/checklist.md
for the full checklist. Quick top-of-mind checks:
- Every variable has a type annotation;
basedpyright(strict) passes;reportMatchNotExhaustive = "error";reportAny = "error" - Guard clauses (
match/early return) over.unwrap()— unwrap only in tests Result[T]for all fallible ops;AppExceptiononly as narrow escape hatch (seereferences/app_exception.md)- Every
matchover a union hascase _ as unreachable: assert_never(unreachable) - Non-trivial functions carry big-O comments (compute AND memory)
- Timeouts on ALL external calls; bounded queues;
asyncio.wait_for(...)wraps every coroutine - Tests verify state/behavior, not interactions; testcontainers for infra, fakes for Protocols
orjsonfor JSON (with narrow typeddefault=),slots=Trueon dataclasses# type: ignorehygiene — every ignore needs a# reasoncomment. More than 3 in one file → extract a typed boundary function.- Re-run the CLI checkers yourself before presenting work —
uv run basedpyright src tests,uv run mypy <file>,uv run ruff check src tests,uv run ruff format --check src tests,uv run pytest -q; paste the tail output, don't trust a subagent's or IDE's "clean" claim. Pay attention toreportPossiblyUnboundaroundResult[T]unwraps: pre-declared-then-match is correct only when every non-assigningcasebranch returns/raises. If CLI says clean but IDE flags "possibly unbound", that's an IDE false-positive — note it explicitly and keep the canonical pattern, don't refactor around it. - Trust but verify the formatter. After
ruff format, runpython -m py_compileon the touched files — formatters have shipped syntax-breaking regressions. Ifpy_compilefails post-format, keep the valid pre-format state and file an upstream issue.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

