mappingtools 0.0.1__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.
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: mappingtools
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: mappingtools. Do stuff with Mappings
|
|
5
|
+
Project-URL: Homepage, https://erivlis.github.io/mappingtools
|
|
6
|
+
Project-URL: Bug Tracker, https://github.com/erivlis/mappingtools/issues
|
|
7
|
+
Project-URL: Source, https://github.com/erivlis/mappingtools
|
|
8
|
+
Author-email: Eran Rivlis <eran@rivlis.info>
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: Mapping,manipulate,mutate,transform
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Information Technology
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Natural Language :: English
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
28
|
+
Provides-Extra: docs
|
|
29
|
+
Requires-Dist: mkdocs-gen-files; extra == 'docs'
|
|
30
|
+
Requires-Dist: mkdocs-git-revision-date-localized-plugin; extra == 'docs'
|
|
31
|
+
Requires-Dist: mkdocs-glightbox; extra == 'docs'
|
|
32
|
+
Requires-Dist: mkdocs-literate-nav; extra == 'docs'
|
|
33
|
+
Requires-Dist: mkdocs-material; extra == 'docs'
|
|
34
|
+
Requires-Dist: mkdocs-section-index; extra == 'docs'
|
|
35
|
+
Requires-Dist: mkdocstrings-python; extra == 'docs'
|
|
36
|
+
Provides-Extra: test
|
|
37
|
+
Requires-Dist: pytest; extra == 'test'
|
|
38
|
+
Requires-Dist: pytest-cov; extra == 'test'
|
|
39
|
+
Requires-Dist: pytest-randomly; extra == 'test'
|
|
40
|
+
Requires-Dist: pytest-xdist; extra == 'test'
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
|
|
43
|
+
# MappingTools
|
|
44
|
+
|
|
45
|
+
> This library provides utility functions for manipulating and transforming data structures which have or include
|
|
46
|
+
> Mapping-like characteristics.
|
|
47
|
+
> Including inverting dictionaries, converting class like objects to dictionaries, creating nested defaultdicts,
|
|
48
|
+
> and unwrapping complex objects.
|
|
49
|
+
|
|
50
|
+
<table>
|
|
51
|
+
<tr style="vertical-align: middle;">
|
|
52
|
+
<td>Package</td>
|
|
53
|
+
<td>
|
|
54
|
+
<img alt="PyPI - version" src="https://img.shields.io/pypi/v/mappingtools">
|
|
55
|
+
<img alt="PyPI - Status" src="https://img.shields.io/pypi/status/mappingtools">
|
|
56
|
+
<img alt="PyPI - Python Version" src="https://img.shields.io/pypi/pyversions/mappingtools">
|
|
57
|
+
<img alt="PyPI - Downloads" src="https://img.shields.io/pypi/dd/mappingtools">
|
|
58
|
+
<br>
|
|
59
|
+
<img alt="GitHub" src="https://img.shields.io/github/license/erivlis/mappingtools">
|
|
60
|
+
<img alt="GitHub repo size" src="https://img.shields.io/github/repo-size/erivlis/mappingtools">
|
|
61
|
+
<img alt="GitHub last commit (by committer)" src="https://img.shields.io/github/last-commit/erivlis/mappingtools">
|
|
62
|
+
<a href="https://github.com/erivlis/mappingtools/graphs/contributors"><img alt="Contributors" src="https://img.shields.io/github/contributors/erivlis/mappingtools.svg"></a>
|
|
63
|
+
</td>
|
|
64
|
+
</tr>
|
|
65
|
+
<tr>
|
|
66
|
+
<td>Tools</td>
|
|
67
|
+
<td>
|
|
68
|
+
<a href="https://www.jetbrains.com/pycharm/"><img alt="PyCharm" src="https://img.shields.io/badge/PyCharm-FCF84A.svg?logo=PyCharm&logoColor=black&labelColor=21D789&color=FCF84A"></a>
|
|
69
|
+
<a href="https://github.com/astral-sh/ruff"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff" style="max-width:100%;"></a>
|
|
70
|
+
<a href="https://github.com/astral-sh/uv"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json" alt="uv" style="max-width:100%;"></a>
|
|
71
|
+
<a href="https://squidfunk.github.io/mkdocs-material/"><img src="https://img.shields.io/badge/Material_for_MkDocs-526CFE?&logo=MaterialForMkDocs&logoColor=white&labelColor=grey"></a>
|
|
72
|
+
</td>
|
|
73
|
+
</tr>
|
|
74
|
+
<tr>
|
|
75
|
+
<td>CI/CD</td>
|
|
76
|
+
<td>
|
|
77
|
+
<a href="https://github.com/erivlis/mappingtools/actions/workflows/test.yml"><img alt="Tests" src="https://github.com/erivlis/mappingtools/actions/workflows/test.yml/badge.svg?branch=main"></a>
|
|
78
|
+
<a href="https://github.com/erivlis/mappingtools/actions/workflows/publish.yml"><img alt="Publish" src="https://github.com/erivlis/mappingtools/actions/workflows/publish.yml/badge.svg"></a>
|
|
79
|
+
<a href="https://github.com/erivlis/mappingtools/actions/workflows/publish-docs.yaml"><img alt="Publish Docs" src="https://github.com/erivlis/mappingtools/actions/workflows/publish-docs.yaml/badge.svg"></a>
|
|
80
|
+
</td>
|
|
81
|
+
</tr>
|
|
82
|
+
<tr>
|
|
83
|
+
<td>Scans</td>
|
|
84
|
+
<td>
|
|
85
|
+
<a href="https://codecov.io/gh/erivlis/mappingtools"><img alt="Coverage" src="https://codecov.io/gh/erivlis/mappingtools/graph/badge.svg?token=POODT8M9NV"/></a>
|
|
86
|
+
<br>
|
|
87
|
+
<a href="https://sonarcloud.io/summary/new_code?id=erivlis_mappingtools"><img alt="Quality Gate Status" src="https://sonarcloud.io/api/project_badges/measure?project=erivlis_mappingtools&metric=alert_status"></a>
|
|
88
|
+
<a href="https://sonarcloud.io/summary/new_code?id=erivlis_mappingtools"><img alt="Security Rating" src="https://sonarcloud.io/api/project_badges/measure?project=erivlis_mappingtools&metric=security_rating"></a>
|
|
89
|
+
<a href="https://sonarcloud.io/summary/new_code?id=erivlis_mappingtools"><img alt="Maintainability Rating" src="https://sonarcloud.io/api/project_badges/measure?project=erivlis_mappingtools&metric=sqale_rating"></a>
|
|
90
|
+
<a href="https://sonarcloud.io/summary/new_code?id=erivlis_mappingtools"><img alt="Reliability Rating" src="https://sonarcloud.io/api/project_badges/measure?project=erivlis_mappingtools&metric=reliability_rating"></a>
|
|
91
|
+
<br>
|
|
92
|
+
<a href="https://sonarcloud.io/summary/new_code?id=erivlis_mappingtools"><img alt="Lines of Code" src="https://sonarcloud.io/api/project_badges/measure?project=erivlis_mappingtools&metric=ncloc"></a>
|
|
93
|
+
<a href="https://sonarcloud.io/summary/new_code?id=erivlis_mappingtools"><img alt="Vulnerabilities" src="https://sonarcloud.io/api/project_badges/measure?project=erivlis_mappingtools&metric=vulnerabilities"></a>
|
|
94
|
+
<a href="https://sonarcloud.io/summary/new_code?id=erivlis_mappingtools"><img alt="Bugs" src="https://sonarcloud.io/api/project_badges/measure?project=erivlis_mappingtools&metric=bugs"></a>
|
|
95
|
+
<br>
|
|
96
|
+
<a href="https://app.codacy.com/gh/erivlis/mappingtools/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade"><img alt="Codacy Badge" src="https://app.codacy.com/project/badge/Grade/8b83a99f939b4883ae2f37d7ec3419d1"></a>
|
|
97
|
+
</td>
|
|
98
|
+
</tr>
|
|
99
|
+
</table>
|
|
100
|
+
|
|
101
|
+
## Usage
|
|
102
|
+
|
|
103
|
+
### `dictify`
|
|
104
|
+
|
|
105
|
+
Converts objects to dictionaries using handlers for mappings, iterables, and classes.
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from mappingtools import dictify
|
|
109
|
+
|
|
110
|
+
data = {'key1': 'value1', 'key2': ['item1', 'item2']}
|
|
111
|
+
dictified_data = dictify(data)
|
|
112
|
+
print(dictified_data)
|
|
113
|
+
# Output: {'key1': 'value1', 'key2': ['item1', 'item2']}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### `distinct`
|
|
117
|
+
|
|
118
|
+
Yields distinct values for a specified key across multiple mappings.
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from mappingtools import distinct
|
|
122
|
+
|
|
123
|
+
mappings = [
|
|
124
|
+
{'a': 1, 'b': 2},
|
|
125
|
+
{'a': 2, 'b': 3},
|
|
126
|
+
{'a': 1, 'b': 4}
|
|
127
|
+
]
|
|
128
|
+
distinct_values = list(distinct('a', *mappings))
|
|
129
|
+
print(distinct_values)
|
|
130
|
+
# Output: [1, 2]
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### `keep`
|
|
134
|
+
|
|
135
|
+
Yields subsets of mappings by retaining only the specified keys.
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from mappingtools import keep
|
|
139
|
+
|
|
140
|
+
mappings = [
|
|
141
|
+
{'a': 1, 'b': 2, 'c': 3},
|
|
142
|
+
{'a': 4, 'b': 5, 'd': 6}
|
|
143
|
+
]
|
|
144
|
+
keys_to_keep = ['a', 'b']
|
|
145
|
+
result = list(keep(keys_to_keep, *mappings))
|
|
146
|
+
# result: [{'a': 1, 'b': 2}, {'a': 4, 'b': 5}]
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### `inverse`
|
|
150
|
+
|
|
151
|
+
Swaps keys and values in a dictionary.
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
from mappingtools import inverse
|
|
155
|
+
|
|
156
|
+
original_mapping = {'a': {1, 2}, 'b': {3}}
|
|
157
|
+
inverted_mapping = inverse(original_mapping)
|
|
158
|
+
print(inverted_mapping)
|
|
159
|
+
# Output: {1: 'a', 2: 'a', 3: 'b'}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### `nested_defaultdict`
|
|
163
|
+
|
|
164
|
+
Creates a nested defaultdict with specified depth and factory.
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from mappingtools import nested_defaultdict
|
|
168
|
+
|
|
169
|
+
nested_dd = nested_defaultdict(2, list)
|
|
170
|
+
nested_dd[0][1].append('value')
|
|
171
|
+
print(nested_dd)
|
|
172
|
+
# Output: defaultdict(<function nested_defaultdict.<locals>.factory at ...>, {0: defaultdict(<function nested_defaultdict.<locals>.factory at ...>, {1: ['value']})})
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### `remove`
|
|
176
|
+
|
|
177
|
+
Yields mappings with specified keys removed. It takes an iterable of keys and multiple mappings, and returns a generator
|
|
178
|
+
of mappings with those keys excluded.
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
from mappingtools import remove
|
|
182
|
+
|
|
183
|
+
mappings = [
|
|
184
|
+
{'a': 1, 'b': 2, 'c': 3},
|
|
185
|
+
{'a': 4, 'b': 5, 'd': 6}
|
|
186
|
+
]
|
|
187
|
+
keys_to_remove = ['a', 'b']
|
|
188
|
+
result = list(remove(keys_to_remove, *mappings))
|
|
189
|
+
# result: [{'c': 3}, {'d': 6}]
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### `unwrap`
|
|
194
|
+
|
|
195
|
+
Transforms complex objects into a list of dictionaries with key and value pairs.
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
from mappingtools import unwrap
|
|
199
|
+
|
|
200
|
+
wrapped_data = {'key1': {'subkey': 'value'}, 'key2': ['item1', 'item2']}
|
|
201
|
+
unwrapped_data = unwrap(wrapped_data)
|
|
202
|
+
print(unwrapped_data)
|
|
203
|
+
# Output: [{'key': 'key1', 'value': [{'key': 'subkey', 'value': 'value'}]}, {'key': 'key2', 'value': ['item1', 'item2']}]
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Development
|
|
207
|
+
|
|
208
|
+
### Ruff
|
|
209
|
+
|
|
210
|
+
```shell
|
|
211
|
+
ruff check src
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Test
|
|
215
|
+
|
|
216
|
+
#### Standard (cobertura) XML Coverage Report
|
|
217
|
+
|
|
218
|
+
```shell
|
|
219
|
+
python -m pytest tests -n auto --cov=src --cov-branch --doctest-modules --cov-report=xml --junitxml=test_results.xml
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
#### HTML Coverage Report
|
|
223
|
+
|
|
224
|
+
```shell
|
|
225
|
+
python -m pytest tests -n auto --cov=src --cov-branch --doctest-modules --cov-report=html --junitxml=test_results.xml
|
|
226
|
+
```
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
mappingtools.py,sha256=I7O2lKPXVW3D7spmfPJRH5rw0Bh21Al4HScaeX0TlCE,6973
|
|
2
|
+
mappingtools-0.0.1.dist-info/METADATA,sha256=mNuu4Re_ncNPgnnwt5Nw52R0lGhpce5IPqTxirbHDkM,9418
|
|
3
|
+
mappingtools-0.0.1.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
4
|
+
mappingtools-0.0.1.dist-info/licenses/LICENSE,sha256=fiCxD4qmBY6VdONaK43ANsvpmf9oxSpFLHaFaij0Jx4,1068
|
|
5
|
+
mappingtools-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Eran Rivlis
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
mappingtools.py
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import inspect
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
from collections.abc import Callable, Generator, Iterable, Mapping
|
|
5
|
+
from itertools import chain
|
|
6
|
+
from typing import Any, TypeVar
|
|
7
|
+
|
|
8
|
+
K = TypeVar("K")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _take(keys: Iterable[K], mapping: Mapping[K, Any], exclude: bool = False) -> dict[K, Any]:
|
|
12
|
+
if not isinstance(mapping, Mapping):
|
|
13
|
+
raise TypeError(f"Parameter 'mapping' should be of type 'Mapping', but instead is type '{type(mapping)}'")
|
|
14
|
+
|
|
15
|
+
mapping_keys = set(mapping.keys())
|
|
16
|
+
keys = set(keys) & mapping_keys # intersection with keys to get actual existing keys
|
|
17
|
+
if exclude:
|
|
18
|
+
keys = mapping_keys - keys
|
|
19
|
+
|
|
20
|
+
return {k: mapping.get(k) for k in keys}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def distinct(key: K, *mappings: Mapping[K, Any]) -> Generator[Any, Any, None]:
|
|
24
|
+
"""
|
|
25
|
+
Yield distinct values for the specified key across multiple mappings.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
key (K): The key to extract distinct values from the mappings.
|
|
29
|
+
*mappings (Mapping[K, Any]): Variable number of mappings to search for distinct values.
|
|
30
|
+
|
|
31
|
+
Yields:
|
|
32
|
+
Generator[K, Any, None]: A generator of distinct values extracted from the mappings.
|
|
33
|
+
"""
|
|
34
|
+
distinct_value_type_pairs = set()
|
|
35
|
+
for mapping in mappings:
|
|
36
|
+
value = mapping.get(key, )
|
|
37
|
+
value_type_pair = (value, type(value))
|
|
38
|
+
if key in mapping and value_type_pair not in distinct_value_type_pairs:
|
|
39
|
+
distinct_value_type_pairs.add(value_type_pair)
|
|
40
|
+
yield value
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def keep(keys: Iterable[K], *mappings: Mapping[K, Any]) -> Generator[Mapping[K, Any], Any, None]:
|
|
44
|
+
"""
|
|
45
|
+
Yield a subset of mappings by keeping only the specified keys.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
keys (Iterable[K]): The keys to keep in the mappings.
|
|
49
|
+
*mappings (Mapping[K, Any]): Variable number of mappings to filter.
|
|
50
|
+
|
|
51
|
+
Yields:
|
|
52
|
+
Generator[Mapping[K, Any], Any, None]: A generator of mappings with only the specified keys.
|
|
53
|
+
"""
|
|
54
|
+
yield from (_take(keys, mapping) for mapping in mappings)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def remove(keys: Iterable[K], *mappings: Mapping[K, Any]) -> Generator[Mapping[K, Any], Any, None]:
|
|
58
|
+
"""
|
|
59
|
+
Yield a subset of mappings by removing the specified keys.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
keys (Iterable[K]): The keys to remove from the mappings.
|
|
63
|
+
*mappings (Mapping[K, Any]): Variable number of mappings to filter.
|
|
64
|
+
|
|
65
|
+
Yields:
|
|
66
|
+
Generator[Mapping[K, Any], Any, None]: A generator of mappings with specified keys removed.
|
|
67
|
+
"""
|
|
68
|
+
yield from (_take(keys, mapping, exclude=True) for mapping in mappings)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def inverse(mapping: Mapping[Any, set]) -> Mapping[Any, set]:
|
|
72
|
+
"""Return a new dictionary with keys and values swapped from the input mapping.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
mapping (Mapping[Any, set]): The input mapping to invert.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Mapping: A new Mapping with values as keys and keys as values.
|
|
79
|
+
"""
|
|
80
|
+
items = chain.from_iterable(((vi, k) for vi in v) for k, v in mapping.items())
|
|
81
|
+
dd = defaultdict(set)
|
|
82
|
+
for k, v in items:
|
|
83
|
+
dd[k].add(v)
|
|
84
|
+
|
|
85
|
+
return dd
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _is_strict_iterable(obj: Iterable) -> bool:
|
|
89
|
+
return isinstance(obj, Iterable) and not isinstance(obj, str | bytes | bytearray)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _is_class_instance(obj) -> bool:
|
|
93
|
+
return (dataclasses.is_dataclass(obj) and not isinstance(obj, type)) or hasattr(obj, '__dict__')
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _process_obj(obj: Any,
|
|
97
|
+
mapping_handler: Callable | None = None,
|
|
98
|
+
iterable_handler: Callable | None = None,
|
|
99
|
+
class_handler: Callable | None = None,
|
|
100
|
+
*args,
|
|
101
|
+
**kwargs):
|
|
102
|
+
if callable(mapping_handler) and isinstance(obj, Mapping):
|
|
103
|
+
return mapping_handler(obj, *args, **kwargs)
|
|
104
|
+
elif callable(iterable_handler) and _is_strict_iterable(obj):
|
|
105
|
+
return iterable_handler(obj, *args, **kwargs)
|
|
106
|
+
elif callable(class_handler) and _is_class_instance(obj):
|
|
107
|
+
return class_handler(obj, *args, **kwargs)
|
|
108
|
+
else:
|
|
109
|
+
return obj
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def dictify(obj, key_converter: Callable[[Any], str] | None = None):
|
|
113
|
+
"""Dictify an object using a specified key converter.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
obj (Any): The object to be dictified.
|
|
117
|
+
key_converter (Optional[Callable[[Any], str]], optional): A function to convert keys. Defaults to None.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
The dictified object.
|
|
121
|
+
"""
|
|
122
|
+
return _process_obj(obj, _dictify_mapping, _dictify_iterable, _dictify_class, key_converter=key_converter)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _dictify_mapping(obj, key_converter: Callable[[Any], str] | None = None) -> dict:
|
|
126
|
+
return {(key_converter(k) if key_converter else k): dictify(v, key_converter) for k, v in obj.items()}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _dictify_iterable(obj, key_converter: Callable[[Any], str] | None = None) -> list:
|
|
130
|
+
return [dictify(v, key_converter) for v in obj]
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _dictify_class(obj, key_converter: Callable[[Any], str] | None = None) -> dict | str:
|
|
134
|
+
return {
|
|
135
|
+
(key_converter(k) if key_converter else k): dictify(v, key_converter)
|
|
136
|
+
for k, v in inspect.getmembers(obj)
|
|
137
|
+
if not k.startswith('_')
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def nested_defaultdict(nesting_depth: int = 0, default_factory: Callable | None = None, **kwargs) -> defaultdict:
|
|
142
|
+
"""Return a nested defaultdict with the specified nesting depth and default factory.
|
|
143
|
+
A nested_defaultdict with nesting_depth=0 is equivalent to builtin 'collections.defaultdict'.
|
|
144
|
+
Each nesting_depth increment effectively adds an additional item accessor.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
nesting_depth (int): The depth of nesting for the defaultdict (default is 0);
|
|
148
|
+
default_factory (Callable): The default factory function for the defaultdict (default is None).
|
|
149
|
+
**kwargs: Additional keyword arguments to initialize the most nested defaultdict.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
defaultdict: A nested defaultdict based on the specified parameters.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
if nesting_depth < 0:
|
|
156
|
+
raise ValueError("'nesting_depth' must be zero or more.")
|
|
157
|
+
|
|
158
|
+
if default_factory is not None and not callable(default_factory):
|
|
159
|
+
raise TypeError("default_factory argument must be Callable or None")
|
|
160
|
+
|
|
161
|
+
def factory():
|
|
162
|
+
if nesting_depth > 0:
|
|
163
|
+
print(1)
|
|
164
|
+
return nested_defaultdict(nesting_depth=nesting_depth - 1, default_factory=default_factory, **kwargs)
|
|
165
|
+
else:
|
|
166
|
+
print(2)
|
|
167
|
+
return default_factory() if default_factory else None
|
|
168
|
+
|
|
169
|
+
return defaultdict(factory, **kwargs)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def unwrap(obj: Any):
|
|
173
|
+
"""
|
|
174
|
+
Unwraps the given object.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
obj (Any): The object to unwrap.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Any: The unwrapped object.
|
|
181
|
+
"""
|
|
182
|
+
return _process_obj(obj, _unwrap_mapping, _unwrap_iterable, _unwrap_class)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _unwrap_mapping(obj: Mapping) -> list[dict]:
|
|
186
|
+
return [{'key': k, 'value': unwrap(v)} for k, v in obj.items()]
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _unwrap_iterable(obj: Iterable) -> list:
|
|
190
|
+
return [unwrap(v) for v in obj]
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _unwrap_class(obj):
|
|
194
|
+
return [{'key': k, 'value': unwrap(v)} for k, v in inspect.getmembers(obj) if not k.startswith('_')]
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
__all__ = ('dictify', 'distinct', 'keep', 'inverse', 'nested_defaultdict', 'remove', 'unwrap')
|