software_patterns.memoize.adapt_build_hash()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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