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,21 @@
|
|
|
1
|
+
"""These are the modules used by most of the code in this package.
|
|
2
|
+
|
|
3
|
+
This is intended for internal use only and is meant to simplify the otherwise
|
|
4
|
+
repetitive importing of the same modules over and over again in most of the
|
|
5
|
+
python files here.
|
|
6
|
+
|
|
7
|
+
I.e. instead of starting most files with the same imports as here, you just go:
|
|
8
|
+
```python
|
|
9
|
+
from batutils.structs import * # This includes the `_base`
|
|
10
|
+
```
|
|
11
|
+
"""
|
|
12
|
+
from typing import *
|
|
13
|
+
|
|
14
|
+
from decimal import Decimal
|
|
15
|
+
|
|
16
|
+
import abc
|
|
17
|
+
import dataclasses
|
|
18
|
+
import enum
|
|
19
|
+
import logging
|
|
20
|
+
import re
|
|
21
|
+
import time
|
batutils/tpu/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'parse_currency_text',
|
|
3
|
+
]
|
|
4
|
+
|
|
5
|
+
from batutils.tpu.structs import *
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
_CURRENCY_REGEX = re.compile(r'(?i)^(?P<curr1>(?:[^0-9 \+\-])*)? *(?P<sign1>[\+\-])? *(?P<amount>(?:[0-9]|[\.\,])+) *(?P<sign2>[\+\-])? *(?P<curr2>(?:[^0-9 \+\-])*)?$')
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def parse_currency_text(text: str) -> Tuple[Optional[str], Optional[float]]:
|
|
12
|
+
"""Parses a text in the form "CURRENCY (+|-) AMOUNT" into a two-tuple of string and float if possible.
|
|
13
|
+
|
|
14
|
+
:param text:
|
|
15
|
+
:type text:
|
|
16
|
+
:return:
|
|
17
|
+
:rtype:
|
|
18
|
+
"""
|
|
19
|
+
from batutils.tpu.casting import float_eval
|
|
20
|
+
m = _CURRENCY_REGEX.match(text)
|
|
21
|
+
if m:
|
|
22
|
+
currency = ''
|
|
23
|
+
curr1 = m.group('curr1')
|
|
24
|
+
curr2 = m.group('curr2')
|
|
25
|
+
if curr1:
|
|
26
|
+
if curr2:
|
|
27
|
+
if curr1.lower() == curr2.lower():
|
|
28
|
+
currency = curr1
|
|
29
|
+
else:
|
|
30
|
+
currency = '%s/%s' % (curr1, curr2)
|
|
31
|
+
else:
|
|
32
|
+
currency = curr1
|
|
33
|
+
elif curr2:
|
|
34
|
+
currency = curr2
|
|
35
|
+
|
|
36
|
+
neg = False
|
|
37
|
+
sign1 = m.group('sign1')
|
|
38
|
+
sign2 = m.group('sign2')
|
|
39
|
+
if sign1 and sign1 == '-':
|
|
40
|
+
neg = True
|
|
41
|
+
elif sign2 and sign2 == '-':
|
|
42
|
+
neg = True
|
|
43
|
+
|
|
44
|
+
amount = 0.0
|
|
45
|
+
amount_text = m.group('amount').replace(',', '')
|
|
46
|
+
if amount_text:
|
|
47
|
+
amount = float_eval(amount_text)
|
|
48
|
+
if neg:
|
|
49
|
+
amount = -amount
|
|
50
|
+
return currency, amount
|
|
51
|
+
|
|
52
|
+
return None, None
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'float_eval',
|
|
3
|
+
'int_eval',
|
|
4
|
+
'bool_eval',
|
|
5
|
+
'enum_eval',
|
|
6
|
+
|
|
7
|
+
'decode_bytes',
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
from batutils.tpu.structs import *
|
|
11
|
+
from batutils._common import decode_bytes # noqa
|
|
12
|
+
|
|
13
|
+
_AFFIRMATIVE = {
|
|
14
|
+
'active',
|
|
15
|
+
'enable',
|
|
16
|
+
'enabled',
|
|
17
|
+
'on',
|
|
18
|
+
'true',
|
|
19
|
+
'yes',
|
|
20
|
+
'y',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
_T_ENUM_CLASS = TypeVar('T_ENUM_CLASS', bound=enum.Enum)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def float_eval(value: Any, default: Union[float, None] = 0.0, raise_on_fail: bool = False) -> Union[float, None]:
|
|
27
|
+
"""Safe evaluation of any type to a float value. The optional default value
|
|
28
|
+
will be returned on any parsing or casting error. You can also pass None as
|
|
29
|
+
the default to check for failures.
|
|
30
|
+
|
|
31
|
+
Adding the raise_on_fail parameter and setting it to True will cause the
|
|
32
|
+
method to raise TypeError or ValueError exceptions if it fails to evaluate a
|
|
33
|
+
float value from the value given.
|
|
34
|
+
|
|
35
|
+
Useful shortcut where you have a string that you're know is a float value
|
|
36
|
+
in string form like prefs entries, DB entries from varchar fields or HTTP
|
|
37
|
+
GET/POST arguments
|
|
38
|
+
|
|
39
|
+
:type value: any
|
|
40
|
+
:type default: float or None
|
|
41
|
+
:type raise_on_fail: bool
|
|
42
|
+
:rtype: float or None
|
|
43
|
+
"""
|
|
44
|
+
if isinstance(value, float):
|
|
45
|
+
return value # Duh!
|
|
46
|
+
elif isinstance(value, int):
|
|
47
|
+
return float(value)
|
|
48
|
+
elif isinstance(value, (str, bytes)):
|
|
49
|
+
try:
|
|
50
|
+
return float(value)
|
|
51
|
+
except (TypeError, ValueError):
|
|
52
|
+
if raise_on_fail:
|
|
53
|
+
raise
|
|
54
|
+
return default
|
|
55
|
+
elif isinstance(value, datetime.timedelta):
|
|
56
|
+
return value.total_seconds()
|
|
57
|
+
elif isinstance(value, datetime.date): # datetime.datetime extends datetime date
|
|
58
|
+
return time.mktime(value.timetuple())
|
|
59
|
+
elif isinstance(value, datetime.time):
|
|
60
|
+
return float(value.hour * 3600 + value.minute * 60 + value.second + value.microsecond * 0.000001)
|
|
61
|
+
else:
|
|
62
|
+
try:
|
|
63
|
+
# Let's just try and pass this through the float caster...?
|
|
64
|
+
return float(value)
|
|
65
|
+
except (TypeError, ValueError):
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
if raise_on_fail:
|
|
69
|
+
raise TypeError(f'Could not evaluate unknown type [{type(value)}] to a float value')
|
|
70
|
+
return default
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def int_eval(value: Any, default: Union[int, None] = 0, raise_on_fail: bool = False) -> Union[int, None]:
|
|
74
|
+
"""Safe evaluation of any type to an int value. The optional default value
|
|
75
|
+
will be returned on any parsing or casting error. You can also pass None as
|
|
76
|
+
the default to check for failures.
|
|
77
|
+
|
|
78
|
+
Long values that contain values larger than ints can contain (or strings
|
|
79
|
+
containing such values) will evaluate to a long.
|
|
80
|
+
|
|
81
|
+
Useful shortcut where you have a string that you're know is an int value
|
|
82
|
+
in string form like prefs entries, DB entries from varchar fields or HTTP
|
|
83
|
+
GET/POST arguments
|
|
84
|
+
|
|
85
|
+
:type value: any
|
|
86
|
+
:type default: int or None
|
|
87
|
+
:type raise_on_fail: bool
|
|
88
|
+
:rtype: int or long or None
|
|
89
|
+
"""
|
|
90
|
+
if isinstance(value, int):
|
|
91
|
+
return value # Duh!
|
|
92
|
+
|
|
93
|
+
elif isinstance(value, float):
|
|
94
|
+
return int(value)
|
|
95
|
+
|
|
96
|
+
elif isinstance(value, (str, bytes)):
|
|
97
|
+
try:
|
|
98
|
+
return int(value, base=0)
|
|
99
|
+
|
|
100
|
+
except (TypeError, ValueError):
|
|
101
|
+
try:
|
|
102
|
+
return int(float(value))
|
|
103
|
+
|
|
104
|
+
except (TypeError, ValueError):
|
|
105
|
+
if raise_on_fail:
|
|
106
|
+
raise
|
|
107
|
+
|
|
108
|
+
return default
|
|
109
|
+
|
|
110
|
+
elif isinstance(value, datetime.timedelta):
|
|
111
|
+
return int(value.total_seconds())
|
|
112
|
+
|
|
113
|
+
elif isinstance(value, datetime.date): # datetime.datetime extends datetime date
|
|
114
|
+
return int(time.mktime(value.timetuple()))
|
|
115
|
+
|
|
116
|
+
elif isinstance(value, datetime.time):
|
|
117
|
+
return int(value.hour * 3600 + value.minute * 60 + value.second)
|
|
118
|
+
|
|
119
|
+
else:
|
|
120
|
+
try:
|
|
121
|
+
# Let's just try and pass this through the float caster...?
|
|
122
|
+
return int(value)
|
|
123
|
+
except (TypeError, ValueError):
|
|
124
|
+
pass
|
|
125
|
+
|
|
126
|
+
if raise_on_fail:
|
|
127
|
+
raise TypeError(f'Could not evaluate unknown type [{type(value)}] to an int value')
|
|
128
|
+
|
|
129
|
+
return default
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def bool_eval(value: Any, raise_on_fail: bool = False) -> bool:
|
|
133
|
+
"""Evaluates if a value should be interpreted as a boolean True value.
|
|
134
|
+
That means a string that is "True" (or other explicit affirmative words)
|
|
135
|
+
regardless of case and whitespace characters as well as any numerical value
|
|
136
|
+
other than 0 and of course a boolean type of true.
|
|
137
|
+
|
|
138
|
+
Anything else returns False, including all objects.
|
|
139
|
+
|
|
140
|
+
Boolean types are return their same value.
|
|
141
|
+
|
|
142
|
+
Strings and bytes with the following values will (case insensitive) evaluate to True:
|
|
143
|
+
- active
|
|
144
|
+
- enable
|
|
145
|
+
- enabled
|
|
146
|
+
- on
|
|
147
|
+
- true
|
|
148
|
+
- yes
|
|
149
|
+
- y
|
|
150
|
+
|
|
151
|
+
Strings and unicode strings that are longs, integers or floats evaluate to
|
|
152
|
+
the same as their "pure" types (int, float) would evaluate to.
|
|
153
|
+
|
|
154
|
+
Longs, Integers and Floating number evaluate to False if they are 0L, 0 and
|
|
155
|
+
0.0 respectively, anything else is True.
|
|
156
|
+
|
|
157
|
+
Any other type or object yield a False return (the opposite of what Python
|
|
158
|
+
does by default, because we're looking for explicit statements of "Yes!")
|
|
159
|
+
|
|
160
|
+
Very useful for evaluating values read from prefs.ini that can use
|
|
161
|
+
different formats.
|
|
162
|
+
|
|
163
|
+
:type value: any
|
|
164
|
+
:type raise_on_fail: bool
|
|
165
|
+
:rtype: bool
|
|
166
|
+
"""
|
|
167
|
+
if isinstance(value, bool):
|
|
168
|
+
return value # Duh!
|
|
169
|
+
|
|
170
|
+
if isinstance(value, bytes):
|
|
171
|
+
try:
|
|
172
|
+
value = decode_bytes(value)
|
|
173
|
+
except ValueError:
|
|
174
|
+
if raise_on_fail:
|
|
175
|
+
raise
|
|
176
|
+
return False
|
|
177
|
+
|
|
178
|
+
if isinstance(value, str):
|
|
179
|
+
value = value.strip().lower()
|
|
180
|
+
if value in _AFFIRMATIVE:
|
|
181
|
+
return True
|
|
182
|
+
|
|
183
|
+
elif value.isdigit():
|
|
184
|
+
return bool(int(value))
|
|
185
|
+
|
|
186
|
+
else:
|
|
187
|
+
neg = False
|
|
188
|
+
if value.startswith('-'):
|
|
189
|
+
value = value[1:]
|
|
190
|
+
neg = True
|
|
191
|
+
if value.isdigit():
|
|
192
|
+
return bool(-int(value))
|
|
193
|
+
parts = value.split('.')
|
|
194
|
+
if len(parts) == 2:
|
|
195
|
+
if parts[0].isdigit() and parts[1].isdigit():
|
|
196
|
+
if neg:
|
|
197
|
+
return bool(-float(value))
|
|
198
|
+
else:
|
|
199
|
+
return bool(float(value))
|
|
200
|
+
return False
|
|
201
|
+
elif isinstance(value, (int, float)):
|
|
202
|
+
return bool(value)
|
|
203
|
+
else:
|
|
204
|
+
return False
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def enum_eval(value: Any,
|
|
208
|
+
enum_class: Type[_T_ENUM_CLASS],
|
|
209
|
+
default: Union[_T_ENUM_CLASS, None] = None,
|
|
210
|
+
raise_on_fail: bool = False) -> _T_ENUM_CLASS:
|
|
211
|
+
if isinstance(value, enum_class):
|
|
212
|
+
return value
|
|
213
|
+
|
|
214
|
+
if isinstance(value, enum.Enum):
|
|
215
|
+
value = value.value
|
|
216
|
+
|
|
217
|
+
if isinstance(value, bytes):
|
|
218
|
+
try:
|
|
219
|
+
value = decode_bytes(value)
|
|
220
|
+
except ValueError:
|
|
221
|
+
if raise_on_fail:
|
|
222
|
+
raise
|
|
223
|
+
return default
|
|
224
|
+
|
|
225
|
+
if isinstance(value, str):
|
|
226
|
+
hit = {o.name.lower(): o for o in enum_class}.get(value.strip().lower(), None)
|
|
227
|
+
if hit is not None:
|
|
228
|
+
return hit
|
|
229
|
+
try:
|
|
230
|
+
value = int(value, base=0)
|
|
231
|
+
except ValueError:
|
|
232
|
+
if raise_on_fail:
|
|
233
|
+
raise ValueError(f'{value!r} is not a valid {enum_class.__name__}')
|
|
234
|
+
return default
|
|
235
|
+
|
|
236
|
+
if isinstance(value, int):
|
|
237
|
+
try:
|
|
238
|
+
return enum_class(value)
|
|
239
|
+
except ValueError:
|
|
240
|
+
if raise_on_fail:
|
|
241
|
+
raise
|
|
242
|
+
return default
|
|
243
|
+
|
|
244
|
+
if raise_on_fail:
|
|
245
|
+
raise ValueError(f'{value!r} is not a valid {enum_class.__name__}')
|
|
246
|
+
|
|
247
|
+
return default
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'ip_to_int',
|
|
3
|
+
'int_to_ip',
|
|
4
|
+
]
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def ip_to_int(ip):
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
:param ip:
|
|
11
|
+
:type ip: str
|
|
12
|
+
:return:
|
|
13
|
+
:rtype: int
|
|
14
|
+
:raises ValueError: If the given string is not a valid IP address
|
|
15
|
+
"""
|
|
16
|
+
parts = ip.split('.')
|
|
17
|
+
if len(parts) != 4:
|
|
18
|
+
raise ValueError('Not a valid IP address')
|
|
19
|
+
i = 0
|
|
20
|
+
|
|
21
|
+
ii = int(parts[0])
|
|
22
|
+
if ii < 0 or ii > 255:
|
|
23
|
+
raise ValueError('Not a valid IP address')
|
|
24
|
+
i += ii << 24
|
|
25
|
+
|
|
26
|
+
ii = int(parts[1])
|
|
27
|
+
if ii < 0 or ii > 255:
|
|
28
|
+
raise ValueError('Not a valid IP address')
|
|
29
|
+
i += ii << 16
|
|
30
|
+
|
|
31
|
+
ii = int(parts[2])
|
|
32
|
+
if ii < 0 or ii > 255:
|
|
33
|
+
raise ValueError('Not a valid IP address')
|
|
34
|
+
i += ii << 8
|
|
35
|
+
|
|
36
|
+
ii = int(parts[3])
|
|
37
|
+
if ii < 0 or ii > 255:
|
|
38
|
+
raise ValueError('Not a valid IP address')
|
|
39
|
+
i += ii
|
|
40
|
+
|
|
41
|
+
return i
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def int_to_ip(i):
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
:param i:
|
|
48
|
+
:type i: int
|
|
49
|
+
:return:
|
|
50
|
+
:rtype: str
|
|
51
|
+
"""
|
|
52
|
+
d = i & 0xFF
|
|
53
|
+
i >>= 8
|
|
54
|
+
c = i & 0xFF
|
|
55
|
+
i >>= 8
|
|
56
|
+
b = i & 0xFF
|
|
57
|
+
a = i >> 8
|
|
58
|
+
return '%s.%s.%s.%s' % (a, b, c, d)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'split_bitmask',
|
|
3
|
+
]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def split_bitmask(composite_value):
|
|
7
|
+
"""Takes an int or long value representing a composite bitmask and returns
|
|
8
|
+
a list of the individual bit flag values contained in the mask.
|
|
9
|
+
|
|
10
|
+
Put another way, this splits a number into it's "power-of-two" components.
|
|
11
|
+
|
|
12
|
+
Examples:
|
|
13
|
+
---------
|
|
14
|
+
|
|
15
|
+
>>> split_bitmask(1)
|
|
16
|
+
[1]
|
|
17
|
+
>>> split_bitmask(12)
|
|
18
|
+
[4, 8]
|
|
19
|
+
>>> split_bitmask(123)
|
|
20
|
+
[1, 2, 8, 16, 32, 64]
|
|
21
|
+
|
|
22
|
+
:param composite_value:
|
|
23
|
+
:type composite_value:
|
|
24
|
+
:return:
|
|
25
|
+
:rtype:
|
|
26
|
+
"""
|
|
27
|
+
buff = []
|
|
28
|
+
bitval = 1
|
|
29
|
+
while composite_value > 0:
|
|
30
|
+
if composite_value & 1:
|
|
31
|
+
buff.append(bitval)
|
|
32
|
+
bitval *= 2
|
|
33
|
+
composite_value >>= 1
|
|
34
|
+
return buff
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'Params',
|
|
3
|
+
'parse_params',
|
|
4
|
+
]
|
|
5
|
+
|
|
6
|
+
from batutils.tpu.structs import *
|
|
7
|
+
|
|
8
|
+
_KW_ARG_MATCHER = re.compile(r'(\w+)=(.+)')
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Params(object):
|
|
12
|
+
__slots__ = ('args', 'kwargs', 'props')
|
|
13
|
+
|
|
14
|
+
def __init__(self, args=None, kwargs=None, props=None):
|
|
15
|
+
self.args = args or []
|
|
16
|
+
self.kwargs = kwargs or {}
|
|
17
|
+
self.props = props or {}
|
|
18
|
+
|
|
19
|
+
def __repr__(self):
|
|
20
|
+
return u'Params(args=%r, kwargs=%r, props=%r)' % (self.args, self.kwargs, self.props)
|
|
21
|
+
|
|
22
|
+
def __unicode__(self):
|
|
23
|
+
return self.__repr__()
|
|
24
|
+
|
|
25
|
+
def __eq__(self, other):
|
|
26
|
+
if isinstance(other, Params):
|
|
27
|
+
return all([self.args == other.args,
|
|
28
|
+
self.kwargs == other.kwargs,
|
|
29
|
+
self.props == other.props])
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def parse_params(params: Union[List[str], str],
|
|
34
|
+
list_of_props: Optional[List[str]] = None,
|
|
35
|
+
props_as_kw_dict: Optional[Dict[str, List[str]]] = None,
|
|
36
|
+
matcher: Optional[Union[re.Pattern, str]] = None):
|
|
37
|
+
"""Takes a list of string parameters or a single string with multiple
|
|
38
|
+
parameters (e.g. from command line input or template tag calls) and splits
|
|
39
|
+
up into numbered arguments, keyword arguments and property flags.
|
|
40
|
+
|
|
41
|
+
>>> parse_params(['foo', 'bar=7', 'debug'], ['debug', 'vocal'])
|
|
42
|
+
Params(args=['foo'], kwargs={'bar': '7'}, props={'debug': True, 'vocal': False})
|
|
43
|
+
|
|
44
|
+
Parameters that are in a key/val format determined by the given regex
|
|
45
|
+
matcher (defaults to "(\\w+)=(.+)" or just "key=val") will populate the
|
|
46
|
+
kwargs property of the returned Param object.
|
|
47
|
+
|
|
48
|
+
Other parameters will either get passed into the args
|
|
49
|
+
list (in order) of the returned Param object or trigger the value of the key
|
|
50
|
+
in the props dict of the returned Param object to be true if that param
|
|
51
|
+
value is in the given list_of_props.
|
|
52
|
+
|
|
53
|
+
All other values in list_of_props will be represented as keys in the props
|
|
54
|
+
dict with the value False.
|
|
55
|
+
|
|
56
|
+
The props_as_kw_dict optionally takes in a dict of string keys and a list of
|
|
57
|
+
string values that will be searched in the parameter list and if found, the
|
|
58
|
+
value will be paired with the key in the `props_as_kw_dict` and added to the
|
|
59
|
+
returned Param object as a `kwargs` pair.
|
|
60
|
+
|
|
61
|
+
>>> parse_params('foo bar warning', props_as_kw_dict={'log_level': ['info', 'warning', 'error']})
|
|
62
|
+
Params(args=['foo', 'bar'], kwargs={'log_level': 'warning'}, props={})
|
|
63
|
+
|
|
64
|
+
:param params:
|
|
65
|
+
:type params: list
|
|
66
|
+
:param list_of_props:
|
|
67
|
+
:type list_of_props: list | None
|
|
68
|
+
:param props_as_kw_dict:
|
|
69
|
+
:type props_as_kw_dict: dict | None
|
|
70
|
+
:param matcher:
|
|
71
|
+
:type matcher: re._pattern_type
|
|
72
|
+
:return:
|
|
73
|
+
:rtype: ccptools.tpu.casting.Params
|
|
74
|
+
"""
|
|
75
|
+
from batutils.tpu.string import dequote
|
|
76
|
+
|
|
77
|
+
matcher = matcher or _KW_ARG_MATCHER
|
|
78
|
+
if isinstance(matcher, str):
|
|
79
|
+
matcher = re.compile(matcher)
|
|
80
|
+
|
|
81
|
+
if isinstance(params, str):
|
|
82
|
+
params = params.split()
|
|
83
|
+
|
|
84
|
+
list_of_props = list_of_props or []
|
|
85
|
+
props_as_kw_dict = props_as_kw_dict or {}
|
|
86
|
+
if props_as_kw_dict:
|
|
87
|
+
for p_list in props_as_kw_dict.values():
|
|
88
|
+
list_of_props.extend(p_list)
|
|
89
|
+
|
|
90
|
+
p = Params(props=dict(zip(list_of_props, [False]*len(list_of_props))))
|
|
91
|
+
for a in params:
|
|
92
|
+
m = matcher.match(a)
|
|
93
|
+
if m:
|
|
94
|
+
p.kwargs[m.group(1)] = dequote(m.group(2))
|
|
95
|
+
else:
|
|
96
|
+
if a in list_of_props:
|
|
97
|
+
p.props[a] = True
|
|
98
|
+
else:
|
|
99
|
+
p.args.append(dequote(a))
|
|
100
|
+
|
|
101
|
+
if props_as_kw_dict:
|
|
102
|
+
for keyword, p_list in props_as_kw_dict.items():
|
|
103
|
+
if keyword not in p.kwargs:
|
|
104
|
+
p.kwargs[keyword] = None
|
|
105
|
+
for p_name in p_list:
|
|
106
|
+
if p.props.pop(p_name, False):
|
|
107
|
+
p.kwargs[keyword] = p_name
|
|
108
|
+
return p
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'any_to_json',
|
|
3
|
+
]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from batutils.tpu.structs import *
|
|
7
|
+
from batutils.tpu.structs.serializers import *
|
|
8
|
+
|
|
9
|
+
_serializer = JsonSerializer()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def any_to_json(anything: Any, skip_keys=None, skip_values=None, skip_private=True, skip_nones=False, skip_empties=False,
|
|
13
|
+
max_depth=0) -> str:
|
|
14
|
+
_serializer.skip_keys = skip_keys
|
|
15
|
+
_serializer.skip_values = skip_values
|
|
16
|
+
_serializer.skip_private = skip_private
|
|
17
|
+
_serializer.skip_nones = skip_nones
|
|
18
|
+
_serializer.skip_empties = skip_empties
|
|
19
|
+
_serializer.max_depth = max_depth
|
|
20
|
+
return _serializer.serialize(anything)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from ._mixin import *
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'ComparableMixin',
|
|
3
|
+
]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ComparableMixin(object):
|
|
7
|
+
"""Mixin class for types that should be comparable.
|
|
8
|
+
Subclasses must implement `__lt__`.
|
|
9
|
+
Other comparison functions can be overridden if desired.
|
|
10
|
+
"""
|
|
11
|
+
def __new__(cls, *args):
|
|
12
|
+
obj = object.__new__(cls)
|
|
13
|
+
if not hasattr(obj, '__lt__'):
|
|
14
|
+
raise NotImplementedError('__lt__ must be overridden.')
|
|
15
|
+
return obj
|
|
16
|
+
|
|
17
|
+
def __eq__(self, other):
|
|
18
|
+
return not self < other and not other < self
|
|
19
|
+
|
|
20
|
+
def __ne__(self, other):
|
|
21
|
+
return self < other or other < self
|
|
22
|
+
|
|
23
|
+
def __gt__(self, other):
|
|
24
|
+
return other < self
|
|
25
|
+
|
|
26
|
+
def __ge__(self, other):
|
|
27
|
+
return not self < other
|
|
28
|
+
|
|
29
|
+
def __le__(self, other):
|
|
30
|
+
return not other < self
|