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,87 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'SentientObject',
|
|
3
|
+
]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class _SentientClassBuilder(type):
|
|
7
|
+
"""This is a sneaky Python black magic way to be able to collect certain
|
|
8
|
+
methods of classes and add some meta data to the via decorated during the
|
|
9
|
+
classes initialization.
|
|
10
|
+
"""
|
|
11
|
+
def __new__(mcs, clsname, bases, dct):
|
|
12
|
+
the_class = super(_SentientClassBuilder, mcs).__new__(mcs, clsname, bases, dct)
|
|
13
|
+
my_map = {}
|
|
14
|
+
for name, attr in dct.items():
|
|
15
|
+
if hasattr(attr, '__call__'):
|
|
16
|
+
sentience = getattr(attr, '__sentience__', None)
|
|
17
|
+
if sentience is not None and isinstance(sentience, dict):
|
|
18
|
+
my_map[name] = sentience
|
|
19
|
+
|
|
20
|
+
current_map = getattr(the_class, '__sentient_method_map__', None) or {}
|
|
21
|
+
current_map[clsname] = my_map
|
|
22
|
+
setattr(the_class, '__sentient_method_map__', current_map)
|
|
23
|
+
return the_class
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SentientObject(object, metaclass=_SentientClassBuilder):
|
|
27
|
+
"""Having a class extend this instead of the normal python object allows
|
|
28
|
+
you to mark some of it's methods via the sentient decorator and give them
|
|
29
|
+
some meta-data. The names of those methods along with the metadata (using
|
|
30
|
+
kwargs in the decorator) will be collected during the classes
|
|
31
|
+
initialization (on first import) and added to the __sentient_method_map__
|
|
32
|
+
dict. The sentient_methods() class method will return that map as such:
|
|
33
|
+
{
|
|
34
|
+
'method_name_1': {
|
|
35
|
+
'meta_data_1': 'meta_value_1',
|
|
36
|
+
'meta_data_2': 'meta_value_2'
|
|
37
|
+
},
|
|
38
|
+
'method_name_2': {
|
|
39
|
+
'meta_data_1': 'meta_value_1',
|
|
40
|
+
'meta_data_2': 'meta_value_2'
|
|
41
|
+
}
|
|
42
|
+
} ... etc.
|
|
43
|
+
|
|
44
|
+
The point is simply to be able to flag certain methods in code with a
|
|
45
|
+
simple decorator (as oppose to hardcoding some list) and then retrieve
|
|
46
|
+
that list for whatever reason e.g. quickly and simply "exposing" (or
|
|
47
|
+
advertising really) some methods to other code systematically (i.e.
|
|
48
|
+
instant automatic API).
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
"""
|
|
52
|
+
__sentient_method_map__ = {}
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def sentient_methods(cls, inherited=True):
|
|
56
|
+
"""Returns the map of "sentient" methods and their meta data.
|
|
57
|
+
|
|
58
|
+
:return: A dict with "sentient" method names as keys and a dict of
|
|
59
|
+
their meta data as value.
|
|
60
|
+
:rtype: dict
|
|
61
|
+
"""
|
|
62
|
+
buff = cls.__sentient_method_map__[cls.__name__]
|
|
63
|
+
if inherited:
|
|
64
|
+
for basecls in cls.__bases__:
|
|
65
|
+
if issubclass(basecls, SentientObject):
|
|
66
|
+
buff.update(basecls.sentient_methods())
|
|
67
|
+
return buff
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def sentient(**kwargs):
|
|
71
|
+
"""Adds the given keywords to a new attribute of the decorated
|
|
72
|
+
function, thus marking it for the SentientClassBuilder and storing
|
|
73
|
+
the meta data.
|
|
74
|
+
|
|
75
|
+
This triggers the SentientClassBuilder to add the method's name and
|
|
76
|
+
meta data to the __sentient_method_map__ when the class (not object)
|
|
77
|
+
is created.
|
|
78
|
+
|
|
79
|
+
:param kwargs: The meta data to store
|
|
80
|
+
:type kwargs: dict
|
|
81
|
+
:return: The original function with the meta data added to it
|
|
82
|
+
:rtype: function
|
|
83
|
+
"""
|
|
84
|
+
def inner(function):
|
|
85
|
+
setattr(function, '__sentience__', kwargs)
|
|
86
|
+
return function
|
|
87
|
+
return inner
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'JsonSerializer',
|
|
3
|
+
]
|
|
4
|
+
|
|
5
|
+
from ._jsonsafe import *
|
|
6
|
+
from typing import *
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class JsonSerializer(JsonSafeSerializer):
|
|
11
|
+
def serialize(self, obj: Any, indent: int = 4, sort_keys: bool = True) -> str:
|
|
12
|
+
return json.dumps(super().serialize(obj), indent=indent, sort_keys=sort_keys)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'JsonSafeSerializer',
|
|
3
|
+
]
|
|
4
|
+
|
|
5
|
+
from ._universal import *
|
|
6
|
+
from batutils._common._decode import decode_bytes
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class JsonSafeSerializer(UniversalSerializer):
|
|
10
|
+
"""
|
|
11
|
+
A version of the UniversalSerializer that serializes types from the datetime
|
|
12
|
+
and decimal modules into JSON safe types.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def _serialize_maps(self, obj):
|
|
16
|
+
"""We need to ensure that all keys are strings, otherwise "sort_keys" in json dumping will fail.
|
|
17
|
+
"""
|
|
18
|
+
d = {}
|
|
19
|
+
for k, v in obj.items():
|
|
20
|
+
k = str(k) # Keys need to be strings in JSON
|
|
21
|
+
if k in self.skip_keys or v in self.skip_values:
|
|
22
|
+
continue
|
|
23
|
+
if self.skip_private and k.startswith('_'):
|
|
24
|
+
continue
|
|
25
|
+
if self.skip_nones and v is None:
|
|
26
|
+
continue
|
|
27
|
+
val = self._serialize_any(v)
|
|
28
|
+
if self.skip_empties and isinstance(val, self._emptyables) and not val:
|
|
29
|
+
continue
|
|
30
|
+
d[k] = val
|
|
31
|
+
return d
|
|
32
|
+
|
|
33
|
+
def _serialize_bytes(self, obj):
|
|
34
|
+
return decode_bytes(obj)
|
|
35
|
+
|
|
36
|
+
def _serialize_decimal(self, obj):
|
|
37
|
+
"""
|
|
38
|
+
:type obj: decimal.Decimal
|
|
39
|
+
:rtype: str
|
|
40
|
+
"""
|
|
41
|
+
return '%s' % obj.normalize()
|
|
42
|
+
|
|
43
|
+
def _serialize_datetime(self, obj):
|
|
44
|
+
"""
|
|
45
|
+
:type obj: datetime.date
|
|
46
|
+
:rtype: str
|
|
47
|
+
"""
|
|
48
|
+
return obj.isoformat()
|
|
49
|
+
|
|
50
|
+
def _serialize_time(self, obj):
|
|
51
|
+
"""
|
|
52
|
+
:type obj: datetime.time
|
|
53
|
+
:rtype: str
|
|
54
|
+
"""
|
|
55
|
+
return obj.isoformat()
|
|
56
|
+
|
|
57
|
+
def _serialize_date(self, obj):
|
|
58
|
+
"""
|
|
59
|
+
:type obj: datetime.date
|
|
60
|
+
:rtype: str
|
|
61
|
+
"""
|
|
62
|
+
return obj.isoformat()
|
|
63
|
+
|
|
64
|
+
def _serialize_timedelta(self, obj):
|
|
65
|
+
"""
|
|
66
|
+
:type obj: datetime.timedelta
|
|
67
|
+
:rtype: float
|
|
68
|
+
"""
|
|
69
|
+
return obj.total_seconds()
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'UniversalSerializer',
|
|
3
|
+
]
|
|
4
|
+
|
|
5
|
+
from batutils.tpu.structs._base import *
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class UniversalSerializer(object):
|
|
9
|
+
_emptyables = (list, tuple, set, dict, str, bytes)
|
|
10
|
+
|
|
11
|
+
def __init__(self, skip_keys=None, skip_values=None, skip_private=True, skip_nones=False, skip_empties=False,
|
|
12
|
+
max_depth=0):
|
|
13
|
+
"""Serializes any python object or type recursively.
|
|
14
|
+
|
|
15
|
+
What this means in practical terms is that anything this function is given
|
|
16
|
+
will be guaranteed to be of and/or only contain the following python types:
|
|
17
|
+
- int
|
|
18
|
+
- float
|
|
19
|
+
- str
|
|
20
|
+
- bytes
|
|
21
|
+
- decimal.Decimal
|
|
22
|
+
- datetime.date
|
|
23
|
+
- datetime.datetime
|
|
24
|
+
- datetime.time
|
|
25
|
+
- datetime.timedelta
|
|
26
|
+
- None
|
|
27
|
+
- list
|
|
28
|
+
- tuple
|
|
29
|
+
- set
|
|
30
|
+
- dict
|
|
31
|
+
|
|
32
|
+
The main purpose/use-case for this is to take any arbitrary classes and/or
|
|
33
|
+
data structures (e.g. from various third party APIs) and turn them into
|
|
34
|
+
simple/common python primary types for safer manipulation and/or use.
|
|
35
|
+
|
|
36
|
+
This minimizes the amount of "defensive" coding needed to handle external
|
|
37
|
+
return values who's structures are wholly or partially unknown or might
|
|
38
|
+
change without notice. Turning an object into a discrete set of types means
|
|
39
|
+
that checking for attributes and values becomes easier and less likely to
|
|
40
|
+
raise annoying exceptions that need handling like AttributeError,
|
|
41
|
+
ValueError, KeyError and such.
|
|
42
|
+
|
|
43
|
+
An example would be turning an object into a dict to ensure that calling
|
|
44
|
+
obj.get('is_foo', False) simply yields True or False without any additional
|
|
45
|
+
safety measures or exception handling and such.
|
|
46
|
+
|
|
47
|
+
Another example would be to turn an object into a dict guaranteed to have
|
|
48
|
+
all it's properties and recursive structures for dumping out in logs or
|
|
49
|
+
encoding into other formats like JSON (although you'd have to handle the
|
|
50
|
+
serialization of values from the datetime and decimal modules specially for
|
|
51
|
+
that).
|
|
52
|
+
|
|
53
|
+
These python types are returned as-is:
|
|
54
|
+
- int
|
|
55
|
+
- float
|
|
56
|
+
- str
|
|
57
|
+
- bytes
|
|
58
|
+
- decimal.Decimal
|
|
59
|
+
- datetime.date
|
|
60
|
+
- datetime.datetime
|
|
61
|
+
- datetime.time
|
|
62
|
+
- datetime.timedelta
|
|
63
|
+
- None
|
|
64
|
+
|
|
65
|
+
These types are returned back (as either lists or dicts) after recursively
|
|
66
|
+
serializing their values and/or properties:
|
|
67
|
+
- list
|
|
68
|
+
- tuple
|
|
69
|
+
- set
|
|
70
|
+
- dict
|
|
71
|
+
|
|
72
|
+
Any other type (e.g. anything inheriting from object) that has a __dict__
|
|
73
|
+
property will be serialized recursively to a dict by using their __dict__.
|
|
74
|
+
|
|
75
|
+
Other types without a __dict__ property will yield the string:
|
|
76
|
+
- '__WHAT__::%s' % type(obj)
|
|
77
|
+
|
|
78
|
+
Reference loops will be serialized as the string
|
|
79
|
+
- '__LOOP__::%s::%s' % (type(obj), id(obj))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
:param skip_keys: List of key names to skip from dict and object.__dict__
|
|
83
|
+
serialization
|
|
84
|
+
:type skip_keys: list | tuple | set
|
|
85
|
+
:param skip_values: List of values to skip from dict and object.__dict__
|
|
86
|
+
serialization
|
|
87
|
+
:type skip_values: list | tuple | set
|
|
88
|
+
:param skip_private: Should private keys (i.e. who's name starts with an
|
|
89
|
+
underscore) be skipped? Default is True.
|
|
90
|
+
:type skip_private: bool
|
|
91
|
+
:param skip_nones: Should values of None be skipped from dict and
|
|
92
|
+
object.__dict__ serialization? Default is False.
|
|
93
|
+
:type skip_nones: bool
|
|
94
|
+
:param skip_empties: Should empty lists, tuples, sets, dicts, strings and
|
|
95
|
+
bytes be skipped from dict and object.__dict__
|
|
96
|
+
serialization? Default is False.
|
|
97
|
+
:type skip_empties: bool
|
|
98
|
+
:param max_depth: Maximum recursion depth (Default is 0 for infinite)
|
|
99
|
+
:type max_depth: int
|
|
100
|
+
|
|
101
|
+
"""
|
|
102
|
+
self.skip_keys = list(skip_keys or [])
|
|
103
|
+
self.skip_values = list(skip_values or [])
|
|
104
|
+
|
|
105
|
+
self.skip_private = skip_private
|
|
106
|
+
self.skip_nones = skip_nones
|
|
107
|
+
self.skip_empties = skip_empties
|
|
108
|
+
self.max_depth = max_depth
|
|
109
|
+
|
|
110
|
+
self._breadcrumbs = []
|
|
111
|
+
|
|
112
|
+
self.serialize_map = {
|
|
113
|
+
int: self._serialize_int,
|
|
114
|
+
float: self._serialize_float,
|
|
115
|
+
str: self._serialize_str,
|
|
116
|
+
bytes: self._serialize_bytes,
|
|
117
|
+
|
|
118
|
+
Decimal: self._serialize_decimal,
|
|
119
|
+
|
|
120
|
+
datetime.time: self._serialize_time,
|
|
121
|
+
datetime.date: self._serialize_date,
|
|
122
|
+
datetime.datetime: self._serialize_datetime,
|
|
123
|
+
datetime.timedelta: self._serialize_timedelta,
|
|
124
|
+
|
|
125
|
+
list: self._serialize_list,
|
|
126
|
+
tuple: self._serialize_tuple,
|
|
127
|
+
set: self._serialize_set,
|
|
128
|
+
dict: self._serialize_dict,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
def serialize(self, obj):
|
|
132
|
+
self._breadcrumbs = [] # Just in case!
|
|
133
|
+
return self._serialize_any(obj)
|
|
134
|
+
|
|
135
|
+
def _serialize_any(self, obj):
|
|
136
|
+
try:
|
|
137
|
+
if obj in self._breadcrumbs:
|
|
138
|
+
return self._serialize_loop(obj)
|
|
139
|
+
|
|
140
|
+
if obj is None:
|
|
141
|
+
return self._serialize_none(obj)
|
|
142
|
+
|
|
143
|
+
if self.max_depth and len(self._breadcrumbs) > self.max_depth:
|
|
144
|
+
return self._serialize_max_depth(obj)
|
|
145
|
+
|
|
146
|
+
t = type(obj)
|
|
147
|
+
serializer = self.serialize_map.get(t, self._serialize_other)
|
|
148
|
+
if serializer:
|
|
149
|
+
self._breadcrumbs.append(obj)
|
|
150
|
+
value = serializer(obj)
|
|
151
|
+
self._breadcrumbs.pop()
|
|
152
|
+
return value
|
|
153
|
+
|
|
154
|
+
except Exception as ex:
|
|
155
|
+
return self._serialize_error(obj, '%s' % ex)
|
|
156
|
+
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
def _serialize_int(self, obj):
|
|
160
|
+
"""
|
|
161
|
+
:type obj: int
|
|
162
|
+
:rtype: int
|
|
163
|
+
"""
|
|
164
|
+
return obj
|
|
165
|
+
|
|
166
|
+
def _serialize_float(self, obj):
|
|
167
|
+
"""
|
|
168
|
+
:type obj: float
|
|
169
|
+
:rtype: float
|
|
170
|
+
"""
|
|
171
|
+
return obj
|
|
172
|
+
|
|
173
|
+
def _serialize_str(self, obj):
|
|
174
|
+
"""
|
|
175
|
+
:type obj: str
|
|
176
|
+
:rtype: str
|
|
177
|
+
"""
|
|
178
|
+
return obj
|
|
179
|
+
|
|
180
|
+
def _serialize_bytes(self, obj):
|
|
181
|
+
"""
|
|
182
|
+
:type obj: bytes
|
|
183
|
+
:rtype: bytes
|
|
184
|
+
"""
|
|
185
|
+
return obj
|
|
186
|
+
|
|
187
|
+
def _serialize_decimal(self, obj):
|
|
188
|
+
"""
|
|
189
|
+
:type obj: decimal.Decimal
|
|
190
|
+
:rtype: decimal.Decimal
|
|
191
|
+
"""
|
|
192
|
+
return obj
|
|
193
|
+
|
|
194
|
+
def _serialize_datetime(self, obj):
|
|
195
|
+
"""
|
|
196
|
+
:type obj: datetime.datetime
|
|
197
|
+
:rtype: datetime.datetime
|
|
198
|
+
"""
|
|
199
|
+
return obj
|
|
200
|
+
|
|
201
|
+
def _serialize_time(self, obj):
|
|
202
|
+
"""
|
|
203
|
+
:type obj: datetime.time
|
|
204
|
+
:rtype: datetime.time
|
|
205
|
+
"""
|
|
206
|
+
return obj
|
|
207
|
+
|
|
208
|
+
def _serialize_date(self, obj):
|
|
209
|
+
"""
|
|
210
|
+
:type obj: datetime.date
|
|
211
|
+
:rtype: datetime.date
|
|
212
|
+
"""
|
|
213
|
+
return obj
|
|
214
|
+
|
|
215
|
+
def _serialize_timedelta(self, obj):
|
|
216
|
+
"""
|
|
217
|
+
:type obj: datetime.timedelta
|
|
218
|
+
:rtype: datetime.timedelta
|
|
219
|
+
"""
|
|
220
|
+
return obj
|
|
221
|
+
|
|
222
|
+
def _serialize_list(self, obj):
|
|
223
|
+
"""
|
|
224
|
+
:type obj: list
|
|
225
|
+
:rtype: list
|
|
226
|
+
"""
|
|
227
|
+
return self._serialize_iters(obj)
|
|
228
|
+
|
|
229
|
+
def _serialize_tuple(self, obj):
|
|
230
|
+
"""
|
|
231
|
+
:type obj: tuple
|
|
232
|
+
:rtype: list
|
|
233
|
+
"""
|
|
234
|
+
return self._serialize_iters(obj)
|
|
235
|
+
|
|
236
|
+
def _serialize_set(self, obj):
|
|
237
|
+
"""
|
|
238
|
+
:type obj: set
|
|
239
|
+
:rtype: list
|
|
240
|
+
"""
|
|
241
|
+
return self._serialize_iters(obj)
|
|
242
|
+
|
|
243
|
+
def _serialize_iters(self, obj):
|
|
244
|
+
"""
|
|
245
|
+
:type obj: list | tuple | set
|
|
246
|
+
:rtype: list
|
|
247
|
+
"""
|
|
248
|
+
return [self._serialize_any(i) for i in obj]
|
|
249
|
+
|
|
250
|
+
def _serialize_maps(self, obj):
|
|
251
|
+
"""
|
|
252
|
+
:type obj: dict
|
|
253
|
+
:rtype: dict
|
|
254
|
+
"""
|
|
255
|
+
d = {}
|
|
256
|
+
for k, v in obj.items():
|
|
257
|
+
if k in self.skip_keys or v in self.skip_values:
|
|
258
|
+
continue
|
|
259
|
+
if self.skip_private and isinstance(k, str) and k.startswith('_'):
|
|
260
|
+
continue
|
|
261
|
+
if self.skip_nones and v is None:
|
|
262
|
+
continue
|
|
263
|
+
val = self._serialize_any(v)
|
|
264
|
+
if self.skip_empties and isinstance(val, self._emptyables) and not val:
|
|
265
|
+
continue
|
|
266
|
+
d[k] = val
|
|
267
|
+
return d
|
|
268
|
+
|
|
269
|
+
def _serialize_dict(self, obj):
|
|
270
|
+
"""
|
|
271
|
+
:type obj: dict
|
|
272
|
+
:rtype: dict
|
|
273
|
+
"""
|
|
274
|
+
return self._serialize_maps(obj)
|
|
275
|
+
|
|
276
|
+
def _serialize_object(self, obj):
|
|
277
|
+
"""
|
|
278
|
+
:type obj: object
|
|
279
|
+
:rtype: dict
|
|
280
|
+
"""
|
|
281
|
+
return self._serialize_maps(obj.__dict__)
|
|
282
|
+
|
|
283
|
+
def _serialize_other(self, obj):
|
|
284
|
+
|
|
285
|
+
# OVERRIDE CUSTOM SERIALIZING HERE!
|
|
286
|
+
|
|
287
|
+
# If no special serializers are found, just serialize the __dict__ of an object!
|
|
288
|
+
if hasattr(obj, '__dict__'):
|
|
289
|
+
return self._serialize_object(obj)
|
|
290
|
+
return self._serialize_derived(obj)
|
|
291
|
+
|
|
292
|
+
def _serialize_none(self, obj):
|
|
293
|
+
"""
|
|
294
|
+
:type obj: None
|
|
295
|
+
:rtype: None
|
|
296
|
+
"""
|
|
297
|
+
return None
|
|
298
|
+
|
|
299
|
+
def _serialize_loop(self, obj):
|
|
300
|
+
return '__LOOP__::%s::%s' % (type(obj), id(obj))
|
|
301
|
+
|
|
302
|
+
def _serialize_derived(self, obj):
|
|
303
|
+
for t, func in self.serialize_map.items():
|
|
304
|
+
# Check if the object extends any of our known types and use that
|
|
305
|
+
if isinstance(obj, t):
|
|
306
|
+
return func(obj)
|
|
307
|
+
|
|
308
|
+
# If all else fails, return unknown serialization
|
|
309
|
+
return self._serialize_unknown(obj)
|
|
310
|
+
|
|
311
|
+
def _serialize_error(self, obj, error):
|
|
312
|
+
return '__ERROR__::%s::%s' % (type(obj), error)
|
|
313
|
+
|
|
314
|
+
def _serialize_unknown(self, obj):
|
|
315
|
+
return '__UNKNOWN__::%s' % type(obj)
|
|
316
|
+
|
|
317
|
+
def _serialize_max_depth(self, obj):
|
|
318
|
+
return '__MAX_DEPTH__::%s::%s' % (type(obj), id(obj))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from ._singleton import *
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'Singleton',
|
|
3
|
+
]
|
|
4
|
+
|
|
5
|
+
from threading import Lock
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Singleton(type):
|
|
9
|
+
"""
|
|
10
|
+
Use:
|
|
11
|
+
from tpu import singleton
|
|
12
|
+
|
|
13
|
+
class MyClass(BaseClass, metaclass=singleton.Singleton):
|
|
14
|
+
...
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
__instances = {'lock': Lock()}
|
|
18
|
+
|
|
19
|
+
def __call__(cls, *args, **kwargs):
|
|
20
|
+
with cls.__instances['lock']:
|
|
21
|
+
if cls not in cls.__instances:
|
|
22
|
+
cls.__instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
|
|
23
|
+
setattr(cls.__instances[cls], '_singleton_reload', Singleton.wrapper(cls))
|
|
24
|
+
return cls.__instances[cls]
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def wrapper(cls):
|
|
28
|
+
def _singleton_reload():
|
|
29
|
+
try:
|
|
30
|
+
with cls.__instances['lock']:
|
|
31
|
+
del cls.__instances[cls]
|
|
32
|
+
except KeyError:
|
|
33
|
+
pass
|
|
34
|
+
return cls()
|
|
35
|
+
return _singleton_reload
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Thordur Matthiasson
|
|
4
|
+
Copyright (c) 2013-2024 CCP Games
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: batutils
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Team Batcave's utilities for date and/or time objects and values and messing with types and so much more.
|
|
5
|
+
Author-email: Thordur Matthiasson <doddi79@gmail.com>, John Aldis <johnaldis@ccpgames.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2024 Thordur Matthiasson
|
|
9
|
+
Copyright (c) 2013-2024 CCP Games
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
|
|
29
|
+
Project-URL: Homepage, https://github.com/doddi79/batutils
|
|
30
|
+
Project-URL: Documentation, https://github.com/doddi79/batutils/blob/main/README.md
|
|
31
|
+
Project-URL: Repository, https://github.com/doddi79/batutils.git
|
|
32
|
+
Project-URL: Issues, https://github.com/doddi79/batutils/issues
|
|
33
|
+
Project-URL: Changelog, https://github.com/doddi79/batutils/blob/main/CHANGELOG.md
|
|
34
|
+
Keywords: datetimeutils,datetime,typeutils,type,singleton,date,time,timespan,tools,batcave,utils
|
|
35
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
36
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
37
|
+
Classifier: Intended Audience :: Developers
|
|
38
|
+
Classifier: Programming Language :: Python
|
|
39
|
+
Classifier: Programming Language :: Python :: 3
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
43
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
44
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
45
|
+
Classifier: Topic :: Software Development
|
|
46
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
47
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
48
|
+
Classifier: Topic :: Utilities
|
|
49
|
+
Description-Content-Type: text/markdown
|
|
50
|
+
License-File: LICENSE
|
|
51
|
+
|
|
52
|
+
# Team Batcave Python Toolkit
|
|
53
|
+
|
|
54
|
+
The `batutils` is a fork of the `batutils` package in order to continue its
|
|
55
|
+
development and maintenance beyond Team Batcave's lifespan.
|
|
56
|
+
|
|
57
|
+
It is a smooshup of a few Python packages from the CCP Tools Team of old (Team
|
|
58
|
+
Batcave).
|
|
59
|
+
|
|
60
|
+
Our two most commonly used internal packages, called `datetimeutils` and
|
|
61
|
+
`typeutils` were too _generically_ named for Pypi.org and since they were
|
|
62
|
+
both used in something like 80-90% of our other projects, it made sense to
|
|
63
|
+
just smoosh them together in one module, and thus, the `batutils` package
|
|
64
|
+
was born.
|
|
65
|
+
|
|
66
|
+
Here's the README of the [Date Time Utils](batutils/dtu/README.md) submodule.
|
|
67
|
+
|
|
68
|
+
Here's the README of the [Type Utils](batutils/tpu/README.md) submodule.
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
### Changes from `ccptools`
|
|
72
|
+
|
|
73
|
+
The `legacyapi` submodule has been removed.
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
## Date-Time Utils
|
|
77
|
+
|
|
78
|
+
The old `datetimeutils` package is now included here as the `dtu` submodule.
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from batutils import dtu
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Structs
|
|
85
|
+
|
|
86
|
+
Importing `*` from the `structs` submodule will import all of the most
|
|
87
|
+
commonly used imports in our projects:
|
|
88
|
+
```python
|
|
89
|
+
from typing import * # For type annotation
|
|
90
|
+
|
|
91
|
+
import abc # For interfaces (Abstract Base Classes)
|
|
92
|
+
import dataclasses # For dataclass structs
|
|
93
|
+
import decimal # Used whenever we're handling money
|
|
94
|
+
import enum # Also used for struct creation
|
|
95
|
+
import logging # Used pretty much everywhere
|
|
96
|
+
import re # Used surprisingly frequently
|
|
97
|
+
import time # Very commonly used
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Note that datetime is not included in this. That's because tt'll also import
|
|
101
|
+
the aliases from the Datetime Utils (`batutils.dtu.structs.aliases`)
|
|
102
|
+
package instead:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
Date = datetime.date
|
|
106
|
+
Time = datetime.time
|
|
107
|
+
Datetime = datetime.datetime
|
|
108
|
+
TimeDelta = datetime.timedelta
|
|
109
|
+
TzInfo = datetime.tzinfo
|
|
110
|
+
TimeZone = datetime.timezone
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Furthermore, it'll also include a few of the most commonly used utility
|
|
114
|
+
classes from the Type Utils submodule:
|
|
115
|
+
|
|
116
|
+
- The `Singleton` Meta Class
|
|
117
|
+
- The `Empty` and `EmptyDict` classes as well as the `if_empty` method
|
|
118
|
+
- The `EnumEx` base class for Enums with the `from_any` class method
|
|
119
|
+
|
|
120
|
+
So in most cases we can cover something like 90% of any imports we tend to
|
|
121
|
+
need in every Python file with a single line:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from batutils.structs import *
|
|
125
|
+
```
|