type_enforced.utils

  1import types, re, copy
  2from functools import update_wrapper
  3
  4
  5class Partial:
  6    """
  7    A special class wrapper to allow for easy partial function wrappings and calls.
  8    """
  9
 10    def __init__(
 11        self,
 12        __fn__,
 13        *__args__,
 14        **__kwargs__,
 15    ):
 16        update_wrapper(self, __fn__)
 17        self.__fn__ = __fn__
 18        self.__args__ = __args__
 19        self.__kwargs__ = __kwargs__
 20        self.__fnArity__ = self.__getFnArity__()
 21        self.__arity__ = self.__getArity__(__args__, __kwargs__)
 22
 23    def __exception__(self, message):
 24        pre_message = (
 25            f"({self.__fn__.__module__}.{self.__fn__.__qualname__}_partial): "
 26        )
 27        raise Exception(pre_message + message)
 28
 29    def __call__(self, *args, **kwargs):
 30        new_args = self.__args__ + args
 31        new_kwargs = {**self.__kwargs__, **kwargs}
 32        self.__arity__ = self.__getArity__(new_args, new_kwargs)
 33        if self.__arity__ < 0:
 34            self.__exception__("Too many arguments were supplied")
 35        if self.__arity__ == 0:
 36            results = self.__fn__(*new_args, **new_kwargs)
 37            return results
 38        return Partial(
 39            self.__fn__,
 40            *new_args,
 41            **new_kwargs,
 42        )
 43
 44    def __repr__(self):
 45        return f"<Partial {self.__fn__.__module__}.{self.__fn__.__qualname__} object at {hex(id(self))}>"
 46
 47    def __getArity__(self, args, kwargs):
 48        return self.__fnArity__ - (len(args) + len(kwargs))
 49
 50    def __getFnArity__(self):
 51        if not isinstance(self.__fn__, (types.MethodType, types.FunctionType)):
 52            self.__exception__(
 53                "A non function was passed as a function and does not have any arity. See the stack trace above for more information."
 54            )
 55        extra_method_input_count = (
 56            1 if isinstance(self.__fn__, (types.MethodType)) else 0
 57        )
 58        return self.__fn__.__code__.co_argcount - extra_method_input_count
 59
 60
 61class GenericConstraint:
 62    def __init__(self, constraints: dict):
 63        """
 64        Creates a generic constraint object that can be used to validate a value against a set of constraints.
 65
 66        Required Arguments:
 67
 68        - `constraints`:
 69            - What: A dictionary of constraint names and their associated functions.
 70            - Type: dict
 71            - Note: All values in the dictionary must be functions that take a single argument and return a boolean.
 72            - Note: The dictionary keys will be used to identify the failed constraints in the error messages.
 73        """
 74        assert all(
 75            hasattr(v, "__call__") for v in constraints.values()
 76        ), "All constraints must be functions."
 77        self.__constraint_checks__ = constraints
 78
 79    def __validate__(self, varname, value):
 80        for check_name, check_func in self.__constraint_checks__.items():
 81            try:
 82                if not check_func(value):
 83                    return f"Constraint `{check_name}` not met with the provided value `{value}`"
 84            except Exception as e:
 85                return f"An exception was raised when checking the constraint `{check_name}` with the provided value `{value}`. Error: {e}"
 86        return True
 87
 88
 89class Constraint(GenericConstraint):
 90    def __init__(
 91        self,
 92        pattern: [str, None] = None,
 93        includes: [list, tuple, set, None] = None,
 94        excludes: [list, tuple, set, None] = None,
 95        gt: [float, int, None] = None,
 96        lt: [float, int, None] = None,
 97        ge: [float, int, None] = None,
 98        le: [float, int, None] = None,
 99        eq: [float, int, None] = None,
100        ne: [float, int, None] = None,
101    ):
102        """
103        Creates a constraint object that can be used to validate a value against a set of constraints.
104
105        Optional Arguments:
106
107        - `pattern`:
108            - What: A regex pattern that the value must match.
109            - Type: str or None
110            - Default: None
111        - `includes`:
112            - What: A list of values that the value must be in.
113            - Type: list, tuple, set or None
114            - Default: None
115        - `excludes`:
116            - What: A list of values that the value must not be in.
117            - Type: list, tuple, set or None
118            - Default: None
119        - `gt`:
120            - What: The value must be greater than this value.
121            - Type: float, int or None
122            - Default: None
123        - `lt`:
124            - What: The value must be less than this value.
125            - Type: float, int or None
126            - Default: None
127        - `ge`:
128            - What: The value must be greater than or equal to this value.
129            - Type: float, int or None
130            - Default: None
131        - `le`:
132            - What: The value must be less than or equal to this value.
133            - Type: float, int or None
134            - Default: None
135        - `eq`:
136            - What: The value must be equal to this value.
137            - Type: float, int or None
138            - Default: None
139        - `ne`:
140            - What: The value must not be equal to this value.
141            - Type: float, int or None
142            - Default: None
143        """
144        assert any(
145            v is not None
146            for v in [pattern, gt, lt, ge, le, eq, ne, includes, excludes]
147        ), "At least one constraint must be provided."
148        assert isinstance(
149            includes, (list, tuple, set, type(None))
150        ), "Includes must be a list, tuple, set or None."
151        assert isinstance(
152            excludes, (list, tuple, set, type(None))
153        ), "Excludes must be a list, tuple, set or None."
154        assert isinstance(
155            pattern, (str, type(None))
156        ), "Pattern must be a string or None."
157        assert isinstance(
158            gt, (float, int, type(None))
159        ), "Greater than constraint must be a float, int or None."
160        assert isinstance(
161            lt, (float, int, type(None))
162        ), "Less than constraint must be a float, int or None."
163        assert isinstance(
164            ge, (float, int, type(None))
165        ), "Greater or equal to constraint must be a float, int or None."
166        assert isinstance(
167            le, (float, int, type(None))
168        ), "Less than or equal to constraint must be a float, int or None."
169        assert isinstance(
170            eq, (float, int, type(None))
171        ), "Equal to constraint must be a float, int or None."
172        assert isinstance(
173            ne, (float, int, type(None))
174        ), "Not equal to constraint must be a float, int or None."
175        self.__constraint_checks__ = {}
176        if pattern is not None:
177            self.__constraint_checks__["be a string"] = lambda x: isinstance(
178                x, str
179            )
180            self.__constraint_checks__["Regex Pattern Match"] = lambda x: bool(
181                re.findall(pattern, x)
182            )
183        if includes is not None:
184            self.__constraint_checks__[f"Includes"] = lambda x: x in includes
185        if excludes is not None:
186            self.__constraint_checks__["Excludes"] = lambda x: x not in excludes
187        if gt is not None:
188            self.__constraint_checks__[f"Greater Than ({gt})"] = (
189                lambda x: x > gt
190            )
191        if lt is not None:
192            self.__constraint_checks__[f"Less Than ({lt})"] = lambda x: x < lt
193        if ge is not None:
194            self.__constraint_checks__[f"Greater Than Or Equal To ({ge})"] = (
195                lambda x: x >= ge
196            )
197        if le is not None:
198            self.__constraint_checks__[f"Less Than Or Equal To ({le})"] = (
199                lambda x: x <= le
200            )
201        if eq is not None:
202            self.__constraint_checks__[f"Equal To ({eq})"] = lambda x: x == eq
203        if ne is not None:
204            self.__constraint_checks__[f"Not Equal To ({ne})"] = (
205                lambda x: x != ne
206            )
207
208
209class WithSubclasses:
210    def __init__(self, obj, cache=True):
211        """
212        A special helper class to allow a class type to be passed and also allow all subclasses of that type.
213
214        This allows delayed evaluation of the subclasses until initial call time such that delayed inherited classes are considered.
215
216        Note: You can not validate that a passed object is a WithSubclasses object as this is a special case.
217
218        Required Arguments:
219
220        - `obj`:
221            - What: An uninitialized class that should also be considered type correct if a subclass is passed.
222            - Type: Any Uninitialized class
223
224        Optional Arguments:
225
226        - `cache`:
227            - What: Whether to cache the calcuation of the subclasses. If set to False, this will recalculate the subclasses on each call.
228            - Type: bool
229            - Default: True
230        """
231        self.obj = obj
232        self.cache = cache
233
234    @classmethod
235    def __get_subclasses__(cls, obj):
236        """
237        A special helper method to get all of the subclasses of a provided class object.
238
239        Requires:
240
241        - `obj`:
242            - What: An uninitialized class that should also be considered type correct if a subclass is passed.
243            - Type: Any Uninitialized class
244
245        Returns:
246
247        - `out`:
248            - What: A list of all of the subclasses (recursively parsed)
249            - Type: list of strs
250
251
252        Notes:
253
254        - From a functional perspective, this recursively gets the subclasses for an uninitialised class (type).
255        """
256        out = [obj]
257        for i in obj.__subclasses__():
258            out += cls.__get_subclasses__(i)
259        return out
260
261    def get_subclasses(self):
262        """
263        Returns a list of all of the subclasses of the class that was passed to the WithSubclasses object.
264
265        If already calculated, it will return the cached value.
266
267        Returns:
268
269        - `subclasses`:
270            - What: A list of all of the subclasses of the class that was passed to the WithSubclasses object.
271            - Type: list of strs
272        """
273        if not hasattr(self, "subclasses") or self.cache == False:
274            self.subclasses = self.__get_subclasses__(self.obj)
275        return self.subclasses
276
277
278def DeepMerge(original: dict, update: dict):
279    """
280    Merge two dictionaries together, recursively merging any nested dictionaries and extending any nested lists.
281
282    Required Arguments:
283
284    - `original`:
285        - What: The original dictionary to merge the update into.
286        - Type: dict
287    - `update`:
288        - What: The dictionary to merge into the original dictionary.
289        - Type: dict
290    """
291    original = copy.deepcopy(original)
292    for key, value in update.items():
293        if (
294            key in original
295            and isinstance(original[key], dict)
296            and isinstance(value, dict)
297        ):
298            DeepMerge(original[key], value)
299        elif (
300            key in original
301            and isinstance(original[key], list)
302            and isinstance(value, list)
303        ):
304            original[key].extend(value)
305        else:
306            original[key] = value
307    return original
class Partial:
 6class Partial:
 7    """
 8    A special class wrapper to allow for easy partial function wrappings and calls.
 9    """
