python-benedict 0.34.1__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.34.1/python_benedict.egg-info → python_benedict-0.36.0}/PKG-INFO +50 -7
- {python_benedict-0.34.1 → python_benedict-0.36.0}/README.md +40 -2
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/core/clean.py +28 -10
- python_benedict-0.36.0/benedict/core/clone.py +18 -0
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/core/dump.py +3 -1
- python_benedict-0.36.0/benedict/core/filter.py +21 -0
- python_benedict-0.36.0/benedict/core/find.py +16 -0
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/core/flatten.py +13 -6
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/core/groupby.py +10 -2
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/core/invert.py +12 -4
- python_benedict-0.36.0/benedict/core/items_sorted.py +25 -0
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/core/keylists.py +17 -4
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/core/keypaths.py +11 -1
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/core/match.py +10 -1
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/core/merge.py +24 -5
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/core/move.py +9 -1
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/core/nest.py +17 -2
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/core/remove.py +4 -1
- python_benedict-0.36.0/benedict/core/rename.py +10 -0
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/core/search.py +24 -5
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/core/standardize.py +5 -3
- python_benedict-0.36.0/benedict/core/subset.py +24 -0
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/core/swap.py +7 -1
- python_benedict-0.36.0/benedict/core/traverse.py +46 -0
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/core/unflatten.py +8 -3
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/core/unique.py +5 -1
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/dicts/__init__.py +112 -61
- python_benedict-0.36.0/benedict/dicts/base/base_dict.py +217 -0
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/dicts/io/io_dict.py +75 -47
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/dicts/io/io_util.py +44 -23
- python_benedict-0.36.0/benedict/dicts/keyattr/keyattr_dict.py +90 -0
- python_benedict-0.36.0/benedict/dicts/keylist/keylist_dict.py +116 -0
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/dicts/keylist/keylist_util.py +21 -9
- python_benedict-0.36.0/benedict/dicts/keypath/keypath_dict.py +77 -0
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/dicts/keypath/keypath_util.py +12 -8
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/dicts/parse/parse_dict.py +200 -60
- python_benedict-0.36.0/benedict/dicts/parse/parse_util.py +344 -0
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/exceptions.py +1 -1
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/extras.py +8 -8
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/metadata.py +1 -1
- python_benedict-0.36.0/benedict/py.typed +0 -0
- python_benedict-0.36.0/benedict/serializers/__init__.py +194 -0
- python_benedict-0.36.0/benedict/serializers/abstract.py +27 -0
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/serializers/base64.py +23 -13
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/serializers/cli.py +13 -8
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/serializers/csv.py +8 -5
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/serializers/html.py +8 -4
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/serializers/ini.py +20 -9
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/serializers/json.py +10 -7
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/serializers/pickle.py +10 -4
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/serializers/plist.py +5 -4
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/serializers/query_string.py +20 -6
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/serializers/toml.py +6 -4
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/serializers/xls.py +32 -13
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/serializers/xml.py +9 -5
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/serializers/yaml.py +9 -7
- python_benedict-0.36.0/benedict/utils/type_util.py +109 -0
- {python_benedict-0.34.1 → python_benedict-0.36.0}/pyproject.toml +31 -4
- {python_benedict-0.34.1 → python_benedict-0.36.0/python_benedict.egg-info}/PKG-INFO +50 -7
- {python_benedict-0.34.1 → python_benedict-0.36.0}/python_benedict.egg-info/SOURCES.txt +1 -0
- {python_benedict-0.34.1 → python_benedict-0.36.0}/python_benedict.egg-info/requires.txt +5 -3
- python_benedict-0.34.1/benedict/core/clone.py +0 -8
- python_benedict-0.34.1/benedict/core/filter.py +0 -13
- python_benedict-0.34.1/benedict/core/find.py +0 -5
- python_benedict-0.34.1/benedict/core/items_sorted.py +0 -10
- python_benedict-0.34.1/benedict/core/rename.py +0 -5
- python_benedict-0.34.1/benedict/core/subset.py +0 -12
- python_benedict-0.34.1/benedict/core/traverse.py +0 -29
- python_benedict-0.34.1/benedict/dicts/base/base_dict.py +0 -163
- python_benedict-0.34.1/benedict/dicts/keyattr/keyattr_dict.py +0 -62
- python_benedict-0.34.1/benedict/dicts/keylist/keylist_dict.py +0 -95
- python_benedict-0.34.1/benedict/dicts/keypath/keypath_dict.py +0 -65
- python_benedict-0.34.1/benedict/dicts/parse/parse_util.py +0 -231
- python_benedict-0.34.1/benedict/serializers/__init__.py +0 -88
- python_benedict-0.34.1/benedict/serializers/abstract.py +0 -17
- python_benedict-0.34.1/benedict/utils/type_util.py +0 -95
- {python_benedict-0.34.1 → python_benedict-0.36.0}/LICENSE.txt +0 -0
- {python_benedict-0.34.1 → python_benedict-0.36.0}/MANIFEST.in +0 -0
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/__init__.py +0 -0
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/core/__init__.py +0 -0
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/dicts/base/__init__.py +0 -0
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/dicts/io/__init__.py +0 -0
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/dicts/keyattr/__init__.py +0 -0
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/dicts/keylist/__init__.py +0 -0
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/dicts/keypath/__init__.py +0 -0
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/dicts/parse/__init__.py +0 -0
- {python_benedict-0.34.1 → python_benedict-0.36.0}/benedict/utils/__init__.py +0 -0
- {python_benedict-0.34.1 → python_benedict-0.36.0}/python_benedict.egg-info/dependency_links.txt +0 -0
- {python_benedict-0.34.1 → python_benedict-0.36.0}/python_benedict.egg-info/top_level.txt +0 -0
- {python_benedict-0.34.1 → python_benedict-0.36.0}/setup.cfg +0 -0
- {python_benedict-0.34.1 → python_benedict-0.36.0}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
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,16 +51,20 @@ 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
|
|
57
58
|
Classifier: Topic :: Text Processing :: Markup :: XML
|
|
58
59
|
Classifier: Topic :: Utilities
|
|
60
|
+
Classifier: Typing :: Typed
|
|
59
61
|
Description-Content-Type: text/markdown
|
|
60
62
|
License-File: LICENSE.txt
|
|
61
|
-
Requires-Dist: python-fsutil<1.0.0,>=0.
|
|
63
|
+
Requires-Dist: python-fsutil<1.0.0,>=0.16.0
|
|
62
64
|
Requires-Dist: python-slugify<9.0.0,>=7.0.0
|
|
63
65
|
Requires-Dist: requests<3.0.0,>=2.26.0
|
|
66
|
+
Requires-Dist: typing_extensions<4.16.0,>=4.13.2
|
|
67
|
+
Requires-Dist: useful-types<0.3.0,>=0.2.1
|
|
64
68
|
Provides-Extra: all
|
|
65
69
|
Requires-Dist: python-benedict[io,parse,s3]; extra == "all"
|
|
66
70
|
Provides-Extra: html
|
|
@@ -71,7 +75,7 @@ Requires-Dist: python-benedict[html,toml,xls,xml,yaml]; extra == "io"
|
|
|
71
75
|
Provides-Extra: parse
|
|
72
76
|
Requires-Dist: ftfy<7.0.0,>=6.0.0; extra == "parse"
|
|
73
77
|
Requires-Dist: mailchecker<7.0.0,>=4.1.0; extra == "parse"
|
|
74
|
-
Requires-Dist: phonenumbers<
|
|
78
|
+
Requires-Dist: phonenumbers<10.0.0,>=8.12.0; extra == "parse"
|
|
75
79
|
Requires-Dist: python-dateutil<3.0.0,>=2.8.0; extra == "parse"
|
|
76
80
|
Provides-Extra: s3
|
|
77
81
|
Requires-Dist: boto3<2.0.0,>=1.24.89; extra == "s3"
|
|
@@ -81,24 +85,26 @@ Provides-Extra: xls
|
|
|
81
85
|
Requires-Dist: openpyxl<4.0.0,>=3.0.0; extra == "xls"
|
|
82
86
|
Requires-Dist: xlrd<3.0.0,>=2.0.0; extra == "xls"
|
|
83
87
|
Provides-Extra: xml
|
|
84
|
-
Requires-Dist: xmltodict<
|
|
88
|
+
Requires-Dist: xmltodict<2.0.0,>=0.12.0; extra == "xml"
|
|
85
89
|
Provides-Extra: yaml
|
|
86
90
|
Requires-Dist: pyyaml<7.0,>=6.0; extra == "yaml"
|
|
91
|
+
Dynamic: license-file
|
|
87
92
|
|
|
88
93
|
[](https://www.python.org/)
|
|
89
94
|
[](https://pypi.org/project/python-benedict/)
|
|
90
95
|
[](https://pepy.tech/project/python-benedict)
|
|
91
96
|
[](https://github.com/fabiocaccamo/python-benedict/stargazers)
|
|
92
97
|
[](https://github.com/fabiocaccamo/python-benedict/blob/main/LICENSE.txt)
|
|
98
|
+
[](https://github.com/fabiocaccamo/python-benedict/releases/latest)
|
|
93
99
|
|
|
94
100
|
[](https://results.pre-commit.ci/latest/github/fabiocaccamo/python-benedict/main)
|
|
95
101
|
[](https://github.com/fabiocaccamo/python-benedict)
|
|
96
102
|
[](https://codecov.io/gh/fabiocaccamo/python-benedict)
|
|
97
|
-
[](https://codeclimate.com/github/fabiocaccamo/python-benedict/)
|
|
98
103
|
[](https://www.codacy.com/app/fabiocaccamo/python-benedict)
|
|
99
104
|
[](https://scrutinizer-ci.com/g/fabiocaccamo/python-benedict/?branch=main)
|
|
100
105
|
[](https://github.com/psf/black)
|
|
101
106
|
[](https://github.com/astral-sh/ruff)
|
|
107
|
+
[](https://scorecard.dev/viewer/?uri=github.com/fabiocaccamo/python-benedict)
|
|
102
108
|
|
|
103
109
|
# python-benedict
|
|
104
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.
|
|
@@ -132,6 +138,7 @@ python-benedict is a dict subclass with **keylist/keypath/keyattr** support, **I
|
|
|
132
138
|
- [I/O methods](#io-methods)
|
|
133
139
|
- [Parse methods](#parse-methods)
|
|
134
140
|
- [Testing](#testing)
|
|
141
|
+
- [Security](#security)
|
|
135
142
|
- [License](#license)
|
|
136
143
|
|
|
137
144
|
## Installation
|
|
@@ -216,6 +223,9 @@ d.keyattr_dynamic = True
|
|
|
216
223
|
|
|
217
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.
|
|
218
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
|
+
|
|
219
229
|
### Keylist
|
|
220
230
|
Wherever a **key** is used, it is possible to use also a **list of keys**.
|
|
221
231
|
|
|
@@ -376,6 +386,8 @@ Here are the details of the supported formats, operations and extra options docs
|
|
|
376
386
|
- [`filter`](#filter)
|
|
377
387
|
- [`find`](#find)
|
|
378
388
|
- [`flatten`](#flatten)
|
|
389
|
+
- [`freeze`](#freeze)
|
|
390
|
+
- [`frozen`](#frozen)
|
|
379
391
|
- [`groupby`](#groupby)
|
|
380
392
|
- [`invert`](#invert)
|
|
381
393
|
- [`items_sorted_by_keys`](#items_sorted_by_keys)
|
|
@@ -393,6 +405,7 @@ Here are the details of the supported formats, operations and extra options docs
|
|
|
393
405
|
- [`swap`](#swap)
|
|
394
406
|
- [`traverse`](#traverse)
|
|
395
407
|
- [`unflatten`](#unflatten)
|
|
408
|
+
- [`unfreeze`](#unfreeze)
|
|
396
409
|
- [`unique`](#unique)
|
|
397
410
|
|
|
398
411
|
- **I/O methods**
|
|
@@ -508,6 +521,22 @@ f = d.find(keys, default=0)
|
|
|
508
521
|
f = d.flatten(separator="_")
|
|
509
522
|
```
|
|
510
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
|
+
|
|
511
540
|
#### `groupby`
|
|
512
541
|
|
|
513
542
|
```python
|
|
@@ -646,6 +675,13 @@ d.traverse(f)
|
|
|
646
675
|
u = d.unflatten(separator="_")
|
|
647
676
|
```
|
|
648
677
|
|
|
678
|
+
#### `unfreeze`
|
|
679
|
+
|
|
680
|
+
```python
|
|
681
|
+
# Make the dict mutable again after a freeze() call.
|
|
682
|
+
d.unfreeze()
|
|
683
|
+
```
|
|
684
|
+
|
|
649
685
|
#### `unique`
|
|
650
686
|
|
|
651
687
|
```python
|
|
@@ -1118,6 +1154,13 @@ tox
|
|
|
1118
1154
|
python -m unittest
|
|
1119
1155
|
```
|
|
1120
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
|
+
|
|
1121
1164
|
## License
|
|
1122
1165
|
Released under [MIT License](LICENSE.txt).
|
|
1123
1166
|
|
|
@@ -1127,7 +1170,7 @@ Released under [MIT License](LICENSE.txt).
|
|
|
1127
1170
|
|
|
1128
1171
|
- :star: Star this project on [GitHub](https://github.com/fabiocaccamo/python-benedict)
|
|
1129
1172
|
- :octocat: Follow me on [GitHub](https://github.com/fabiocaccamo)
|
|
1130
|
-
- :blue_heart: Follow me on [
|
|
1173
|
+
- :blue_heart: Follow me on [Bluesky](https://bsky.app/profile/fabiocaccamo.bsky.social)
|
|
1131
1174
|
- :moneybag: Sponsor me on [Github](https://github.com/sponsors/fabiocaccamo)
|
|
1132
1175
|
|
|
1133
1176
|
## See also
|
|
@@ -3,15 +3,16 @@
|
|
|
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)
|
|
9
10
|
[](https://codecov.io/gh/fabiocaccamo/python-benedict)
|
|
10
|
-
[](https://codeclimate.com/github/fabiocaccamo/python-benedict/)
|
|
11
11
|
[](https://www.codacy.com/app/fabiocaccamo/python-benedict)
|
|
12
12
|
[](https://scrutinizer-ci.com/g/fabiocaccamo/python-benedict/?branch=main)
|
|
13
13
|
[](https://github.com/psf/black)
|
|
14
14
|
[](https://github.com/astral-sh/ruff)
|
|
15
|
+
[](https://scorecard.dev/viewer/?uri=github.com/fabiocaccamo/python-benedict)
|
|
15
16
|
|
|
16
17
|
# python-benedict
|
|
17
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.
|
|
@@ -45,6 +46,7 @@ python-benedict is a dict subclass with **keylist/keypath/keyattr** support, **I
|
|
|
45
46
|
- [I/O methods](#io-methods)
|
|
46
47
|
- [Parse methods](#parse-methods)
|
|
47
48
|
- [Testing](#testing)
|
|
49
|
+
- [Security](#security)
|
|
48
50
|
- [License](#license)
|
|
49
51
|
|
|
50
52
|
## Installation
|
|
@@ -129,6 +131,9 @@ d.keyattr_dynamic = True
|
|
|
129
131
|
|
|
130
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.
|
|
131
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
|
+
|
|
132
137
|
### Keylist
|
|
133
138
|
Wherever a **key** is used, it is possible to use also a **list of keys**.
|
|
134
139
|
|
|
@@ -289,6 +294,8 @@ Here are the details of the supported formats, operations and extra options docs
|
|
|
289
294
|
- [`filter`](#filter)
|
|
290
295
|
- [`find`](#find)
|
|
291
296
|
- [`flatten`](#flatten)
|
|
297
|
+
- [`freeze`](#freeze)
|
|
298
|
+
- [`frozen`](#frozen)
|
|
292
299
|
- [`groupby`](#groupby)
|
|
293
300
|
- [`invert`](#invert)
|
|
294
301
|
- [`items_sorted_by_keys`](#items_sorted_by_keys)
|
|
@@ -306,6 +313,7 @@ Here are the details of the supported formats, operations and extra options docs
|
|
|
306
313
|
- [`swap`](#swap)
|
|
307
314
|
- [`traverse`](#traverse)
|
|
308
315
|
- [`unflatten`](#unflatten)
|
|
316
|
+
- [`unfreeze`](#unfreeze)
|
|
309
317
|
- [`unique`](#unique)
|
|
310
318
|
|
|
311
319
|
- **I/O methods**
|
|
@@ -421,6 +429,22 @@ f = d.find(keys, default=0)
|
|
|
421
429
|
f = d.flatten(separator="_")
|
|
422
430
|
```
|
|
423
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
|
+
|
|
424
448
|
#### `groupby`
|
|
425
449
|
|
|
426
450
|
```python
|
|
@@ -559,6 +583,13 @@ d.traverse(f)
|
|
|
559
583
|
u = d.unflatten(separator="_")
|
|
560
584
|
```
|
|
561
585
|
|
|
586
|
+
#### `unfreeze`
|
|
587
|
+
|
|
588
|
+
```python
|
|
589
|
+
# Make the dict mutable again after a freeze() call.
|
|
590
|
+
d.unfreeze()
|
|
591
|
+
```
|
|
592
|
+
|
|
562
593
|
#### `unique`
|
|
563
594
|
|
|
564
595
|
```python
|
|
@@ -1031,6 +1062,13 @@ tox
|
|
|
1031
1062
|
python -m unittest
|
|
1032
1063
|
```
|
|
1033
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
|
+
|
|
1034
1072
|
## License
|
|
1035
1073
|
Released under [MIT License](LICENSE.txt).
|
|
1036
1074
|
|
|
@@ -1040,7 +1078,7 @@ Released under [MIT License](LICENSE.txt).
|
|
|
1040
1078
|
|
|
1041
1079
|
- :star: Star this project on [GitHub](https://github.com/fabiocaccamo/python-benedict)
|
|
1042
1080
|
- :octocat: Follow me on [GitHub](https://github.com/fabiocaccamo)
|
|
1043
|
-
- :blue_heart: Follow me on [
|
|
1081
|
+
- :blue_heart: Follow me on [Bluesky](https://bsky.app/profile/fabiocaccamo.bsky.social)
|
|
1044
1082
|
- :moneybag: Sponsor me on [Github](https://github.com/sponsors/fabiocaccamo)
|
|
1045
1083
|
|
|
1046
1084
|
## See also
|
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import MutableMapping, MutableSequence
|
|
4
|
+
from typing import Any, TypeVar
|
|
5
|
+
|
|
6
|
+
_K = TypeVar("_K")
|
|
7
|
+
_V = TypeVar("_V")
|
|
8
|
+
_T = TypeVar("_T")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _clean_dict(
|
|
12
|
+
d: MutableMapping[_K, _V], strings: bool, collections: bool
|
|
13
|
+
) -> MutableMapping[_K, _V]:
|
|
2
14
|
keys = list(d.keys())
|
|
3
15
|
for key in keys:
|
|
4
16
|
d[key] = _clean_value(d[key], strings=strings, collections=collections)
|
|
@@ -7,7 +19,9 @@ def _clean_dict(d, strings, collections):
|
|
|
7
19
|
return d
|
|
8
20
|
|
|
9
21
|
|
|
10
|
-
def _clean_list(
|
|
22
|
+
def _clean_list(
|
|
23
|
+
ls: MutableSequence[_T], strings: bool, collections: bool
|
|
24
|
+
) -> MutableSequence[_T]:
|
|
11
25
|
for i in range(len(ls) - 1, -1, -1):
|
|
12
26
|
ls[i] = _clean_value(ls[i], strings=strings, collections=collections)
|
|
13
27
|
if ls[i] is None:
|
|
@@ -15,7 +29,7 @@ def _clean_list(ls, strings, collections):
|
|
|
15
29
|
return ls
|
|
16
30
|
|
|
17
31
|
|
|
18
|
-
def _clean_set(values, strings, collections):
|
|
32
|
+
def _clean_set(values: set[_T], strings: bool, collections: bool) -> set[_T]:
|
|
19
33
|
return {
|
|
20
34
|
value
|
|
21
35
|
for value in values
|
|
@@ -23,11 +37,13 @@ def _clean_set(values, strings, collections):
|
|
|
23
37
|
}
|
|
24
38
|
|
|
25
39
|
|
|
26
|
-
def _clean_str(s, strings, collections):
|
|
40
|
+
def _clean_str(s: str, strings: bool, collections: bool) -> str | None:
|
|
27
41
|
return s if s and s.strip() else None
|
|
28
42
|
|
|
29
43
|
|
|
30
|
-
def _clean_tuple(
|
|
44
|
+
def _clean_tuple(
|
|
45
|
+
values: tuple[_T, ...], strings: bool, collections: bool
|
|
46
|
+
) -> tuple[_T, ...]:
|
|
31
47
|
return tuple(
|
|
32
48
|
value
|
|
33
49
|
for value in values
|
|
@@ -35,13 +51,15 @@ def _clean_tuple(values, strings, collections):
|
|
|
35
51
|
)
|
|
36
52
|
|
|
37
53
|
|
|
38
|
-
def _clean_value(value, strings, collections):
|
|
54
|
+
def _clean_value(value: Any, strings: bool, collections: bool) -> Any:
|
|
39
55
|
if value is None:
|
|
40
56
|
return value
|
|
41
|
-
elif isinstance(value,
|
|
57
|
+
elif isinstance(value, MutableSequence) and collections:
|
|
42
58
|
value = _clean_list(value, strings=strings, collections=collections) or None
|
|
43
|
-
elif isinstance(value,
|
|
44
|
-
value =
|
|
59
|
+
elif isinstance(value, MutableMapping) and collections:
|
|
60
|
+
value = (
|
|
61
|
+
_clean_dict(dict(value), strings=strings, collections=collections) or None
|
|
62
|
+
)
|
|
45
63
|
elif isinstance(value, set) and collections:
|
|
46
64
|
value = _clean_set(value, strings=strings, collections=collections) or None
|
|
47
65
|
elif isinstance(value, str) and strings:
|
|
@@ -51,5 +69,5 @@ def _clean_value(value, strings, collections):
|
|
|
51
69
|
return value
|
|
52
70
|
|
|
53
71
|
|
|
54
|
-
def clean(d, strings=True, collections=True):
|
|
72
|
+
def clean(d: Any, strings: bool = True, collections: bool = True) -> Any:
|
|
55
73
|
return _clean_dict(d, strings=strings, collections=collections)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
from collections.abc import MutableMapping
|
|
5
|
+
from typing import Any, TypeVar
|
|
6
|
+
|
|
7
|
+
_T = TypeVar("_T")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def clone(
|
|
11
|
+
obj: _T,
|
|
12
|
+
empty: bool = False,
|
|
13
|
+
memo: dict[int, Any] | None = None,
|
|
14
|
+
) -> _T:
|
|
15
|
+
d = copy.deepcopy(obj, memo)
|
|
16
|
+
if empty and isinstance(d, MutableMapping):
|
|
17
|
+
d.clear()
|
|
18
|
+
return d
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from collections.abc import Callable, MutableMapping
|
|
2
|
+
from typing import TypeVar
|
|
3
|
+
|
|
4
|
+
from benedict.core.clone import clone
|
|
5
|
+
|
|
6
|
+
_K = TypeVar("_K")
|
|
7
|
+
_V = TypeVar("_V")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def filter(
|
|
11
|
+
d: MutableMapping[_K, _V], predicate: Callable[[_K, _V], bool]
|
|
12
|
+
) -> MutableMapping[_K, _V]:
|
|
13
|
+
if not callable(predicate):
|
|
14
|
+
raise ValueError("predicate argument must be a callable.")
|
|
15
|
+
new_dict = clone(d, empty=True)
|
|
16
|
+
keys = list(d.keys())
|
|
17
|
+
for key in keys:
|
|
18
|
+
value = d[key]
|
|
19
|
+
if predicate(key, value):
|
|
20
|
+
new_dict[key] = value
|
|
21
|
+
return new_dict
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable, Mapping
|
|
4
|
+
from typing import TypeVar
|
|
5
|
+
|
|
6
|
+
_K = TypeVar("_K")
|
|
7
|
+
_V = TypeVar("_V")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def find(
|
|
11
|
+
d: Mapping[_K, _V], keys: Iterable[_K], default: _V | None = None
|
|
12
|
+
) -> _V | None:
|
|
13
|
+
for key in keys:
|
|
14
|
+
if key in d:
|
|
15
|
+
return d[key]
|
|
16
|
+
return default
|
|
@@ -1,20 +1,27 @@
|
|
|
1
|
-
from
|
|
2
|
-
from
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
from typing import Any
|
|
3
3
|
|
|
4
|
+
from benedict.core.clone import clone
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
|
|
7
|
+
def _flatten_key(base_key: str, key: str, separator: str) -> str:
|
|
6
8
|
if base_key and separator:
|
|
7
9
|
return f"{base_key}{separator}{key}"
|
|
8
10
|
return key
|
|
9
11
|
|
|
10
12
|
|
|
11
|
-
def _flatten_item(
|
|
13
|
+
def _flatten_item(
|
|
14
|
+
d: Any,
|
|
15
|
+
base_dict: Any,
|
|
16
|
+
base_key: str,
|
|
17
|
+
separator: str,
|
|
18
|
+
) -> Any:
|
|
12
19
|
new_dict = base_dict
|
|
13
20
|
keys = list(d.keys())
|
|
14
21
|
for key in keys:
|
|
15
22
|
new_key = _flatten_key(base_key, key, separator)
|
|
16
23
|
value = d.get(key, None)
|
|
17
|
-
if
|
|
24
|
+
if isinstance(value, Mapping):
|
|
18
25
|
new_value = _flatten_item(
|
|
19
26
|
value, base_dict=new_dict, base_key=new_key, separator=separator
|
|
20
27
|
)
|
|
@@ -26,6 +33,6 @@ def _flatten_item(d, base_dict, base_key, separator):
|
|
|
26
33
|
return new_dict
|
|
27
34
|
|
|
28
35
|
|
|
29
|
-
def flatten(d, separator="_"):
|
|
36
|
+
def flatten(d: Any, separator: str = "_") -> Any:
|
|
30
37
|
new_dict = clone(d, empty=True)
|
|
31
38
|
return _flatten_item(d, base_dict=new_dict, base_key="", separator=separator)
|
|
@@ -1,10 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping, MutableSequence, Sequence
|
|
4
|
+
from typing import Any, TypeVar
|
|
5
|
+
|
|
1
6
|
from benedict.utils import type_util
|
|
2
7
|
|
|
8
|
+
_K = TypeVar("_K")
|
|
9
|
+
_V = TypeVar("_V", bound=MutableSequence[Any])
|
|
10
|
+
|
|
3
11
|
|
|
4
|
-
def groupby(items, key):
|
|
12
|
+
def groupby(items: Sequence[Mapping[_K, Any]], key: _K) -> dict[Any, Any]:
|
|
5
13
|
if not type_util.is_list(items):
|
|
6
14
|
raise ValueError("items should be a list of dicts.")
|
|
7
|
-
items_grouped = {}
|
|
15
|
+
items_grouped: dict[Any, Any] = {}
|
|
8
16
|
for item in items:
|
|
9
17
|
if not type_util.is_dict(item):
|
|
10
18
|
raise ValueError("item should be a dict.")
|
|
@@ -1,20 +1,28 @@
|
|
|
1
|
-
from
|
|
1
|
+
from collections.abc import MutableMapping, Sequence
|
|
2
|
+
from typing import Any, TypeVar
|
|
3
|
+
|
|
4
|
+
from benedict.core.clone import clone
|
|
2
5
|
from benedict.utils import type_util
|
|
3
6
|
|
|
7
|
+
_K = TypeVar("_K")
|
|
8
|
+
_V = TypeVar("_V")
|
|
9
|
+
|
|
4
10
|
|
|
5
|
-
def _invert_item(d, key, value, flat):
|
|
11
|
+
def _invert_item(d: MutableMapping[Any, Any], key: _K, value: _V, flat: bool) -> None:
|
|
6
12
|
if flat:
|
|
7
13
|
d.setdefault(value, key)
|
|
8
14
|
else:
|
|
9
15
|
d.setdefault(value, []).append(key)
|
|
10
16
|
|
|
11
17
|
|
|
12
|
-
def _invert_list(
|
|
18
|
+
def _invert_list(
|
|
19
|
+
d: MutableMapping[Any, Any], key: _K, value: Sequence[Any], flat: bool
|
|
20
|
+
) -> None:
|
|
13
21
|
for value_item in value:
|
|
14
22
|
_invert_item(d, key, value_item, flat)
|
|
15
23
|
|
|
16
24
|
|
|
17
|
-
def invert(d, flat=False):
|
|
25
|
+
def invert(d: MutableMapping[_K, _V], flat: bool = False) -> MutableMapping[Any, Any]:
|
|
18
26
|
new_dict = clone(d, empty=True)
|
|
19
27
|
for key, value in d.items():
|
|
20
28
|
if type_util.is_list_or_tuple(value):
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
|
|
5
|
+
from useful_types import SupportsRichComparisonT
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _items_sorted_by_item_at_index(
|
|
9
|
+
d: Mapping[SupportsRichComparisonT, SupportsRichComparisonT],
|
|
10
|
+
index: int,
|
|
11
|
+
reverse: bool,
|
|
12
|
+
) -> list[tuple[SupportsRichComparisonT, SupportsRichComparisonT]]:
|
|
13
|
+
return sorted(d.items(), key=lambda item: item[index], reverse=reverse)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def items_sorted_by_keys(
|
|
17
|
+
d: Mapping[SupportsRichComparisonT, SupportsRichComparisonT], reverse: bool = False
|
|
18
|
+
) -> list[tuple[SupportsRichComparisonT, SupportsRichComparisonT]]:
|
|
19
|
+
return _items_sorted_by_item_at_index(d, 0, reverse)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def items_sorted_by_values(
|
|
23
|
+
d: Mapping[SupportsRichComparisonT, SupportsRichComparisonT], reverse: bool = False
|
|
24
|
+
) -> list[tuple[SupportsRichComparisonT, SupportsRichComparisonT]]:
|
|
25
|
+
return _items_sorted_by_item_at_index(d, 1, reverse)
|
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping, Sequence
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
1
6
|
from benedict.utils import type_util
|
|
2
7
|
|
|
3
8
|
|
|
4
|
-
def _get_keylist_for_dict(
|
|
9
|
+
def _get_keylist_for_dict(
|
|
10
|
+
d: Mapping[Any, Any], parent_keys: list[Any], indexes: bool
|
|
11
|
+
) -> list[list[Any]]:
|
|
5
12
|
keylist = []
|
|
6
13
|
for key, value in d.items():
|
|
7
14
|
keys = parent_keys + [key]
|
|
@@ -10,7 +17,9 @@ def _get_keylist_for_dict(d, parent_keys, indexes):
|
|
|
10
17
|
return keylist
|
|
11
18
|
|
|
12
19
|
|
|
13
|
-
def _get_keylist_for_list(
|
|
20
|
+
def _get_keylist_for_list(
|
|
21
|
+
ls: Sequence[Any], parent_keys: list[Any], indexes: bool
|
|
22
|
+
) -> list[list[Any]]:
|
|
14
23
|
keylist = []
|
|
15
24
|
for key, value in enumerate(ls):
|
|
16
25
|
keys = list(parent_keys)
|
|
@@ -20,7 +29,9 @@ def _get_keylist_for_list(ls, parent_keys, indexes):
|
|
|
20
29
|
return keylist
|
|
21
30
|
|
|
22
31
|
|
|
23
|
-
def _get_keylist_for_value(
|
|
32
|
+
def _get_keylist_for_value(
|
|
33
|
+
value: Mapping[Any, Any] | Sequence[Any], parent_keys: list[Any], indexes: bool
|
|
34
|
+
) -> list[list[Any]]:
|
|
24
35
|
if type_util.is_dict(value):
|
|
25
36
|
return _get_keylist_for_dict(value, parent_keys, indexes)
|
|
26
37
|
elif type_util.is_list(value) and indexes:
|
|
@@ -28,5 +39,7 @@ def _get_keylist_for_value(value, parent_keys, indexes):
|
|
|
28
39
|
return []
|
|
29
40
|
|
|
30
41
|
|
|
31
|
-
def keylists(
|
|
42
|
+
def keylists(
|
|
43
|
+
d: Mapping[Any, Any] | Sequence[Any], indexes: bool = False
|
|
44
|
+
) -> list[list[Any]]:
|
|
32
45
|
return _get_keylist_for_value(d, [], indexes)
|
|
@@ -1,8 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
1
6
|
from benedict.core.keylists import keylists
|
|
2
7
|
from benedict.utils import type_util
|
|
3
8
|
|
|
4
9
|
|
|
5
|
-
def keypaths(
|
|
10
|
+
def keypaths(
|
|
11
|
+
d: Mapping[Any, Any],
|
|
12
|
+
separator: str | None = ".",
|
|
13
|
+
indexes: bool = False,
|
|
14
|
+
sort: bool = True,
|
|
15
|
+
) -> list[str]:
|
|
6
16
|
separator = separator or "."
|
|
7
17
|
if not type_util.is_string(separator):
|
|
8
18
|
raise ValueError("separator argument must be a (non-empty) string.")
|
|
@@ -1,10 +1,19 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import re
|
|
4
|
+
from collections.abc import Mapping
|
|
5
|
+
from typing import Any
|
|
2
6
|
|
|
3
7
|
from benedict.core.keypaths import keypaths
|
|
4
8
|
from benedict.utils import type_util
|
|
5
9
|
|
|
6
10
|
|
|
7
|
-
def match(
|
|
11
|
+
def match(
|
|
12
|
+
d: Mapping[Any, Any],
|
|
13
|
+
pattern: str | re.Pattern[str],
|
|
14
|
+
separator: str | None = ".",
|
|
15
|
+
indexes: bool = True,
|
|
16
|
+
) -> list[Any]:
|
|
8
17
|
if type_util.is_regex(pattern):
|
|
9
18
|
regex = pattern
|
|
10
19
|
elif type_util.is_string(pattern):
|