batutils 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- batutils/__init__.py +5 -0
- batutils/_common/__init__.py +6 -0
- batutils/_common/_decode.py +40 -0
- batutils/dtu/__init__.py +19 -0
- batutils/dtu/casting/__init__.py +5 -0
- batutils/dtu/casting/_any.py +136 -0
- batutils/dtu/casting/_filetime.py +36 -0
- batutils/dtu/casting/_instant.py +31 -0
- batutils/dtu/casting/_string.py +81 -0
- batutils/dtu/casting/_timestamp.py +50 -0
- batutils/dtu/formatting/__init__.py +2 -0
- batutils/dtu/formatting/_delta.py +62 -0
- batutils/dtu/formatting/_serialize.py +26 -0
- batutils/dtu/parsers/__init__.py +2 -0
- batutils/dtu/parsers/_string.py +44 -0
- batutils/dtu/parsers/_timedelta.py +161 -0
- batutils/dtu/shortcuts/__init__.py +3 -0
- batutils/dtu/shortcuts/_aliases.py +29 -0
- batutils/dtu/shortcuts/_checkers.py +21 -0
- batutils/dtu/shortcuts/_finders.py +80 -0
- batutils/dtu/structs/__init__.py +5 -0
- batutils/dtu/structs/_base.py +5 -0
- batutils/dtu/structs/_deltasplit.py +278 -0
- batutils/dtu/structs/_types.py +24 -0
- batutils/dtu/structs/aliases.py +21 -0
- batutils/dtu/structs/consts.py +35 -0
- batutils/structs/__init__.py +7 -0
- batutils/structs/_base.py +21 -0
- batutils/tpu/__init__.py +0 -0
- batutils/tpu/casting/__init__.py +9 -0
- batutils/tpu/casting/_currency.py +52 -0
- batutils/tpu/casting/_evals.py +247 -0
- batutils/tpu/casting/_ip.py +58 -0
- batutils/tpu/casting/_mask.py +34 -0
- batutils/tpu/casting/_params.py +108 -0
- batutils/tpu/casting/_serialize.py +20 -0
- batutils/tpu/comparison/__init__.py +1 -0
- batutils/tpu/comparison/_mixin.py +30 -0
- batutils/tpu/insp/__init__.py +2 -0
- batutils/tpu/insp/_insp_old.py +347 -0
- batutils/tpu/insp/_typecheck.py +89 -0
- batutils/tpu/iters/__init__.py +4 -0
- batutils/tpu/iters/_attrs.py +84 -0
- batutils/tpu/iters/_dicts.py +130 -0
- batutils/tpu/iters/_group.py +50 -0
- batutils/tpu/iters/_lists.py +178 -0
- batutils/tpu/size/__init__.py +1 -0
- batutils/tpu/size/_total.py +52 -0
- batutils/tpu/strimp/__init__.py +1 -0
- batutils/tpu/strimp/_getters.py +285 -0
- batutils/tpu/string/__init__.py +1 -0
- batutils/tpu/string/_string.py +160 -0
- batutils/tpu/structs/__init__.py +1 -0
- batutils/tpu/structs/_base.py +3 -0
- batutils/tpu/structs/empty/__init__.py +1 -0
- batutils/tpu/structs/empty/_empty.py +387 -0
- batutils/tpu/structs/enumex/__init__.py +1 -0
- batutils/tpu/structs/enumex/_enumextra.py +25 -0
- batutils/tpu/structs/fluid/__init__.py +1 -0
- batutils/tpu/structs/fluid/_attrfluid.py +77 -0
- batutils/tpu/structs/sentience/__init__.py +1 -0
- batutils/tpu/structs/sentience/_builder.py +87 -0
- batutils/tpu/structs/serializers/__init__.py +3 -0
- batutils/tpu/structs/serializers/_json.py +12 -0
- batutils/tpu/structs/serializers/_jsonsafe.py +69 -0
- batutils/tpu/structs/serializers/_universal.py +318 -0
- batutils/tpu/structs/singleton/__init__.py +1 -0
- batutils/tpu/structs/singleton/_singleton.py +35 -0
- batutils-2.0.0.dist-info/LICENSE +22 -0
- batutils-2.0.0.dist-info/METADATA +125 -0
- batutils-2.0.0.dist-info/RECORD +73 -0
- batutils-2.0.0.dist-info/WHEEL +5 -0
- batutils-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'is_methfunc',
|
|
3
|
+
'is_callable',
|
|
4
|
+
'get_argspec',
|
|
5
|
+
'fits_callable_profile',
|
|
6
|
+
'get_any_name',
|
|
7
|
+
'get_first_attr',
|
|
8
|
+
'resolve_attr',
|
|
9
|
+
'resolve_first',
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
from batutils.tpu.structs import empty
|
|
13
|
+
import inspect
|
|
14
|
+
from typing import *
|
|
15
|
+
|
|
16
|
+
_NOT_SUPPLIED = object()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def is_methfunc(function_or_method):
|
|
20
|
+
"""Checks if the given parameter is a function/method or not.
|
|
21
|
+
|
|
22
|
+
This simply joins python's inspect.isfunction and inspect.ismethod in one
|
|
23
|
+
call.
|
|
24
|
+
|
|
25
|
+
A function is:
|
|
26
|
+
- Anything declared via def on the top level of a module (or inside
|
|
27
|
+
another function or method)
|
|
28
|
+
- Anything created with lambda
|
|
29
|
+
- Any method defined in a class with the @staticmethod decorator
|
|
30
|
+
A method is:
|
|
31
|
+
- Anything declared via def inside a class definition (except those
|
|
32
|
+
that have the @staticmethod decorator)
|
|
33
|
+
|
|
34
|
+
:param function_or_method: The thing to check
|
|
35
|
+
:type function_or_method: function or method or any
|
|
36
|
+
:return: True if the given parameter is a function or a method, false if not
|
|
37
|
+
:rtype: bool
|
|
38
|
+
"""
|
|
39
|
+
return inspect.isfunction(function_or_method) or inspect.ismethod(function_or_method)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def is_callable(something):
|
|
43
|
+
"""Checks if the given parameter is callable or not.
|
|
44
|
+
|
|
45
|
+
Callable means a function, method or object of a class that implements the
|
|
46
|
+
__call__ method and can therefor be called like a function.
|
|
47
|
+
|
|
48
|
+
See is_methfunc() for more details.
|
|
49
|
+
|
|
50
|
+
:param something: The thing to check
|
|
51
|
+
:type something: function or method or object or any
|
|
52
|
+
:return: True if the given parameter is callable, false if not
|
|
53
|
+
:rtype: bool
|
|
54
|
+
"""
|
|
55
|
+
if is_methfunc(something):
|
|
56
|
+
return True
|
|
57
|
+
else:
|
|
58
|
+
call_func = getattr(something, '__call__', None)
|
|
59
|
+
if call_func:
|
|
60
|
+
return is_methfunc(call_func)
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_argspec(somecallable):
|
|
65
|
+
"""Gets the argument specifications of a callable.
|
|
66
|
+
|
|
67
|
+
This simply extends the inspect.getargspec function to also work for
|
|
68
|
+
objects of classes that implement the __call__ method.
|
|
69
|
+
|
|
70
|
+
Returns None if the given parameter isn't callable.
|
|
71
|
+
|
|
72
|
+
:param somecallable: The thing to fetch the argument spec for
|
|
73
|
+
:type somecallable: function or method or object or any
|
|
74
|
+
:return: The argspecs or None if the given parameter wasn't callable
|
|
75
|
+
:rtype: inspect.ArgSpec or collections.namedtuple or None
|
|
76
|
+
"""
|
|
77
|
+
if is_methfunc(somecallable):
|
|
78
|
+
return inspect.getfullargspec(somecallable)
|
|
79
|
+
else:
|
|
80
|
+
call_func = getattr(somecallable, '__call__', None)
|
|
81
|
+
if call_func:
|
|
82
|
+
if is_methfunc(call_func):
|
|
83
|
+
return inspect.getfullargspec(call_func)
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def fits_callable_profile(somecallable, required_arg_count=None,
|
|
88
|
+
total_arg_count=None, default_count=None,
|
|
89
|
+
has_varargs=None, has_kwargs=None, arg_names=None,
|
|
90
|
+
raise_error=False):
|
|
91
|
+
"""Checks if a callable (function, method or object with __call__) fits
|
|
92
|
+
the given argument profile.
|
|
93
|
+
|
|
94
|
+
Returns False if the given parameter to check is not callable.
|
|
95
|
+
|
|
96
|
+
All check parameters default to None which skips that particular check.
|
|
97
|
+
|
|
98
|
+
Required arguments means total number of arguments minus the number of
|
|
99
|
+
arguments with default values.
|
|
100
|
+
|
|
101
|
+
If the arg_names parameter is a list (NOT a tuple), this will only check
|
|
102
|
+
if the argument names there in are present regardless of order.
|
|
103
|
+
|
|
104
|
+
If the arg_names parameter is a tuple (NOT a list), this will check if the
|
|
105
|
+
order pf the argument names there in are present and in the same order
|
|
106
|
+
although you can use '*' in the tuple to skip the check for the argument
|
|
107
|
+
in that place (e.g. if you don't care what the "self" parameter in a bound
|
|
108
|
+
method is called but you need a certain argument set to be present in order
|
|
109
|
+
after that). The arg_names tuple can be shorter than the number of
|
|
110
|
+
arguments in the callable though without that failing (use the argument
|
|
111
|
+
count checks if that is needed).
|
|
112
|
+
|
|
113
|
+
:param somecallable: The thing to check
|
|
114
|
+
:type somecallable: function or method or object or any
|
|
115
|
+
:param required_arg_count: The number of required arguments (if given),
|
|
116
|
+
either the exact number or a 2-tuple with
|
|
117
|
+
the minimum and maximum allowed number.
|
|
118
|
+
:type required_arg_count: int or (int, int) or None
|
|
119
|
+
:param total_arg_count: The total number of arguments (if given), either
|
|
120
|
+
the exact number or a 2-tuple with the minimum
|
|
121
|
+
and maximum allowed number.
|
|
122
|
+
:type total_arg_count: int or (int, int) or None
|
|
123
|
+
:param default_count: The number of arguments with default values (if
|
|
124
|
+
given), either the exact number or a 2-tuple with
|
|
125
|
+
the minimum and maximum allowed number.
|
|
126
|
+
:type default_count: int or (int, int) or None
|
|
127
|
+
:param has_varargs: Should the callable accept a variable number of
|
|
128
|
+
arguments (*args)? If this is a string, then
|
|
129
|
+
the args argument must be called that to pass.
|
|
130
|
+
:type has_varargs: bool or str or None
|
|
131
|
+
:param has_kwargs: Should the callable accept a variable number of
|
|
132
|
+
named arguments (**kwargs)? If this is a string, then
|
|
133
|
+
the kwargs argument must be called that to pass.
|
|
134
|
+
:type has_kwargs: bool or str or None
|
|
135
|
+
:param arg_names: A list of argument names the callable should have or a
|
|
136
|
+
tuple of argument names the callable should have in that
|
|
137
|
+
exact order (using "*" to skip individual name checks).
|
|
138
|
+
:type arg_names: list[str] or tuple[str] or None
|
|
139
|
+
:param raise_error: Should an ValueError be raised if a check fails
|
|
140
|
+
(with the details)?
|
|
141
|
+
:type raise_error: bool
|
|
142
|
+
:return: True if the given callable fits all the given profile checks,
|
|
143
|
+
False if not.
|
|
144
|
+
:rtype: bool
|
|
145
|
+
"""
|
|
146
|
+
# TODO(thordurm@ccpgames.com) 2024-03-20: This could probably use a "modern"
|
|
147
|
+
# overhaul and to include type annotations and such!... I think this was
|
|
148
|
+
# written for Python 2.7 :D
|
|
149
|
+
specs = get_argspec(somecallable)
|
|
150
|
+
if not specs:
|
|
151
|
+
if raise_error:
|
|
152
|
+
raise ValueError('Not a callable!')
|
|
153
|
+
return False
|
|
154
|
+
def_count = 0
|
|
155
|
+
if specs.defaults:
|
|
156
|
+
def_count = len(specs.defaults)
|
|
157
|
+
arg_count = 0
|
|
158
|
+
if specs.args:
|
|
159
|
+
arg_count = len(specs.args)
|
|
160
|
+
req_args = arg_count - def_count
|
|
161
|
+
|
|
162
|
+
if required_arg_count is not None:
|
|
163
|
+
if isinstance(required_arg_count, (list, tuple)):
|
|
164
|
+
mi = required_arg_count[0]
|
|
165
|
+
ma = required_arg_count[1]
|
|
166
|
+
else:
|
|
167
|
+
mi = required_arg_count
|
|
168
|
+
ma = required_arg_count
|
|
169
|
+
if not mi <= req_args <= ma:
|
|
170
|
+
if raise_error:
|
|
171
|
+
raise ValueError('Required arguments check fail: min=%s >= count=%s >= max=%s' % (mi, req_args, ma))
|
|
172
|
+
return False # Required argument count fail! :(
|
|
173
|
+
|
|
174
|
+
if total_arg_count is not None:
|
|
175
|
+
if isinstance(total_arg_count, (list, tuple)):
|
|
176
|
+
mi = total_arg_count[0]
|
|
177
|
+
ma = total_arg_count[1]
|
|
178
|
+
else:
|
|
179
|
+
mi = total_arg_count
|
|
180
|
+
ma = total_arg_count
|
|
181
|
+
if not mi <= arg_count <= ma:
|
|
182
|
+
if raise_error:
|
|
183
|
+
raise ValueError('Arguments count check fail: min=%s >= count=%s >= max=%s' % (mi, arg_count, ma))
|
|
184
|
+
return False # Total argument count fail! :(
|
|
185
|
+
|
|
186
|
+
if default_count is not None:
|
|
187
|
+
if isinstance(default_count, (list, tuple)):
|
|
188
|
+
mi = default_count[0]
|
|
189
|
+
ma = default_count[1]
|
|
190
|
+
else:
|
|
191
|
+
mi = default_count
|
|
192
|
+
ma = default_count
|
|
193
|
+
if not mi <= def_count <= ma:
|
|
194
|
+
if raise_error:
|
|
195
|
+
raise ValueError('Default argument check fail: min=%s >= count=%s >= max=%s' % (mi, def_count, ma))
|
|
196
|
+
return False # Default argument count fail! :(
|
|
197
|
+
|
|
198
|
+
if has_varargs is not None:
|
|
199
|
+
if isinstance(has_varargs, str):
|
|
200
|
+
if has_varargs != specs.varargs:
|
|
201
|
+
if raise_error:
|
|
202
|
+
raise ValueError('Variable arguments name fail: is=%s, should be=%s' % (specs.varargs, has_varargs))
|
|
203
|
+
return False # Has variable arguments fail! :(
|
|
204
|
+
if bool(has_varargs) != bool(specs.varargs):
|
|
205
|
+
if raise_error:
|
|
206
|
+
raise ValueError('Variable arguments fail: is=%s, should be=%s' % (bool(specs.varargs), bool(has_varargs)))
|
|
207
|
+
return False # Has variable arguments fail! :(
|
|
208
|
+
|
|
209
|
+
if has_kwargs is not None:
|
|
210
|
+
kwarg_name = specs.varkw
|
|
211
|
+
if isinstance(has_kwargs, str):
|
|
212
|
+
# if has_kwargs != specs.keywords:
|
|
213
|
+
if has_kwargs != kwarg_name:
|
|
214
|
+
if raise_error:
|
|
215
|
+
raise ValueError('Keyword arguments name fail: is=%s, should be=%s' % (kwarg_name, has_kwargs))
|
|
216
|
+
return False # Has variable keyword arguments fail! :(
|
|
217
|
+
# if bool(has_kwargs) != bool(specs.keywords):
|
|
218
|
+
if bool(has_kwargs) != bool(kwarg_name):
|
|
219
|
+
if raise_error:
|
|
220
|
+
raise ValueError('Keyword arguments fail: is=%s, should be=%s' % (bool(kwarg_name), bool(has_kwargs)))
|
|
221
|
+
return False # Has variable keyword arguments fail! :(
|
|
222
|
+
|
|
223
|
+
if arg_names:
|
|
224
|
+
if not specs.args:
|
|
225
|
+
if raise_error:
|
|
226
|
+
raise ValueError('Named arguments fail: No named arguments found')
|
|
227
|
+
return False # Has argument name fail! :(
|
|
228
|
+
if isinstance(arg_names, list): # Order doesn't matter!
|
|
229
|
+
for arg_name in arg_names:
|
|
230
|
+
if arg_name not in specs.args:
|
|
231
|
+
if raise_error:
|
|
232
|
+
raise ValueError('Named arguments fail: Argument "%s" not found' % arg_name)
|
|
233
|
+
return False # Missing required argument! :(
|
|
234
|
+
elif isinstance(arg_names, tuple):
|
|
235
|
+
for i, arg_name in enumerate(arg_names):
|
|
236
|
+
if specs.args[i] != arg_name and arg_name != '*':
|
|
237
|
+
if raise_error:
|
|
238
|
+
raise ValueError('Named arguments fail: Argument "%s" not found in correct order' % arg_name)
|
|
239
|
+
return False # Missing required argument in order! :(
|
|
240
|
+
else:
|
|
241
|
+
if raise_error:
|
|
242
|
+
raise ValueError('Named arguments fail: arg_names must be a list or tuple')
|
|
243
|
+
return False
|
|
244
|
+
|
|
245
|
+
return True # Looks good! :D
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def get_any_name(anything: Any) -> str:
|
|
249
|
+
if anything is None:
|
|
250
|
+
return 'None'
|
|
251
|
+
|
|
252
|
+
if anything is empty.Empty:
|
|
253
|
+
return f'{get_any_name(empty)}.Empty'
|
|
254
|
+
|
|
255
|
+
if inspect.ismodule(anything):
|
|
256
|
+
return getattr(anything, '__name__', f'?m:{anything!r}?')
|
|
257
|
+
|
|
258
|
+
if isinstance(anything, property):
|
|
259
|
+
anything = anything.fget
|
|
260
|
+
|
|
261
|
+
if inspect.isfunction(anything) or inspect.ismethod(anything):
|
|
262
|
+
module = getattr(anything, '__module__', None)
|
|
263
|
+
if module and module != 'builtins':
|
|
264
|
+
return f"{module}.{getattr(anything, '__qualname__', getattr(anything, '__name__', '?f1:noname?'))}"
|
|
265
|
+
else:
|
|
266
|
+
return f"{getattr(anything, '__qualname__', getattr(anything, '__name__', '?f1:noname?'))}"
|
|
267
|
+
|
|
268
|
+
cls = None
|
|
269
|
+
module = None
|
|
270
|
+
if isinstance(anything, Type): # noqa
|
|
271
|
+
# Got a class/type/metaclass etc.
|
|
272
|
+
cls = anything
|
|
273
|
+
|
|
274
|
+
else:
|
|
275
|
+
cls = getattr(anything, '__class__', None)
|
|
276
|
+
|
|
277
|
+
if not cls:
|
|
278
|
+
return f'?{anything!r}?' # Worth a shot!
|
|
279
|
+
|
|
280
|
+
name = getattr(cls, '__name__', None)
|
|
281
|
+
if not name:
|
|
282
|
+
name = f'??{cls!r}??'
|
|
283
|
+
|
|
284
|
+
module = getattr(cls, '__module__', None)
|
|
285
|
+
if module == 'builtins':
|
|
286
|
+
module = ''
|
|
287
|
+
|
|
288
|
+
if module is None:
|
|
289
|
+
module = '?nomodule?'
|
|
290
|
+
|
|
291
|
+
if module:
|
|
292
|
+
return f'{module}.{name}'
|
|
293
|
+
|
|
294
|
+
else:
|
|
295
|
+
return f'{name}'
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def get_first_attr(obj: Any, list_of_attributes: Union[str, List[str]], default: Any = None,
|
|
299
|
+
skip_nones: bool = False) -> Any:
|
|
300
|
+
"""Gets the first attribute of an object found from the given list of
|
|
301
|
+
attribute names.
|
|
302
|
+
|
|
303
|
+
This is similar to looping through a list using `getattr` although this will
|
|
304
|
+
also work on dicts and dict like mapping objects.
|
|
305
|
+
"""
|
|
306
|
+
from batutils.tpu.iters import any_getter
|
|
307
|
+
for attr_name in list_of_attributes:
|
|
308
|
+
attr = any_getter(obj, attr_name, _NOT_SUPPLIED)
|
|
309
|
+
if attr != _NOT_SUPPLIED:
|
|
310
|
+
if skip_nones and attr is None:
|
|
311
|
+
continue
|
|
312
|
+
else:
|
|
313
|
+
return attr
|
|
314
|
+
|
|
315
|
+
return default
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def resolve_attr(attr: Union[Callable, property, Any]) -> Any:
|
|
319
|
+
"""Resolves an attribute that may be a property or something callable to
|
|
320
|
+
it's post-call value and returns.
|
|
321
|
+
|
|
322
|
+
If the attr given is not a property or a callable entity, it's assumed to
|
|
323
|
+
be a proper value to begin with and is returned unchanged.
|
|
324
|
+
"""
|
|
325
|
+
if isinstance(attr, property):
|
|
326
|
+
attr = attr.fget
|
|
327
|
+
|
|
328
|
+
if callable(attr):
|
|
329
|
+
return attr()
|
|
330
|
+
|
|
331
|
+
return attr
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def resolve_first(obj: Any, list_of_attributes: Union[str, List[str]], default: Any = None,
|
|
335
|
+
skip_nones: bool = False) -> Any:
|
|
336
|
+
"""Resolves the first attribute of an object found from the given list of
|
|
337
|
+
attribute names that may be a property or something callable to it's
|
|
338
|
+
post-call value and returns.
|
|
339
|
+
|
|
340
|
+
Basically just a shortcut for using `get_first_attr` and `resolve_attr` at
|
|
341
|
+
once.
|
|
342
|
+
"""
|
|
343
|
+
attr = get_first_attr(obj, list_of_attributes, _NOT_SUPPLIED, skip_nones=skip_nones)
|
|
344
|
+
if attr == _NOT_SUPPLIED:
|
|
345
|
+
return default
|
|
346
|
+
else:
|
|
347
|
+
return resolve_attr(attr)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'check_type',
|
|
3
|
+
]
|
|
4
|
+
from typing import *
|
|
5
|
+
import typing
|
|
6
|
+
import sys
|
|
7
|
+
import logging
|
|
8
|
+
log = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
_POST_39 = sys.version_info >= (3, 9)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def check_type(val: Any, type_or_annotation: Any) -> bool:
|
|
14
|
+
def _is_special(toa: Any) -> bool:
|
|
15
|
+
if _POST_39:
|
|
16
|
+
if isinstance(toa, typing._SpecialGenericAlias): # noqa
|
|
17
|
+
return True
|
|
18
|
+
else:
|
|
19
|
+
return toa._special # noqa
|
|
20
|
+
|
|
21
|
+
if val is None and type_or_annotation is None:
|
|
22
|
+
return True
|
|
23
|
+
|
|
24
|
+
m = getattr(type_or_annotation, '__module__', None)
|
|
25
|
+
if m == 'typing':
|
|
26
|
+
if type_or_annotation == Any:
|
|
27
|
+
return True
|
|
28
|
+
if isinstance(type_or_annotation, typing._GenericAlias): # noqa
|
|
29
|
+
if _is_special(type_or_annotation): # noqa Not subscripted
|
|
30
|
+
return isinstance(val, type_or_annotation.__origin__)
|
|
31
|
+
|
|
32
|
+
else:
|
|
33
|
+
if type_or_annotation.__origin__ == Union:
|
|
34
|
+
if not type_or_annotation.__args__:
|
|
35
|
+
return True
|
|
36
|
+
if Any in type_or_annotation.__args__:
|
|
37
|
+
return True
|
|
38
|
+
return isinstance(val, type_or_annotation.__args__)
|
|
39
|
+
|
|
40
|
+
elif type_or_annotation.__origin__ in (list, set): # Check list and set
|
|
41
|
+
if not isinstance(val, type_or_annotation.__origin__):
|
|
42
|
+
return False
|
|
43
|
+
if not type_or_annotation.__args__ or Any in type_or_annotation.__args__:
|
|
44
|
+
return True
|
|
45
|
+
for sub_val in val:
|
|
46
|
+
if not check_type(sub_val, Union[type_or_annotation.__args__]):
|
|
47
|
+
return False
|
|
48
|
+
return True # Should be good now! :)
|
|
49
|
+
|
|
50
|
+
elif type_or_annotation.__origin__ == tuple: # Check tuple!
|
|
51
|
+
if not isinstance(val, tuple):
|
|
52
|
+
return False
|
|
53
|
+
if not type_or_annotation.__args__:
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
if type_or_annotation.__args__[-1] is ... and len(type_or_annotation.__args__) == 2:
|
|
57
|
+
for sub_val in val:
|
|
58
|
+
if not check_type(sub_val, Union[type_or_annotation.__args__[0]]):
|
|
59
|
+
return False
|
|
60
|
+
return True # Should be good now! :)
|
|
61
|
+
|
|
62
|
+
if len(type_or_annotation.__args__) != len(val):
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
for i, sub_val in enumerate(val):
|
|
66
|
+
if not check_type(sub_val, Union[type_or_annotation.__args__[i]]):
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
return True # Should be good now! :)
|
|
70
|
+
|
|
71
|
+
elif issubclass(type_or_annotation.__origin__, Mapping):
|
|
72
|
+
if not isinstance(val, type_or_annotation.__origin__):
|
|
73
|
+
return False
|
|
74
|
+
if not type_or_annotation.__args__ or type_or_annotation.__args__ == (Any, Any):
|
|
75
|
+
return True
|
|
76
|
+
|
|
77
|
+
for k, v in val.items():
|
|
78
|
+
if not (check_type(k, Union[type_or_annotation.__args__[0]]) and check_type(v, Union[type_or_annotation.__args__[1]])):
|
|
79
|
+
return False
|
|
80
|
+
return True
|
|
81
|
+
else:
|
|
82
|
+
log.warning('I do not know how to check type %r of typing module:(', type_or_annotation)
|
|
83
|
+
|
|
84
|
+
if isinstance(type_or_annotation, TypeVar):
|
|
85
|
+
# TODO(thordurm@ccpgames.com>): Not really tested and supported yet officially...
|
|
86
|
+
# TODO(thordurm@ccpgames.com>): Main issue is nested types (if they don't flatten)
|
|
87
|
+
return check_type(val, Union[type_or_annotation.__constraints__])
|
|
88
|
+
|
|
89
|
+
return isinstance(val, type_or_annotation)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'any_getter',
|
|
3
|
+
'any_setter',
|
|
4
|
+
'nested_get',
|
|
5
|
+
'nested_set',
|
|
6
|
+
]
|
|
7
|
+
from batutils.tpu.structs import *
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def any_getter(obj: Any, key: Union[str, int], default: Any = None, eval_call: bool = False) -> Optional[Any]:
|
|
11
|
+
if isinstance(key, int):
|
|
12
|
+
try:
|
|
13
|
+
val = obj[key]
|
|
14
|
+
except (IndexError, KeyError):
|
|
15
|
+
return default
|
|
16
|
+
else:
|
|
17
|
+
if isinstance(obj, Mapping):
|
|
18
|
+
val = obj.get(key, default)
|
|
19
|
+
else:
|
|
20
|
+
val = getattr(obj, key, default)
|
|
21
|
+
if eval_call:
|
|
22
|
+
if val and isinstance(val, Callable):
|
|
23
|
+
val = val()
|
|
24
|
+
return val
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def any_setter(obj: Any, key: Union[str, int], value: Any):
|
|
28
|
+
if isinstance(obj, MutableSequence):
|
|
29
|
+
obj[key] = value
|
|
30
|
+
|
|
31
|
+
elif isinstance(obj, MutableMapping):
|
|
32
|
+
obj[key] = value
|
|
33
|
+
|
|
34
|
+
elif isinstance(key, int):
|
|
35
|
+
obj[key] = value
|
|
36
|
+
|
|
37
|
+
else:
|
|
38
|
+
setattr(obj, key, value)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def nested_get(obj: Union[Mapping, Sequence],
|
|
42
|
+
key_list: Sequence[Union[str, int]],
|
|
43
|
+
default: Any = None) -> Any:
|
|
44
|
+
"""Given a nested Mapping, Sequence or any indexable/subscriptable object of
|
|
45
|
+
other such, and a Sequence of keys and/or indexes, fetches the object at the
|
|
46
|
+
end of the Sequence of keys.
|
|
47
|
+
|
|
48
|
+
Returns the given default value if any key/index isn't found (defaults to
|
|
49
|
+
None if nothing is given).
|
|
50
|
+
|
|
51
|
+
Example:
|
|
52
|
+
>>> nested_get({'a': {7: {'c': ['apple', 'banana', 'cantaloupe']}, 5: 'other'}}, ('a', 7, 'c', -2))
|
|
53
|
+
'banana'
|
|
54
|
+
"""
|
|
55
|
+
next_obj = obj
|
|
56
|
+
for key in key_list:
|
|
57
|
+
next_obj = any_getter(next_obj, key, None)
|
|
58
|
+
if next_obj is None:
|
|
59
|
+
return default
|
|
60
|
+
return next_obj
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def nested_set(obj: Union[Mapping, Sequence],
|
|
64
|
+
key_list: Sequence[Union[str, int]],
|
|
65
|
+
value: Any):
|
|
66
|
+
"""Given a nested Mapping, Sequence or any indexable/subscriptable object of
|
|
67
|
+
other such, and a Sequence of keys and/or indexes, sets the value the those
|
|
68
|
+
keys/indexes point to.
|
|
69
|
+
|
|
70
|
+
If any key/index doesn't exist, it will be created/set if possible
|
|
71
|
+
|
|
72
|
+
"""
|
|
73
|
+
next_obj = obj
|
|
74
|
+
last = key_list[-1]
|
|
75
|
+
path = key_list[:-1]
|
|
76
|
+
for key in path:
|
|
77
|
+
next_next_obj = any_getter(next_obj, key, None)
|
|
78
|
+
if next_next_obj is None:
|
|
79
|
+
next_next_obj = {}
|
|
80
|
+
any_setter(next_obj, key, next_next_obj)
|
|
81
|
+
next_obj = next_next_obj
|
|
82
|
+
|
|
83
|
+
any_setter(next_obj, last, value)
|
|
84
|
+
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'nested_dict_update',
|
|
3
|
+
'nest_dict',
|
|
4
|
+
'str_nest_dict',
|
|
5
|
+
'flatten_dict',
|
|
6
|
+
'str_flatten_dict',
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
from batutils.tpu.structs import *
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def nested_dict_update(base_map: Dict[Any, Any], update_map: Dict[Any, Any]):
|
|
13
|
+
"""Updates a nested dicts. This means that if the `base_map` and
|
|
14
|
+
`update_map` contain the same key that is a dict in both, that nested dict
|
|
15
|
+
will be updated the same way `base_map.update(update_map)` works, i.e. it
|
|
16
|
+
will preserve any pre-existing keys in `base_map` and only update the values
|
|
17
|
+
of keys in the `update_map` (again, in a nested way).
|
|
18
|
+
|
|
19
|
+
Example of normal update:
|
|
20
|
+
>>> a = {'foo': 1, 'bar': {'alpha': 2, 'beta': 3}, 'oof': 4}
|
|
21
|
+
>>> b = {'foo': 101, 'bar': {'beta': 103}}
|
|
22
|
+
>>> a.update(b)
|
|
23
|
+
>>> print(a)
|
|
24
|
+
{'foo': 101, 'bar': {'beta': 103}, 'oof': 4}
|
|
25
|
+
|
|
26
|
+
Example of nested update:
|
|
27
|
+
>>> a = {'foo': 1, 'bar': {'alpha': 2, 'beta': 3}, 'oof': 4}
|
|
28
|
+
>>> b = {'foo': 101, 'bar': {'beta': 103}}
|
|
29
|
+
>>> nested_dict_update(a, b)
|
|
30
|
+
>>> print(a)
|
|
31
|
+
{'foo': 101, 'bar': {'alpha': 2, 'beta': 103}, 'oof': 4}
|
|
32
|
+
|
|
33
|
+
Note that if a key (or nested key) who's value is a dict in `base_map` is
|
|
34
|
+
NOT a dict in `update_map`, that dict value will be overwritten completely
|
|
35
|
+
with the new value and type.
|
|
36
|
+
"""
|
|
37
|
+
for k, v in update_map.items():
|
|
38
|
+
if k in base_map:
|
|
39
|
+
base_val = base_map[k]
|
|
40
|
+
if isinstance(base_val, dict) and isinstance(v, dict):
|
|
41
|
+
nested_dict_update(base_val, v)
|
|
42
|
+
base_map[k] = base_val
|
|
43
|
+
else:
|
|
44
|
+
base_map[k] = v
|
|
45
|
+
else:
|
|
46
|
+
base_map[k] = v
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def nest_dict(key_list: List[Any], value: Any = None) -> Dict[Any, Any]:
|
|
50
|
+
"""Creates a nested dict using a list of keys to construct an ever deepening
|
|
51
|
+
dict of a dict of a dict etc.
|
|
52
|
+
|
|
53
|
+
Example:
|
|
54
|
+
>>> nest_dict(['a', 'b', 'c'], 'value')
|
|
55
|
+
{'a': {'b': {'c': 'value'}}}
|
|
56
|
+
"""
|
|
57
|
+
if not key_list:
|
|
58
|
+
return value
|
|
59
|
+
k = key_list.pop(0)
|
|
60
|
+
return {k: nest_dict(key_list, value)}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def str_nest_dict(nested_key_string: str, value: Any = None, key_seperator: str = '.') -> Dict[str, Any]:
|
|
64
|
+
"""Creates a nested dict using a string with some separator symbol to split
|
|
65
|
+
it into a list of keys to construct an ever deepening dict of a dict of a
|
|
66
|
+
dict etc.
|
|
67
|
+
|
|
68
|
+
Example:
|
|
69
|
+
>>> str_nest_dict('a.b.c', 'value')
|
|
70
|
+
{'a': {'b': {'c': 'value'}}}
|
|
71
|
+
"""
|
|
72
|
+
return nest_dict(nested_key_string.split(key_seperator), value)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _denest_dict(some_dict: Dict[Any, Any]) -> List[Tuple[List[Any], Any]]:
|
|
76
|
+
"""Takes a potentially nested dict and returns a list of Tuple pairs
|
|
77
|
+
containing all "leaf" values (values that aren't dicts) as each Tuples
|
|
78
|
+
second value while the first value will be a list of the "key-path" through
|
|
79
|
+
the nested dict to that value.
|
|
80
|
+
|
|
81
|
+
Example:
|
|
82
|
+
>>> _denest_dict({'a': {7: {'c': 'value'}, 5: 'other'}})
|
|
83
|
+
[(['a', 7, 'c'], 'value'), (['a', 5], 'other')]
|
|
84
|
+
"""
|
|
85
|
+
buff = []
|
|
86
|
+
for k, v in some_dict.items():
|
|
87
|
+
if isinstance(v, dict):
|
|
88
|
+
for nk, nv in _denest_dict(v):
|
|
89
|
+
buff.append(([k]+nk, nv))
|
|
90
|
+
else:
|
|
91
|
+
buff.append(([k], v))
|
|
92
|
+
return buff
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def flatten_dict(some_dict: Dict[Any, Any]) -> Dict[Tuple, Any]:
|
|
96
|
+
"""Takes a dict that may (or may not) be nested (contain other dicts) and
|
|
97
|
+
flattens it out by keys, returning a single dict with one value entry per
|
|
98
|
+
"leaf" value (any value in the nested dict tree that is NOT another dict)
|
|
99
|
+
where they key for each value is a tuple of the key "path" to it in the
|
|
100
|
+
original dict.
|
|
101
|
+
|
|
102
|
+
>>> flatten_dict({'a': {7: {'c': 'value'}, 5: 'other'}})
|
|
103
|
+
{('a', 7, 'c'): 'value', ('a', 5): 'other'}
|
|
104
|
+
"""
|
|
105
|
+
buff = {}
|
|
106
|
+
for k, v in _denest_dict(some_dict):
|
|
107
|
+
buff[tuple(k)] = v
|
|
108
|
+
return buff
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def str_flatten_dict(some_dict: Dict[str, Any], joiner: str = '.', caster: Callable = str) -> Dict[str, Any]:
|
|
112
|
+
"""Does the same as `flatten_dict` except instead of keys of tuples, keys
|
|
113
|
+
are converted to str and joined with a dot character (by default).
|
|
114
|
+
|
|
115
|
+
Both the joining character (or string) and how each value in the key tuple
|
|
116
|
+
is converted to a str is customizable.
|
|
117
|
+
|
|
118
|
+
This can be especially useful if keys contain objects who's hash values
|
|
119
|
+
differ but who's str representations are the same, e.g. the int 1 and the
|
|
120
|
+
string '1' in {1: 'a', '1': 'b'}. That dict will turn into {'1': 'b'}.
|
|
121
|
+
|
|
122
|
+
:type some_dict: dict
|
|
123
|
+
:type joiner: str
|
|
124
|
+
:type caster: function
|
|
125
|
+
:rtype: dict[str,Any]
|
|
126
|
+
"""
|
|
127
|
+
buff = {}
|
|
128
|
+
for k, v in _denest_dict(some_dict):
|
|
129
|
+
buff[joiner.join([caster(ki) for ki in k])] = v
|
|
130
|
+
return buff
|