From e3d7d8ae022f5c9d8553a49c18146c7603ce7ee9 Mon Sep 17 00:00:00 2001 From: "Roy, Debakar" Date: Mon, 14 Aug 2023 11:04:07 +0530 Subject: [PATCH 1/3] Adding typehint --- patterns/behavioral/memento.py | 24 ++++++++++++------------ patterns/behavioral/specification.py | 25 +++++++++++++------------ patterns/behavioral/visitor.py | 7 ++++--- patterns/creational/lazy_evaluation.py | 13 +++++++------ patterns/creational/pool.py | 9 +++++---- patterns/other/graph_search.py | 19 ++++++++++++++----- patterns/structural/bridge.py | 11 ++++++----- patterns/structural/flyweight.py | 4 ++-- patterns/structural/mvc.py | 24 ++++++++++++++---------- 9 files changed, 77 insertions(+), 59 deletions(-) diff --git a/patterns/behavioral/memento.py b/patterns/behavioral/memento.py index e1d42fc2..7e2ab56c 100644 --- a/patterns/behavioral/memento.py +++ b/patterns/behavioral/memento.py @@ -5,14 +5,14 @@ Provides the ability to restore an object to its previous state. """ -from typing import Callable, List +from typing import Any, Type, Callable, List from copy import copy, deepcopy -def memento(obj, deep=False): +def memento(obj: Any, deep: bool = False) -> Callable: state = deepcopy(obj.__dict__) if deep else copy(obj.__dict__) - def restore(): + def restore() -> None: obj.__dict__.clear() obj.__dict__.update(state) @@ -28,15 +28,15 @@ class Transaction: deep = False states: List[Callable[[], None]] = [] - def __init__(self, deep, *targets): + def __init__(self, deep: bool, *targets: Any) -> None: self.deep = deep self.targets = targets self.commit() - def commit(self): + def commit(self) -> None: self.states = [memento(target, self.deep) for target in self.targets] - def rollback(self): + def rollback(self) -> None: for a_state in self.states: a_state() @@ -47,10 +47,10 @@ class Transactional: @Transactional will rollback to entry-state upon exceptions. """ - def __init__(self, method): + def __init__(self, method: Callable) -> None: self.method = method - def __get__(self, obj, T): + def __get__(self, obj: Any, T: Type) -> Callable: """ A decorator that makes a function transactional. @@ -69,17 +69,17 @@ def transaction(*args, **kwargs): class NumObj: - def __init__(self, value): + def __init__(self, value: int) -> None: self.value = value - def __repr__(self): + def __repr__(self) -> str: return f"<{self.__class__.__name__}: {self.value!r}>" - def increment(self): + def increment(self) -> None: self.value += 1 @Transactional - def do_stuff(self): + def do_stuff(self) -> None: self.value = "1111" # <- invalid value self.increment() # <- will fail and rollback diff --git a/patterns/behavioral/specification.py b/patterns/behavioral/specification.py index 303ee513..10d22689 100644 --- a/patterns/behavioral/specification.py +++ b/patterns/behavioral/specification.py @@ -6,6 +6,7 @@ """ from abc import abstractmethod +from typing import Union class Specification: @@ -28,22 +29,22 @@ class CompositeSpecification(Specification): def is_satisfied_by(self, candidate): pass - def and_specification(self, candidate): + def and_specification(self, candidate: "Specification") -> "AndSpecification": return AndSpecification(self, candidate) - def or_specification(self, candidate): + def or_specification(self, candidate: "Specification") -> "OrSpecification": return OrSpecification(self, candidate) - def not_specification(self): + def not_specification(self) -> "NotSpecification": return NotSpecification(self) class AndSpecification(CompositeSpecification): - def __init__(self, one, other): + def __init__(self, one: "Specification", other: "Specification") -> None: self._one: Specification = one self._other: Specification = other - def is_satisfied_by(self, candidate): + 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) @@ -51,11 +52,11 @@ def is_satisfied_by(self, candidate): class OrSpecification(CompositeSpecification): - def __init__(self, one, other): + def __init__(self, one: "Specification", other: "Specification") -> None: self._one: Specification = one self._other: Specification = other - def is_satisfied_by(self, candidate): + def is_satisfied_by(self, candidate: Union["User", str]): return bool( self._one.is_satisfied_by(candidate) or self._other.is_satisfied_by(candidate) @@ -63,25 +64,25 @@ def is_satisfied_by(self, candidate): class NotSpecification(CompositeSpecification): - def __init__(self, wrapped): + def __init__(self, wrapped: "Specification"): self._wrapped: Specification = wrapped - def is_satisfied_by(self, candidate): + 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=False): + def __init__(self, super_user: bool = False) -> None: self.super_user = super_user class UserSpecification(CompositeSpecification): - def is_satisfied_by(self, candidate): + def is_satisfied_by(self, candidate: Union["User", str]) -> bool: return isinstance(candidate, User) class SuperUserSpecification(CompositeSpecification): - def is_satisfied_by(self, candidate): + def is_satisfied_by(self, candidate: "User") -> bool: return getattr(candidate, "super_user", False) diff --git a/patterns/behavioral/visitor.py b/patterns/behavioral/visitor.py index 00d95248..dc019ddf 100644 --- a/patterns/behavioral/visitor.py +++ b/patterns/behavioral/visitor.py @@ -14,6 +14,7 @@ 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: @@ -33,7 +34,7 @@ class C(A, B): class Visitor: - def visit(self, node, *args, **kwargs): + def visit(self, node: Union[A, C, B], *args, **kwargs) -> None: meth = None for cls in node.__class__.__mro__: meth_name = "visit_" + cls.__name__ @@ -45,10 +46,10 @@ def visit(self, node, *args, **kwargs): meth = self.generic_visit return meth(node, *args, **kwargs) - def generic_visit(self, node, *args, **kwargs): + def generic_visit(self, node: A, *args, **kwargs) -> None: print("generic_visit " + node.__class__.__name__) - def visit_B(self, node, *args, **kwargs): + def visit_B(self, node: Union[C, B], *args, **kwargs) -> None: print("visit_B " + node.__class__.__name__) diff --git a/patterns/creational/lazy_evaluation.py b/patterns/creational/lazy_evaluation.py index b56daf0c..1f8db6bd 100644 --- a/patterns/creational/lazy_evaluation.py +++ b/patterns/creational/lazy_evaluation.py @@ -20,14 +20,15 @@ """ import functools +from typing import Callable, Type class lazy_property: - def __init__(self, function): + def __init__(self, function: Callable) -> None: self.function = function functools.update_wrapper(self, function) - def __get__(self, obj, type_): + def __get__(self, obj: "Person", type_: Type["Person"]) -> str: if obj is None: return self val = self.function(obj) @@ -35,7 +36,7 @@ def __get__(self, obj, type_): return val -def lazy_property2(fn): +def lazy_property2(fn: Callable) -> property: """ A lazy property decorator. @@ -54,19 +55,19 @@ def _lazy_property(self): class Person: - def __init__(self, name, occupation): + def __init__(self, name: str, occupation: str) -> None: self.name = name self.occupation = occupation self.call_count2 = 0 @lazy_property - def relatives(self): + 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): + def parents(self) -> str: self.call_count2 += 1 return "Father and mother" diff --git a/patterns/creational/pool.py b/patterns/creational/pool.py index 1d70ea69..7a4c7c3a 100644 --- a/patterns/creational/pool.py +++ b/patterns/creational/pool.py @@ -27,24 +27,25 @@ *TL;DR Stores a set of initialized objects kept ready to use. """ +from queue import Queue class ObjectPool: - def __init__(self, queue, auto_get=False): + 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): + def __enter__(self) -> str: if self.item is None: self.item = self._queue.get() return self.item - def __exit__(self, Type, value, traceback): + def __exit__(self, Type: None, value: None, traceback: None) -> None: if self.item is not None: self._queue.put(self.item) self.item = None - def __del__(self): + def __del__(self) -> None: if self.item is not None: self._queue.put(self.item) self.item = None diff --git a/patterns/other/graph_search.py b/patterns/other/graph_search.py index ad224db3..2daa0146 100644 --- a/patterns/other/graph_search.py +++ b/patterns/other/graph_search.py @@ -1,3 +1,6 @@ +from typing import Any, Dict, List, Optional, Union + + class GraphSearch: """Graph search emulation in python, from source @@ -6,10 +9,12 @@ class GraphSearch: dfs stands for Depth First Search bfs stands for Breadth First Search""" - def __init__(self, graph): + def __init__(self, graph: Dict[str, List[str]]) -> None: self.graph = graph - def find_path_dfs(self, start, end, path=None): + def find_path_dfs( + self, start: str, end: str, path: Optional[List[str]] = None + ) -> Optional[List[str]]: path = path or [] path.append(start) @@ -21,7 +26,9 @@ def find_path_dfs(self, start, end, path=None): if newpath: return newpath - def find_all_paths_dfs(self, start, end, path=None): + 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: @@ -33,7 +40,9 @@ def find_all_paths_dfs(self, start, end, path=None): paths.extend(newpaths) return paths - def find_shortest_path_dfs(self, start, end, path=None): + def find_shortest_path_dfs( + self, start: str, end: str, path: Optional[List[str]] = None + ) -> Optional[List[str]]: path = path or [] path.append(start) @@ -48,7 +57,7 @@ def find_shortest_path_dfs(self, start, end, path=None): shortest = newpath return shortest - def find_shortest_path_bfs(self, start, end): + 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. diff --git a/patterns/structural/bridge.py b/patterns/structural/bridge.py index feddb675..92e1709e 100644 --- a/patterns/structural/bridge.py +++ b/patterns/structural/bridge.py @@ -5,34 +5,35 @@ *TL;DR Decouples an abstraction from its implementation. """ +from typing import Union # ConcreteImplementor 1/2 class DrawingAPI1: - def draw_circle(self, x, y, radius): + 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, y, radius): + 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, y, radius, drawing_api): + 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): + 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): + def scale(self, pct: float) -> None: self._radius *= pct diff --git a/patterns/structural/flyweight.py b/patterns/structural/flyweight.py index fad17a8b..68b6f43c 100644 --- a/patterns/structural/flyweight.py +++ b/patterns/structural/flyweight.py @@ -36,7 +36,7 @@ class Card: # when there are no other references to it. _pool: weakref.WeakValueDictionary = weakref.WeakValueDictionary() - def __new__(cls, value, suit): + 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) @@ -52,7 +52,7 @@ def __new__(cls, value, suit): # def __init__(self, value, suit): # self.value, self.suit = value, suit - def __repr__(self): + def __repr__(self) -> str: return f"" diff --git a/patterns/structural/mvc.py b/patterns/structural/mvc.py index 3f7dc315..78c437ce 100644 --- a/patterns/structural/mvc.py +++ b/patterns/structural/mvc.py @@ -4,6 +4,8 @@ """ from abc import ABC, abstractmethod +from ProductModel import Price +from typing import Dict, List, Union class Model(ABC): @@ -28,7 +30,7 @@ class Price(float): """A polymorphic way to pass a float with a particular __str__ functionality.""" - def __str__(self): + def __str__(self) -> str: return f"{self:.2f}" products = { @@ -39,10 +41,10 @@ def __str__(self): item_type = "product" - def __iter__(self): + def __iter__(self) -> None: yield from self.products - def get(self, product): + def get(self, product: str) -> Dict[str, Union[Price, int]]: try: return self.products[product] except KeyError as e: @@ -66,17 +68,19 @@ def item_not_found(self, item_type, item_name): class ConsoleView(View): - def show_item_list(self, item_type, item_list): + def show_item_list(self, item_type: str, item_list: List[str]) -> None: print(item_type.upper() + " LIST:") for item in item_list: print(item) print("") @staticmethod - def capitalizer(string): + def capitalizer(string: str) -> str: return string[0].upper() + string[1:].lower() - def show_item_information(self, item_type, item_name, item_info): + def show_item_information( + self, item_type: str, item_name: str, item_info: Dict[str, Union[Price, int]] + ) -> None: print(item_type.upper() + " INFORMATION:") printout = "Name: %s" % item_name for key, value in item_info.items(): @@ -84,21 +88,21 @@ def show_item_information(self, item_type, item_name, item_info): printout += "\n" print(printout) - def item_not_found(self, item_type, item_name): + 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: - def __init__(self, model, view): + def __init__(self, model: ProductModel, view: ConsoleView) -> None: self.model = model self.view = view - def show_items(self): + 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): + 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 From a11ecbdf04d692ef2a3ee2fd540bf8fdc460a831 Mon Sep 17 00:00:00 2001 From: "Roy, Debakar" Date: Mon, 14 Aug 2023 11:07:53 +0530 Subject: [PATCH 2/3] Update __exit__ type --- patterns/creational/pool.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/patterns/creational/pool.py b/patterns/creational/pool.py index 7a4c7c3a..02f61791 100644 --- a/patterns/creational/pool.py +++ b/patterns/creational/pool.py @@ -28,6 +28,8 @@ Stores a set of initialized objects kept ready to use. """ from queue import Queue +from types import TracebackType +from typing import Union class ObjectPool: @@ -40,7 +42,12 @@ def __enter__(self) -> str: self.item = self._queue.get() return self.item - def __exit__(self, Type: None, value: None, traceback: None) -> None: + 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 From ff36bf4d1232e319a238db5670b6f22aecf44ce7 Mon Sep 17 00:00:00 2001 From: "Roy, Debakar" Date: Mon, 14 Aug 2023 11:15:19 +0530 Subject: [PATCH 3/3] fix black formatting --- patterns/behavioral/catalog.py | 1 - patterns/behavioral/registry.py | 1 - patterns/creational/abstract_factory.py | 1 + patterns/creational/factory.py | 1 - patterns/structural/3-tier.py | 1 - patterns/structural/bridge.py | 4 +++- 6 files changed, 4 insertions(+), 5 deletions(-) diff --git a/patterns/behavioral/catalog.py b/patterns/behavioral/catalog.py index 7c91aa7d..ffdf40a3 100644 --- a/patterns/behavioral/catalog.py +++ b/patterns/behavioral/catalog.py @@ -13,7 +13,6 @@ class Catalog: """ 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 diff --git a/patterns/behavioral/registry.py b/patterns/behavioral/registry.py index d44a992e..60cae019 100644 --- a/patterns/behavioral/registry.py +++ b/patterns/behavioral/registry.py @@ -2,7 +2,6 @@ class RegistryHolder(type): - REGISTRY: Dict[str, "RegistryHolder"] = {} def __new__(cls, name, bases, attrs): diff --git a/patterns/creational/abstract_factory.py b/patterns/creational/abstract_factory.py index d092a6b4..3662e7a0 100644 --- a/patterns/creational/abstract_factory.py +++ b/patterns/creational/abstract_factory.py @@ -80,6 +80,7 @@ def buy_pet(self, name: str) -> Pet: # Additional factories: + # Create a random animal def random_animal(name: str) -> Pet: """Let's be dynamic!""" diff --git a/patterns/creational/factory.py b/patterns/creational/factory.py index a1beffa8..86eb1568 100644 --- a/patterns/creational/factory.py +++ b/patterns/creational/factory.py @@ -50,7 +50,6 @@ def localize(self, msg: str) -> str: def get_localizer(language: str = "English") -> Localizer: - """Factory""" localizers: Dict[str, Type[Localizer]] = { "English": EnglishLocalizer, diff --git a/patterns/structural/3-tier.py b/patterns/structural/3-tier.py index ecc04243..287badaf 100644 --- a/patterns/structural/3-tier.py +++ b/patterns/structural/3-tier.py @@ -16,7 +16,6 @@ class Data: } def __get__(self, obj, klas): - print("(Fetching from Data Store)") return {"products": self.products} diff --git a/patterns/structural/bridge.py b/patterns/structural/bridge.py index 92e1709e..1575cb53 100644 --- a/patterns/structural/bridge.py +++ b/patterns/structural/bridge.py @@ -22,7 +22,9 @@ def draw_circle(self, x: int, y: int, radius: float) -> None: # Refined Abstraction class CircleShape: - def __init__(self, x: int, y: int, radius: int, drawing_api: Union[DrawingAPI2, DrawingAPI1]) -> None: + def __init__( + self, x: int, y: int, radius: int, drawing_api: Union[DrawingAPI2, DrawingAPI1] + ) -> None: self._x = x self._y = y self._radius = radius