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,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
File without changes
@@ -0,0 +1,9 @@
1
+ from batutils._common import decode_bytes # noqa
2
+ from batutils.tpu.structs.serializers import *
3
+
4
+ from ._evals import *
5
+ from ._ip import *
6
+ from ._mask import *
7
+ from ._params import *
8
+ from ._currency import *
9
+ from ._serialize import *
@@ -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
@@ -0,0 +1,2 @@
1
+ from ._insp_old import *
2
+ from ._typecheck import *