Full Code of faif/python-patterns for AI

master 74151cfec276 cached
87 files
164.7 KB
42.8k tokens
619 symbols
1 requests
Download .txt
Repository: faif/python-patterns
Branch: master
Commit: 74151cfec276
Files: 87
Total size: 164.7 KB

Directory structure:
gitextract_isa67v9p/

├── .codespellignore
├── .github/
│   └── workflows/
│       ├── lint_pr.yml
│       └── lint_python.yml
├── .gitignore
├── .travis.yml
├── Makefile
├── README.md
├── config_backup/
│   ├── .coveragerc
│   ├── setup.cfg
│   └── tox.ini
├── lint.sh
├── patterns/
│   ├── __init__.py
│   ├── behavioral/
│   │   ├── __init__.py
│   │   ├── 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
│   ├── creational/
│   │   ├── __init__.py
│   │   ├── abstract_factory.py
│   │   ├── borg.py
│   │   ├── builder.py
│   │   ├── factory.py
│   │   ├── lazy_evaluation.py
│   │   ├── pool.py
│   │   └── prototype.py
│   ├── dependency_injection.py
│   ├── fundamental/
│   │   ├── __init__.py
│   │   └── delegation_pattern.py
│   ├── other/
│   │   ├── __init__.py
│   │   ├── blackboard.py
│   │   ├── graph_search.py
│   │   └── hsm/
│   │       ├── __init__.py
│   │       └── hsm.py
│   └── structural/
│       ├── 3-tier.py
│       ├── __init__.py
│       ├── adapter.py
│       ├── bridge.py
│       ├── composite.py
│       ├── decorator.py
│       ├── facade.py
│       ├── flyweight.py
│       ├── flyweight_with_metaclass.py
│       ├── front_controller.py
│       ├── mvc.py
│       └── proxy.py
├── pyproject.toml
├── pytest_local.ini
├── requirements-dev.txt
└── tests/
    ├── __init__.py
    ├── 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
    ├── fundamental/
    │   └── test_delegation.py
    ├── structural/
    │   ├── test_adapter.py
    │   ├── test_bridge.py
    │   ├── test_decorator.py
    │   ├── test_facade.py
    │   ├── test_flyweight.py
    │   ├── test_mvc.py
    │   └── test_proxy.py
    └── test_hsm.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .codespellignore
================================================
__pycache__
*.pyc
.idea
*.egg-info/
.tox/
env/
venv/
.env
.venv
.vscode/
.python-version
.coverage
build/
dist/

