Jesse A. Green / notebook

python-patterns Admin & Contributor Guide

Generated June 2026 from faif/python-patterns at 74151cf

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

This guide is for maintainers, contributors, and architects working on the python-patterns repository — as opposed to developers simply reading the patterns (covered in the User Guide). It documents how the collection is structured, how its self-testing machinery works, the toolchain and CI that keep it consistent, the contribution contract, and the known issues and improvement opportunities surfaced by the project's own issue tracker.


1. Executive Summary

python-patterns is a teaching catalog, not a deployable application or a published library. Its "product" is a set of ~33 self-contained Python files, each demonstrating one design pattern or idiom, grouped by the classic Gang of Four taxonomy plus a few project-specific categories.

Aspect Reality
What it is A curated reference collection of design-pattern implementations in Python
What it is not Not a service, not a CLI tool, not an importable dependency library
Runtime dependencies None — every pattern is standard-library only
The "build" output There is none; the source files are the deliverable
The "tests" Embedded doctests in every pattern file, plus a parallel tests/ suite of pytest unit tests
Quality gates black, isort, flake8, mypy, pyupgrade, codespell, run via tox and Travis CI
Supported Python 3.10 – 3.13 (requires-python = ">=3.10")
License None declared in this revision (see §11 Known Issues — this is an open tracker item)

Important

Because there is no service and no published package, the operational concerns here are entirely about repository hygiene: keeping every pattern consistent in style, ensuring every documented example actually runs, and managing contributions. There is nothing to deploy, monitor, scale, or secure at runtime. This guide reframes the usual "deployment / operations" chapters accordingly.


2. Architecture

The repository has two architectures worth understanding: the content taxonomy (how patterns are organized) and the self-verification architecture (how every example stays correct).

2.1 Content taxonomy

patterns/
├── creational/      7 patterns — object creation
│   ├── abstract_factory.py  borg.py  builder.py  factory.py
│   ├── lazy_evaluation.py    pool.py  prototype.py
│   └── viz/                  rendered .png class diagrams (one per pattern)
├── structural/      10 patterns — object/class composition
│   ├── 3-tier.py  adapter.py  bridge.py  composite.py  decorator.py
│   ├── facade.py  flyweight.py  flyweight_with_metaclass.py
│   ├── front_controller.py  mvc.py  proxy.py
│   └── viz/
├── behavioral/      17 patterns — communication & responsibility
│   ├── catalog.py  chain_of_responsibility.py  chaining_method.py
│   ├── command.py  iterator.py  iterator_alt.py  mediator.py
│   ├── memento.py  observer.py  publish_subscribe.py  registry.py
│   ├── servant.py  specification.py  state.py  strategy.py
│   ├── template.py  visitor.py
│   └── viz/
├── fundamental/     1 pattern — foundational idioms
│   └── delegation_pattern.py
├── other/           3 non-GoF patterns
│   ├── blackboard.py  graph_search.py
│   └── hsm/hsm.py   (hierarchical state machine + its own exceptions)
├── dependency_injection.py   design-for-testability (3 variants)
└── __init__.py

Each viz/ subfolder holds pre-rendered *.py.png class diagrams (one per pattern) used by the README. These are generated artifacts, not source.

Note

The taxonomy is deliberately the same one the Gang of Four book uses, so the repository doubles as a navigable index into that canon. The fundamental, design-for-testability (dependency_injection.py), and other buckets extend it with idioms the GoF set does not cover.

2.2 Self-verification architecture — the core design decision

The single most important architectural choice in this repository is that every pattern file is its own test. There is no separate "examples vs. tests" split for the demonstration code; the demonstration is a test.

┌─────────────────────────────────────────────┐
│  patterns/<category>/<name>.py               │
│                                              │
│  """module docstring — the WHY + trade-offs"""│
│                                              │
│  class / function implementations            │
│                                              │
│  def main():                                 │
│      """                                     │
│      >>> usage example                       │   ← doctest: doc + example + test
│      expected output                         │
│      """                                     │
│                                              │
│  if __name__ == "__main__":                  │
│      import doctest                           │
│      doctest.testmod()                       │   ← runs the doctest on execution
└─────────────────────────────────────────────┘
            │                        │
            ▼                        ▼
   python <file>.py          pytest --doctest-modules patterns/
   (runs that file's          (discovers and runs EVERY pattern's
    doctest, silent on         doctest in one pass)
    success)

