From bc4c583778dfbb5ba8ec1b5bb46357a6b029fbc0 Mon Sep 17 00:00:00 2001 From: BinilRaj KuttikkattuBaburaj <69275128+Binilkks@users.noreply.github.com> Date: Fri, 13 Feb 2026 05:29:09 -0500 Subject: [PATCH 1/3] Test coverage changes added (#464) Co-authored-by: Binil --- patterns/behavioral/memento.py | 2 +- patterns/structural/mvc.py | 1 - pytest_local.ini | 3 ++ tests/fundamental/test_delegation.py | 16 +++++++ tests/structural/test_facade.py | 11 +++++ tests/structural/test_flyweight.py | 20 +++++++++ tests/structural/test_mvc.py | 67 ++++++++++++++++++++++++++++ 7 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 pytest_local.ini create mode 100644 tests/fundamental/test_delegation.py create mode 100644 tests/structural/test_facade.py create mode 100644 tests/structural/test_flyweight.py create mode 100644 tests/structural/test_mvc.py diff --git a/patterns/behavioral/memento.py b/patterns/behavioral/memento.py index 4d072833..c0d63e9e 100644 --- a/patterns/behavioral/memento.py +++ b/patterns/behavioral/memento.py @@ -6,7 +6,7 @@ """ from copy import copy, deepcopy -from typing import Callable, List +from typing import Any, Callable, List, Type def memento(obj: Any, deep: bool = False) -> Callable: diff --git a/patterns/structural/mvc.py b/patterns/structural/mvc.py index 27765fb7..0a7c4034 100644 --- a/patterns/structural/mvc.py +++ b/patterns/structural/mvc.py @@ -4,7 +4,6 @@ """ from abc import ABC, abstractmethod -from ProductModel import Price from typing import Dict, List, Union, Any from inspect import signature from sys import argv diff --git a/pytest_local.ini b/pytest_local.ini new file mode 100644 index 00000000..154db6e6 --- /dev/null +++ b/pytest_local.ini @@ -0,0 +1,3 @@ +[pytest] +addopts = -q +testpaths = tests diff --git a/tests/fundamental/test_delegation.py b/tests/fundamental/test_delegation.py new file mode 100644 index 00000000..3bfd0496 --- /dev/null +++ b/tests/fundamental/test_delegation.py @@ -0,0 +1,16 @@ +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 diff --git a/tests/structural/test_facade.py b/tests/structural/test_facade.py new file mode 100644 index 00000000..2ff24ca3 --- /dev/null +++ b/tests/structural/test_facade.py @@ -0,0 +1,11 @@ +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 diff --git a/tests/structural/test_flyweight.py b/tests/structural/test_flyweight.py new file mode 100644 index 00000000..a200203f --- /dev/null +++ b/tests/structural/test_flyweight.py @@ -0,0 +1,20 @@ +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) == "" + + +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") diff --git a/tests/structural/test_mvc.py b/tests/structural/test_mvc.py new file mode 100644 index 00000000..5991c511 --- /dev/null +++ b/tests/structural/test_mvc.py @@ -0,0 +1,67 @@ +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") From de0e3f8d8b827ae11f132acb344ec3a512d9194c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A1draic=20Slattery?= Date: Fri, 13 Feb 2026 11:29:57 +0100 Subject: [PATCH 2/3] chore: Update outdated GitHub Actions versions (#465) --- .github/workflows/lint_pr.yml | 10 +++++----- .github/workflows/lint_python.yml | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/lint_pr.yml b/.github/workflows/lint_pr.yml index ca1eaddf..3942ffce 100644 --- a/.github/workflows/lint_pr.yml +++ b/.github/workflows/lint_pr.yml @@ -7,7 +7,7 @@ jobs: has_python_changes: ${{ steps.changed-files.outputs.has_python_changes }} files: ${{ steps.changed-files.outputs.files }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 with: fetch-depth: 0 # To get all history for git diff commands @@ -71,15 +71,15 @@ jobs: exit 0 fi - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 with: fetch-depth: 0 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v6 with: python-version: 3.12 - - uses: actions/cache@v3 + - uses: actions/cache@v5 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('requirements-dev.txt') }} @@ -273,7 +273,7 @@ jobs: if: ${{ always() }} runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - name: Summarize results run: | diff --git a/.github/workflows/lint_python.yml b/.github/workflows/lint_python.yml index 288a94b0..4e2f16be 100644 --- a/.github/workflows/lint_python.yml +++ b/.github/workflows/lint_python.yml @@ -4,8 +4,8 @@ jobs: lint_python: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 with: python-version: 3.12 - name: Install dependencies From 39708b9d59b49e371c508b2cd5fc42bb2b692221 Mon Sep 17 00:00:00 2001 From: Sanjana G Date: Fri, 13 Feb 2026 16:01:33 +0530 Subject: [PATCH 3/3] Docs: Add ecosystem examples to CoR, add Factory tests (#466) Co-authored-by: Sakis Kasampalis --- .../behavioral/chain_of_responsibility.py | 4 +++ tests/creational/test_factory.py | 30 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 tests/creational/test_factory.py diff --git a/patterns/behavioral/chain_of_responsibility.py b/patterns/behavioral/chain_of_responsibility.py index 9d46c4a8..46c3a419 100644 --- a/patterns/behavioral/chain_of_responsibility.py +++ b/patterns/behavioral/chain_of_responsibility.py @@ -14,6 +14,10 @@ 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. """ diff --git a/tests/creational/test_factory.py b/tests/creational/test_factory.py new file mode 100644 index 00000000..4bcfd4c5 --- /dev/null +++ b/tests/creational/test_factory.py @@ -0,0 +1,30 @@ +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)