================================================
FILE: .github/workflows/lint_pr.yml
================================================
name: lint_pull_request
on: [pull_request, push]
jobs:
  check_changes:
    runs-on: ubuntu-24.04
    outputs:
      has_python_changes: ${{ steps.changed-files.outputs.has_python_changes }}
      files: ${{ steps.changed-files.outputs.files }}
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0 # To get all history for git diff commands

      - name: Get changed Python files
        id: changed-files
        run: |
          if [ "${{ github.event_name }}" == "pull_request" ]; then
            # For PRs, compare against base branch
            CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRT origin/${{ github.base_ref }} HEAD | grep '\.py$' | grep -v "^setup\.py$" || echo "")
            # Check if setup.py specifically changed
            SETUP_PY_CHANGED=$(git diff --name-only --diff-filter=ACMRT origin/${{ github.base_ref }} HEAD | grep "^setup\.py$" || echo "")
            if [ ! -z "$SETUP_PY_CHANGED" ]; then
              CHANGED_FILES="$CHANGED_FILES $SETUP_PY_CHANGED"
            fi
          else
            # For pushes, use the before/after SHAs
            CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRT ${{ github.event.before }} ${{ github.event.after }} | grep '\.py$' | grep -v "^setup\.py$" || echo "")
            # Check if setup.py specifically changed
            SETUP_PY_CHANGED=$(git diff --name-only --diff-filter=ACMRT ${{ github.event.before }} ${{ github.event.after }} | grep "^setup\.py$" || echo "")
            if [ ! -z "$SETUP_PY_CHANGED" ]; then
              CHANGED_FILES="$CHANGED_FILES $SETUP_PY_CHANGED"
            fi
          fi

          # Check if any Python files were changed and set the output accordingly
          if [ -z "$CHANGED_FILES" ]; then
            echo "No Python files changed"
            echo "has_python_changes=false" >> $GITHUB_OUTPUT
            echo "files=" >> $GITHUB_OUTPUT
          else
            echo "Changed Python files: $CHANGED_FILES"
            echo "has_python_changes=true" >> $GITHUB_OUTPUT
            # Use proper delimiter formatting for GitHub Actions
            FILES_SINGLE_LINE=$(echo "$CHANGED_FILES" | tr '\n' ' ' | sed 's/[[:space:]]\+/ /g' | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
            echo "files=$FILES_SINGLE_LINE" >> $GITHUB_OUTPUT
          fi

      - name: PR information
        if: ${{ github.event_name == 'pull_request' }}
        run: |
          if [[ "${{ steps.changed-files.outputs.has_python_changes }}" == "true" ]]; then
            echo "This PR contains Python changes that will be linted."
          else
            echo "This PR contains no Python changes, but still requires manual approval."
          fi

  lint:
    needs: check_changes
    if: ${{ needs.check_changes.outputs.has_python_changes == 'true' }}
    runs-on: ubuntu-24.04
    strategy:
      fail-fast: false
      matrix:
        tool: [flake8, format, mypy, pytest, pyupgrade, tox]
    steps:
      # Additional check to ensure we have Python files before proceeding
      - name: Verify Python changes
        run: |
          if [[ "${{ needs.check_changes.outputs.has_python_changes }}" != "true" ]]; then
            echo "No Python files were changed. Skipping linting."
            exit 0
          fi

      - uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - uses: actions/setup-python@v6
        with:
          python-version: 3.12

      - uses: actions/cache@v5
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-pip-${{ hashFiles('requirements-dev.txt') }}
          restore-keys: |
            ${{ runner.os }}-pip-

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements-dev.txt

      # Flake8 linting
      - name: Lint with flake8
        if: ${{ matrix.tool == 'flake8' }}
        id: flake8
        run: |
          echo "Linting files: ${{ needs.check_changes.outputs.files }}"
          flake8 ${{ needs.check_changes.outputs.files }} --count --show-source --statistics

      # Format checking with isort and black
      - name: Format check
        if: ${{ matrix.tool == 'format' }}
        id: format
        run: |
          echo "Checking format with isort for: ${{ needs.check_changes.outputs.files }}"
          isort --profile black --check ${{ needs.check_changes.outputs.files }}
          echo "Checking format with black for: ${{ needs.check_changes.outputs.files }}"
          black --check ${{ needs.check_changes.outputs.files }}

      # Type checking with mypy
      - name: Type check with mypy
        if: ${{ matrix.tool == 'mypy' }}
        id: mypy
        run: |
          echo "Type checking: ${{ needs.check_changes.outputs.files }}"
          mypy --ignore-missing-imports ${{ needs.check_changes.outputs.files }}

      # Run tests with pytest
      - name: Run tests with pytest
        if: ${{ matrix.tool == 'pytest' }}
        id: pytest
        run: |
          echo "Running pytest discovery..."
          python -m pytest --collect-only -v

          # First run any test files that correspond to changed files
          echo "Running tests for changed files..."
          changed_files="${{ needs.check_changes.outputs.files }}"

          # Extract module paths from changed files
          modules=()
          for file in $changed_files; do
            # Convert file path to module path (remove .py and replace / with .)
            if [[ $file == patterns/* ]]; then
              module_path=${file%.py}
              module_path=${module_path//\//.}
              modules+=("$module_path")
            fi
          done

          # Run tests for each module
          for module in "${modules[@]}"; do
            echo "Testing module: $module"
            python -m pytest -xvs tests/ -k "$module" || true
          done

          # Then run doctests on the changed files
          echo "Running doctests for changed files..."
          for file in $changed_files; do
            if [[ $file == *.py ]]; then
              echo "Running doctest for $file"
              python -m pytest --doctest-modules -v $file || true
            fi
          done

      # Check Python version compatibility
      - name: Check Python version compatibility
        if: ${{ matrix.tool == 'pyupgrade' }}
        id: pyupgrade
        run: pyupgrade --py312-plus ${{ needs.check_changes.outputs.files }}

      # Run tox
      - name: Run tox
        if: ${{ matrix.tool == 'tox' }}
        id: tox
        run: |
          echo "Running tox integration for changed files..."
          changed_files="${{ needs.check_changes.outputs.files }}"

          # Create a temporary tox configuration that extends the original one
          echo "[tox]" > tox_pr.ini
          echo "envlist = py312" >> tox_pr.ini
          echo "skip_missing_interpreters = true" >> tox_pr.ini

          echo "[testenv]" >> tox_pr.ini
          echo "setenv =" >> tox_pr.ini
          echo "    COVERAGE_FILE = .coverage.{envname}" >> tox_pr.ini
          echo "deps =" >> tox_pr.ini
          echo "    -r requirements-dev.txt" >> tox_pr.ini
          echo "allowlist_externals =" >> tox_pr.ini
          echo "    pytest" >> tox_pr.ini
          echo "    coverage" >> tox_pr.ini
          echo "    python" >> tox_pr.ini
          echo "commands =" >> tox_pr.ini

          # Check if we have any implementation files that changed
          pattern_files=0
          test_files=0

          for file in $changed_files; do
            if [[ $file == patterns/* ]]; then
              pattern_files=1
            elif [[ $file == tests/* ]]; then
              test_files=1
            fi
          done

          # Only run targeted tests, no baseline
          echo "    # Run specific tests for changed files" >> tox_pr.ini

          has_tests=false

          # Add coverage-focused test commands
          for file in $changed_files; do
            if [[ $file == *.py ]]; then
              # Run coverage tests for implementation files
              if [[ $file == patterns/* ]]; then
                module_name=$(basename $file .py)
                
                # Get the pattern type (behavioral, structural, etc.)
                if [[ $file == patterns/behavioral/* ]]; then
                  pattern_dir="behavioral"
                elif [[ $file == patterns/creational/* ]]; then
                  pattern_dir="creational"  
                elif [[ $file == patterns/structural/* ]]; then
                  pattern_dir="structural"
                elif [[ $file == patterns/fundamental/* ]]; then
                  pattern_dir="fundamental"
                elif [[ $file == patterns/other/* ]]; then
                  pattern_dir="other"
                else
                  pattern_dir=""
                fi
                
                echo "    # Testing $file" >> tox_pr.ini
                
                # Check if specific test exists
                if [ -n "$pattern_dir" ]; then
                  test_path="tests/${pattern_dir}/test_${module_name}.py"
                  echo "    if [ -f \"${test_path}\" ]; then echo \"Test file ${test_path} exists: true\" && coverage run -m pytest -xvs --cov=patterns --cov-append ${test_path}; else echo \"Test file ${test_path} exists: false\"; fi" >> tox_pr.ini
                  
                  # Also try to find any test that might include this module
                  echo "    coverage run -m pytest -xvs --cov=patterns --cov-append tests/${pattern_dir}/ -k \"${module_name}\" --no-header" >> tox_pr.ini
                fi
                
                # Run doctests for the file
                echo "    coverage run -m pytest --doctest-modules -v --cov=patterns --cov-append $file" >> tox_pr.ini
                
                has_tests=true
              fi
              
              # Run test files directly if modified
              if [[ $file == tests/* ]]; then
                echo "    coverage run -m pytest -xvs --cov=patterns --cov-append $file" >> tox_pr.ini
                has_tests=true
              fi
            fi
          done

          # If we didn't find any specific tests to run, mention it
          if [ "$has_tests" = false ]; then
            echo "    python -c \"print('No specific tests found for changed files. Consider adding tests.')\"" >> tox_pr.ini
            # Add a minimal test to avoid failure, but ensure it generates coverage data
            echo "    coverage run -m pytest -xvs --cov=patterns --cov-append -k \"not integration\" --no-header" >> tox_pr.ini
          fi

          # Add coverage report command
          echo "    coverage combine" >> tox_pr.ini
          echo "    coverage report -m" >> tox_pr.ini

          # Run tox with the custom configuration
          echo "Running tox with custom PR configuration..."
          echo "======================== TOX CONFIG ========================"
          cat tox_pr.ini
          echo "==========================================================="
          tox -c tox_pr.ini

  summary:
    needs: [check_changes, lint]
    # Run summary in all cases, regardless of whether lint job ran
    if: ${{ always() }}
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v6

      - name: Summarize results
        run: |
          echo "## Pull Request Lint Results" >> $GITHUB_STEP_SUMMARY
          if [[ "${{ needs.check_changes.outputs.has_python_changes }}" == "true" ]]; then
            echo "Linting has completed for all Python files changed in this PR." >> $GITHUB_STEP_SUMMARY
            echo "See individual job logs for detailed results." >> $GITHUB_STEP_SUMMARY
          else
            echo "No Python files were changed in this PR. Linting was skipped." >> $GITHUB_STEP_SUMMARY
          fi
          echo "" >> $GITHUB_STEP_SUMMARY
          echo "⚠️ **Note:** This PR still requires manual approval regardless of linting results." >> $GITHUB_STEP_SUMMARY


================================================
FILE: .github/workflows/lint_python.yml
================================================
name: lint_python
on: [pull_request, push]
jobs:
  lint_python:
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-python@v6
        with:
          python-version: 3.12
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install .[dev]
      - name: Lint with flake8
        run: flake8 ./patterns --count --show-source --statistics
        continue-on-error: true
      - name: Format check with isort and black
        run: |
          isort --profile black --check ./patterns
          black --check ./patterns
        continue-on-error: true
      - name: Type check with mypy
        run: mypy --ignore-missing-imports ./patterns || true
        continue-on-error: true
      - name: Run tests with pytest
        run: |
          pytest ./patterns
          pytest --doctest-modules ./patterns || true
        continue-on-error: true
      - name: Check Python version compatibility
        run: shopt -s globstar && pyupgrade --py312-plus ./patterns/**/*.py
        continue-on-error: true
      - name: Run tox
        run: tox
        continue-on-error: true


================================================
FILE: .gitignore
================================================
__pycache__
*.pyc
.idea
*.egg-info/
.tox/
env/
venv/
.env
.venv
.vscode/
.python-version
.coverage
.project
.pydevproject
/.pytest_cache/
build/
dist/


================================================
FILE: .travis.yml
================================================
os: linux
dist: noble
language: python

jobs:
  include:
    - python: "3.12"
      env: TOXENV=py312

cache:
  - pip

install:
  - pip install codecov tox

script:
  - tox

after_success:
  - codecov


================================================
FILE: Makefile
================================================
# REDNAFI
# This only works with embedded venv not virtualenv
# Install venv: python3.8 -m venv venv
# Activate venv: source venv/bin/activate

# Usage (line =black line length, path = action path, ignore= exclude folders)
# ------
# make pylinter [make pylinter line=88 path=.]
# make pyupgrade

path := .
line := 88
ignore := *env

all:
	@echo

.PHONY: checkvenv
checkvenv:
# raises error if environment is not active
ifeq ("$(VIRTUAL_ENV)","")
	@echo "Venv is not activated!"
	@echo "Activate venv first."
	@echo
	exit 1
endif

.PHONY: pyupgrade
pyupgrade: checkvenv
# checks if pip-tools is installed
ifeq ("$(wildcard venv/bin/pip-compile)","")
	@echo "Installing Pip-tools..."
	@pip install pip-tools
endif

ifeq ("$(wildcard venv/bin/pip-sync)","")
	@echo "Installing Pip-tools..."
	@pip install pip-tools
endif

# pip-tools
	# @pip-compile --upgrade requirements-dev.txt
	@pip-sync requirements-dev.txt


.PHONY: pylinter
pylinter: checkvenv
# checks if black is installed
ifeq ("$(wildcard venv/bin/black)","")
	@echo "Installing Black..."
	@pip install black
endif

# checks if isort is installed
ifeq ("$(wildcard venv/bin/isort)","")
	@echo "Installing Isort..."
	@pip install isort
endif

# checks if flake8 is installed
ifeq ("$(wildcard venv/bin/flake8)","")
	@echo -e "Installing flake8..."
	@pip install flake8
	@echo
endif

# black
	@echo "Applying Black"
	@echo "----------------\n"
	@black --line-length $(line) --exclude $(ignore) $(path)
	@echo

# isort
	@echo "Applying Isort"
	@echo "----------------\n"
	@isort --atomic --profile black $(path)
	@echo

# flake8
	@echo "Applying Flake8"
	@echo "----------------\n"
	@flake8 --max-line-length "$(line)" \
			--max-complexity "18" \
			--select "B,C,E,F,W,T4,B9" \
			--ignore "E203,E266,E501,W503,F403,F401,E402" \
			--exclude ".git,__pycache__,old, build, \
						dist, venv, .tox" $(path)


================================================
FILE: README.md
================================================
# python-patterns

A collection of design patterns and idioms in Python.

Remember that each pattern has its own trade-offs. And you need to pay attention more to why you're choosing a certain pattern than to how to implement it.

## Creational Patterns

> Patterns that deal with **object creation** — abstracting and controlling how instances are made.

```mermaid
graph LR
    Client -->|requests object| AbstractFactory
    AbstractFactory -->|delegates to| ConcreteFactory
    ConcreteFactory -->|produces| Product

    Builder -->|step-by-step| Director
    Director -->|returns| BuiltObject

    FactoryMethod -->|subclass decides| ConcreteProduct
    Pool -->|reuses| PooledInstance
```

| Pattern | Description |
|:-------:| ----------- |
| [abstract_factory](patterns/creational/abstract_factory.py) | use a generic function with specific factories |
| [borg](patterns/creational/borg.py) | a singleton with shared-state among instances |
| [builder](patterns/creational/builder.py) | instead of using multiple constructors, builder object receives parameters and returns constructed objects |
| [factory](patterns/creational/factory.py) | delegate a specialized function/method to create instances |
| [lazy_evaluation](patterns/creational/lazy_evaluation.py) | lazily-evaluated property pattern in Python |
| [pool](patterns/creational/pool.py) | preinstantiate and maintain a group of instances of the same type |
| [prototype](patterns/creational/prototype.py) | use a factory and clones of a prototype for new instances (if instantiation is expensive) |

## Structural Patterns

> Patterns that define **how classes and objects are composed** to form larger, flexible structures.

```mermaid
graph TD
    Client --> Facade
    Facade --> SubsystemA
    Facade --> SubsystemB
    Facade --> SubsystemC

    Client2 --> Adapter
    Adapter --> LegacyService

    Client3 --> Proxy
    Proxy -->|controls access to| RealSubject

    Component --> Composite
    Composite --> Leaf1
    Composite --> Leaf2
```

| Pattern | Description |
|:-------:| ----------- |
| [3-tier](patterns/structural/3-tier.py) | data<->business logic<->presentation separation (strict relationships) |
| [adapter](patterns/structural/adapter.py) | adapt one interface to another using a white-list |
| [bridge](patterns/structural/bridge.py) | a client-provider middleman to soften interface changes |
| [composite](patterns/structural/composite.py) | lets clients treat individual objects and compositions uniformly |
| [decorator](patterns/structural/decorator.py) | wrap functionality with other functionality in order to affect outputs |
| [facade](patterns/structural/facade.py) | use one class as an API to a number of others |
| [flyweight](patterns/structural/flyweight.py) | transparently reuse existing instances of objects with similar/identical state |
| [front_controller](patterns/structural/front_controller.py) | single handler requests coming to the application |
| [mvc](patterns/structural/mvc.py) | model<->view<->controller (non-strict relationships) |
| [proxy](patterns/structural/proxy.py) | an object funnels operations to something else |

## Behavioral Patterns

> Patterns concerned with **communication and responsibility** between objects.

```mermaid
graph LR
    Sender -->|sends event| Observer1
    Sender -->|sends event| Observer2

    Request --> Handler1
    Handler1 -->|passes if unhandled| Handler2
    Handler2 -->|passes if unhandled| Handler3

    Context -->|delegates to| Strategy
    Strategy -->|executes| Algorithm

    Originator -->|saves state to| Memento
    Caretaker -->|holds| Memento
```

| Pattern | Description |
|:-------:| ----------- |
| [chain_of_responsibility](patterns/behavioral/chain_of_responsibility.py) | apply a chain of successive handlers to try and process the data |
| [catalog](patterns/behavioral/catalog.py) | general methods will call different specialized methods based on construction parameter |
| [chaining_method](patterns/behavioral/chaining_method.py) | continue callback next object method |
| [command](patterns/behavioral/command.py) | bundle a command and arguments to call later |
| [interpreter](patterns/behavioral/interpreter.py) | define a grammar for a language and use it to interpret statements |
| [iterator](patterns/behavioral/iterator.py) | traverse a container and access the container's elements |
| [iterator](patterns/behavioral/iterator_alt.py) (alt. impl.)| traverse a container and access the container's elements |
| [mediator](patterns/behavioral/mediator.py) | an object that knows how to connect other objects and act as a proxy |
| [memento](patterns/behavioral/memento.py) | generate an opaque token that can be used to go back to a previous state |
| [observer](patterns/behavioral/observer.py) | provide a callback for notification of events/changes to data |
| [publish_subscribe](patterns/behavioral/publish_subscribe.py) | a source syndicates events/data to 0+ registered listeners |
| [registry](patterns/behavioral/registry.py) | keep track of all subclasses of a given class |
| [servant](patterns/behavioral/servant.py) | provide common functionality to a group of classes without using inheritance |
| [specification](patterns/behavioral/specification.py) | business rules can be recombined by chaining the business rules together using boolean logic |
| [state](patterns/behavioral/state.py) | logic is organized into a discrete number of potential states and the next state that can be transitioned to |
| [strategy](patterns/behavioral/strategy.py) | selectable operations over the same data |
| [template](patterns/behavioral/template.py) | an object imposes a structure but takes pluggable components |
| [visitor](patterns/behavioral/visitor.py) | invoke a callback for all items of a collection |

## Design for Testability Patterns

| Pattern | Description |
|:-------:| ----------- |
| [dependency_injection](patterns/dependency_injection.py) | 3 variants of dependency injection |

## Fundamental Patterns

| Pattern | Description |
|:-------:| ----------- |
| [delegation_pattern](patterns/fundamental/delegation_pattern.py) | an object handles a request by delegating to a second object (the delegate) |

## Others

| Pattern | Description |
|:-------:| ----------- |
| [blackboard](patterns/other/blackboard.py) | architectural model, assemble different sub-system knowledge to build a solution, AI approach - non gang of four pattern |
| [graph_search](patterns/other/graph_search.py) | graphing algorithms - non gang of four pattern |
| [hsm](patterns/other/hsm/hsm.py) | hierarchical state machine - non gang of four pattern |

## 🚫 Anti-Patterns

This section lists some common design patterns that are **not recommended** in Python and explains why.

### 🧱 Singleton
**Why not:**
- Python modules are already singletons — every module is imported only once.
- Explicit singleton classes add unnecessary complexity.
- Better alternatives: use module-level variables or dependency injection.

### 🌀 God Object
**Why not:**
- Centralizes too much logic in a single class.
- Makes code harder to test and maintain.
- Better alternative: split functionality into smaller, cohesive classes.

### 🔁 Inheritance overuse
**Why not:**
- Deep inheritance trees make code brittle.
- Prefer composition and delegation.
- “Favor composition over inheritance.”

## Videos

* [Design Patterns in Python by Peter Ullrich](https://www.youtube.com/watch?v=bsyjSW46TDg)
* [Sebastian Buczyński - Why you don't need design patterns in Python?](https://www.youtube.com/watch?v=G5OeYHCJuv0)
* [You Don't Need That!](https://www.youtube.com/watch?v=imW-trt0i9I)
* [Pluggable Libs Through Design Patterns](https://www.youtube.com/watch?v=PfgEU3W0kyU)

## Contributing

When an implementation is added or modified, please review the following guidelines:

##### Docstrings
Add module level description in form of a docstring with links to corresponding references or other useful information. 
Add "Examples in Python ecosystem" section if you know some. It shows how patterns could be applied to real-world problems.
[facade.py](patterns/structural/facade.py) has a good example of detailed description, but sometimes the shorter one as in [template.py](patterns/behavioral/template.py) would suffice.

##### Python 2 compatibility
To see Python 2 compatible versions of some patterns please check-out the [legacy](https://github.com/faif/python-patterns/tree/legacy) tag.

##### Update README
When everything else is done - update corresponding part of README.

##### Travis CI
Please run the following before submitting a patch:
- `black .` This lints your code.
- Either `tox` or `tox -e ci37` for unit tests.
- If you have a bash compatible shell, use `./lint.sh`.

## Contributing via issue triage [![Open Source Helpers](https://www.codetriage.com/faif/python-patterns/badges/users.svg)](https://www.codetriage.com/faif/python-patterns)
You can triage issues and pull requests on [CodeTriage](https://www.codetriage.com/faif/python-patterns).


================================================
FILE: config_backup/.coveragerc
================================================
[run]
branch = True

[report]
; Regexes for lines to exclude from consideration
exclude_also =
    ; Don't complain about missing debug-only code:
    def __repr__
    if self\.debug

    ; Don't complain if tests don't hit defensive assertion code:
    raise AssertionError
    raise NotImplementedError

    ; Don't complain if non-runnable code isn't run:
    if 0:
    if __name__ == .__main__.:

    ; Don't complain about abstract methods, they aren't run:
    @(abc\.)?abstractmethod

ignore_errors = True

[html]
directory = coverage_html_report

================================================
FILE: config_backup/setup.cfg
================================================
[flake8]
max-line-length = 120
ignore = E266 E731 W503
exclude = venv*

[tool:pytest]
filterwarnings =
    ; ignore TestRunner class from facade example
    ignore:.*test class 'TestRunner'.*:Warning

[mypy]
python_version = 3.12
ignore_missing_imports = True


================================================
FILE: config_backup/tox.ini
================================================
[tox]
envlist = py312,cov-report
skip_missing_interpreters = true
usedevelop = true

[testenv]
setenv =
    COVERAGE_FILE = .coverage.{envname}
deps =
    -r requirements-dev.txt
allowlist_externals =
    pytest
    flake8
    mypy
commands =
    flake8 --exclude="venv/,.tox/" patterns/
    ; `randomly-seed` option from `pytest-randomly` helps with deterministic outputs for examples like `other/blackboard.py`
    pytest --randomly-seed=1234 --doctest-modules patterns/
    pytest -s -vv --cov=patterns/ --log-level=INFO tests/


[testenv:cov-report]
setenv =
    COVERAGE_FILE = .coverage
deps = coverage
commands =
    coverage combine
    coverage report


================================================
FILE: lint.sh
================================================
#! /bin/bash

pip install --upgrade pip
pip install black codespell flake8 isort mypy pytest pyupgrade tox
pip install -e .

source_dir="./patterns"

codespell --quiet-level=2 ./patterns  # --ignore-words-list="" --skip=""
flake8 "${source_dir}"  --count --show-source --statistics
isort --profile black "${source_dir}"
tox 
mypy --ignore-missing-imports "${source_dir}" || true
pytest "${source_dir}"
pytest --doctest-modules "${source_dir}" || true
shopt -s globstar && pyupgrade --py312-plus ${source_dir}/*.py


================================================
FILE: patterns/__init__.py
================================================


================================================
FILE: patterns/behavioral/__init__.py
================================================


================================================
FILE: patterns/behavioral/catalog.py
================================================
"""
A class that uses different static functions depending on a parameter passed
during initialization. Uses a single dictionary instead of multiple conditions.
"""


__author__ = "Ibrahim Diop <ibrahim@sikilabs.com>"


class Catalog:
    """catalog of multiple static methods that are executed depending on an init parameter
    """

    def __init__(self, param: str) -> None:
        # dictionary that will be used to determine which static method is
        # to be executed but that will be also used to store possible param
        # value
        self._static_method_choices = {
            "param_value_1": self._static_method_1,
            "param_value_2": self._static_method_2,
        }

        # simple test to validate param value
        if param in self._static_method_choices.keys():
            self.param = param
        else:
            raise ValueError(f"Invalid Value for Param: {param}")

    @staticmethod
    def _static_method_1() -> str:
        return "executed method 1!"

    @staticmethod
    def _static_method_2() -> str:
        return "executed method 2!"

    def main_method(self) -> str:
        """will execute either _static_method_1 or _static_method_2

        depending on self.param value
        """
        return self._static_method_choices[self.param]()


# Alternative implementation for different levels of methods
class CatalogInstance:
    """catalog of multiple methods that are executed depending on an init
    parameter
    """

    def __init__(self, param: str) -> None:
        self.x1 = "x1"
        self.x2 = "x2"
        # simple test to validate param value
        if param in self._instance_method_choices:
            self.param = param
        else:
            raise ValueError(f"Invalid Value for Param: {param}")

    def _instance_method_1(self) -> str:
        return f"Value {self.x1}"

    def _instance_method_2(self) -> str:
        return f"Value {self.x2}"

    _instance_method_choices = {
        "param_value_1": _instance_method_1,
        "param_value_2": _instance_method_2,
    }

    def main_method(self) -> str:
        """will execute either _instance_method_1 or _instance_method_2

        depending on self.param value
        """
        return self._instance_method_choices[self.param].__get__(self)()  # type: ignore
        # type ignore reason: https://github.com/python/mypy/issues/10206


class CatalogClass:
    """catalog of multiple class methods that are executed depending on an init
    parameter
    """

    x1 = "x1"
    x2 = "x2"

    def __init__(self, param: str) -> None:
        # simple test to validate param value
        if param in self._class_method_choices:
            self.param = param
        else:
            raise ValueError(f"Invalid Value for Param: {param}")

    @classmethod
    def _class_method_1(cls) -> str:
        return f"Value {cls.x1}"

    @classmethod
    def _class_method_2(cls) -> str:
        return f"Value {cls.x2}"

    _class_method_choices = {
        "param_value_1": _class_method_1,
        "param_value_2": _class_method_2,
    }

    def main_method(self) -> str:
        """will execute either _class_method_1 or _class_method_2

        depending on self.param value
        """
        return self._class_method_choices[self.param].__get__(None, self.__class__)()  # type: ignore
        # type ignore reason: https://github.com/python/mypy/issues/10206


class CatalogStatic:
    """catalog of multiple static methods that are executed depending on an init
    parameter
    """

    def __init__(self, param: str) -> None:
        # simple test to validate param value
        if param in self._static_method_choices:
            self.param = param
        else:
            raise ValueError(f"Invalid Value for Param: {param}")

    @staticmethod
    def _static_method_1() -> str:
        return "executed method 1!"

    @staticmethod
    def _static_method_2() -> str:
        return "executed method 2!"

    _static_method_choices = {
        "param_value_1": _static_method_1,
        "param_value_2": _static_method_2,
    }

    def main_method(self) -> str:
        """will execute either _static_method_1 or _static_method_2

        depending on self.param value
        """

        return self._static_method_choices[self.param].__get__(None, self.__class__)()  # type: ignore
        # type ignore reason: https://github.com/python/mypy/issues/10206


def main():
    """
    >>> test = Catalog('param_value_2')
    >>> test.main_method()
    'executed method 2!'

    >>> test = CatalogInstance('param_value_1')
    >>> test.main_method()
    'Value x1'

    >>> test = CatalogClass('param_value_2')
    >>> test.main_method()
    'Value x2'

    >>> test = CatalogStatic('param_value_1')
    >>> test.main_method()
    'executed method 1!'
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod()


================================================
FILE: patterns/behavioral/chain_of_responsibility.py
================================================
"""
*What is this pattern about?

The Chain of responsibility is an object oriented version of the
`if ... elif ... elif ... else ...` idiom, with the
benefit that the condition–action blocks can be dynamically rearranged
and reconfigured at runtime.

This pattern aims to decouple the senders of a request from its
receivers by allowing request to move through chained
receivers until it is handled.

Request receiver in simple form keeps a reference to a single successor.
As a variation some receivers may be capable of sending requests out
in several directions, forming a `tree of responsibility`.

*Examples in Python ecosystem:
Django Middleware: https://docs.djangoproject.com/en/stable/topics/http/middleware/
The middleware components act as a chain where each processes the request/response.

*TL;DR
Allow a request to pass down a chain of receivers until it is handled.
"""

from abc import ABC, abstractmethod
from typing import Optional, Tuple


class Handler(ABC):
    def __init__(self, successor: Optional["Handler"] = None):
        self.successor = successor

    def handle(self, request: int) -> None:
        """
        Handle request and stop.
        If can't - call next handler in chain.

        As an alternative you might even in case of success
        call the next handler.
        """
        res = self.check_range(request)
        if not res and self.successor:
            self.successor.handle(request)

    @abstractmethod
    def check_range(self, request: int) -> Optional[bool]:
        """Compare passed value to predefined interval"""


class ConcreteHandler0(Handler):
    """Each handler can be different.
    Be simple and static...
    """

    @staticmethod
    def check_range(request: int) -> Optional[bool]:
        if 0 <= request < 10:
            print(f"request {request} handled in handler 0")
            return True
        return None


class ConcreteHandler1(Handler):
    """... With it's own internal state"""

    start, end = 10, 20

    def check_range(self, request: int) -> Optional[bool]:
        if self.start <= request < self.end:
            print(f"request {request} handled in handler 1")
            return True
        return None


class ConcreteHandler2(Handler):
    """... With helper methods."""

    def check_range(self, request: int) -> Optional[bool]:
        start, end = self.get_interval_from_db()
        if start <= request < end:
            print(f"request {request} handled in handler 2")
            return True
        return None

    @staticmethod
    def get_interval_from_db() -> Tuple[int, int]:
        return (20, 30)


class FallbackHandler(Handler):
    @staticmethod
    def check_range(request: int) -> Optional[bool]:
        print(f"end of chain, no handler for {request}")
        return False


def main():
    """
    >>> h0 = ConcreteHandler0()
    >>> h1 = ConcreteHandler1()
    >>> h2 = ConcreteHandler2(FallbackHandler())
    >>> h0.successor = h1
    >>> h1.successor = h2

    >>> requests = [2, 5, 14, 22, 18, 3, 35, 27, 20]
    >>> for request in requests:
    ...     h0.handle(request)
    request 2 handled in handler 0
    request 5 handled in handler 0
    request 14 handled in handler 1
    request 22 handled in handler 2
    request 18 handled in handler 1
    request 3 handled in handler 0
    end of chain, no handler for 35
    request 27 handled in handler 2
    request 20 handled in handler 2
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod(optionflags=doctest.ELLIPSIS)


================================================
FILE: patterns/behavioral/chaining_method.py
================================================
from __future__ import annotations


class Person:
    def __init__(self, name: str) -> None:
        self.name = name

    def do_action(self, action: Action) -> Action:
        print(self.name, action.name, end=" ")
        return action


class Action:
    def __init__(self, name: str) -> None:
        self.name = name

    def amount(self, val: str) -> Action:
        print(val, end=" ")
        return self

    def stop(self) -> None:
        print("then stop")


def main():
    """
    >>> move = Action('move')
    >>> person = Person('Jack')
    >>> person.do_action(move).amount('5m').stop()
    Jack move 5m then stop
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod()


================================================
FILE: patterns/behavioral/command.py
================================================
"""
Command pattern decouples the object invoking a job from the one who knows
how to do it. As mentioned in the GoF book, a good example is in menu items.
You have a menu that has lots of items. Each item is responsible for doing a
special thing and you want your menu item just call the execute method when
it is pressed. To achieve this you implement a command object with the execute
method for each menu item and pass to it.

*About the example
We have a menu containing two items. Each item accepts a file name, one hides the file
and the other deletes it. Both items have an undo option.
Each item is a MenuItem class that accepts the corresponding command as input and executes
it's execute method when it is pressed.

*TL;DR
Object oriented implementation of callback functions.

*Examples in Python ecosystem:
Django HttpRequest (without execute method):
https://docs.djangoproject.com/en/2.1/ref/request-response/#httprequest-objects
"""

from typing import List, Union


class HideFileCommand:
    """
    A command to hide a file given its name
    """

    def __init__(self) -> None:
        # an array of files hidden, to undo them as needed
        self._hidden_files: List[str] = []

    def execute(self, filename: str) -> None:
        print(f"hiding {filename}")
        self._hidden_files.append(filename)

    def undo(self) -> None:
        filename = self._hidden_files.pop()
        print(f"un-hiding {filename}")


class DeleteFileCommand:
    """
    A command to delete a file given its name
    """

    def __init__(self) -> None:
        # an array of deleted files, to undo them as needed
        self._deleted_files: List[str] = []

    def execute(self, filename: str) -> None:
        print(f"deleting {filename}")
        self._deleted_files.append(filename)

    def undo(self) -> None:
        filename = self._deleted_files.pop()
        print(f"restoring {filename}")


class MenuItem:
    """
    The invoker class. Here it is items in a menu.
    """

    def __init__(self, command: Union[HideFileCommand, DeleteFileCommand]) -> None:
        self._command = command

    def on_do_press(self, filename: str) -> None:
        self._command.execute(filename)

    def on_undo_press(self) -> None:
        self._command.undo()


def main():
    """
    >>> item1 = MenuItem(DeleteFileCommand())

    >>> item2 = MenuItem(HideFileCommand())

    # create a file named `test-file` to work with
    >>> test_file_name = 'test-file'

    # deleting `test-file`
    >>> item1.on_do_press(test_file_name)
    deleting test-file

    # restoring `test-file`
    >>> item1.on_undo_press()
    restoring test-file

    # hiding `test-file`
    >>> item2.on_do_press(test_file_name)
    hiding test-file

    # un-hiding `test-file`
    >>> item2.on_undo_press()
    un-hiding test-file
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod()


================================================
FILE: patterns/behavioral/iterator.py
================================================
"""
http://ginstrom.com/scribbles/2007/10/08/design-patterns-python-style/
Implementation of the iterator pattern with a generator

*TL;DR
Traverses a container and accesses the container's elements.
"""


def count_to(count: int):
    """Counts by word numbers, up to a maximum of five"""
    numbers = ["one", "two", "three", "four", "five"]
    yield from numbers[:count]


# Test the generator
def count_to_two() -> None:
    return count_to(2)


def count_to_five() -> None:
    return count_to(5)


def main():
    """
    # Counting to two...
    >>> for number in count_to_two():
    ...     print(number)
    one
    two

    # Counting to five...
    >>> for number in count_to_five():
    ...     print(number)
    one
    two
    three
    four
    five
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod()


================================================
FILE: patterns/behavioral/iterator_alt.py
================================================
"""
Implementation of the iterator pattern using the iterator protocol from Python

*TL;DR
Traverses a container and accesses the container's elements.
"""

from __future__ import annotations


class NumberWords:
    """Counts by word numbers, up to a maximum of five"""

    _WORD_MAP = (
        "one",
        "two",
        "three",
        "four",
        "five",
    )

    def __init__(self, start: int, stop: int) -> None:
        self.start = start
        self.stop = stop

    def __iter__(self) -> NumberWords:  # this makes the class an Iterable
        return self

    def __next__(self) -> str:  # this makes the class an Iterator
        if self.start > self.stop or self.start > len(self._WORD_MAP):
            raise StopIteration
        current = self.start
        self.start += 1
        return self._WORD_MAP[current - 1]


# Test the iterator


def main():
    """
    # Counting to two...
    >>> for number in NumberWords(start=1, stop=2):
    ...     print(number)
    one
    two

    # Counting to five...
    >>> for number in NumberWords(start=1, stop=5):
    ...     print(number)
    one
    two
    three
    four
    five
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod()


================================================
FILE: patterns/behavioral/mediator.py
================================================
"""
https://www.djangospin.com/design-patterns-python/mediator/

Objects in a system communicate through a Mediator instead of directly with each other.
This reduces the dependencies between communicating objects, thereby reducing coupling.

*TL;DR
Encapsulates how a set of objects interact.
"""

from __future__ import annotations


class ChatRoom:
    """Mediator class"""

    def display_message(self, user: User, message: str) -> None:
        return f"[{user} says]: {message}"


class User:
    """A class whose instances want to interact with each other"""

    def __init__(self, name: str) -> None:
        self.name = name
        self.chat_room = ChatRoom()

    def say(self, message: str) -> None:
        return self.chat_room.display_message(self, message)

    def __str__(self) -> str:
        return self.name


def main():
    """
    >>> molly = User('Molly')
    >>> mark = User('Mark')
    >>> ethan = User('Ethan')

    >>> molly.say("Hi Team! Meeting at 3 PM today.")
    '[Molly says]: Hi Team! Meeting at 3 PM today.'
    >>> mark.say("Roger that!")
    '[Mark says]: Roger that!'
    >>> ethan.say("Alright.")
    '[Ethan says]: Alright.'
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod()


================================================
FILE: patterns/behavioral/memento.py
================================================
"""
http://code.activestate.com/recipes/413838-memento-closure/

*TL;DR
Provides the ability to restore an object to its previous state.
"""

from copy import copy, deepcopy
from typing import Any, Callable, List, Type


def memento(obj: Any, deep: bool = False) -> Callable:
    state = deepcopy(obj.__dict__) if deep else copy(obj.__dict__)

    def restore() -> None:
        obj.__dict__.clear()
        obj.__dict__.update(state)

    return restore


class Transaction:
    """A transaction guard.

    This is, in fact, just syntactic sugar around a memento closure.
    """

    deep = False
    states: List[Callable[[], None]] = []

    def __init__(self, deep: bool, *targets: Any) -> None:
        self.deep = deep
        self.targets = targets
        self.commit()

    def commit(self) -> None:
        self.states = [memento(target, self.deep) for target in self.targets]

    def rollback(self) -> None:
        for a_state in self.states:
            a_state()


def Transactional(method):
    """Adds transactional semantics to methods. Methods decorated  with
    @Transactional will roll back to entry-state upon exceptions.

    :param method: The function to be decorated.
    """

    def __init__(self, method: Callable) -> None:
        self.method = method

    def __get__(self, obj: Any, T: Type) -> Callable:
        """
        A decorator that makes a function transactional.

        :param method: The function to be decorated.
        """

        def transaction(*args, **kwargs):
            state = memento(obj)
            try:
                return self.method(obj, *args, **kwargs)
            except Exception as e:
                state()
                raise e

    return transaction


class NumObj:
    def __init__(self, value: int) -> None:
        self.value = value

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__}: {self.value!r}>"

    def increment(self) -> None:
        self.value += 1

    @Transactional
    def do_stuff(self) -> None:
        self.value = "1111"  # <- invalid value
        self.increment()  # <- will fail and rollback


def main():
    """
    >>> num_obj = NumObj(-1)
    >>> print(num_obj)
    <NumObj: -1>

    >>> a_transaction = Transaction(True, num_obj)

    >>> try:
    ...    for i in range(3):
    ...        num_obj.increment()
    ...        print(num_obj)
    ...    a_transaction.commit()
    ...    print('-- committed')
    ...    for i in range(3):
    ...        num_obj.increment()
    ...        print(num_obj)
    ...    num_obj.value += 'x'  # will fail
    ...    print(num_obj)
    ... except Exception:
    ...    a_transaction.rollback()
    ...    print('-- rolled back')
    <NumObj: 0>
    <NumObj: 1>
    <NumObj: 2>
    -- committed
    <NumObj: 3>
    <NumObj: 4>
    <NumObj: 5>
    -- rolled back

    >>> print(num_obj)
    <NumObj: 2>

    >>> print('-- now doing stuff ...')
    -- now doing stuff ...

    >>> try:
    ...    num_obj.do_stuff()
    ... except Exception:
    ...    print('-> doing stuff failed!')
    ...    import sys
    ...    import traceback
    ...    traceback.print_exc(file=sys.stdout)
    -> doing stuff failed!
    Traceback (most recent call last):
    ...
    TypeError: ...str...int...

    >>> print(num_obj)
    <NumObj: 2>
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod(optionflags=doctest.ELLIPSIS)


================================================
FILE: patterns/behavioral/observer.py
================================================
"""
http://code.activestate.com/recipes/131499-observer-pattern/

*TL;DR
Maintains a list of dependents and notifies them of any state changes.

*Examples in Python ecosystem:
Django Signals: https://docs.djangoproject.com/en/3.1/topics/signals/
Flask Signals: https://flask.palletsprojects.com/en/1.1.x/signals/
"""

# observer.py

from __future__ import annotations
from typing import List

class Observer:
    def update(self, subject: Subject) -> None:
        """
        Receive update from the subject.

        Args:
            subject (Subject): The subject instance sending the update.
        """
        pass


class Subject:
    _observers: List[Observer]

    def __init__(self) -> None:
        """
        Initialize the subject with an empty observer list.
        """
        self._observers = []

    def attach(self, observer: Observer) -> None:
        """
        Attach an observer to the subject.

        Args:
            observer (Observer): The observer instance to attach.
        """
        if observer not in self._observers:
            self._observers.append(observer)

    def detach(self, observer: Observer) -> None:
        """
        Detach an observer from the subject.

        Args:
            observer (Observer): The observer instance to detach.
        """
        try:
            self._observers.remove(observer)
        except ValueError:
            pass

    def notify(self) -> None:
        """
        Notify all attached observers by calling their update method.
        """
        for observer in self._observers:
            observer.update(self)


class Data(Subject):
    def __init__(self, name: str = "") -> None:
        super().__init__()
        self.name = name
        self._data = 0

    @property
    def data(self) -> int:
        return self._data

    @data.setter
    def data(self, value: int) -> None:
        self._data = value
        self.notify()


class HexViewer:
    def update(self, subject: Data) -> None:
        print(f"HexViewer: Subject {subject.name} has data 0x{subject.data:x}")


class DecimalViewer:
    def update(self, subject: Data) -> None:
        print(f"DecimalViewer: Subject {subject.name} has data {subject.data}")


def main():
    """
    >>> data1 = Data('Data 1')
    >>> data2 = Data('Data 2')
    >>> view1 = DecimalViewer()
    >>> view2 = HexViewer()
    >>> data1.attach(view1)
    >>> data1.attach(view2)
    >>> data2.attach(view2)
    >>> data2.attach(view1)

    >>> data1.data = 10
    DecimalViewer: Subject Data 1 has data 10
    HexViewer: Subject Data 1 has data 0xa

    >>> data2.data = 15
    HexViewer: Subject Data 2 has data 0xf
    DecimalViewer: Subject Data 2 has data 15

    >>> data1.data = 3
    DecimalViewer: Subject Data 1 has data 3
    HexViewer: Subject Data 1 has data 0x3

    >>> data2.data = 5
    HexViewer: Subject Data 2 has data 0x5
    DecimalViewer: Subject Data 2 has data 5

    # Detach HexViewer from data1 and data2
    >>> data1.detach(view2)
    >>> data2.detach(view2)

    >>> data1.data = 10
    DecimalViewer: Subject Data 1 has data 10

    >>> data2.data = 15
    DecimalViewer: Subject Data 2 has data 15
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod()


================================================
FILE: patterns/behavioral/publish_subscribe.py
================================================
"""
Reference:
http://www.slideshare.net/ishraqabd/publish-subscribe-model-overview-13368808
Author: https://github.com/HanWenfang
"""

from __future__ import annotations


class Provider:
    def __init__(self) -> None:
        self.msg_queue = []
        self.subscribers = {}

    def notify(self, msg: str) -> None:
        self.msg_queue.append(msg)

    def subscribe(self, msg: str, subscriber: Subscriber) -> None:
        self.subscribers.setdefault(msg, []).append(subscriber)

    def unsubscribe(self, msg: str, subscriber: Subscriber) -> None:
        self.subscribers[msg].remove(subscriber)

    def update(self) -> None:
        for msg in self.msg_queue:
            for sub in self.subscribers.get(msg, []):
                sub.run(msg)
        self.msg_queue = []


class Publisher:
    def __init__(self, msg_center: Provider) -> None:
        self.provider = msg_center

    def publish(self, msg: str) -> None:
        self.provider.notify(msg)


class Subscriber:
    def __init__(self, name: str, msg_center: Provider) -> None:
        self.name = name
        self.provider = msg_center

    def subscribe(self, msg: str) -> None:
        self.provider.subscribe(msg, self)

    def unsubscribe(self, msg: str) -> None:
        self.provider.unsubscribe(msg, self)

    def run(self, msg: str) -> None:
        print(f"{self.name} got {msg}")


def main():
    """
    >>> message_center = Provider()

    >>> fftv = Publisher(message_center)

    >>> jim = Subscriber("jim", message_center)
    >>> jim.subscribe("cartoon")
    >>> jack = Subscriber("jack", message_center)
    >>> jack.subscribe("music")
    >>> gee = Subscriber("gee", message_center)
    >>> gee.subscribe("movie")
    >>> vani = Subscriber("vani", message_center)
    >>> vani.subscribe("movie")
    >>> vani.unsubscribe("movie")

    # Note that no one subscribed to `ads`
    # and that vani changed their mind

    >>> fftv.publish("cartoon")
    >>> fftv.publish("music")
    >>> fftv.publish("ads")
    >>> fftv.publish("movie")
    >>> fftv.publish("cartoon")
    >>> fftv.publish("cartoon")
    >>> fftv.publish("movie")
    >>> fftv.publish("blank")

    >>> message_center.update()
    jim got cartoon
    jack got music
    gee got movie
    jim got cartoon
    jim got cartoon
    gee got movie
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod()


================================================
FILE: patterns/behavioral/registry.py
================================================
from typing import Dict


class RegistryHolder(type):
    REGISTRY: Dict[str, "RegistryHolder"] = {}

    def __new__(cls, name, bases, attrs):
        new_cls = type.__new__(cls, name, bases, attrs)
        """
            Here the name of the class is used as key but it could be any class
            parameter.
        """
        cls.REGISTRY[new_cls.__name__] = new_cls
        return new_cls

    @classmethod
    def get_registry(cls):
        return dict(cls.REGISTRY)


class BaseRegisteredClass(metaclass=RegistryHolder):
    """
    Any class that will inherits from BaseRegisteredClass will be included
    inside the dict RegistryHolder.REGISTRY, the key being the name of the
    class and the associated value, the class itself.
    """


def main():
    """
    Before subclassing
    >>> sorted(RegistryHolder.REGISTRY)
    ['BaseRegisteredClass']

    >>> class ClassRegistree(BaseRegisteredClass):
    ...    def __init__(self, *args, **kwargs):
    ...        pass

    After subclassing
    >>> sorted(RegistryHolder.REGISTRY)
    ['BaseRegisteredClass', 'ClassRegistree']
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod(optionflags=doctest.ELLIPSIS)


================================================
FILE: patterns/behavioral/servant.py
================================================
"""
Implementation of the Servant design pattern.

The Servant design pattern is a behavioral pattern used to offer functionality
to a group of classes without requiring them to inherit from a base class.

This pattern involves creating a Servant class that provides certain services
or functionalities. These services are used by other classes which do not need
to be related through a common parent class. It is particularly useful in
scenarios where adding the desired functionality through inheritance is impractical
or would lead to a rigid class hierarchy.

This pattern is characterized by the following:

- A Servant class that provides specific services or actions.
- Client classes that need these services, but do not derive from the Servant class.
- The use of the Servant class by the client classes to perform actions on their behalf.

References:
- https://en.wikipedia.org/wiki/Servant_(design_pattern)
"""

import math


class Position:
    """Representation of a 2D position with x and y coordinates."""

    def __init__(self, x, y):
        self.x = x
        self.y = y


class Circle:
    """Representation of a circle defined by a radius and a position."""

    def __init__(self, radius, position: Position):
        self.radius = radius
        self.position = position


class Rectangle:
    """Representation of a rectangle defined by width, height, and a position."""

    def __init__(self, width, height, position: Position):
        self.width = width
        self.height = height
        self.position = position


class GeometryTools:
    """
    Servant class providing geometry-related services, including area and
    perimeter calculations and position updates.
    """

    @staticmethod
    def calculate_area(shape):
        """
        Calculate the area of a given shape.

        Args:
            shape: The geometric shape whose area is to be calculated.

        Returns:
            The area of the shape.

        Raises:
            ValueError: If the shape type is unsupported.
        """
        if isinstance(shape, Circle):
            return math.pi * shape.radius**2
        elif isinstance(shape, Rectangle):
            return shape.width * shape.height
        else:
            raise ValueError("Unsupported shape type")

    @staticmethod
    def calculate_perimeter(shape):
        """
        Calculate the perimeter of a given shape.

        Args:
            shape: The geometric shape whose perimeter is to be calculated.

        Returns:
            The perimeter of the shape.

        Raises:
            ValueError: If the shape type is unsupported.
        """
        if isinstance(shape, Circle):
            return 2 * math.pi * shape.radius
        elif isinstance(shape, Rectangle):
            return 2 * (shape.width + shape.height)
        else:
            raise ValueError("Unsupported shape type")

    @staticmethod
    def move_to(shape, new_position: Position):
        """
        Move a given shape to a new position.

        Args:
            shape: The geometric shape to be moved.
            new_position: The new position to move the shape to.
        """
        shape.position = new_position
        print(f"Moved to ({shape.position.x}, {shape.position.y})")


def main():
    """
    >>> servant = GeometryTools()
    >>> circle = Circle(5, Position(0, 0))
    >>> rectangle = Rectangle(3, 4, Position(0, 0))
    >>> servant.calculate_area(circle)
    78.53981633974483
    >>> servant.calculate_perimeter(rectangle)
    14
    >>> servant.move_to(circle, Position(3, 4))
    Moved to (3, 4)
    >>> servant.move_to(rectangle, Position(5, 6))
    Moved to (5, 6)
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod()


================================================
FILE: patterns/behavioral/specification.py
================================================
"""
@author: Gordeev Andrey <gordeev.and.and@gmail.com>

*TL;DR
Provides recombination business logic by chaining together using boolean logic.
"""

from abc import abstractmethod
from typing import Union


class Specification:
    def and_specification(self, candidate):
        raise NotImplementedError()

    def or_specification(self, candidate):
        raise NotImplementedError()

    def not_specification(self):
        raise NotImplementedError()

    @abstractmethod
    def is_satisfied_by(self, candidate):
        pass


class CompositeSpecification(Specification):
    @abstractmethod
    def is_satisfied_by(self, candidate):
        pass

    def and_specification(self, candidate: "Specification") -> "AndSpecification":
        return AndSpecification(self, candidate)

    def or_specification(self, candidate: "Specification") -> "OrSpecification":
        return OrSpecification(self, candidate)

    def not_specification(self) -> "NotSpecification":
        return NotSpecification(self)


class AndSpecification(CompositeSpecification):
    def __init__(self, one: "Specification", other: "Specification") -> None:
        self._one: Specification = one
        self._other: Specification = other

    def is_satisfied_by(self, candidate: Union["User", str]) -> bool:
        return bool(
            self._one.is_satisfied_by(candidate)
            and self._other.is_satisfied_by(candidate)
        )


class OrSpecification(CompositeSpecification):
    def __init__(self, one: "Specification", other: "Specification") -> None:
        self._one: Specification = one
        self._other: Specification = other

    def is_satisfied_by(self, candidate: Union["User", str]):
        return bool(
            self._one.is_satisfied_by(candidate)
            or self._other.is_satisfied_by(candidate)
        )


class NotSpecification(CompositeSpecification):
    def __init__(self, wrapped: "Specification"):
        self._wrapped: Specification = wrapped

    def is_satisfied_by(self, candidate: Union["User", str]):
        return bool(not self._wrapped.is_satisfied_by(candidate))


class User:
    def __init__(self, super_user: bool = False) -> None:
        self.super_user = super_user


class UserSpecification(CompositeSpecification):
    def is_satisfied_by(self, candidate: Union["User", str]) -> bool:
        return isinstance(candidate, User)


class SuperUserSpecification(CompositeSpecification):
    def is_satisfied_by(self, candidate: "User") -> bool:
        return getattr(candidate, "super_user", False)


def main():
    """
    >>> andrey = User()
    >>> ivan = User(super_user=True)
    >>> vasiliy = 'not User instance'

    >>> root_specification = UserSpecification().and_specification(SuperUserSpecification())

    # Is specification satisfied by <name>
    >>> root_specification.is_satisfied_by(andrey), 'andrey'
    (False, 'andrey')
    >>> root_specification.is_satisfied_by(ivan), 'ivan'
    (True, 'ivan')
    >>> root_specification.is_satisfied_by(vasiliy), 'vasiliy'
    (False, 'vasiliy')
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod()


================================================
FILE: patterns/behavioral/state.py
================================================
"""
Implementation of the state pattern

http://ginstrom.com/scribbles/2007/10/08/design-patterns-python-style/

*TL;DR
Implements state as a derived class of the state pattern interface.
Implements state transitions by invoking methods from the pattern's superclass.
"""

from __future__ import annotations


class State:
    """Base state. This is to share functionality"""

    def scan(self) -> None:
        """Scan the dial to the next station"""
        self.pos += 1
        if self.pos == len(self.stations):
            self.pos = 0
        print(f"Scanning... Station is {self.stations[self.pos]} {self.name}")


class AmState(State):
    def __init__(self, radio: Radio) -> None:
        self.radio = radio
        self.stations = ["1250", "1380", "1510"]
        self.pos = 0
        self.name = "AM"

    def toggle_amfm(self) -> None:
        print("Switching to FM")
        self.radio.state = self.radio.fmstate


class FmState(State):
    def __init__(self, radio: Radio) -> None:
        self.radio = radio
        self.stations = ["81.3", "89.1", "103.9"]
        self.pos = 0
        self.name = "FM"

    def toggle_amfm(self) -> None:
        print("Switching to AM")
        self.radio.state = self.radio.amstate


class Radio:
    """A radio.     It has a scan button, and an AM/FM toggle switch."""

    def __init__(self) -> None:
        """We have an AM state and an FM state"""
        self.amstate = AmState(self)
        self.fmstate = FmState(self)
        self.state = self.amstate

    def toggle_amfm(self) -> None:
        self.state.toggle_amfm()

    def scan(self) -> None:
        self.state.scan()


def main():
    """
    >>> radio = Radio()
    >>> actions = [radio.scan] * 2 + [radio.toggle_amfm] + [radio.scan] * 2
    >>> actions *= 2

    >>> for action in actions:
    ...    action()
    Scanning... Station is 1380 AM
    Scanning... Station is 1510 AM
    Switching to FM
    Scanning... Station is 89.1 FM
    Scanning... Station is 103.9 FM
    Scanning... Station is 81.3 FM
    Scanning... Station is 89.1 FM
    Switching to AM
    Scanning... Station is 1250 AM
    Scanning... Station is 1380 AM
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod()


================================================
FILE: patterns/behavioral/strategy.py
================================================
"""
*What is this pattern about?
Define a family of algorithms, encapsulate each one, and make them interchangeable.
Strategy lets the algorithm vary independently from clients that use it.

*TL;DR
Enables selecting an algorithm at runtime.
"""

from __future__ import annotations

from typing import Callable


class DiscountStrategyValidator:  # Descriptor class for check perform
    @staticmethod
    def validate(obj: Order, value: Callable) -> bool:
        try:
            if obj.price - value(obj) < 0:
                raise ValueError(
                    f"Discount cannot be applied due to negative price resulting. {value.__name__}"
                )
        except ValueError as ex:
            print(str(ex))
            return False
        else:
            return True

    def __set_name__(self, owner, name: str) -> None:
        self.private_name = f"_{name}"

    def __set__(self, obj: Order, value: Callable = None) -> None:
        if value and self.validate(obj, value):
            setattr(obj, self.private_name, value)
        else:
            setattr(obj, self.private_name, None)

    def __get__(self, obj: object, objtype: type = None):
        return getattr(obj, self.private_name)


class Order:
    discount_strategy = DiscountStrategyValidator()

    def __init__(self, price: float, discount_strategy: Callable = None) -> None:
        self.price: float = price
        self.discount_strategy = discount_strategy

    def apply_discount(self) -> float:
        if self.discount_strategy:
            discount = self.discount_strategy(self)
        else:
            discount = 0

        return self.price - discount

    def __repr__(self) -> str:
        strategy = getattr(self.discount_strategy, "__name__", None)
        return f"<Order price: {self.price} with discount strategy: {strategy}>"


def ten_percent_discount(order: Order) -> float:
    return order.price * 0.10


def on_sale_discount(order: Order) -> float:
    return order.price * 0.25 + 20


def main():
    """
    >>> order = Order(100, discount_strategy=ten_percent_discount)
    >>> print(order)
    <Order price: 100 with discount strategy: ten_percent_discount>
    >>> print(order.apply_discount())
    90.0
    >>> order = Order(100, discount_strategy=on_sale_discount)
    >>> print(order)
    <Order price: 100 with discount strategy: on_sale_discount>
    >>> print(order.apply_discount())
    55.0
    >>> order = Order(10, discount_strategy=on_sale_discount)
    Discount cannot be applied due to negative price resulting. on_sale_discount
    >>> print(order)
    <Order price: 10 with discount strategy: None>
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod()


================================================
FILE: patterns/behavioral/template.py
================================================
"""
An example of the Template pattern in Python

*TL;DR
Defines the skeleton of a base algorithm, deferring definition of exact
steps to subclasses.

*Examples in Python ecosystem:
Django class based views: https://docs.djangoproject.com/en/2.1/topics/class-based-views/
"""


def get_text() -> str:
    return "plain-text"


def get_pdf() -> str:
    return "pdf"


def get_csv() -> str:
    return "csv"


def convert_to_text(data: str) -> str:
    print("[CONVERT]")
    return f"{data} as text"


def saver() -> None:
    print("[SAVE]")


def template_function(getter, converter=False, to_save=False) -> None:
    data = getter()
    print(f"Got `{data}`")

    if len(data) <= 3 and converter:
        data = converter(data)
    else:
        print("Skip conversion")

    if to_save:
        saver()

    print(f"`{data}` was processed")


def main():
    """
    >>> template_function(get_text, to_save=True)
    Got `plain-text`
    Skip conversion
    [SAVE]
    `plain-text` was processed

    >>> template_function(get_pdf, converter=convert_to_text)
    Got `pdf`
    [CONVERT]
    `pdf as text` was processed

    >>> template_function(get_csv, to_save=True)
    Got `csv`
    Skip conversion
    [SAVE]
    `csv` was processed
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod()


================================================
FILE: patterns/behavioral/visitor.py
================================================
"""
http://peter-hoffmann.com/2010/extrinsic-visitor-pattern-python-inheritance.html

*TL;DR
Separates an algorithm from an object structure on which it operates.

An interesting recipe could be found in
Brian Jones, David Beazley "Python Cookbook" (2013):
- "8.21. Implementing the Visitor Pattern"
- "8.22. Implementing the Visitor Pattern Without Recursion"

*Examples in Python ecosystem:
- Python's ast.NodeVisitor: https://github.com/python/cpython/blob/master/Lib/ast.py#L250
which is then being used e.g. in tools like `pyflakes`.
- `Black` formatter tool implements it's own: https://github.com/ambv/black/blob/master/black.py#L718
"""
from typing import Union


class Node:
    pass


class A(Node):
    pass


class B(Node):
    pass


class C(A, B):
    pass


class Visitor:
    def visit(self, node: Union[A, C, B], *args, **kwargs) -> None:
        meth = None
        for cls in node.__class__.__mro__:
            meth_name = "visit_" + cls.__name__
            meth = getattr(self, meth_name, None)
            if meth:
                break

        if not meth:
            meth = self.generic_visit
        return meth(node, *args, **kwargs)

    def generic_visit(self, node: A, *args, **kwargs) -> None:
        print("generic_visit " + node.__class__.__name__)

    def visit_B(self, node: Union[C, B], *args, **kwargs) -> None:
        print("visit_B " + node.__class__.__name__)


def main():
    """
    >>> a, b, c = A(), B(), C()
    >>> visitor = Visitor()

    >>> visitor.visit(a)
    'generic_visit A'

    >>> visitor.visit(b)
    'visit_B B'

    >>> visitor.visit(c)
    'visit_B C'
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod()


================================================
FILE: patterns/creational/__init__.py
================================================


================================================
FILE: patterns/creational/abstract_factory.py
================================================
"""
*What is this pattern about?

In Java and other languages, the Abstract Factory Pattern serves to provide an interface for
creating related/dependent objects without need to specify their
actual class.

The idea is to abstract the creation of objects depending on business
logic, platform choice, etc.

In Python, the interface we use is simply a callable, which is "builtin" interface
in Python, and in normal circumstances we can simply use the class itself as
that callable, because classes are first class objects in Python.

*What does this example do?
This particular implementation abstracts the creation of a pet and
does so depending on the factory we chose (Dog or Cat, or random_animal)
This works because both Dog/Cat and random_animal respect a common
interface (callable for creation and .speak()).
Now my application can create pets abstractly and decide later,
based on my own criteria, dogs over cats.

*Where is the pattern used practically?

*References:
https://sourcemaking.com/design_patterns/abstract_factory
http://ginstrom.com/scribbles/2007/10/08/design-patterns-python-style/

*TL;DR
Provides a way to encapsulate a group of individual factories.
"""

import random
from typing import Type


class Pet:
    def __init__(self, name: str) -> None:
        self.name = name

    def speak(self) -> None:
        raise NotImplementedError

    def __str__(self) -> str:
        raise NotImplementedError


class Dog(Pet):
    def speak(self) -> None:
        print("woof")

    def __str__(self) -> str:
        return f"Dog<{self.name}>"


class Cat(Pet):
    def speak(self) -> None:
        print("meow")

    def __str__(self) -> str:
        return f"Cat<{self.name}>"


class PetShop:
    """A pet shop"""

    def __init__(self, animal_factory: Type[Pet]) -> None:
        """pet_factory is our abstract factory.  We can set it at will."""

        self.pet_factory = animal_factory

    def buy_pet(self, name: str) -> Pet:
        """Creates and shows a pet using the abstract factory"""

        pet = self.pet_factory(name)
        print(f"Here is your lovely {pet}")
        return pet


# Show pets with various factories
def main() -> None:
    """
    # A Shop that sells only cats
    >>> cat_shop = PetShop(Cat)
    >>> pet = cat_shop.buy_pet("Lucy")
    Here is your lovely Cat<Lucy>
    >>> pet.speak()
    meow
    """


if __name__ == "__main__":
    animals = [Dog, Cat]
    random_animal: Type[Pet] = random.choice(animals)

    shop = PetShop(random_animal)
    import doctest

    doctest.testmod()


================================================
FILE: patterns/creational/borg.py
================================================
"""
*What is this pattern about?
The Borg pattern (also known as the Monostate pattern) is a way to
implement singleton behavior, but instead of having only one instance
of a class, there are multiple instances that share the same state. In
other words, the focus is on sharing state instead of sharing instance
identity.

*What does this example do?
To understand the implementation of this pattern in Python, it is
important to know that, in Python, instance attributes are stored in a
attribute dictionary called __dict__. Usually, each instance will have
its own dictionary, but the Borg pattern modifies this so that all
instances have the same dictionary.
In this example, the __shared_state attribute will be the dictionary
shared between all instances, and this is ensured by assigning
__shared_state to the __dict__ variable when initializing a new
instance (i.e., in the __init__ method). Other attributes are usually
added to the instance's attribute dictionary, but, since the attribute
dictionary itself is shared (which is __shared_state), all other
attributes will also be shared.

*Where is the pattern used practically?
Sharing state is useful in applications like managing database connections:
https://github.com/onetwopunch/pythonDbTemplate/blob/master/database.py

*References:
- https://fkromer.github.io/python-pattern-references/design/#singleton
- https://learning.oreilly.com/library/view/python-cookbook/0596001673/ch05s23.html
- http://www.aleax.it/5ep.html

*TL;DR
Provides singleton-like behavior sharing state between instances.
"""

from typing import Dict


class Borg:
    _shared_state: Dict[str, str] = {}

    def __init__(self) -> None:
        self.__dict__ = self._shared_state


class YourBorg(Borg):
    def __init__(self, state: str = None) -> None:
        super().__init__()
        if state:
            self.state = state
        else:
            # initiate the first instance with default state
            if not hasattr(self, "state"):
                self.state = "Init"

    def __str__(self) -> str:
        return self.state


def main():
    """
    >>> rm1 = YourBorg()
    >>> rm2 = YourBorg()

    >>> rm1.state = 'Idle'
    >>> rm2.state = 'Running'

    >>> print('rm1: {0}'.format(rm1))
    rm1: Running
    >>> print('rm2: {0}'.format(rm2))
    rm2: Running

    # When the `state` attribute is modified from instance `rm2`,
    # the value of `state` in instance `rm1` also changes
    >>> rm2.state = 'Zombie'

    >>> print('rm1: {0}'.format(rm1))
    rm1: Zombie
    >>> print('rm2: {0}'.format(rm2))
    rm2: Zombie

    # Even though `rm1` and `rm2` share attributes, the instances are not the same
    >>> rm1 is rm2
    False

    # New instances also get the same shared state
    >>> rm3 = YourBorg()

    >>> print('rm1: {0}'.format(rm1))
    rm1: Zombie
    >>> print('rm2: {0}'.format(rm2))
    rm2: Zombie
    >>> print('rm3: {0}'.format(rm3))
    rm3: Zombie

    # A new instance can explicitly change the state during creation
    >>> rm4 = YourBorg('Running')

    >>> print('rm4: {0}'.format(rm4))
    rm4: Running

    # Existing instances reflect that change as well
    >>> print('rm3: {0}'.format(rm3))
    rm3: Running
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod()


================================================
FILE: patterns/creational/builder.py
================================================
"""
What is this pattern about?
It decouples the creation of a complex object and its representation,
so that the same process can be reused to build objects from the same
family.
This is useful when you must separate the specification of an object
from its actual representation (generally for abstraction).

What does this example do?
The first example achieves this by using an abstract base
class for a building, where the initializer (__init__ method) specifies the
steps needed, and the concrete subclasses implement these steps.

In other programming languages, a more complex arrangement is sometimes
necessary. In particular, you cannot have polymorphic behaviour in a constructor in C++ -
see https://stackoverflow.com/questions/1453131/how-can-i-get-polymorphic-behavior-in-a-c-constructor
- which means this Python technique will not work. The polymorphism
required has to be provided by an external, already constructed
instance of a different class.

In general, in Python this won't be necessary, but a second example showing
this kind of arrangement is also included.

Where is the pattern used practically?
See: https://sourcemaking.com/design_patterns/builder

TL;DR
Decouples the creation of a complex object and its representation.
"""



# Abstract Building
class Building:
    def __init__(self) -> None:
        self.build_floor()
        self.build_size()

    def build_floor(self):
        raise NotImplementedError

    def build_size(self):
        raise NotImplementedError

    def __repr__(self) -> str:
        return "Floor: {0.floor} | Size: {0.size}".format(self)


# Concrete Buildings
class House(Building):
    def build_floor(self) -> None:
        self.floor = "One"

    def build_size(self) -> None:
        self.size = "Big"


class Flat(Building):
    def build_floor(self) -> None:
        self.floor = "More than One"

    def build_size(self) -> None:
        self.size = "Small"


# In some very complex cases, it might be desirable to pull out the building
# logic into another function (or a method on another class), rather than being
# in the base class '__init__'. (This leaves you in the strange situation where
# a concrete class does not have a useful constructor)


class ComplexBuilding:
    def __repr__(self) -> str:
        return "Floor: {0.floor} | Size: {0.size}".format(self)


class ComplexHouse(ComplexBuilding):
    def build_floor(self) -> None:
        self.floor = "One"

    def build_size(self) -> None:
        self.size = "Big and fancy"


def construct_building(cls) -> Building:
    building = cls()
    building.build_floor()
    building.build_size()
    return building


def main():
    """
    >>> house = House()
    >>> house
    Floor: One | Size: Big

    >>> flat = Flat()
    >>> flat
    Floor: More than One | Size: Small

    # Using an external constructor function:
    >>> complex_house = construct_building(ComplexHouse)
    >>> complex_house
    Floor: One | Size: Big and fancy
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod()


================================================
FILE: patterns/creational/factory.py
================================================
"""*What is this pattern about?
A Factory is an object for creating other objects.

*What does this example do?
The code shows a way to localize words in two languages: English and
Greek. "get_localizer" is the factory function that constructs a
localizer depending on the language chosen. The localizer object will
be an instance from a different class according to the language
localized. However, the main code does not have to worry about which
localizer will be instantiated, since the method "localize" will be called
in the same way independently of the language.

*Where can the pattern be used practically?
The Factory Method can be seen in the popular web framework Django:
https://docs.djangoproject.com/en/4.0/topics/forms/formsets/
For example, different types of forms are created using a formset_factory

*References:
http://ginstrom.com/scribbles/2007/10/08/design-patterns-python-style/

*TL;DR
Creates objects without having to specify the exact class.
"""

from typing import Dict, Protocol, Type


class Localizer(Protocol):
    def localize(self, msg: str) -> str: ...


class GreekLocalizer:
    """A simple localizer a la gettext"""

    def __init__(self) -> None:
        self.translations = {"dog": "σκύλος", "cat": "γάτα"}

    def localize(self, msg: str) -> str:
        """We'll punt if we don't have a translation"""
        return self.translations.get(msg, msg)


class EnglishLocalizer:
    """Simply echoes the message"""

    def localize(self, msg: str) -> str:
        return msg


def get_localizer(language: str = "English") -> Localizer:
    """Factory"""
    localizers: Dict[str, Type[Localizer]] = {
        "English": EnglishLocalizer,
        "Greek": GreekLocalizer,
    }

    return localizers.get(language, EnglishLocalizer)()


def main():
    """
    # Create our localizers
    >>> e, g = get_localizer(language="English"), get_localizer(language="Greek")

    # Localize some text
    >>> for msg in "dog parrot cat bear".split():
    ...     print(e.localize(msg), g.localize(msg))
    dog σκύλος
    parrot parrot
    cat γάτα
    bear bear
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod()


================================================
FILE: patterns/creational/lazy_evaluation.py
================================================
"""
Lazily-evaluated property pattern in Python.

https://en.wikipedia.org/wiki/Lazy_evaluation

*References:
bottle
https://github.com/bottlepy/bottle/blob/cafc15419cbb4a6cb748e6ecdccf92893bb25ce5/bottle.py#L270
django
https://github.com/django/django/blob/ffd18732f3ee9e6f0374aff9ccf350d85187fac2/django/utils/functional.py#L19
pip
https://github.com/pypa/pip/blob/cb75cca785629e15efb46c35903827b3eae13481/pip/utils/__init__.py#L821
pyramid
https://github.com/Pylons/pyramid/blob/7909e9503cdfc6f6e84d2c7ace1d3c03ca1d8b73/pyramid/decorator.py#L4
werkzeug
https://github.com/pallets/werkzeug/blob/5a2bf35441006d832ab1ed5a31963cbc366c99ac/werkzeug/utils.py#L35

*TL;DR
Delays the eval of an expr until its value is needed and avoids repeated evals.
"""

import functools
from typing import Callable, Type


class lazy_property:
    def __init__(self, function: Callable) -> None:
        self.function = function
        functools.update_wrapper(self, function)

    def __get__(self, obj: "Person", type_: Type["Person"]) -> str:
        if obj is None:
            return self
        val = self.function(obj)
        obj.__dict__[self.function.__name__] = val
        return val


def lazy_property2(fn: Callable) -> property:
    """
    A lazy property decorator.

    The function decorated is called the first time to retrieve the result and
    then that calculated result is used the next time you access the value.
    """
    attr = "_lazy__" + fn.__name__

    @property
    def _lazy_property(self):
        if not hasattr(self, attr):
            setattr(self, attr, fn(self))
        return getattr(self, attr)

    return _lazy_property


class Person:
    def __init__(self, name: str, occupation: str) -> None:
        self.name = name
        self.occupation = occupation
        self.call_count2 = 0

    @lazy_property
    def relatives(self) -> str:
        # Get all relatives, let's assume that it costs much time.
        relatives = "Many relatives."
        return relatives

    @lazy_property2
    def parents(self) -> str:
        self.call_count2 += 1
        return "Father and mother"


def main():
    """
    >>> Jhon = Person('Jhon', 'Coder')

    >>> Jhon.name
    'Jhon'
    >>> Jhon.occupation
    'Coder'

    # Before we access `relatives`
    >>> sorted(Jhon.__dict__.items())
    [('call_count2', 0), ('name', 'Jhon'), ('occupation', 'Coder')]

    >>> Jhon.relatives
    'Many relatives.'

    # After we've accessed `relatives`
    >>> sorted(Jhon.__dict__.items())
    [('call_count2', 0), ..., ('relatives', 'Many relatives.')]

    >>> Jhon.parents
    'Father and mother'

    >>> sorted(Jhon.__dict__.items())
    [('_lazy__parents', 'Father and mother'), ('call_count2', 1), ..., ('relatives', 'Many relatives.')]

    >>> Jhon.parents
    'Father and mother'

    >>> Jhon.call_count2
    1
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod(optionflags=doctest.ELLIPSIS)


================================================
FILE: patterns/creational/pool.py
================================================
"""
*What is this pattern about?
This pattern is used when creating an object is costly (and they are
created frequently) but only a few are used at a time. With a Pool we
can manage those instances we have as of now by caching them. Now it
is possible to skip the costly creation of an object if one is
available in the pool.
A pool allows to 'check out' an inactive object and then to return it.
If none are available the pool creates one to provide without wait.

*What does this example do?
In this example queue.Queue is used to create the pool (wrapped in a
custom ObjectPool object to use with the with statement), and it is
populated with strings.
As we can see, the first string object put in "yam" is USED by the
with statement. But because it is released back into the pool
afterwards it is reused by the explicit call to sample_queue.get().
Same thing happens with "sam", when the ObjectPool created inside the
function is deleted (by the GC) and the object is returned.

*Where is the pattern used practically?

*References:
http://stackoverflow.com/questions/1514120/python-implementation-of-the-object-pool-design-pattern
https://sourcemaking.com/design_patterns/object_pool

*TL;DR
Stores a set of initialized objects kept ready to use.
"""
from queue import Queue
from types import TracebackType
from typing import Union


class ObjectPool:
    def __init__(self, queue: Queue, auto_get: bool = False) -> None:
        self._queue = queue
        self.item = self._queue.get() if auto_get else None

    def __enter__(self) -> str:
        if self.item is None:
            self.item = self._queue.get()
        return self.item

    def __exit__(
        self,
        Type: Union[type[BaseException], None],
        value: Union[BaseException, None],
        traceback: Union[TracebackType, None],
    ) -> None:
        if self.item is not None:
            self._queue.put(self.item)
            self.item = None

    def __del__(self) -> None:
        if self.item is not None:
            self._queue.put(self.item)
            self.item = None


def main():
    """
    >>> import queue

    >>> def test_object(queue):
    ...    pool = ObjectPool(queue, True)
    ...    print('Inside func: {}'.format(pool.item))

    >>> sample_queue = queue.Queue()

    >>> sample_queue.put('yam')
    >>> with ObjectPool(sample_queue) as obj:
    ...    print('Inside with: {}'.format(obj))
    Inside with: yam

    >>> print('Outside with: {}'.format(sample_queue.get()))
    Outside with: yam

    >>> sample_queue.put('sam')
    >>> test_object(sample_queue)
    Inside func: sam

    >>> print('Outside func: {}'.format(sample_queue.get()))
    Outside func: sam

    if not sample_queue.empty():
        print(sample_queue.get())
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod()


================================================
FILE: patterns/creational/prototype.py
================================================
"""
*What is this pattern about?
This patterns aims to reduce the number of classes required by an
application. Instead of relying on subclasses it creates objects by
copying a prototypical instance at run-time.

This is useful as it makes it easier to derive new kinds of objects,
when instances of the class have only a few different combinations of
state, and when instantiation is expensive.

*What does this example do?
When the number of prototypes in an application can vary, it can be
useful to keep a Dispatcher (aka, Registry or Manager). This allows
clients to query the Dispatcher for a prototype before cloning a new
instance.

Below provides an example of such Dispatcher, which contains three
copies of the prototype: 'default', 'objecta' and 'objectb'.

*TL;DR
Creates new object instances by cloning prototype.
"""

from __future__ import annotations

from typing import Any


class Prototype:
    def __init__(self, value: str = "default", **attrs: Any) -> None:
        self.value = value
        self.__dict__.update(attrs)

    def clone(self, **attrs: Any) -> Prototype:
        """Clone a prototype and update inner attributes dictionary"""
        # Python in Practice, Mark Summerfield
        # copy.deepcopy can be used instead of next line.
        obj = self.__class__(**self.__dict__)
        obj.__dict__.update(attrs)
        return obj


class PrototypeDispatcher:
    def __init__(self):
        self._objects = {}

    def get_objects(self) -> dict[str, Prototype]:
        """Get all objects"""
        return self._objects

    def register_object(self, name: str, obj: Prototype) -> None:
        """Register an object"""
        self._objects[name] = obj

    def unregister_object(self, name: str) -> None:
        """Unregister an object"""
        del self._objects[name]


def main() -> None:
    """
    >>> dispatcher = PrototypeDispatcher()
    >>> prototype = Prototype()

    >>> d = prototype.clone()
    >>> a = prototype.clone(value='a-value', category='a')
    >>> b = a.clone(value='b-value', is_checked=True)
    >>> dispatcher.register_object('objecta', a)
    >>> dispatcher.register_object('objectb', b)
    >>> dispatcher.register_object('default', d)

    >>> [{n: p.value} for n, p in dispatcher.get_objects().items()]
    [{'objecta': 'a-value'}, {'objectb': 'b-value'}, {'default': 'default'}]

    >>> print(b.category, b.is_checked)
    a True
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod()


================================================
FILE: patterns/dependency_injection.py
================================================
"""
Dependency Injection (DI) is a technique whereby one object supplies the dependencies (services)
to another object (client).
It allows to decouple objects: no need to change client code simply because an object it depends on
needs to be changed to a different one. (Open/Closed principle)

Port of the Java example of Dependency Injection" in
"xUnit Test Patterns - Refactoring Test Code" by Gerard Meszaros
(ISBN-10: 0131495054, ISBN-13: 978-0131495050)

In the following example `time_provider` (service) is embedded into TimeDisplay (client).
If such service performed an expensive operation you would like to substitute or mock it in tests.

class TimeDisplay(object):

    def __init__(self):
        self.time_provider = datetime.datetime.now

    def get_current_time_as_html_fragment(self):
        current_time = self.time_provider()
        current_time_as_html_fragment = "<span class=\"tinyBoldText\">{}</span>".format(current_time)
        return current_time_as_html_fragment

"""

import datetime
from typing import Callable


class ConstructorInjection:
    def __init__(self, time_provider: Callable) -> None:
        self.time_provider = time_provider

    def get_current_time_as_html_fragment(self) -> str:
        current_time = self.time_provider()
        current_time_as_html_fragment = '<span class="tinyBoldText">{}</span>'.format(
            current_time
        )
        return current_time_as_html_fragment


class ParameterInjection:
    def __init__(self) -> None:
        pass

    def get_current_time_as_html_fragment(self, time_provider: Callable) -> str:
        current_time = time_provider()
        current_time_as_html_fragment = '<span class="tinyBoldText">{}</span>'.format(
            current_time
        )
        return current_time_as_html_fragment


class SetterInjection:
    """Setter Injection"""

    def __init__(self):
        pass

    def set_time_provider(self, time_provider: Callable):
        self.time_provider = time_provider

    def get_current_time_as_html_fragment(self):
        current_time = self.time_provider()
        current_time_as_html_fragment = '<span class="tinyBoldText">{}</span>'.format(
            current_time
        )
        return current_time_as_html_fragment


def production_code_time_provider() -> str:
    """
    Production code version of the time provider (just a wrapper for formatting
    datetime for this example).
    """
    current_time = datetime.datetime.now()
    current_time_formatted = f"{current_time.hour}:{current_time.minute}"
    return current_time_formatted


def midnight_time_provider() -> str:
    """Hard-coded stub"""
    return "24:01"


def main():
    """
    >>> time_with_ci1 = ConstructorInjection(midnight_time_provider)
    >>> time_with_ci1.get_current_time_as_html_fragment()
    '<span class="tinyBoldText">24:01</span>'

    >>> time_with_ci2 = ConstructorInjection(production_code_time_provider)
    >>> time_with_ci2.get_current_time_as_html_fragment()
    '<span class="tinyBoldText">...</span>'

    >>> time_with_pi = ParameterInjection()
    >>> time_with_pi.get_current_time_as_html_fragment(midnight_time_provider)
    '<span class="tinyBoldText">24:01</span>'

    >>> time_with_si = SetterInjection()

    >>> time_with_si.get_current_time_as_html_fragment()
    Traceback (most recent call last):
    ...
    AttributeError: 'SetterInjection' object has no attribute 'time_provider'

    >>> time_with_si.set_time_provider(midnight_time_provider)
    >>> time_with_si.get_current_time_as_html_fragment()
    '<span class="tinyBoldText">24:01</span>'
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod(optionflags=doctest.ELLIPSIS)


================================================
FILE: patterns/fundamental/__init__.py
================================================


================================================
FILE: patterns/fundamental/delegation_pattern.py
================================================
"""
Reference: https://en.wikipedia.org/wiki/Delegation_pattern
Author: https://github.com/IuryAlves

*TL;DR
Allows object composition to achieve the same code reuse as inheritance.
"""

from __future__ import annotations

from typing import Any, Callable


class Delegator:
    """
    >>> delegator = Delegator(Delegate())
    >>> delegator.p1
    123
    >>> delegator.p2
    Traceback (most recent call last):
    ...
    AttributeError: 'Delegate' object has no attribute 'p2'. Did you mean: 'p1'?
    >>> delegator.do_something("nothing")
    'Doing nothing'
    >>> delegator.do_something("something", kw=", faif!")
    'Doing something, faif!'
    >>> delegator.do_anything()
    Traceback (most recent call last):
    ...
    AttributeError: 'Delegate' object has no attribute 'do_anything'. Did you mean: 'do_something'?
    """

    def __init__(self, delegate: Delegate) -> None:
        self.delegate = delegate

    def __getattr__(self, name: str) -> Any | Callable:
        attr = getattr(self.delegate, name)

        if not callable(attr):
            return attr

        def wrapper(*args, **kwargs):
            return attr(*args, **kwargs)

        return wrapper


class Delegate:
    def __init__(self) -> None:
        self.p1 = 123

    def do_something(self, something: str, kw=None) -> str:
        return f"Doing {something}{kw or ''}"


if __name__ == "__main__":
    import doctest

    doctest.testmod()


================================================
FILE: patterns/other/__init__.py
================================================


================================================
FILE: patterns/other/blackboard.py
================================================
"""
@author: Eugene Duboviy <eugene.dubovoy@gmail.com> | github.com/duboviy

In Blackboard pattern several specialised sub-systems (knowledge sources)
assemble their knowledge to build a possibly partial or approximate solution.
In this way, the sub-systems work together to solve the problem,
where the solution is the sum of its parts.

https://en.wikipedia.org/wiki/Blackboard_system
"""

from abc import ABC, abstractmethod
import random


class AbstractExpert(ABC):
    """Abstract class for experts in the blackboard system."""

    @abstractmethod
    def __init__(self, blackboard) -> None:
        self.blackboard = blackboard

    @property
    @abstractmethod
    def is_eager_to_contribute(self) -> int:
        raise NotImplementedError("Must provide implementation in subclass.")

    @abstractmethod
    def contribute(self) -> None:
        raise NotImplementedError("Must provide implementation in subclass.")


class Blackboard:
    """The blackboard system that holds the common state."""

    def __init__(self) -> None:
        self.experts: list = []
        self.common_state = {
            "problems": 0,
            "suggestions": 0,
            "contributions": [],
            "progress": 0,  # percentage, if 100 -> task is finished
        }

    def add_expert(self, expert: AbstractExpert) -> None:
        self.experts.append(expert)


class Controller:
    """The controller that manages the blackboard system."""

    def __init__(self, blackboard: Blackboard) -> None:
        self.blackboard = blackboard

    def run_loop(self):
        """
        This function is a loop that runs until the progress reaches 100.
        It checks if an expert is eager to contribute and then calls its contribute method.
        """
        while self.blackboard.common_state["progress"] < 100:
            for expert in self.blackboard.experts:
                if expert.is_eager_to_contribute:
                    expert.contribute()
        return self.blackboard.common_state["contributions"]


class Student(AbstractExpert):
    """Concrete class for a student expert."""

    def __init__(self, blackboard) -> None:
        super().__init__(blackboard)

    @property
    def is_eager_to_contribute(self) -> bool:
        return True

    def contribute(self) -> None:
        self.blackboard.common_state["problems"] += random.randint(1, 10)
        self.blackboard.common_state["suggestions"] += random.randint(1, 10)
        self.blackboard.common_state["contributions"] += [self.__class__.__name__]
        self.blackboard.common_state["progress"] += random.randint(1, 2)


class Scientist(AbstractExpert):
    """Concrete class for a scientist expert."""

    def __init__(self, blackboard) -> None:
        super().__init__(blackboard)

    @property
    def is_eager_to_contribute(self) -> int:
        return random.randint(0, 1)

    def contribute(self) -> None:
        self.blackboard.common_state["problems"] += random.randint(10, 20)
        self.blackboard.common_state["suggestions"] += random.randint(10, 20)
        self.blackboard.common_state["contributions"] += [self.__class__.__name__]
        self.blackboard.common_state["progress"] += random.randint(10, 30)


class Professor(AbstractExpert):
    def __init__(self, blackboard) -> None:
        super().__init__(blackboard)

    @property
    def is_eager_to_contribute(self) -> bool:
        return True if self.blackboard.common_state["problems"] > 100 else False

    def contribute(self) -> None:
        self.blackboard.common_state["problems"] += random.randint(1, 2)
        self.blackboard.common_state["suggestions"] += random.randint(10, 20)
        self.blackboard.common_state["contributions"] += [self.__class__.__name__]
        self.blackboard.common_state["progress"] += random.randint(10, 100)


def main():
    """
    >>> blackboard = Blackboard()
    >>> blackboard.add_expert(Student(blackboard))
    >>> blackboard.add_expert(Scientist(blackboard))
    >>> blackboard.add_expert(Professor(blackboard))

    >>> c = Controller(blackboard)
    >>> contributions = c.run_loop()

    >>> from pprint import pprint
    >>> pprint(contributions)
     ['Student',
    'Scientist',
    'Student',
    'Scientist',
    'Student',
    'Scientist',
    'Professor']
    """


if __name__ == "__main__":
    random.seed(1234)  # for deterministic doctest outputs
    import doctest

    doctest.testmod()


================================================
FILE: patterns/other/graph_search.py
================================================
from typing import Any, Dict, List, Optional, Union


class GraphSearch:
    """Graph search emulation in python, from source
    http://www.python.org/doc/essays/graphs/

    dfs stands for Depth First Search
    bfs stands for Breadth First Search"""

    def __init__(self, graph: Dict[str, List[str]]) -> None:
        self.graph = graph

    def find_path_dfs(
        self, start: str, end: str, path: Optional[List[str]] = None
    ) -> Optional[List[str]]:
        path = path or []

        path.append(start)
        if start == end:
            return path
        for node in self.graph.get(start, []):
            if node not in path:
                newpath = self.find_path_dfs(node, end, path[:])
                if newpath:
                    return newpath

    def find_all_paths_dfs(
        self, start: str, end: str, path: Optional[List[str]] = None
    ) -> List[Union[List[str], Any]]:
        path = path or []
        path.append(start)
        if start == end:
            return [path]
        paths = []
        for node in self.graph.get(start, []):
            if node not in path:
                newpaths = self.find_all_paths_dfs(node, end, path[:])
                paths.extend(newpaths)
        return paths

    def find_shortest_path_dfs(
        self, start: str, end: str, path: Optional[List[str]] = None
    ) -> Optional[List[str]]:
        path = path or []
        path.append(start)

        if start == end:
            return path
        shortest = None
        for node in self.graph.get(start, []):
            if node not in path:
                newpath = self.find_shortest_path_dfs(node, end, path[:])
                if newpath:
                    if not shortest or len(newpath) < len(shortest):
                        shortest = newpath
        return shortest

    def find_shortest_path_bfs(self, start: str, end: str) -> Optional[List[str]]:
        """
        Finds the shortest path between two nodes in a graph using breadth-first search.

        :param start: The node to start from.
        :type start: str or int
        :param end: The node to find the shortest path to.
            :type end: str or int

            :returns queue_path_to_end, dist_to[end]: A list of nodes
        representing the shortest path from `start` to `end`, and a dictionary
        mapping each node in the graph (except for `start`) with its distance from it
        (in terms of hops). If no such path exists, returns an empty list and an empty
        dictionary instead.
        """
        queue = [start]
        dist_to = {start: 0}
        edge_to = {}

        if start == end:
            return queue

        while len(queue):
            value = queue.pop(0)
            for node in self.graph[value]:
                if node not in dist_to.keys():
                    edge_to[node] = value
                    dist_to[node] = dist_to[value] + 1
                    queue.append(node)
                    if end in edge_to.keys():
                        path = []
                        node = end
                        while dist_to[node] != 0:
                            path.insert(0, node)
                            node = edge_to[node]
                        path.insert(0, start)
                        return path


def main():
    """
    # example of graph usage
    >>> graph = {
    ...     'A': ['B', 'C'],
    ...     'B': ['C', 'D'],
    ...     'C': ['D', 'G'],
    ...     'D': ['C'],
    ...     'E': ['F'],
    ...     'F': ['C'],
    ...     'G': ['E'],
    ...     'H': ['C']
    ... }

    # initialization of new graph search object
    >>> graph_search = GraphSearch(graph)

    >>> print(graph_search.find_path_dfs('A', 'D'))
    ['A', 'B', 'C', 'D']

    # start the search somewhere in the middle
    >>> print(graph_search.find_path_dfs('G', 'F'))
    ['G', 'E', 'F']

    # unreachable node
    >>> print(graph_search.find_path_dfs('C', 'H'))
    None

    # non existing node
    >>> print(graph_search.find_path_dfs('C', 'X'))
    None

    >>> print(graph_search.find_all_paths_dfs('A', 'D'))
    [['A', 'B', 'C', 'D'], ['A', 'B', 'D'], ['A', 'C', 'D']]
    >>> print(graph_search.find_shortest_path_dfs('A', 'D'))
    ['A', 'B', 'D']
    >>> print(graph_search.find_shortest_path_dfs('A', 'F'))
    ['A', 'C', 'G', 'E', 'F']

    >>> print(graph_search.find_shortest_path_bfs('A', 'D'))
    ['A', 'B', 'D']
    >>> print(graph_search.find_shortest_path_bfs('A', 'F'))
    ['A', 'C', 'G', 'E', 'F']

    # start the search somewhere in the middle
    >>> print(graph_search.find_shortest_path_bfs('G', 'F'))
    ['G', 'E', 'F']

    # unreachable node
    >>> print(graph_search.find_shortest_path_bfs('A', 'H'))
    None

    # non existing node
    >>> print(graph_search.find_shortest_path_bfs('A', 'X'))
    None
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod()


================================================
FILE: patterns/other/hsm/__init__.py
================================================


================================================
FILE: patterns/other/hsm/hsm.py
================================================
"""
Implementation of the HSM (hierarchical state machine) or
NFSM (nested finite state machine) C++ example from
http://www.eventhelix.com/RealtimeMantra/HierarchicalStateMachine.htm#.VwqLVEL950w
in Python

- single source 'message type' for state transition changes
- message type considered, messages (comment) not considered to avoid complexity
"""


class UnsupportedMessageType(BaseException):
    pass


class UnsupportedState(BaseException):
    pass


class UnsupportedTransition(BaseException):
    pass


class HierachicalStateMachine:
    def __init__(self):
        self._active_state = Active(self)  # Unit.Inservice.Active()
        self._standby_state = Standby(self)  # Unit.Inservice.Standby()
        self._suspect_state = Suspect(self)  # Unit.OutOfService.Suspect()
        self._failed_state = Failed(self)  # Unit.OutOfService.Failed()
        self._current_state = self._standby_state
        self.states = {
            "active": self._active_state,
            "standby": self._standby_state,
            "suspect": self._suspect_state,
            "failed": self._failed_state,
        }
        self.message_types = {
            "fault trigger": self._current_state.on_fault_trigger,
            "switchover": self._current_state.on_switchover,
            "diagnostics passed": self._current_state.on_diagnostics_passed,
            "diagnostics failed": self._current_state.on_diagnostics_failed,
            "operator inservice": self._current_state.on_operator_inservice,
        }

    def _next_state(self, state):
        try:
            self._current_state = self.states[state]
        except KeyError:
            raise UnsupportedState

    def _send_diagnostics_request(self):
        return "send diagnostic request"

    def _raise_alarm(self):
        return "raise alarm"

    def _clear_alarm(self):
        return "clear alarm"

    def _perform_switchover(self):
        return "perform switchover"

    def _send_switchover_response(self):
        return "send switchover response"

    def _send_operator_inservice_response(self):
        return "send operator inservice response"

    def _send_diagnostics_failure_report(self):
        return "send diagnostics failure report"

    def _send_diagnostics_pass_report(self):
        return "send diagnostics pass report"

    def _abort_diagnostics(self):
        return "abort diagnostics"

    def _check_mate_status(self):
        return "check mate status"

    def on_message(self, message_type):  # message ignored
        if message_type in self.message_types.keys():
            self.message_types[message_type]()
        else:
            raise UnsupportedMessageType


class Unit:
    def __init__(self, HierachicalStateMachine):
        self.hsm = HierachicalStateMachine

    def on_switchover(self):
        raise UnsupportedTransition

    def on_fault_trigger(self):
        raise UnsupportedTransition

    def on_diagnostics_failed(self):
        raise UnsupportedTransition

    def on_diagnostics_passed(self):
        raise UnsupportedTransition

    def on_operator_inservice(self):
        raise UnsupportedTransition


class Inservice(Unit):
    def __init__(self, HierachicalStateMachine):
        self._hsm = HierachicalStateMachine

    def on_fault_trigger(self):
        self._hsm._next_state("suspect")
        self._hsm._send_diagnostics_request()
        self._hsm._raise_alarm()

    def on_switchover(self):
        self._hsm._perform_switchover()
        self._hsm._check_mate_status()
        self._hsm._send_switchover_response()


class Active(Inservice):
    def __init__(self, HierachicalStateMachine):
        self._hsm = HierachicalStateMachine

    def on_fault_trigger(self):
        super().perform_switchover()
        super().on_fault_trigger()

    def on_switchover(self):
        self._hsm.on_switchover()  # message ignored
        self._hsm.next_state("standby")


class Standby(Inservice):
    def __init__(self, HierachicalStateMachine):
        self._hsm = HierachicalStateMachine

    def on_switchover(self):
        super().on_switchover()  # message ignored
        self._hsm._next_state("active")


class OutOfService(Unit):
    def __init__(self, HierachicalStateMachine):
        self._hsm = HierachicalStateMachine

    def on_operator_inservice(self):
        self._hsm.on_switchover()  # message ignored
        self._hsm.send_operator_inservice_response()
        self._hsm.next_state("suspect")


class Suspect(OutOfService):
    def __init__(self, HierachicalStateMachine):
        self._hsm = HierachicalStateMachine

    def on_diagnostics_failed(self):
        super().send_diagnostics_failure_report()
        super().next_state("failed")

    def on_diagnostics_passed(self):
        super().send_diagnostics_pass_report()
        super().clear_alarm()  # loss of redundancy alarm
        super().next_state("standby")

    def on_operator_inservice(self):
        super().abort_diagnostics()
        super().on_operator_inservice()  # message ignored


class Failed(OutOfService):
    """No need to override any method."""

    def __init__(self, HierachicalStateMachine):
        self._hsm = HierachicalStateMachine


================================================
FILE: patterns/structural/3-tier.py
================================================
"""
*TL;DR
Separates presentation, application processing, and data management functions.
"""

from typing import Dict, KeysView, Optional, Union


class Data:
    """Data Store Class"""

    products = {
        "milk": {"price": 1.50, "quantity": 10},
        "eggs": {"price": 0.20, "quantity": 100},
        "cheese": {"price": 2.00, "quantity": 10},
    }

    def __get__(self, obj, klas):
        print("(Fetching from Data Store)")
        return {"products": self.products}


class BusinessLogic:
    """Business logic holding data store instances"""

    data = Data()

    def product_list(self) -> KeysView[str]:
        return self.data["products"].keys()

    def product_information(
        self, product: str
    ) -> Optional[Dict[str, Union[int, float]]]:
        return self.data["products"].get(product, None)


class Ui:
    """UI interaction class"""

    def __init__(self) -> None:
        self.business_logic = BusinessLogic()

    def get_product_list(self) -> None:
        print("PRODUCT LIST:")
        for product in self.business_logic.product_list():
            print(product)
        print("")

    def get_product_information(self, product: str) -> None:
        product_info = self.business_logic.product_information(product)
        if product_info:
            print("PRODUCT INFORMATION:")
            print(
                f"Name: {product.title()}, "
                + f"Price: {product_info.get('price', 0):.2f}, "
                + f"Quantity: {product_info.get('quantity', 0):}"
            )
        else:
            print(f"That product '{product}' does not exist in the records")


def main():
    """
    >>> ui = Ui()
    >>> ui.get_product_list()
    PRODUCT LIST:
    (Fetching from Data Store)
    milk
    eggs
    cheese
    <BLANKLINE>

    >>> ui.get_product_information("cheese")
    (Fetching from Data Store)
    PRODUCT INFORMATION:
    Name: Cheese, Price: 2.00, Quantity: 10

    >>> ui.get_product_information("eggs")
    (Fetching from Data Store)
    PRODUCT INFORMATION:
    Name: Eggs, Price: 0.20, Quantity: 100

    >>> ui.get_product_information("milk")
    (Fetching from Data Store)
    PRODUCT INFORMATION:
    Name: Milk, Price: 1.50, Quantity: 10

    >>> ui.get_product_information("arepas")
    (Fetching from Data Store)
    That product 'arepas' does not exist in the records
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod()


================================================
FILE: patterns/structural/__init__.py
================================================


================================================
FILE: patterns/structural/adapter.py
================================================
"""
*What is this pattern about?
The Adapter pattern provides a different interface for a class. We can
think about it as a cable adapter that allows you to charge a phone
somewhere that has outlets in a different shape. Following this idea,
the Adapter pattern is useful to integrate classes that couldn't be
integrated due to their incompatible interfaces.

*What does this example do?

The example has classes that represent entities (Dog, Cat, Human, Car)
that make different noises. The Adapter class provides a different
interface to the original methods that make such noises. So the
original interfaces (e.g., bark and meow) are available under a
different name: make_noise.

*Where is the pattern used practically?
The Grok framework uses adapters to make objects work with a
particular API without modifying the objects themselves:
http://grok.zope.org/doc/current/grok_overview.html#adapters

*References:
http://ginstrom.com/scribbles/2008/11/06/generic-adapter-class-in-python/
https://sourcemaking.com/design_patterns/adapter
http://python-3-patterns-idioms-test.readthedocs.io/en/latest/ChangeInterface.html#adapter

*TL;DR
Allows the interface of an existing class to be used as another interface.
"""

from typing import Callable, TypeVar, Any, Dict

T = TypeVar("T")


class Dog:
    def __init__(self) -> None:
        self.name = "Dog"

    def bark(self) -> str:
        return "woof!"


class Cat:
    def __init__(self) -> None:
        self.name = "Cat"

    def meow(self) -> str:
        return "meow!"


class Human:
    def __init__(self) -> None:
        self.name = "Human"

    def speak(self) -> str:
        return "'hello'"


class Car:
    def __init__(self) -> None:
        self.name = "Car"

    def make_noise(self, octane_level: int) -> str:
        return f"vroom{'!' * octane_level}"


class Adapter:
    """Adapts an object by replacing methods.

    Usage
    ------
    dog = Dog()
    dog = Adapter(dog, make_noise=dog.bark)
    """

    def __init__(self, obj: T, **adapted_methods: Callable[..., Any]) -> None:
        """We set the adapted methods in the object's dict."""
        self.obj = obj
        self.__dict__.update(adapted_methods)

    def __getattr__(self, attr: str) -> Any:
        """All non-adapted calls are passed to the object."""
        return getattr(self.obj, attr)

    def original_dict(self) -> Dict[str, Any]:
        """Print original object dict."""
        return self.obj.__dict__


def main():
    """
    >>> objects = []
    >>> dog = Dog()
    >>> print(dog.__dict__)
    {'name': 'Dog'}

    >>> objects.append(Adapter(dog, make_noise=dog.bark))

    >>> objects[0].__dict__['obj'], objects[0].__dict__['make_noise']
    (<...Dog object at 0x...>, <bound method Dog.bark of <...Dog object at 0x...>>)

    >>> print(objects[0].original_dict())
    {'name': 'Dog'}

    >>> cat = Cat()
    >>> objects.append(Adapter(cat, make_noise=cat.meow))
    >>> human = Human()
    >>> objects.append(Adapter(human, make_noise=human.speak))
    >>> car = Car()
    >>> objects.append(Adapter(car, make_noise=lambda: car.make_noise(3)))

    >>> for obj in objects:
    ...    print("A {0} goes {1}".format(obj.name, obj.make_noise()))
    A Dog goes woof!
    A Cat goes meow!
    A Human goes 'hello'
    A Car goes vroom!!!
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod(optionflags=doctest.ELLIPSIS)


================================================
FILE: patterns/structural/bridge.py
================================================
"""
*References:
http://en.wikibooks.org/wiki/Computer_Science_Design_Patterns/Bridge_Pattern#Python

*TL;DR
Decouples an abstraction from its implementation.
"""
from typing import Union


# ConcreteImplementor 1/2
class DrawingAPI1:
    def draw_circle(self, x: int, y: int, radius: float) -> None:
        print(f"API1.circle at {x}:{y} radius {radius}")


# ConcreteImplementor 2/2
class DrawingAPI2:
    def draw_circle(self, x: int, y: int, radius: float) -> None:
        print(f"API2.circle at {x}:{y} radius {radius}")


# Refined Abstraction
class CircleShape:
    def __init__(
        self, x: int, y: int, radius: int, drawing_api: Union[DrawingAPI2, DrawingAPI1]
    ) -> None:
        self._x = x
        self._y = y
        self._radius = radius
        self._drawing_api = drawing_api

    # low-level i.e. Implementation specific
    def draw(self) -> None:
        self._drawing_api.draw_circle(self._x, self._y, self._radius)

    # high-level i.e. Abstraction specific
    def scale(self, pct: float) -> None:
        self._radius *= pct


def main():
    """
    >>> shapes = (CircleShape(1, 2, 3, DrawingAPI1()), CircleShape(5, 7, 11, DrawingAPI2()))

    >>> for shape in shapes:
    ...    shape.scale(2.5)
    ...    shape.draw()
    API1.circle at 1:2 radius 7.5
    API2.circle at 5:7 radius 27.5
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod()


================================================
FILE: patterns/structural/composite.py
================================================
"""
*What is this pattern about?
The composite pattern describes a group of objects that is treated the
same way as a single instance of the same type of object. The intent of
a composite is to "compose" objects into tree structures to represent
part-whole hierarchies. Implementing the composite pattern lets clients
treat individual objects and compositions uniformly.

*What does this example do?
The example implements a graphic class,which can be either an ellipse
or a composition of several graphics. Every graphic can be printed.

*Where is the pattern used practically?
In graphics editors a shape can be basic or complex. An example of a
simple shape is a line, where a complex shape is a rectangle which is
made of four line objects. Since shapes have many operations in common
such as rendering the shape to screen, and since shapes follow a
part-whole hierarchy, composite pattern can be used to enable the
program to deal with all shapes uniformly.

*References:
https://en.wikipedia.org/wiki/Composite_pattern
https://infinitescript.com/2014/10/the-23-gang-of-three-design-patterns/

*TL;DR
Describes a group of objects that is treated as a single instance.
"""

from abc import ABC, abstractmethod
from typing import List


class Graphic(ABC):
    @abstractmethod
    def render(self) -> None:
        raise NotImplementedError("You should implement this!")


class CompositeGraphic(Graphic):
    def __init__(self) -> None:
        self.graphics: List[Graphic] = []

    def render(self) -> None:
        for graphic in self.graphics:
            graphic.render()

    def add(self, graphic: Graphic) -> None:
        self.graphics.append(graphic)

    def remove(self, graphic: Graphic) -> None:
        self.graphics.remove(graphic)


class Ellipse(Graphic):
    def __init__(self, name: str) -> None:
        self.name = name

    def render(self) -> None:
        print(f"Ellipse: {self.name}")


def main():
    """
    >>> ellipse1 = Ellipse("1")
    >>> ellipse2 = Ellipse("2")
    >>> ellipse3 = Ellipse("3")
    >>> ellipse4 = Ellipse("4")

    >>> graphic1 = CompositeGraphic()
    >>> graphic2 = CompositeGraphic()

    >>> graphic1.add(ellipse1)
    >>> graphic1.add(ellipse2)
    >>> graphic1.add(ellipse3)
    >>> graphic2.add(ellipse4)

    >>> graphic = CompositeGraphic()

    >>> graphic.add(graphic1)
    >>> graphic.add(graphic2)

    >>> graphic.render()
    Ellipse: 1
    Ellipse: 2
    Ellipse: 3
    Ellipse: 4
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod()


================================================
FILE: patterns/structural/decorator.py
================================================
"""
*What is this pattern about?
The Decorator pattern is used to dynamically add a new feature to an
object without changing its implementation. It differs from
inheritance because the new feature is added only to that particular
object, not to the entire subclass.

*What does this example do?
This example shows a way to add formatting options (boldface and
italic) to a text by appending the corresponding tags (<b> and
<i>). Also, we can see that decorators can be applied one after the other,
since the original text is passed to the bold wrapper, which in turn
is passed to the italic wrapper.

*Where is the pattern used practically?
The Grok framework uses decorators to add functionalities to methods,
like permissions or subscription to an event:
http://grok.zope.org/doc/current/reference/decorators.html

*References:
https://sourcemaking.com/design_patterns/decorator

*TL;DR
Adds behaviour to object without affecting its class.
"""


class TextTag:
    """Represents a base text tag"""

    def __init__(self, text: str) -> None:
        self._text = text

    def render(self) -> str:
        return self._text


class BoldWrapper(TextTag):
    """Wraps a tag in <b>"""

    def __init__(self, wrapped: TextTag) -> None:
        self._wrapped = wrapped

    def render(self) -> str:
        return f"<b>{self._wrapped.render()}</b>"


class ItalicWrapper(TextTag):
    """Wraps a tag in <i>"""

    def __init__(self, wrapped: TextTag) -> None:
        self._wrapped = wrapped

    def render(self) -> str:
        return f"<i>{self._wrapped.render()}</i>"


def main():
    """
    >>> simple_hello = TextTag("hello, world!")
    >>> special_hello = ItalicWrapper(BoldWrapper(simple_hello))

    >>> print("before:", simple_hello.render())
    before: hello, world!

    >>> print("after:", special_hello.render())
    after: <i><b>hello, world!</b></i>
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod()


================================================
FILE: patterns/structural/facade.py
================================================
"""
Example from https://en.wikipedia.org/wiki/Facade_pattern#Python


*What is this pattern about?
The Facade pattern is a way to provide a simpler unified interface to
a more complex system. It provides an easier way to access functions
of the underlying system by providing a single entry point.
This kind of abstraction is seen in many real life situations. For
example, we can turn on a computer by just pressing a button, but in
fact there are many procedures and operations done when that happens
(e.g., loading programs from disk to memory). In this case, the button
serves as an unified interface to all the underlying procedures to
turn on a computer.

*Where is the pattern used practically?
This pattern can be seen in the Python standard library when we use
the isdir function. Although a user simply uses this function to know
whether a path refers to a directory, the system makes a few
operations and calls other modules (e.g., os.stat) to give the result.

*References:
https://sourcemaking.com/design_patterns/facade
https://fkromer.github.io/python-pattern-references/design/#facade
http://python-3-patterns-idioms-test.readthedocs.io/en/latest/ChangeInterface.html#facade

*TL;DR
Provides a simpler unified interface to a complex system.
"""


# Complex computer parts
class CPU:
    """
    Simple CPU representation.
    """

    def freeze(self) -> None:
        print("Freezing processor.")

    def jump(self, position: str) -> None:
        print("Jumping to:", position)

    def execute(self) -> None:
        print("Executing.")


class Memory:
    """
    Simple memory representation.
    """

    def load(self, position: str, data: str) -> None:
        print(f"Loading from {position} data: '{data}'.")


class SolidStateDrive:
    """
    Simple solid state drive representation.
    """

    def read(self, lba: str, size: str) -> str:
        return f"Some data from sector {lba} with size {size}"


class ComputerFacade:
    """
    Represents a facade for various computer parts.
    """

    def __init__(self):
        self.cpu = CPU()
        self.memory = Memory()
        self.ssd = SolidStateDrive()

    def start(self):
        self.cpu.freeze()
        self.memory.load("0x00", self.ssd.read("100", "1024"))
        self.cpu.jump("0x00")
        self.cpu.execute()


def main():
    """
    >>> computer_facade = ComputerFacade()
    >>> computer_facade.start()
    Freezing processor.
    Loading from 0x00 data: 'Some data from sector 100 with size 1024'.
    Jumping to: 0x00
    Executing.
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod(optionflags=doctest.ELLIPSIS)


================================================
FILE: patterns/structural/flyweight.py
================================================
"""
*What is this pattern about?
This pattern aims to minimise the number of objects that are needed by
a program at run-time. A Flyweight is an object shared by multiple
contexts, and is indistinguishable from an object that is not shared.

The state of a Flyweight should not be affected by it's context, this
is known as its intrinsic state. The decoupling of the objects state
from the object's context, allows the Flyweight to be shared.

*What does this example do?
The example below sets-up an 'object pool' which stores initialised
objects. When a 'Card' is created it first checks to see if it already
exists instead of creating a new one. This aims to reduce the number of
objects initialised by the program.

*References:
http://codesnipers.com/?q=python-flyweights
https://python-patterns.guide/gang-of-four/flyweight/

*Examples in Python ecosystem:
https://docs.python.org/3/library/sys.html#sys.intern

*TL;DR
Minimizes memory usage by sharing data with other similar objects.
"""

import weakref


class Card:
    """The Flyweight"""

    # Could be a simple dict.
    # With WeakValueDictionary garbage collection can reclaim the object
    # when there are no other references to it.
    _pool: weakref.WeakValueDictionary = weakref.WeakValueDictionary()

    def __new__(cls, value: str, suit: str):
        # If the object exists in the pool - just return it
        obj = cls._pool.get(value + suit)
        # otherwise - create new one (and add it to the pool)
        if obj is None:
            obj = object.__new__(Card)
            cls._pool[value + suit] = obj
            # This row does the part we usually see in `__init__`
            obj.value, obj.suit = value, suit
        return obj

    # If you uncomment `__init__` and comment-out `__new__` -
    #   Card becomes normal (non-flyweight).
    # def __init__(self, value, suit):
    #     self.value, self.suit = value, suit

    def __repr__(self) -> str:
        return f"<Card: {self.value}{self.suit}>"


def main():
    """
    >>> c1 = Card('9', 'h')
    >>> c2 = Card('9', 'h')
    >>> c1, c2
    (<Card: 9h>, <Card: 9h>)
    >>> c1 == c2
    True
    >>> c1 is c2
    True

    >>> c1.new_attr = 'temp'
    >>> c3 = Card('9', 'h')
    >>> hasattr(c3, 'new_attr')
    True

    >>> Card._pool.clear()
    >>> c4 = Card('9', 'h')
    >>> hasattr(c4, 'new_attr')
    False
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod()


================================================
FILE: patterns/structural/flyweight_with_metaclass.py
================================================
import weakref


class FlyweightMeta(type):
    def __new__(mcs, name, parents, dct):
        """
        Set up object pool

        :param name: class name
        :param parents: class parents
        :param dct: dict: includes class attributes, class methods,
        static methods, etc
        :return: new class
        """
        dct["pool"] = weakref.WeakValueDictionary()
        return super().__new__(mcs, name, parents, dct)

    @staticmethod
    def _serialize_params(cls, *args, **kwargs):
        """
        Serialize input parameters to a key.
        Simple implementation is just to serialize it as a string
        """
        args_list = list(map(str, args))
        args_list.extend([str(kwargs), cls.__name__])
        key = "".join(args_list)
        return key

    def __call__(cls, *args, **kwargs):
        key = FlyweightMeta._serialize_params(cls, *args, **kwargs)
        pool = getattr(cls, "pool", {})

        instance = pool.get(key)
        if instance is None:
            instance = super().__call__(*args, **kwargs)
            pool[key] = instance
        return instance


class Card2(metaclass=FlyweightMeta):
    def __init__(self, *args, **kwargs):
        # print('Init {}: {}'.format(self.__class__, (args, kwargs)))
        pass


if __name__ == "__main__":
    instances_pool = getattr(Card2, "pool")
    cm1 = Card2("10", "h", a=1)
    cm2 = Card2("10", "h", a=1)
    cm3 = Card2("10", "h", a=2)

    assert (cm1 == cm2) and (cm1 != cm3)
    assert (cm1 is cm2) and (cm1 is not cm3)
    assert len(instances_pool) == 2

    del cm1
    assert len(instances_pool) == 2

    del cm2
    assert len(instances_pool) == 1

    del cm3
    assert len(instances_pool) == 0


================================================
FILE: patterns/structural/front_controller.py
================================================
"""
@author: Gordeev Andrey <gordeev.and.and@gmail.com>

*TL;DR
Provides a centralized entry point that controls and manages request handling.
"""

from __future__ import annotations

from typing import Any


class MobileView:
    def show_index_page(self) -> None:
        print("Displaying mobile index page")


class TabletView:
    def show_index_page(self) -> None:
        print("Displaying tablet index page")


class Dispatcher:
    def __init__(self) -> None:
        self.mobile_view = MobileView()
        self.tablet_view = TabletView()

    def dispatch(self, request: Request) -> None:
        """
        This function is used to dispatch the request based on the type of device.
        If it is a mobile, then mobile view will be called and if it is a tablet,
        then tablet view will be called.
        Otherwise, an error message will be printed saying that cannot dispatch the request.
        """
        if request.type == Request.mobile_type:
            self.mobile_view.show_index_page()
        elif request.type == Request.tablet_type:
            self.tablet_view.show_index_page()
        else:
            print("Cannot dispatch the request")


class RequestController:
    """front controller"""

    def __init__(self) -> None:
        self.dispatcher = Dispatcher()

    def dispatch_request(self, request: Any) -> None:
        """
        This function takes a request object and sends it to the dispatcher.
        """
        if isinstance(request, Request):
            self.dispatcher.dispatch(request)
        else:
            print("request must be a Request object")


class Request:
    """request"""

    mobile_type = "mobile"
    tablet_type = "tablet"

    def __init__(self, request):
        self.type = None
        request = request.lower()
        if request == self.mobile_type:
            self.type = self.mobile_type
        elif request == self.tablet_type:
            self.type = self.tablet_type


def main():
    """
    >>> front_controller = RequestController()

    >>> front_controller.dispatch_request(Request('mobile'))
    Displaying mobile index page

    >>> front_controller.dispatch_request(Request('tablet'))
    Displaying tablet index page

    >>> front_controller.dispatch_request(Request('desktop'))
    Cannot dispatch the request

    >>> front_controller.dispatch_request('mobile')
    request must be a Request object
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod()


================================================
FILE: patterns/structural/mvc.py
================================================
"""
*TL;DR
Separates data in GUIs from the ways it is presented, and accepted.
"""

from abc import ABC, abstractmethod
from typing import Dict, List, Union, Any
from inspect import signature
from sys import argv


class Model(ABC):
    """The Model is the data layer of the application."""

    @abstractmethod
    def __iter__(self) -> Any:
        pass

    @abstractmethod
    def get(self, item: str) -> dict:
        """Returns an object with a .items() call method
        that iterates over key,value pairs of its information."""
        pass

    @property
    @abstractmethod
    def item_type(self) -> str:
        pass


class ProductModel(Model):
    """The Model is the data layer of the application."""

    class Price(float):
        """A polymorphic way to pass a float with a particular
        __str__ functionality."""

        def __str__(self) -> str:
            return f"{self:.2f}"

    products = {
        "milk": {"price": Price(1.50), "quantity": 10},
        "eggs": {"price": Price(0.20), "quantity": 100},
        "cheese": {"price": Price(2.00), "quantity": 10},
    }

    item_type = "product"

    def __iter__(self) -> Any:
        yield from self.products

    def get(self, product: str) -> dict:
        try:
            return self.products[product]
        except KeyError as e:
            raise KeyError(str(e) + " not in the model's item list.")


class View(ABC):
    """The View is the presentation layer of the application."""

    @abstractmethod
    def show_item_list(self, item_type: str, item_list: list) -> None:
        pass

    @abstractmethod
    def show_item_information(
        self, item_type: str, item_name: str, item_info: dict
    ) -> None:
        """Will look for item information by iterating over key,value pairs
        yielded by item_info.items()"""
        pass

    @abstractmethod
    def item_not_found(self, item_type: str, item_name: str) -> None:
        pass


class ConsoleView(View):
    """The View is the presentation layer of the application."""

    def show_item_list(self, item_type: str, item_list: list) -> None:
        print(item_type.upper() + " LIST:")
        for item in item_list:
            print(item)
        print("")

    @staticmethod
    def capitalizer(string: str) -> str:
        """Capitalizes the first letter of a string and lowercases the rest."""
        return string[0].upper() + string[1:].lower()

    def show_item_information(
        self, item_type: str, item_name: str, item_info: dict
    ) -> None:
        """Will look for item information by iterating over key,value pairs"""
        print(item_type.upper() + " INFORMATION:")
        printout = "Name: %s" % item_name
        for key, value in item_info.items():
            printout += ", " + self.capitalizer(str(key)) + ": " + str(value)
        printout += "\n"
        print(printout)

    def item_not_found(self, item_type: str, item_name: str) -> None:
        print(f'That {item_type} "{item_name}" does not exist in the records')


class Controller:
    """The Controller is the intermediary between the Model and the View."""

    def __init__(self, model_class: Model, view_class: View) -> None:
        self.model: Model = model_class
        self.view: View = view_class

    def show_items(self) -> None:
        items = list(self.model)
        item_type = self.model.item_type
        self.view.show_item_list(item_type, items)

    def show_item_information(self, item_name: str) -> None:
        """
        Show information about a {item_type} item.
        :param str item_name: the name of the {item_type} item to show information about
        """
        item_type: str = self.model.item_type
        try:
            item_info: dict = self.model.get(item_name)
        except Exception:
            self.view.item_not_found(item_type, item_name)
        else:
            self.view.show_item_information(item_type, item_name, item_info)


class Router:
    """The Router is the entry point of the application."""

    def __init__(self):
        self.routes = {}

    def register(
        self,
        path: str,
        controller_class: type[Controller],
        model_class: type[Model],
        view_class: type[View],
    ) -> None:
        model_instance: Model = model_class()
        view_instance: View = view_class()
        self.routes[path] = controller_class(model_instance, view_instance)

    def resolve(self, path: str) -> Controller:
        if self.routes.get(path):
            controller: Controller = self.routes[path]
            return controller
        else:
            raise KeyError(f"No controller registered for path '{path}'")


def main():
    """
    >>> model = ProductModel()
    >>> view = ConsoleView()
    >>> controller = Controller(model, view)

    >>> controller.show_items()
    PRODUCT LIST:
    milk
    eggs
    cheese
    <BLANKLINE>

    >>> controller.show_item_information("cheese")
    PRODUCT INFORMATION:
    Name: cheese, Price: 2.00, Quantity: 10
    <BLANKLINE>

    >>> controller.show_item_information("eggs")
    PRODUCT INFORMATION:
    Name: eggs, Price: 0.20, Quantity: 100
    <BLANKLINE>

    >>> controller.show_item_information("milk")
    PRODUCT INFORMATION:
    Name: milk, Price: 1.50, Quantity: 10
    <BLANKLINE>

    >>> controller.show_item_information("arepas")
    That product "arepas" does not exist in the records
    """


if __name__ == "__main__":
    router = Router()
    router.register("products", Controller, ProductModel, ConsoleView)
    controller: Controller = router.resolve(argv[1])

    action: str = str(argv[2]) if len(argv) > 2 else ""
    args: str = " ".join(map(str, argv[3:])) if len(argv) > 3 else ""

    if hasattr(controller, action):
        command = getattr(controller, action)
        sig = signature(command)

        if len(sig.parameters) > 0:
            if args:
                command(args)
            else:
                print("Command requires arguments.")
        else:
            command()
    else:
        print(f"Command {action} not found in the controller.")

    import doctest

    doctest.testmod()


================================================
FILE: patterns/structural/proxy.py
================================================
"""
*What is this pattern about?
Proxy is used in places where you want to add functionality to a class without
changing its interface. The main class is called `Real Subject`. A client should
use the proxy or the real subject without any code change, so both must have the
same interface. Logging and controlling access to the real subject are some of
the proxy pattern usages.

*References:
https://refactoring.guru/design-patterns/proxy/python/example
https://python-3-patterns-idioms-test.readthedocs.io/en/latest/Fronting.html

*TL;DR
Add functionality or logic (e.g. logging, caching, authorization) to a resource
without changing its interface.
"""

from typing import Union


class Subject:
    """
    As mentioned in the document, interfaces of both RealSubject and Proxy should
    be the same, because the client should be able to use RealSubject or Proxy with
    no code change.

    Not all times this interface is necessary. The point is the client should be
    able to use RealSubject or Proxy interchangeably with no change in code.
    """

    def do_the_job(self, user: str) -> None:
        raise NotImplementedError()


class RealSubject(Subject):
    """
    This is the main job doer. External services like payment gateways can be a
    good example.
    """

    def do_the_job(self, user: str) -> None:
        print(f"I am doing the job for {user}")


class Proxy(Subject):
    def __init__(self) -> None:
        self._real_subject = RealSubject()

    def do_the_job(self, user: str) -> None:
        """
        logging and controlling access are some examples of proxy usages.
        """

        print(f"[log] Doing the job for {user} is requested.")

        if user == "admin":
            self._real_subject.do_the_job(user)
        else:
            print("[log] I can do the job just for `admins`.")


def client(job_doer: Union[RealSubject, Proxy], user: str) -> None:
    job_doer.do_the_job(user)


def main():
    """
    >>> proxy = Proxy()

    >>> real_subject = RealSubject()

    >>> client(proxy, 'admin')
    [log] Doing the job for admin is requested.
    I am doing the job for admin

    >>> client(proxy, 'anonymous')
    [log] Doing the job for anonymous is requested.
    [log] I can do the job just for `admins`.

    >>> client(real_subject, 'admin')
    I am doing the job for admin

    >>> client(real_subject, 'anonymous')
    I am doing the job for anonymous
    """


if __name__ == "__main__":
    import doctest

    doctest.testmod()


================================================
FILE: pyproject.toml
================================================
[build-system]
requires = ["setuptools >= 77.0.3"]
build-backend = "setuptools.build_meta"

[project]
name = "python-patterns"
description = "A collection of design patterns and idioms in Python."
version = "0.1.0"
readme = "README.md"
requires-python = ">=3.10"
classifiers = [
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
    "Programming Language :: Python :: 3.13",
]
dependencies= [
]

maintainers=[
    { name="faif" }
]

[project.urls]
Homepage = "https://github.com/faif/python-patterns"
Repository = "https://github.com/faif/python-patterns"
"Bug Tracker" = "https://github.com/faif/python-patterns/issues"
Contributors = "https://github.com/faif/python-patterns/graphs/contributors"

[project.optional-dependencies]
dev = [
    "mypy",
    "pipx>=1.7.1",
    "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"
]

[tool.setuptools]
packages = ["patterns"]

[tool.pytest.ini_options]
filterwarnings = [
    "ignore::Warning:.*test class 'TestRunner'.*"
]
# Adding settings from tox.ini for pytest
testpaths = ["tests"]
#testpaths = ["tests", "patterns"]
python_files = ["test_*.py", "*_test.py"]
# Enable doctest discovery in patterns directory
addopts = "--doctest-modules --randomly-seed=1234 --cov=patterns --cov-report=term-missing"
doctest_optionflags = ["ELLIPSIS", "NORMALIZE_WHITESPACE"]
log_level = "INFO"

[tool.coverage.run]
branch = true
source = ["./"]
#source = ["patterns"]
# Ensure coverage data is collected properly
relative_files = true
parallel = true
dynamic_context = "test_function"
data_file = ".coverage"

[tool.coverage.report]
# Regexes for lines to exclude from consideration
exclude_lines = [
    "def __repr__",
    "if self\\.debug",
    "raise AssertionError",
    "raise NotImplementedError",
    "if 0:",
    "if __name__ == .__main__.:",
    "@(abc\\.)?abstractmethod"
]
ignore_errors = true

[tool.coverage.html]
directory = "coverage_html_report"

[tool.mypy]
python_version = "3.12"
ignore_missing_imports = true

[tool.flake8]
max-line-length = 120
ignore = ["E266", "E731", "W503"]
exclude = ["venv*"]

[tool.tox]
legacy_tox_ini = """
[tox]
envlist = py312,cov-report
skip_missing_interpreters = true
usedevelop = true

#[testenv]
#setenv =
#    COVERAGE_FILE = .coverage.{envname}
#deps =
#    -r requirements-dev.txt
#commands =
#    flake8 --exclude="venv/,.tox/" patterns/
#    coverage run -m pytest --randomly-seed=1234 --doctest-modules patterns/
#    coverage run -m pytest -s -vv --cov=patterns/ --log-level=INFO tests/

#[testenv:cov-report]
#setenv =
#    COVERAGE_FILE = .coverage
#deps = coverage
#commands =
#    coverage combine
#    coverage report
#"""

================================================
FILE: pytest_local.ini
================================================
[pytest]
addopts = -q
testpaths = tests


================================================
FILE: requirements-dev.txt
================================================
flake8
black
isort
pytest
pytest-randomly
mypy
pyupgrade
tox


================================================
FILE: tests/__init__.py
================================================


================================================
FILE: tests/behavioral/test_catalog.py
================================================
import pytest

from patterns.behavioral.catalog import Catalog, CatalogClass, CatalogInstance, CatalogStatic

def test_catalog_multiple_methods():
    test = Catalog('param_value_2')
    token = test.main_method()
    assert token == 'executed method 2!'

def test_catalog_multiple_instance_methods():
    test = CatalogInstance('param_value_1')
    token = test.main_method()
    assert token == 'Value x1'
    
def test_catalog_multiple_class_methods():
    test = CatalogClass('param_value_2')
    token = test.main_method()
    assert token == 'Value x2'

def test_catalog_multiple_static_methods():
    test = CatalogStatic('param_value_1')
    token = test.main_method()
    assert token == 'executed method 1!'


================================================
FILE: tests/behavioral/test_mediator.py
================================================
import pytest

from patterns.behavioral.mediator import User

def test_mediated_comments():
    molly = User('Molly')
    mediated_comment = molly.say("Hi Team! Meeting at 3 PM today.")
    assert mediated_comment == "[Molly says]: Hi Team! Meeting at 3 PM today."

    mark = User('Mark')
    mediated_comment = mark.say("Roger that!")
    assert mediated_comment == "[Mark says]: Roger that!"

    ethan = User('Ethan')
    mediated_comment = ethan.say("Alright.")
    assert mediated_comment == "[Ethan says]: Alright."


================================================
FILE: tests/behavioral/test_memento.py
================================================
import pytest

from patterns.behavioral.memento import NumObj, Transaction

def test_object_creation():
    num_obj = NumObj(-1)
    assert repr(num_obj) == '<NumObj: -1>', "Object representation not as expected"

def test_rollback_on_transaction():
    num_obj = NumObj(-1)
    a_transaction = Transaction(True, num_obj)
    for _i in range(3):
        num_obj.increment()
    a_transaction.commit()
    assert num_obj.value == 2

    for _i in range(3):
        num_obj.increment()
    try:
        num_obj.value += 'x'  # will fail
    except TypeError:
        a_transaction.rollback()
    assert num_obj.value == 2, "Transaction did not rollback as expected"

def test_rollback_with_transactional_annotation():
    num_obj = NumObj(2)
    with pytest.raises(TypeError):
        num_obj.do_stuff()
    assert num_obj.value == 2


================================================
FILE: tests/behavioral/test_observer.py
================================================
from unittest.mock import Mock, patch

import pytest

from patterns.behavioral.observer import Data, DecimalViewer, HexViewer


@pytest.fixture
def observable():
    return Data("some data")


def test_attach_detach(observable):
    decimal_viewer = DecimalViewer()
    assert len(observable._observers) == 0

    observable.attach(decimal_viewer)
    assert decimal_viewer in observable._observers

    observable.detach(decimal_viewer)
    assert decimal_viewer not in observable._observers


def test_one_data_change_notifies_each_observer_once(observable):
    observable.attach(DecimalViewer())
    observable.attach(HexViewer())

    with patch(
        "patterns.behavioral.observer.DecimalViewer.update", new_callable=Mock()
    ) as mocked_update:
        assert mocked_update.call_count == 0
        observable.data = 10
        assert mocked_update.call_count == 1


================================================
FILE: tests/behavioral/test_publish_subscribe.py
================================================
import unittest
from unittest.mock import call, patch

from patterns.behavioral.publish_subscribe import Provider, Publisher, Subscriber


class TestProvider(unittest.TestCase):
    """
    Integration tests ~ provider class with as little mocking as possible.
    """

    def test_subscriber_shall_be_attachable_to_subscriptions(cls):
        subscription = "sub msg"
        pro = Provider()
        cls.assertEqual(len(pro.subscribers), 0)
        sub = Subscriber("sub name", pro)
        sub.subscribe(subscription)
        cls.assertEqual(len(pro.subscribers[subscription]), 1)

    def test_subscriber_shall_be_detachable_from_subscriptions(cls):
        subscription = "sub msg"
        pro = Provider()
        sub = Subscriber("sub name", pro)
        sub.subscribe(subscription)
        cls.assertEqual(len(pro.subscribers[subscription]), 1)
        sub.unsubscribe(subscription)
        cls.assertEqual(len(pro.subscribers[subscription]), 0)

    def test_publisher_shall_append_subscription_message_to_queue(cls):
        """msg_queue ~ Provider.notify(msg) ~ Publisher.publish(msg)"""
        expected_msg = "expected msg"
        pro = Provider()
        pub = Publisher(pro)
        Subscriber("sub name", pro)
        cls.assertEqual(len(pro.msg_queue), 0)
        pub.publish(expected_msg)
        cls.assertEqual(len(pro.msg_queue), 1)
        cls.assertEqual(pro.msg_queue[0], expected_msg)

    def test_provider_shall_update_affected_subscribers_with_published_subscription(
        cls,
    ):
        pro = Provider()
        pub = Publisher(pro)
        sub1 = Subscriber("sub 1 name", pro)
        sub1.subscribe("sub 1 msg 1")
        sub1.subscribe("sub 1 msg 2")
        sub2 = Subscriber("sub 2 name", pro)
        sub2.subscribe("sub 2 msg 1")
        sub2.subscribe("sub 2 msg 2")
        with (
            patch.object(sub1, "run") as mock_subscriber1_run,
            patch.object(sub2, "run") as mock_subscriber2_run,
        ):
            pro.update()
            cls.assertEqual(mock_subscriber1_run.call_count, 0)
            cls.assertEqual(mock_subscriber2_run.call_count, 0)
        pub.publish("sub 1 msg 1")
        pub.publish("sub 1 msg 2")
        pub.publish("sub 2 msg 1")
        pub.publish("sub 2 msg 2")
        with (
            patch.object(sub1, "run") as mock_subscriber1_run,
            patch.object(sub2, "run") as mock_subscriber2_run,
        ):
            pro.update()
            expected_sub1_calls = [call("sub 1 msg 1"), call("sub 1 msg 2")]
            mock_subscriber1_run.assert_has_calls(expected_sub1_calls)
            expected_sub2_calls = [call("sub 2 msg 1"), call("sub 2 msg 2")]
            mock_subscriber2_run.assert_has_calls(expected_sub2_calls)


================================================
FILE: tests/behavioral/test_servant.py
================================================
from patterns.behavioral.servant import GeometryTools, Circle, Rectangle, Position
import pytest
import math


@pytest.fixture
def circle():
    return Circle(3, Position(0, 0))


@pytest.fixture
def rectangle():
    return Rectangle(4, 5, Position(0, 0))


def test_calculate_area(circle, rectangle):
    assert GeometryTools.calculate_area(circle) == math.pi * 3**2
    assert GeometryTools.calculate_area(rectangle) == 4 * 5

    with pytest.raises(ValueError):
        GeometryTools.calculate_area("invalid shape")


def test_calculate_perimeter(circle, rectangle):
    assert GeometryTools.calculate_perimeter(circle) == 2 * math.pi * 3
    assert GeometryTools.calculate_perimeter(rectangle) == 2 * (4 + 5)

    with pytest.raises(ValueError):
        GeometryTools.calculate_perimeter("invalid shape")


def test_move_to(circle, rectangle):
    new_position = Position(1, 1)
    GeometryTools.move_to(circle, new_position)
    assert circle.position == new_position

    new_position = Position(1, 1)
    GeometryTools.move_to(rectangle, new_position)
    assert rectangle.position == new_position


================================================
FILE: tests/behavioral/test_state.py
================================================
import pytest

from patterns.behavioral.state import Radio


@pytest.fixture
def radio():
    return Radio()


def test_initial_state(radio):
    assert radio.state.name == "AM"


def test_initial_am_station(radio):
    initial_pos = radio.state.pos
    assert radio.state.stations[initial_pos] == "1250"


def test_toggle_amfm(radio):
    assert radio.state.name == "AM"

    radio.toggle_amfm()
    assert radio.state.name == "FM"

    radio.toggle_amfm()
    assert radio.state.name == "AM"


================================================
FILE: tests/behavioral/test_strategy.py
================================================
import pytest

from patterns.behavioral.strategy import Order, on_sale_discount, ten_percent_discount


@pytest.fixture
def order():
    return Order(100)


@pytest.mark.parametrize(
    "func, discount", [(ten_percent_discount, 10.0), (on_sale_discount, 45.0)]
)
def test_discount_function_return(func, order, discount):
    assert func(order) == discount


@pytest.mark.parametrize(
    "func, price", [(ten_percent_discount, 100), (on_sale_discount, 100)]
)
def test_order_discount_strategy_validate_success(func, price):
    order = Order(price, func)

    assert order.price == price
    assert order.discount_strategy == func


def test_order_discount_strategy_validate_error():
    order = Order(10, discount_strategy=on_sale_discount)

    assert order.discount_strategy is None


@pytest.mark.parametrize(
    "func, price, discount",
    [(ten_percent_discount, 100, 90.0), (on_sale_discount, 100, 55.0)],
)
def test_discount_apply_success(func, price, discount):
    order = Order(price, func)

    assert order.apply_discount() == discount


================================================
FILE: tests/behavioral/test_visitor.py
================================================
import pytest

from patterns.behavioral.visitor import A, B, C, Visitor

@pytest.fixture
def visitor():
    return Visitor()

def test_visiting_generic_node(visitor):
    a = A()
    token = visitor.visit(a)
    assert token == 'generic_visit A', "The expected generic object was not called"

def test_visiting_specific_nodes(visitor):
    b = B()
    token = visitor.visit(b)
    assert token == 'visit_B B', "The expected specific object was not called"

def test_visiting_inherited_nodes(visitor):
    c = C()
    token = visitor.visit(c)
    assert token == 'visit_B C', "The expected inherited object was not called"


================================================
FILE: tests/creational/test_abstract_factory.py
================================================
import unittest
from unittest.mock import patch

from patterns.creational.abstract_factory import Dog, PetShop


class TestPetShop(unittest.TestCase):
    def test_dog_pet_shop_shall_show_dog_instance(self):
        dog_pet_shop = PetShop(Dog)
        with patch.object(Dog, "speak") as mock_Dog_speak:
            pet = dog_pet_shop.buy_pet("")
            pet.speak()
            self.assertEqual(mock_Dog_speak.call_count, 1)


================================================
FILE: tests/creational/test_borg.py
================================================
import unittest

from patterns.creational.borg import Borg, YourBorg


class BorgTest(unittest.TestCase):
    def setUp(self):
        self.b1 = Borg()
        self.b2 = Borg()
        # creating YourBorg instance implicitly sets the state attribute
        # for all borg instances.
        self.ib1 = YourBorg()

    def tearDown(self):
        self.ib1.state = "Init"

    def test_initial_borg_state_shall_be_init(self):
        b = Borg()
        self.assertEqual(b.state, "Init")

    def test_changing_instance_attribute_shall_change_borg_state(self):
        self.b1.state = "Running"
        self.assertEqual(self.b1.state, "Running")
        self.assertEqual(self.b2.state, "Running")
        self.assertEqual(self.ib1.state, "Running")

    def test_instances_shall_have_own_ids(self):
        self.assertNotEqual(id(self.b1), id(self.b2), id(self.ib1))


================================================
FILE: tests/creational/test_builder.py
================================================
import unittest

from patterns.creational.builder import ComplexHouse, Flat, House, construct_building


class TestSimple(unittest.TestCase):
    def test_house(self):
        house = House()
        self.assertEqual(house.size, "Big")
        self.assertEqual(house.floor, "One")

    def test_flat(self):
        flat = Flat()
        self.assertEqual(flat.size, "Small")
        self.assertEqual(flat.floor, "More than One")


class TestComplex(unittest.TestCase):
    def test_house(self):
        house = construct_building(ComplexHouse)
        self.assertEqual(house.size, "Big and fancy")
        self.assertEqual(house.floor, "One")


================================================
FILE: tests/creational/test_factory.py
================================================
import unittest
from patterns.creational.factory import get_localizer, GreekLocalizer, EnglishLocalizer

class TestFactory(unittest.TestCase):
    def test_get_localizer_greek(self):
        localizer = get_localizer("Greek")
        self.assertIsInstance(localizer, GreekLocalizer)
        self.assertEqual(localizer.localize("dog"), "σκύλος")
        self.assertEqual(localizer.localize("cat"), "γάτα")
        # Test unknown word returns the word itself
        self.assertEqual(localizer.localize("monkey"), "monkey")

    def test_get_localizer_english(self):
        localizer = get_localizer("English")
        self.assertIsInstance(localizer, EnglishLocalizer)
        self.assertEqual(localizer.localize("dog"), "dog")
        self.assertEqual(localizer.localize("cat"), "cat")

    def test_get_localizer_default(self):
        # Test default argument
        localizer = get_localizer()
        self.assertIsInstance(localizer, EnglishLocalizer)

    def test_get_localizer_unknown_language(self):
        # Test fallback for unknown language if applicable, 
        # or just verify what happens. 
        # Based on implementation: localizers.get(language, EnglishLocalizer)()
        # It defaults to EnglishLocalizer for unknown keys.
        localizer = get_localizer("Spanish")
        self.assertIsInstance(localizer, EnglishLocalizer)


================================================
FILE: tests/creational/test_lazy.py
================================================
import unittest

from patterns.creational.lazy_evaluation import Person


class TestDynamicExpanding(unittest.TestCase):
    def setUp(self):
        self.John = Person("John", "Coder")

    def test_innate_properties(self):
        self.assertDictEqual(
            {"name": "John", "occupation": "Coder", "call_count2": 0},
            self.John.__dict__,
        )

    def test_relatives_not_in_properties(self):
        self.assertNotIn("relatives", self.John.__dict__)

    def test_extended_properties(self):
        print(f"John's relatives: {self.John.relatives}")
        self.assertDictEqual(
            {
                "name": "John",
                "occupation": "Coder",
                "relatives": "Many relatives.",
                "call_count2": 0,
            },
            self.John.__dict__,
        )

    def test_relatives_after_access(self):
        print(f"John's relatives: {self.John.relatives}")
        self.assertIn("relatives", self.John.__dict__)

    def test_parents(self):
        for _ in range(2):
            self.assertEqual(self.John.parents, "Father and mother")
        self.assertEqual(self.John.call_count2, 1)


================================================
FILE: tests/creational/test_pool.py
================================================
import queue
import unittest

from patterns.creational.pool import ObjectPool


class TestPool(unittest.TestCase):
    def setUp(self):
        self.sample_queue = queue.Queue()
        self.sample_queue.put("first")
        self.sample_queue.put("second")

    def test_items_recoil(self):
        with ObjectPool(self.sample_queue, True) as pool:
            self.assertEqual(pool, "first")
        self.assertTrue(self.sample_queue.get() == "second")
        self.assertFalse(self.sample_queue.empty())
        self.assertTrue(self.sample_queue.get() == "first")
        self.assertTrue(self.sample_queue.empty())

    def test_frozen_pool(self):
        with ObjectPool(self.sample_queue) as pool:
            self.assertEqual(pool, "first")
            self.assertEqual(pool, "first")
        self.assertTrue(self.sample_queue.get() == "second")
        self.assertFalse(self.sample_queue.empty())
        self.assertTrue(self.sample_queue.get() == "first")
        self.assertTrue(self.sample_queue.empty())


class TestNaitivePool(unittest.TestCase):
    """def test_object(queue):
    queue_object = QueueObject(queue, True)
    print('Inside func: {}'.format(queue_object.object))"""

    def test_pool_behavior_with_single_object_inside(self):
        sample_queue = queue.Queue()
        sample_queue.put("yam")
        with ObjectPool(sample_queue) as obj:
            # print('Inside with: {}'.format(obj))
            self.assertEqual(obj, "yam")
        self.assertFalse(sample_queue.empty())
        self.assertTrue(sample_queue.get() == "yam")
        self.assertTrue(sample_queue.empty())

    # sample_queue.put('sam')
    # test_object(sample_queue)
    # print('Outside func: {}'.format(sample_queue.get()))

    # if not sample_queue.empty():


================================================
FILE: tests/creational/test_prototype.py
================================================
import unittest

from patterns.creational.prototype import Prototype, PrototypeDispatcher


class TestPrototypeFeatures(unittest.TestCase):
    def setUp(self):
        self.prototype = Prototype()

    def test_cloning_propperty_innate_values(self):
        sample_object_1 = self.prototype.clone()
        sample_object_2 = self.prototype.clone()
        self.assertEqual(sample_object_1.value, sample_object_2.value)

    def test_extended_property_values_cloning(self):
        sample_object_1 = self.prototype.clone()
        sample_object_1.some_value = "test string"
        sample_object_2 = self.prototype.clone()
        self.assertRaises(AttributeError, lambda: sample_object_2.some_value)

    def test_cloning_propperty_assigned_values(self):
        sample_object_1 = self.prototype.clone()
        sample_object_2 = self.prototype.clone(value="re-assigned")
        self.assertNotEqual(sample_object_1.value, sample_object_2.value)


class TestDispatcherFeatures(unittest.TestCase):
    def setUp(self):
        self.dispatcher = PrototypeDispatcher()
        self.prototype = Prototype()
        c = self.prototype.clone()
        a = self.prototype.clone(value="a-value", ext_value="E")
        b = self.prototype.clone(value="b-value", diff=True)
        self.dispatcher.register_object("A", a)
        self.dispatcher.register_object("B", b)
        self.dispatcher.register_object("C", c)

    def test_batch_retrieving(self):
        self.assertEqual(len(self.dispatcher.get_objects()), 3)

    def test_particular_properties_retrieving(self):
        self.assertEqual(self.dispatcher.get_objects()["A"].value, "a-value")
        self.assertEqual(self.dispatcher.get_objects()["B"].value, "b-value")
        self.assertEqual(self.dispatcher.get_objects()["C"].value, "default")

    def test_extended_properties_retrieving(self):
        self.assertEqual(self.dispatcher.get_objects()["A"].ext_value, "E")
        self.assertTrue(self.dispatcher.get_objects()["B"].diff)


================================================
FILE: tests/fundamental/test_delegation.py
================================================
import pytest

from patterns.fundamental.delegation_pattern import Delegator, Delegate


def test_delegator_delegates_attribute_and_call():
    d = Delegator(Delegate())
    assert d.p1 == 123
    assert d.do_something("something") == "Doing something"
    assert d.do_something("something", kw=", hi") == "Doing something, hi"


def test_delegator_missing_attribute_raises():
    d = Delegator(Delegate())
    with pytest.raises(AttributeError):
        _ = d.p2


================================================
FILE: tests/structural/test_adapter.py
================================================
import unittest

from patterns.structural.adapter import Adapter, Car, Cat, Dog, Human


class ClassTest(unittest.TestCase):
    def setUp(self):
        self.dog = Dog()
        self.cat = Cat()
        self.human = Human()
        self.car = Car()

    def test_dog_shall_bark(self):
        noise = self.dog.bark()
        expected_noise = "woof!"
        self.assertEqual(noise, expected_noise)

    def test_cat_shall_meow(self):
        noise = self.cat.meow()
        expected_noise = "meow!"
        self.assertEqual(noise, expected_noise)

    def test_human_shall_speak(self):
        noise = self.human.speak()
        expected_noise = "'hello'"
        self.assertEqual(noise, expected_noise)

    def test_car_shall_make_loud_noise(self):
        noise = self.car.make_noise(1)
        expected_noise = "vroom!"
        self.assertEqual(noise, expected_noise)

    def test_car_shall_make_very_loud_noise(self):
        noise = self.car.make_noise(10)
        expected_noise = "vroom!!!!!!!!!!"
        self.assertEqual(noise, expected_noise)


class AdapterTest(unittest.TestCase):
    def test_dog_adapter_shall_make_noise(self):
        dog = Dog()
        dog_adapter = Adapter(dog, make_noise=dog.bark)
        noise = dog_adapter.make_noise()
        expected_noise = "woof!"
        self.assertEqual(noise, expected_noise)

    def test_cat_adapter_shall_make_noise(self):
        cat = Cat()
        cat_adapter = Adapter(cat, make_noise=cat.meow)
        noise = cat_adapter.make_noise()
        expected_noise = "meow!"
        self.assertEqual(noise, expected_noise)

    def test_human_adapter_shall_make_noise(self):
        human = Human()
        human_adapter = Adapter(human, make_noise=human.speak)
        noise = human_adapter.make_noise()
        expected_noise = "'hello'"
        self.assertEqual(noise, expected_noise)

    def test_car_adapter_shall_make_loud_noise(self):
        car = Car()
        car_adapter = Adapter(car, make_noise=car.make_noise)
        noise = car_adapter.make_noise(1)
        expected_noise = "vroom!"
        self.assertEqual(noise, expected_noise)

    def test_car_adapter_shall_make_very_loud_noise(self):
        car = Car()
        car_adapter = Adapter(car, make_noise=car.make_noise)
        noise = car_adapter.make_noise(10)
        expected_noise = "vroom!!!!!!!!!!"

        self.assertEqual(noise, expected_noise)


================================================
FILE: tests/structural/test_bridge.py
================================================
import unittest
from unittest.mock import patch

from patterns.structural.bridge import CircleShape, DrawingAPI1, DrawingAPI2


class BridgeTest(unittest.TestCase):
    def test_bridge_shall_draw_with_concrete_api_implementation(cls):
        ci1 = DrawingAPI1()
        ci2 = DrawingAPI2()
        with (
            patch.object(ci1, "draw_circle") as mock_ci1_draw_circle,
            patch.object(ci2, "draw_circle") as mock_ci2_draw_circle,
        ):
            sh1 = CircleShape(1, 2, 3, ci1)
            sh1.draw()
            cls.assertEqual(mock_ci1_draw_circle.call_count, 1)
            sh2 = CircleShape(1, 2, 3, ci2)
            sh2.draw()
            cls.assertEqual(mock_ci2_draw_circle.call_count, 1)

    def test_bridge_shall_scale_both_api_circles_with_own_implementation(cls):
        SCALE_FACTOR = 2
        CIRCLE1_RADIUS = 3
        EXPECTED_CIRCLE1_RADIUS = 6
        CIRCLE2_RADIUS = CIRCLE1_RADIUS * CIRCLE1_RADIUS
        EXPECTED_CIRCLE2_RADIUS = CIRCLE2_RADIUS * SCALE_FACTOR

        ci1 = DrawingAPI1()
        ci2 = DrawingAPI2()
        sh1 = CircleShape(1, 2, CIRCLE1_RADIUS, ci1)
        sh2 = CircleShape(1, 2, CIRCLE2_RADIUS, ci2)
        sh1.scale(SCALE_FACTOR)
        sh2.scale(SCALE_FACTOR)
        cls.assertEqual(sh1._radius, EXPECTED_CIRCLE1_RADIUS)
        cls.assertEqual(sh2._radius, EXPECTED_CIRCLE2_RADIUS)
        with (
            patch.object(sh1, "scale") as mock_sh1_scale_circle,
            patch.object(sh2, "scale") as mock_sh2_scale_circle,
        ):
            sh1.scale(2)
            sh2.scale(2)
            cls.assertEqual(mock_sh1_scale_circle.call_count, 1)
            cls.assertEqual(mock_sh2_scale_circle.call_count, 1)


================================================
FILE: tests/structural/test_decorator.py
================================================
import unittest

from patterns.structural.decorator import BoldWrapper, ItalicWrapper, TextTag


class TestTextWrapping(unittest.TestCase):
    def setUp(self):
        self.raw_string = TextTag("raw but not cruel")

    def test_italic(self):
        self.assertEqual(
            ItalicWrapper(self.raw_string).render(), "<i>raw but not cruel</i>"
        )

    def test_bold(self):
        self.assertEqual(
            BoldWrapper(self.raw_string).render(), "<b>raw but not cruel</b>"
        )

    def test_mixed_bold_and_italic(self):
        self.assertEqual(
            BoldWrapper(ItalicWrapper(self.raw_string)).render(),
            "<b><i>raw but not cruel</i></b>",
        )


================================================
FILE: tests/structural/test_facade.py
================================================
from patterns.structural.facade import ComputerFacade


def test_computer_facade_start(capsys):
    cf = ComputerFacade()
    cf.start()
    out = capsys.readouterr().out
    assert "Freezing processor." in out
    assert "Loading from 0x00 data:" in out
    assert "Jumping to: 0x00" in out
    assert "Executing." in out


================================================
FILE: tests/structural/test_flyweight.py
================================================
from patterns.structural.flyweight import Card


def test_card_flyweight_identity_and_repr():
    c1 = Card("9", "h")
    c2 = Card("9", "h")
    assert c1 is c2
    assert repr(c1) == "<Card: 9h>"


def test_card_attribute_persistence_and_pool_clear():
    Card._pool.clear()
    c1 = Card("A", "s")
    c1.temp = "t"
    c2 = Card("A", "s")
    assert hasattr(c2, "temp")

    Card._pool.clear()
    c3 = Card("A", "s")
    assert not hasattr(c3, "temp")


================================================
FILE: tests/structural/test_mvc.py
================================================
import pytest

from patterns.structural.mvc import (
    ProductModel,
    ConsoleView,
    Controller,
    Router,
)


def test_productmodel_iteration_and_price_str():
    pm = ProductModel()
    items = list(pm)
    assert set(items) == {"milk", "eggs", "cheese"}

    info = pm.get("cheese")
    assert info["quantity"] == 10
    assert str(info["price"]) == "2.00"


def test_productmodel_get_raises_keyerror():
    pm = ProductModel()
    with pytest.raises(KeyError) as exc:
        pm.get("unknown_item")
    assert "not in the model's item list." in str(exc.value)


def test_consoleview_capitalizer_and_list_and_info(capsys):
    view = ConsoleView()
    # capitalizer
    assert view.capitalizer("heLLo") == "Hello"

    # show item list
    view.show_item_list("product", ["x", "y"])
    out = capsys.readouterr().out
    assert "PRODUCT LIST:" in out
    assert "x" in out and "y" in out

    # show item information formatting
    pm = ProductModel()
    controller = Controller(pm, view)
    controller.show_item_information("milk")
    out = capsys.readouterr().out
    assert "PRODUCT INFORMATION:" in out
    assert "Name: milk" in out
    assert "Price: 1.50" in out
    assert "Quantity: 10" in out


def test_show_item_information_missing_calls_item_not_found(capsys):
    view = ConsoleView()
    pm = ProductModel()
    controller = Controller(pm, view)

    controller.show_item_information("arepas")
    out = capsys.readouterr().out
    assert 'That product "arepas" does not exist in the records' in out


def test_router_register_resolve_and_unknown():
    router = Router()
    router.register("products", Controller, ProductModel, ConsoleView)
    controller = router.resolve("products")
    assert isinstance(controller, Controller)

    with pytest.raises(KeyError):
        router.resolve("no-such-path")


================================================
FILE: tests/structural/test_proxy.py
================================================
import sys
import unittest
from io import StringIO

from patterns.structural.proxy import Proxy, client


class ProxyTest(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        """Class scope setup."""
        cls.proxy = Proxy()

    def setUp(cls):
        """Function/test case scope setup."""
        cls.output = StringIO()
        cls.saved_stdout = sys.stdout
        sys.stdout = cls.output

    def tearDown(cls):
        """Function/test case scope teardown."""
        cls.output.close()
        sys.stdout = cls.saved_stdout

    def test_do_the_job_for_admin_shall_pass(self):
        client(self.proxy, "admin")
        assert self.output.getvalue() == (
            "[log] Doing the job for admin is requested.\n"
            "I am doing the job for admin\n"
        )

    def test_do_the_job_for_anonymous_shall_reject(self):
        client(self.proxy, "anonymous")
        assert self.output.getvalue() == (
            "[log] Doing the job for anonymous is requested.\n"
            "[log] I can do the job just for `admins`.\n"
        )


================================================
FILE: tests/test_hsm.py
================================================
import unittest
from unittest.mock import patch

from patterns.other.hsm.hsm import (
    Active,
    HierachicalStateMachine,
    Standby,
    Suspect,
    UnsupportedMessageType,
    UnsupportedState,
    UnsupportedTransition,
)


class HsmMethodTest(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.hsm = HierachicalStateMachine()

    def test_initial_state_shall_be_standby(cls):
        cls.assertEqual(isinstance(cls.hsm._current_state, Standby), True)

    def test_unsupported_state_shall_raise_exception(cls):
        with cls.assertRaises(UnsupportedState):
            cls.hsm._next_state("missing")

    def test_unsupported_message_type_shall_raise_exception(cls):
        with cls.assertRaises(UnsupportedMessageType):
            cls.hsm.on_message("trigger")

    def test_calling_next_state_shall_change_current_state(cls):
        cls.hsm._current_state = Standby  # initial state
        cls.hsm._next_state("active")
        cls.assertEqual(isinstance(cls.hsm._current_state, Active), True)
        cls.hsm._current_state = Standby(cls.hsm)  # initial state

    def test_method_perform_switchover_shall_return_specifically(cls):
        """Exemplary HierachicalStateMachine method test.
        (here: _perform_switchover()). Add additional test cases..."""
        return_value = cls.hsm._perform_switchover()
        expected_return_value = "perform switchover"
        cls.assertEqual(return_value, expected_return_value)


class StandbyStateTest(unittest.TestCase):
    """Exemplary 2nd level state test class (here: Standby state). Add missing
    state test classes..."""

    @classmethod
    def setUpClass(cls):
        cls.hsm = HierachicalStateMachine()

    def setUp(cls):
        cls.hsm._current_state = Standby(cls.hsm)

    def test_given_standby_on_message_switchover_shall_set_active(cls):
        cls.hsm.on_message("switchover")
        cls.assertEqual(isinstance(cls.hsm._current_state, Active), True)

    def test_given_standby_on_message_switchover_shall_call_hsm_methods(cls):
        with (
            patch.object(cls.hsm, "_perform_switchover") as mock_perform_switchover,
            patch.object(cls.hsm, "_check_mate_status") as mock_check_mate_status,
            patch.object(
                cls.hsm, "_send_switchover_response"
            ) as mock_send_switchover_response,
            patch.object(cls.hsm, "_next_state") as mock_next_state,
        ):
            cls.hsm.on_message("switchover")
            cls.assertEqual(mock_perform_switchover.call_count, 1)
            cls.assertEqual(mock_check_mate_status.call_count, 1)
            cls.assertEqual(mock_send_switchover_response.call_count, 1)
            cls.assertEqual(mock_next_state.call_count, 1)

    def test_given_standby_on_message_fault_trigger_shall_set_suspect(cls):
        cls.hsm.on_message("fault trigger")
        cls.assertEqual(isinstance(cls.hsm._current_state, Suspect), True)

    def test_given_standby_on_message_diagnostics_failed_shall_raise_exception_and_keep_in_state(
        cls,
    ):
        with cls.assertRaises(UnsupportedTransition):
            cls.hsm.on_message("diagnostics failed")
        cls.assertEqual(isinstance(cls.hsm._current_state, Standby), True)

    def test_given_standby_on_message_diagnostics_passed_shall_raise_exception_and_keep_in_state(
        cls,
    ):
        with cls.assertRaises(UnsupportedTransition):
            cls.hsm.on_message("diagnostics passed")
        cls.assertEqual(isinstance(cls.hsm._current_state, Standby), True)

    def test_given_standby_on_message_operator_inservice_shall_raise_exception_and_keep_in_state(
        cls,
    ):
        with cls.assertRaises(UnsupportedTransition):
            cls.hsm.on_message("operator inservice")
        cls.assertEqual(isinstance(cls.hsm._current_state, Standby), True)
Download .txt
gitextract_isa67v9p/

├── .codespellignore
├── .github/
│   └── workflows/
│       ├── lint_pr.yml
│       └── lint_python.yml
├── .gitignore
├── .travis.yml
├── Makefile
├── README.md
├── config_backup/
│   ├── .coveragerc
│   ├── setup.cfg
│   └── tox.ini
├── lint.sh
├── patterns/
│   ├── __init__.py
│   ├── behavioral/
│   │   ├── __init__.py
│   │   ├── 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
│   ├── creational/
│   │   ├── __init__.py
│   │   ├── abstract_factory.py
│   │   ├── borg.py
│   │   ├── builder.py
│   │   ├── factory.py
│   │   ├── lazy_evaluation.py
│   │   ├── pool.py
│   │   └── prototype.py
│   ├── dependency_injection.py
│   ├── fundamental/
│   │   ├── __init__.py
│   │   └── delegation_pattern.py
│   ├── other/
│   │   ├── __init__.py
│   │   ├── blackboard.py
│   │   ├── graph_search.py
│   │   └── hsm/
│   │       ├── __init__.py
│   │       └── hsm.py
│   └── structural/
│       ├── 3-tier.py
│       ├── __init__.py
│       ├── adapter.py
│       ├── bridge.py
│       ├── composite.py
│       ├── decorator.py
│       ├── facade.py
│       ├── flyweight.py
│       ├── flyweight_with_metaclass.py
│       ├── front_controller.py
│       ├── mvc.py
│       └── proxy.py
├── pyproject.toml
├── pytest_local.ini
├── requirements-dev.txt
└── tests/
    ├── __init__.py
    ├── 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
    ├── fundamental/
    │   └── test_delegation.py
    ├── structural/
    │   ├── test_adapter.py
    │   ├── test_bridge.py
    │   ├── test_decorator.py
    │   ├── test_facade.py
    │   ├── test_flyweight.py
    │   ├── test_mvc.py
    │   └── test_proxy.py
    └── test_hsm.py
Download .txt
SYMBOL INDEX (619 symbols across 65 files)

FILE: patterns/behavioral/catalog.py
  class Catalog (line 10) | class Catalog:
    method __init__ (line 14) | def __init__(self, param: str) -> None:
    method _static_method_1 (line 30) | def _static_method_1() -> str:
    method _static_method_2 (line 34) | def _static_method_2() -> str:
    method main_method (line 37) | def main_method(self) -> str:
  class CatalogInstance (line 46) | class CatalogInstance:
    method __init__ (line 51) | def __init__(self, param: str) -> None:
    method _instance_method_1 (line 60) | def _instance_method_1(self) -> str:
    method _instance_method_2 (line 63) | def _instance_method_2(self) -> str:
    method main_method (line 71) | def main_method(self) -> str:
  class CatalogClass (line 80) | class CatalogClass:
    method __init__ (line 88) | def __init__(self, param: str) -> None:
    method _class_method_1 (line 96) | def _class_method_1(cls) -> str:
    method _class_method_2 (line 100) | def _class_method_2(cls) -> str:
    method main_method (line 108) | def main_method(self) -> str:
  class CatalogStatic (line 117) | class CatalogStatic:
    method __init__ (line 122) | def __init__(self, param: str) -> None:
    method _static_method_1 (line 130) | def _static_method_1() -> str:
    method _static_method_2 (line 134) | def _static_method_2() -> str:
    method main_method (line 142) | def main_method(self) -> str:
  function main (line 152) | def main():

FILE: patterns/behavioral/chain_of_responsibility.py
  class Handler (line 29) | class Handler(ABC):
    method __init__ (line 30) | def __init__(self, successor: Optional["Handler"] = None):
    method handle (line 33) | def handle(self, request: int) -> None:
    method check_range (line 46) | def check_range(self, request: int) -> Optional[bool]:
  class ConcreteHandler0 (line 50) | class ConcreteHandler0(Handler):
    method check_range (line 56) | def check_range(request: int) -> Optional[bool]:
  class ConcreteHandler1 (line 63) | class ConcreteHandler1(Handler):
    method check_range (line 68) | def check_range(self, request: int) -> Optional[bool]:
  class ConcreteHandler2 (line 75) | class ConcreteHandler2(Handler):
    method check_range (line 78) | def check_range(self, request: int) -> Optional[bool]:
    method get_interval_from_db (line 86) | def get_interval_from_db() -> Tuple[int, int]:
  class FallbackHandler (line 90) | class FallbackHandler(Handler):
    method check_range (line 92) | def check_range(request: int) -> Optional[bool]:
  function main (line 97) | def main():

FILE: patterns/behavioral/chaining_method.py
  class Person (line 4) | class Person:
    method __init__ (line 5) | def __init__(self, name: str) -> None:
    method do_action (line 8) | def do_action(self, action: Action) -> Action:
  class Action (line 13) | class Action:
    method __init__ (line 14) | def __init__(self, name: str) -> None:
    method amount (line 17) | def amount(self, val: str) -> Action:
    method stop (line 21) | def stop(self) -> None:
  function main (line 25) | def main():

FILE: patterns/behavioral/command.py
  class HideFileCommand (line 26) | class HideFileCommand:
    method __init__ (line 31) | def __init__(self) -> None:
    method execute (line 35) | def execute(self, filename: str) -> None:
    method undo (line 39) | def undo(self) -> None:
  class DeleteFileCommand (line 44) | class DeleteFileCommand:
    method __init__ (line 49) | def __init__(self) -> None:
    method execute (line 53) | def execute(self, filename: str) -> None:
    method undo (line 57) | def undo(self) -> None:
  class MenuItem (line 62) | class MenuItem:
    method __init__ (line 67) | def __init__(self, command: Union[HideFileCommand, DeleteFileCommand])...
    method on_do_press (line 70) | def on_do_press(self, filename: str) -> None:
    method on_undo_press (line 73) | def on_undo_press(self) -> None:
  function main (line 77) | def main():

FILE: patterns/behavioral/iterator.py
  function count_to (line 10) | def count_to(count: int):
  function count_to_two (line 17) | def count_to_two() -> None:
  function count_to_five (line 21) | def count_to_five() -> None:
  function main (line 25) | def main():

FILE: patterns/behavioral/iterator_alt.py
  class NumberWords (line 11) | class NumberWords:
    method __init__ (line 22) | def __init__(self, start: int, stop: int) -> None:
    method __iter__ (line 26) | def __iter__(self) -> NumberWords:  # this makes the class an Iterable
    method __next__ (line 29) | def __next__(self) -> str:  # this makes the class an Iterator
  function main (line 40) | def main():

FILE: patterns/behavioral/mediator.py
  class ChatRoom (line 14) | class ChatRoom:
    method display_message (line 17) | def display_message(self, user: User, message: str) -> None:
  class User (line 21) | class User:
    method __init__ (line 24) | def __init__(self, name: str) -> None:
    method say (line 28) | def say(self, message: str) -> None:
    method __str__ (line 31) | def __str__(self) -> str:
  function main (line 35) | def main():

FILE: patterns/behavioral/memento.py
  function memento (line 12) | def memento(obj: Any, deep: bool = False) -> Callable:
  class Transaction (line 22) | class Transaction:
    method __init__ (line 31) | def __init__(self, deep: bool, *targets: Any) -> None:
    method commit (line 36) | def commit(self) -> None:
    method rollback (line 39) | def rollback(self) -> None:
  function Transactional (line 44) | def Transactional(method):
  class NumObj (line 72) | class NumObj:
    method __init__ (line 73) | def __init__(self, value: int) -> None:
    method __repr__ (line 76) | def __repr__(self) -> str:
    method increment (line 79) | def increment(self) -> None:
    method do_stuff (line 83) | def do_stuff(self) -> None:
  function main (line 88) | def main():

FILE: patterns/behavioral/observer.py
  class Observer (line 17) | class Observer:
    method update (line 18) | def update(self, subject: Subject) -> None:
  class Subject (line 28) | class Subject:
    method __init__ (line 31) | def __init__(self) -> None:
    method attach (line 37) | def attach(self, observer: Observer) -> None:
    method detach (line 47) | def detach(self, observer: Observer) -> None:
    method notify (line 59) | def notify(self) -> None:
  class Data (line 67) | class Data(Subject):
    method __init__ (line 68) | def __init__(self, name: str = "") -> None:
    method data (line 74) | def data(self) -> int:
    method data (line 78) | def data(self, value: int) -> None:
  class HexViewer (line 83) | class HexViewer:
    method update (line 84) | def update(self, subject: Data) -> None:
  class DecimalViewer (line 88) | class DecimalViewer:
    method update (line 89) | def update(self, subject: Data) -> None:
  function main (line 93) | def main():

FILE: patterns/behavioral/publish_subscribe.py
  class Provider (line 10) | class Provider:
    method __init__ (line 11) | def __init__(self) -> None:
    method notify (line 15) | def notify(self, msg: str) -> None:
    method subscribe (line 18) | def subscribe(self, msg: str, subscriber: Subscriber) -> None:
    method unsubscribe (line 21) | def unsubscribe(self, msg: str, subscriber: Subscriber) -> None:
    method update (line 24) | def update(self) -> None:
  class Publisher (line 31) | class Publisher:
    method __init__ (line 32) | def __init__(self, msg_center: Provider) -> None:
    method publish (line 35) | def publish(self, msg: str) -> None:
  class Subscriber (line 39) | class Subscriber:
    method __init__ (line 40) | def __init__(self, name: str, msg_center: Provider) -> None:
    method subscribe (line 44) | def subscribe(self, msg: str) -> None:
    method unsubscribe (line 47) | def unsubscribe(self, msg: str) -> None:
    method run (line 50) | def run(self, msg: str) -> None:
  function main (line 54) | def main():

FILE: patterns/behavioral/registry.py
  class RegistryHolder (line 4) | class RegistryHolder(type):
    method __new__ (line 7) | def __new__(cls, name, bases, attrs):
    method get_registry (line 17) | def get_registry(cls):
  class BaseRegisteredClass (line 21) | class BaseRegisteredClass(metaclass=RegistryHolder):
  function main (line 29) | def main():

FILE: patterns/behavioral/servant.py
  class Position (line 26) | class Position:
    method __init__ (line 29) | def __init__(self, x, y):
  class Circle (line 34) | class Circle:
    method __init__ (line 37) | def __init__(self, radius, position: Position):
  class Rectangle (line 42) | class Rectangle:
    method __init__ (line 45) | def __init__(self, width, height, position: Position):
  class GeometryTools (line 51) | class GeometryTools:
    method calculate_area (line 58) | def calculate_area(shape):
    method calculate_perimeter (line 79) | def calculate_perimeter(shape):
    method move_to (line 100) | def move_to(shape, new_position: Position):
  function main (line 112) | def main():

FILE: patterns/behavioral/specification.py
  class Specification (line 12) | class Specification:
    method and_specification (line 13) | def and_specification(self, candidate):
    method or_specification (line 16) | def or_specification(self, candidate):
    method not_specification (line 19) | def not_specification(self):
    method is_satisfied_by (line 23) | def is_satisfied_by(self, candidate):
  class CompositeSpecification (line 27) | class CompositeSpecification(Specification):
    method is_satisfied_by (line 29) | def is_satisfied_by(self, candidate):
    method and_specification (line 32) | def and_specification(self, candidate: "Specification") -> "AndSpecifi...
    method or_specification (line 35) | def or_specification(self, candidate: "Specification") -> "OrSpecifica...
    method not_specification (line 38) | def not_specification(self) -> "NotSpecification":
  class AndSpecification (line 42) | class AndSpecification(CompositeSpecification):
    method __init__ (line 43) | def __init__(self, one: "Specification", other: "Specification") -> None:
    method is_satisfied_by (line 47) | def is_satisfied_by(self, candidate: Union["User", str]) -> bool:
  class OrSpecification (line 54) | class OrSpecification(CompositeSpecification):
    method __init__ (line 55) | def __init__(self, one: "Specification", other: "Specification") -> None:
    method is_satisfied_by (line 59) | def is_satisfied_by(self, candidate: Union["User", str]):
  class NotSpecification (line 66) | class NotSpecification(CompositeSpecification):
    method __init__ (line 67) | def __init__(self, wrapped: "Specification"):
    method is_satisfied_by (line 70) | def is_satisfied_by(self, candidate: Union["User", str]):
  class User (line 74) | class User:
    method __init__ (line 75) | def __init__(self, super_user: bool = False) -> None:
  class UserSpecification (line 79) | class UserSpecification(CompositeSpecification):
    method is_satisfied_by (line 80) | def is_satisfied_by(self, candidate: Union["User", str]) -> bool:
  class SuperUserSpecification (line 84) | class SuperUserSpecification(CompositeSpecification):
    method is_satisfied_by (line 85) | def is_satisfied_by(self, candidate: "User") -> bool:
  function main (line 89) | def main():

FILE: patterns/behavioral/state.py
  class State (line 14) | class State:
    method scan (line 17) | def scan(self) -> None:
  class AmState (line 25) | class AmState(State):
    method __init__ (line 26) | def __init__(self, radio: Radio) -> None:
    method toggle_amfm (line 32) | def toggle_amfm(self) -> None:
  class FmState (line 37) | class FmState(State):
    method __init__ (line 38) | def __init__(self, radio: Radio) -> None:
    method toggle_amfm (line 44) | def toggle_amfm(self) -> None:
  class Radio (line 49) | class Radio:
    method __init__ (line 52) | def __init__(self) -> None:
    method toggle_amfm (line 58) | def toggle_amfm(self) -> None:
    method scan (line 61) | def scan(self) -> None:
  function main (line 65) | def main():

FILE: patterns/behavioral/strategy.py
  class DiscountStrategyValidator (line 15) | class DiscountStrategyValidator:  # Descriptor class for check perform
    method validate (line 17) | def validate(obj: Order, value: Callable) -> bool:
    method __set_name__ (line 29) | def __set_name__(self, owner, name: str) -> None:
    method __set__ (line 32) | def __set__(self, obj: Order, value: Callable = None) -> None:
    method __get__ (line 38) | def __get__(self, obj: object, objtype: type = None):
  class Order (line 42) | class Order:
    method __init__ (line 45) | def __init__(self, price: float, discount_strategy: Callable = None) -...
    method apply_discount (line 49) | def apply_discount(self) -> float:
    method __repr__ (line 57) | def __repr__(self) -> str:
  function ten_percent_discount (line 62) | def ten_percent_discount(order: Order) -> float:
  function on_sale_discount (line 66) | def on_sale_discount(order: Order) -> float:
  function main (line 70) | def main():

FILE: patterns/behavioral/template.py
  function get_text (line 13) | def get_text() -> str:
  function get_pdf (line 17) | def get_pdf() -> str:
  function get_csv (line 21) | def get_csv() -> str:
  function convert_to_text (line 25) | def convert_to_text(data: str) -> str:
  function saver (line 30) | def saver() -> None:
  function template_function (line 34) | def template_function(getter, converter=False, to_save=False) -> None:
  function main (line 49) | def main():

FILE: patterns/behavioral/visitor.py
  class Node (line 20) | class Node:
  class A (line 24) | class A(Node):
  class B (line 28) | class B(Node):
  class C (line 32) | class C(A, B):
  class Visitor (line 36) | class Visitor:
    method visit (line 37) | def visit(self, node: Union[A, C, B], *args, **kwargs) -> None:
    method generic_visit (line 49) | def generic_visit(self, node: A, *args, **kwargs) -> None:
    method visit_B (line 52) | def visit_B(self, node: Union[C, B], *args, **kwargs) -> None:
  function main (line 56) | def main():

FILE: patterns/creational/abstract_factory.py
  class Pet (line 37) | class Pet:
    method __init__ (line 38) | def __init__(self, name: str) -> None:
    method speak (line 41) | def speak(self) -> None:
    method __str__ (line 44) | def __str__(self) -> str:
  class Dog (line 48) | class Dog(Pet):
    method speak (line 49) | def speak(self) -> None:
    method __str__ (line 52) | def __str__(self) -> str:
  class Cat (line 56) | class Cat(Pet):
    method speak (line 57) | def speak(self) -> None:
    method __str__ (line 60) | def __str__(self) -> str:
  class PetShop (line 64) | class PetShop:
    method __init__ (line 67) | def __init__(self, animal_factory: Type[Pet]) -> None:
    method buy_pet (line 72) | def buy_pet(self, name: str) -> Pet:
  function main (line 81) | def main() -> None:

FILE: patterns/creational/borg.py
  class Borg (line 39) | class Borg:
    method __init__ (line 42) | def __init__(self) -> None:
  class YourBorg (line 46) | class YourBorg(Borg):
    method __init__ (line 47) | def __init__(self, state: str = None) -> None:
    method __str__ (line 56) | def __str__(self) -> str:
  function main (line 60) | def main():

FILE: patterns/creational/builder.py
  class Building (line 34) | class Building:
    method __init__ (line 35) | def __init__(self) -> None:
    method build_floor (line 39) | def build_floor(self):
    method build_size (line 42) | def build_size(self):
    method __repr__ (line 45) | def __repr__(self) -> str:
  class House (line 50) | class House(Building):
    method build_floor (line 51) | def build_floor(self) -> None:
    method build_size (line 54) | def build_size(self) -> None:
  class Flat (line 58) | class Flat(Building):
    method build_floor (line 59) | def build_floor(self) -> None:
    method build_size (line 62) | def build_size(self) -> None:
  class ComplexBuilding (line 72) | class ComplexBuilding:
    method __repr__ (line 73) | def __repr__(self) -> str:
  class ComplexHouse (line 77) | class ComplexHouse(ComplexBuilding):
    method build_floor (line 78) | def build_floor(self) -> None:
    method build_size (line 81) | def build_size(self) -> None:
  function construct_building (line 85) | def construct_building(cls) -> Building:
  function main (line 92) | def main():

FILE: patterns/creational/factory.py
  class Localizer (line 28) | class Localizer(Protocol):
    method localize (line 29) | def localize(self, msg: str) -> str: ...
  class GreekLocalizer (line 32) | class GreekLocalizer:
    method __init__ (line 35) | def __init__(self) -> None:
    method localize (line 38) | def localize(self, msg: str) -> str:
  class EnglishLocalizer (line 43) | class EnglishLocalizer:
    method localize (line 46) | def localize(self, msg: str) -> str:
  function get_localizer (line 50) | def get_localizer(language: str = "English") -> Localizer:
  function main (line 60) | def main():

FILE: patterns/creational/lazy_evaluation.py
  class lazy_property (line 26) | class lazy_property:
    method __init__ (line 27) | def __init__(self, function: Callable) -> None:
    method __get__ (line 31) | def __get__(self, obj: "Person", type_: Type["Person"]) -> str:
  function lazy_property2 (line 39) | def lazy_property2(fn: Callable) -> property:
  class Person (line 57) | class Person:
    method __init__ (line 58) | def __init__(self, name: str, occupation: str) -> None:
    method relatives (line 64) | def relatives(self) -> str:
    method parents (line 70) | def parents(self) -> str:
  function main (line 75) | def main():

FILE: patterns/creational/pool.py
  class ObjectPool (line 35) | class ObjectPool:
    method __init__ (line 36) | def __init__(self, queue: Queue, auto_get: bool = False) -> None:
    method __enter__ (line 40) | def __enter__(self) -> str:
    method __exit__ (line 45) | def __exit__(
    method __del__ (line 55) | def __del__(self) -> None:
  function main (line 61) | def main():

FILE: patterns/creational/prototype.py
  class Prototype (line 29) | class Prototype:
    method __init__ (line 30) | def __init__(self, value: str = "default", **attrs: Any) -> None:
    method clone (line 34) | def clone(self, **attrs: Any) -> Prototype:
  class PrototypeDispatcher (line 43) | class PrototypeDispatcher:
    method __init__ (line 44) | def __init__(self):
    method get_objects (line 47) | def get_objects(self) -> dict[str, Prototype]:
    method register_object (line 51) | def register_object(self, name: str, obj: Prototype) -> None:
    method unregister_object (line 55) | def unregister_object(self, name: str) -> None:
  function main (line 60) | def main() -> None:

FILE: patterns/dependency_injection.py
  class ConstructorInjection (line 30) | class ConstructorInjection:
    method __init__ (line 31) | def __init__(self, time_provider: Callable) -> None:
    method get_current_time_as_html_fragment (line 34) | def get_current_time_as_html_fragment(self) -> str:
  class ParameterInjection (line 42) | class ParameterInjection:
    method __init__ (line 43) | def __init__(self) -> None:
    method get_current_time_as_html_fragment (line 46) | def get_current_time_as_html_fragment(self, time_provider: Callable) -...
  class SetterInjection (line 54) | class SetterInjection:
    method __init__ (line 57) | def __init__(self):
    method set_time_provider (line 60) | def set_time_provider(self, time_provider: Callable):
    method get_current_time_as_html_fragment (line 63) | def get_current_time_as_html_fragment(self):
  function production_code_time_provider (line 71) | def production_code_time_provider() -> str:
  function midnight_time_provider (line 81) | def midnight_time_provider() -> str:
  function main (line 86) | def main():

FILE: patterns/fundamental/delegation_pattern.py
  class Delegator (line 14) | class Delegator:
    method __init__ (line 33) | def __init__(self, delegate: Delegate) -> None:
    method __getattr__ (line 36) | def __getattr__(self, name: str) -> Any | Callable:
  class Delegate (line 48) | class Delegate:
    method __init__ (line 49) | def __init__(self) -> None:
    method do_something (line 52) | def do_something(self, something: str, kw=None) -> str:

FILE: patterns/other/blackboard.py
  class AbstractExpert (line 16) | class AbstractExpert(ABC):
    method __init__ (line 20) | def __init__(self, blackboard) -> None:
    method is_eager_to_contribute (line 25) | def is_eager_to_contribute(self) -> int:
    method contribute (line 29) | def contribute(self) -> None:
  class Blackboard (line 33) | class Blackboard:
    method __init__ (line 36) | def __init__(self) -> None:
    method add_expert (line 45) | def add_expert(self, expert: AbstractExpert) -> None:
  class Controller (line 49) | class Controller:
    method __init__ (line 52) | def __init__(self, blackboard: Blackboard) -> None:
    method run_loop (line 55) | def run_loop(self):
  class Student (line 67) | class Student(AbstractExpert):
    method __init__ (line 70) | def __init__(self, blackboard) -> None:
    method is_eager_to_contribute (line 74) | def is_eager_to_contribute(self) -> bool:
    method contribute (line 77) | def contribute(self) -> None:
  class Scientist (line 84) | class Scientist(AbstractExpert):
    method __init__ (line 87) | def __init__(self, blackboard) -> None:
    method is_eager_to_contribute (line 91) | def is_eager_to_contribute(self) -> int:
    method contribute (line 94) | def contribute(self) -> None:
  class Professor (line 101) | class Professor(AbstractExpert):
    method __init__ (line 102) | def __init__(self, blackboard) -> None:
    method is_eager_to_contribute (line 106) | def is_eager_to_contribute(self) -> bool:
    method contribute (line 109) | def contribute(self) -> None:
  function main (line 116) | def main():

FILE: patterns/other/graph_search.py
  class GraphSearch (line 4) | class GraphSearch:
    method __init__ (line 11) | def __init__(self, graph: Dict[str, List[str]]) -> None:
    method find_path_dfs (line 14) | def find_path_dfs(
    method find_all_paths_dfs (line 28) | def find_all_paths_dfs(
    method find_shortest_path_dfs (line 42) | def find_shortest_path_dfs(
    method find_shortest_path_bfs (line 59) | def find_shortest_path_bfs(self, start: str, end: str) -> Optional[Lis...
  function main (line 98) | def main():

FILE: patterns/other/hsm/hsm.py
  class UnsupportedMessageType (line 12) | class UnsupportedMessageType(BaseException):
  class UnsupportedState (line 16) | class UnsupportedState(BaseException):
  class UnsupportedTransition (line 20) | class UnsupportedTransition(BaseException):
  class HierachicalStateMachine (line 24) | class HierachicalStateMachine:
    method __init__ (line 25) | def __init__(self):
    method _next_state (line 45) | def _next_state(self, state):
    method _send_diagnostics_request (line 51) | def _send_diagnostics_request(self):
    method _raise_alarm (line 54) | def _raise_alarm(self):
    method _clear_alarm (line 57) | def _clear_alarm(self):
    method _perform_switchover (line 60) | def _perform_switchover(self):
    method _send_switchover_response (line 63) | def _send_switchover_response(self):
    method _send_operator_inservice_response (line 66) | def _send_operator_inservice_response(self):
    method _send_diagnostics_failure_report (line 69) | def _send_diagnostics_failure_report(self):
    method _send_diagnostics_pass_report (line 72) | def _send_diagnostics_pass_report(self):
    method _abort_diagnostics (line 75) | def _abort_diagnostics(self):
    method _check_mate_status (line 78) | def _check_mate_status(self):
    method on_message (line 81) | def on_message(self, message_type):  # message ignored
  class Unit (line 88) | class Unit:
    method __init__ (line 89) | def __init__(self, HierachicalStateMachine):
    method on_switchover (line 92) | def on_switchover(self):
    method on_fault_trigger (line 95) | def on_fault_trigger(self):
    method on_diagnostics_failed (line 98) | def on_diagnostics_failed(self):
    method on_diagnostics_passed (line 101) | def on_diagnostics_passed(self):
    method on_operator_inservice (line 104) | def on_operator_inservice(self):
  class Inservice (line 108) | class Inservice(Unit):
    method __init__ (line 109) | def __init__(self, HierachicalStateMachine):
    method on_fault_trigger (line 112) | def on_fault_trigger(self):
    method on_switchover (line 117) | def on_switchover(self):
  class Active (line 123) | class Active(Inservice):
    method __init__ (line 124) | def __init__(self, HierachicalStateMachine):
    method on_fault_trigger (line 127) | def on_fault_trigger(self):
    method on_switchover (line 131) | def on_switchover(self):
  class Standby (line 136) | class Standby(Inservice):
    method __init__ (line 137) | def __init__(self, HierachicalStateMachine):
    method on_switchover (line 140) | def on_switchover(self):
  class OutOfService (line 145) | class OutOfService(Unit):
    method __init__ (line 146) | def __init__(self, HierachicalStateMachine):
    method on_operator_inservice (line 149) | def on_operator_inservice(self):
  class Suspect (line 155) | class Suspect(OutOfService):
    method __init__ (line 156) | def __init__(self, HierachicalStateMachine):
    method on_diagnostics_failed (line 159) | def on_diagnostics_failed(self):
    method on_diagnostics_passed (line 163) | def on_diagnostics_passed(self):
    method on_operator_inservice (line 168) | def on_operator_inservice(self):
  class Failed (line 173) | class Failed(OutOfService):
    method __init__ (line 176) | def __init__(self, HierachicalStateMachine):

FILE: patterns/structural/3-tier.py
  class Data (line 9) | class Data:
    method __get__ (line 18) | def __get__(self, obj, klas):
  class BusinessLogic (line 23) | class BusinessLogic:
    method product_list (line 28) | def product_list(self) -> KeysView[str]:
    method product_information (line 31) | def product_information(
  class Ui (line 37) | class Ui:
    method __init__ (line 40) | def __init__(self) -> None:
    method get_product_list (line 43) | def get_product_list(self) -> None:
    method get_product_information (line 49) | def get_product_information(self, product: str) -> None:
  function main (line 62) | def main():

FILE: patterns/structural/adapter.py
  class Dog (line 36) | class Dog:
    method __init__ (line 37) | def __init__(self) -> None:
    method bark (line 40) | def bark(self) -> str:
  class Cat (line 44) | class Cat:
    method __init__ (line 45) | def __init__(self) -> None:
    method meow (line 48) | def meow(self) -> str:
  class Human (line 52) | class Human:
    method __init__ (line 53) | def __init__(self) -> None:
    method speak (line 56) | def speak(self) -> str:
  class Car (line 60) | class Car:
    method __init__ (line 61) | def __init__(self) -> None:
    method make_noise (line 64) | def make_noise(self, octane_level: int) -> str:
  class Adapter (line 68) | class Adapter:
    method __init__ (line 77) | def __init__(self, obj: T, **adapted_methods: Callable[..., Any]) -> N...
    method __getattr__ (line 82) | def __getattr__(self, attr: str) -> Any:
    method original_dict (line 86) | def original_dict(self) -> Dict[str, Any]:
  function main (line 91) | def main():

FILE: patterns/structural/bridge.py
  class DrawingAPI1 (line 12) | class DrawingAPI1:
    method draw_circle (line 13) | def draw_circle(self, x: int, y: int, radius: float) -> None:
  class DrawingAPI2 (line 18) | class DrawingAPI2:
    method draw_circle (line 19) | def draw_circle(self, x: int, y: int, radius: float) -> None:
  class CircleShape (line 24) | class CircleShape:
    method __init__ (line 25) | def __init__(
    method draw (line 34) | def draw(self) -> None:
    method scale (line 38) | def scale(self, pct: float) -> None:
  function main (line 42) | def main():

FILE: patterns/structural/composite.py
  class Graphic (line 33) | class Graphic(ABC):
    method render (line 35) | def render(self) -> None:
  class CompositeGraphic (line 39) | class CompositeGraphic(Graphic):
    method __init__ (line 40) | def __init__(self) -> None:
    method render (line 43) | def render(self) -> None:
    method add (line 47) | def add(self, graphic: Graphic) -> None:
    method remove (line 50) | def remove(self, graphic: Graphic) -> None:
  class Ellipse (line 54) | class Ellipse(Graphic):
    method __init__ (line 55) | def __init__(self, name: str) -> None:
    method render (line 58) | def render(self) -> None:
  function main (line 62) | def main():

FILE: patterns/structural/decorator.py
  class TextTag (line 28) | class TextTag:
    method __init__ (line 31) | def __init__(self, text: str) -> None:
    method render (line 34) | def render(self) -> str:
  class BoldWrapper (line 38) | class BoldWrapper(TextTag):
    method __init__ (line 41) | def __init__(self, wrapped: TextTag) -> None:
    method render (line 44) | def render(self) -> str:
  class ItalicWrapper (line 48) | class ItalicWrapper(TextTag):
    method __init__ (line 51) | def __init__(self, wrapped: TextTag) -> None:
    method render (line 54) | def render(self) -> str:
  function main (line 58) | def main():

FILE: patterns/structural/facade.py
  class CPU (line 33) | class CPU:
    method freeze (line 38) | def freeze(self) -> None:
    method jump (line 41) | def jump(self, position: str) -> None:
    method execute (line 44) | def execute(self) -> None:
  class Memory (line 48) | class Memory:
    method load (line 53) | def load(self, position: str, data: str) -> None:
  class SolidStateDrive (line 57) | class SolidStateDrive:
    method read (line 62) | def read(self, lba: str, size: str) -> str:
  class ComputerFacade (line 66) | class ComputerFacade:
    method __init__ (line 71) | def __init__(self):
    method start (line 76) | def start(self):
  function main (line 83) | def main():

FILE: patterns/structural/flyweight.py
  class Card (line 31) | class Card:
    method __new__ (line 39) | def __new__(cls, value: str, suit: str):
    method __repr__ (line 55) | def __repr__(self) -> str:
  function main (line 59) | def main():

FILE: patterns/structural/flyweight_with_metaclass.py
  class FlyweightMeta (line 4) | class FlyweightMeta(type):
    method __new__ (line 5) | def __new__(mcs, name, parents, dct):
    method _serialize_params (line 19) | def _serialize_params(cls, *args, **kwargs):
    method __call__ (line 29) | def __call__(cls, *args, **kwargs):
  class Card2 (line 40) | class Card2(metaclass=FlyweightMeta):
    method __init__ (line 41) | def __init__(self, *args, **kwargs):

FILE: patterns/structural/front_controller.py
  class MobileView (line 13) | class MobileView:
    method show_index_page (line 14) | def show_index_page(self) -> None:
  class TabletView (line 18) | class TabletView:
    method show_index_page (line 19) | def show_index_page(self) -> None:
  class Dispatcher (line 23) | class Dispatcher:
    method __init__ (line 24) | def __init__(self) -> None:
    method dispatch (line 28) | def dispatch(self, request: Request) -> None:
  class RequestController (line 43) | class RequestController:
    method __init__ (line 46) | def __init__(self) -> None:
    method dispatch_request (line 49) | def dispatch_request(self, request: Any) -> None:
  class Request (line 59) | class Request:
    method __init__ (line 65) | def __init__(self, request):
  function main (line 74) | def main():

FILE: patterns/structural/mvc.py
  class Model (line 12) | class Model(ABC):
    method __iter__ (line 16) | def __iter__(self) -> Any:
    method get (line 20) | def get(self, item: str) -> dict:
    method item_type (line 27) | def item_type(self) -> str:
  class ProductModel (line 31) | class ProductModel(Model):
    class Price (line 34) | class Price(float):
      method __str__ (line 38) | def __str__(self) -> str:
    method __iter__ (line 49) | def __iter__(self) -> Any:
    method get (line 52) | def get(self, product: str) -> dict:
  class View (line 59) | class View(ABC):
    method show_item_list (line 63) | def show_item_list(self, item_type: str, item_list: list) -> None:
    method show_item_information (line 67) | def show_item_information(
    method item_not_found (line 75) | def item_not_found(self, item_type: str, item_name: str) -> None:
  class ConsoleView (line 79) | class ConsoleView(View):
    method show_item_list (line 82) | def show_item_list(self, item_type: str, item_list: list) -> None:
    method capitalizer (line 89) | def capitalizer(string: str) -> str:
    method show_item_information (line 93) | def show_item_information(
    method item_not_found (line 104) | def item_not_found(self, item_type: str, item_name: str) -> None:
  class Controller (line 108) | class Controller:
    method __init__ (line 111) | def __init__(self, model_class: Model, view_class: View) -> None:
    method show_items (line 115) | def show_items(self) -> None:
    method show_item_information (line 120) | def show_item_information(self, item_name: str) -> None:
  class Router (line 134) | class Router:
    method __init__ (line 137) | def __init__(self):
    method register (line 140) | def register(
    method resolve (line 151) | def resolve(self, path: str) -> Controller:
  function main (line 159) | def main():

FILE: patterns/structural/proxy.py
  class Subject (line 21) | class Subject:
    method do_the_job (line 31) | def do_the_job(self, user: str) -> None:
  class RealSubject (line 35) | class RealSubject(Subject):
    method do_the_job (line 41) | def do_the_job(self, user: str) -> None:
  class Proxy (line 45) | class Proxy(Subject):
    method __init__ (line 46) | def __init__(self) -> None:
    method do_the_job (line 49) | def do_the_job(self, user: str) -> None:
  function client (line 62) | def client(job_doer: Union[RealSubject, Proxy], user: str) -> None:
  function main (line 66) | def main():

FILE: tests/behavioral/test_catalog.py
  function test_catalog_multiple_methods (line 5) | def test_catalog_multiple_methods():
  function test_catalog_multiple_instance_methods (line 10) | def test_catalog_multiple_instance_methods():
  function test_catalog_multiple_class_methods (line 15) | def test_catalog_multiple_class_methods():
  function test_catalog_multiple_static_methods (line 20) | def test_catalog_multiple_static_methods():

FILE: tests/behavioral/test_mediator.py
  function test_mediated_comments (line 5) | def test_mediated_comments():

FILE: tests/behavioral/test_memento.py
  function test_object_creation (line 5) | def test_object_creation():
  function test_rollback_on_transaction (line 9) | def test_rollback_on_transaction():
  function test_rollback_with_transactional_annotation (line 25) | def test_rollback_with_transactional_annotation():

FILE: tests/behavioral/test_observer.py
  function observable (line 9) | def observable():
  function test_attach_detach (line 13) | def test_attach_detach(observable):
  function test_one_data_change_notifies_each_observer_once (line 24) | def test_one_data_change_notifies_each_observer_once(observable):

FILE: tests/behavioral/test_publish_subscribe.py
  class TestProvider (line 7) | class TestProvider(unittest.TestCase):
    method test_subscriber_shall_be_attachable_to_subscriptions (line 12) | def test_subscriber_shall_be_attachable_to_subscriptions(cls):
    method test_subscriber_shall_be_detachable_from_subscriptions (line 20) | def test_subscriber_shall_be_detachable_from_subscriptions(cls):
    method test_publisher_shall_append_subscription_message_to_queue (line 29) | def test_publisher_shall_append_subscription_message_to_queue(cls):
    method test_provider_shall_update_affected_subscribers_with_published_subscription (line 40) | def test_provider_shall_update_affected_subscribers_with_published_sub...

FILE: tests/behavioral/test_servant.py
  function circle (line 7) | def circle():
  function rectangle (line 12) | def rectangle():
  function test_calculate_area (line 16) | def test_calculate_area(circle, rectangle):
  function test_calculate_perimeter (line 24) | def test_calculate_perimeter(circle, rectangle):
  function test_move_to (line 32) | def test_move_to(circle, rectangle):

FILE: tests/behavioral/test_state.py
  function radio (line 7) | def radio():
  function test_initial_state (line 11) | def test_initial_state(radio):
  function test_initial_am_station (line 15) | def test_initial_am_station(radio):
  function test_toggle_amfm (line 20) | def test_toggle_amfm(radio):

FILE: tests/behavioral/test_strategy.py
  function order (line 7) | def order():
  function test_discount_function_return (line 14) | def test_discount_function_return(func, order, discount):
  function test_order_discount_strategy_validate_success (line 21) | def test_order_discount_strategy_validate_success(func, price):
  function test_order_discount_strategy_validate_error (line 28) | def test_order_discount_strategy_validate_error():
  function test_discount_apply_success (line 38) | def test_discount_apply_success(func, price, discount):

FILE: tests/behavioral/test_visitor.py
  function visitor (line 6) | def visitor():
  function test_visiting_generic_node (line 9) | def test_visiting_generic_node(visitor):
  function test_visiting_specific_nodes (line 14) | def test_visiting_specific_nodes(visitor):
  function test_visiting_inherited_nodes (line 19) | def test_visiting_inherited_nodes(visitor):

FILE: tests/creational/test_abstract_factory.py
  class TestPetShop (line 7) | class TestPetShop(unittest.TestCase):
    method test_dog_pet_shop_shall_show_dog_instance (line 8) | def test_dog_pet_shop_shall_show_dog_instance(self):

FILE: tests/creational/test_borg.py
  class BorgTest (line 6) | class BorgTest(unittest.TestCase):
    method setUp (line 7) | def setUp(self):
    method tearDown (line 14) | def tearDown(self):
    method test_initial_borg_state_shall_be_init (line 17) | def test_initial_borg_state_shall_be_init(self):
    method test_changing_instance_attribute_shall_change_borg_state (line 21) | def test_changing_instance_attribute_shall_change_borg_state(self):
    method test_instances_shall_have_own_ids (line 27) | def test_instances_shall_have_own_ids(self):

FILE: tests/creational/test_builder.py
  class TestSimple (line 6) | class TestSimple(unittest.TestCase):
    method test_house (line 7) | def test_house(self):
    method test_flat (line 12) | def test_flat(self):
  class TestComplex (line 18) | class TestComplex(unittest.TestCase):
    method test_house (line 19) | def test_house(self):

FILE: tests/creational/test_factory.py
  class TestFactory (line 4) | class TestFactory(unittest.TestCase):
    method test_get_localizer_greek (line 5) | def test_get_localizer_greek(self):
    method test_get_localizer_english (line 13) | def test_get_localizer_english(self):
    method test_get_localizer_default (line 19) | def test_get_localizer_default(self):
    method test_get_localizer_unknown_language (line 24) | def test_get_localizer_unknown_language(self):

FILE: tests/creational/test_lazy.py
  class TestDynamicExpanding (line 6) | class TestDynamicExpanding(unittest.TestCase):
    method setUp (line 7) | def setUp(self):
    method test_innate_properties (line 10) | def test_innate_properties(self):
    method test_relatives_not_in_properties (line 16) | def test_relatives_not_in_properties(self):
    method test_extended_properties (line 19) | def test_extended_properties(self):
    method test_relatives_after_access (line 31) | def test_relatives_after_access(self):
    method test_parents (line 35) | def test_parents(self):

FILE: tests/creational/test_pool.py
  class TestPool (line 7) | class TestPool(unittest.TestCase):
    method setUp (line 8) | def setUp(self):
    method test_items_recoil (line 13) | def test_items_recoil(self):
    method test_frozen_pool (line 21) | def test_frozen_pool(self):
  class TestNaitivePool (line 31) | class TestNaitivePool(unittest.TestCase):
    method test_pool_behavior_with_single_object_inside (line 36) | def test_pool_behavior_with_single_object_inside(self):

FILE: tests/creational/test_prototype.py
  class TestPrototypeFeatures (line 6) | class TestPrototypeFeatures(unittest.TestCase):
    method setUp (line 7) | def setUp(self):
    method test_cloning_propperty_innate_values (line 10) | def test_cloning_propperty_innate_values(self):
    method test_extended_property_values_cloning (line 15) | def test_extended_property_values_cloning(self):
    method test_cloning_propperty_assigned_values (line 21) | def test_cloning_propperty_assigned_values(self):
  class TestDispatcherFeatures (line 27) | class TestDispatcherFeatures(unittest.TestCase):
    method setUp (line 28) | def setUp(self):
    method test_batch_retrieving (line 38) | def test_batch_retrieving(self):
    method test_particular_properties_retrieving (line 41) | def test_particular_properties_retrieving(self):
    method test_extended_properties_retrieving (line 46) | def test_extended_properties_retrieving(self):

FILE: tests/fundamental/test_delegation.py
  function test_delegator_delegates_attribute_and_call (line 6) | def test_delegator_delegates_attribute_and_call():
  function test_delegator_missing_attribute_raises (line 13) | def test_delegator_missing_attribute_raises():

FILE: tests/structural/test_adapter.py
  class ClassTest (line 6) | class ClassTest(unittest.TestCase):
    method setUp (line 7) | def setUp(self):
    method test_dog_shall_bark (line 13) | def test_dog_shall_bark(self):
    method test_cat_shall_meow (line 18) | def test_cat_shall_meow(self):
    method test_human_shall_speak (line 23) | def test_human_shall_speak(self):
    method test_car_shall_make_loud_noise (line 28) | def test_car_shall_make_loud_noise(self):
    method test_car_shall_make_very_loud_noise (line 33) | def test_car_shall_make_very_loud_noise(self):
  class AdapterTest (line 39) | class AdapterTest(unittest.TestCase):
    method test_dog_adapter_shall_make_noise (line 40) | def test_dog_adapter_shall_make_noise(self):
    method test_cat_adapter_shall_make_noise (line 47) | def test_cat_adapter_shall_make_noise(self):
    method test_human_adapter_shall_make_noise (line 54) | def test_human_adapter_shall_make_noise(self):
    method test_car_adapter_shall_make_loud_noise (line 61) | def test_car_adapter_shall_make_loud_noise(self):
    method test_car_adapter_shall_make_very_loud_noise (line 68) | def test_car_adapter_shall_make_very_loud_noise(self):

FILE: tests/structural/test_bridge.py
  class BridgeTest (line 7) | class BridgeTest(unittest.TestCase):
    method test_bridge_shall_draw_with_concrete_api_implementation (line 8) | def test_bridge_shall_draw_with_concrete_api_implementation(cls):
    method test_bridge_shall_scale_both_api_circles_with_own_implementation (line 22) | def test_bridge_shall_scale_both_api_circles_with_own_implementation(c...

FILE: tests/structural/test_decorator.py
  class TestTextWrapping (line 6) | class TestTextWrapping(unittest.TestCase):
    method setUp (line 7) | def setUp(self):
    method test_italic (line 10) | def test_italic(self):
    method test_bold (line 15) | def test_bold(self):
    method test_mixed_bold_and_italic (line 20) | def test_mixed_bold_and_italic(self):

FILE: tests/structural/test_facade.py
  function test_computer_facade_start (line 4) | def test_computer_facade_start(capsys):

FILE: tests/structural/test_flyweight.py
  function test_card_flyweight_identity_and_repr (line 4) | def test_card_flyweight_identity_and_repr():
  function test_card_attribute_persistence_and_pool_clear (line 11) | def test_card_attribute_persistence_and_pool_clear():

FILE: tests/structural/test_mvc.py
  function test_productmodel_iteration_and_price_str (line 11) | def test_productmodel_iteration_and_price_str():
  function test_productmodel_get_raises_keyerror (line 21) | def test_productmodel_get_raises_keyerror():
  function test_consoleview_capitalizer_and_list_and_info (line 28) | def test_consoleview_capitalizer_and_list_and_info(capsys):
  function test_show_item_information_missing_calls_item_not_found (line 50) | def test_show_item_information_missing_calls_item_not_found(capsys):
  function test_router_register_resolve_and_unknown (line 60) | def test_router_register_resolve_and_unknown():

FILE: tests/structural/test_proxy.py
  class ProxyTest (line 8) | class ProxyTest(unittest.TestCase):
    method setUpClass (line 10) | def setUpClass(cls):
    method setUp (line 14) | def setUp(cls):
    method tearDown (line 20) | def tearDown(cls):
    method test_do_the_job_for_admin_shall_pass (line 25) | def test_do_the_job_for_admin_shall_pass(self):
    method test_do_the_job_for_anonymous_shall_reject (line 32) | def test_do_the_job_for_anonymous_shall_reject(self):

FILE: tests/test_hsm.py
  class HsmMethodTest (line 15) | class HsmMethodTest(unittest.TestCase):
    method setUpClass (line 17) | def setUpClass(cls):
    method test_initial_state_shall_be_standby (line 20) | def test_initial_state_shall_be_standby(cls):
    method test_unsupported_state_shall_raise_exception (line 23) | def test_unsupported_state_shall_raise_exception(cls):
    method test_unsupported_message_type_shall_raise_exception (line 27) | def test_unsupported_message_type_shall_raise_exception(cls):
    method test_calling_next_state_shall_change_current_state (line 31) | def test_calling_next_state_shall_change_current_state(cls):
    method test_method_perform_switchover_shall_return_specifically (line 37) | def test_method_perform_switchover_shall_return_specifically(cls):
  class StandbyStateTest (line 45) | class StandbyStateTest(unittest.TestCase):
    method setUpClass (line 50) | def setUpClass(cls):
    method setUp (line 53) | def setUp(cls):
    method test_given_standby_on_message_switchover_shall_set_active (line 56) | def test_given_standby_on_message_switchover_shall_set_active(cls):
    method test_given_standby_on_message_switchover_shall_call_hsm_methods (line 60) | def test_given_standby_on_message_switchover_shall_call_hsm_methods(cls):
    method test_given_standby_on_message_fault_trigger_shall_set_suspect (line 75) | def test_given_standby_on_message_fault_trigger_shall_set_suspect(cls):
    method test_given_standby_on_message_diagnostics_failed_shall_raise_exception_and_keep_in_state (line 79) | def test_given_standby_on_message_diagnostics_failed_shall_raise_excep...
    method test_given_standby_on_message_diagnostics_passed_shall_raise_exception_and_keep_in_state (line 86) | def test_given_standby_on_message_diagnostics_passed_shall_raise_excep...
    method test_given_standby_on_message_operator_inservice_shall_raise_exception_and_keep_in_state (line 93) | def test_given_standby_on_message_operator_inservice_shall_raise_excep...
Condensed preview — 87 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (182K chars).
[
  {
    "path": ".codespellignore",
    "chars": 111,
    "preview": "__pycache__\n*.pyc\n.idea\n*.egg-info/\n.tox/\nenv/\nvenv/\n.env\n.venv\n.vscode/\n.python-version\n.coverage\nbuild/\ndist/"
  },
  {
    "path": ".github/workflows/lint_pr.yml",
    "chars": 12034,
    "preview": "name: lint_pull_request\non: [pull_request, push]\njobs:\n  check_changes:\n    runs-on: ubuntu-24.04\n    outputs:\n      has"
  },
  {
    "path": ".github/workflows/lint_python.yml",
    "chars": 1175,
    "preview": "name: lint_python\non: [pull_request, push]\njobs:\n  lint_python:\n    runs-on: ubuntu-24.04\n    steps:\n      - uses: actio"
  },
  {
    "path": ".gitignore",
    "chars": 151,
    "preview": "__pycache__\n*.pyc\n.idea\n*.egg-info/\n.tox/\nenv/\nvenv/\n.env\n.venv\n.vscode/\n.python-version\n.coverage\n.project\n.pydevprojec"
  },
  {
    "path": ".travis.yml",
    "chars": 201,
    "preview": "os: linux\ndist: noble\nlanguage: python\n\njobs:\n  include:\n    - python: \"3.12\"\n      env: TOXENV=py312\n\ncache:\n  - pip\n\ni"
  },
  {
    "path": "Makefile",
    "chars": 1865,
    "preview": "# REDNAFI\n# This only works with embedded venv not virtualenv\n# Install venv: python3.8 -m venv venv\n# Activate venv: so"
  },
  {
    "path": "README.md",
    "chars": 9056,
    "preview": "# python-patterns\n\nA collection of design patterns and idioms in Python.\n\nRemember that each pattern has its own trade-o"
  },
  {
    "path": "config_backup/.coveragerc",
    "chars": 553,
    "preview": "[run]\nbranch = True\n\n[report]\n; Regexes for lines to exclude from consideration\nexclude_also =\n    ; Don't complain abou"
  },
  {
    "path": "config_backup/setup.cfg",
    "chars": 260,
    "preview": "[flake8]\nmax-line-length = 120\nignore = E266 E731 W503\nexclude = venv*\n\n[tool:pytest]\nfilterwarnings =\n    ; ignore Test"
  },
  {
    "path": "config_backup/tox.ini",
    "chars": 661,
    "preview": "[tox]\nenvlist = py312,cov-report\nskip_missing_interpreters = true\nusedevelop = true\n\n[testenv]\nsetenv =\n    COVERAGE_FIL"
  },
  {
    "path": "lint.sh",
    "chars": 514,
    "preview": "#! /bin/bash\n\npip install --upgrade pip\npip install black codespell flake8 isort mypy pytest pyupgrade tox\npip install -"
  },
  {
    "path": "patterns/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "patterns/behavioral/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "patterns/behavioral/catalog.py",
    "chars": 4901,
    "preview": "\"\"\"\nA class that uses different static functions depending on a parameter passed\nduring initialization. Uses a single di"
  },
  {
    "path": "patterns/behavioral/chain_of_responsibility.py",
    "chars": 3537,
    "preview": "\"\"\"\n*What is this pattern about?\n\nThe Chain of responsibility is an object oriented version of the\n`if ... elif ... elif"
  },
  {
    "path": "patterns/behavioral/chaining_method.py",
    "chars": 712,
    "preview": "from __future__ import annotations\n\n\nclass Person:\n    def __init__(self, name: str) -> None:\n        self.name = name\n\n"
  },
  {
    "path": "patterns/behavioral/command.py",
    "chars": 2898,
    "preview": "\"\"\"\nCommand pattern decouples the object invoking a job from the one who knows\nhow to do it. As mentioned in the GoF boo"
  },
  {
    "path": "patterns/behavioral/iterator.py",
    "chars": 845,
    "preview": "\"\"\"\nhttp://ginstrom.com/scribbles/2007/10/08/design-patterns-python-style/\nImplementation of the iterator pattern with a"
  },
  {
    "path": "patterns/behavioral/iterator_alt.py",
    "chars": 1237,
    "preview": "\"\"\"\nImplementation of the iterator pattern using the iterator protocol from Python\n\n*TL;DR\nTraverses a container and acc"
  },
  {
    "path": "patterns/behavioral/mediator.py",
    "chars": 1247,
    "preview": "\"\"\"\nhttps://www.djangospin.com/design-patterns-python/mediator/\n\nObjects in a system communicate through a Mediator inst"
  },
  {
    "path": "patterns/behavioral/memento.py",
    "chars": 3410,
    "preview": "\"\"\"\nhttp://code.activestate.com/recipes/413838-memento-closure/\n\n*TL;DR\nProvides the ability to restore an object to its"
  },
  {
    "path": "patterns/behavioral/observer.py",
    "chars": 3250,
    "preview": "\"\"\"\nhttp://code.activestate.com/recipes/131499-observer-pattern/\n\n*TL;DR\nMaintains a list of dependents and notifies the"
  },
  {
    "path": "patterns/behavioral/publish_subscribe.py",
    "chars": 2382,
    "preview": "\"\"\"\nReference:\nhttp://www.slideshare.net/ishraqabd/publish-subscribe-model-overview-13368808\nAuthor: https://github.com/"
  },
  {
    "path": "patterns/behavioral/registry.py",
    "chars": 1202,
    "preview": "from typing import Dict\n\n\nclass RegistryHolder(type):\n    REGISTRY: Dict[str, \"RegistryHolder\"] = {}\n\n    def __new__(cl"
  },
  {
    "path": "patterns/behavioral/servant.py",
    "chars": 3741,
    "preview": "\"\"\"\nImplementation of the Servant design pattern.\n\nThe Servant design pattern is a behavioral pattern used to offer func"
  },
  {
    "path": "patterns/behavioral/specification.py",
    "chars": 3136,
    "preview": "\"\"\"\n@author: Gordeev Andrey <gordeev.and.and@gmail.com>\n\n*TL;DR\nProvides recombination business logic by chaining togeth"
  },
  {
    "path": "patterns/behavioral/state.py",
    "chars": 2235,
    "preview": "\"\"\"\nImplementation of the state pattern\n\nhttp://ginstrom.com/scribbles/2007/10/08/design-patterns-python-style/\n\n*TL;DR\n"
  },
  {
    "path": "patterns/behavioral/strategy.py",
    "chars": 2716,
    "preview": "\"\"\"\n*What is this pattern about?\nDefine a family of algorithms, encapsulate each one, and make them interchangeable.\nStr"
  },
  {
    "path": "patterns/behavioral/template.py",
    "chars": 1322,
    "preview": "\"\"\"\nAn example of the Template pattern in Python\n\n*TL;DR\nDefines the skeleton of a base algorithm, deferring definition "
  },
  {
    "path": "patterns/behavioral/visitor.py",
    "chars": 1698,
    "preview": "\"\"\"\nhttp://peter-hoffmann.com/2010/extrinsic-visitor-pattern-python-inheritance.html\n\n*TL;DR\nSeparates an algorithm from"
  },
  {
    "path": "patterns/creational/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "patterns/creational/abstract_factory.py",
    "chars": 2551,
    "preview": "\"\"\"\n*What is this pattern about?\n\nIn Java and other languages, the Abstract Factory Pattern serves to provide an interfa"
  },
  {
    "path": "patterns/creational/borg.py",
    "chars": 3285,
    "preview": "\"\"\"\n*What is this pattern about?\nThe Borg pattern (also known as the Monostate pattern) is a way to\nimplement singleton "
  },
  {
    "path": "patterns/creational/builder.py",
    "chars": 3054,
    "preview": "\"\"\"\nWhat is this pattern about?\nIt decouples the creation of a complex object and its representation,\nso that the same p"
  },
  {
    "path": "patterns/creational/factory.py",
    "chars": 2176,
    "preview": "\"\"\"*What is this pattern about?\nA Factory is an object for creating other objects.\n\n*What does this example do?\nThe code"
  },
  {
    "path": "patterns/creational/lazy_evaluation.py",
    "chars": 2949,
    "preview": "\"\"\"\nLazily-evaluated property pattern in Python.\n\nhttps://en.wikipedia.org/wiki/Lazy_evaluation\n\n*References:\nbottle\nhtt"
  },
  {
    "path": "patterns/creational/pool.py",
    "chars": 2829,
    "preview": "\"\"\"\n*What is this pattern about?\nThis pattern is used when creating an object is costly (and they are\ncreated frequently"
  },
  {
    "path": "patterns/creational/prototype.py",
    "chars": 2487,
    "preview": "\"\"\"\n*What is this pattern about?\nThis patterns aims to reduce the number of classes required by an\napplication. Instead "
  },
  {
    "path": "patterns/dependency_injection.py",
    "chars": 3708,
    "preview": "\"\"\"\nDependency Injection (DI) is a technique whereby one object supplies the dependencies (services)\nto another object ("
  },
  {
    "path": "patterns/fundamental/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "patterns/fundamental/delegation_pattern.py",
    "chars": 1436,
    "preview": "\"\"\"\nReference: https://en.wikipedia.org/wiki/Delegation_pattern\nAuthor: https://github.com/IuryAlves\n\n*TL;DR\nAllows obje"
  },
  {
    "path": "patterns/other/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "patterns/other/blackboard.py",
    "chars": 4421,
    "preview": "\"\"\"\n@author: Eugene Duboviy <eugene.dubovoy@gmail.com> | github.com/duboviy\n\nIn Blackboard pattern several specialised s"
  },
  {
    "path": "patterns/other/graph_search.py",
    "chars": 4905,
    "preview": "from typing import Any, Dict, List, Optional, Union\n\n\nclass GraphSearch:\n    \"\"\"Graph search emulation in python, from s"
  },
  {
    "path": "patterns/other/hsm/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "patterns/other/hsm/hsm.py",
    "chars": 5190,
    "preview": "\"\"\"\nImplementation of the HSM (hierarchical state machine) or\nNFSM (nested finite state machine) C++ example from\nhttp:/"
  },
  {
    "path": "patterns/structural/3-tier.py",
    "chars": 2437,
    "preview": "\"\"\"\n*TL;DR\nSeparates presentation, application processing, and data management functions.\n\"\"\"\n\nfrom typing import Dict, "
  },
  {
    "path": "patterns/structural/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "patterns/structural/adapter.py",
    "chars": 3407,
    "preview": "\"\"\"\n*What is this pattern about?\nThe Adapter pattern provides a different interface for a class. We can\nthink about it a"
  },
  {
    "path": "patterns/structural/bridge.py",
    "chars": 1404,
    "preview": "\"\"\"\n*References:\nhttp://en.wikibooks.org/wiki/Computer_Science_Design_Patterns/Bridge_Pattern#Python\n\n*TL;DR\nDecouples a"
  },
  {
    "path": "patterns/structural/composite.py",
    "chars": 2532,
    "preview": "\"\"\"\n*What is this pattern about?\nThe composite pattern describes a group of objects that is treated the\nsame way as a si"
  },
  {
    "path": "patterns/structural/decorator.py",
    "chars": 1951,
    "preview": "\"\"\"\n*What is this pattern about?\nThe Decorator pattern is used to dynamically add a new feature to an\nobject without cha"
  },
  {
    "path": "patterns/structural/facade.py",
    "chars": 2649,
    "preview": "\"\"\"\nExample from https://en.wikipedia.org/wiki/Facade_pattern#Python\n\n\n*What is this pattern about?\nThe Facade pattern i"
  },
  {
    "path": "patterns/structural/flyweight.py",
    "chars": 2444,
    "preview": "\"\"\"\n*What is this pattern about?\nThis pattern aims to minimise the number of objects that are needed by\na program at run"
  },
  {
    "path": "patterns/structural/flyweight_with_metaclass.py",
    "chars": 1718,
    "preview": "import weakref\n\n\nclass FlyweightMeta(type):\n    def __new__(mcs, name, parents, dct):\n        \"\"\"\n        Set up object "
  },
  {
    "path": "patterns/structural/front_controller.py",
    "chars": 2485,
    "preview": "\"\"\"\n@author: Gordeev Andrey <gordeev.and.and@gmail.com>\n\n*TL;DR\nProvides a centralized entry point that controls and man"
  },
  {
    "path": "patterns/structural/mvc.py",
    "chars": 6140,
    "preview": "\"\"\"\n*TL;DR\nSeparates data in GUIs from the ways it is presented, and accepted.\n\"\"\"\n\nfrom abc import ABC, abstractmethod\n"
  },
  {
    "path": "patterns/structural/proxy.py",
    "chars": 2503,
    "preview": "\"\"\"\n*What is this pattern about?\nProxy is used in places where you want to add functionality to a class without\nchanging"
  },
  {
    "path": "pyproject.toml",
    "chars": 2849,
    "preview": "[build-system]\nrequires = [\"setuptools >= 77.0.3\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[project]\nname = \"python-pat"
  },
  {
    "path": "pytest_local.ini",
    "chars": 40,
    "preview": "[pytest]\naddopts = -q\ntestpaths = tests\n"
  },
  {
    "path": "requirements-dev.txt",
    "chars": 61,
    "preview": "flake8\nblack\nisort\npytest\npytest-randomly\nmypy\npyupgrade\ntox\n"
  },
  {
    "path": "tests/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/behavioral/test_catalog.py",
    "chars": 718,
    "preview": "import pytest\n\nfrom patterns.behavioral.catalog import Catalog, CatalogClass, CatalogInstance, CatalogStatic\n\ndef test_c"
  },
  {
    "path": "tests/behavioral/test_mediator.py",
    "chars": 523,
    "preview": "import pytest\n\nfrom patterns.behavioral.mediator import User\n\ndef test_mediated_comments():\n    molly = User('Molly')\n  "
  },
  {
    "path": "tests/behavioral/test_memento.py",
    "chars": 832,
    "preview": "import pytest\n\nfrom patterns.behavioral.memento import NumObj, Transaction\n\ndef test_object_creation():\n    num_obj = Nu"
  },
  {
    "path": "tests/behavioral/test_observer.py",
    "chars": 876,
    "preview": "from unittest.mock import Mock, patch\n\nimport pytest\n\nfrom patterns.behavioral.observer import Data, DecimalViewer, HexV"
  },
  {
    "path": "tests/behavioral/test_publish_subscribe.py",
    "chars": 2732,
    "preview": "import unittest\nfrom unittest.mock import call, patch\n\nfrom patterns.behavioral.publish_subscribe import Provider, Publi"
  },
  {
    "path": "tests/behavioral/test_servant.py",
    "chars": 1105,
    "preview": "from patterns.behavioral.servant import GeometryTools, Circle, Rectangle, Position\nimport pytest\nimport math\n\n\n@pytest.f"
  },
  {
    "path": "tests/behavioral/test_state.py",
    "chars": 494,
    "preview": "import pytest\n\nfrom patterns.behavioral.state import Radio\n\n\n@pytest.fixture\ndef radio():\n    return Radio()\n\n\ndef test_"
  },
  {
    "path": "tests/behavioral/test_strategy.py",
    "chars": 1052,
    "preview": "import pytest\n\nfrom patterns.behavioral.strategy import Order, on_sale_discount, ten_percent_discount\n\n\n@pytest.fixture\n"
  },
  {
    "path": "tests/behavioral/test_visitor.py",
    "chars": 622,
    "preview": "import pytest\n\nfrom patterns.behavioral.visitor import A, B, C, Visitor\n\n@pytest.fixture\ndef visitor():\n    return Visit"
  },
  {
    "path": "tests/creational/test_abstract_factory.py",
    "chars": 429,
    "preview": "import unittest\nfrom unittest.mock import patch\n\nfrom patterns.creational.abstract_factory import Dog, PetShop\n\n\nclass T"
  },
  {
    "path": "tests/creational/test_borg.py",
    "chars": 865,
    "preview": "import unittest\n\nfrom patterns.creational.borg import Borg, YourBorg\n\n\nclass BorgTest(unittest.TestCase):\n    def setUp("
  },
  {
    "path": "tests/creational/test_builder.py",
    "chars": 642,
    "preview": "import unittest\n\nfrom patterns.creational.builder import ComplexHouse, Flat, House, construct_building\n\n\nclass TestSimpl"
  },
  {
    "path": "tests/creational/test_factory.py",
    "chars": 1354,
    "preview": "import unittest\nfrom patterns.creational.factory import get_localizer, GreekLocalizer, EnglishLocalizer\n\nclass TestFacto"
  },
  {
    "path": "tests/creational/test_lazy.py",
    "chars": 1161,
    "preview": "import unittest\n\nfrom patterns.creational.lazy_evaluation import Person\n\n\nclass TestDynamicExpanding(unittest.TestCase):"
  },
  {
    "path": "tests/creational/test_pool.py",
    "chars": 1765,
    "preview": "import queue\nimport unittest\n\nfrom patterns.creational.pool import ObjectPool\n\n\nclass TestPool(unittest.TestCase):\n    d"
  },
  {
    "path": "tests/creational/test_prototype.py",
    "chars": 1992,
    "preview": "import unittest\n\nfrom patterns.creational.prototype import Prototype, PrototypeDispatcher\n\n\nclass TestPrototypeFeatures("
  },
  {
    "path": "tests/fundamental/test_delegation.py",
    "chars": 464,
    "preview": "import pytest\n\nfrom patterns.fundamental.delegation_pattern import Delegator, Delegate\n\n\ndef test_delegator_delegates_at"
  },
  {
    "path": "tests/structural/test_adapter.py",
    "chars": 2394,
    "preview": "import unittest\n\nfrom patterns.structural.adapter import Adapter, Car, Cat, Dog, Human\n\n\nclass ClassTest(unittest.TestCa"
  },
  {
    "path": "tests/structural/test_bridge.py",
    "chars": 1695,
    "preview": "import unittest\nfrom unittest.mock import patch\n\nfrom patterns.structural.bridge import CircleShape, DrawingAPI1, Drawin"
  },
  {
    "path": "tests/structural/test_decorator.py",
    "chars": 692,
    "preview": "import unittest\n\nfrom patterns.structural.decorator import BoldWrapper, ItalicWrapper, TextTag\n\n\nclass TestTextWrapping("
  },
  {
    "path": "tests/structural/test_facade.py",
    "chars": 323,
    "preview": "from patterns.structural.facade import ComputerFacade\n\n\ndef test_computer_facade_start(capsys):\n    cf = ComputerFacade("
  },
  {
    "path": "tests/structural/test_flyweight.py",
    "chars": 457,
    "preview": "from patterns.structural.flyweight import Card\n\n\ndef test_card_flyweight_identity_and_repr():\n    c1 = Card(\"9\", \"h\")\n  "
  },
  {
    "path": "tests/structural/test_mvc.py",
    "chars": 1837,
    "preview": "import pytest\n\nfrom patterns.structural.mvc import (\n    ProductModel,\n    ConsoleView,\n    Controller,\n    Router,\n)\n\n\n"
  },
  {
    "path": "tests/structural/test_proxy.py",
    "chars": 1073,
    "preview": "import sys\nimport unittest\nfrom io import StringIO\n\nfrom patterns.structural.proxy import Proxy, client\n\n\nclass ProxyTes"
  },
  {
    "path": "tests/test_hsm.py",
    "chars": 3850,
    "preview": "import unittest\nfrom unittest.mock import patch\n\nfrom patterns.other.hsm.hsm import (\n    Active,\n    HierachicalStateMa"
  }
]

About this extraction

This page contains the full source code of the faif/python-patterns GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 87 files (164.7 KB), approximately 42.8k tokens, and a symbol index with 619 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!