encommon 0.18.0__py3-none-any.whl → 0.19.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -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']
@@ -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,6 @@
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
+ """
@@ -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'])
@@ -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.18.0
1
+ 0.19.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: encommon
3
- Version: 0.18.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=9ek4KWfJgzBmpVgIgp83UPyOEYGxcMr9Iiw3BIu0mnc,7
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=_DVoVFJWd-9aCpXtPEaB3CnZ-5gt-uYxMWzquXfXHpg,1047
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=4xn1RqS1VMsNnhR-2Xpv4ykB6d2RteFa5zdiWKzo55o,745
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=RXvIQvr1nODutPBRx5hMsPRq_10AzBWF594KINoMgU8,437
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.18.0.dist-info/LICENSE,sha256=otnXKCtMjPlbHs0wgZ_BWULrp3g_2dWQJ6icRk9nkgg,1071
81
- encommon-0.18.0.dist-info/METADATA,sha256=upaliFBqed1iQjZnK9uwLnlrhAyLj2vcyM5pHROcVig,3449
82
- encommon-0.18.0.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
83
- encommon-0.18.0.dist-info/top_level.txt,sha256=bP8q7-5tLDNm-3XPlqn_bDENfYNug5801H_xfz3BEAM,9
84
- encommon-0.18.0.dist-info/RECORD,,
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,,