10
11    def __init__(
12        self,
13        __fn__,
14        *__args__,
15        **__kwargs__,
16    ):
17        update_wrapper(self, __fn__)
18        self.__fn__ = __fn__
19        self.__args__ = __args__
20        self.__kwargs__ = __kwargs__
21        self.__fnArity__ = self.__getFnArity__()
22        self.__arity__ = self.__getArity__(__args__, __kwargs__)
23
24    def __exception__(self, message):
25        pre_message = (
26            f"({self.__fn__.__module__}.{self.__fn__.__qualname__}_partial): "
27        )
28        raise Exception(pre_message + message)
29
30    def __call__(self, *args, **kwargs):
31        new_args = self.__args__ + args
32        new_kwargs = {**self.__kwargs__, **kwargs}
33        self.__arity__ = self.__getArity__(new_args, new_kwargs)
34        if self.__arity__ < 0:
35            self.__exception__("Too many arguments were supplied")
36        if self.__arity__ == 0:
37            results = self.__fn__(*new_args, **new_kwargs)
38            return results
39        return Partial(
40            self.__fn__,
41            *new_args,
42            **new_kwargs,
43        )
44
45    def __repr__(self):
46        return f"<Partial {self.__fn__.__module__}.{self.__fn__.__qualname__} object at {hex(id(self))}>"
47
48    def __getArity__(self, args, kwargs):
49        return self.__fnArity__ - (len(args) + len(kwargs))
50
51    def __getFnArity__(self):
52        if not isinstance(self.__fn__, (types.MethodType, types.FunctionType)):
53            self.__exception__(
54                "A non function was passed as a function and does not have any arity. See the stack trace above for more information."
55            )
56        extra_method_input_count = (
57            1 if isinstance(self.__fn__, (types.MethodType)) else 0
58        )
59        return self.__fn__.__code__.co_argcount - extra_method_input_count

