encommon 0.20.5__py3-none-any.whl → 0.21.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -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