In parallel, a conventional unit-test suite mirrors the pattern tree:

tests/
├── behavioral/    test_catalog.py, test_mediator.py, test_memento.py,
│                  test_observer.py, test_publish_subscribe.py, test_servant.py,
│                  test_state.py, test_strategy.py, test_visitor.py
├── creational/    test_abstract_factory.py, test_borg.py, test_builder.py,
│                  test_factory.py, test_lazy.py, test_pool.py, test_prototype.py
├── structural/    test_adapter.py, test_bridge.py, test_decorator.py,
│                  test_facade.py, test_flyweight.py, test_mvc.py, test_proxy.py
├── fundamental/   test_delegation.py
└── test_hsm.py

Important

Two test surfaces coexist: doctests inside patterns/ (which verify the documented usage examples) and pytest unit tests inside tests/ (which verify behavior more rigorously). The pyproject.toml addopts wires both together with --doctest-modules. When you change a pattern, you may have to update both the in-file docstring and the matching tests/ file.

2.3 Negative space — what the architecture intentionally omits

  • No runtime dependencies. pyproject.toml's dependencies array is empty. This is a hard design constraint: every pattern must work on a clean standard-library Python so it can be copied out in isolation.
  • No Singleton, God Object, or deep-inheritance examples. These are listed in the README as anti-patterns and are deliberately not implemented.
  • No interpreter.py. Although an "interpreter" row appears in the README's behavioral table, no such file exists in this revision (commit 74151cf). Treat the on-disk file tree, not the README table, as the source of truth for what exists.
  • No build/packaging output. There is no dist/, no wheel pipeline intended for PyPI distribution; the editable install exists only to support testing.

3. System Requirements

Component Requirement Source of truth
Python >=3.10 (CI runs 3.12) pyproject.toml requires-python; .travis.yml python: "3.12"
Standard library only No runtime deps pyproject.toml dependencies = []
Git For cloning / legacy tag access README
Dev toolchain mypy, pyupgrade, pytest>=6.2.0, pytest-cov>=2.11.0, pytest-randomly>=3.1.0, black>=25.1.0, build>=1.2.2, isort>=5.7.0, flake8>=7.1.0, tox>=4.25.0, pipx>=1.7.1 pyproject.toml [project.optional-dependencies].dev

Note

The requirements-dev.txt file lists the dev tools unpinned (flake8, black, isort, pytest, pytest-randomly, mypy, pyupgrade, tox). The authoritative pinned versions live in pyproject.toml's dev extra. When the two drift, prefer pyproject.toml.


4. Development Environment Setup

git clone https://github.com/faif/python-patterns.git
cd python-patterns
python -m venv venv
source venv/bin/activate          # Windows: venv\Scripts\activate
python -m pip install -e ".[dev]"
  • [ ] Python 3.10+ active (python --version)
  • [ ] Virtual environment created and activated
  • [ ] pip install -e ".[dev]" completed without errors
  • [ ] pytest runs green
  • [ ] ./lint.sh runs clean (bash-compatible shell)

Warning

The Makefile targets (make pyupgrade, make pylinter) assume an embedded venv at ./venv, not a virtualenv elsewhere — the checkvenv target hard-fails if $VIRTUAL_ENV is unset, and pyupgrade looks for venv/bin/pip-compile. Create the venv exactly as python -m venv venv for the Makefile to work as written.


5. Testing

5.1 Test configuration

The pytest configuration lives in pyproject.toml under [tool.pytest.ini_options]:

testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
addopts = "--doctest-modules --randomly-seed=1234 --cov=patterns --cov-report=term-missing"
doctest_optionflags = ["ELLIPSIS", "NORMALIZE_WHITESPACE"]
log_level = "INFO"

Key points:

  • --doctest-modules makes pytest collect and run the doctests embedded in the pattern files, in addition to the tests/ unit tests.
  • --randomly-seed=1234 (from pytest-randomly) fixes the test execution order so randomized ordering is reproducible.
  • --cov=patterns produces a coverage report for the patterns/ package, with missing lines listed in the terminal.
  • doctest_optionflags enables ELLIPSIS (so ... matches variable output like memory addresses) and NORMALIZE_WHITESPACE.

Note

A second, minimal pytest config exists at pytest_local.ini (addopts = -q, testpaths = tests). It is a lightweight local override for running just the unit tests quietly, without coverage or doctest collection.

5.2 Running the tests

Everything (coverage + doctests)

pytest