A special class wrapper to allow for easy partial function wrappings and calls.

Partial(__fn__, *__args__, **__kwargs__)
11    def __init__(
12        self,
13        __fn__,
14        *__args__,
15        **__kwargs__,
16    ):
17        update_wrapper(self, __fn__)
18        self.__fn__ = __fn__
19        self.__args__ = __args__
20        self.__kwargs__ = __kwargs__
21        self.__fnArity__ = self.__getFnArity__()
22        self.__arity__ = self.__getArity__(__args__, __kwargs__)
class GenericConstraint:
62class GenericConstraint:
63    def __init__(self, constraints: dict):
64        """
65        Creates a generic constraint object that can be used to validate a value against a set of constraints.
66
67        Required Arguments:
68
69        - `constraints`:
70            - What: A dictionary of constraint names and their associated functions.
71            - Type: dict
72            - Note: All values in the dictionary must be functions that take a single argument and return a boolean.
73            - Note: The dictionary keys will be used to identify the failed constraints in the error messages.
74        """
75        assert all(
76            hasattr(v, "__call__") for v in constraints.values()
77        ), "All constraints must be functions."
78        self.__constraint_checks__ = constraints
79
80    def __validate__(self, varname, value):
81        for check_name, check_func in self.__constraint_checks__.items():
82            try:
83                if not check_func(value):
84                    return f"Constraint `{check_name}` not met with the provided value `{value}`"
85            except Exception as e:
86                return f"An exception was raised when checking the constraint `{check_name}` with the provided value `{value}`. Error: {e}"
87        return True
GenericConstraint(constraints: dict)
63    def __init__(self, constraints: dict):
64        """
65        Creates a generic constraint object that can be used to validate a value against a set of constraints.
66
67        Required Arguments:
68
69        - `constraints`:
70            - What: A dictionary of constraint names and their associated functions.
71            - Type: dict
72            - Note: All values in the dictionary must be functions that take a single argument and return a boolean.
73            - Note: The dictionary keys will be used to identify the failed constraints in the error messages.
74        """
75        assert all(
76            hasattr(v, "__call__") for v in constraints.values()
77        ), "All constraints must be functions."
78        self.__constraint_checks__ = constraints

