type_enforced.utils

  1import types, re, copy
  2from functools import update_wrapper
  3from typing import Union
  4
  5
  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 __get__(self, instance, owner):
 46        def bind(*args, **kwargs):
 47            if instance is not None and self.__arity__ == self.__fnArity__:
 48                return self.__call__(instance, *args, **kwargs)
 49            else:
 50                return self.__call__(*args, **kwargs)
 51
 52        return bind
 53
 54    def __repr__(self):
 55        return f"<Partial {self.__fn__.__module__}.{self.__fn__.__qualname__} object at {hex(id(self))}>"
 56
 57    def __getArity__(self, args, kwargs):
 58        return self.__fnArity__ - (len(args) + len(kwargs))
 59
 60    def __getFnArity__(self):
 61        if not isinstance(self.__fn__, (types.MethodType, types.FunctionType)):
 62            self.__exception__(
 63                "A non function was passed as a function and does not have any arity. See the stack trace above for more information."
 64            )
 65        extra_method_input_count = (
 66            1 if isinstance(self.__fn__, (types.MethodType)) else 0
 67        )
 68        return self.__fn__.__code__.co_argcount - extra_method_input_count
 69
 70
 71class GenericConstraint:
 72    def __init__(self, constraints: dict):
 73        """
 74        Creates a generic constraint object that can be used to validate a value against a set of constraints.
 75
 76        Required Arguments:
 77
 78        - `constraints`:
 79            - What: A dictionary of constraint names and their associated functions.
 80            - Type: dict
 81            - Note: All values in the dictionary must be functions that take a single argument and return a boolean.
 82            - Note: The dictionary keys will be used to identify the failed constraints in the error messages.
 83        """
 84        assert all(
 85            hasattr(v, "__call__") for v in constraints.values()
 86        ), "All constraints must be functions."
 87        self.__constraint_checks__ = constraints
 88
 89    def __validate__(self, varname, value):
 90        for check_name, check_func in self.__constraint_checks__.items():
 91            try:
 92                if not check_func(value):
 93                    return f"Constraint `{check_name}` not met with the provided value `{value}`"
 94            except Exception as e:
 95                return f"An exception was raised when checking the constraint `{check_name}` with the provided value `{value}`. Error: {e}"
 96        return True
 97
 98    def __ror__(self, other):
 99        """
100        Allows the use of | operator to combine GenericConstraints via a union.
101        """
102        return Union[other, self]
103
104
105class Constraint(GenericConstraint):
106    def __init__(
107        self,
108        pattern: str | None = None,
109        includes: list | tuple | set | None = None,
110        excludes: list | tuple | set | None = None,
111        gt: float | int | None = None,
112        lt: float | int | None = None,
113        ge: float | int | None = None,
114        le: float | int | None = None,
115        eq: float | int | None = None,
116        ne: float | int | None = None,
117    ):
118        """
119        Creates a constraint object that can be used to validate a value against a set of constraints.
120
121        Optional Arguments:
122
123        - `pattern`:
124            - What: A regex pattern that the value must match.
125            - Type: str or None
126            - Default: None
127        - `includes`:
128            - What: A list of values that the value must be in.
129            - Type: list, tuple, set or None
130            - Default: None
131        - `excludes`:
132            - What: A list of values that the value must not be in.
133            - Type: list, tuple, set or None
134            - Default: None
135        - `gt`:
136            - What: The value must be greater than this value.
137            - Type: float, int or None
138            - Default: None
139        - `lt`:
140            - What: The value must be less than this value.
141            - Type: float, int or None
142            - Default: None
143        - `ge`:
144            - What: The value must be greater than or equal to this value.
145            - Type: float, int or None
146            - Default: None
147        - `le`:
148            - What: The value must be less than or equal to this value.
149            - Type: float, int or None
150            - Default: None
151        - `eq`:
152            - What: The value must be equal to this value.
153            - Type: float, int or None
154            - Default: None
155        - `ne`:
156            - What: The value must not be equal to this value.
157            - Type: float, int or None
158            - Default: None
159        """
160        assert any(
161            v is not None
162            for v in [pattern, gt, lt, ge, le, eq, ne, includes, excludes]
163        ), "At least one constraint must be provided."
164        assert isinstance(
165            includes, (list, tuple, set, type(None))
166        ), "Includes must be a list, tuple, set or None."
167        assert isinstance(
168            excludes, (list, tuple, set, type(None))
169        ), "Excludes must be a list, tuple, set or None."
170        assert isinstance(
171            pattern, (str, type(None))
172        ), "Pattern must be a string or None."
173        assert isinstance(
174            gt, (float, int, type(None))
175        ), "Greater than constraint must be a float, int or None."
176        assert isinstance(
177            lt, (float, int, type(None))
178        ), "Less than constraint must be a float, int or None."
179        assert isinstance(
180            ge, (float, int, type(None))
181        ), "Greater or equal to constraint must be a float, int or None."
182        assert isinstance(
183            le, (float, int, type(None))
184        ), "Less than or equal to constraint must be a float, int or None."
185        assert isinstance(
186            eq, (float, int, type(None))
187        ), "Equal to constraint must be a float, int or None."
188        assert isinstance(
189            ne, (float, int, type(None))
190        ), "Not equal to constraint must be a float, int or None."
191        self.__constraint_checks__ = {}
192        if pattern is not None:
193            self.__constraint_checks__["be a string"] = lambda x: isinstance(
194                x, str
195            )
196            self.__constraint_checks__["Regex Pattern Match"] = lambda x: bool(
197                re.findall(pattern, x)
198            )
199        if includes is not None:
200            self.__constraint_checks__[f"Includes"] = lambda x: x in includes
201        if excludes is not None:
202            self.__constraint_checks__["Excludes"] = lambda x: x not in excludes
203        if gt is not None:
204            self.__constraint_checks__[f"Greater Than ({gt})"] = (
205                lambda x: x > gt
206            )
207        if lt is not None:
208            self.__constraint_checks__[f"Less Than ({lt})"] = lambda x: x < lt
209        if ge is not None:
210            self.__constraint_checks__[f"Greater Than Or Equal To ({ge})"] = (
211                lambda x: x >= ge
212            )
213        if le is not None:
214            self.__constraint_checks__[f"Less Than Or Equal To ({le})"] = (
215                lambda x: x <= le
216            )
217        if eq is not None:
218            self.__constraint_checks__[f"Equal To ({eq})"] = lambda x: x == eq
219        if ne is not None:
220            self.__constraint_checks__[f"Not Equal To ({ne})"] = (
221                lambda x: x != ne
222            )
223
224
225def DeepMerge(original: dict, update: dict):
226    """
227    Merge two dictionaries together, recursively merging any nested dictionaries and extending any nested lists.
228
229    Required Arguments:
230
231    - `original`:
232        - What: The original dictionary to merge the update into.
233        - Type: dict
234    - `update`:
235        - What: The dictionary to merge into the original dictionary.
236        - Type: dict
237    """
238    original = copy.deepcopy(original)
239    for key, value in update.items():
240        if (
241            key in original
242            and isinstance(original[key], dict)
243            and isinstance(value, dict)
244        ):
245            original[key] = DeepMerge(original[key], value)
246        elif (
247            key in original
248            and isinstance(original[key], list)
249            and isinstance(value, list)
250        ):
251            original[key].extend(value)
252        else:
253            original[key] = value
254    return original
255
256
257def WithSubclasses(cls):
258    """
259    A utility class to preserve backwards compatibility
260    with the older versions of type_enforced.
261
262    By default subclasses of all classes are now enforced by type_enforced.
263
264    It is slated to be removed in the next major version.
265    """
266    # TODO: Remove this in the next major version of type_enforced
267    return cls
268
269
270iterable_types = set([list, tuple, set, dict])
class Partial:
 7class Partial:
 8    """
 9    A special class wrapper to allow for easy partial function wrappings and calls.
10    """
11
12    def __init__(
13        self,
14        __fn__,
15        *__args__,
16        **__kwargs__,
17    ):
18        update_wrapper(self, __fn__)
19        self.__fn__ = __fn__
20        self.__args__ = __args__
21        self.__kwargs__ = __kwargs__
22        self.__fnArity__ = self.__getFnArity__()
23        self.__arity__ = self.__getArity__(__args__, __kwargs__)
24
25    def __exception__(self, message):
26        pre_message = (
27            f"({self.__fn__.__module__}.{self.__fn__.__qualname__}_partial): "
28        )
29        raise Exception(pre_message + message)
30
31    def __call__(self, *args, **kwargs):
32        new_args = self.__args__ + args
33        new_kwargs = {**self.__kwargs__, **kwargs}
34        self.__arity__ = self.__getArity__(new_args, new_kwargs)
35        if self.__arity__ < 0:
36            self.__exception__("Too many arguments were supplied")
37        if self.__arity__ == 0:
38            results = self.__fn__(*new_args, **new_kwargs)
39            return results
40        return Partial(
41            self.__fn__,
42            *new_args,
43            **new_kwargs,
44        )
45
46    def __get__(self, instance, owner):
47        def bind(*args, **kwargs):
48            if instance is not None and self.__arity__ == self.__fnArity__:
49                return self.__call__(instance, *args, **kwargs)
50            else:
51                return self.__call__(*args, **kwargs)
52
53        return bind
54
55    def __repr__(self):
56        return f"<Partial {self.__fn__.__module__}.{self.__fn__.__qualname__} object at {hex(id(self))}>"
57
58    def __getArity__(self, args, kwargs):
59        return self.__fnArity__ - (len(args) + len(kwargs))
60
61    def __getFnArity__(self):
62        if not isinstance(self.__fn__, (types.MethodType, types.FunctionType)):
63            self.__exception__(
64                "A non function was passed as a function and does not have any arity. See the stack trace above for more information."
65            )
66        extra_method_input_count = (
67            1 if isinstance(self.__fn__, (types.MethodType)) else 0
68        )
69        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__)
12    def __init__(
13        self,
14        __fn__,
15        *__args__,
16        **__kwargs__,
17    ):
18        update_wrapper(self, __fn__)
19        self.__fn__ = __fn__
20        self.__args__ = __args__
21        self.__kwargs__ = __kwargs__
22        self.__fnArity__ = self.__getFnArity__()
23        self.__arity__ = self.__getArity__(__args__, __kwargs__)
class GenericConstraint:
 72class GenericConstraint:
 73    def __init__(self, constraints: dict):
 74        """
 75        Creates a generic constraint object that can be used to validate a value against a set of constraints.
 76
 77        Required Arguments:
 78
 79        - `constraints`:
 80            - What: A dictionary of constraint names and their associated functions.
 81            - Type: dict
 82            - Note: All values in the dictionary must be functions that take a single argument and return a boolean.
 83            - Note: The dictionary keys will be used to identify the failed constraints in the error messages.
 84        """
 85        assert all(
 86            hasattr(v, "__call__") for v in constraints.values()
 87        ), "All constraints must be functions."
 88        self.__constraint_checks__ = constraints
 89
 90    def __validate__(self, varname, value):
 91        for check_name, check_func in self.__constraint_checks__.items():
 92            try:
 93                if not check_func(value):
 94                    return f"Constraint `{check_name}` not met with the provided value `{value}`"
 95            except Exception as e:
 96                return f"An exception was raised when checking the constraint `{check_name}` with the provided value `{value}`. Error: {e}"
 97        return True
 98
 99    def __ror__(self, other):
