Skip to content

Commit

Permalink
feat($demo): support strategy design pattern in Python
Browse files Browse the repository at this point in the history
  • Loading branch information
johnnymillergh committed May 5, 2023
1 parent c1909ba commit 2d33e6e
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 0 deletions.
51 changes: 51 additions & 0 deletions python_boilerplate/demo/strategy_design_pattern_usage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from loguru import logger


class BaseStrategy:
_strategies: list["BaseStrategy"] = []

@classmethod
def init(cls) -> None:
if len(cls._strategies) > 0:
return
strategy_classes = BaseStrategy.__subclasses__()
cls._strategies = [
strategy_class.__new__(strategy_class)
for strategy_class in strategy_classes
]

def strategy_name(self) -> str:
return self.__class__.__name__

def matches(self, data_input: str) -> bool:
raise NotImplementedError(
f"Function `{self.matches.__qualname__}` is not implemented"
)

def execute(self, data_input: str) -> None:
raise NotImplementedError(
f"Function `{self.execute.__qualname__}` is not implemented"
)

@classmethod
def iter_execute(cls, data_input: str) -> None:
strategies = list(filter(lambda x: x.matches(data_input), cls._strategies))
if len(strategies) != 1:
raise ValueError("No supported strategies found")
strategies[0].execute(data_input)


class StrategyOne(BaseStrategy):
def matches(self, data_input: str) -> bool:
return "one" in data_input

def execute(self, data_input: str) -> None:
logger.info(f"Executing {self.strategy_name()} with: {data_input}")


class StrategyTwo(BaseStrategy):
def matches(self, data_input: str) -> bool:
return "two" in data_input

def execute(self, data_input: str) -> None:
logger.info(f"Executing {self.strategy_name()} with: {data_input}")
47 changes: 47 additions & 0 deletions tests/demo/test_strategy_design_pattern_usage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import pytest
from loguru import logger

from python_boilerplate.demo.strategy_design_pattern_usage import BaseStrategy


@pytest.fixture(scope="session", autouse=True)
def setup() -> None:
logger.info("Initializing tests...")
BaseStrategy.init()


def test_strategies_when_matches() -> None:
BaseStrategy.iter_execute("Data for strategy one")
BaseStrategy.iter_execute("Data for strategy two")


def test_strategies_when_not_matches() -> None:
with pytest.raises(ValueError) as exc_info:
BaseStrategy.iter_execute("Data that doesn't match strategy")
assert exc_info is not None
logger.warning(f"Expected exception: {exc_info.value}")


def test_base_strategy() -> None:
strategy = BaseStrategy()
strategy_name = strategy.strategy_name()
assert strategy_name is not None
assert strategy_name == BaseStrategy.__name__
with pytest.raises(NotImplementedError) as exc_info_for_matches:
strategy.matches("any")
assert exc_info_for_matches is not None
with pytest.raises(NotImplementedError) as exc_info_for_execute:
strategy.execute("any")
assert exc_info_for_execute is not None


def test_call_twice_init() -> None:
try:
BaseStrategy.init()
BaseStrategy.init()
strategies = BaseStrategy._strategies
assert strategies is not None
assert len(strategies) == 2
logger.info(f"Strategies: {strategies}")
except Exception as e:
assert False, f"Unexpected exception raised: {e}"

0 comments on commit 2d33e6e

Please sign in to comment.