Creates a generic constraint object that can be used to validate a value against a set of constraints.

Required Arguments:

  • constraints:
    • What: A dictionary of constraint names and their associated functions.
    • Type: dict
    • Note: All values in the dictionary must be functions that take a single argument and return a boolean.
    • Note: The dictionary keys will be used to identify the failed constraints in the error messages.
class Constraint(GenericConstraint):
 90class Constraint(GenericConstraint):
 91    def __init__(
 92        self,
 93        pattern: [str, None] = None,
 94        includes: [list, tuple, set, None] = None,
 95        excludes: [list, tuple, set, None] = None,
 96        gt: [float, int, None] = None,
 97        lt: [float, int, None] = None,
 98        ge: [float, int, None] = None,
 99        le: [float, int, None] = None,
100        eq: [float, int, None] = None,
101        ne: [float, int, None] = None,
102    ):
103        """
104        Creates a constraint object that can be used to validate a value against a set of constraints.
105
106        Optional Arguments:
107
108        - `pattern`:
109            - What: A regex pattern that the value must match.
110            - Type: str or None
111            - Default: None
112        - `includes`:
113            - What: A list of values that the value must be in.
114            - Type: list, tuple, set or None
115            - Default: None
116        - `excludes`:
117            - What: A list of values that the value must not be in.
118            - Type: list, tuple, set or None
119            - Default: None
120        - `gt`:
121            - What: The value must be greater than this value.
122            - Type: float, int or None
123            - Default: None
124        - `lt`:
125            - What: The value must be less than this value.
126            - Type: float, int or None
127            - Default: None
128        - `ge`:
129            - What: The value must be greater than or equal to this value.
130            - Type: float, int or None
131            - Default: None
132        - `le`:
133            - What: The value must be less than or equal to this value.
134            - Type: float, int or None
135            - Default: None
136        - `eq`:
137            - What: The value must be equal to this value.
138            - Type: float, int or None
139            - Default: None
140        - `ne`:
141            - What: The value must not be equal to this value.
142            - Type: float, int or None
143            - Default: None
144        """
145        assert any(
146            v is not None
147            for v in [pattern, gt, lt, ge, le, eq, ne, includes, excludes]
148        ), "At least one constraint must be provided."
149        assert isinstance(
150            includes, (list, tuple, set, type(None))
151        ), "Includes must be a list, tuple, set or None."
152        assert isinstance(
153            excludes, (list, tuple, set, type(None))
154        ), "Excludes must be a list, tuple, set or None."
155        assert isinstance(
156            pattern, (str, type(None))
157        ), "Pattern must be a string or None."
158        assert isinstance(
159            gt, (float, int, type(None))
160        ), "Greater than constraint must be a float, int or None."
161        assert isinstance(
162            lt, (float, int, type(None))
163        ), "Less than constraint must be a float, int or None."
164        assert isinstance(
165            ge, (float, int, type(None))
166        ), "Greater or equal to constraint must be a float, int or None."
167        assert isinstance(
168            le, (float, int, type(None))
169        ), "Less than or equal to constraint must be a float, int or None."
170        assert isinstance(
171            eq, (float, int, type(None))
172        ), "Equal to constraint must be a float, int or None."
173        assert isinstance(
174            ne, (float, int, type(None))
175        ), "Not equal to constraint must be a float, int or None."
176        self.__constraint_checks__ = {}
177        if pattern is not None:
178            self.__constraint_checks__["be a string"] = lambda x: isinstance(
179                x, str
180            )
181            self.__constraint_checks__["Regex Pattern Match"] = lambda x: bool(
182                re.findall(pattern, x)
183            )
184        if includes is not None:
185            self.__constraint_checks__[f"Includes"] = lambda x: x in includes
186        if excludes is not None:
187            self.__constraint_checks__["Excludes"] = lambda x: x not in excludes
188        if gt is not None:
189            self.__constraint_checks__[f"Greater Than ({gt})"] = (
190                lambda x: x > gt
191            )
192        if lt is not None:
193            self.__constraint_checks__[f"Less Than ({lt})"] = lambda x: x < lt
194        if ge is not None:
195            self.__constraint_checks__[f"Greater Than Or Equal To ({ge})"] = (
196                lambda x: x >= ge
197            )
198        if le is not None:
199            self.__constraint_checks__[f"Less Than Or Equal To ({le})"] = (
200                lambda x: x <= le
201            )
202        if eq is not None:
203            self.__constraint_checks__[f"Equal To ({eq})"] = lambda x: x == eq
204        if ne is not None:
205            self.__constraint_checks__[f"Not Equal To ({ne})"] = (
206                lambda x: x != ne
207            )
Constraint( pattern: [<class 'str'>, None] = None, includes: [<class 'list'>, <class 'tuple'>, <class 'set'>, None] = None, excludes: [<class 'list'>, <class 'tuple'>, <class 'set'>, None] = None, gt: [<class 'float'>, <class 'int'>, None] = None, lt: [<class 'float'>, <class 'int'>, None] = None, ge: [<class 'float'>, <class 'int'>, None] = None, le: [<class 'float'>, <class 'int'>, None] = None, eq: [<class 'float'>, <class 'int'>, None] = None, ne: [<class 'float'>, <class 'int'>, None] = None)
 91    def __init__(
 92        self,
 93        pattern: [str, None] = None,
 94        includes: [list, tuple, set, None] = None,
 95        excludes: [list, tuple, set, None] = None,
 96        gt: [float, int, None] = None,
 97        lt: [float, int, None] = None,
 98        ge: [float, int, None] = None,
 99        le: [float, int, None] = None,
100        eq: [float, int, None] = None,
101        ne: [float, int, None] = None,
102    ):
103        """
104        Creates a constraint object that can be used to validate a value against a set of constraints.
105
106        Optional Arguments:
107
108        - `pattern`:
109            - What: A regex pattern that the value must match.
110            - Type: str or None
111            - Default: None
112        - `includes`:
113            - What: A list of values that the value must be in.
114            - Type: list, tuple, set or None
115            - Default: None
116        - `excludes`:
117            - What: A list of values that the value must not be in.
118            - Type: list, tuple, set or None
119            - Default: None
120        - `gt`:
121            - What: The value must be greater than this value.
122            - Type: float, int or None
123            - Default: None
124        - `lt`:
125            - What: The value must be less than this value.
126            - Type: float, int or None
127            - Default: None
128        - `ge`:
129            - What: The value must be greater than or equal to this value.
130            - Type: float, int or None
131            - Default: None
132        - `le`:
133            - What: The value must be less than or equal to this value.
134            - Type: float, int or None
135            - Default: None
136        - `eq`:
137            - What: The value must be equal to this value.
138            - Type: float, int or None
139            - Default: None
140        - `ne`:
141            - What: The value must not be equal to this value.
142            - Type: float, int or None
143            - Default: None
144        """
145        assert any(
146            v is not None
147            for v in [pattern, gt, lt, ge, le, eq, ne, includes, excludes]
148        ), "At least one constraint must be provided."
149        assert isinstance(
150            includes, (list, tuple, set, type(None))
151        ), "Includes must be a list, tuple, set or None."
152        assert isinstance(
153            excludes, (list, tuple, set, type(None))
154        ), "Excludes must be a list, tuple, set or None."
155        assert isinstance(
156            pattern, (str, type(None))
157        ), "Pattern must be a string or None."
158        assert isinstance(
159            gt, (float, int, type(None))
160        ), "Greater than constraint must be a float, int or None."
161        assert isinstance(
162            lt, (float, int, type(None))
163        ), "Less than constraint must be a float, int or None."
164        assert isinstance(
165            ge, (float, int, type(None))
166        ), "Greater or equal to constraint must be a float, int or None."
167        assert isinstance(
168            le, (float, int, type(None))
169        ), "Less than or equal to constraint must be a float, int or None."
170        assert isinstance(
171            eq, (float, int, type(None))
172        ), "Equal to constraint must be a float, int or None."
173        assert isinstance(
174            ne, (float, int, type(None))
175        ), "Not equal to constraint must be a float, int or None."
176        self.__constraint_checks__ = {}
177        if pattern is not None:
178            self.__constraint_checks__["be a string"] = lambda x: isinstance(
179                x, str
180            )
181            self.__constraint_checks__["Regex Pattern Match"] = lambda x: bool(
182                re.findall(pattern, x)
183            )
184        if includes is not None:
185            self.__constraint_checks__[f"Includes"] = lambda x: x in includes
186        if excludes is not None:
187            self.__constraint_checks__["Excludes"] = lambda x: x not in excludes
188        if gt is not None:
189            self.__constraint_checks__[f"Greater Than ({gt})"] = (
190                lambda x: x > gt
191            )
192        if lt is not None:
193            self.__constraint_checks__[f"Less Than ({lt})"] = lambda x: x < lt
194        if ge is not None:
195            self.__constraint_checks__[f"Greater Than Or Equal To ({ge})"] = (
196                lambda x: x >= ge
197            )
198        if le is not None:
199            self.__constraint_checks__[f"Less Than Or Equal To ({le})"] = (
200                lambda x: x <= le
201            )
202        if eq is not None:
203            self.__constraint_checks__[f"Equal To ({eq})"] = lambda x: x == eq
204        if ne is not None:
205            self.__constraint_checks__[f"Not Equal To ({ne})"] = (
206                lambda x: x != ne
207            )

