encommon 0.18.0__py3-none-any.whl → 0.19.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.
- encommon/parse/__init__.py +20 -0
- encommon/parse/jinja2.py +282 -0
- encommon/parse/network.py +453 -0
- encommon/parse/test/__init__.py +6 -0
- encommon/parse/test/test_jinja2.py +190 -0
- encommon/parse/test/test_network.py +226 -0
- encommon/types/__init__.py +4 -0
- encommon/types/lists.py +71 -0
- encommon/types/test/test_lists.py +41 -0
- encommon/version.txt +1 -1
- {encommon-0.18.0.dist-info → encommon-0.19.0.dist-info}/METADATA +3 -1
- {encommon-0.18.0.dist-info → encommon-0.19.0.dist-info}/RECORD +15 -9
- {encommon-0.18.0.dist-info → encommon-0.19.0.dist-info}/LICENSE +0 -0
- {encommon-0.18.0.dist-info → encommon-0.19.0.dist-info}/WHEEL +0 -0
- {encommon-0.18.0.dist-info → encommon-0.19.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
"""
|
2
|
+
Functions and routines associated with Enasis Network Common Library.
|
3
|
+
|
4
|
+
This file is part of Enasis Network software eco-system. Distribution
|
5
|
+
is permitted, for more information consult the project license file.
|
6
|
+
"""
|
7
|
+
|
8
|
+
|
9
|
+
|
10
|
+
from .jinja2 import Jinja2
|
11
|
+
from .network import Network
|
12
|
+
from .network import insubnet_ip
|
13
|
+
from .network import isvalid_ip
|
14
|
+
|
15
|
+
|
16
|
+
__all__ = [
|
17
|
+
'Jinja2',
|
18
|
+
'Network',
|
19
|
+
'insubnet_ip',
|
20
|
+
'isvalid_ip']
|
encommon/parse/jinja2.py
ADDED
@@ -0,0 +1,282 @@
|
|
1
|
+
"""
|
2
|
+
Functions and routines associated with Enasis Network Common Library.
|
3
|
+
|
4
|
+
This file is part of Enasis Network software eco-system. Distribution
|
5
|
+
is permitted, for more information consult the project license file.
|
6
|
+
"""
|
7
|
+
|
8
|
+
|
9
|
+
|
10
|
+
from ast import literal_eval as leval
|
11
|
+
from contextlib import suppress
|
12
|
+
from re import DOTALL
|
13
|
+
from re import findall as re_findall
|
14
|
+
from re import match as re_match
|
15
|
+
from typing import Any
|
16
|
+
from typing import Callable
|
17
|
+
from typing import Optional
|
18
|
+
|
19
|
+
from jinja2 import Environment
|
20
|
+
from jinja2 import StrictUndefined
|
21
|
+
|
22
|
+
from ..colors import Color
|
23
|
+
from ..crypts import Hashes
|
24
|
+
from ..times import Duration
|
25
|
+
from ..times import Time
|
26
|
+
from ..types import DictStrAny
|
27
|
+
from ..types import fuzzy_list
|
28
|
+
from ..utils import fuzz_match
|
29
|
+
from ..utils import rgxp_match
|
30
|
+
|
31
|
+
|
32
|
+
|
33
|
+
FILTER = Callable[..., Any]
|
34
|
+
|
35
|
+
JINJA2 = (
|
36
|
+
r'(\{\{.+?\}\})|'
|
37
|
+
r'(\{\%.+?\%\})')
|
38
|
+
|
39
|
+
LITERAL = (
|
40
|
+
r'^((\{([^\{%].+?)?\})|(\[(.+?|)\])'
|
41
|
+
'|True|False|None|'
|
42
|
+
r'(\-?([1-9]\d*|0)(\.\d+)?))$')
|
43
|
+
|
44
|
+
DEFAULT: dict[str, FILTER] = {
|
45
|
+
'Duration': Duration,
|
46
|
+
'Color': Color,
|
47
|
+
'Hashes': Hashes,
|
48
|
+
'Time': Time,
|
49
|
+
'fuzz_match': fuzz_match,
|
50
|
+
'fuzzy_list': fuzzy_list,
|
51
|
+
'rgxp_match': rgxp_match}
|
52
|
+
|
53
|
+
|
54
|
+
|
55
|
+
class Jinja2:
|
56
|
+
"""
|
57
|
+
Parse the provided input and intelligently return value.
|
58
|
+
|
59
|
+
Example
|
60
|
+
-------
|
61
|
+
>>> jinja2 = Jinja2()
|
62
|
+
>>> jinja2.parse('{{ 0 | Time}}')
|
63
|
+
'1970-01-01T00:00:00.000000+0000'
|
64
|
+
|
65
|
+
:param statics: Additional values available for parsing.
|
66
|
+
:param filters: Additional filter functions for parsing.
|
67
|
+
"""
|
68
|
+
|
69
|
+
__statics: DictStrAny
|
70
|
+
__filters: dict[str, FILTER]
|
71
|
+
|
72
|
+
__jinjenv: Environment
|
73
|
+
|
74
|
+
|
75
|
+
def __init__(
|
76
|
+
self,
|
77
|
+
statics: Optional[DictStrAny] = None,
|
78
|
+
filters: Optional[dict[str, FILTER]] = None,
|
79
|
+
) -> None:
|
80
|
+
"""
|
81
|
+
Initialize instance for class using provided parameters.
|
82
|
+
"""
|
83
|
+
|
84
|
+
statics = dict(statics or {})
|
85
|
+
filters = dict(filters or {})
|
86
|
+
|
87
|
+
items = DEFAULT.items()
|
88
|
+
|
89
|
+
for key, filter in items:
|
90
|
+
filters[key] = filter
|
91
|
+
|
92
|
+
self.__statics = statics
|
93
|
+
self.__filters = filters
|
94
|
+
|
95
|
+
jinjenv = Environment(
|
96
|
+
auto_reload=False,
|
97
|
+
autoescape=False,
|
98
|
+
cache_size=0,
|
99
|
+
extensions=[
|
100
|
+
'jinja2.ext.i18n',
|
101
|
+
'jinja2.ext.loopcontrols',
|
102
|
+
'jinja2.ext.do'],
|
103
|
+
keep_trailing_newline=False,
|
104
|
+
lstrip_blocks=False,
|
105
|
+
newline_sequence='\n',
|
106
|
+
optimized=True,
|
107
|
+
trim_blocks=False,
|
108
|
+
undefined=StrictUndefined)
|
109
|
+
|
110
|
+
jinjenv.filters |= filters
|
111
|
+
|
112
|
+
self.__jinjenv = jinjenv
|
113
|
+
|
114
|
+
|
115
|
+
@property
|
116
|
+
def statics(
|
117
|
+
self,
|
118
|
+
) -> DictStrAny:
|
119
|
+
"""
|
120
|
+
Return the value for the attribute from class instance.
|
121
|
+
|
122
|
+
:returns: Value for the attribute from class instance.
|
123
|
+
"""
|
124
|
+
|
125
|
+
return dict(self.__statics)
|
126
|
+
|
127
|
+
|
128
|
+
@property
|
129
|
+
def filters(
|
130
|
+
self,
|
131
|
+
) -> dict[str, FILTER]:
|
132
|
+
"""
|
133
|
+
Return the value for the attribute from class instance.
|
134
|
+
|
135
|
+
:returns: Value for the attribute from class instance.
|
136
|
+
"""
|
137
|
+
|
138
|
+
return dict(self.__filters)
|
139
|
+
|
140
|
+
|
141
|
+
@property
|
142
|
+
def jinjenv(
|
143
|
+
self,
|
144
|
+
) -> Environment:
|
145
|
+
"""
|
146
|
+
Return the value for the attribute from class instance.
|
147
|
+
|
148
|
+
:returns: Value for the attribute from class instance.
|
149
|
+
"""
|
150
|
+
|
151
|
+
return self.__jinjenv
|
152
|
+
|
153
|
+
|
154
|
+
def parser(
|
155
|
+
self,
|
156
|
+
value: str,
|
157
|
+
statics: Optional[DictStrAny] = None,
|
158
|
+
) -> Any:
|
159
|
+
"""
|
160
|
+
Return the provided input using the Jinja2 environment.
|
161
|
+
|
162
|
+
:param value: Input that will be processed and returned.
|
163
|
+
:param statics: Additional values available for parsing.
|
164
|
+
:returns: Provided input using the Jinja2 environment.
|
165
|
+
"""
|
166
|
+
|
167
|
+
statics = statics or {}
|
168
|
+
|
169
|
+
parser = (
|
170
|
+
self.__jinjenv
|
171
|
+
.from_string)
|
172
|
+
|
173
|
+
rendered = (
|
174
|
+
parser(value)
|
175
|
+
.render(**statics))
|
176
|
+
|
177
|
+
return rendered
|
178
|
+
|
179
|
+
|
180
|
+
def parse( # noqa: CFQ004
|
181
|
+
self,
|
182
|
+
value: Any,
|
183
|
+
statics: Optional[DictStrAny] = None,
|
184
|
+
literal: bool = True,
|
185
|
+
) -> Any:
|
186
|
+
"""
|
187
|
+
Return the provided input using the Jinja2 environment.
|
188
|
+
|
189
|
+
:param value: Input that will be processed and returned.
|
190
|
+
:param statics: Additional values available for parsing.
|
191
|
+
:param literal: Determine if Python objects are evaled.
|
192
|
+
:returns: Provided input using the Jinja2 environment.
|
193
|
+
"""
|
194
|
+
|
195
|
+
|
196
|
+
def _final( # noqa: CFQ004
|
197
|
+
value: Any,
|
198
|
+
) -> Any:
|
199
|
+
|
200
|
+
if literal is False:
|
201
|
+
return value
|
202
|
+
|
203
|
+
match = re_match(
|
204
|
+
LITERAL, str(value))
|
205
|
+
|
206
|
+
if match is None:
|
207
|
+
return value
|
208
|
+
|
209
|
+
with suppress(Exception):
|
210
|
+
return leval(value)
|
211
|
+
|
212
|
+
return value
|
213
|
+
|
214
|
+
|
215
|
+
def _parse(
|
216
|
+
value: Any,
|
217
|
+
) -> Any:
|
218
|
+
|
219
|
+
return self.parse(
|
220
|
+
value,
|
221
|
+
statics, literal)
|
222
|
+
|
223
|
+
|
224
|
+
def _parser(
|
225
|
+
value: Any,
|
226
|
+
) -> Any:
|
227
|
+
|
228
|
+
parsed = self.parser(
|
229
|
+
value, statics)
|
230
|
+
|
231
|
+
return _final(parsed)
|
232
|
+
|
233
|
+
|
234
|
+
def _found(
|
235
|
+
value: Any,
|
236
|
+
) -> list[Any]:
|
237
|
+
|
238
|
+
value = str(value)
|
239
|
+
|
240
|
+
return re_findall(
|
241
|
+
JINJA2, value, DOTALL)
|
242
|
+
|
243
|
+
|
244
|
+
if not len(_found(value)):
|
245
|
+
return _final(value)
|
246
|
+
|
247
|
+
|
248
|
+
with suppress(Exception):
|
249
|
+
value = _final(value)
|
250
|
+
|
251
|
+
|
252
|
+
if isinstance(value, dict):
|
253
|
+
|
254
|
+
value = dict(value)
|
255
|
+
|
256
|
+
items = value.items()
|
257
|
+
|
258
|
+
for key, _value in items:
|
259
|
+
|
260
|
+
_value = _parse(_value)
|
261
|
+
|
262
|
+
value[key] = _value
|
263
|
+
|
264
|
+
|
265
|
+
elif isinstance(value, list):
|
266
|
+
|
267
|
+
value = list(value)
|
268
|
+
|
269
|
+
values = enumerate(value)
|
270
|
+
|
271
|
+
for idx, _value in values:
|
272
|
+
|
273
|
+
_value = _parse(_value)
|
274
|
+
|
275
|
+
value[idx] = _value
|
276
|
+
|
277
|
+
|
278
|
+
elif value is not None:
|
279
|
+
value = _parser(value)
|
280
|
+
|
281
|
+
|
282
|
+
return value
|
@@ -0,0 +1,453 @@
|
|
1
|
+
"""
|
2
|
+
Functions and routines associated with Enasis Network Common Library.
|
3
|
+
|
4
|
+
This file is part of Enasis Network software eco-system. Distribution
|
5
|
+
is permitted, for more information consult the project license file.
|
6
|
+
"""
|
7
|
+
|
8
|
+
|
9
|
+
|
10
|
+
from contextlib import suppress
|
11
|
+
from typing import Any
|
12
|
+
from typing import Optional
|
13
|
+
|
14
|
+
from netaddr import IPAddress
|
15
|
+
from netaddr import IPNetwork
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
class Network:
|
20
|
+
"""
|
21
|
+
Convert the network into the various supported formats.
|
22
|
+
|
23
|
+
:param source: Network IPv4 or IPv6 network or address.
|
24
|
+
"""
|
25
|
+
|
26
|
+
__source: IPNetwork
|
27
|
+
|
28
|
+
|
29
|
+
def __init__(
|
30
|
+
self,
|
31
|
+
source: str,
|
32
|
+
) -> None:
|
33
|
+
"""
|
34
|
+
Initialize instance for class using provided parameters.
|
35
|
+
"""
|
36
|
+
|
37
|
+
network = IPNetwork(source)
|
38
|
+
|
39
|
+
self.__source = network
|
40
|
+
|
41
|
+
|
42
|
+
def __repr__(
|
43
|
+
self,
|
44
|
+
) -> str:
|
45
|
+
"""
|
46
|
+
Built-in method for representing the values for instance.
|
47
|
+
|
48
|
+
:returns: String representation for values from instance.
|
49
|
+
"""
|
50
|
+
|
51
|
+
return f"Network('{self.address_cidr}')"
|
52
|
+
|
53
|
+
|
54
|
+
def __hash__(
|
55
|
+
self,
|
56
|
+
) -> int:
|
57
|
+
"""
|
58
|
+
Built-in method called when performing hashing operation.
|
59
|
+
|
60
|
+
:returns: Boolean indicating outcome from the operation.
|
61
|
+
"""
|
62
|
+
|
63
|
+
return hash(self.__source)
|
64
|
+
|
65
|
+
|
66
|
+
def __str__(
|
67
|
+
self,
|
68
|
+
) -> str:
|
69
|
+
"""
|
70
|
+
Built-in method for representing the values for instance.
|
71
|
+
|
72
|
+
:returns: String representation for values from instance.
|
73
|
+
"""
|
74
|
+
|
75
|
+
return self.address_cidr
|
76
|
+
|
77
|
+
|
78
|
+
def __eq__(
|
79
|
+
self,
|
80
|
+
other: object,
|
81
|
+
) -> bool:
|
82
|
+
"""
|
83
|
+
Built-in method for comparing this instance with another.
|
84
|
+
|
85
|
+
:param other: Other value being compared with instance.
|
86
|
+
:returns: Boolean indicating outcome from the operation.
|
87
|
+
"""
|
88
|
+
|
89
|
+
source = self.__source
|
90
|
+
|
91
|
+
with suppress(Exception):
|
92
|
+
|
93
|
+
other = IPNetwork(str(other))
|
94
|
+
|
95
|
+
return source == other
|
96
|
+
|
97
|
+
return False
|
98
|
+
|
99
|
+
|
100
|
+
def __ne__(
|
101
|
+
self,
|
102
|
+
other: object,
|
103
|
+
) -> bool:
|
104
|
+
"""
|
105
|
+
Built-in method for comparing this instance with another.
|
106
|
+
|
107
|
+
:param other: Other value being compared with instance.
|
108
|
+
:returns: Boolean indicating outcome from the operation.
|
109
|
+
"""
|
110
|
+
|
111
|
+
return not self.__eq__(other)
|
112
|
+
|
113
|
+
|
114
|
+
@property
|
115
|
+
def source(
|
116
|
+
self,
|
117
|
+
) -> IPNetwork:
|
118
|
+
"""
|
119
|
+
Return the value for the attribute from class instance.
|
120
|
+
|
121
|
+
:returns: Value for the attribute from class instance.
|
122
|
+
"""
|
123
|
+
|
124
|
+
return self.__source
|
125
|
+
|
126
|
+
|
127
|
+
@property
|
128
|
+
def version(
|
129
|
+
self,
|
130
|
+
) -> int:
|
131
|
+
"""
|
132
|
+
Return the value for the attribute from class instance.
|
133
|
+
|
134
|
+
:returns: Value for the attribute from class instance.
|
135
|
+
"""
|
136
|
+
|
137
|
+
return self.__source.version
|
138
|
+
|
139
|
+
|
140
|
+
@property
|
141
|
+
def cidr(
|
142
|
+
self,
|
143
|
+
) -> int:
|
144
|
+
"""
|
145
|
+
Return the value for the attribute from class instance.
|
146
|
+
|
147
|
+
:returns: Value for the attribute from class instance.
|
148
|
+
"""
|
149
|
+
|
150
|
+
source = self.__source
|
151
|
+
|
152
|
+
return source.prefixlen
|
153
|
+
|
154
|
+
|
155
|
+
@property
|
156
|
+
def address(
|
157
|
+
self,
|
158
|
+
) -> str:
|
159
|
+
"""
|
160
|
+
Return the value for the attribute from class instance.
|
161
|
+
|
162
|
+
:returns: Value for the attribute from class instance.
|
163
|
+
"""
|
164
|
+
|
165
|
+
source = self.__source
|
166
|
+
|
167
|
+
return str(source.ip)
|
168
|
+
|
169
|
+
|
170
|
+
@property
|
171
|
+
def address_cidr(
|
172
|
+
self,
|
173
|
+
) -> str:
|
174
|
+
"""
|
175
|
+
Return the value for the attribute from class instance.
|
176
|
+
|
177
|
+
:returns: Value for the attribute from class instance.
|
178
|
+
"""
|
179
|
+
|
180
|
+
address = self.address
|
181
|
+
|
182
|
+
return f'{address}/{self.cidr}'
|
183
|
+
|
184
|
+
|
185
|
+
@property
|
186
|
+
def address_host(
|
187
|
+
self,
|
188
|
+
) -> str:
|
189
|
+
"""
|
190
|
+
Return the value for the attribute from class instance.
|
191
|
+
|
192
|
+
:returns: Value for the attribute from class instance.
|
193
|
+
"""
|
194
|
+
|
195
|
+
address = self.address
|
196
|
+
|
197
|
+
prefix = (
|
198
|
+
128
|
199
|
+
if self.version == 6
|
200
|
+
else 32)
|
201
|
+
|
202
|
+
return f'{address}/{prefix}'
|
203
|
+
|
204
|
+
|
205
|
+
@property
|
206
|
+
def network(
|
207
|
+
self,
|
208
|
+
) -> str:
|
209
|
+
"""
|
210
|
+
Return the value for the attribute from class instance.
|
211
|
+
|
212
|
+
:returns: Value for the attribute from class instance.
|
213
|
+
"""
|
214
|
+
|
215
|
+
source = self.__source
|
216
|
+
|
217
|
+
return str(source.network)
|
218
|
+
|
219
|
+
|
220
|
+
@property
|
221
|
+
def network_cidr(
|
222
|
+
self,
|
223
|
+
) -> str:
|
224
|
+
"""
|
225
|
+
Return the value for the attribute from class instance.
|
226
|
+
|
227
|
+
:returns: Value for the attribute from class instance.
|
228
|
+
"""
|
229
|
+
|
230
|
+
network = self.network
|
231
|
+
|
232
|
+
return f'{network}/{self.cidr}'
|
233
|
+
|
234
|
+
|
235
|
+
@property
|
236
|
+
def broadcast(
|
237
|
+
self,
|
238
|
+
) -> Optional[str]:
|
239
|
+
"""
|
240
|
+
Return the value for the attribute from class instance.
|
241
|
+
|
242
|
+
:returns: Value for the attribute from class instance.
|
243
|
+
"""
|
244
|
+
|
245
|
+
source = self.__source
|
246
|
+
|
247
|
+
address = source.broadcast
|
248
|
+
|
249
|
+
if address is None:
|
250
|
+
return None
|
251
|
+
|
252
|
+
return str(address)
|
253
|
+
|
254
|
+
|
255
|
+
@property
|
256
|
+
def padded(
|
257
|
+
self,
|
258
|
+
) -> str:
|
259
|
+
"""
|
260
|
+
Return the value for the attribute from class instance.
|
261
|
+
|
262
|
+
:returns: Value for the attribute from class instance.
|
263
|
+
"""
|
264
|
+
|
265
|
+
if self.version != 4:
|
266
|
+
raise ValueError('version')
|
267
|
+
|
268
|
+
address = self.address
|
269
|
+
|
270
|
+
octets = address.split('.')
|
271
|
+
|
272
|
+
pads = [
|
273
|
+
x.zfill(3)
|
274
|
+
for x in octets]
|
275
|
+
|
276
|
+
return '.'.join(pads)
|
277
|
+
|
278
|
+
|
279
|
+
@property
|
280
|
+
def reverse(
|
281
|
+
self,
|
282
|
+
) -> str:
|
283
|
+
"""
|
284
|
+
Return the value for the attribute from class instance.
|
285
|
+
|
286
|
+
:returns: Value for the attribute from class instance.
|
287
|
+
"""
|
288
|
+
|
289
|
+
if self.version != 4:
|
290
|
+
raise ValueError('version')
|
291
|
+
|
292
|
+
|
293
|
+
address = self.address
|
294
|
+
|
295
|
+
octets = address.split('.')
|
296
|
+
|
297
|
+
reverse = list(reversed(octets))
|
298
|
+
|
299
|
+
return '.'.join(reverse)
|
300
|
+
|
301
|
+
|
302
|
+
@property
|
303
|
+
def hwaddr(
|
304
|
+
self,
|
305
|
+
) -> str:
|
306
|
+
"""
|
307
|
+
Return the value for the attribute from class instance.
|
308
|
+
|
309
|
+
:returns: Value for the attribute from class instance.
|
310
|
+
"""
|
311
|
+
|
312
|
+
if self.version != 4:
|
313
|
+
raise ValueError('version')
|
314
|
+
|
315
|
+
padded = self.padded
|
316
|
+
|
317
|
+
nodots = (
|
318
|
+
padded
|
319
|
+
.replace('.', ''))
|
320
|
+
|
321
|
+
ranged = range(
|
322
|
+
0, len(nodots), 2)
|
323
|
+
|
324
|
+
pairs = [
|
325
|
+
nodots[x:x + 2]
|
326
|
+
for x in ranged]
|
327
|
+
|
328
|
+
return '-'.join(pairs)
|
329
|
+
|
330
|
+
|
331
|
+
@property
|
332
|
+
def netmask(
|
333
|
+
self,
|
334
|
+
) -> str:
|
335
|
+
"""
|
336
|
+
Return the value for the attribute from class instance.
|
337
|
+
|
338
|
+
:returns: Value for the attribute from class instance.
|
339
|
+
"""
|
340
|
+
|
341
|
+
source = self.__source
|
342
|
+
|
343
|
+
return str(source.netmask)
|
344
|
+
|
345
|
+
|
346
|
+
|
347
|
+
@property
|
348
|
+
def ispublic(
|
349
|
+
self,
|
350
|
+
) -> bool:
|
351
|
+
"""
|
352
|
+
Return the boolean indicating whether instance is state.
|
353
|
+
"""
|
354
|
+
|
355
|
+
return (
|
356
|
+
self.source.ip
|
357
|
+
.is_global())
|
358
|
+
|
359
|
+
|
360
|
+
@property
|
361
|
+
def isprivate(
|
362
|
+
self,
|
363
|
+
) -> bool:
|
364
|
+
"""
|
365
|
+
Return the boolean indicating whether instance is state.
|
366
|
+
"""
|
367
|
+
|
368
|
+
return not self.ispublic
|
369
|
+
|
370
|
+
|
371
|
+
@property
|
372
|
+
def islinklocal(
|
373
|
+
self,
|
374
|
+
) -> bool:
|
375
|
+
"""
|
376
|
+
Return the boolean indicating whether instance is state.
|
377
|
+
"""
|
378
|
+
|
379
|
+
return (
|
380
|
+
self.source
|
381
|
+
.is_link_local())
|
382
|
+
|
383
|
+
|
384
|
+
@property
|
385
|
+
def islocalhost(
|
386
|
+
self,
|
387
|
+
) -> bool:
|
388
|
+
"""
|
389
|
+
Return the boolean indicating whether instance is state.
|
390
|
+
"""
|
391
|
+
|
392
|
+
return (
|
393
|
+
self.source
|
394
|
+
.is_loopback())
|
395
|
+
|
396
|
+
|
397
|
+
|
398
|
+
def insubnet_ip(
|
399
|
+
address: str,
|
400
|
+
networks: str | list[str] | tuple[str, ...],
|
401
|
+
) -> bool:
|
402
|
+
"""
|
403
|
+
Return the boolean indicating address is in the network.
|
404
|
+
|
405
|
+
:param address: Provided address to find in the network.
|
406
|
+
:param network: Networks which values can be within any.
|
407
|
+
:returns: Boolean indicating address is in the network.
|
408
|
+
"""
|
409
|
+
|
410
|
+
if isinstance(networks, str):
|
411
|
+
networks = [networks]
|
412
|
+
|
413
|
+
networks = list(networks)
|
414
|
+
|
415
|
+
if not isvalid_ip(address):
|
416
|
+
raise ValueError('address')
|
417
|
+
|
418
|
+
naddr = Network(address)
|
419
|
+
|
420
|
+
if (naddr.version == 4
|
421
|
+
and naddr.cidr == 32):
|
422
|
+
address = naddr.address
|
423
|
+
|
424
|
+
if (naddr.version == 6
|
425
|
+
and naddr.cidr == 128):
|
426
|
+
address = naddr.address
|
427
|
+
|
428
|
+
parsed = IPAddress(address)
|
429
|
+
|
430
|
+
return any(
|
431
|
+
parsed in IPNetwork(x)
|
432
|
+
for x in networks)
|
433
|
+
|
434
|
+
|
435
|
+
|
436
|
+
def isvalid_ip(
|
437
|
+
value: Any, # noqa: ANN401
|
438
|
+
) -> bool:
|
439
|
+
"""
|
440
|
+
Return the boolean indicating whether the value is valid.
|
441
|
+
|
442
|
+
:param value: Value that will be validated as an address.
|
443
|
+
:returns: Boolean indicating whether the value is valid.
|
444
|
+
"""
|
445
|
+
|
446
|
+
value = str(value)
|
447
|
+
|
448
|
+
try:
|
449
|
+
Network(value)
|
450
|
+
return True
|
451
|
+
|
452
|
+
except Exception:
|
453
|
+
return False
|
@@ -0,0 +1,190 @@
|
|
1
|
+
"""
|
2
|
+
Functions and routines associated with Enasis Network Common Library.
|
3
|
+
|
4
|
+
This file is part of Enasis Network software eco-system. Distribution
|
5
|
+
is permitted, for more information consult the project license file.
|
6
|
+
"""
|
7
|
+
|
8
|
+
|
9
|
+
|
10
|
+
from typing import Any
|
11
|
+
|
12
|
+
from pytest import fixture
|
13
|
+
from pytest import mark
|
14
|
+
|
15
|
+
from ..jinja2 import Jinja2
|
16
|
+
from ... import PROJECT
|
17
|
+
from ...times.common import UNIXMPOCH
|
18
|
+
from ...types import inrepr
|
19
|
+
from ...types import instr
|
20
|
+
from ...types import lattrs
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
@fixture
|
25
|
+
def jinja2() -> Jinja2:
|
26
|
+
"""
|
27
|
+
Construct the instance for use in the downstream tests.
|
28
|
+
|
29
|
+
:returns: Newly constructed instance of related class.
|
30
|
+
"""
|
31
|
+
|
32
|
+
|
33
|
+
def strval(
|
34
|
+
# NOCVR,
|
35
|
+
input: str,
|
36
|
+
) -> str:
|
37
|
+
return str(input)
|
38
|
+
|
39
|
+
|
40
|
+
return Jinja2(
|
41
|
+
{'PROJECT': PROJECT},
|
42
|
+
{'strval': strval})
|
43
|
+
|
44
|
+
|
45
|
+
|
46
|
+
def test_Jinja2(
|
47
|
+
jinja2: Jinja2,
|
48
|
+
) -> None:
|
49
|
+
"""
|
50
|
+
Perform various tests associated with relevant routines.
|
51
|
+
|
52
|
+
:param jinja2: Parsing class for the Jinja2 templating.
|
53
|
+
"""
|
54
|
+
|
55
|
+
|
56
|
+
attrs = lattrs(jinja2)
|
57
|
+
|
58
|
+
assert attrs == [
|
59
|
+
'_Jinja2__statics',
|
60
|
+
'_Jinja2__filters',
|
61
|
+
'_Jinja2__jinjenv']
|
62
|
+
|
63
|
+
|
64
|
+
assert inrepr(
|
65
|
+
'jinja2.Jinja2 object',
|
66
|
+
jinja2)
|
67
|
+
|
68
|
+
assert hash(jinja2) > 0
|
69
|
+
|
70
|
+
assert instr(
|
71
|
+
'jinja2.Jinja2 object',
|
72
|
+
jinja2)
|
73
|
+
|
74
|
+
|
75
|
+
assert jinja2.statics
|
76
|
+
|
77
|
+
assert jinja2.filters
|
78
|
+
|
79
|
+
assert jinja2.jinjenv
|
80
|
+
|
81
|
+
assert jinja2.parse('1') == 1
|
82
|
+
|
83
|
+
|
84
|
+
|
85
|
+
def test_Jinja2_recurse(
|
86
|
+
jinja2: Jinja2,
|
87
|
+
) -> None:
|
88
|
+
"""
|
89
|
+
Perform various tests associated with relevant routines.
|
90
|
+
|
91
|
+
:param jinja2: Parsing class for the Jinja2 templating.
|
92
|
+
"""
|
93
|
+
|
94
|
+
parse = jinja2.parse
|
95
|
+
|
96
|
+
source = {
|
97
|
+
'dict': {'key': '{{ "val" }}'},
|
98
|
+
'list': [1, '{{ 2 }}', 3, '4'],
|
99
|
+
'deep': '{{ {"foo": "bar"} }}'}
|
100
|
+
|
101
|
+
parsed = parse(source)
|
102
|
+
|
103
|
+
assert parsed == {
|
104
|
+
'dict': {'key': 'val'},
|
105
|
+
'list': [1, 2, 3, 4],
|
106
|
+
'deep': {'foo': 'bar'}}
|
107
|
+
|
108
|
+
_parsed = parse(f'{source}')
|
109
|
+
|
110
|
+
assert _parsed == parsed
|
111
|
+
|
112
|
+
|
113
|
+
|
114
|
+
@mark.parametrize(
|
115
|
+
'value,expect',
|
116
|
+
[('{{ none }}', None),
|
117
|
+
('{{ 123 }}', 123),
|
118
|
+
('{{ 1.2 }}', 1.2),
|
119
|
+
('{{ -1.2 }}', -1.2)])
|
120
|
+
def test_Jinja2_literal(
|
121
|
+
jinja2: Jinja2,
|
122
|
+
value: Any, # noqa: ANN401
|
123
|
+
expect: Any, # noqa: ANN401
|
124
|
+
) -> None:
|
125
|
+
"""
|
126
|
+
Perform various tests associated with relevant routines.
|
127
|
+
|
128
|
+
:param jinja2: Parsing class for the Jinja2 templating.
|
129
|
+
:param value: Input that will be processed and returned.
|
130
|
+
:param expect: Expected output from the testing routine.
|
131
|
+
"""
|
132
|
+
|
133
|
+
parse = jinja2.parse
|
134
|
+
|
135
|
+
parsed = parse(value)
|
136
|
+
|
137
|
+
parsed = parse(
|
138
|
+
value,
|
139
|
+
literal=False)
|
140
|
+
|
141
|
+
assert parsed == str(expect)
|
142
|
+
|
143
|
+
parsed = parse(
|
144
|
+
value,
|
145
|
+
literal=True)
|
146
|
+
|
147
|
+
assert parsed == expect
|
148
|
+
|
149
|
+
|
150
|
+
|
151
|
+
@mark.parametrize(
|
152
|
+
'value,expect',
|
153
|
+
[('foo', 'foo'),
|
154
|
+
('1.0.0', '1.0.0'),
|
155
|
+
('1', 1),
|
156
|
+
('1.0', 1.0),
|
157
|
+
({'a': 'b'}, {'a': 'b'}),
|
158
|
+
("{'a': 'b'}", {'a': 'b'}),
|
159
|
+
('{{ 1 }}', 1),
|
160
|
+
('{{ "1" | float }}', 1.0),
|
161
|
+
('{{ "1" | int }}', 1),
|
162
|
+
('{{ 0 | Time }}', UNIXMPOCH),
|
163
|
+
(100000, 100000),
|
164
|
+
('100000', 100000),
|
165
|
+
(10.001, 10.001),
|
166
|
+
('10.001', 10.001),
|
167
|
+
([1, 2], [1, 2]),
|
168
|
+
('[1, 2]', [1, 2]),
|
169
|
+
('01', '01'),
|
170
|
+
('{{ "01" }}', '01'),
|
171
|
+
('-01', '-01'),
|
172
|
+
('{{ "-01" }}', '-01')])
|
173
|
+
def test_Jinja2_cover(
|
174
|
+
jinja2: Jinja2,
|
175
|
+
value: Any, # noqa: ANN401
|
176
|
+
expect: Any, # noqa: ANN401
|
177
|
+
) -> None:
|
178
|
+
"""
|
179
|
+
Perform various tests associated with relevant routines.
|
180
|
+
|
181
|
+
:param jinja2: Parsing class for the Jinja2 templating.
|
182
|
+
:param value: Input that will be processed and returned.
|
183
|
+
:param expect: Expected output from the testing routine.
|
184
|
+
"""
|
185
|
+
|
186
|
+
parse = jinja2.parse
|
187
|
+
|
188
|
+
parsed = parse(value)
|
189
|
+
|
190
|
+
assert parsed == expect
|
@@ -0,0 +1,226 @@
|
|
1
|
+
"""
|
2
|
+
Functions and routines associated with Enasis Network Common Library.
|
3
|
+
|
4
|
+
This file is part of Enasis Network software eco-system. Distribution
|
5
|
+
is permitted, for more information consult the project license file.
|
6
|
+
"""
|
7
|
+
|
8
|
+
|
9
|
+
|
10
|
+
from pytest import raises
|
11
|
+
|
12
|
+
from ..network import Network
|
13
|
+
from ..network import insubnet_ip
|
14
|
+
from ..network import isvalid_ip
|
15
|
+
from ...types import inrepr
|
16
|
+
from ...types import instr
|
17
|
+
from ...types import lattrs
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
def test_Network() -> None:
|
22
|
+
"""
|
23
|
+
Perform various tests associated with relevant routines.
|
24
|
+
"""
|
25
|
+
|
26
|
+
|
27
|
+
naddr = Network('12.34.56.7')
|
28
|
+
|
29
|
+
|
30
|
+
attrs = lattrs(naddr)
|
31
|
+
|
32
|
+
assert attrs == [
|
33
|
+
'_Network__source']
|
34
|
+
|
35
|
+
|
36
|
+
assert inrepr(
|
37
|
+
"Network('12.34.",
|
38
|
+
naddr)
|
39
|
+
|
40
|
+
assert hash(naddr) > 0
|
41
|
+
|
42
|
+
assert instr(
|
43
|
+
'12.34.56.7/32',
|
44
|
+
naddr)
|
45
|
+
|
46
|
+
|
47
|
+
assert naddr == '12.34.56.7'
|
48
|
+
assert naddr != 'invalid'
|
49
|
+
|
50
|
+
|
51
|
+
assert naddr.source
|
52
|
+
|
53
|
+
assert naddr.version == 4
|
54
|
+
|
55
|
+
assert naddr.cidr == 32
|
56
|
+
|
57
|
+
assert naddr.address == '12.34.56.7'
|
58
|
+
|
59
|
+
assert naddr.address_cidr == '12.34.56.7/32'
|
60
|
+
|
61
|
+
assert naddr.address_host == '12.34.56.7/32'
|
62
|
+
|
63
|
+
assert naddr.network == '12.34.56.7'
|
64
|
+
|
65
|
+
assert naddr.network_cidr == '12.34.56.7/32'
|
66
|
+
|
67
|
+
assert not naddr.broadcast
|
68
|
+
|
69
|
+
assert naddr.netmask == '255.255.255.255'
|
70
|
+
|
71
|
+
assert naddr.padded == '012.034.056.007'
|
72
|
+
|
73
|
+
assert naddr.reverse == '7.56.34.12'
|
74
|
+
|
75
|
+
assert naddr.hwaddr == '01-20-34-05-60-07'
|
76
|
+
|
77
|
+
assert naddr.ispublic
|
78
|
+
|
79
|
+
assert not naddr.isprivate
|
80
|
+
|
81
|
+
assert not naddr.islinklocal
|
82
|
+
|
83
|
+
assert not naddr.islocalhost
|
84
|
+
|
85
|
+
|
86
|
+
|
87
|
+
def test_Network_ipv4() -> None:
|
88
|
+
"""
|
89
|
+
Perform various tests associated with relevant routines.
|
90
|
+
"""
|
91
|
+
|
92
|
+
|
93
|
+
naddr = Network('12.34.56.7/24')
|
94
|
+
|
95
|
+
|
96
|
+
assert naddr.cidr == 24
|
97
|
+
|
98
|
+
assert naddr.address == '12.34.56.7'
|
99
|
+
|
100
|
+
assert naddr.address_cidr == '12.34.56.7/24'
|
101
|
+
|
102
|
+
assert naddr.address_host == '12.34.56.7/32'
|
103
|
+
|
104
|
+
assert naddr.network == '12.34.56.0'
|
105
|
+
|
106
|
+
assert naddr.network_cidr == '12.34.56.0/24'
|
107
|
+
|
108
|
+
assert naddr.broadcast == '12.34.56.255'
|
109
|
+
|
110
|
+
assert naddr.netmask == '255.255.255.0'
|
111
|
+
|
112
|
+
assert naddr.padded == '012.034.056.007'
|
113
|
+
|
114
|
+
assert naddr.reverse == '7.56.34.12'
|
115
|
+
|
116
|
+
assert naddr.hwaddr == '01-20-34-05-60-07'
|
117
|
+
|
118
|
+
assert naddr.ispublic
|
119
|
+
|
120
|
+
assert not naddr.isprivate
|
121
|
+
|
122
|
+
assert not naddr.islinklocal
|
123
|
+
|
124
|
+
assert not naddr.islocalhost
|
125
|
+
|
126
|
+
|
127
|
+
|
128
|
+
def test_Network_ipv6() -> None:
|
129
|
+
"""
|
130
|
+
Perform various tests associated with relevant routines.
|
131
|
+
"""
|
132
|
+
|
133
|
+
|
134
|
+
naddr = Network('2001:db8::/64')
|
135
|
+
|
136
|
+
|
137
|
+
assert naddr.cidr == 64
|
138
|
+
|
139
|
+
assert naddr.address == '2001:db8::'
|
140
|
+
|
141
|
+
assert naddr.address_cidr == '2001:db8::/64'
|
142
|
+
|
143
|
+
assert naddr.address_host == '2001:db8::/128'
|
144
|
+
|
145
|
+
assert naddr.network == '2001:db8::'
|
146
|
+
|
147
|
+
assert naddr.network_cidr == '2001:db8::/64'
|
148
|
+
|
149
|
+
assert naddr.broadcast == (
|
150
|
+
'2001:db8::ffff:ffff:ffff:ffff')
|
151
|
+
|
152
|
+
assert naddr.netmask == (
|
153
|
+
'ffff:ffff:ffff:ffff::')
|
154
|
+
|
155
|
+
with raises(ValueError):
|
156
|
+
naddr.padded
|
157
|
+
|
158
|
+
with raises(ValueError):
|
159
|
+
naddr.reverse
|
160
|
+
|
161
|
+
with raises(ValueError):
|
162
|
+
naddr.hwaddr
|
163
|
+
|
164
|
+
assert not naddr.ispublic
|
165
|
+
|
166
|
+
assert naddr.isprivate
|
167
|
+
|
168
|
+
assert not naddr.islinklocal
|
169
|
+
|
170
|
+
assert not naddr.islocalhost
|
171
|
+
|
172
|
+
|
173
|
+
|
174
|
+
def test_isvalid_ip() -> None:
|
175
|
+
"""
|
176
|
+
Perform various tests associated with relevant routines.
|
177
|
+
"""
|
178
|
+
|
179
|
+
assert isvalid_ip('1.2.3.4')
|
180
|
+
|
181
|
+
assert not isvalid_ip('1.2')
|
182
|
+
|
183
|
+
assert not isvalid_ip(None)
|
184
|
+
|
185
|
+
|
186
|
+
|
187
|
+
def test_insubnet_ip() -> None:
|
188
|
+
"""
|
189
|
+
Perform various tests associated with relevant routines.
|
190
|
+
"""
|
191
|
+
|
192
|
+
assert insubnet_ip(
|
193
|
+
'192.168.1.1',
|
194
|
+
['172.16.0.0/12',
|
195
|
+
'192.168.0.0/16'])
|
196
|
+
|
197
|
+
assert not insubnet_ip(
|
198
|
+
'1.2.3.4',
|
199
|
+
['172.16.0.0/12',
|
200
|
+
'192.168.0.0/16'])
|
201
|
+
|
202
|
+
assert insubnet_ip(
|
203
|
+
'192.168.1.1',
|
204
|
+
['192.168.1.1/32'])
|
205
|
+
|
206
|
+
assert insubnet_ip(
|
207
|
+
'192.168.1.1/32',
|
208
|
+
'192.168.1.1/32')
|
209
|
+
|
210
|
+
assert insubnet_ip(
|
211
|
+
'2001:db8::',
|
212
|
+
['2001:db8::/128'])
|
213
|
+
|
214
|
+
assert insubnet_ip(
|
215
|
+
'2001:db8::',
|
216
|
+
['2001:db8::/32'])
|
217
|
+
|
218
|
+
assert insubnet_ip(
|
219
|
+
'2001:db8::/128',
|
220
|
+
['2001:db8::/32'])
|
221
|
+
|
222
|
+
with raises(ValueError):
|
223
|
+
|
224
|
+
insubnet_ip(
|
225
|
+
'invalid',
|
226
|
+
['2001:db8::/32'])
|
encommon/types/__init__.py
CHANGED
@@ -13,6 +13,8 @@ from .classes import lattrs
|
|
13
13
|
from .dicts import merge_dicts
|
14
14
|
from .dicts import sort_dict
|
15
15
|
from .empty import Empty
|
16
|
+
from .lists import dedup_list
|
17
|
+
from .lists import fuzzy_list
|
16
18
|
from .lists import inlist
|
17
19
|
from .notate import delate
|
18
20
|
from .notate import getate
|
@@ -32,6 +34,8 @@ from .types import NCTrue
|
|
32
34
|
__all__ = [
|
33
35
|
'BaseModel',
|
34
36
|
'clsname',
|
37
|
+
'dedup_list',
|
38
|
+
'fuzzy_list',
|
35
39
|
'delate',
|
36
40
|
'DictStrAny',
|
37
41
|
'Empty',
|
encommon/types/lists.py
CHANGED
@@ -12,6 +12,77 @@ from typing import Sequence
|
|
12
12
|
|
13
13
|
|
14
14
|
|
15
|
+
def dedup_list(
|
16
|
+
value: list[Any],
|
17
|
+
update: bool = True,
|
18
|
+
) -> list[Any]:
|
19
|
+
"""
|
20
|
+
Return the provided list values with duplicates removed.
|
21
|
+
|
22
|
+
.. warning::
|
23
|
+
This function will update the ``value`` reference.
|
24
|
+
|
25
|
+
:param value: List of values processed for duplication.
|
26
|
+
:returns: Provided list values with duplicates removed.
|
27
|
+
"""
|
28
|
+
|
29
|
+
if update is False:
|
30
|
+
|
31
|
+
dedup = dict.fromkeys(value)
|
32
|
+
|
33
|
+
return list(dedup)
|
34
|
+
|
35
|
+
|
36
|
+
unique: list[Any] = []
|
37
|
+
|
38
|
+
for item in list(value):
|
39
|
+
|
40
|
+
if item in unique:
|
41
|
+
|
42
|
+
value.remove(item)
|
43
|
+
|
44
|
+
continue
|
45
|
+
|
46
|
+
unique.append(item)
|
47
|
+
|
48
|
+
|
49
|
+
return value
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
def fuzzy_list(
|
54
|
+
values: str | list[str],
|
55
|
+
patterns: str | list[str],
|
56
|
+
) -> list[str]:
|
57
|
+
"""
|
58
|
+
Return the provided values that match provided patterns.
|
59
|
+
|
60
|
+
:param values: Value or values to enumerate for matching.
|
61
|
+
:param patterns: Patterns which values can match any one.
|
62
|
+
:returns: Provided values that match provided patterns.
|
63
|
+
"""
|
64
|
+
|
65
|
+
from ..utils import fuzz_match
|
66
|
+
|
67
|
+
if isinstance(values, str):
|
68
|
+
values = [values]
|
69
|
+
|
70
|
+
matched: list[str] = []
|
71
|
+
|
72
|
+
for value in values:
|
73
|
+
|
74
|
+
match = fuzz_match(
|
75
|
+
value, patterns)
|
76
|
+
|
77
|
+
if match is False:
|
78
|
+
continue
|
79
|
+
|
80
|
+
matched.append(value)
|
81
|
+
|
82
|
+
return matched
|
83
|
+
|
84
|
+
|
85
|
+
|
15
86
|
def inlist(
|
16
87
|
needle: Any, # noqa: ANN401
|
17
88
|
haystack: Sequence[Any],
|
@@ -7,6 +7,8 @@ is permitted, for more information consult the project license file.
|
|
7
7
|
|
8
8
|
|
9
9
|
|
10
|
+
from ..lists import dedup_list
|
11
|
+
from ..lists import fuzzy_list
|
10
12
|
from ..lists import inlist
|
11
13
|
|
12
14
|
|
@@ -21,3 +23,42 @@ def test_inlist() -> None:
|
|
21
23
|
haystack = [123, 456]
|
22
24
|
|
23
25
|
assert inlist(needle, haystack)
|
26
|
+
|
27
|
+
|
28
|
+
|
29
|
+
def test_dedup_list() -> None:
|
30
|
+
"""
|
31
|
+
Perform various tests associated with relevant routines.
|
32
|
+
"""
|
33
|
+
|
34
|
+
value = [1, 1, '2', 2, 3, 3]
|
35
|
+
|
36
|
+
dedup = dedup_list(
|
37
|
+
value,
|
38
|
+
update=False)
|
39
|
+
|
40
|
+
assert dedup != value
|
41
|
+
assert dedup == [1, '2', 2, 3]
|
42
|
+
|
43
|
+
dedup_list(value)
|
44
|
+
|
45
|
+
assert dedup == value
|
46
|
+
assert dedup == [1, '2', 2, 3]
|
47
|
+
|
48
|
+
|
49
|
+
|
50
|
+
def test_fuzzy_list() -> None:
|
51
|
+
"""
|
52
|
+
Perform various tests associated with relevant routines.
|
53
|
+
"""
|
54
|
+
|
55
|
+
values = ['1', '2']
|
56
|
+
patterns = ['1*']
|
57
|
+
|
58
|
+
matched = fuzzy_list(
|
59
|
+
values, patterns)
|
60
|
+
|
61
|
+
assert matched == ['1']
|
62
|
+
|
63
|
+
assert not (
|
64
|
+
fuzzy_list('', []))
|
encommon/version.txt
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.19.0
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: encommon
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.19.0
|
4
4
|
Summary: Enasis Network Common Library
|
5
5
|
License: MIT
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
@@ -11,6 +11,8 @@ Description-Content-Type: text/markdown
|
|
11
11
|
License-File: LICENSE
|
12
12
|
Requires-Dist: croniter
|
13
13
|
Requires-Dist: cryptography
|
14
|
+
Requires-Dist: jinja2
|
15
|
+
Requires-Dist: netaddr
|
14
16
|
Requires-Dist: pydantic
|
15
17
|
Requires-Dist: python-dateutil
|
16
18
|
Requires-Dist: pyyaml
|
@@ -1,7 +1,7 @@
|
|
1
1
|
encommon/__init__.py,sha256=YDGzuhpk5Gd1hq54LI0hw1NrrDvrJDrvH20TEy_0l5E,443
|
2
2
|
encommon/conftest.py,sha256=qorgldYdoDt_LFQupdT0ZUF5eAVPuJ5X3Jvv4VIa78Q,1900
|
3
3
|
encommon/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
|
-
encommon/version.txt,sha256=
|
4
|
+
encommon/version.txt,sha256=BsU91PIrLgA4yiu5rzLMGrfmmJjZnMO_oivZZfIarI8,7
|
5
5
|
encommon/colors/__init__.py,sha256=XRiGimMj8oo040NO5a5ZsbsIUxaGVW4tf4xWTPWgnZY,269
|
6
6
|
encommon/colors/color.py,sha256=EiUxNbVL1689Cqhw1LmO9ysmN3ulCVtGZGylyV8LuVA,10884
|
7
7
|
encommon/colors/test/__init__.py,sha256=PjrnBYT0efyvbaGeNx94dm3tP3EVHUHSVs-VGeLEv5g,218
|
@@ -26,6 +26,12 @@ encommon/crypts/params.py,sha256=2uH_i1NBMlzhq7UtWgX2jsiUGNniOK69eTCeeiQFpdw,141
|
|
26
26
|
encommon/crypts/test/__init__.py,sha256=PjrnBYT0efyvbaGeNx94dm3tP3EVHUHSVs-VGeLEv5g,218
|
27
27
|
encommon/crypts/test/test_crypts.py,sha256=F-81-2R8_xfPMRU8QLYzfnvp01uP5BB-xA0XtmMISJE,3482
|
28
28
|
encommon/crypts/test/test_hashes.py,sha256=OmidSycLkUyso6K5Hfun2NopPXA1uL3SFqz_2aITOMM,1201
|
29
|
+
encommon/parse/__init__.py,sha256=6uV4GCm_nOYC77x2jQvTDsa0F6vBGRbCgju_HCc96zM,422
|
30
|
+
encommon/parse/jinja2.py,sha256=AzLermYAge3oN33hBDvIXSqlF09n34wWS-y_Hg6JWWc,6051
|
31
|
+
encommon/parse/network.py,sha256=PgQ6xV6Y9KmyH0iXqQ-b88Gtkrry75Fzc-tZd-BH0ng,8771
|
32
|
+
encommon/parse/test/__init__.py,sha256=PjrnBYT0efyvbaGeNx94dm3tP3EVHUHSVs-VGeLEv5g,218
|
33
|
+
encommon/parse/test/test_jinja2.py,sha256=hZu4BaRWZgyU_edVUcDiJs9gJnoFoSWF9o7CPROmcAI,3760
|
34
|
+
encommon/parse/test/test_network.py,sha256=apBv7rNtdjSbGnpXwg1VX-ybF3w-tyqYjpQDv6mlwfM,4148
|
29
35
|
encommon/times/__init__.py,sha256=QX4iuZ59UlsMbEWbubnVJXJtrOubNxAAAv510urcLUA,972
|
30
36
|
encommon/times/common.py,sha256=HWgWBbqDoyKHIqeg4bBAZZfRM3499X3WPu8dVCzt_5o,995
|
31
37
|
encommon/times/duration.py,sha256=LTROiKcRXvPcs2Gz9KaB5Cmxo9NXd3TcMo5-jnTxPo0,10794
|
@@ -49,11 +55,11 @@ encommon/times/test/test_unitime.py,sha256=5i4UjBCw8R9h-Lw963GfB_dHBMEQhjvv1k-t2
|
|
49
55
|
encommon/times/test/test_utils.py,sha256=WkzHJY6zOt02Ujg5FItOo1nPtktz5ss8ODmG1tRQaaw,2056
|
50
56
|
encommon/times/test/test_window.py,sha256=gNJpWVrwQTnUFQ00OLzWUuvJjWUCoiCwydohr9mevT0,6116
|
51
57
|
encommon/times/test/test_windows.py,sha256=IaaxUXqf5n9IF9X0HkRqtCdyOdeCq5DYR1ySLORA9gE,4474
|
52
|
-
encommon/types/__init__.py,sha256=
|
58
|
+
encommon/types/__init__.py,sha256=wMgdz0PuJyL_LIfafDlWIDaDLJi-bhnQJ4YTuUgN2gY,1143
|
53
59
|
encommon/types/classes.py,sha256=FYFTu8Uj-74JWudHOlhaOrsXXPxitorBfM9_QM3EGSU,1689
|
54
60
|
encommon/types/dicts.py,sha256=lC2FmPzMEj9L73jUjf3o6OVQ-LqK_3gp5nBwYibdGfo,2265
|
55
61
|
encommon/types/empty.py,sha256=n5y5maXkcM3xNYNYGK6iqvk98ivQSeguaedwc0HoMv4,2739
|
56
|
-
encommon/types/lists.py,sha256=
|
62
|
+
encommon/types/lists.py,sha256=xjTDOzndqPHu_Cl0yWRDl60lvjLqEqHFW4P7uJ2lfvc,2046
|
57
63
|
encommon/types/notate.py,sha256=0JIzF5VDnQ6C4umZIpgxIvd91gFo3MXTZ7Kp54S538A,6454
|
58
64
|
encommon/types/strings.py,sha256=LW2WZND64cKE1LhNip3vqsoP3elLsUP6cpS0dYnUKGE,2800
|
59
65
|
encommon/types/types.py,sha256=DbzdDLLclD1Gk8bmyhDUUWVDnJ5LdaolLV3JYKHFVgA,322
|
@@ -61,7 +67,7 @@ encommon/types/test/__init__.py,sha256=UIyNazTlIIdUzQS0brlD7hCPN_LYw_J6Ucxm8wh3c
|
|
61
67
|
encommon/types/test/test_classes.py,sha256=CjthMInwz5WB7aTc7-GpzgcYAvkF9dRmC6nXJVoE91k,1475
|
62
68
|
encommon/types/test/test_dicts.py,sha256=-RLkcyexCXGSyJyPx1e3QU8sNXEgtSvD9pZakdOwVvg,2702
|
63
69
|
encommon/types/test/test_empty.py,sha256=LVsZbKOg0deyKOtg_0Fhf0b_0c94LftwdDhijna-FbA,995
|
64
|
-
encommon/types/test/test_lists.py,sha256=
|
70
|
+
encommon/types/test/test_lists.py,sha256=uRdON1vnDT21TBl2prlO15SHIkN7YOApZzHS78R-Bvs,1139
|
65
71
|
encommon/types/test/test_notate.py,sha256=NfrDmMD6hOoVi9wlQ8yLZNnuHwS6Z7bLze70FkxOjSA,4008
|
66
72
|
encommon/types/test/test_strings.py,sha256=oXusioFMdknHeBdwlP_GakDVS9Tf2YBndjonj22UfmM,1277
|
67
73
|
encommon/utils/__init__.py,sha256=bBgh81wX_TwLgWkx0aBAyVLHNphrfcyQc1AF7-ziyNI,1027
|
@@ -77,8 +83,8 @@ encommon/utils/test/test_match.py,sha256=QagKpTFdRo23-Y55fSaJrSMpt5jIebScKbz0h8t
|
|
77
83
|
encommon/utils/test/test_paths.py,sha256=4AzIhQyYFEWhRWHgOZCCzomQ3Zs3EVwRnDQDa6Nq1Mc,1942
|
78
84
|
encommon/utils/test/test_sample.py,sha256=Qf-W0XbjTe5PfG87sdVizL2ymUPRTdX0qQtLGHaTgx8,3539
|
79
85
|
encommon/utils/test/test_stdout.py,sha256=fYiqEaUraD-3hFQYLxMPR4Ti_8CbTjEc8WvReXUA884,6139
|
80
|
-
encommon-0.
|
81
|
-
encommon-0.
|
82
|
-
encommon-0.
|
83
|
-
encommon-0.
|
84
|
-
encommon-0.
|
86
|
+
encommon-0.19.0.dist-info/LICENSE,sha256=otnXKCtMjPlbHs0wgZ_BWULrp3g_2dWQJ6icRk9nkgg,1071
|
87
|
+
encommon-0.19.0.dist-info/METADATA,sha256=wQiFB7-IXLGMcMeIqzFMcnhFOx6SbkY3r30lIJmMQEM,3494
|
88
|
+
encommon-0.19.0.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
|
89
|
+
encommon-0.19.0.dist-info/top_level.txt,sha256=bP8q7-5tLDNm-3XPlqn_bDENfYNug5801H_xfz3BEAM,9
|
90
|
+
encommon-0.19.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|