Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 18 additions & 9 deletions .claude/commands/new-pattern.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
---
description: Scaffold a new pattern unit under patterns/<group>/<slug>
description: Scaffold a new pattern module under patterns/<group>/<slug>
argument-hint: <group>/<slug> "Pattern Name"
---

Scaffold a new pattern unit for $ARGUMENTS.
Scaffold a new pattern module for $ARGUMENTS.

1. Validate the group is one of: principle, python, creational, structural, behavioral, modern.
Refuse anything else.
2. Create `patterns/<group>/<slug>/` with the exact template from CLAUDE.md:
README.md (frontmatter with `id: <group>/<slug>`, all schema keys present,
`verdict:` left as `use-with-care` with a `TODO` caveat), empty-but-importable
`__init__.py`, and stub `naive.py`, `pythonic.py`, `real_world.py` each with a
typed `main() -> None` and script guard, plus `tests/test_<slug>.py` with one
failing `test_todo` marked `xfail(reason="unit not yet written")`.
3. Run `make check` and report the result. Do not write the actual pattern content —
2. Create `patterns/<group>/<slug>/` with the exact module template from CLAUDE.md:
- `README.md` — frontmatter with `id: <group>/<slug>`, all schema keys present,
`verdict:` left as `use-with-care` with a `TODO` caveat; a ~10-line front door
mapping the folders.
- `__init__.py` re-exporting from `pattern/`; `pattern/__init__.py` +
`pattern/<slug>.py` with a typed stub.
- `docs/fundamentals.md`, `docs/implementation.md`, `docs/examples.md` — each a
heading plus a `TODO` line naming what belongs there (classic-form contrast in
fundamentals; never use the word "naive").
- `examples/__init__.py` and `examples/demo/` (`__init__.py`, `__main__.py` with a
typed `main() -> None` + script guard that imports from `...pattern`).
- `tests/test_<slug>.py` with one failing `test_todo` marked
`xfail(reason="unit not yet written")`.
3. Run `make check` and report the result. Note: the catalog loader will fail the
unit until the docs files, a runnable example importing `pattern/`, and real
tests exist — that is the point. Do not write the actual pattern content —
scaffolding only.
32 changes: 31 additions & 1 deletion docs/code-review.md → .github/code-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ tests that's idiomatic, not a finding.
- **PEP 8** (style) · **PEP 257** (docstrings) · **PEP 20** (design sensibility)
- **Google Python Style Guide** — the most common team-level extension
- This repo's own bar: [CLAUDE.md](../CLAUDE.md) (unit template, frontmatter
schema) and [verdicts.md](verdicts.md)
schema) and [docs/verdicts.md](../docs/verdicts.md)

## Layer 2: what human reviewers actually check

Expand Down Expand Up @@ -54,6 +54,36 @@ Severity-ordered — block on CRITICAL/HIGH, note MEDIUM:
- **Context7 MCP** — current library/framework docs, for "is this the right API usage?" questions.
- **python-patterns.guide** — the prose authority behind this catalog's verdicts.

## House rules (settled during the v2 migration, enforced in review)

- Name-keyed registries and caretakers refuse silent duplicates: `ValueError`
unless `replace=True`. Ordered collections where repeats are meaningful —
chains, signals — append freely; `functools.singledispatch`'s own overwrite
behavior is inherited, noted not fought.
- `ParamSpec` typing only where a wrapper callable is returned
(`structural/decorator` is the precedent); registries that return callables
unchanged use plain identity typing.
- Never use `None` as a cache sentinel — a factory may legitimately return
`None` and it must still cache once (`LazyProxy._MISSING` precedent).
Immutability guards recurse into containers: a tuple holding a list is
mutable where it counts (`InternPool` precedent).
- Every unit's `examples/` must genuinely build on its `pattern/` package —
an AST import check enforces the import; reviewers judge token imports
(an annotation-only import that vanishes at runtime does not count).
- Frontmatter stays stable; deliberate caveat improvements are allowed and
called out in review.

## Mutation discipline

Reading a diff is not verification. For any load-bearing claim — a shutdown
discipline, a durability promise, a security guard, an "import does no work"
assertion — apply the mutation that would falsify it (swap the operator,
collapse the branch, make the write non-atomic) and confirm the suite fails.
During the v2 migration this caught four real defects that reading alone did
not: an untestable shutdown switch, a sqlite adapter that never committed, a
fixture that erased its own evidence, and a runtime-erased annotation import.
If the mutant survives, the finding is the missing test, not the mutation.

## Review etiquette

- Cite the rule or the file, not taste ("B008: mutable default" beats "I don't like this").
Expand Down
64 changes: 42 additions & 22 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,55 @@
# Agent instructions — python-design-patterns