Creates a constraint object that can be used to validate a value against a set of constraints.

Optional Arguments:

  • pattern:
    • What: A regex pattern that the value must match.
    • Type: str or None
    • Default: None
  • includes:
    • What: A list of values that the value must be in.
    • Type: list, tuple, set or None
    • Default: None
  • excludes:
    • What: A list of values that the value must not be in.
    • Type: list, tuple, set or None
    • Default: None
  • gt:
    • What: The value must be greater than this value.
    • Type: float, int or None
    • Default: None
  • lt:
    • What: The value must be less than this value.
    • Type: float, int or None
    • Default: None
  • ge:
    • What: The value must be greater than or equal to this value.
    • Type: float, int or None
    • Default: None
  • le:
    • What: The value must be less than or equal to this value.
    • Type: float, int or None
    • Default: None
  • eq:
    • What: The value must be equal to this value.
    • Type: float, int or None
    • Default: None
  • ne:
    • What: The value must not be equal to this value.
    • Type: float, int or None
    • Default: None
class WithSubclasses:
210class WithSubclasses:
211    def __init__(self, obj, cache=True):
212        """
213        A special helper class to allow a class type to be passed and also allow all subclasses of that type.
214
215        This allows delayed evaluation of the subclasses until initial call time such that delayed inherited classes are considered.
216
217        Note: You can not validate that a passed object is a WithSubclasses object as this is a special case.
218
219        Required Arguments:
220
221        - `obj`:
222            - What: An uninitialized class that should also be considered type correct if a subclass is passed.
223            - Type: Any Uninitialized class
224
225        Optional Arguments:
226
227        - `cache`:
228            - What: Whether to cache the calcuation of the subclasses. If set to False, this will recalculate the subclasses on each call.
229            - Type: bool
230            - Default: True
231        """
232        self.obj = obj
233        self.cache = cache
234
235    @classmethod
236    def __get_subclasses__(cls, obj):
237        """
238        A special helper method to get all of the subclasses of a provided class object.
239
240        Requires:
241
242        - `obj`:
243            - What: An uninitialized class that should also be considered type correct if a subclass is passed.
244            - Type: Any Uninitialized class
245
246        Returns:
247
248        - `out`:
249            - What: A list of all of the subclasses (recursively parsed)
250            - Type: list of strs
251
252
253        Notes:
254
255        - From a functional perspective, this recursively gets the subclasses for an uninitialised class (type).
256        """
257        out = [obj]
258        for i in obj.__subclasses__():
259            out += cls.__get_subclasses__(i)
260        return out
261
262    def get_subclasses(self):
263        """
264        Returns a list of all of the subclasses of the class that was passed to the WithSubclasses object.
265
266        If already calculated, it will return the cached value.
267
268        Returns:
269
270        - `subclasses`:
271            - What: A list of all of the subclasses of the class that was passed to the WithSubclasses object.
272            - Type: list of strs
273        """
274        if not hasattr(self, "subclasses") or self.cache == False:
275            self.subclasses = self.__get_subclasses__(self.obj)
276        return self.subclasses
WithSubclasses(obj, cache=True)
211    def __init__(self, obj, cache=True):
212        """
213        A special helper class to allow a class type to be passed and also allow all subclasses of that type.
214
215        This allows delayed evaluation of the subclasses until initial call time such that delayed inherited classes are considered.
216
217        Note: You can not validate that a passed object is a WithSubclasses object as this is a special case.
218
219        Required Arguments:
220
221        - `obj`:
222            - What: An uninitialized class that should also be considered type correct if a subclass is passed.
223            - Type: Any Uninitialized class
224
225        Optional Arguments:
226
227        - `cache`:
228            - What: Whether to cache the calcuation of the subclasses. If set to False, this will recalculate the subclasses on each call.
229            - Type: bool
230            - Default: True
231        """
232        self.obj = obj
233        self.cache = cache

