From 7f71d370879b6c0602c5ac3c91ed9ceaa241dea1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB?= Date: Thu, 27 May 2021 17:44:08 +0300 Subject: [PATCH 1/3] Added type hints and pytest tests --- patterns/behavioral/strategy.py | 67 +++++++++++++++++++++++-------- tests/behavioral/test_strategy.py | 52 ++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 16 deletions(-) create mode 100644 tests/behavioral/test_strategy.py diff --git a/patterns/behavioral/strategy.py b/patterns/behavioral/strategy.py index 92d11f25..638a40f6 100644 --- a/patterns/behavioral/strategy.py +++ b/patterns/behavioral/strategy.py @@ -7,42 +7,77 @@ Enables selecting an algorithm at runtime. """ +from __future__ import annotations + +from typing import Callable, Type + + +class DiscountStrategyValidator: # Descriptor class for check perform + def validate(self, 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__}") + return True + except ValueError as ex: + print(str(ex)) + return False + + def __set_name__(self, owner, name: str) -> None: + self.private_name = '_' + 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: - def __init__(self, price, discount_strategy=None): - self.price = price + discount_strategy = DiscountStrategyValidator() + + def __init__(self, price: float, discount_strategy: Callable = None) -> None: + self.price: float = price self.discount_strategy = discount_strategy - def price_after_discount(self): + def apply_discount(self) -> float: if self.discount_strategy: discount = self.discount_strategy(self) else: discount = 0 + return self.price - discount - def __repr__(self): - fmt = "" - return fmt.format(self.price, self.price_after_discount()) + def __repr__(self) -> str: + return f"" -def ten_percent_discount(order): +def ten_percent_discount(order: Order) -> float: return order.price * 0.10 -def on_sale_discount(order): +def on_sale_discount(order: Order) -> float: return order.price * 0.25 + 20 def main(): """ - >>> Order(100) - - - >>> Order(100, discount_strategy=ten_percent_discount) - - - >>> Order(1000, discount_strategy=on_sale_discount) - + >>> order = Order(100, discount_strategy=ten_percent_discount) + >>> print(order) + + >>> print(order.apply_discount()) + 90.0 + >>> order = Order(100, discount_strategy=on_sale_discount) + >>> print(order) + + >>> 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) + """ diff --git a/tests/behavioral/test_strategy.py b/tests/behavioral/test_strategy.py new file mode 100644 index 00000000..6a3b2504 --- /dev/null +++ b/tests/behavioral/test_strategy.py @@ -0,0 +1,52 @@ +import pytest + +from patterns.behavioral.strategy import Order, ten_percent_discount, on_sale_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 From b8849c1dbbf0585d331a41ba7b80212a2930c79f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB?= Date: Fri, 28 May 2021 09:08:07 +0300 Subject: [PATCH 2/3] resolve review --- patterns/behavioral/strategy.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/patterns/behavioral/strategy.py b/patterns/behavioral/strategy.py index 638a40f6..93432275 100644 --- a/patterns/behavioral/strategy.py +++ b/patterns/behavioral/strategy.py @@ -7,23 +7,26 @@ Enables selecting an algorithm at runtime. """ + from __future__ import annotations from typing import Callable, Type class DiscountStrategyValidator: # Descriptor class for check perform - def validate(self, obj: Order, value: Callable) -> bool: + @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__}") - return True except ValueError as ex: print(str(ex)) return False + else: + return True def __set_name__(self, owner, name: str) -> None: - self.private_name = '_' + name + self.private_name = f"_{name}" def __set__(self, obj: Order, value: Callable = None) -> None: if value and self.validate(obj, value): From c0677d7302d4de398b8738db19285b4bc345b2d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB?= Date: Mon, 31 May 2021 09:06:18 +0300 Subject: [PATCH 3/3] Formatted with black --- patterns/behavioral/strategy.py | 4 +++- patterns/structural/3-tier.py | 6 +++--- patterns/structural/front_controller.py | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/patterns/behavioral/strategy.py b/patterns/behavioral/strategy.py index 93432275..595df255 100644 --- a/patterns/behavioral/strategy.py +++ b/patterns/behavioral/strategy.py @@ -18,7 +18,9 @@ class DiscountStrategyValidator: # Descriptor class for check perform 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__}") + raise ValueError( + f"Discount cannot be applied due to negative price resulting. {value.__name__}" + ) except ValueError as ex: print(str(ex)) return False diff --git a/patterns/structural/3-tier.py b/patterns/structural/3-tier.py index 64835f99..ecc04243 100644 --- a/patterns/structural/3-tier.py +++ b/patterns/structural/3-tier.py @@ -7,7 +7,7 @@ class Data: - """ Data Store Class """ + """Data Store Class""" products = { "milk": {"price": 1.50, "quantity": 10}, @@ -22,7 +22,7 @@ def __get__(self, obj, klas): class BusinessLogic: - """ Business logic holding data store instances """ + """Business logic holding data store instances""" data = Data() @@ -36,7 +36,7 @@ def product_information( class Ui: - """ UI interaction class """ + """UI interaction class""" def __init__(self) -> None: self.business_logic = BusinessLogic() diff --git a/patterns/structural/front_controller.py b/patterns/structural/front_controller.py index 9377fefe..d93f74d6 100644 --- a/patterns/structural/front_controller.py +++ b/patterns/structural/front_controller.py @@ -31,7 +31,7 @@ def dispatch(self, request): class RequestController: - """ front controller """ + """front controller""" def __init__(self): self.dispatcher = Dispatcher() @@ -44,7 +44,7 @@ def dispatch_request(self, request): class Request: - """ request """ + """request""" mobile_type = "mobile" tablet_type = "tablet"