diff --git a/AGENTS.md b/AGENTS.md index 8e4e0d1..4e3d4d7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -73,7 +73,7 @@ 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 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`. +(e.g. Singleton → module global, Visitor → singledispatch). ## Workflow diff --git a/CLAUDE.md b/CLAUDE.md index 01ae7e5..a4ca06f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -73,7 +73,7 @@ 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 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`. +(e.g. Singleton → module global, Visitor → singledispatch). ## Workflow diff --git a/docs/contributing.md b/CONTRIBUTING.md similarity index 55% rename from docs/contributing.md rename to CONTRIBUTING.md index c7ddd01..5f1b84e 100644 --- a/docs/contributing.md +++ b/CONTRIBUTING.md @@ -8,17 +8,17 @@ takes only reviewed milestone merges. CI (3.11/3.12/3.13) must pass. ## Adding a pattern unit 1. Scaffold: `/new-pattern / "Name"` (Claude Code) or copy an - existing unit's shape. -2. Fill the frontmatter — every key; `id` must equal `/`; pick - the verdict per [verdicts.md](verdicts.md). The catalog loader validates - this in CI and fails loudly. -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//` 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. + existing unit's shape (the template is in [CLAUDE.md](CLAUDE.md)). +2. Fill the frontmatter — every key; `id` must equal `/`; pick the + verdict (`pythonic` | `use-with-care` | `prefer-alternative`, defined in + [CLAUDE.md](CLAUDE.md)). The catalog loader validates this in CI and fails loudly. +3. Build the module: `pattern/` (the importable code), the three `docs/` files, + at least one `examples//` mini-project that genuinely imports + `pattern/` (entry point `main.py`, never `__main__.py`), 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 @@ -29,4 +29,4 @@ takes only reviewed milestone merges. CI (3.11/3.12/3.13) must pass. are severity-ordered; machines own style, humans argue design. - 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. + (GoF) form lives in each unit's `docs/fundamentals.md` as an annotated listing. diff --git a/README.md b/README.md index e750d34..fcbc991 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,23 @@ # Python Design Patterns -Look up any design pattern and see what a fluent Python developer would -*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. +Every design pattern as a fluent Python developer would actually write it — +the classic GoF form contrasted with the Python form, importable code, a +runnable mini-project, and an honest verdict when the right answer is "don't". -## Use it +## MCP + +```bash +# add to Claude Code +claude mcp add design-patterns -- uv run --directory python-design-patterns-mcp -Each pattern is a self-contained module: +# stdio server, for any MCP client +uv run python-design-patterns-mcp +# streamable HTTP on /mcp +uv run python-design-patterns-mcp --http --host 127.0.0.1 --port 8734 ``` -patterns/structural/decorator/ -├── 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 -``` + +## Use it ```python from patterns.structural.decorator import retry, logged @@ -25,80 +25,44 @@ from patterns.structural.decorator import retry, logged Run any mini-project: `uv run python -m patterns.structural.decorator.examples.resilient_client.main` -Give it to your agents (MCP server with search, runnable examples, and -pattern recommendations): - -```bash -claude mcp add design-patterns -- uv run --directory python-design-patterns-mcp -``` - ## Catalog Based on [python-patterns.guide](https://python-patterns.guide/). -### Principles - -| Pattern | Verdict | Problem it solves | -|---|---|---| -| [Composition Over Inheritance](patterns/principle/composition_over_inheritance/) | ✅ pythonic | Vary independent behaviors without one subclass per combination of them. | - -### Python-native - -| Pattern | Verdict | Problem it solves | -|---|---|---| -| [Global Object](patterns/python/global_object/) | ⚠️ use with care | Give a whole program shared access to a constant or a pre-built object by assigning it at module level. | -| [Prebound Method](patterns/python/prebound_method/) | ✅ pythonic | Offer module-level functions that share state, by binding the methods of one hidden instance to module globals. | -| [Sentinel Object](patterns/python/sentinel_object/) | ✅ pythonic | Mark 'no value here' unambiguously when None itself is a legitimate value. | - -### Creational (GoF) - -| Pattern | Verdict | Problem it solves | -|---|---|---| -| [Abstract Factory](patterns/creational/abstract_factory/) | 🔄 prefer alternative | Let code build families of related objects without naming their concrete classes. | -| [Builder](patterns/creational/builder/) | ⚠️ use with care | Assemble a complex object step by step, so the assembly process is reusable and readable. | -| [Factory Method](patterns/creational/factory_method/) | 🔄 prefer alternative | Let a class defer which helper object it constructs, so subclasses or callers can substitute another. | -| [Prototype](patterns/creational/prototype/) | 🔄 prefer alternative | Create new objects by copying a pre-configured exemplar instead of constructing from scratch. | -| [Singleton](patterns/creational/singleton/) | 🔄 prefer alternative | Guarantee a class has exactly one instance and give the whole program access to it. | - -### Structural (GoF) - -| Pattern | Verdict | Problem it solves | -|---|---|---| -| [Adapter](patterns/structural/adapter/) | ✅ pythonic | Make an existing class usable through the interface your code expects, without editing either side. | -| [Bridge](patterns/structural/bridge/) | 🔄 prefer alternative | Let an abstraction and its implementation vary independently, instead of multiplying subclasses across both axes. | -| [Composite](patterns/structural/composite/) | ✅ pythonic | Let callers treat a single object and a whole tree of objects through one interface. | -| [Decorator](patterns/structural/decorator/) | ✅ pythonic | Add behavior around an object or callable without editing it or subclassing it. | -| [Facade](patterns/structural/facade/) | ✅ pythonic | Give a complicated subsystem one simple entry point for the common case. | -| [Flyweight](patterns/structural/flyweight/) | ⚠️ use with care | Support huge numbers of fine-grained objects by sharing immutable instances instead of duplicating them. | -| [Proxy](patterns/structural/proxy/) | ⚠️ use with care | Stand in for another object to control access to it — deferring, guarding, or instrumenting the real thing. | - -### Behavioral (GoF) - -| Pattern | Verdict | Problem it solves | -|---|---|---| -| [Chain of Responsibility](patterns/behavioral/chain_of_responsibility/) | 🔄 prefer alternative | Pass a request along a line of handlers until one of them takes it. | -| [Command](patterns/behavioral/command/) | ⚠️ use with care | Package a request as an object so it can be queued, logged, undone, or executed later by code that doesn't know its details. | -| [Interpreter](patterns/behavioral/interpreter/) | 🔄 prefer alternative | Represent a small language's grammar as data and evaluate sentences in it. | -| [Iterator](patterns/behavioral/iterator/) | ✅ pythonic | Traverse a container's elements without exposing how the container stores them. | -| [Mediator](patterns/behavioral/mediator/) | ⚠️ use with care | Stop a web of objects from referencing each other by routing their interactions through one coordinator. | -| [Memento](patterns/behavioral/memento/) | ⚠️ use with care | Capture an object's state so it can be restored later, without exposing its internals. | -| [Observer](patterns/behavioral/observer/) | ✅ pythonic | Notify interested parties when something changes, without the subject knowing who they are. | -| [State](patterns/behavioral/state/) | ⚠️ use with care | Change an object's behavior when its internal state changes, without an if-forest over a mode flag. | -| [Strategy](patterns/behavioral/strategy/) | 🔄 prefer alternative | Make an algorithm interchangeable at runtime without the caller knowing which variant it got. | -| [Template Method](patterns/behavioral/template_method/) | 🔄 prefer alternative | Fix an algorithm's skeleton while letting callers vary individual steps. | -| [Visitor](patterns/behavioral/visitor/) | 🔄 prefer alternative | Run a new operation over every node of an object structure without adding a method to every node class. | - -### Modern Python - -| Pattern | Verdict | Problem it solves | -|---|---|---| -| [Async Producer/Consumer](patterns/modern/async_producer_consumer/) | ⚠️ use with care | Decouple work generation from work processing under asyncio, with bounded memory and clean shutdown. | -| [Context Manager](patterns/modern/context_manager/) | ✅ pythonic | Guarantee acquire/release pairing around a block of code, even when it raises. | -| [Dependency Injection](patterns/modern/dependency_injection/) | ✅ pythonic | Hand an object its collaborators instead of letting it construct them, so they can be swapped — above all in tests. | -| [Registry](patterns/modern/registry/) | ✅ pythonic | Let implementations announce themselves by name, so dispatch is a lookup instead of an if/elif ladder. | -| [Repository](patterns/modern/repository/) | ⚠️ use with care | Keep domain logic ignorant of how objects are stored, behind a collection-like interface. | +| Pattern | Group | Verdict | Problem it solves | +|---|---|---|---| +| [Composition Over Inheritance](patterns/principle/composition_over_inheritance/) | Principles | ✅ pythonic | Vary independent behaviors without one subclass per combination of them. | +| [Global Object](patterns/python/global_object/) | Python-native | ⚠️ use with care | Give a whole program shared access to a constant or a pre-built object by assigning it at module level. | +| [Prebound Method](patterns/python/prebound_method/) | Python-native | ✅ pythonic | Offer module-level functions that share state, by binding the methods of one hidden instance to module globals. | +| [Sentinel Object](patterns/python/sentinel_object/) | Python-native | ✅ pythonic | Mark 'no value here' unambiguously when None itself is a legitimate value. | +| [Abstract Factory](patterns/creational/abstract_factory/) | Creational (GoF) | 🔄 prefer alternative | Let code build families of related objects without naming their concrete classes. | +| [Builder](patterns/creational/builder/) | Creational (GoF) | ⚠️ use with care | Assemble a complex object step by step, so the assembly process is reusable and readable. | +| [Factory Method](patterns/creational/factory_method/) | Creational (GoF) | 🔄 prefer alternative | Let a class defer which helper object it constructs, so subclasses or callers can substitute another. | +| [Prototype](patterns/creational/prototype/) | Creational (GoF) | 🔄 prefer alternative | Create new objects by copying a pre-configured exemplar instead of constructing from scratch. | +| [Singleton](patterns/creational/singleton/) | Creational (GoF) | 🔄 prefer alternative | Guarantee a class has exactly one instance and give the whole program access to it. | +| [Adapter](patterns/structural/adapter/) | Structural (GoF) | ✅ pythonic | Make an existing class usable through the interface your code expects, without editing either side. | +| [Bridge](patterns/structural/bridge/) | Structural (GoF) | 🔄 prefer alternative | Let an abstraction and its implementation vary independently, instead of multiplying subclasses across both axes. | +| [Composite](patterns/structural/composite/) | Structural (GoF) | ✅ pythonic | Let callers treat a single object and a whole tree of objects through one interface. | +| [Decorator](patterns/structural/decorator/) | Structural (GoF) | ✅ pythonic | Add behavior around an object or callable without editing it or subclassing it. | +| [Facade](patterns/structural/facade/) | Structural (GoF) | ✅ pythonic | Give a complicated subsystem one simple entry point for the common case. | +| [Flyweight](patterns/structural/flyweight/) | Structural (GoF) | ⚠️ use with care | Support huge numbers of fine-grained objects by sharing immutable instances instead of duplicating them. | +| [Proxy](patterns/structural/proxy/) | Structural (GoF) | ⚠️ use with care | Stand in for another object to control access to it — deferring, guarding, or instrumenting the real thing. | +| [Chain of Responsibility](patterns/behavioral/chain_of_responsibility/) | Behavioral (GoF) | 🔄 prefer alternative | Pass a request along a line of handlers until one of them takes it. | +| [Command](patterns/behavioral/command/) | Behavioral (GoF) | ⚠️ use with care | Package a request as an object so it can be queued, logged, undone, or executed later by code that doesn't know its details. | +| [Interpreter](patterns/behavioral/interpreter/) | Behavioral (GoF) | 🔄 prefer alternative | Represent a small language's grammar as data and evaluate sentences in it. | +| [Iterator](patterns/behavioral/iterator/) | Behavioral (GoF) | ✅ pythonic | Traverse a container's elements without exposing how the container stores them. | +| [Mediator](patterns/behavioral/mediator/) | Behavioral (GoF) | ⚠️ use with care | Stop a web of objects from referencing each other by routing their interactions through one coordinator. | +| [Memento](patterns/behavioral/memento/) | Behavioral (GoF) | ⚠️ use with care | Capture an object's state so it can be restored later, without exposing its internals. | +| [Observer](patterns/behavioral/observer/) | Behavioral (GoF) | ✅ pythonic | Notify interested parties when something changes, without the subject knowing who they are. | +| [State](patterns/behavioral/state/) | Behavioral (GoF) | ⚠️ use with care | Change an object's behavior when its internal state changes, without an if-forest over a mode flag. | +| [Strategy](patterns/behavioral/strategy/) | Behavioral (GoF) | 🔄 prefer alternative | Make an algorithm interchangeable at runtime without the caller knowing which variant it got. | +| [Template Method](patterns/behavioral/template_method/) | Behavioral (GoF) | 🔄 prefer alternative | Fix an algorithm's skeleton while letting callers vary individual steps. | +| [Visitor](patterns/behavioral/visitor/) | Behavioral (GoF) | 🔄 prefer alternative | Run a new operation over every node of an object structure without adding a method to every node class. | +| [Async Producer/Consumer](patterns/modern/async_producer_consumer/) | Modern Python | ⚠️ use with care | Decouple work generation from work processing under asyncio, with bounded memory and clean shutdown. | +| [Context Manager](patterns/modern/context_manager/) | Modern Python | ✅ pythonic | Guarantee acquire/release pairing around a block of code, even when it raises. | +| [Dependency Injection](patterns/modern/dependency_injection/) | Modern Python | ✅ pythonic | Hand an object its collaborators instead of letting it construct them, so they can be swapped — above all in tests. | +| [Registry](patterns/modern/registry/) | Modern Python | ✅ pythonic | Let implementations announce themselves by name, so dispatch is a lookup instead of an if/elif ladder. | +| [Repository](patterns/modern/repository/) | Modern Python | ⚠️ use with care | Keep domain logic ignorant of how objects are stored, behind a collection-like interface. | - -More in [docs/](docs/index.md) — verdict definitions, MCP reference, contributing. diff --git a/docs/how-to-read-this-repo.md b/docs/how-to-read-this-repo.md deleted file mode 100644 index 59208dc..0000000 --- a/docs/how-to-read-this-repo.md +++ /dev/null @@ -1,39 +0,0 @@ -# How to read this repo - -Every pattern is a self-contained module in `patterns///`: - -| Part | Job | -|---|---| -| `README.md` | YAML frontmatter (machine-readable metadata) + a ten-line front door: problem, verdict, map of the folders below | -| `pattern/` | The pattern itself as importable, typed library code — what you `from patterns.. import` | -| `docs/fundamentals.md` | What the pattern *is*: intent, participants, mechanism, when/when-not — including the classic (GoF/Java) form as an annotated listing, diffed against the Python form | -| `docs/implementation.md` | How to introduce the pattern into a real system: the smell, the steps, the idioms, the pitfalls | -| `docs/examples.md` | Cited external usages — stdlib, major OSS, articles — the extra resources to pull during design and review | -| `examples//` | A runnable mini-project that imports `pattern/` and puts it to work in a realistic domain (more can be added over time) | -| `tests/` | Behavioral tests for the pattern code and each mini-project | - -## Usage contract - -```python -from patterns.behavioral.chain_of_responsibility import Chain -``` - -```bash -uv run python -m patterns.behavioral.chain_of_responsibility.examples.ticket_escalation.main -``` - -## Where to start - -- Reading for education: start with `principle/composition_over_inheritance`, - then any pattern whose *symptom* you recognize (the frontmatter lists them); - read `docs/fundamentals.md` first, the code second. -- Solving a problem now: search the catalog through the [MCP server](mcp.md) - (`recommend_pattern`) or skim the README table's "problem it solves" column, - then go straight to that unit's `docs/implementation.md`. - -## Groups - -`principle` · `python` (patterns native to the language, from -[python-patterns.guide](https://python-patterns.guide/)) · `creational` / -`structural` / `behavioral` (the GoF 23) · `modern` (post-GoF additions: -DI, Repository, Context Manager, Registry, async producer/consumer). diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index c4dcea0..0000000 --- a/docs/index.md +++ /dev/null @@ -1,6 +0,0 @@ -# Documentation - -- [How to read this repo](how-to-read-this-repo.md) — the module anatomy and where to start -- [Verdicts](verdicts.md) — what ✅ / ⚠️ / 🔄 mean, and who decides -- [MCP server](mcp.md) — connect agents to the catalog -- [Contributing](contributing.md) — adding or improving a pattern unit diff --git a/docs/mcp.md b/docs/mcp.md deleted file mode 100644 index fa59d44..0000000 --- a/docs/mcp.md +++ /dev/null @@ -1,59 +0,0 @@ -# MCP server - -The catalog ships as an MCP server so agents can search the docs, read the -reference code, execute the examples, and get pattern recommendations with -honest verdicts attached. - -## Connect - -From a checkout: - -```bash -claude mcp add design-patterns -- uv run --directory /path/to/python-design-patterns python-design-patterns-mcp -``` - -Once published to PyPI: - -```bash -claude mcp add design-patterns -- uvx python-design-patterns-mcp -``` - -Remote/HTTP (Streamable HTTP on `/mcp`): - -```bash -python-design-patterns-mcp --http --host 127.0.0.1 --port 8734 -``` - -## Tools - -| Tool | What it does | -|---|---| -| `list_patterns(group?, verdict?)` | Catalog listing, filterable | -| `get_pattern(pattern_id)` | One pattern's metadata and README prose | -| `search_patterns(query, limit?)` | BM25 full-text search over names, aliases, problems, symptoms, prose | -| `get_pattern_docs(pattern_id, doc)` | A pattern's teaching doc: `fundamentals`, `implementation`, or `examples` | -| `list_examples(pattern_id)` | A pattern's runnable mini-projects | -| `run_example(pattern_id, example)` | Executes a mini-project in a sandboxed subprocess; returns real stdout | -| `read_source(pattern_id)` | A pattern's own implementation (`pattern/` package) | -| `recommend_pattern(problem_statement, limit?)` | Ranked candidates with caveats; `prefer-alternative` verdicts tell you what to write instead | - -Every pattern follows three access levels: scan docs -(`get_pattern_docs`) → run a use case (`list_examples` + `run_example`) → -read the source (`read_source`). - -## Resources - -- `catalog://index` — the whole catalog as JSON -- `pattern:///` — one pattern's prose -- `pattern:////docs/` — one pattern's teaching doc - -## Prompts - -`refactor_toward(pattern_id, code)` · `explain_pattern(pattern_id, audience?)` · `choose_pattern(problem)` - -## Sandbox contract - -`run_example` executes only packages resolved from the catalog index — the -`(id, example)` pair is a dictionary lookup, never joined into a path. The -subprocess runs `python -I` in a temp cwd with a scrubbed environment, a 10s -timeout, and 64KB output caps. There is no arbitrary-code-execution tool. diff --git a/docs/verdicts.md b/docs/verdicts.md deleted file mode 100644 index 958a855..0000000 --- a/docs/verdicts.md +++ /dev/null @@ -1,15 +0,0 @@ -# Verdicts - -Every unit's frontmatter carries one verdict — the catalog's honest answer to -"should I write this in Python?" - -| Verdict | Meaning | -|---|---| -| ✅ `pythonic` | Use it as shown in the unit's `pattern/` package; the pattern (in its Python form) is what we'd genuinely recommend. | -| ⚠️ `use-with-care` | Legitimate uses exist, but each caveat in the frontmatter names a concrete failure mode. Read them first. | -| 🔄 `prefer-alternative` | The honest answer is usually "don't". The classic form appears in `docs/fundamentals.md` for study; the unit's `pattern/` package exports the alternative to write instead (e.g. Singleton → module global, Visitor → `functools.singledispatch`). | - -Where [python-patterns.guide](https://python-patterns.guide/) has a chapter, -its verdict wins and the unit links it. Where it doesn't, we reason from the -same principles: composition over inheritance, callables over class -hierarchies, the language's own features over ceremony. diff --git a/src/design_patterns/readme_table.py b/src/design_patterns/readme_table.py index d95a43a..90884c1 100644 --- a/src/design_patterns/readme_table.py +++ b/src/design_patterns/readme_table.py @@ -33,17 +33,14 @@ def render_table() -> str: catalog = load_catalog() - lines: list[str] = [] + lines = ["\n| Pattern | Group | Verdict | Problem it solves |", "|---|---|---|---|"] for group in _GROUP_ORDER: members = [p for p in catalog.patterns if p.group == group] - if not members: - continue - lines.append(f"\n### {_GROUP_TITLES[group]}\n") - lines.append("| Pattern | Verdict | Problem it solves |") - lines.append("|---|---|---|") for p in sorted(members, key=lambda p: p.slug): link = f"[{p.name}](patterns/{p.id}/)" - lines.append(f"| {link} | {_VERDICT_BADGES[p.verdict]} | {p.problem} |") + lines.append( + f"| {link} | {_GROUP_TITLES[group]} | {_VERDICT_BADGES[p.verdict]} | {p.problem} |" + ) return "\n".join(lines) + "\n"