software_patterns.memoize   A
last analyzed

Complexity

Total Complexity 5

Size/Duplication

Total Lines 108
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 37
dl 0
loc 108
rs 10
c 0
b 0
f 0
wmc 5

1 Function

Rating   Name   Duplication   Size   Complexity  
A adapt_build_hash() 0 5 1

3 Methods

Rating   Name   Duplication   Size   Complexity  
A ObjectsPool.__init__() 0 11 1
A ObjectsPool.__build_hash() 0 7 1
A ObjectsPool.get_object() 0 15 2
1
"""Implementation of the Memoize Software Design Pattern.
2
3
Memoize is implemented using an Object Pool which is queried by a key which is
4
the result of computing a hash given runtime arguments.
5
6
"""
7
8
import types
9
from typing import Any, Callable, Dict, Generic, Optional, TypeVar, Union
10
11
__all__ = ['ObjectsPool']
12
13
14
DictKey = Union[int, str]
15
T = TypeVar('T')
16
17
RuntimeBuildHashCallable = Callable[..., Union[int, str]]
18
19
20
def adapt_build_hash(a_callable: RuntimeBuildHashCallable):
21
    def build_hash(_self: T, *args, **kwargs):
22
        return a_callable(*args, **kwargs)
23
24
    return build_hash
25
26
27
class ObjectsPool(Generic[T]):
28
    """Cache objects and allow to query (the pool) using runtime arguments.
29
30
    Instances of the ObjectsPool class implement the Object Pool Software Design
31
    Creational Pattern.
32
33
    Whenever an object is requested, it is checked whether it exists in the
34
    pool by using the runtimetime arguments to query a python dictionary.
35
    If it exists, a reference is returned, otherwise a new object is
36
    constructed (given the provided callback) and its reference is returned.
37
38
    Example:
39
        >>> from software_patterns import ObjectsPool
40
        >>> class ClientClass:
41
        ...  def __init__(self, a: int, b: int):
42
        ...   pass
43
44
        >>> object_pool = ObjectsPool[ClientClass](ClientClass)
45
46
        >>> obj1 = object_pool.get_object(1, 2)
47
        >>> obj2 = object_pool.get_object(1, 3)
48
        >>> obj3 = object_pool.get_object(1, 2)
49
50
        >>> id(obj1) == id(obj3)
51
        True
52
53
        >>> len(object_pool._objects)
54
        2
55
56
    Args:
57
        callback (Callable[..., ObjectType]): constructs objects given arguments
58
        hash_callback (Optional[RuntimeBuildHashCallable], optional): option to
59
            overide the default hash key computer. Defaults to None.
60
    Returns:
61
        [type]: [description]
62
    """
63
64
    _objects: Dict[DictKey, T]
65
66
    user_supplied_callback: Dict[bool, Callable] = {
67
        True: lambda callback: callback,
68
        False: lambda callback: ObjectsPool.__build_hash,
69
    }
70
71
    def __init__(
72
        self,
73
        callback: Callable[..., T],
74
        hash_callback: Optional[RuntimeBuildHashCallable] = None,
75
    ):
76
        self.constructor = callback
77
        build_hash_callback = self.user_supplied_callback[callable(hash_callback)](
78
            hash_callback
79
        )
80
        self._build_hash = types.MethodType(adapt_build_hash(build_hash_callback), self)
81
        self._objects = {}
82
83
    @staticmethod
84
    def __build_hash(*args: Any, **kwargs: Any) -> int:
85
        r"""Construct a hash out of the input \*args and \*\*kwargs."""
86
        return hash(
87
            '-'.join(
88
                [str(_) for _ in args]
89
                + [f'{key}={str(value)}' for key, value in kwargs.items()]
90
            )
91
        )
92
93
    def get_object(self, *args: Any, **kwargs: Any) -> T:
94
        r"""Request an object from the pool.
95
96
        Get or create an object given the input arguments, which are used to
97
        create a unique hash key. The key is used to query a python dictionary
98
        and determine whether the object request refers to a cached object.
99
100
        Returns:
101
            object (ObjectType): the reference to the object that corresponds to the input
102
            arguments, regardless of whether it was found in the pool or not
103
        """
104
        key = self._build_hash(*args, **kwargs)
105
        if key not in self._objects:
106
            self._objects[key] = self.constructor(*args, **kwargs)
107
        return self._objects[key]
108