encommon 0.20.5__py3-none-any.whl → 0.21.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.
@@ -17,7 +17,9 @@ from .lists import dedup_list
17
17
  from .lists import fuzzy_list
18
18
  from .lists import inlist
19
19
  from .notate import delate
20
+ from .notate import expate
20
21
  from .notate import getate
22
+ from .notate import impate
21
23
  from .notate import setate
22
24
  from .strings import hasstr
23
25
  from .strings import inrepr
@@ -39,8 +41,10 @@ __all__ = [
39
41
  'delate',
40
42
  'DictStrAny',
41
43
  'Empty',
44
+ 'expate',
42
45
  'getate',
43
46
  'hasstr',
47
+ 'impate',
44
48
  'inlist',
45
49
  'inrepr',
46
50
  'instr',
encommon/types/notate.py CHANGED
@@ -193,6 +193,185 @@ def delate(
193
193
 
194
194
 
195
195
 
196
+ def impate( # noqa: CFQ001,CFQ004
197
+ source: DictStrAny | list[DictStrAny],
198
+ delim: str = '/',
199
+ parent: Optional[str] = None,
200
+ *,
201
+ implode_list: bool = True,
202
+ recurse_list: bool = True,
203
+ ) -> DictStrAny | list[DictStrAny]:
204
+ """
205
+ Implode the dictionary into a single depth of notation.
206
+
207
+ Example
208
+ -------
209
+ >>> impate({'foo': {'bar': 'baz'}})
210
+ {'foo/bar': 'baz'}
211
+
212
+ :param source: Dictionary object processed in notation.
213
+ :param delim: Override default delimiter between parts.
214
+ :param parent: Parent key prefix for downstream update.
215
+ :param implode_list: Determine whether list is imploded.
216
+ :param recurse_list: Determine whether flatten in list.
217
+ :returns: New dictionary that was recursively imploded.
218
+ It is also possible that a list of dictionary will
219
+ be returned when provided and implode_list is False.
220
+ """
221
+
222
+ _implode = implode_list
223
+ _recurse = recurse_list
224
+
225
+
226
+ def _proclist(
227
+ source: list[Any],
228
+ delim: str,
229
+ parent: Optional[str],
230
+ ) -> DictStrAny | list[Any]:
231
+
232
+
233
+ if _implode is False:
234
+
235
+ process = [
236
+ (impate(
237
+ item, delim,
238
+ implode_list=_implode,
239
+ recurse_list=_recurse)
240
+ if isinstance(item, dict | list)
241
+ and _recurse is True
242
+ else item)
243
+ for item in source]
244
+
245
+ return (
246
+ {parent: process}
247
+ if parent is not None
248
+ else process)
249
+
250
+
251
+ returned: DictStrAny = {}
252
+
253
+ for i, item in enumerate(source):
254
+
255
+ key = (
256
+ f'{parent}{delim}{i}'
257
+ if parent is not None
258
+ else str(i))
259
+
260
+ if (isinstance(item, dict | list)
261
+ and _recurse is True):
262
+
263
+ implode = impate(
264
+ item, delim, key,
265
+ implode_list=_implode,
266
+ recurse_list=_recurse)
267
+
268
+ assert isinstance(implode, dict)
269
+
270
+ returned.update(implode)
271
+
272
+ else:
273
+ returned[key] = item
274
+
275
+ return returned
276
+
277
+
278
+ def _procdict(
279
+ source: DictStrAny,
280
+ delim: str,
281
+ parent: Optional[str],
282
+ ) -> DictStrAny:
283
+
284
+ returned: DictStrAny = {}
285
+
286
+ for key, value in source.items():
287
+
288
+ key = (
289
+ f'{parent}{delim}{key}'
290
+ if parent is not None
291
+ else key)
292
+
293
+ if isinstance(value, dict):
294
+
295
+ implode = impate(
296
+ value, delim, key,
297
+ implode_list=_implode,
298
+ recurse_list=_recurse)
299
+
300
+ assert isinstance(implode, dict)
301
+
302
+ returned |= implode
303
+
304
+ elif isinstance(value, list):
305
+
306
+ process = _proclist(
307
+ value, delim, key)
308
+
309
+ returned |= (
310
+ {key: process}
311
+ if not isinstance(process, dict)
312
+ else process)
313
+
314
+ else:
315
+ returned[key] = value
316
+
317
+ return returned
318
+
319
+
320
+ if isinstance(source, dict):
321
+ return _procdict(
322
+ source, delim, parent)
323
+
324
+ if isinstance(source, list):
325
+ return _proclist(
326
+ source, delim, parent)
327
+
328
+ raise ValueError('source')
329
+
330
+
331
+
332
+ def expate(
333
+ source: DictStrAny,
334
+ delim: str = '/',
335
+ ) -> DictStrAny:
336
+ """
337
+ Explode the dictionary from a single depth of notation.
338
+
339
+ Example
340
+ -------
341
+ >>> expate({'foo/bar': 'baz'})
342
+ {'foo': {'bar': 'baz'}}
343
+
344
+ :param source: Dictionary object processed in notation.
345
+ :param delim: Override default delimiter between parts.
346
+ :returns: New dictionary that was recursively exploded.
347
+ """
348
+
349
+ returned: DictStrAny = {}
350
+
351
+
352
+ items = source.items()
353
+
354
+ for key, value in items:
355
+
356
+ if isinstance(value, list):
357
+ value = [
358
+ (expate(x, delim)
359
+ if isinstance(x, dict)
360
+ else x)
361
+ for x in value]
362
+
363
+ if isinstance(value, dict):
364
+ value = expate(value, delim)
365
+
366
+ setate(
367
+ returned, key,
368
+ value, delim)
369
+
370
+
371
+ return returned
372
+
373
+
374
+
196
375
  def _setpath(
197
376
  source: _SETABLE,
198
377
  path: str,
@@ -8,6 +8,13 @@ is permitted, for more information consult the project license file.
8
8
 
9
9
 
10
10
  from copy import deepcopy
11
+ from pathlib import Path
12
+
13
+
14
+
15
+ SAMPLES = (
16
+ Path(__file__).parent
17
+ / 'samples')
11
18
 
12
19
 
13
20
 
@@ -140,6 +140,7 @@ def test_merge_dicts() -> None: # noqa: CFQ001
140
140
  assert source is not _source
141
141
 
142
142
 
143
+
143
144
  def test_sort_dict() -> None:
144
145
  """
145
146
  Perform various tests associated with relevant routines.
@@ -8,15 +8,132 @@ is permitted, for more information consult the project license file.
8
8
 
9
9
 
10
10
  from copy import deepcopy
11
+ from pathlib import Path
11
12
 
12
13
  from _pytest.python_api import RaisesContext
13
14
 
15
+ from pytest import fixture
14
16
  from pytest import raises
15
17
 
18
+ from . import SAMPLES
16
19
  from . import _DICT1R
17
20
  from ..notate import delate
21
+ from ..notate import expate
18
22
  from ..notate import getate
23
+ from ..notate import impate
19
24
  from ..notate import setate
25
+ from ..types import DictStrAny
26
+ from ...utils import load_sample
27
+ from ...utils import prep_sample
28
+ from ...utils.sample import ENPYRWS
29
+
30
+
31
+
32
+ _SAMPLE = dict[str, DictStrAny]
33
+
34
+
35
+
36
+ @fixture
37
+ def sample_impate() -> _SAMPLE:
38
+ """
39
+ Construct the dictionary for use with downstream tests.
40
+
41
+ :returns: Newly constructed dictionary for use in tests.
42
+ """
43
+
44
+ recurse_implode = (
45
+ impate(
46
+ deepcopy(_DICT1R),
47
+ recurse_list=True,
48
+ implode_list=True))
49
+
50
+ assert isinstance(
51
+ recurse_implode, dict)
52
+
53
+ nocurse_noplode = (
54
+ impate(
55
+ deepcopy(_DICT1R),
56
+ recurse_list=False,
57
+ implode_list=False))
58
+
59
+ assert isinstance(
60
+ nocurse_noplode, dict)
61
+
62
+ recurse_noplode = (
63
+ impate(
64
+ deepcopy(_DICT1R),
65
+ recurse_list=True,
66
+ implode_list=False))
67
+
68
+ assert isinstance(
69
+ recurse_noplode, dict)
70
+
71
+ nocurse_implode = (
72
+ impate(
73
+ deepcopy(_DICT1R),
74
+ recurse_list=False,
75
+ implode_list=True))
76
+
77
+ assert isinstance(
78
+ nocurse_implode, dict)
79
+
80
+
81
+ return {
82
+ 'recurse_implode': recurse_implode,
83
+ 'nocurse_noplode': nocurse_noplode,
84
+ 'recurse_noplode': recurse_noplode,
85
+ 'nocurse_implode': nocurse_implode}
86
+
87
+
88
+
89
+ @fixture
90
+ def sample_expate(
91
+ sample_impate: _SAMPLE,
92
+ ) -> _SAMPLE:
93
+ """
94
+ Construct the dictionary for use with downstream tests.
95
+
96
+ :param sample_impate: Source dictionary for use in test.
97
+ :returns: Newly constructed dictionary for use in tests.
98
+ """
99
+
100
+ recurse_implode = (
101
+ sample_impate[
102
+ 'recurse_implode'])
103
+
104
+ assert isinstance(
105
+ recurse_implode, dict)
106
+
107
+ nocurse_noplode = (
108
+ sample_impate[
109
+ 'nocurse_noplode'])
110
+
111
+ assert isinstance(
112
+ nocurse_noplode, dict)
113
+
114
+ recurse_noplode = (
115
+ sample_impate[
116
+ 'recurse_noplode'])
117
+
118
+ assert isinstance(
119
+ recurse_noplode, dict)
120
+
121
+ nocurse_implode = (
122
+ sample_impate[
123
+ 'nocurse_implode'])
124
+
125
+ assert isinstance(
126
+ nocurse_implode, dict)
127
+
128
+ return {
129
+ 'recurse_implode': (
130
+ expate(recurse_implode)),
131
+ 'nocurse_noplode': (
132
+ expate(nocurse_noplode)),
133
+ 'recurse_noplode': (
134
+ expate(recurse_noplode)),
135
+ 'nocurse_implode': (
136
+ expate(nocurse_implode))}
20
137
 
21
138
 
22
139
 
@@ -215,3 +332,147 @@ def test_delate_raises() -> None:
215
332
  _reason = str(reason.value)
216
333
 
217
334
  assert _reason == 'source'
335
+
336
+
337
+
338
+ def test_impate(
339
+ tmp_path: Path,
340
+ sample_impate: _SAMPLE,
341
+ sample_expate: _SAMPLE,
342
+ ) -> None:
343
+ """
344
+ Perform various tests associated with relevant routines.
345
+
346
+ :param sample_impate: Source dictionary for use in test.
347
+ :param sample_expate: Source dictionary for use in test.
348
+ :param tmp_path: pytest object for temporal filesystem.
349
+ """
350
+
351
+ sample_path = (
352
+ SAMPLES / 'impate.json')
353
+
354
+ sample = load_sample(
355
+ path=sample_path,
356
+ update=ENPYRWS,
357
+ content=sample_impate)
358
+
359
+ expect = prep_sample(
360
+ content=sample_impate)
361
+
362
+ assert expect == sample
363
+
364
+
365
+
366
+ def test_impate_cover(
367
+ tmp_path: Path,
368
+ sample_impate: _SAMPLE,
369
+ sample_expate: _SAMPLE,
370
+ ) -> None:
371
+ """
372
+ Perform various tests associated with relevant routines.
373
+
374
+ :param sample_impate: Source dictionary for use in test.
375
+ :param sample_expate: Source dictionary for use in test.
376
+ :param tmp_path: pytest object for temporal filesystem.
377
+ """
378
+
379
+
380
+ default = sample_impate[
381
+ 'recurse_implode']
382
+
383
+ assert default == impate(default)
384
+
385
+
386
+ sample = 'recurse_noplode'
387
+
388
+ implode = impate(
389
+ [sample_expate[sample],
390
+ sample_expate[sample]],
391
+ implode_list=False,
392
+ recurse_list=True)
393
+
394
+ assert isinstance(implode, list)
395
+
396
+ assert implode[0] == (
397
+ sample_impate[sample])
398
+
399
+
400
+ source = {'foo': {'bar': 'baz'}}
401
+ expect = {'foo/bar': 'baz'}
402
+
403
+ assert impate(source) == expect
404
+
405
+
406
+
407
+ def test_impate_raises() -> None:
408
+ """
409
+ Perform various tests associated with relevant routines.
410
+ """
411
+
412
+ _raises: RaisesContext[ValueError]
413
+
414
+
415
+ _raises = raises(ValueError)
416
+
417
+ with _raises as reason:
418
+ impate('foo') # type: ignore
419
+
420
+ _reason = str(reason.value)
421
+
422
+ assert _reason == 'source'
423
+
424
+
425
+
426
+ def test_expate(
427
+ tmp_path: Path,
428
+ sample_impate: _SAMPLE,
429
+ sample_expate: _SAMPLE,
430
+ ) -> None:
431
+ """
432
+ Perform various tests associated with relevant routines.
433
+
434
+ :param sample_impate: Source dictionary for use in test.
435
+ :param sample_expate: Source dictionary for use in test.
436
+ :param tmp_path: pytest object for temporal filesystem.
437
+ """
438
+
439
+ sample_path = (
440
+ SAMPLES / 'expate.json')
441
+
442
+ sample = load_sample(
443
+ path=sample_path,
444
+ update=ENPYRWS,
445
+ content=sample_expate)
446
+
447
+ expect = prep_sample(
448
+ content=sample_expate)
449
+
450
+ assert expect == sample
451
+
452
+
453
+
454
+ def test_expate_cover(
455
+ tmp_path: Path,
456
+ sample_impate: _SAMPLE,
457
+ sample_expate: _SAMPLE,
458
+ ) -> None:
459
+ """
460
+ Perform various tests associated with relevant routines.
461
+
462
+ :param sample_impate: Source dictionary for use in test.
463
+ :param sample_expate: Source dictionary for use in test.
464
+ :param tmp_path: pytest object for temporal filesystem.
465
+ """
466
+
467
+
468
+ assert sample_expate == (
469
+ expate(sample_impate))
470
+
471
+
472
+ assert sample_expate == (
473
+ expate(sample_expate))
474
+
475
+
476
+ assert all(
477
+ x == _DICT1R for x in
478
+ sample_expate.values())
encommon/version.txt CHANGED
@@ -1 +1 @@
1
- 0.20.5
1
+ 0.21.0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: encommon
3
- Version: 0.20.5
3
+ Version: 0.21.0
4
4
  Summary: Enasis Network Common Library
5
5
  License: MIT
6
6
  Project-URL: Source, https://github.com/enasisnetwork/encommon
@@ -1,7 +1,7 @@
1
1
  encommon/__init__.py,sha256=YDGzuhpk5Gd1hq54LI0hw1NrrDvrJDrvH20TEy_0l5E,443
2
2
  encommon/conftest.py,sha256=I7Zl2cMytnA-mwSPh0rRjsU0YSlES94jQq6mocRhVUE,1884
3
3
  encommon/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- encommon/version.txt,sha256=zAFOylcqU7YquFNYPks2kOCmdCQzNW6Hu0iuhZa8Iec,7
4
+ encommon/version.txt,sha256=NjxqrLtomi72kbaWdGKfZH8a-v68ncNTqG5rCTALJPY,7
5
5
  encommon/colors/__init__.py,sha256=XRiGimMj8oo040NO5a5ZsbsIUxaGVW4tf4xWTPWgnZY,269
6
6
  encommon/colors/color.py,sha256=rDWWL5oMx2SVSBEuRYX43u71nzMhMTZipXAHmEXwAjQ,10919
7
7
  encommon/colors/test/__init__.py,sha256=PjrnBYT0efyvbaGeNx94dm3tP3EVHUHSVs-VGeLEv5g,218
@@ -55,20 +55,20 @@ encommon/times/test/test_unitime.py,sha256=5i4UjBCw8R9h-Lw963GfB_dHBMEQhjvv1k-t2
55
55
  encommon/times/test/test_utils.py,sha256=WkzHJY6zOt02Ujg5FItOo1nPtktz5ss8ODmG1tRQaaw,2056
56
56
  encommon/times/test/test_window.py,sha256=6ySO5DaYzg1bsVNCqB6u71rKWc0vpolxQ09ruoswN2c,6138
57
57
  encommon/times/test/test_windows.py,sha256=Sq31BCvJtEN9OGGYXFKiagVZP0kc1n6HuaEBNwbkuks,4496
58
- encommon/types/__init__.py,sha256=wMgdz0PuJyL_LIfafDlWIDaDLJi-bhnQJ4YTuUgN2gY,1143
58
+ encommon/types/__init__.py,sha256=ABenLJ4SOSAN1kRMKQrAZ4A-eYtr7g42hhb1BtuhzPo,1225
59
59
  encommon/types/classes.py,sha256=FYFTu8Uj-74JWudHOlhaOrsXXPxitorBfM9_QM3EGSU,1689
60
60
  encommon/types/dicts.py,sha256=IuLoVdtilhM83ujT74mcz0Zi1HI87P4k7wjnnyMxPag,2821
61
61
  encommon/types/empty.py,sha256=n5y5maXkcM3xNYNYGK6iqvk98ivQSeguaedwc0HoMv4,2739
62
62
  encommon/types/lists.py,sha256=AX-siqXfLwm_5mGDsomg_7XWalZOYLE60D3wHwbNEzo,2358
63
- encommon/types/notate.py,sha256=0JIzF5VDnQ6C4umZIpgxIvd91gFo3MXTZ7Kp54S538A,6454
63
+ encommon/types/notate.py,sha256=xcvifAe5tXVK0NoxE5P-OEbJXp4UYa5RGsXU-A1TKA4,10698
64
64
  encommon/types/strings.py,sha256=LW2WZND64cKE1LhNip3vqsoP3elLsUP6cpS0dYnUKGE,2800
65
65
  encommon/types/types.py,sha256=DbzdDLLclD1Gk8bmyhDUUWVDnJ5LdaolLV3JYKHFVgA,322
66
- encommon/types/test/__init__.py,sha256=uauiJIPPJjk1bzp5WEH_YEFLR5m0zxVN_c1liYAYIro,827
66
+ encommon/types/test/__init__.py,sha256=WZm1yZbFd2VQg-E1b6a02E6V2QXmIWiW5TIiKFFPV-s,910
67
67
  encommon/types/test/test_classes.py,sha256=CjthMInwz5WB7aTc7-GpzgcYAvkF9dRmC6nXJVoE91k,1475
68
- encommon/types/test/test_dicts.py,sha256=kVYIGlIyXOx9yiCPKbhhFMf0TpiTU0ESNOaJYIq0_Ms,3650
68
+ encommon/types/test/test_dicts.py,sha256=W--IcPwvdKaFGs_00tvWBGziFSA0wtDQMuPk4rl0gKU,3651
69
69
  encommon/types/test/test_empty.py,sha256=eLsHuqq2YNABFkMLPbGbJMXeW2nyGNIxzUZv7YhPT5U,1017
70
70
  encommon/types/test/test_lists.py,sha256=uRdON1vnDT21TBl2prlO15SHIkN7YOApZzHS78R-Bvs,1139
71
- encommon/types/test/test_notate.py,sha256=NfrDmMD6hOoVi9wlQ8yLZNnuHwS6Z7bLze70FkxOjSA,4008
71
+ encommon/types/test/test_notate.py,sha256=KTOqlHUuS7ed80_h0n7TJNkAqoeULrkbiMH6sPw0lak,9520
72
72
  encommon/types/test/test_strings.py,sha256=oXusioFMdknHeBdwlP_GakDVS9Tf2YBndjonj22UfmM,1277
73
73
  encommon/utils/__init__.py,sha256=bBgh81wX_TwLgWkx0aBAyVLHNphrfcyQc1AF7-ziyNI,1027
74
74
  encommon/utils/common.py,sha256=-bjGJ2UJa-WTOsVYiuZcVj1gkyH5OlRdRkJtxPw8J6k,555
@@ -83,8 +83,8 @@ encommon/utils/test/test_match.py,sha256=QagKpTFdRo23-Y55fSaJrSMpt5jIebScKbz0h8t
83
83
  encommon/utils/test/test_paths.py,sha256=4AzIhQyYFEWhRWHgOZCCzomQ3Zs3EVwRnDQDa6Nq1Mc,1942
84
84
  encommon/utils/test/test_sample.py,sha256=Qf-W0XbjTe5PfG87sdVizL2ymUPRTdX0qQtLGHaTgx8,3539
85
85
  encommon/utils/test/test_stdout.py,sha256=fYiqEaUraD-3hFQYLxMPR4Ti_8CbTjEc8WvReXUA884,6139
86
- encommon-0.20.5.dist-info/LICENSE,sha256=otnXKCtMjPlbHs0wgZ_BWULrp3g_2dWQJ6icRk9nkgg,1071
87
- encommon-0.20.5.dist-info/METADATA,sha256=2OO8NLbJBybg5k6gAmoclYc4XbzzDSFhdxmPzYnLAAE,4282
88
- encommon-0.20.5.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
89
- encommon-0.20.5.dist-info/top_level.txt,sha256=bP8q7-5tLDNm-3XPlqn_bDENfYNug5801H_xfz3BEAM,9
90
- encommon-0.20.5.dist-info/RECORD,,
86
+ encommon-0.21.0.dist-info/LICENSE,sha256=otnXKCtMjPlbHs0wgZ_BWULrp3g_2dWQJ6icRk9nkgg,1071
87
+ encommon-0.21.0.dist-info/METADATA,sha256=wY93Eig3-FwAU0dWgJrp9osqjreo0Zcxmojm-1SN-wo,4282
88
+ encommon-0.21.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
89
+ encommon-0.21.0.dist-info/top_level.txt,sha256=bP8q7-5tLDNm-3XPlqn_bDENfYNug5801H_xfz3BEAM,9
90
+ encommon-0.21.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.2.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5