Pythonic Code
Write Python that makes domain behavior obvious to human and AI readers. Apply a WWGD lens:
choose the simplest correct design that feels native to Python and is easy to verify.
The preferred design method is Constructive Domain Modeling: define the valid values and
outcomes a program can construct, then let their types carry obligations to the code that
consumes them.
Orient Before Coding
- Read the repository's
AGENTS.md or CLAUDE.md instructions.
- Read
docs/ENGINEERING_STYLE.md when present.
- Read
docs/DOMAIN_MODEL.md when the change touches domain language, ownership, identity,
source-of-truth rules, or lifecycle behavior.
- Read each target file completely before editing it.
- Identify the supported Python version, configured tools, surrounding patterns, and behavior
that must remain stable.
Let local project rules override generic style advice.
Make Decisions In This Order
- Preserve correctness, domain invariants, and public behavior.
- Respect repository conventions and compatibility constraints.
- Make data flow, control flow, errors, and side effects obvious.
- Choose the smallest abstraction that reduces cognitive load now.
- Use Python idioms when they clarify intent rather than merely shorten code.
- Prove the result with types, tests, and repository tooling.
Model The Positive Space
Constructive Domain Modeling describes what the program supports instead of starting with a
broad representation and a growing list of invalid combinations.
- Represent one valid state with a product of required fields, usually a frozen dataclass.
- Represent meaningful alternatives with a closed union using a Python 3.12
type alias.
- Use Pydantic models and discriminated unions at API, CLI, MCP, configuration, and persistence
boundaries where untrusted values require runtime validation or serialization.
- Parse or classify a broad boundary shape once, then pass the narrower domain value internally.
Do not make every consumer rediscover the invariant through checks and casts.
- Consume a closed union with explicit
match cases. Use typing.assert_never when it proves
exhaustive handling, and avoid catch-all cases that hide a newly added variant.
- Prefer a total function over a partial one. When a case is expected, either narrow the input so
the case is impossible or widen the return union so the caller must handle it.
- Return explicit variants for recoverable domain outcomes when callers can respond differently.
Keep exceptions for broken invariants, cancellation, and unpredictable filesystem, network,
queue, or database failures.
- Choose the simplest model that rules out a real error. Do not add wrapper-only IDs, Result
types around every operation, or maximum-precision unions that cost more than they clarify.
Before narrowing an ORM model or compatibility schema, trace its writers and serialized forms.
Storage may remain broad while a parser constructs a safer domain value for the core workflow.
Prefer Functions Before Hierarchies
- Start with an ordinary, fully typed function.
- Pair functions with a dataclass when related state or an operation result needs a name.
- Use callbacks, closures, or
functools.partial when binding behavior is clearer than creating
another object.
- Use
functools.singledispatch only when behavior genuinely varies by the first argument's
runtime type and open registration is an intentional extension point.
- Use a narrow
Protocol for genuine replaceable behavior. Do not use property-only protocols to
describe internal result data; return a concrete frozen dataclass unless callers truly require
structural interoperability.
- Use a concrete class when identity, cohesive mutable state, lifecycle, or resource ownership
requires one.
- Use an abstract base class only when runtime-enforced subclassing or shared skeletal behavior
is part of the current design.
Do not replace one class hierarchy with clever functional machinery. Prefer the form with the
fewest concepts, hidden rules, and call hops.
Keep Reasoning Local
- Keep a straightforward workflow together when top-to-bottom reading is clearest.
- Extract a helper only when its name captures a domain operation, it isolates a side effect or
constraint, it removes meaningful duplication, or it forms a cohesive testable computation.
- Do not extract helpers merely to shorten a function.
- Treat a class dominated by private methods as a signal that behavior may belong in explicit
module-level functions operating on typed values.
- Treat long chains of
_prepare_*, _resolve_*, _apply_*, and _build_* calls as a prompt to
reconsider the data flow or name one meaningful phase object.
- Avoid manager, factory, base, adapter, strategy, and registry abstractions with only one real
implementation.
- Avoid dynamic registration, metaprogramming, and decorator-driven control flow unless the
product currently needs that extension mechanism.
If extracting a helper makes the reader navigate more but understand no less, keep the logic
local.
Write Explicit Python
- Name values after the domain concept they carry.
- Use full annotations and narrow types. Do not hide uncertainty with
Any, broad casts,
speculative getattr, or unstructured dictionaries.
- Use frozen dataclasses for internal domain values and Pydantic at validation and serialization
boundaries. A Pydantic model is not automatically the best internal state representation.
- Prefer direct iteration, context managers, standard-library building blocks, and simple
comprehensions where their meaning is immediate.
- Distinguish absence from falsiness; use truth-value testing only when empty values share the
intended meaning.
- Keep async work, resource ownership, cancellation, and cleanup visible.
- Fail fast with specific errors when an invariant or external operation fails. Do not use
exceptions for ordinary domain branching, or add silent fallbacks and broad exception handling.
- Comment decisions and constraints, not mechanics.
- Optimize measured hot paths; do not trade readability for hypothetical performance.
Match The Requested Mode
Write
Establish the valid states, outcomes, and boundary parser first. Implement the direct path, make
closed variants exhaustive, then add only the abstractions required by real variation, state, or
boundaries.
Refactor
Preserve observable behavior, keep the diff focused, and add or update a regression test when
the behavior is risky. Look for status strings coupled to optional fields, repeated validation,
"should never happen" branches, and expected outcomes carried by exceptions. Replace them only
when a smaller constructive model removes a real unsupported state. Do not mechanically rewrite
already-clear code, convert I/O failures to Result types, or reshape persisted data before tracing
its writers.
Review
Report concrete readability, abstraction, typing, lifecycle, and domain-model risks. Explain the
smallest practical improvement. Ask which invalid state or unhandled obligation a proposed type
actually removes; stronger-looking types without a concrete payoff are not an improvement. Do not
edit unless the user asks for fixes.
Verify The Result
Run the narrowest command that proves the change, then widen according to risk:
- Focused tests for the changed behavior.
- Formatter, linter, and type checker configured by the project. Use the type checker to prove
exhaustive consumers where the domain is a closed union.
- Repository health, package, integration, or full gates when boundaries are affected.
Lead the final response with the outcome and verification. Explain design choices only when they
are non-obvious or materially affect future work.
1---2name: pythonic-code3description: Write, refactor, and review Python for clarity, explicit behavior, local reasoning, strong types, constructive domain modeling, and minimal abstraction. Use when creating or changing nontrivial Python, simplifying object-heavy, helper-heavy, or overly procedural code, evaluating whether code is Pythonic, or reviewing Python maintainability in Basic Memory repositories.4---5
6# Pythonic Code
7
8Write Python that makes domain behavior obvious to human and AI readers. Apply a WWGD lens:
9choose the simplest correct design that feels native to Python and is easy to verify.
10
11The preferred design method is **Constructive Domain Modeling**: define the valid values and
12outcomes a program can construct, then let their types carry obligations to the code that
13consumes them.
14
15## Orient Before Coding
16
171. Read the repository's `AGENTS.md` or `CLAUDE.md` instructions.
182. Read `docs/ENGINEERING_STYLE.md` when present.
193. Read `docs/DOMAIN_MODEL.md` when the change touches domain language, ownership, identity,
20 source-of-truth rules, or lifecycle behavior.
214. Read each target file completely before editing it.
225. Identify the supported Python version, configured tools, surrounding patterns, and behavior
23 that must remain stable.
24
25Let local project rules override generic style advice.
26
27## Make Decisions In This Order
28
291. Preserve correctness, domain invariants, and public behavior.
302. Respect repository conventions and compatibility constraints.
313. Make data flow, control flow, errors, and side effects obvious.
324. Choose the smallest abstraction that reduces cognitive load now.
335. Use Python idioms when they clarify intent rather than merely shorten code.
346. Prove the result with types, tests, and repository tooling.
35
36## Model The Positive Space
37
38Constructive Domain Modeling describes what the program supports instead of starting with a
39broad representation and a growing list of invalid combinations.
40
41- Represent one valid state with a product of required fields, usually a frozen dataclass.
42- Represent meaningful alternatives with a closed union using a Python 3.12 `type` alias.
43- Use Pydantic models and discriminated unions at API, CLI, MCP, configuration, and persistence
44 boundaries where untrusted values require runtime validation or serialization.
45- Parse or classify a broad boundary shape once, then pass the narrower domain value internally.
46 Do not make every consumer rediscover the invariant through checks and casts.
47- Consume a closed union with explicit `match` cases. Use `typing.assert_never` when it proves
48 exhaustive handling, and avoid catch-all cases that hide a newly added variant.
49- Prefer a total function over a partial one. When a case is expected, either narrow the input so
50 the case is impossible or widen the return union so the caller must handle it.
51- Return explicit variants for recoverable domain outcomes when callers can respond differently.
52 Keep exceptions for broken invariants, cancellation, and unpredictable filesystem, network,
53 queue, or database failures.
54- Choose the simplest model that rules out a real error. Do not add wrapper-only IDs, Result
55 types around every operation, or maximum-precision unions that cost more than they clarify.
56
57Before narrowing an ORM model or compatibility schema, trace its writers and serialized forms.
58Storage may remain broad while a parser constructs a safer domain value for the core workflow.
59
60## Prefer Functions Before Hierarchies
61
62- Start with an ordinary, fully typed function.
63- Pair functions with a dataclass when related state or an operation result needs a name.
64- Use callbacks, closures, or `functools.partial` when binding behavior is clearer than creating
65 another object.
66- Use `functools.singledispatch` only when behavior genuinely varies by the first argument's
67 runtime type and open registration is an intentional extension point.
68- Use a narrow `Protocol` for genuine replaceable behavior. Do not use property-only protocols to
69 describe internal result data; return a concrete frozen dataclass unless callers truly require
70 structural interoperability.
71- Use a concrete class when identity, cohesive mutable state, lifecycle, or resource ownership
72 requires one.
73- Use an abstract base class only when runtime-enforced subclassing or shared skeletal behavior
74 is part of the current design.
75
76Do not replace one class hierarchy with clever functional machinery. Prefer the form with the
77fewest concepts, hidden rules, and call hops.
78
79## Keep Reasoning Local
80
81- Keep a straightforward workflow together when top-to-bottom reading is clearest.
82- Extract a helper only when its name captures a domain operation, it isolates a side effect or
83 constraint, it removes meaningful duplication, or it forms a cohesive testable computation.
84- Do not extract helpers merely to shorten a function.
85- Treat a class dominated by private methods as a signal that behavior may belong in explicit
86 module-level functions operating on typed values.
87- Treat long chains of `_prepare_*`, `_resolve_*`, `_apply_*`, and `_build_*` calls as a prompt to
88 reconsider the data flow or name one meaningful phase object.
89- Avoid manager, factory, base, adapter, strategy, and registry abstractions with only one real
90 implementation.
91- Avoid dynamic registration, metaprogramming, and decorator-driven control flow unless the
92 product currently needs that extension mechanism.
93
94If extracting a helper makes the reader navigate more but understand no less, keep the logic
95local.
96
97## Write Explicit Python
98
99- Name values after the domain concept they carry.
100- Use full annotations and narrow types. Do not hide uncertainty with `Any`, broad casts,
101 speculative `getattr`, or unstructured dictionaries.
102- Use frozen dataclasses for internal domain values and Pydantic at validation and serialization
103 boundaries. A Pydantic model is not automatically the best internal state representation.
104- Prefer direct iteration, context managers, standard-library building blocks, and simple
105 comprehensions where their meaning is immediate.
106- Distinguish absence from falsiness; use truth-value testing only when empty values share the
107 intended meaning.
108- Keep async work, resource ownership, cancellation, and cleanup visible.
109- Fail fast with specific errors when an invariant or external operation fails. Do not use
110 exceptions for ordinary domain branching, or add silent fallbacks and broad exception handling.
111- Comment decisions and constraints, not mechanics.
112- Optimize measured hot paths; do not trade readability for hypothetical performance.
113
114## Match The Requested Mode
115
116### Write
117
118Establish the valid states, outcomes, and boundary parser first. Implement the direct path, make
119closed variants exhaustive, then add only the abstractions required by real variation, state, or
120boundaries.
121
122### Refactor
123
124Preserve observable behavior, keep the diff focused, and add or update a regression test when
125the behavior is risky. Look for status strings coupled to optional fields, repeated validation,
126"should never happen" branches, and expected outcomes carried by exceptions. Replace them only
127when a smaller constructive model removes a real unsupported state. Do not mechanically rewrite
128already-clear code, convert I/O failures to Result types, or reshape persisted data before tracing
129its writers.
130
131### Review
132
133Report concrete readability, abstraction, typing, lifecycle, and domain-model risks. Explain the
134smallest practical improvement. Ask which invalid state or unhandled obligation a proposed type
135actually removes; stronger-looking types without a concrete payoff are not an improvement. Do not
136edit unless the user asks for fixes.
137
138## Verify The Result
139
140Run the narrowest command that proves the change, then widen according to risk:
141
1421. Focused tests for the changed behavior.
1432. Formatter, linter, and type checker configured by the project. Use the type checker to prove
144 exhaustive consumers where the domain is a closed union.
1453. Repository health, package, integration, or full gates when boundaries are affected.
146
147Lead the final response with the outcome and verification. Explain design choices only when they
148are non-obvious or materially affect future work.