Just the unit tests, quietly

pytest -c pytest_local.ini

Just the in-file doctests

pytest --doctest-modules patterns/

A single pattern's doctest, verbose

python -m doctest -v patterns/creational/factory.py

5.3 Coverage configuration

[tool.coverage.*] in pyproject.toml enables branch coverage with a per-test dynamic context and excludes boilerplate lines from the report:

[tool.coverage.run]
branch = true
source = ["./"]
relative_files = true
parallel = true
dynamic_context = "test_function"

[tool.coverage.report]
exclude_lines = [
    "def __repr__", "if self\\.debug", "raise AssertionError",
    "raise NotImplementedError", "if 0:",
    "if __name__ == .__main__.:", "@(abc\\.)?abstractmethod",
]

Tip

The exclude_lines patterns mean abstract methods, __repr__, and the __main__ guards do not count against coverage — so a green coverage report reflects the pattern logic, not the scaffolding around it. An HTML report is configured to land in coverage_html_report/.


6. Quality Toolchain & CI

6.1 The linting pipeline

The canonical lint sequence is captured in lint.sh:

codespell --quiet-level=2 ./patterns       # spelling
flake8 ./patterns --count --show-source --statistics
isort --profile black ./patterns           # import ordering
tox                                         # tests across env
mypy --ignore-missing-imports ./patterns || true
pytest ./patterns
pytest --doctest-modules ./patterns || true
shopt -s globstar && pyupgrade --py312-plus ./patterns/*.py
Tool Role Config location
black Code formatting (line length 88 via Makefile) invoked directly / make pylinter
isort Import sorting, --profile black lint.sh, Makefile
flake8 Linting [tool.flake8] in pyproject.toml: max-line-length = 120, ignore = ["E266","E731","W503"], exclude = ["venv*"]
mypy Static type checking [tool.mypy]: python_version = "3.12", ignore_missing_imports = true
pyupgrade Modernize syntax to --py312-plus lint.sh, Makefile
codespell Catch typos .codespellignore

Caution

There are two different flake8 line-length settings in play. pyproject.toml's [tool.flake8] sets max-line-length = 120, while the Makefile's pylinter target passes --max-line-length 88 (matching black's default) with a broader --select/--ignore set. The Makefile path is the stricter one. When reconciling lint failures, know which entry point produced them.

6.2 Continuous integration

CI runs on Travis CI (.travis.yml):

os: linux
dist: noble
language: python
jobs:
  include:
    - python: "3.12"
      env: TOXENV=py312
install:
  - pip install codecov tox
script:
  - tox
after_success:
  - codecov

The pipeline installs tox + codecov, runs tox, and uploads coverage to Codecov on success.

6.3 tox

tox configuration lives inline in pyproject.toml under [tool.tox] as legacy_tox_ini:

[tox]
envlist = py312,cov-report
skip_missing_interpreters = true
usedevelop = true

Note

Most of the per-environment tox command detail (the [testenv] and [testenv:cov-report] blocks) is commented out in the current legacy_tox_ini. The active configuration declares the py312 and cov-report environments with usedevelop = true (editable install) and skip_missing_interpreters = true. If you are revamping CI, this commented block is the historical reference for what the full matrix used to run (flake8, coverage run -m pytest --doctest-modules, then a combined coverage report).


7. Module & Pattern Reference (selected internals)

Rather than re-document every pattern (the User Guide catalogs all of them), this section highlights the implementations whose internals are notable for a maintainer.

Pattern Internal technique worth knowing
creational/borg.py Reassigns self.__dict__ to a class-level _shared_state dict — the entire Monostate trick is one line in __init__
creational/lazy_evaluation.py Two caching strategies: a descriptor (lazy_property.__get__) and a decorator (lazy_property2) that stash the result in the instance __dict__ / a _lazy__<name> attribute
structural/flyweight.py Pools instances in a weakref.WeakValueDictionary keyed by value+suit, overriding __new__ (not __init__)
structural/flyweight_with_metaclass.py Same pooling via a FlyweightMeta(type) metaclass intercepting __call__; verified by asserts, not a doctest
behavioral/registry.py A RegistryHolder(type) metaclass auto-registers every subclass in a shared REGISTRY dict at class-creation time
behavioral/memento.py A memento() closure snapshots obj.__dict__; the @Transactional descriptor rolls back on exception
behavioral/strategy.py Uses a descriptor (DiscountStrategyValidator) with __set_name__/__set__/__get__ to validate the injected strategy
behavioral/visitor.py Dispatches by walking node.__class__.__mro__ to find a visit_<ClassName> method, falling back to generic_visit
structural/mvc.py The only file with a substantive executable __main__ — a Router plus sys.argv-driven command dispatch
other/hsm/hsm.py A nested FSM with four states and its own exception hierarchy (UnsupportedMessageType, UnsupportedState, UnsupportedTransition)

Tip

Several patterns lean on Python's data-model hooks (__new__, __getattr__, __get__/__set__/__set_name__, metaclasses). These are exactly the places where a "port from Java" would look different — and the module docstrings frequently call out the Python-specific simplification (e.g. "in Python the interface is simply a callable").

7.1 Error/exception inventory

The patterns raise a small, well-defined set of exceptions — useful when writing or reviewing tests:

Exception Message Location
NotImplementedError Must provide implementation in subclass. other/blackboard.py (abstract expert)
NotImplementedError You should implement this! structural/composite.py (Graphic.render)
KeyError No controller registered for path '{path}' structural/mvc.py (Router.resolve)
KeyError ... not in the model's item list. structural/mvc.py (ProductModel.get)
ValueError Invalid Value for Param: {param} behavioral/catalog.py (all four catalog classes)
ValueError Unsupported shape type behavioral/servant.py (GeometryTools)
ValueError Discount cannot be applied due to negative price resulting. {name} behavioral/strategy.py (DiscountStrategyValidator)
UnsupportedMessageType / UnsupportedState / UnsupportedTransition (no message) other/hsm/hsm.py

8. Code Conventions

The contribution guidelines in the README codify the house style:

  1. Module docstring required. Every pattern starts with a docstring describing the pattern, ideally with *What is this pattern about?, *What does this example do?, *Where is the pattern used practically?, *References:, and a *TL;DR line. structural/facade.py is cited as the gold-standard detailed example; behavioral/template.py as an acceptable shorter one.
  2. "Examples in Python ecosystem" sections are encouraged where real-world equivalents exist (e.g. Django Signals for Observer, ast.NodeVisitor for Visitor, Django middleware for Chain of Responsibility).
  3. Type hints throughout. Most files annotate parameters and return types; mypy runs in CI.
  4. Black formatting + isort ordering are mandatory before submitting.
  5. Doctests as examples. New patterns put their worked example in a main() docstring as a doctest so it is verified automatically.
  6. README update. When a pattern is added or changed, the corresponding README table/section must be updated to match.

Important

The README explicitly states the project's editorial stance: emphasize why you'd choose a pattern (and its trade-offs) over how to implement it. Contributions that add an exotic implementation without explaining when to reach for it run against the grain of the project.


9. Contribution Workflow

1. Implement / modify a pattern under patterns/<category>/<name>.py
2. Add a module docstring (why + example + references + TL;DR)
3. Add a main() doctest demonstrating usage
4. Add/adjust the mirror test under tests/<category>/test_<name>.py
5. Run quality gates:
     black .
     ./lint.sh         (bash-compatible shells)
     tox   or   tox -e ci37
6. Update the matching README section/table
7. Open a PR

Pre-submission checklist (from the README's "Travis CI" contribution note):

  • [ ] black . applied
  • [ ] tox (or tox -e ci37) passes
  • [ ] ./lint.sh clean (if you have a bash-compatible shell)
  • [ ] README updated to reflect the change

Note

The README also points contributors at CodeTriage for issue/PR triage, and notes that Python 2-compatible versions of some patterns are preserved on the legacy git tag — relevant if a contributor asks where Python 2 support went.


10. Security Analysis

There is no running service, no network surface, no authentication, no secrets, and no third-party runtime dependencies — so the conventional application threat model does not apply. The realistic security considerations are repository-and-supply-chain concerns:

Concern Assessment
Runtime dependency CVEs None — standard-library only, so there is no third-party runtime attack surface
Dev-toolchain supply chain The dev extra pulls black/flake8/mypy/tox/etc. from PyPI; pin versions (already done in pyproject.toml) and enable 2FA on the project's PyPI/GitHub accounts
CI token exposure Travis + Codecov integration — keep the Codecov upload token and any Travis secrets out of the repo; they are not in the source tree
Untrusted example input Patterns like mvc.py read sys.argv and graph_search.py/hsm.py process caller-supplied data, but they are illustrative; they perform no file/network/eval operations and are safe to run locally
Code-injection vectors No use of eval, exec, pickle.loads on untrusted data, subprocess, or os.system in the pattern files

Caution

The biggest governance gap is the missing LICENSE file (tracked as issue #143). Without an explicit license, the default is "all rights reserved," which complicates downstream reuse despite the educational intent. This is a legal/compliance issue rather than a runtime security one, but it is the most consequential open item for anyone copying code out of the repo.


11. Known Issues & Improvement Opportunities

These are drawn from the project's own open GitHub issues (community-reported, real signal):

# Title Type Maintainer note
#143 Needs a COPYRIGHT/LICENSE file Governance Highest-impact: blocks clean downstream reuse
#468 Visualizing pattern relationships with Mermaid diagrams Enhancement README already uses some mermaid graphs; this would extend them
#373 Support for type hints Enhancement Largely already in place; tracks completing coverage
#433 Remove deprecated Python versions Maintenance Aligns with requires-python >= 3.10
#333 Notebook version of the repo Enhancement Alternative presentation of the catalog
#277 Code coverage (infrastructure) Infrastructure Coverage is configured via pytest-cov + Codecov; this tracks surfacing/raising it
#278 Add DAO / DTO patterns New content Would extend the catalog
#245 Add an automation-testing pattern New content Fits the design-for-testability bucket
#279 List anti-patterns in README Docs Partially done — the README already has an Anti-Patterns section
#120 3-tier.py and mvc.py are almost identical Refactor/dedup Both model a product catalog; worth differentiating or cross-referencing
#244 Functional programming patterns (question) Discussion Scope question for the catalog
#437 Replace setuptools with pyproject.toml Build Closed — the project now uses pyproject.toml with the setuptools build backend

Tip

Issue #120 (3-tier vs. MVC near-duplication) is a genuine code-quality observation: both files use the same milk/eggs/cheese product model and a similar layered structure. If you touch either, consider adding a comment cross-referencing the other and clarifying the strict vs. non-strict layering distinction that motivates having both.

11.1 Additional maintainer-visible gaps

  • README vs. file tree drift. The README's behavioral table lists an interpreter pattern, but no interpreter.py exists in this revision. Either add the implementation or correct the table. (Per the README's own contribution rule #6, the table should track the files.)
  • Dev dependency duplication. requirements-dev.txt (unpinned) and pyproject.toml's dev extra (pinned) list overlapping but not identical tool sets. Consolidating to a single source would prevent drift.
  • Two flake8 line lengths (120 in pyproject.toml, 88 in the Makefile) — reconcile to one.
  • Mostly-commented tox matrix. The active legacy_tox_ini declares environments but leaves the command detail commented out; the real test commands effectively come from pyproject.toml's addopts. Consider making tox self-contained again.

12. Quick Reference

Commands

Task Command
Clone git clone https://github.com/faif/python-patterns.git
Dev install python -m pip install -e ".[dev]"
Run one pattern (verify) python patterns/<cat>/<name>.py
Run one pattern (verbose) python -m doctest -v patterns/<cat>/<name>.py
Run all doctests pytest --doctest-modules patterns/
Full test + coverage pytest
Quick unit tests pytest -c pytest_local.ini
Format black .
Full lint sequence ./lint.sh
Lint via Makefile make pylinter (requires ./venv)
CI environment tox
Python 2 versions git checkout legacy

Key files

File Purpose
pyproject.toml Single source of truth: build, deps, pytest, coverage, mypy, flake8, tox
pytest_local.ini Lightweight local pytest config (-q, tests only)
lint.sh Canonical lint + test sequence
Makefile pylinter / pyupgrade targets (assume ./venv)
.travis.yml CI: Python 3.12, tox, Codecov
requirements-dev.txt Unpinned dev tool list (prefer pyproject.toml dev extra)
.codespellignore Spelling-checker allowlist
README.md Catalog tables, mermaid diagrams, anti-patterns, contribution rules

Configuration facts

Setting Value Source
Minimum Python 3.10 pyproject.toml
CI Python 3.12 .travis.yml
Runtime dependencies none pyproject.toml
Random test seed 1234 pyproject.toml addopts
Coverage target package patterns pyproject.toml
flake8 line length 120 (pyproject) / 88 (Makefile) both
mypy Python version 3.12, ignore_missing_imports pyproject.toml
Build backend setuptools.build_meta pyproject.toml
Packaged package patterns pyproject.toml [tool.setuptools]