100        """
101        Allows the use of | operator to combine GenericConstraints via a union.
102        """
103        return Union[other, self]
GenericConstraint(constraints: dict)
73    def __init__(self, constraints: dict):
74        """
75        Creates a generic constraint object that can be used to validate a value against a set of constraints.
76
77        Required Arguments:
78
79        - `constraints`:
80            - What: A dictionary of constraint names and their associated functions.
81            - Type: dict
82            - Note: All values in the dictionary must be functions that take a single argument and return a boolean.
83            - Note: The dictionary keys will be used to identify the failed constraints in the error messages.
84        """
85        assert all(
86            hasattr(v, "__call__") for v in constraints.values()
87        ), "All constraints must be functions."
88        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):
106class Constraint(GenericConstraint):
107    def __init__(
108        self,
109        pattern: str | None = None,
110        includes: list | tuple | set | None = None,
111        excludes: list | tuple | set | None = None,
112        gt: float | int | None = None,
113        lt: float | int | None = None,
114        ge: float | int | None = None,
115        le: float | int | None = None,
116        eq: float | int | None = None,
117        ne: float | int | None = None,
118    ):
119        """
120        Creates a constraint object that can be used to validate a value against a set of constraints.
121
122        Optional Arguments:
123
124        - `pattern`:
125            - What: A regex pattern that the value must match.
126            - Type: str or None
127            - Default: None
128        - `includes`:
129            - What: A list of values that the value must be in.
130            - Type: list, tuple, set or None
131            - Default: None
132        - `excludes`:
133            - What: A list of values that the value must not be in.
134            - Type: list, tuple, set or None
135            - Default: None
136        - `gt`:
137            - What: The value must be greater than this value.
138            - Type: float, int or None
139            - Default: None
140        - `lt`:
141            - What: The value must be less than this value.
142            - Type: float, int or None
143            - Default: None
144        - `ge`:
145            - What: The value must be greater than or equal to this value.
146            - Type: float, int or None
147            - Default: None
148        - `le`:
149            - What: The value must be less than or equal to this value.
150            - Type: float, int or None
151            - Default: None
152        - `eq`:
153            - What: The value must be equal to this value.
154            - Type: float, int or None
155            - Default: None
156        - `ne`:
157            - What: The value must not be equal to this value.
158            - Type: float, int or None
159            - Default: None
160        """
161        assert any(
162            v is not None
163            for v in [pattern, gt, lt, ge, le, eq, ne, includes, excludes]
164        ), "At least one constraint must be provided."
165        assert isinstance(
166            includes, (list, tuple, set, type(None))
167        ), "Includes must be a list, tuple, set or None."
168        assert isinstance(
169            excludes, (list, tuple, set, type(None))
170        ), "Excludes must be a list, tuple, set or None."
171        assert isinstance(
172            pattern, (str, type(None))
173        ), "Pattern must be a string or None."
174        assert isinstance(
175            gt, (float, int, type(None))
176        ), "Greater than constraint must be a float, int or None."
177        assert isinstance(
178            lt, (float, int, type(None))
179        ), "Less than constraint must be a float, int or None."
180        assert isinstance(
181            ge, (float, int, type(None))
182        ), "Greater or equal to constraint must be a float, int or None."
183        assert isinstance(
184            le, (float, int, type(None))
185        ), "Less than or equal to constraint must be a float, int or None."
186        assert isinstance(
187            eq, (float, int, type(None))
188        ), "Equal to constraint must be a float, int or None."
189        assert isinstance(
190            ne, (float, int, type(None))
191        ), "Not equal to constraint must be a float, int or None."
192        self.__constraint_checks__ = {}
193        if pattern is not None:
194            self.__constraint_checks__["be a string"] = lambda x: isinstance(
195                x, str
196            )
197            self.__constraint_checks__["Regex Pattern Match"] = lambda x: bool(
198                re.findall(pattern, x)
199            )
200        if includes is not None:
201            self.__constraint_checks__[f"Includes"] = lambda x: x in includes
202        if excludes is not None:
203            self.__constraint_checks__["Excludes"] = lambda x: x not in excludes
204        if gt is not None:
205            self.__constraint_checks__[f"Greater Than ({gt})"] = (
206                lambda x: x > gt
207            )
208        if lt is not None:
209            self.__constraint_checks__[f"Less Than ({lt})"] = lambda x: x < lt
210        if ge is not None:
211            self.__constraint_checks__[f"Greater Than Or Equal To ({ge})"] = (
212                lambda x: x >= ge
213            )
214        if le is not None:
215            self.__constraint_checks__[f"Less Than Or Equal To ({le})"] = (
216                lambda x: x <= le
217            )
218        if eq is not None:
219            self.__constraint_checks__[f"Equal To ({eq})"] = lambda x: x == eq
220        if ne is not None:
221            self.__constraint_checks__[f"Not Equal To ({ne})"] = (
222                lambda x: x != ne
223            )
Constraint( pattern: str | None = None, includes: list | tuple | set | None = None, excludes: list | tuple | set | None = None, gt: float | int | None = None, lt: float | int | None = None, ge: float | int | None = None, le: float | int | None = None, eq: float | int | None = None, ne: float | int | None = None)
107    def __init__(
108        self,
109        pattern: str | None = None,
110        includes: list | tuple | set | None = None,
111        excludes: list | tuple | set | None = None,
112        gt: float | int | None = None,
113        lt: float | int | None = None,
114        ge: float | int | None = None,
115        le: float | int | None = None,
116        eq: float | int | None = None,
117        ne: float | int | None = None,
118    ):
119        """
120        Creates a constraint object that can be used to validate a value against a set of constraints.
121
122        Optional Arguments:
123
124        - `pattern`:
125            - What: A regex pattern that the value must match.
126            - Type: str or None
127            - Default: None
128        - `includes`:
129            - What: A list of values that the value must be in.
130            - Type: list, tuple, set or None
131            - Default: None
132        - `excludes`:
133            - What: A list of values that the value must not be in.
134            - Type: list, tuple, set or None
135            - Default: None
136        - `gt`:
137            - What: The value must be greater than this value.
138            - Type: float, int or None
139            - Default: None
140        - `lt`:
141            - What: The value must be less than this value.
142            - Type: float, int or None
143            - Default: None
144        - `ge`:
145            - What: The value must be greater than or equal to this value.
146            - Type: float, int or None
147            - Default: None
148        - `le`:
149            - What: The value must be less than or equal to this value.
150            - Type: float, int or None
151            - Default: None
152        - `eq`:
153            - What: The value must be equal to this value.
154            - Type: float, int or None
155            - Default: None
156        - `ne`:
157            - What: The value must not be equal to this value.
158            - Type: float, int or None
159            - Default: None
160        """
161        assert any(
162            v is not None
163            for v in [pattern, gt, lt, ge, le, eq, ne, includes, excludes]
164        ), "At least one constraint must be provided."
165        assert isinstance(
166            includes, (list, tuple, set, type(None))
167        ), "Includes must be a list, tuple, set or None."
168        assert isinstance(
169            excludes, (list, tuple, set, type(None))
170        ), "Excludes must be a list, tuple, set or None."
171        assert isinstance(
172            pattern, (str, type(None))
173        ), "Pattern must be a string or None."
174        assert isinstance(
175            gt, (float, int, type(None))
176        ), "Greater than constraint must be a float, int or None."
177        assert isinstance(
178            lt, (float, int, type(None))
179        ), "Less than constraint must be a float, int or None."
180        assert isinstance(
181            ge, (float, int, type(None))
182        ), "Greater or equal to constraint must be a float, int or None."
183        assert isinstance(
184            le, (float, int, type(None))
185        ), "Less than or equal to constraint must be a float, int or None."
186        assert isinstance(
187            eq, (float, int, type(None))
188        ), "Equal to constraint must be a float, int or None."
189        assert isinstance(
190            ne, (float, int, type(None))
191        ), "Not equal to constraint must be a float, int or None."
192        self.__constraint_checks__ = {}
193        if pattern is not None:
194            self.__constraint_checks__["be a string"] = lambda x: isinstance(
195                x, str
196            )
197            self.__constraint_checks__["Regex Pattern Match"] = lambda x: bool(
198                re.findall(pattern, x)
199            )
200        if includes is not None:
201            self.__constraint_checks__[f"Includes"] = lambda x: x in includes
202        if excludes is not None:
203            self.__constraint_checks__["Excludes"] = lambda x: x not in excludes
204        if gt is not None:
205            self.__constraint_checks__[f"Greater Than ({gt})"] = (
206                lambda x: x > gt
207            )
208        if lt is not None:
209            self.__constraint_checks__[f"Less Than ({lt})"] = lambda x: x < lt
210        if ge is not None:
211            self.__constraint_checks__[f"Greater Than Or Equal To ({ge})"] = (
212                lambda x: x >= ge
213            )
214        if le is not None:
215            self.__constraint_checks__[f"Less Than Or Equal To ({le})"] = (
216                lambda x: x <= le
217            )
218        if eq is not None:
219            self.__constraint_checks__[f"Equal To ({eq})"] = lambda x: x == eq
220        if ne is not None:
221            self.__constraint_checks__[f"Not Equal To ({ne})"] = (
222                lambda x: x != ne
223            )

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
def DeepMerge(original: dict, update: dict):
226def DeepMerge(original: dict, update: dict):
227    """
228    Merge two dictionaries together, recursively merging any nested dictionaries and extending any nested lists.
229
230    Required Arguments:
231
232    - `original`:
233        - What: The original dictionary to merge the update into.
234        - Type: dict
235    - `update`:
236        - What: The dictionary to merge into the original dictionary.
237        - Type: dict
238    """
239    original = copy.deepcopy(original)
240    for key, value in update.items():
241        if (
242            key in original
243            and isinstance(original[key], dict)
244            and isinstance(value, dict)
245        ):
246            original[key] = DeepMerge(original[key], value)
247        elif (
248            key in original
249            and isinstance(original[key], list)
250            and isinstance(value, list)
251        ):
252            original[key].extend(value)
253        else:
254            original[key] = value
255    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
def WithSubclasses(cls):
258def WithSubclasses(cls):
259    """
260    A utility class to preserve backwards compatibility
261    with the older versions of type_enforced.
262
263    By default subclasses of all classes are now enforced by type_enforced.
264
265    It is slated to be removed in the next major version.
266    """
267    # TODO: Remove this in the next major version of type_enforced
268    return cls

A utility class to preserve backwards compatibility with the older versions of type_enforced.

By default subclasses of all classes are now enforced by type_enforced.

It is slated to be removed in the next major version.

iterable_types = {<class 'dict'>, <class 'list'>, <class 'set'>, <class 'tuple'>}