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.
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.
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'>}