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.
Files changed (73) hide show
  1. batutils/__init__.py +5 -0
  2. batutils/_common/__init__.py +6 -0
  3. batutils/_common/_decode.py +40 -0
  4. batutils/dtu/__init__.py +19 -0
  5. batutils/dtu/casting/__init__.py +5 -0
  6. batutils/dtu/casting/_any.py +136 -0
  7. batutils/dtu/casting/_filetime.py +36 -0
  8. batutils/dtu/casting/_instant.py +31 -0
  9. batutils/dtu/casting/_string.py +81 -0
  10. batutils/dtu/casting/_timestamp.py +50 -0
  11. batutils/dtu/formatting/__init__.py +2 -0
  12. batutils/dtu/formatting/_delta.py +62 -0
  13. batutils/dtu/formatting/_serialize.py +26 -0
  14. batutils/dtu/parsers/__init__.py +2 -0
  15. batutils/dtu/parsers/_string.py +44 -0
  16. batutils/dtu/parsers/_timedelta.py +161 -0
  17. batutils/dtu/shortcuts/__init__.py +3 -0
  18. batutils/dtu/shortcuts/_aliases.py +29 -0
  19. batutils/dtu/shortcuts/_checkers.py +21 -0
  20. batutils/dtu/shortcuts/_finders.py +80 -0
  21. batutils/dtu/structs/__init__.py +5 -0
  22. batutils/dtu/structs/_base.py +5 -0
  23. batutils/dtu/structs/_deltasplit.py +278 -0
  24. batutils/dtu/structs/_types.py +24 -0
  25. batutils/dtu/structs/aliases.py +21 -0
  26. batutils/dtu/structs/consts.py +35 -0
  27. batutils/structs/__init__.py +7 -0
  28. batutils/structs/_base.py +21 -0
  29. batutils/tpu/__init__.py +0 -0
  30. batutils/tpu/casting/__init__.py +9 -0
  31. batutils/tpu/casting/_currency.py +52 -0
  32. batutils/tpu/casting/_evals.py +247 -0
  33. batutils/tpu/casting/_ip.py +58 -0
  34. batutils/tpu/casting/_mask.py +34 -0
  35. batutils/tpu/casting/_params.py +108 -0
  36. batutils/tpu/casting/_serialize.py +20 -0
  37. batutils/tpu/comparison/__init__.py +1 -0
  38. batutils/tpu/comparison/_mixin.py +30 -0
  39. batutils/tpu/insp/__init__.py +2 -0
  40. batutils/tpu/insp/_insp_old.py +347 -0
  41. batutils/tpu/insp/_typecheck.py +89 -0
  42. batutils/tpu/iters/__init__.py +4 -0
  43. batutils/tpu/iters/_attrs.py +84 -0
  44. batutils/tpu/iters/_dicts.py +130 -0
  45. batutils/tpu/iters/_group.py +50 -0
  46. batutils/tpu/iters/_lists.py +178 -0
  47. batutils/tpu/size/__init__.py +1 -0
  48. batutils/tpu/size/_total.py +52 -0
  49. batutils/tpu/strimp/__init__.py +1 -0
  50. batutils/tpu/strimp/_getters.py +285 -0
  51. batutils/tpu/string/__init__.py +1 -0
  52. batutils/tpu/string/_string.py +160 -0
  53. batutils/tpu/structs/__init__.py +1 -0
  54. batutils/tpu/structs/_base.py +3 -0
  55. batutils/tpu/structs/empty/__init__.py +1 -0
  56. batutils/tpu/structs/empty/_empty.py +387 -0
  57. batutils/tpu/structs/enumex/__init__.py +1 -0
  58. batutils/tpu/structs/enumex/_enumextra.py +25 -0
  59. batutils/tpu/structs/fluid/__init__.py +1 -0
  60. batutils/tpu/structs/fluid/_attrfluid.py +77 -0
  61. batutils/tpu/structs/sentience/__init__.py +1 -0
  62. batutils/tpu/structs/sentience/_builder.py +87 -0
  63. batutils/tpu/structs/serializers/__init__.py +3 -0
  64. batutils/tpu/structs/serializers/_json.py +12 -0
  65. batutils/tpu/structs/serializers/_jsonsafe.py +69 -0
  66. batutils/tpu/structs/serializers/_universal.py +318 -0
  67. batutils/tpu/structs/singleton/__init__.py +1 -0
  68. batutils/tpu/structs/singleton/_singleton.py +35 -0
  69. batutils-2.0.0.dist-info/LICENSE +22 -0
  70. batutils-2.0.0.dist-info/METADATA +125 -0
  71. batutils-2.0.0.dist-info/RECORD +73 -0
  72. batutils-2.0.0.dist-info/WHEEL +5 -0
  73. 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,3 @@
1
+ from ._universal import *
2
+ from ._jsonsafe import *
3
+ from ._json import *
@@ -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
+ ```