Companion catalog to [python-patterns.guide](https://python-patterns.guide/): every design
pattern as runnable, tested, typed Python — plus an MCP server (`src/design_patterns_mcp/`)
that serves the catalog to agents.
pattern as a self-contained, tested, typed Python module — plus an MCP server
(`src/design_patterns/mcp/`) that serves the catalog to agents.

## Layout

- `patterns/<group>/<slug>/` — one directory per pattern ("unit"). Groups:
- `patterns/<group>/<slug>/` — one module per pattern ("unit"). Groups:
`principle`, `python`, `creational`, `structural`, `behavioral`, `modern`.
- `src/design_patterns/` — catalog loader (frontmatter → typed `Pattern` objects).
- `src/design_patterns_mcp/` — FastMCP server (tools, resources, prompts, sandbox).
- Legacy flat dirs (`behavioral/`, `combos/`, `creational/`, `structural/`) are
pre-migration code: excluded from lint, deleted as units absorb them. Do not add to them.
- `src/design_patterns/` — catalog loader (frontmatter → typed `Pattern` objects,
strict structure validation in CI).
- `src/design_patterns/mcp/` — MCP server on the mcp 2.x SDK (`MCPServer`, not
FastMCP): tools, resources, prompts, sandbox.

## Pattern unit template

Every unit has exactly this shape (scaffold one with `/new-pattern`):

```
patterns/<group>/<slug>/
├── README.md # YAML frontmatter + prose
├── __init__.py
├── naive.py # the literal 1994/Java-style translation
├── pythonic.py # what you actually write in Python
├── real_world.py # the pattern as it appears in the stdlib
└── tests/test_<slug>.py
├── README.md # YAML frontmatter + ~10-line front door: problem, verdict, folder map
├── __init__.py # public API — re-exports from pattern/
├── pattern/ # the pattern as importable, typed library code
│ ├── __init__.py
│ └── <named>.py # named for what it provides (chain.py, decorators.py, …)
├── docs/
│ ├── fundamentals.md # intent, participants, mechanism, when/when-not,
│ │ # the classic (GoF) form as an annotated listing — never call it "naive"
│ ├── implementation.md# introducing it into a real system: smell, steps, idioms, pitfalls
│ └── examples.md # cited EXTERNAL usages: stdlib, OSS, articles
├── examples/ # runnable mini-projects that import pattern/
│ ├── __init__.py
│ └── <project>/ # realistic domain, no Foo/Bar; __main__.py + modules
└── tests/ # isolated: test_<named>.py + test_<project>.py
```

- Each `.py` variant is import-safe (no side effects at import) and has a
`main() -> None` demo runnable as a script (`if __name__ == "__main__": main()`).
- Tests import the variants and assert behavior — never just "it runs".
- Everything import-safe (no side effects at import); mini-projects run via
`uv run python -m patterns.<group>.<slug>.examples.<project>`.
- Tests assert behavior, never just "it runs"; load-bearing claims get the
mutation treatment (`.github/code-review.md`).
- Examples must genuinely build on `pattern/` — the loader and a catalog test
enforce the structure and the import; reviewers reject token imports.
- Full type hints; `mypy --strict` must pass.

## House rules

- Name-keyed registries refuse silent duplicates (`ValueError` unless
`replace=True`); ordered collections append freely.
- `ParamSpec` only where a wrapper callable is returned; identity typing otherwise.
- Never `None` as a cache sentinel; immutability guards recurse into containers.

## Frontmatter schema (the MCP server indexes this — keep it valid)

```yaml
Expand All @@ -47,14 +65,15 @@ stdlib_sightings: [functools.wraps, contextlib.contextmanager]
```

Verdicts: `pythonic` = use it as shown; `use-with-care` = valid but has sharp edges
(caveats say which); `prefer-alternative` = the naive form exists for study, the
pythonic file shows what to write instead (e.g. Singleton → module global,
Visitor → singledispatch). See `docs/verdicts.md`.
(caveats say which); `prefer-alternative` = the classic form exists for study in
docs/fundamentals.md, `pattern/` exports the alternative to write instead
(e.g. Singleton → module global, Visitor → singledispatch). See `docs/verdicts.md`.

## Workflow

- Branches: `main ← staging ← feat/<slug>`. PRs target `staging`. Never push to `main`.
- Gate before any PR: `make check` (ruff lint+format, mypy --strict, pytest+cov) green.
- Gate before any PR: `make check` (ruff lint+format, mypy --strict, pytest+cov,
readme-table drift) green.
- Commit style: `<type>: <summary>` (`feat`, `fix`, `chore`, `docs`, `refactor`).
- Toolchain is uv only — no pip/poetry. `make install` to set up.

Expand All @@ -63,5 +82,6 @@ Visitor → singledispatch). See `docs/verdicts.md`.
- Lead with the problem, not the pattern name's history.
- Say plainly when Python makes the pattern unnecessary — that honesty is the
point of the repo. Cite the guide chapter when one exists.
- naive.py mirrors the GoF book faithfully, even when un-Pythonic (that's its job);
pythonic.py is idiomatic; real_world.py points at real stdlib usage.
- The classic form in fundamentals.md mirrors the GoF book faithfully, even when
un-Pythonic (that's its job); `pattern/` is idiomatic; examples.md points at
real external usage.
64 changes: 42 additions & 22 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,55 @@
# python-design-patterns

Companion catalog to [python-patterns.guide](https://python-patterns.guide/): every design
pattern as runnable, tested, typed Python — plus an MCP server (`src/design_patterns_mcp/`)
that serves the catalog to agents.
pattern as a self-contained, tested, typed Python module — plus an MCP server
(`src/design_patterns/mcp/`) that serves the catalog to agents.

## Layout

- `patterns/<group>/<slug>/` — one directory per pattern ("unit"). Groups:
- `patterns/<group>/<slug>/` — one module per pattern ("unit"). Groups:
`principle`, `python`, `creational`, `structural`, `behavioral`, `modern`.
- `src/design_patterns/` — catalog loader (frontmatter → typed `Pattern` objects).
- `src/design_patterns_mcp/` — FastMCP server (tools, resources, prompts, sandbox).
- Legacy flat dirs (`behavioral/`, `combos/`, `creational/`, `structural/`) are
pre-migration code: excluded from lint, deleted as units absorb them. Do not add to them.
- `src/design_patterns/` — catalog loader (frontmatter → typed `Pattern` objects,
strict structure validation in CI).
- `src/design_patterns/mcp/` — MCP server on the mcp 2.x SDK (`MCPServer`, not
FastMCP): tools, resources, prompts, sandbox.

## Pattern unit template

Every unit has exactly this shape (scaffold one with `/new-pattern`):

```
patterns/<group>/<slug>/
├── README.md # YAML frontmatter + prose
├── __init__.py
├── naive.py # the literal 1994/Java-style translation
├── pythonic.py # what you actually write in Python
├── real_world.py # the pattern as it appears in the stdlib
└── tests/test_<slug>.py
├── README.md # YAML frontmatter + ~10-line front door: problem, verdict, folder map
├── __init__.py # public API — re-exports from pattern/
├── pattern/ # the pattern as importable, typed library code
│ ├── __init__.py
│ └── <named>.py # named for what it provides (chain.py, decorators.py, …)
├── docs/
│ ├── fundamentals.md # intent, participants, mechanism, when/when-not,
│ │ # the classic (GoF) form as an annotated listing — never call it "naive"
│ ├── implementation.md# introducing it into a real system: smell, steps, idioms, pitfalls
│ └── examples.md # cited EXTERNAL usages: stdlib, OSS, articles
├── examples/ # runnable mini-projects that import pattern/
│ ├── __init__.py
│ └── <project>/ # realistic domain, no Foo/Bar; __main__.py + modules
└── tests/ # isolated: test_<named>.py + test_<project>.py
```

- Each `.py` variant is import-safe (no side effects at import) and has a
`main() -> None` demo runnable as a script (`if __name__ == "__main__": main()`).
- Tests import the variants and assert behavior — never just "it runs".
- Everything import-safe (no side effects at import); mini-projects run via
`uv run python -m patterns.<group>.<slug>.examples.<project>`.
- Tests assert behavior, never just "it runs"; load-bearing claims get the
mutation treatment (`.github/code-review.md`).
- Examples must genuinely build on `pattern/` — the loader and a catalog test
enforce the structure and the import; reviewers reject token imports.
- Full type hints; `mypy --strict` must pass.

## House rules

- Name-keyed registries refuse silent duplicates (`ValueError` unless
`replace=True`); ordered collections append freely.
- `ParamSpec` only where a wrapper callable is returned; identity typing otherwise.
- Never `None` as a cache sentinel; immutability guards recurse into containers.

## Frontmatter schema (the MCP server indexes this — keep it valid)

```yaml
Expand All @@ -47,14 +65,15 @@ stdlib_sightings: [functools.wraps, contextlib.contextmanager]
```

Verdicts: `pythonic` = use it as shown; `use-with-care` = valid but has sharp edges
(caveats say which); `prefer-alternative` = the naive form exists for study, the
pythonic file shows what to write instead (e.g. Singleton → module global,
Visitor → singledispatch). See `docs/verdicts.md`.
(caveats say which); `prefer-alternative` = the classic form exists for study in
docs/fundamentals.md, `pattern/` exports the alternative to write instead
(e.g. Singleton → module global, Visitor → singledispatch). See `docs/verdicts.md`.

## Workflow

- Branches: `main ← staging ← feat/<slug>`. PRs target `staging`. Never push to `main`.
- Gate before any PR: `make check` (ruff lint+format, mypy --strict, pytest+cov) green.
- Gate before any PR: `make check` (ruff lint+format, mypy --strict, pytest+cov,
readme-table drift) green.
- Commit style: `<type>: <summary>` (`feat`, `fix`, `chore`, `docs`, `refactor`).
- Toolchain is uv only — no pip/poetry. `make install` to set up.

Expand All @@ -63,5 +82,6 @@ Visitor → singledispatch). See `docs/verdicts.md`.
- Lead with the problem, not the pattern name's history.
- Say plainly when Python makes the pattern unnecessary — that honesty is the
point of the repo. Cite the guide chapter when one exists.
- naive.py mirrors the GoF book faithfully, even when un-Pythonic (that's its job);
pythonic.py is idiomatic; real_world.py points at real stdlib usage.
- The classic form in fundamentals.md mirrors the GoF book faithfully, even when
un-Pythonic (that's its job); `pattern/` is idiomatic; examples.md points at
real external usage.
25 changes: 15 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
# Python Design Patterns

Look up any design pattern and see what a fluent Python developer would
*actually* write — the classic GoF form, the pythonic form, and where the
standard library already does it — with an honest verdict when the right
answer is "don't". All 23 Gang of Four patterns plus Python-native and
modern ones, every example typed, tested, and runnable.
*actually* write — the classic GoF form contrasted with the Python form,
importable reference code, and a mini-project that puts it to work — with an
honest verdict when the right answer is "don't". All 23 Gang of Four patterns
plus Python-native and modern ones, every unit typed, tested, and runnable.

## Use it

Each pattern is a folder — read them in this order:
Each pattern is a self-contained module:

```
patterns/structural/decorator/
├── README.md # the problem, the trade-offs, the verdict
├── naive.py # the classic 1994 translation
├── pythonic.py # what you actually write in Python
└── real_world.py # where the stdlib already does this
├── README.md # the problem, the verdict, the map
├── pattern/ # the pattern as importable, typed code
├── docs/ # fundamentals · implementation · cited external examples
├── examples/ # runnable mini-projects that use pattern/
└── tests/ # behavioral tests for both
```

Run any example: `uv run python -m patterns.structural.decorator.pythonic`
```python
from patterns.structural.decorator import retry, logged
```

Run any mini-project: `uv run python -m patterns.structural.decorator.examples.resilient_client`

Give it to your agents (MCP server with search, runnable examples, and
pattern recommendations):
Expand Down
17 changes: 12 additions & 5 deletions docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,20 @@ takes only reviewed milestone merges. CI (3.11/3.12/3.13) must pass.
2. Fill the frontmatter — every key; `id` must equal `<group>/<slug>`; pick
the verdict per [verdicts.md](verdicts.md). The catalog loader validates
this in CI and fails loudly.
3. Write the three variants (see [how-to-read-this-repo.md](how-to-read-this-repo.md)
for what each is for) and behavioral tests for all of them.
4. `make check` — ruff, mypy --strict, pytest must all pass.
3. Build the module (see [how-to-read-this-repo.md](how-to-read-this-repo.md)
for what each part is for): `pattern/` (the importable code), the three
`docs/` files, at least one `examples/<project>/` mini-project that
genuinely imports `pattern/`, and behavioral tests for both.
4. `make check` — ruff, mypy --strict, pytest must all pass. The loader
rejects a unit missing any part of the template, and a catalog test
rejects an example that never imports its own pattern package.
5. `make readme` — regenerate the catalog table (CI rejects a stale one).

## Quality bar

- Full type hints; import-safe modules (no side effects at import).
- Tests assert behavior, not "it runs".
- Prose: one page, problem-first, no UML, no history lessons.
- Tests assert behavior, not "it runs" — and load-bearing claims get the
mutation treatment (see [.github/code-review.md](../.github/code-review.md)).
- Mini-projects use realistic domains, no Foo/Bar.
- Prose: one page, problem-first, no UML, no history lessons. The classic
(GoF) form lives in `docs/fundamentals.md` as an annotated listing.
Loading
Loading