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.
@@ -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,,