encommon 0.9.0__py3-none-any.whl → 0.11.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. encommon/config/__init__.py +3 -3
  2. encommon/config/config.py +28 -2
  3. encommon/config/files.py +3 -3
  4. encommon/config/logger.py +37 -6
  5. encommon/config/params.py +24 -4
  6. encommon/config/paths.py +2 -2
  7. encommon/config/test/test_logger.py +3 -0
  8. encommon/config/test/{test_common.py → test_utils.py} +3 -3
  9. encommon/config/{common.py → utils.py} +1 -10
  10. encommon/conftest.py +4 -4
  11. encommon/crypts/crypts.py +23 -22
  12. encommon/crypts/params.py +15 -2
  13. encommon/crypts/test/test_crypts.py +8 -20
  14. encommon/times/__init__.py +14 -2
  15. encommon/times/common.py +0 -127
  16. encommon/times/params.py +155 -0
  17. encommon/times/parse.py +5 -5
  18. encommon/times/test/test_params.py +64 -0
  19. encommon/times/test/test_parse.py +1 -1
  20. encommon/times/test/test_timer.py +86 -0
  21. encommon/times/test/test_timers.py +87 -36
  22. encommon/times/test/{test_common.py → test_utils.py} +3 -3
  23. encommon/times/test/test_window.py +101 -51
  24. encommon/times/test/test_windows.py +264 -0
  25. encommon/times/timer.py +147 -0
  26. encommon/times/timers.py +207 -133
  27. encommon/times/times.py +6 -6
  28. encommon/times/utils.py +148 -0
  29. encommon/times/window.py +124 -85
  30. encommon/times/windows.py +459 -0
  31. encommon/types/__init__.py +6 -0
  32. encommon/types/notate.py +319 -0
  33. encommon/types/test/__init__.py +37 -0
  34. encommon/types/test/test_dicts.py +23 -28
  35. encommon/types/test/test_notate.py +217 -0
  36. encommon/utils/__init__.py +2 -2
  37. encommon/utils/common.py +0 -39
  38. encommon/utils/files.py +71 -0
  39. encommon/utils/paths.py +1 -1
  40. encommon/utils/sample.py +2 -2
  41. encommon/utils/test/{test_common.py → test_files.py} +2 -2
  42. encommon/utils/test/test_paths.py +2 -1
  43. encommon/version.txt +1 -1
  44. {encommon-0.9.0.dist-info → encommon-0.11.0.dist-info}/METADATA +1 -1
  45. encommon-0.11.0.dist-info/RECORD +73 -0
  46. encommon-0.9.0.dist-info/RECORD +0 -63
  47. {encommon-0.9.0.dist-info → encommon-0.11.0.dist-info}/LICENSE +0 -0
  48. {encommon-0.9.0.dist-info → encommon-0.11.0.dist-info}/WHEEL +0 -0
  49. {encommon-0.9.0.dist-info → encommon-0.11.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,319 @@
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 re import compile
12
+ from re import match as re_match
13
+ from typing import Any
14
+ from typing import Optional
15
+ from typing import Union
16
+
17
+ from .empty import Empty
18
+
19
+
20
+
21
+ _INTEGER = compile(r'^\d+$')
22
+ _INDICES = (list, tuple, dict)
23
+ _RECURSE = dict
24
+
25
+
26
+
27
+ _SETABLE = Union[
28
+ dict[str, Any],
29
+ list[Any]]
30
+
31
+ _GETABLE = Union[
32
+ tuple[Any, ...],
33
+ _SETABLE]
34
+
35
+
36
+
37
+ def getate(
38
+ source: _GETABLE,
39
+ path: str,
40
+ default: Optional[Any] = None,
41
+ delim: str = '/',
42
+ ) -> Any: # noqa: ANN401
43
+ """
44
+ Collect the value within the dictionary using notation.
45
+
46
+ Example
47
+ -------
48
+ >>> source = {'foo': {'bar': 'baz'}}
49
+ >>> getate(source, 'foo/bar')
50
+ 'baz'
51
+
52
+ Example
53
+ -------
54
+ >>> source = {'foo': ['bar', 'baz']}
55
+ >>> getate(source, 'foo/1')
56
+ 'baz'
57
+
58
+ :param source: Dictionary object processed in notation.
59
+ :param path: Path to the value within the source object.
60
+ :param default: Value to use if none is found in source.
61
+ :param delim: Override default delimiter between parts.
62
+ :returns: Value that was located within provided source.
63
+ """
64
+
65
+ sourze: Any = source
66
+
67
+ split = path.split(delim)
68
+
69
+ length = len(split)
70
+
71
+
72
+ items = enumerate(split)
73
+
74
+ for index, base in items:
75
+
76
+ if sourze is Empty:
77
+ return default
78
+
79
+
80
+ indices = isinstance(
81
+ sourze, _INDICES)
82
+
83
+ if (indices is False
84
+ and index < length):
85
+ return default
86
+
87
+
88
+ recurse = isinstance(
89
+ sourze, _RECURSE)
90
+
91
+ if recurse is False:
92
+
93
+ with suppress(IndexError):
94
+ sourze = sourze[int(base)]
95
+ continue
96
+
97
+
98
+ if base not in sourze:
99
+ sourze = Empty
100
+ continue
101
+
102
+ sourze = sourze[base]
103
+
104
+
105
+ return (
106
+ default if sourze is Empty
107
+ else sourze)
108
+
109
+
110
+
111
+ def setate(
112
+ source: _SETABLE,
113
+ path: str,
114
+ value: Any, # noqa: ANN401
115
+ delim: str = '/',
116
+ ) -> None:
117
+ """
118
+ Define the value within the dictionary using notation.
119
+
120
+ Example
121
+ -------
122
+ >>> source = {'foo': {'bar': 'baz'}}
123
+ >>> source['foo']['bar']
124
+ 'baz'
125
+ >>> setate(source, 'foo/bar', 'bop')
126
+ >>> source['foo']['bar']
127
+ 'bop'
128
+
129
+ :param source: Dictionary object processed in notation.
130
+ :param path: Path to the value within the source object.
131
+ :param value: Value which will be defined at noted point.
132
+ :param delim: Override default delimiter between parts.
133
+ """
134
+
135
+ _setvalue(source, path, value, delim)
136
+
137
+
138
+
139
+ def delate(
140
+ source: _SETABLE,
141
+ path: str,
142
+ delim: str = '/',
143
+ ) -> None:
144
+ """
145
+ Delete the value within the dictionary using notation.
146
+
147
+ Example
148
+ -------
149
+ >>> source = {'foo': {'bar': 'baz'}}
150
+ >>> delate(source, 'foo/bar')
151
+ >>> source
152
+ {'foo': {}}
153
+
154
+ :param source: Dictionary object processed in notation.
155
+ :param path: Path to the value within the source object.
156
+ :param delim: Override default delimiter between parts.
157
+ """
158
+
159
+ split = path.split(delim)
160
+
161
+ with suppress(KeyError, IndexError):
162
+
163
+
164
+ for part in split[:-1]:
165
+
166
+ setable = isinstance(
167
+ source, dict | list)
168
+
169
+ if setable is False:
170
+ raise ValueError('source')
171
+
172
+ if isinstance(source, list):
173
+ source = source[int(part)]
174
+
175
+ elif isinstance(source, dict):
176
+ source = source[part]
177
+
178
+
179
+ part = split[-1]
180
+
181
+ setable = isinstance(
182
+ source, dict | list)
183
+
184
+ if setable is False:
185
+ raise ValueError('source')
186
+
187
+ if isinstance(source, dict):
188
+ del source[part]
189
+
190
+ if isinstance(source, list):
191
+ del source[int(part)]
192
+
193
+
194
+
195
+ def _setpath(
196
+ source: _SETABLE,
197
+ path: str,
198
+ value: Any, # noqa: ANN401
199
+ delim: str = '/',
200
+ ) -> None:
201
+ """
202
+ Define the value within the dictionary using notation.
203
+
204
+ .. note::
205
+ This is a private helper function that could change.
206
+
207
+ :param source: Dictionary object processed in notation.
208
+ :param path: Path to the value within the source object.
209
+ :param value: Value which will be defined at noted point.
210
+ :param delim: Override default delimiter between parts.
211
+ """
212
+
213
+
214
+ setable = isinstance(
215
+ source, dict | list)
216
+
217
+ assert setable is True
218
+
219
+
220
+ base, path = (
221
+ path.split(delim, 1))
222
+
223
+ next = (
224
+ path.split(delim, 1)[0])
225
+
226
+
227
+ default: _SETABLE = {}
228
+
229
+ if re_match(_INTEGER, next):
230
+ default = []
231
+
232
+ update: Any
233
+
234
+
235
+ if isinstance(source, list):
236
+
237
+ length = len(source)
238
+ index = int(base)
239
+
240
+ update = default
241
+
242
+ with suppress(IndexError):
243
+ update = source[index]
244
+
245
+ _setvalue(
246
+ update, path,
247
+ value, delim)
248
+
249
+ if length == index:
250
+ source.append(update)
251
+
252
+ elif length > index:
253
+ source[index] = update
254
+
255
+
256
+ elif isinstance(source, dict):
257
+
258
+ update = default
259
+
260
+ with suppress(KeyError):
261
+ update = source[base]
262
+
263
+ _setvalue(
264
+ update, path,
265
+ value, delim)
266
+
267
+ source[base] = update
268
+
269
+
270
+
271
+ def _setvalue(
272
+ source: _SETABLE,
273
+ path: str,
274
+ value: Any, # noqa: ANN401
275
+ delim: str = '/',
276
+ ) -> None:
277
+ """
278
+ Define the value within the dictionary using notation.
279
+
280
+ .. note::
281
+ This is a private helper function that could change.
282
+
283
+ :param source: Dictionary object processed in notation.
284
+ :param path: Path to the value within the source object.
285
+ :param value: Value which will be defined at noted point.
286
+ :param delim: Override default delimiter between parts.
287
+ """
288
+
289
+
290
+ setable = isinstance(
291
+ source, dict | list)
292
+
293
+ if setable is False:
294
+ raise ValueError('source')
295
+
296
+
297
+ if delim in path:
298
+ return _setpath(
299
+ source, path,
300
+ value, delim)
301
+
302
+
303
+ if isinstance(source, list):
304
+
305
+ length = len(source)
306
+ index = int(path)
307
+
308
+ if index > length:
309
+ raise IndexError(index)
310
+
311
+ if length == index:
312
+ source.append(value)
313
+
314
+ elif length > index:
315
+ source[index] = value
316
+
317
+
318
+ if isinstance(source, dict):
319
+ source[path] = value
@@ -4,3 +4,40 @@ Functions and routines associated with Enasis Network Common Library.
4
4
  This file is part of Enasis Network software eco-system. Distribution
5
5
  is permitted, for more information consult the project license file.
6
6
  """
7
+
8
+
9
+
10
+ from copy import deepcopy
11
+
12
+
13
+
14
+ _DICT1 = {
15
+ 'dict1': 'dict1',
16
+ 'str': 'd1string',
17
+ 'list': ['d1list'],
18
+ 'tuple': (1, 2),
19
+ 'dict': {'key': 'd1dict'},
20
+ 'bool': False}
21
+
22
+ _DICT2 = {
23
+ 'dict2': 'dict2',
24
+ 'str': 'd2string',
25
+ 'list': ['d2list'],
26
+ 'tuple': (3, 4),
27
+ 'dict': {'key': 'd2dict'},
28
+ 'bool': True}
29
+
30
+ _DICT1R = deepcopy(_DICT1)
31
+ _DICT2R = deepcopy(_DICT2)
32
+
33
+ _DICT1R['recurse'] = (
34
+ deepcopy(_DICT1))
35
+
36
+ _DICT2R['recurse'] = (
37
+ deepcopy(_DICT2))
38
+
39
+ _DICT1R['nested'] = [
40
+ deepcopy(_DICT1)]
41
+
42
+ _DICT2R['nested'] = [
43
+ deepcopy(_DICT2)]
@@ -9,37 +9,22 @@ is permitted, for more information consult the project license file.
9
9
 
10
10
  from copy import deepcopy
11
11
 
12
+ from . import _DICT1
13
+ from . import _DICT1R
14
+ from . import _DICT2
15
+ from . import _DICT2R
12
16
  from ..dicts import merge_dicts
13
17
  from ..dicts import sort_dict
14
18
 
15
19
 
16
20
 
17
- _DICT1 = {
18
- 'dict1': 'dict1',
19
- 'str': 'd1string',
20
- 'list': ['d1list'],
21
- 'dict': {'key': 'd1value'},
22
- 'bool': False}
23
-
24
- _DICT2 = {
25
- 'dict2': 'dict2',
26
- 'str': 'd2string',
27
- 'list': ['d2list'],
28
- 'dict': {'key': 'd2value'},
29
- 'bool': True}
30
-
31
-
32
-
33
21
  def test_merge_dicts() -> None:
34
22
  """
35
23
  Perform various tests associated with relevant routines.
36
24
  """
37
25
 
38
- dict1 = deepcopy(_DICT1)
39
- dict2 = deepcopy(_DICT2)
40
-
41
- dict1['recurse'] = deepcopy(dict1)
42
- dict2['recurse'] = deepcopy(dict2)
26
+ dict1 = deepcopy(_DICT1R)
27
+ dict2 = deepcopy(_DICT2R)
43
28
 
44
29
 
45
30
  source = deepcopy(dict1)
@@ -52,14 +37,17 @@ def test_merge_dicts() -> None:
52
37
  'dict2': 'dict2',
53
38
  'str': 'd1string',
54
39
  'list': ['d1list', 'd2list'],
55
- 'dict': {'key': 'd1value'},
40
+ 'dict': {'key': 'd1dict'},
41
+ 'tuple': (1, 2),
56
42
  'bool': False,
43
+ 'nested': [_DICT1, _DICT2],
57
44
  'recurse': {
58
45
  'dict1': 'dict1',
59
46
  'dict2': 'dict2',
60
47
  'str': 'd1string',
61
48
  'list': ['d1list', 'd2list'],
62
- 'dict': {'key': 'd1value'},
49
+ 'dict': {'key': 'd1dict'},
50
+ 'tuple': (1, 2),
63
51
  'bool': False}}
64
52
 
65
53
 
@@ -73,14 +61,17 @@ def test_merge_dicts() -> None:
73
61
  'dict2': 'dict2',
74
62
  'str': 'd2string',
75
63
  'list': ['d1list', 'd2list'],
76
- 'dict': {'key': 'd2value'},
64
+ 'dict': {'key': 'd2dict'},
65
+ 'tuple': (3, 4),
77
66
  'bool': True,
67
+ 'nested': [_DICT1, _DICT2],
78
68
  'recurse': {
79
69
  'dict1': 'dict1',
80
70
  'dict2': 'dict2',
81
71
  'str': 'd2string',
82
72
  'list': ['d1list', 'd2list'],
83
- 'dict': {'key': 'd2value'},
73
+ 'dict': {'key': 'd2dict'},
74
+ 'tuple': (3, 4),
84
75
  'bool': True}}
85
76
 
86
77
 
@@ -98,13 +89,16 @@ def test_merge_dicts() -> None:
98
89
  'dict2': 'dict2',
99
90
  'str': 'd1string',
100
91
  'list': ['d1list'],
101
- 'dict': {'key': 'd1value'},
92
+ 'dict': {'key': 'd1dict'},
93
+ 'tuple': (1, 2),
102
94
  'bool': False,
95
+ 'nested': [_DICT1],
103
96
  'recurse': {
104
97
  'dict1': 'dict1',
105
98
  'str': 'd1string',
106
99
  'list': ['d1list'],
107
- 'dict': {'key': 'd1value'},
100
+ 'dict': {'key': 'd1dict'},
101
+ 'tuple': (1, 2),
108
102
  'bool': False}}
109
103
 
110
104
 
@@ -116,7 +110,8 @@ def test_sort_dict() -> None:
116
110
 
117
111
  assert sort_dict(_DICT1) == {
118
112
  'bool': False,
119
- 'dict': {'key': 'd1value'},
113
+ 'dict': {'key': 'd1dict'},
114
+ 'tuple': (1, 2),
120
115
  'dict1': 'dict1',
121
116
  'list': ['d1list'],
122
117
  'str': 'd1string'}
@@ -0,0 +1,217 @@
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 copy import deepcopy
11
+
12
+ from _pytest.python_api import RaisesContext
13
+
14
+ from pytest import raises
15
+
16
+ from . import _DICT1R
17
+ from ..notate import delate
18
+ from ..notate import getate
19
+ from ..notate import setate
20
+
21
+
22
+
23
+ def test_getate() -> None:
24
+ """
25
+ Perform various tests associated with relevant routines.
26
+ """
27
+
28
+ source = deepcopy(_DICT1R)
29
+
30
+
31
+ value = getate(['1', 2], '1')
32
+ assert value == 2
33
+
34
+ value = getate((1, 2), '1')
35
+ assert value == 2
36
+
37
+ value = getate({'1': 2}, '1')
38
+ assert value == 2
39
+
40
+
41
+ path = 'recurse/dict/key'
42
+ value = getate(source, path)
43
+
44
+ assert value == 'd1dict'
45
+
46
+
47
+ path = 'recurse/list/0'
48
+ value = getate(source, path)
49
+
50
+ assert value == 'd1list'
51
+
52
+
53
+
54
+ def test_getate_cover() -> None:
55
+ """
56
+ Perform various tests associated with relevant routines.
57
+ """
58
+
59
+ source = deepcopy(_DICT1R)
60
+
61
+
62
+ assert not getate({}, 'd/n/e')
63
+ assert not getate([], '0/n/e')
64
+
65
+
66
+ path = 'recurse/str/a'
67
+ value = getate(source, path)
68
+
69
+ assert value is None
70
+
71
+
72
+
73
+ def test_setate() -> None:
74
+ """
75
+ Perform various tests associated with relevant routines.
76
+ """
77
+
78
+ source = deepcopy(_DICT1R)
79
+
80
+
81
+ path = 'list/1'
82
+ before = getate(source, path)
83
+ setate(source, path, 1)
84
+ after = getate(source, path)
85
+ assert after == 1
86
+ assert before is None
87
+
88
+
89
+ path = 'recurse/dict/key'
90
+ before = getate(source, path)
91
+ setate(source, path, 1)
92
+ after = getate(source, path)
93
+ assert after == 1
94
+ assert before == 'd1dict'
95
+
96
+
97
+ path = 'nested/0/dict/key'
98
+ before = getate(source, path)
99
+ setate(source, path, 1)
100
+ after = getate(source, path)
101
+ assert after == 1
102
+ assert before == 'd1dict'
103
+
104
+
105
+ path = 'recurse/list/0'
106
+ before = getate(source, path)
107
+ setate(source, path, 1)
108
+ after = getate(source, path)
109
+ assert after == 1
110
+ assert before == 'd1list'
111
+
112
+
113
+
114
+ def test_setate_cover() -> None:
115
+ """
116
+ Perform various tests associated with relevant routines.
117
+ """
118
+
119
+ source = deepcopy(_DICT1R)
120
+
121
+
122
+ path = 'nested/1/dict/key'
123
+ before = getate(source, path)
124
+ setate(source, path, 1)
125
+ after = getate(source, path)
126
+ assert after == 1
127
+ assert before is None
128
+
129
+
130
+
131
+ def test_setate_raises() -> None:
132
+ """
133
+ Perform various tests associated with relevant routines.
134
+ """
135
+
136
+ _raises: RaisesContext[
137
+ ValueError | IndexError]
138
+
139
+
140
+ _raises = raises(ValueError)
141
+
142
+ with _raises as reason:
143
+ setate(1, '1', 1) # type: ignore
144
+
145
+ _reason = str(reason.value)
146
+
147
+ assert _reason == 'source'
148
+
149
+
150
+ _raises = raises(IndexError)
151
+
152
+ with _raises as reason:
153
+ setate([], '1', 1)
154
+
155
+ _reason = str(reason.value)
156
+
157
+ assert _reason == '1'
158
+
159
+
160
+
161
+ def test_delate() -> None:
162
+ """
163
+ Perform various tests associated with relevant routines.
164
+ """
165
+
166
+ source = deepcopy(_DICT1R)
167
+
168
+
169
+ path = 'recurse/dict/key'
170
+ before = getate(source, path)
171
+ delate(source, path)
172
+ after = getate(source, path)
173
+ assert after is None
174
+ assert before == 'd1dict'
175
+
176
+
177
+ path = 'nested/0/dict/key'
178
+ before = getate(source, path)
179
+ delate(source, path)
180
+ after = getate(source, path)
181
+ assert after is None
182
+ assert before == 'd1dict'
183
+
184
+
185
+ path = 'recurse/list/0'
186
+ before = getate(source, path)
187
+ delate(source, path)
188
+ after = getate(source, path)
189
+ assert after is None
190
+ assert before == 'd1list'
191
+
192
+
193
+
194
+ def test_delate_raises() -> None:
195
+ """
196
+ Perform various tests associated with relevant routines.
197
+ """
198
+
199
+
200
+ _raises = raises(ValueError)
201
+
202
+ with _raises as reason:
203
+ delate(1, '1') # type: ignore
204
+
205
+ _reason = str(reason.value)
206
+
207
+ assert _reason == 'source'
208
+
209
+
210
+ _raises = raises(ValueError)
211
+
212
+ with _raises as reason:
213
+ delate({'a': 1}, 'a/1/c')
214
+
215
+ _reason = str(reason.value)
216
+
217
+ assert _reason == 'source'
@@ -7,8 +7,8 @@ is permitted, for more information consult the project license file.
7
7
 
8
8
 
9
9
 
10
- from .common import read_text
11
- from .common import save_text
10
+ from .files import read_text
11
+ from .files import save_text
12
12
  from .match import fuzz_match
13
13
  from .match import rgxp_match
14
14
  from .paths import resolve_path