A special helper class to allow a class type to be passed and also allow all subclasses of that type.

This allows delayed evaluation of the subclasses until initial call time such that delayed inherited classes are considered.

Note: You can not validate that a passed object is a WithSubclasses object as this is a special case.

Required Arguments:

  • obj:
    • What: An uninitialized class that should also be considered type correct if a subclass is passed.
    • Type: Any Uninitialized class

Optional Arguments:

  • cache:
    • What: Whether to cache the calcuation of the subclasses. If set to False, this will recalculate the subclasses on each call.
    • Type: bool
    • Default: True
obj
cache
def get_subclasses(self):
262    def get_subclasses(self):
263        """
264        Returns a list of all of the subclasses of the class that was passed to the WithSubclasses object.
265
266        If already calculated, it will return the cached value.
267
268        Returns:
269
270        - `subclasses`:
271            - What: A list of all of the subclasses of the class that was passed to the WithSubclasses object.
272            - Type: list of strs
273        """
274        if not hasattr(self, "subclasses") or self.cache == False:
275            self.subclasses = self.__get_subclasses__(self.obj)
276        return self.subclasses

Returns a list of all of the subclasses of the class that was passed to the WithSubclasses object.

If already calculated, it will return the cached value.

Returns:

  • subclasses:
    • What: A list of all of the subclasses of the class that was passed to the WithSubclasses object.
    • Type: list of strs
def DeepMerge(original: dict, update: dict):
279def DeepMerge(original: dict, update: dict):
280    """
281    Merge two dictionaries together, recursively merging any nested dictionaries and extending any nested lists.
282
283    Required Arguments:
284
285    - `original`:
286        - What: The original dictionary to merge the update into.
287        - Type: dict
288    - `update`:
289        - What: The dictionary to merge into the original dictionary.
290        - Type: dict
291    """
292    original = copy.deepcopy(original)
293    for key, value in update.items():
294        if (
295            key in original
296            and isinstance(original[key], dict)
297            and isinstance(value, dict)
298        ):
299            DeepMerge(original[key], value)
300        elif (
301            key in original
302            and isinstance(original[key], list)
303            and isinstance(value, list)
304        ):
305            original[key].extend(value)
306        else:
307            original[key] = value
308    return original

Merge two dictionaries together, recursively merging any nested dictionaries and extending any nested lists.

Required Arguments:

  • original:
    • What: The original dictionary to merge the update into.
    • Type: dict
  • update:
    • What: The dictionary to merge into the original dictionary.
    • Type: dict