python-benedict 0.35.0__tar.gz → 0.36.0__tar.gz
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.
- {python_benedict-0.35.0/python_benedict.egg-info → python_benedict-0.36.0}/PKG-INFO +42 -2
- {python_benedict-0.35.0 → python_benedict-0.36.0}/README.md +39 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/dicts/__init__.py +17 -2
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/dicts/base/base_dict.py +48 -9
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/dicts/keyattr/keyattr_dict.py +16 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/metadata.py +1 -1
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/serializers/xls.py +11 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/pyproject.toml +2 -1
- {python_benedict-0.35.0 → python_benedict-0.36.0/python_benedict.egg-info}/PKG-INFO +42 -2
- {python_benedict-0.35.0 → python_benedict-0.36.0}/python_benedict.egg-info/requires.txt +1 -1
- {python_benedict-0.35.0 → python_benedict-0.36.0}/LICENSE.txt +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/MANIFEST.in +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/__init__.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/core/__init__.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/core/clean.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/core/clone.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/core/dump.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/core/filter.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/core/find.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/core/flatten.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/core/groupby.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/core/invert.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/core/items_sorted.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/core/keylists.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/core/keypaths.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/core/match.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/core/merge.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/core/move.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/core/nest.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/core/remove.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/core/rename.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/core/search.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/core/standardize.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/core/subset.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/core/swap.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/core/traverse.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/core/unflatten.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/core/unique.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/dicts/base/__init__.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/dicts/io/__init__.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/dicts/io/io_dict.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/dicts/io/io_util.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/dicts/keyattr/__init__.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/dicts/keylist/__init__.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/dicts/keylist/keylist_dict.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/dicts/keylist/keylist_util.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/dicts/keypath/__init__.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/dicts/keypath/keypath_dict.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/dicts/keypath/keypath_util.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/dicts/parse/__init__.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/dicts/parse/parse_dict.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/dicts/parse/parse_util.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/exceptions.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/extras.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/py.typed +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/serializers/__init__.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/serializers/abstract.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/serializers/base64.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/serializers/cli.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/serializers/csv.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/serializers/html.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/serializers/ini.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/serializers/json.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/serializers/pickle.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/serializers/plist.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/serializers/query_string.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/serializers/toml.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/serializers/xml.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/serializers/yaml.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/utils/__init__.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/benedict/utils/type_util.py +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/python_benedict.egg-info/SOURCES.txt +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/python_benedict.egg-info/dependency_links.txt +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/python_benedict.egg-info/top_level.txt +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/setup.cfg +0 -0
- {python_benedict-0.35.0 → python_benedict-0.36.0}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-benedict
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.36.0
|
|
4
4
|
Summary: python-benedict is a dict subclass with keylist/keypath/keyattr support, normalized I/O operations (base64, csv, ini, json, pickle, plist, query-string, toml, xls, xml, yaml) and many utilities... for humans, obviously.
|
|
5
5
|
Author-email: Fabio Caccamo <fabio.caccamo@gmail.com>
|
|
6
6
|
Maintainer-email: Fabio Caccamo <fabio.caccamo@gmail.com>
|
|
@@ -51,6 +51,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
51
51
|
Classifier: Programming Language :: Python :: 3.11
|
|
52
52
|
Classifier: Programming Language :: Python :: 3.12
|
|
53
53
|
Classifier: Programming Language :: Python :: 3.13
|
|
54
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
54
55
|
Classifier: Topic :: Education :: Testing
|
|
55
56
|
Classifier: Topic :: Software Development :: Build Tools
|
|
56
57
|
Classifier: Topic :: System :: Filesystems
|
|
@@ -59,7 +60,7 @@ Classifier: Topic :: Utilities
|
|
|
59
60
|
Classifier: Typing :: Typed
|
|
60
61
|
Description-Content-Type: text/markdown
|
|
61
62
|
License-File: LICENSE.txt
|
|
62
|
-
Requires-Dist: python-fsutil<1.0.0,>=0.
|
|
63
|
+
Requires-Dist: python-fsutil<1.0.0,>=0.16.0
|
|
63
64
|
Requires-Dist: python-slugify<9.0.0,>=7.0.0
|
|
64
65
|
Requires-Dist: requests<3.0.0,>=2.26.0
|
|
65
66
|
Requires-Dist: typing_extensions<4.16.0,>=4.13.2
|
|
@@ -94,6 +95,7 @@ Dynamic: license-file
|
|
|
94
95
|
[](https://pepy.tech/project/python-benedict)
|
|
95
96
|
[](https://github.com/fabiocaccamo/python-benedict/stargazers)
|
|
96
97
|
[](https://github.com/fabiocaccamo/python-benedict/blob/main/LICENSE.txt)
|
|
98
|
+
[](https://github.com/fabiocaccamo/python-benedict/releases/latest)
|
|
97
99
|
|
|
98
100
|
[](https://results.pre-commit.ci/latest/github/fabiocaccamo/python-benedict/main)
|
|
99
101
|
[](https://github.com/fabiocaccamo/python-benedict)
|
|
@@ -102,6 +104,7 @@ Dynamic: license-file
|
|
|
102
104
|
[](https://scrutinizer-ci.com/g/fabiocaccamo/python-benedict/?branch=main)
|
|
103
105
|
[](https://github.com/psf/black)
|
|
104
106
|
[](https://github.com/astral-sh/ruff)
|
|
107
|
+
[](https://scorecard.dev/viewer/?uri=github.com/fabiocaccamo/python-benedict)
|
|
105
108
|
|
|
106
109
|
# python-benedict
|
|
107
110
|
python-benedict is a dict subclass with **keylist/keypath/keyattr** support, **I/O** shortcuts (`base64`, `cli`, `csv`, `html`, `ini`, `json`, `pickle`, `plist`, `query-string`, `toml`, `xls`, `xml`, `yaml`) and many **utilities**... for humans, obviously.
|
|
@@ -135,6 +138,7 @@ python-benedict is a dict subclass with **keylist/keypath/keyattr** support, **I
|
|
|
135
138
|
- [I/O methods](#io-methods)
|
|
136
139
|
- [Parse methods](#parse-methods)
|
|
137
140
|
- [Testing](#testing)
|
|
141
|
+
- [Security](#security)
|
|
138
142
|
- [License](#license)
|
|
139
143
|
|
|
140
144
|
## Installation
|
|
@@ -219,6 +223,9 @@ d.keyattr_dynamic = True
|
|
|
219
223
|
|
|
220
224
|
> **Warning** - even if this feature is very useful, it has some obvious limitations: it works only for string keys that are *unprotected* (not starting with an `_`) and that don't clash with the currently supported methods names.
|
|
221
225
|
|
|
226
|
+
#### Tab-completion support
|
|
227
|
+
`benedict` supports tab-completion in **VSCode**, **IPython**, and **Jupyter** for both attribute-style access (`d.<TAB>`) and subscript-style access (`d["<TAB>"]`).
|
|
228
|
+
|
|
222
229
|
### Keylist
|
|
223
230
|
Wherever a **key** is used, it is possible to use also a **list of keys**.
|
|
224
231
|
|
|
@@ -379,6 +386,8 @@ Here are the details of the supported formats, operations and extra options docs
|
|
|
379
386
|
- [`filter`](#filter)
|
|
380
387
|
- [`find`](#find)
|
|
381
388
|
- [`flatten`](#flatten)
|
|
389
|
+
- [`freeze`](#freeze)
|
|
390
|
+
- [`frozen`](#frozen)
|
|
382
391
|
- [`groupby`](#groupby)
|
|
383
392
|
- [`invert`](#invert)
|
|
384
393
|
- [`items_sorted_by_keys`](#items_sorted_by_keys)
|
|
@@ -396,6 +405,7 @@ Here are the details of the supported formats, operations and extra options docs
|
|
|
396
405
|
- [`swap`](#swap)
|
|
397
406
|
- [`traverse`](#traverse)
|
|
398
407
|
- [`unflatten`](#unflatten)
|
|
408
|
+
- [`unfreeze`](#unfreeze)
|
|
399
409
|
- [`unique`](#unique)
|
|
400
410
|
|
|
401
411
|
- **I/O methods**
|
|
@@ -511,6 +521,22 @@ f = d.find(keys, default=0)
|
|
|
511
521
|
f = d.flatten(separator="_")
|
|
512
522
|
```
|
|
513
523
|
|
|
524
|
+
#### `freeze`
|
|
525
|
+
|
|
526
|
+
```python
|
|
527
|
+
# Make the dict immutable: any attempt to modify it will raise a TypeError.
|
|
528
|
+
# Only top-level keys are frozen; nested dicts are not affected.
|
|
529
|
+
d.freeze()
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
#### `frozen`
|
|
533
|
+
|
|
534
|
+
```python
|
|
535
|
+
# Return True if the dict is frozen (immutable), False otherwise.
|
|
536
|
+
if d.frozen:
|
|
537
|
+
...
|
|
538
|
+
```
|
|
539
|
+
|
|
514
540
|
#### `groupby`
|
|
515
541
|
|
|
516
542
|
```python
|
|
@@ -649,6 +675,13 @@ d.traverse(f)
|
|
|
649
675
|
u = d.unflatten(separator="_")
|
|
650
676
|
```
|
|
651
677
|
|
|
678
|
+
#### `unfreeze`
|
|
679
|
+
|
|
680
|
+
```python
|
|
681
|
+
# Make the dict mutable again after a freeze() call.
|
|
682
|
+
d.unfreeze()
|
|
683
|
+
```
|
|
684
|
+
|
|
652
685
|
#### `unique`
|
|
653
686
|
|
|
654
687
|
```python
|
|
@@ -1121,6 +1154,13 @@ tox
|
|
|
1121
1154
|
python -m unittest
|
|
1122
1155
|
```
|
|
1123
1156
|
|
|
1157
|
+
## Security
|
|
1158
|
+
|
|
1159
|
+
- **SBOM** — a Software Bill of Materials in [CycloneDX](https://cyclonedx.org/) format (JSON and XML) is generated and published as a release asset on every release. You can download it from the [Releases](https://github.com/fabiocaccamo/python-benedict/releases/latest) page.
|
|
1160
|
+
- **Trusted Publishing** — packages are published to PyPI via [OIDC Trusted Publishing](https://docs.pypi.org/trusted-publishers/), without long-lived secrets.
|
|
1161
|
+
- **OpenSSF Scorecard** — the repository is evaluated weekly against the [OpenSSF Scorecard](https://scorecard.dev/viewer/?uri=github.com/fabiocaccamo/python-benedict) checks; results are visible in the GitHub Security tab.
|
|
1162
|
+
- **Reporting** — to report a vulnerability, please follow the [Security Policy](SECURITY.md).
|
|
1163
|
+
|
|
1124
1164
|
## License
|
|
1125
1165
|
Released under [MIT License](LICENSE.txt).
|
|
1126
1166
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
[](https://pepy.tech/project/python-benedict)
|
|
4
4
|
[](https://github.com/fabiocaccamo/python-benedict/stargazers)
|
|
5
5
|
[](https://github.com/fabiocaccamo/python-benedict/blob/main/LICENSE.txt)
|
|
6
|
+
[](https://github.com/fabiocaccamo/python-benedict/releases/latest)
|
|
6
7
|
|
|
7
8
|
[](https://results.pre-commit.ci/latest/github/fabiocaccamo/python-benedict/main)
|
|
8
9
|
[](https://github.com/fabiocaccamo/python-benedict)
|
|
@@ -11,6 +12,7 @@
|
|
|
11
12
|
[](https://scrutinizer-ci.com/g/fabiocaccamo/python-benedict/?branch=main)
|
|
12
13
|
[](https://github.com/psf/black)
|
|
13
14
|
[](https://github.com/astral-sh/ruff)
|
|
15
|
+
[](https://scorecard.dev/viewer/?uri=github.com/fabiocaccamo/python-benedict)
|
|
14
16
|
|
|
15
17
|
# python-benedict
|
|
16
18
|
python-benedict is a dict subclass with **keylist/keypath/keyattr** support, **I/O** shortcuts (`base64`, `cli`, `csv`, `html`, `ini`, `json`, `pickle`, `plist`, `query-string`, `toml`, `xls`, `xml`, `yaml`) and many **utilities**... for humans, obviously.
|
|
@@ -44,6 +46,7 @@ python-benedict is a dict subclass with **keylist/keypath/keyattr** support, **I
|
|
|
44
46
|
- [I/O methods](#io-methods)
|
|
45
47
|
- [Parse methods](#parse-methods)
|
|
46
48
|
- [Testing](#testing)
|
|
49
|
+
- [Security](#security)
|
|
47
50
|
- [License](#license)
|
|
48
51
|
|
|
49
52
|
## Installation
|
|
@@ -128,6 +131,9 @@ d.keyattr_dynamic = True
|
|
|
128
131
|
|
|
129
132
|
> **Warning** - even if this feature is very useful, it has some obvious limitations: it works only for string keys that are *unprotected* (not starting with an `_`) and that don't clash with the currently supported methods names.
|
|
130
133
|
|
|
134
|
+
#### Tab-completion support
|
|
135
|
+
`benedict` supports tab-completion in **VSCode**, **IPython**, and **Jupyter** for both attribute-style access (`d.<TAB>`) and subscript-style access (`d["<TAB>"]`).
|
|
136
|
+
|
|
131
137
|
### Keylist
|
|
132
138
|
Wherever a **key** is used, it is possible to use also a **list of keys**.
|
|
133
139
|
|
|
@@ -288,6 +294,8 @@ Here are the details of the supported formats, operations and extra options docs
|
|
|
288
294
|
- [`filter`](#filter)
|
|
289
295
|
- [`find`](#find)
|
|
290
296
|
- [`flatten`](#flatten)
|
|
297
|
+
- [`freeze`](#freeze)
|
|
298
|
+
- [`frozen`](#frozen)
|
|
291
299
|
- [`groupby`](#groupby)
|
|
292
300
|
- [`invert`](#invert)
|
|
293
301
|
- [`items_sorted_by_keys`](#items_sorted_by_keys)
|
|
@@ -305,6 +313,7 @@ Here are the details of the supported formats, operations and extra options docs
|
|
|
305
313
|
- [`swap`](#swap)
|
|
306
314
|
- [`traverse`](#traverse)
|
|
307
315
|
- [`unflatten`](#unflatten)
|
|
316
|
+
- [`unfreeze`](#unfreeze)
|
|
308
317
|
- [`unique`](#unique)
|
|
309
318
|
|
|
310
319
|
- **I/O methods**
|
|
@@ -420,6 +429,22 @@ f = d.find(keys, default=0)
|
|
|
420
429
|
f = d.flatten(separator="_")
|
|
421
430
|
```
|
|
422
431
|
|
|
432
|
+
#### `freeze`
|
|
433
|
+
|
|
434
|
+
```python
|
|
435
|
+
# Make the dict immutable: any attempt to modify it will raise a TypeError.
|
|
436
|
+
# Only top-level keys are frozen; nested dicts are not affected.
|
|
437
|
+
d.freeze()
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
#### `frozen`
|
|
441
|
+
|
|
442
|
+
```python
|
|
443
|
+
# Return True if the dict is frozen (immutable), False otherwise.
|
|
444
|
+
if d.frozen:
|
|
445
|
+
...
|
|
446
|
+
```
|
|
447
|
+
|
|
423
448
|
#### `groupby`
|
|
424
449
|
|
|
425
450
|
```python
|
|
@@ -558,6 +583,13 @@ d.traverse(f)
|
|
|
558
583
|
u = d.unflatten(separator="_")
|
|
559
584
|
```
|
|
560
585
|
|
|
586
|
+
#### `unfreeze`
|
|
587
|
+
|
|
588
|
+
```python
|
|
589
|
+
# Make the dict mutable again after a freeze() call.
|
|
590
|
+
d.unfreeze()
|
|
591
|
+
```
|
|
592
|
+
|
|
561
593
|
#### `unique`
|
|
562
594
|
|
|
563
595
|
```python
|
|
@@ -1030,6 +1062,13 @@ tox
|
|
|
1030
1062
|
python -m unittest
|
|
1031
1063
|
```
|
|
1032
1064
|
|
|
1065
|
+
## Security
|
|
1066
|
+
|
|
1067
|
+
- **SBOM** — a Software Bill of Materials in [CycloneDX](https://cyclonedx.org/) format (JSON and XML) is generated and published as a release asset on every release. You can download it from the [Releases](https://github.com/fabiocaccamo/python-benedict/releases/latest) page.
|
|
1068
|
+
- **Trusted Publishing** — packages are published to PyPI via [OIDC Trusted Publishing](https://docs.pypi.org/trusted-publishers/), without long-lived secrets.
|
|
1069
|
+
- **OpenSSF Scorecard** — the repository is evaluated weekly against the [OpenSSF Scorecard](https://scorecard.dev/viewer/?uri=github.com/fabiocaccamo/python-benedict) checks; results are visible in the GitHub Security tab.
|
|
1070
|
+
- **Reporting** — to report a vulnerability, please follow the [Security Policy](SECURITY.md).
|
|
1071
|
+
|
|
1033
1072
|
## License
|
|
1034
1073
|
Released under [MIT License](LICENSE.txt).
|
|
1035
1074
|
|
|
@@ -88,12 +88,15 @@ class benedict(KeyattrDict[_K, _V], KeypathDict[_V], IODict[_K, _V], ParseDict[_
|
|
|
88
88
|
)
|
|
89
89
|
for key, value in self.items():
|
|
90
90
|
obj[key] = _clone(value, memo=memo)
|
|
91
|
+
if self._frozen:
|
|
92
|
+
obj.freeze()
|
|
91
93
|
return obj
|
|
92
94
|
|
|
93
95
|
def __getitem__(self, key: _KPT) -> Any: # type: ignore[override]
|
|
94
96
|
return self._cast(super().__getitem__(key))
|
|
95
97
|
|
|
96
98
|
def __setitem__(self, key: _KPT, value: _V) -> None: # type: ignore[override]
|
|
99
|
+
self._check_frozen()
|
|
97
100
|
super().__setitem__(key, self._cast(value))
|
|
98
101
|
|
|
99
102
|
def _cast(self, value: Any) -> Any:
|
|
@@ -133,7 +136,17 @@ class benedict(KeyattrDict[_K, _V], KeypathDict[_V], IODict[_K, _V], ParseDict[_
|
|
|
133
136
|
"""
|
|
134
137
|
Creates and return a copy of the current instance (shallow copy).
|
|
135
138
|
"""
|
|
136
|
-
|
|
139
|
+
obj_type = type(self)
|
|
140
|
+
obj = obj_type(
|
|
141
|
+
keyattr_enabled=self._keyattr_enabled,
|
|
142
|
+
keyattr_dynamic=self._keyattr_dynamic,
|
|
143
|
+
keypath_separator=self._keypath_separator,
|
|
144
|
+
)
|
|
145
|
+
for key, value in self.items():
|
|
146
|
+
obj[key] = value
|
|
147
|
+
if self._frozen:
|
|
148
|
+
obj.freeze()
|
|
149
|
+
return obj
|
|
137
150
|
|
|
138
151
|
def deepcopy(self) -> Self:
|
|
139
152
|
"""
|
|
@@ -195,7 +208,9 @@ class benedict(KeyattrDict[_K, _V], KeypathDict[_V], IODict[_K, _V], ParseDict[_
|
|
|
195
208
|
"""
|
|
196
209
|
Group a list of dicts at key by the value of the given by_key and return a new dict.
|
|
197
210
|
"""
|
|
198
|
-
|
|
211
|
+
result = _groupby(self[key], by_key)
|
|
212
|
+
keypath_util.check_keys(result, self._keypath_separator)
|
|
213
|
+
return cast("Self", self._cast(result))
|
|
199
214
|
|
|
200
215
|
def invert(self, flat: bool = False) -> Self:
|
|
201
216
|
"""
|
|
@@ -19,7 +19,8 @@ _V = TypeVar("_V", default=Any)
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
class BaseDict(dict[_K, _V]):
|
|
22
|
-
_dict: dict[_K, _V] | None
|
|
22
|
+
_dict: dict[_K, _V] | None
|
|
23
|
+
_frozen: bool
|
|
23
24
|
|
|
24
25
|
@classmethod
|
|
25
26
|
def _get_dict_or_value(cls, value: Any) -> Any:
|
|
@@ -32,11 +33,19 @@ class BaseDict(dict[_K, _V]):
|
|
|
32
33
|
value[key] = key_val
|
|
33
34
|
return value
|
|
34
35
|
|
|
36
|
+
def __new__(cls, *args: Any, **kwargs: Any) -> Self:
|
|
37
|
+
obj = super().__new__(cls)
|
|
38
|
+
# bypass subclass __setattr__ (e.g. KeyattrDict) which may access _dict before it exists
|
|
39
|
+
object.__setattr__(obj, "_dict", None)
|
|
40
|
+
object.__setattr__(obj, "_frozen", False)
|
|
41
|
+
return obj
|
|
42
|
+
|
|
35
43
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
36
|
-
self._dict = None
|
|
37
44
|
if len(args) == 1 and isinstance(args[0], Mapping):
|
|
38
|
-
|
|
39
|
-
|
|
45
|
+
# bypass subclass __setattr__ (e.g. KeyattrDict) which may treat _dict as a key
|
|
46
|
+
d = self._get_dict_or_value(args[0])
|
|
47
|
+
object.__setattr__(self, "_dict", d)
|
|
48
|
+
super().__init__(d)
|
|
40
49
|
return
|
|
41
50
|
super().__init__(*args, **kwargs)
|
|
42
51
|
|
|
@@ -54,9 +63,12 @@ class BaseDict(dict[_K, _V]):
|
|
|
54
63
|
obj = self.__class__()
|
|
55
64
|
for key, value in self.items():
|
|
56
65
|
obj[key] = _clone(value, memo=memo)
|
|
66
|
+
if self._frozen:
|
|
67
|
+
obj.freeze()
|
|
57
68
|
return obj
|
|
58
69
|
|
|
59
70
|
def __delitem__(self, key: _K) -> None:
|
|
71
|
+
self._check_frozen()
|
|
60
72
|
if self._dict is not None:
|
|
61
73
|
del self._dict[key]
|
|
62
74
|
return
|
|
@@ -73,6 +85,7 @@ class BaseDict(dict[_K, _V]):
|
|
|
73
85
|
return super().__getitem__(key)
|
|
74
86
|
|
|
75
87
|
def __ior__(self, other: Any) -> Self: # type: ignore[misc,override]
|
|
88
|
+
self._check_frozen()
|
|
76
89
|
if self._dict is not None:
|
|
77
90
|
return cast("Self", self._dict.__ior__(other))
|
|
78
91
|
return super().__ior__(other)
|
|
@@ -98,6 +111,7 @@ class BaseDict(dict[_K, _V]):
|
|
|
98
111
|
return super().__repr__()
|
|
99
112
|
|
|
100
113
|
def __setitem__(self, key: _K, value: _V) -> None:
|
|
114
|
+
self._check_frozen()
|
|
101
115
|
value = self._get_dict_or_value(value)
|
|
102
116
|
if self._dict is not None:
|
|
103
117
|
is_dict_item = key in self._dict and isinstance(self._dict[key], dict)
|
|
@@ -114,24 +128,46 @@ class BaseDict(dict[_K, _V]):
|
|
|
114
128
|
super().__setitem__(key, value)
|
|
115
129
|
|
|
116
130
|
def __setstate__(self, state: Mapping[str, Any]) -> None:
|
|
117
|
-
self
|
|
118
|
-
self
|
|
131
|
+
object.__setattr__(self, "_dict", state.get("_dict", None))
|
|
132
|
+
object.__setattr__(self, "_frozen", state.get("_frozen", False))
|
|
119
133
|
|
|
120
134
|
def __str__(self) -> str:
|
|
121
135
|
if self._dict is not None:
|
|
122
136
|
return str(self._dict)
|
|
123
137
|
return super().__str__()
|
|
124
138
|
|
|
139
|
+
def _check_frozen(self) -> None:
|
|
140
|
+
if self._frozen:
|
|
141
|
+
raise TypeError(
|
|
142
|
+
f"{self.__class__.__name__!r} object is frozen and cannot be modified."
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def frozen(self) -> bool:
|
|
147
|
+
return self._frozen
|
|
148
|
+
|
|
149
|
+
def freeze(self) -> Self:
|
|
150
|
+
object.__setattr__(self, "_frozen", True)
|
|
151
|
+
return self
|
|
152
|
+
|
|
153
|
+
def unfreeze(self) -> Self:
|
|
154
|
+
object.__setattr__(self, "_frozen", False)
|
|
155
|
+
return self
|
|
156
|
+
|
|
125
157
|
def clear(self) -> None:
|
|
158
|
+
self._check_frozen()
|
|
126
159
|
if self._dict is not None:
|
|
127
160
|
self._dict.clear()
|
|
128
161
|
return
|
|
129
162
|
super().clear()
|
|
130
163
|
|
|
131
164
|
def copy(self) -> Self:
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
165
|
+
obj = self.__class__()
|
|
166
|
+
for key, value in self.items():
|
|
167
|
+
obj[key] = value
|
|
168
|
+
if self._frozen:
|
|
169
|
+
obj.freeze()
|
|
170
|
+
return obj
|
|
135
171
|
|
|
136
172
|
def dict(self) -> Self:
|
|
137
173
|
if self._dict is not None:
|
|
@@ -154,11 +190,13 @@ class BaseDict(dict[_K, _V]):
|
|
|
154
190
|
return super().keys()
|
|
155
191
|
|
|
156
192
|
def pop(self, key: _K, *args: Any) -> _V:
|
|
193
|
+
self._check_frozen()
|
|
157
194
|
if self._dict is not None:
|
|
158
195
|
return self._dict.pop(key, *args) # type: ignore[no-any-return]
|
|
159
196
|
return super().pop(key, *args) # type: ignore[no-any-return]
|
|
160
197
|
|
|
161
198
|
def setdefault(self, key: _K, default: _V | None = None) -> _V:
|
|
199
|
+
self._check_frozen()
|
|
162
200
|
default = self._get_dict_or_value(default)
|
|
163
201
|
assert default is not None
|
|
164
202
|
if self._dict is not None:
|
|
@@ -166,6 +204,7 @@ class BaseDict(dict[_K, _V]):
|
|
|
166
204
|
return super().setdefault(key, default)
|
|
167
205
|
|
|
168
206
|
def update(self, other: Any) -> None:
|
|
207
|
+
self._check_frozen()
|
|
169
208
|
other = self._get_dict_or_value(other)
|
|
170
209
|
if self._dict is not None:
|
|
171
210
|
self._dict.update(other)
|
|
@@ -68,6 +68,22 @@ class KeyattrDict(BaseDict[_K, _V]):
|
|
|
68
68
|
raise AttributeError(attr_message)
|
|
69
69
|
self.__setitem__(attr_k, value)
|
|
70
70
|
|
|
71
|
+
def __dir__(self) -> list[str]:
|
|
72
|
+
# Extend tab-completion (VSCode, IPython, etc.) with the dict keys when
|
|
73
|
+
# keyattr is enabled, since keys are accessible as attributes via __getattr__.
|
|
74
|
+
# Non-string keys are excluded: __dir__ must return strings (Python protocol)
|
|
75
|
+
# and non-string keys can never be valid attribute names anyway.
|
|
76
|
+
keys: list[str] = (
|
|
77
|
+
[key for key in self.keys() if isinstance(key, str)]
|
|
78
|
+
if self._keyattr_enabled
|
|
79
|
+
else []
|
|
80
|
+
)
|
|
81
|
+
return sorted(set(list(super().__dir__()) + keys))
|
|
82
|
+
|
|
83
|
+
def _ipython_key_completions_(self) -> list[Any]:
|
|
84
|
+
# Used by IPython/Jupyter for subscript tab-completion: obj["<TAB>"]
|
|
85
|
+
return list(self.keys())
|
|
86
|
+
|
|
71
87
|
def __setstate__(self, state: Mapping[str, Any]) -> None:
|
|
72
88
|
super().__setstate__(state)
|
|
73
89
|
self._keyattr_enabled = state["_keyattr_enabled"]
|
|
@@ -156,6 +156,17 @@ class XLSSerializer(AbstractSerializer[str, list[dict[str, Any]]]):
|
|
|
156
156
|
values = [cell.value for cell in row]
|
|
157
157
|
items.append(dict(zip(columns, values)))
|
|
158
158
|
|
|
159
|
+
try:
|
|
160
|
+
# explicitly close vba_archive if present: Workbook.close() never
|
|
161
|
+
# closes it, so relying on GC (__del__ -> close()) is unsafe because
|
|
162
|
+
# the underlying BytesIO may already be finalized first, causing a
|
|
163
|
+
# ValueError when ZipFile tries to flush the end-of-central-directory
|
|
164
|
+
vba_archive = getattr(workbook, "vba_archive", None)
|
|
165
|
+
if vba_archive is not None:
|
|
166
|
+
vba_archive.close()
|
|
167
|
+
except Exception:
|
|
168
|
+
pass
|
|
169
|
+
|
|
159
170
|
# close the worksheet
|
|
160
171
|
workbook.close()
|
|
161
172
|
|
|
@@ -81,6 +81,7 @@ classifiers = [
|
|
|
81
81
|
"Programming Language :: Python :: 3.11",
|
|
82
82
|
"Programming Language :: Python :: 3.12",
|
|
83
83
|
"Programming Language :: Python :: 3.13",
|
|
84
|
+
"Programming Language :: Python :: 3.14",
|
|
84
85
|
"Topic :: Education :: Testing",
|
|
85
86
|
"Topic :: Software Development :: Build Tools",
|
|
86
87
|
"Topic :: System :: Filesystems",
|
|
@@ -89,7 +90,7 @@ classifiers = [
|
|
|
89
90
|
"Typing :: Typed",
|
|
90
91
|
]
|
|
91
92
|
dependencies = [
|
|
92
|
-
"python-fsutil >= 0.
|
|
93
|
+
"python-fsutil >= 0.16.0, < 1.0.0",
|
|
93
94
|
"python-slugify >= 7.0.0, < 9.0.0",
|
|
94
95
|
"requests >= 2.26.0, < 3.0.0",
|
|
95
96
|
"typing_extensions >= 4.13.2, < 4.16.0",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-benedict
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.36.0
|
|
4
4
|
Summary: python-benedict is a dict subclass with keylist/keypath/keyattr support, normalized I/O operations (base64, csv, ini, json, pickle, plist, query-string, toml, xls, xml, yaml) and many utilities... for humans, obviously.
|
|
5
5
|
Author-email: Fabio Caccamo <fabio.caccamo@gmail.com>
|
|
6
6
|
Maintainer-email: Fabio Caccamo <fabio.caccamo@gmail.com>
|
|
@@ -51,6 +51,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
51
51
|
Classifier: Programming Language :: Python :: 3.11
|
|
52
52
|
Classifier: Programming Language :: Python :: 3.12
|
|
53
53
|
Classifier: Programming Language :: Python :: 3.13
|
|
54
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
54
55
|
Classifier: Topic :: Education :: Testing
|
|
55
56
|
Classifier: Topic :: Software Development :: Build Tools
|
|
56
57
|
Classifier: Topic :: System :: Filesystems
|
|
@@ -59,7 +60,7 @@ Classifier: Topic :: Utilities
|
|
|
59
60
|
Classifier: Typing :: Typed
|
|
60
61
|
Description-Content-Type: text/markdown
|
|
61
62
|
License-File: LICENSE.txt
|
|
62
|
-
Requires-Dist: python-fsutil<1.0.0,>=0.
|
|
63
|
+
Requires-Dist: python-fsutil<1.0.0,>=0.16.0
|
|
63
64
|
Requires-Dist: python-slugify<9.0.0,>=7.0.0
|
|
64
65
|
Requires-Dist: requests<3.0.0,>=2.26.0
|
|
65
66
|
Requires-Dist: typing_extensions<4.16.0,>=4.13.2
|
|
@@ -94,6 +95,7 @@ Dynamic: license-file
|
|
|
94
95
|
[](https://pepy.tech/project/python-benedict)
|
|
95
96
|
[](https://github.com/fabiocaccamo/python-benedict/stargazers)
|
|
96
97
|
[](https://github.com/fabiocaccamo/python-benedict/blob/main/LICENSE.txt)
|
|
98
|
+
[](https://github.com/fabiocaccamo/python-benedict/releases/latest)
|
|
97
99
|
|
|
98
100
|
[](https://results.pre-commit.ci/latest/github/fabiocaccamo/python-benedict/main)
|
|
99
101
|
[](https://github.com/fabiocaccamo/python-benedict)
|
|
@@ -102,6 +104,7 @@ Dynamic: license-file
|
|
|
102
104
|
[](https://scrutinizer-ci.com/g/fabiocaccamo/python-benedict/?branch=main)
|
|
103
105
|
[](https://github.com/psf/black)
|
|
104
106
|
[](https://github.com/astral-sh/ruff)
|
|
107
|
+
[](https://scorecard.dev/viewer/?uri=github.com/fabiocaccamo/python-benedict)
|
|
105
108
|
|
|
106
109
|
# python-benedict
|
|
107
110
|
python-benedict is a dict subclass with **keylist/keypath/keyattr** support, **I/O** shortcuts (`base64`, `cli`, `csv`, `html`, `ini`, `json`, `pickle`, `plist`, `query-string`, `toml`, `xls`, `xml`, `yaml`) and many **utilities**... for humans, obviously.
|
|
@@ -135,6 +138,7 @@ python-benedict is a dict subclass with **keylist/keypath/keyattr** support, **I
|
|
|
135
138
|
- [I/O methods](#io-methods)
|
|
136
139
|
- [Parse methods](#parse-methods)
|
|
137
140
|
- [Testing](#testing)
|
|
141
|
+
- [Security](#security)
|
|
138
142
|
- [License](#license)
|
|
139
143
|
|
|
140
144
|
## Installation
|
|
@@ -219,6 +223,9 @@ d.keyattr_dynamic = True
|
|
|
219
223
|
|
|
220
224
|
> **Warning** - even if this feature is very useful, it has some obvious limitations: it works only for string keys that are *unprotected* (not starting with an `_`) and that don't clash with the currently supported methods names.
|
|
221
225
|
|
|
226
|
+
#### Tab-completion support
|
|
227
|
+
`benedict` supports tab-completion in **VSCode**, **IPython**, and **Jupyter** for both attribute-style access (`d.<TAB>`) and subscript-style access (`d["<TAB>"]`).
|
|
228
|
+
|
|
222
229
|
### Keylist
|
|
223
230
|
Wherever a **key** is used, it is possible to use also a **list of keys**.
|
|
224
231
|
|
|
@@ -379,6 +386,8 @@ Here are the details of the supported formats, operations and extra options docs
|
|
|
379
386
|
- [`filter`](#filter)
|
|
380
387
|
- [`find`](#find)
|
|
381
388
|
- [`flatten`](#flatten)
|
|
389
|
+
- [`freeze`](#freeze)
|
|
390
|
+
- [`frozen`](#frozen)
|
|
382
391
|
- [`groupby`](#groupby)
|
|
383
392
|
- [`invert`](#invert)
|
|
384
393
|
- [`items_sorted_by_keys`](#items_sorted_by_keys)
|
|
@@ -396,6 +405,7 @@ Here are the details of the supported formats, operations and extra options docs
|
|
|
396
405
|
- [`swap`](#swap)
|
|
397
406
|
- [`traverse`](#traverse)
|
|
398
407
|
- [`unflatten`](#unflatten)
|
|
408
|
+
- [`unfreeze`](#unfreeze)
|
|
399
409
|
- [`unique`](#unique)
|
|
400
410
|
|
|
401
411
|
- **I/O methods**
|
|
@@ -511,6 +521,22 @@ f = d.find(keys, default=0)
|
|
|
511
521
|
f = d.flatten(separator="_")
|
|
512
522
|
```
|
|
513
523
|
|
|
524
|
+
#### `freeze`
|
|
525
|
+
|
|
526
|
+
```python
|
|
527
|
+
# Make the dict immutable: any attempt to modify it will raise a TypeError.
|
|
528
|
+
# Only top-level keys are frozen; nested dicts are not affected.
|
|
529
|
+
d.freeze()
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
#### `frozen`
|
|
533
|
+
|
|
534
|
+
```python
|
|
535
|
+
# Return True if the dict is frozen (immutable), False otherwise.
|
|
536
|
+
if d.frozen:
|
|
537
|
+
...
|
|
538
|
+
```
|
|
539
|
+
|
|
514
540
|
#### `groupby`
|
|
515
541
|
|
|
516
542
|
```python
|
|
@@ -649,6 +675,13 @@ d.traverse(f)
|
|
|
649
675
|
u = d.unflatten(separator="_")
|
|
650
676
|
```
|
|
651
677
|
|
|
678
|
+
#### `unfreeze`
|
|
679
|
+
|
|
680
|
+
```python
|
|
681
|
+
# Make the dict mutable again after a freeze() call.
|
|
682
|
+
d.unfreeze()
|
|
683
|
+
```
|
|
684
|
+
|
|
652
685
|
#### `unique`
|
|
653
686
|
|
|
654
687
|
```python
|
|
@@ -1121,6 +1154,13 @@ tox
|
|
|
1121
1154
|
python -m unittest
|
|
1122
1155
|
```
|
|
1123
1156
|
|
|
1157
|
+
## Security
|
|
1158
|
+
|
|
1159
|
+
- **SBOM** — a Software Bill of Materials in [CycloneDX](https://cyclonedx.org/) format (JSON and XML) is generated and published as a release asset on every release. You can download it from the [Releases](https://github.com/fabiocaccamo/python-benedict/releases/latest) page.
|
|
1160
|
+
- **Trusted Publishing** — packages are published to PyPI via [OIDC Trusted Publishing](https://docs.pypi.org/trusted-publishers/), without long-lived secrets.
|
|
1161
|
+
- **OpenSSF Scorecard** — the repository is evaluated weekly against the [OpenSSF Scorecard](https://scorecard.dev/viewer/?uri=github.com/fabiocaccamo/python-benedict) checks; results are visible in the GitHub Security tab.
|
|
1162
|
+
- **Reporting** — to report a vulnerability, please follow the [Security Policy](SECURITY.md).
|
|
1163
|
+
|
|
1124
1164
|
## License
|
|
1125
1165
|
Released under [MIT License](LICENSE.txt).
|
|
1126
1166
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_benedict-0.35.0 → python_benedict-0.36.0}/python_benedict.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|