mappingtools 0.0.2__tar.gz → 0.0.4__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.
- {mappingtools-0.0.2 → mappingtools-0.0.4}/PKG-INFO +21 -6
- {mappingtools-0.0.2 → mappingtools-0.0.4}/README.md +19 -2
- {mappingtools-0.0.2 → mappingtools-0.0.4}/pyproject.toml +2 -4
- {mappingtools-0.0.2 → mappingtools-0.0.4}/src/mappingtools.py +95 -3
- {mappingtools-0.0.2 → mappingtools-0.0.4}/tests/test_dictify.py +1 -2
- {mappingtools-0.0.2 → mappingtools-0.0.4}/tests/test_distinct.py +0 -1
- {mappingtools-0.0.2 → mappingtools-0.0.4}/tests/test_inverse.py +0 -1
- mappingtools-0.0.4/tests/test_mapping_collector.py +155 -0
- {mappingtools-0.0.2 → mappingtools-0.0.4}/tests/test_nested_defaultdict.py +0 -1
- {mappingtools-0.0.2 → mappingtools-0.0.4}/tests/test_remove.py +0 -1
- {mappingtools-0.0.2 → mappingtools-0.0.4}/.coveragerc +0 -0
- {mappingtools-0.0.2 → mappingtools-0.0.4}/.github/dependabot.yml +0 -0
- {mappingtools-0.0.2 → mappingtools-0.0.4}/.github/workflows/publish.yml +0 -0
- {mappingtools-0.0.2 → mappingtools-0.0.4}/.github/workflows/test-beta.yml +0 -0
- {mappingtools-0.0.2 → mappingtools-0.0.4}/.github/workflows/test.yml +0 -0
- {mappingtools-0.0.2 → mappingtools-0.0.4}/.gitignore +0 -0
- {mappingtools-0.0.2 → mappingtools-0.0.4}/LICENSE +0 -0
- {mappingtools-0.0.2 → mappingtools-0.0.4}/tests/test_keep.py +0 -0
- {mappingtools-0.0.2 → mappingtools-0.0.4}/tests/test_unwrap.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: mappingtools
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
4
4
|
Summary: MappingTools. Do stuff with Mappings
|
|
5
5
|
Project-URL: Homepage, https://erivlis.github.io/mappingtools
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/erivlis/mappingtools/issues
|
|
@@ -16,15 +16,13 @@ Classifier: Natural Language :: English
|
|
|
16
16
|
Classifier: Operating System :: OS Independent
|
|
17
17
|
Classifier: Programming Language :: Python :: 3
|
|
18
18
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
21
19
|
Classifier: Programming Language :: Python :: 3.10
|
|
22
20
|
Classifier: Programming Language :: Python :: 3.11
|
|
23
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
24
22
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
25
23
|
Classifier: Topic :: Software Development :: Libraries
|
|
26
24
|
Classifier: Typing :: Typed
|
|
27
|
-
Requires-Python: >=3.
|
|
25
|
+
Requires-Python: >=3.10
|
|
28
26
|
Provides-Extra: dev
|
|
29
27
|
Requires-Dist: ruff; extra == 'dev'
|
|
30
28
|
Provides-Extra: docs
|
|
@@ -241,6 +239,23 @@ print(unwrapped_data)
|
|
|
241
239
|
# Output: [{'key': 'key1', 'value': [{'key': 'subkey', 'value': 'value'}]}, {'key': 'key2', 'value': ['item1', 'item2']}]
|
|
242
240
|
```
|
|
243
241
|
|
|
242
|
+
### `MappingCollector`
|
|
243
|
+
|
|
244
|
+
A class designed to collect key-value pairs into an internal mapping,
|
|
245
|
+
with two modes of operation: one_to_one and one_to_many.
|
|
246
|
+
The mode determines whether each key maps to a single value or multiple values.
|
|
247
|
+
|
|
248
|
+
```python
|
|
249
|
+
from mappingtools import MappingCollector, MappingCollectorMode
|
|
250
|
+
|
|
251
|
+
collector = MappingCollector(MappingCollectorMode.one_to_many)
|
|
252
|
+
collector.add('a', 1)
|
|
253
|
+
collector.add('a', 2)
|
|
254
|
+
collector.collect([('b', 3), ('b', 4)])
|
|
255
|
+
print(collector.mapping)
|
|
256
|
+
# Output: {'a': [1, 2], 'b': [3, 4]}
|
|
257
|
+
```
|
|
258
|
+
|
|
244
259
|
## Development
|
|
245
260
|
|
|
246
261
|
### Ruff
|
|
@@ -254,11 +269,11 @@ ruff check src
|
|
|
254
269
|
#### Standard (cobertura) XML Coverage Report
|
|
255
270
|
|
|
256
271
|
```shell
|
|
257
|
-
python -m pytest tests -n auto --cov=src --cov-branch --doctest-modules --cov-report=xml
|
|
272
|
+
python -m pytest tests -n auto --cov=src --cov-branch --doctest-modules --cov-report=xml
|
|
258
273
|
```
|
|
259
274
|
|
|
260
275
|
#### HTML Coverage Report
|
|
261
276
|
|
|
262
277
|
```shell
|
|
263
|
-
python -m pytest tests -n auto --cov=src --cov-branch --doctest-modules --cov-report=html
|
|
278
|
+
python -m pytest tests -n auto --cov=src --cov-branch --doctest-modules --cov-report=html
|
|
264
279
|
```
|
|
@@ -197,6 +197,23 @@ print(unwrapped_data)
|
|
|
197
197
|
# Output: [{'key': 'key1', 'value': [{'key': 'subkey', 'value': 'value'}]}, {'key': 'key2', 'value': ['item1', 'item2']}]
|
|
198
198
|
```
|
|
199
199
|
|
|
200
|
+
### `MappingCollector`
|
|
201
|
+
|
|
202
|
+
A class designed to collect key-value pairs into an internal mapping,
|
|
203
|
+
with two modes of operation: one_to_one and one_to_many.
|
|
204
|
+
The mode determines whether each key maps to a single value or multiple values.
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
from mappingtools import MappingCollector, MappingCollectorMode
|
|
208
|
+
|
|
209
|
+
collector = MappingCollector(MappingCollectorMode.one_to_many)
|
|
210
|
+
collector.add('a', 1)
|
|
211
|
+
collector.add('a', 2)
|
|
212
|
+
collector.collect([('b', 3), ('b', 4)])
|
|
213
|
+
print(collector.mapping)
|
|
214
|
+
# Output: {'a': [1, 2], 'b': [3, 4]}
|
|
215
|
+
```
|
|
216
|
+
|
|
200
217
|
## Development
|
|
201
218
|
|
|
202
219
|
### Ruff
|
|
@@ -210,11 +227,11 @@ ruff check src
|
|
|
210
227
|
#### Standard (cobertura) XML Coverage Report
|
|
211
228
|
|
|
212
229
|
```shell
|
|
213
|
-
python -m pytest tests -n auto --cov=src --cov-branch --doctest-modules --cov-report=xml
|
|
230
|
+
python -m pytest tests -n auto --cov=src --cov-branch --doctest-modules --cov-report=xml
|
|
214
231
|
```
|
|
215
232
|
|
|
216
233
|
#### HTML Coverage Report
|
|
217
234
|
|
|
218
235
|
```shell
|
|
219
|
-
python -m pytest tests -n auto --cov=src --cov-branch --doctest-modules --cov-report=html
|
|
236
|
+
python -m pytest tests -n auto --cov=src --cov-branch --doctest-modules --cov-report=html
|
|
220
237
|
```
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mappingtools"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.4"
|
|
4
4
|
authors = [
|
|
5
5
|
{ name = "Eran Rivlis", email = "eran@rivlis.info" },
|
|
6
6
|
]
|
|
7
7
|
description = "MappingTools. Do stuff with Mappings"
|
|
8
8
|
readme = "README.md"
|
|
9
|
-
requires-python = ">=3.
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
10
|
classifiers = [
|
|
11
11
|
"Development Status :: 4 - Beta",
|
|
12
12
|
"Intended Audience :: Developers",
|
|
13
13
|
"Intended Audience :: Information Technology",
|
|
14
14
|
"Programming Language :: Python :: 3",
|
|
15
|
-
"Programming Language :: Python :: 3.8",
|
|
16
|
-
"Programming Language :: Python :: 3.9",
|
|
17
15
|
"Programming Language :: Python :: 3.10",
|
|
18
16
|
"Programming Language :: Python :: 3.11",
|
|
19
17
|
"Programming Language :: Python :: 3.12",
|
|
@@ -2,13 +2,104 @@ import dataclasses
|
|
|
2
2
|
import inspect
|
|
3
3
|
from collections import defaultdict
|
|
4
4
|
from collections.abc import Callable, Generator, Iterable, Mapping
|
|
5
|
+
from enum import Enum, auto
|
|
5
6
|
from itertools import chain
|
|
6
7
|
from typing import Any, TypeVar
|
|
7
8
|
|
|
8
|
-
K = TypeVar(
|
|
9
|
+
K = TypeVar('K')
|
|
10
|
+
KT = TypeVar('KT')
|
|
11
|
+
VT = TypeVar('VT')
|
|
12
|
+
VT_co = TypeVar('VT_co')
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MappingCollectorMode(Enum):
|
|
16
|
+
"""
|
|
17
|
+
Define an enumeration class for mapping collector modes with two options: one_to_one and one_to_many.
|
|
18
|
+
"""
|
|
19
|
+
one_to_one = auto()
|
|
20
|
+
one_to_many = auto()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class MappingCollector:
|
|
24
|
+
|
|
25
|
+
def __init__(self, mode: MappingCollectorMode = MappingCollectorMode.one_to_one, *args, **kwargs):
|
|
26
|
+
"""
|
|
27
|
+
Initialize the MappingCollector with the specified mode.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
mode (MappingCollectorMode): The mode for collecting mappings.
|
|
31
|
+
*args: Variable positional arguments used to initialize the internal mapping.
|
|
32
|
+
**kwargs: Variable keyword arguments used to initialize the internal mapping.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
self.mode = mode
|
|
36
|
+
|
|
37
|
+
match self.mode:
|
|
38
|
+
case MappingCollectorMode.one_to_one:
|
|
39
|
+
self._mapping = dict(*args, **kwargs)
|
|
40
|
+
case MappingCollectorMode.one_to_many:
|
|
41
|
+
self._mapping = defaultdict(list, *args, **kwargs)
|
|
42
|
+
case _:
|
|
43
|
+
raise ValueError("Invalid mode")
|
|
44
|
+
|
|
45
|
+
def __repr__(self):
|
|
46
|
+
return f'MappingCollector(mode={self.mode}, mapping={self.mapping})'
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def mapping(self) -> Mapping[KT, VT_co]:
|
|
50
|
+
"""
|
|
51
|
+
Return a shallow copy of the internal mapping.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Mapping[KT, VT_co]: A shallow copy of the internal mapping.
|
|
55
|
+
"""
|
|
56
|
+
return dict(self._mapping)
|
|
57
|
+
|
|
58
|
+
def add(self, key: KT, value: VT):
|
|
59
|
+
"""
|
|
60
|
+
Add a key-value pair to the internal mapping based on the specified mode.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
key: The key to be added to the mapping.
|
|
64
|
+
value: The value corresponding to the key.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
None
|
|
68
|
+
"""
|
|
69
|
+
match self.mode:
|
|
70
|
+
case MappingCollectorMode.one_to_one:
|
|
71
|
+
self._mapping[key] = value
|
|
72
|
+
case MappingCollectorMode.one_to_many:
|
|
73
|
+
self._mapping[key].append(value)
|
|
74
|
+
|
|
75
|
+
def collect(self, iterable: Iterable[tuple[KT, VT]]):
|
|
76
|
+
"""
|
|
77
|
+
Collect key-value pairs from the given iterable and add them to the internal mapping
|
|
78
|
+
based on the specified mode.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
iterable (Iterable[tuple[KT, VT]]): An iterable containing key-value pairs to collect.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
None
|
|
85
|
+
"""
|
|
86
|
+
for k, v in iterable:
|
|
87
|
+
self.add(k, v)
|
|
9
88
|
|
|
10
89
|
|
|
11
90
|
def _take(keys: Iterable[K], mapping: Mapping[K, Any], exclude: bool = False) -> dict[K, Any]:
|
|
91
|
+
"""
|
|
92
|
+
Return a dictionary pertaining to the specified keys and their corresponding values from the mapping.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
keys (Iterable[K]): The keys to include in the resulting dictionary.
|
|
96
|
+
mapping (Mapping[K, Any]): The mapping to extract key-value pairs from.
|
|
97
|
+
exclude (bool, optional): If True, exclude the specified keys from the mapping. Defaults to False.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
dict[K, Any]: A dictionary with the selected keys and their values from the mapping.
|
|
101
|
+
"""
|
|
102
|
+
|
|
12
103
|
if not isinstance(mapping, Mapping):
|
|
13
104
|
raise TypeError(f"Parameter 'mapping' should be of type 'Mapping', but instead is type '{type(mapping)}'")
|
|
14
105
|
|
|
@@ -109,7 +200,7 @@ def _process_obj(obj: Any,
|
|
|
109
200
|
return obj
|
|
110
201
|
|
|
111
202
|
|
|
112
|
-
def dictify(obj, key_converter: Callable[[Any], str] | None = None):
|
|
203
|
+
def dictify(obj: Any, key_converter: Callable[[Any], str] | None = None) -> Any:
|
|
113
204
|
"""Dictify an object using a specified key converter.
|
|
114
205
|
|
|
115
206
|
Args:
|
|
@@ -192,4 +283,5 @@ def _unwrap_class(obj):
|
|
|
192
283
|
return [{'key': k, 'value': unwrap(v)} for k, v in inspect.getmembers(obj) if not k.startswith('_')]
|
|
193
284
|
|
|
194
285
|
|
|
195
|
-
__all__ = ('dictify', 'distinct', 'keep', 'inverse', 'nested_defaultdict', 'remove', 'unwrap'
|
|
286
|
+
__all__ = ('dictify', 'distinct', 'keep', 'inverse', 'nested_defaultdict', 'remove', 'unwrap', 'MappingCollector',
|
|
287
|
+
'MappingCollectorMode')
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Generated by CodiumAI
|
|
2
|
+
import pytest
|
|
3
|
+
from mappingtools import MappingCollector, MappingCollectorMode
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestMappingCollector:
|
|
7
|
+
|
|
8
|
+
# Initialize MappingCollector with default mode and verify internal mapping is a dictionary
|
|
9
|
+
def test_initialize_default_mode(self):
|
|
10
|
+
# Arrange
|
|
11
|
+
collector = MappingCollector()
|
|
12
|
+
|
|
13
|
+
# Act
|
|
14
|
+
collector.add(1, 2)
|
|
15
|
+
collector.add(1, 3)
|
|
16
|
+
result = collector.mapping
|
|
17
|
+
item0 = result[1]
|
|
18
|
+
|
|
19
|
+
# Assert
|
|
20
|
+
assert isinstance(result, dict)
|
|
21
|
+
assert isinstance(item0, int)
|
|
22
|
+
assert item0 != 2
|
|
23
|
+
assert item0 == 3
|
|
24
|
+
|
|
25
|
+
# Initialize MappingCollector with one_to_many mode and verify internal mapping is a defaultdict
|
|
26
|
+
def test_initialize_one_to_many_mode(self):
|
|
27
|
+
# Arrange
|
|
28
|
+
collector = MappingCollector(MappingCollectorMode.one_to_many)
|
|
29
|
+
|
|
30
|
+
# Act
|
|
31
|
+
collector.add(1, 2)
|
|
32
|
+
collector.add(1, 3)
|
|
33
|
+
result = collector.mapping
|
|
34
|
+
item0 = result[1]
|
|
35
|
+
|
|
36
|
+
# Assert
|
|
37
|
+
assert isinstance(result, dict)
|
|
38
|
+
assert isinstance(item0, list)
|
|
39
|
+
assert item0 == [2, 3]
|
|
40
|
+
|
|
41
|
+
# Add a single key-value pair in one_to_one mode and verify the mapping
|
|
42
|
+
def test_add_single_key_value_one_to_one(self):
|
|
43
|
+
# Arrange
|
|
44
|
+
collector = MappingCollector()
|
|
45
|
+
|
|
46
|
+
# Act
|
|
47
|
+
collector.add('key1', 'value1')
|
|
48
|
+
result = collector.mapping
|
|
49
|
+
|
|
50
|
+
# Assert
|
|
51
|
+
assert result == {'key1': 'value1'}
|
|
52
|
+
|
|
53
|
+
# Add multiple key-value pairs in one_to_many mode and verify the mapping
|
|
54
|
+
def test_add_multiple_key_values_one_to_many(self):
|
|
55
|
+
# Arrange
|
|
56
|
+
collector = MappingCollector(MappingCollectorMode.one_to_many)
|
|
57
|
+
|
|
58
|
+
# Act
|
|
59
|
+
collector.add('key1', 'value1')
|
|
60
|
+
collector.add('key1', 'value2')
|
|
61
|
+
result = collector.mapping
|
|
62
|
+
|
|
63
|
+
# Assert
|
|
64
|
+
assert result == {'key1': ['value1', 'value2']}
|
|
65
|
+
|
|
66
|
+
# Collect key-value pairs from an iterable in one_to_one mode and verify the mapping
|
|
67
|
+
def test_collect_iterable_one_to_one(self):
|
|
68
|
+
# Arrange
|
|
69
|
+
collector = MappingCollector()
|
|
70
|
+
iterable = [('key1', 'value1'), ('key2', 'value2')]
|
|
71
|
+
|
|
72
|
+
# Act
|
|
73
|
+
collector.collect(iterable)
|
|
74
|
+
result = collector.mapping
|
|
75
|
+
|
|
76
|
+
# Assert
|
|
77
|
+
assert result == {'key1': 'value1', 'key2': 'value2'}
|
|
78
|
+
|
|
79
|
+
# Collect key-value pairs from an iterable in one_to_many mode and verify the mapping
|
|
80
|
+
def test_collect_iterable_one_to_many(self):
|
|
81
|
+
# Arrange
|
|
82
|
+
collector = MappingCollector(MappingCollectorMode.one_to_many)
|
|
83
|
+
iterable = [('key1', 'value1'), ('key1', 'value2')]
|
|
84
|
+
|
|
85
|
+
# Act
|
|
86
|
+
collector.collect(iterable)
|
|
87
|
+
result = collector.mapping
|
|
88
|
+
|
|
89
|
+
# Assert
|
|
90
|
+
assert result == {'key1': ['value1', 'value2']}
|
|
91
|
+
|
|
92
|
+
# Initialize MappingCollector with an invalid mode and verify it raises an error
|
|
93
|
+
def test_initialize_invalid_mode(self):
|
|
94
|
+
# Arrange / Act / Assert
|
|
95
|
+
with pytest.raises(ValueError): # noqa: PT011
|
|
96
|
+
MappingCollector(mode="invalid_mode")
|
|
97
|
+
|
|
98
|
+
# Add a key-value pair with a non-hashable key and verify it raises an error
|
|
99
|
+
def test_add_non_hashable_key(self):
|
|
100
|
+
# Arrange
|
|
101
|
+
collector = MappingCollector()
|
|
102
|
+
|
|
103
|
+
# Act / Assert
|
|
104
|
+
with pytest.raises(TypeError):
|
|
105
|
+
collector.add(['non-hashable'], 'value')
|
|
106
|
+
|
|
107
|
+
# Collect key-value pairs from an empty iterable and verify the internal mapping remains unchanged
|
|
108
|
+
def test_collect_empty_iterable(self):
|
|
109
|
+
# Arrange
|
|
110
|
+
collector = MappingCollector()
|
|
111
|
+
|
|
112
|
+
# Act
|
|
113
|
+
collector.collect([])
|
|
114
|
+
result = collector.mapping
|
|
115
|
+
|
|
116
|
+
# Assert
|
|
117
|
+
assert result == {}
|
|
118
|
+
|
|
119
|
+
# Add a key-value pair with None as the key and verify the mapping
|
|
120
|
+
def test_add_none_key(self):
|
|
121
|
+
# Arrange
|
|
122
|
+
collector = MappingCollector()
|
|
123
|
+
|
|
124
|
+
# Act
|
|
125
|
+
collector.add(None, 'value')
|
|
126
|
+
result = collector.mapping
|
|
127
|
+
|
|
128
|
+
# Assert
|
|
129
|
+
assert result == {None: 'value'}
|
|
130
|
+
|
|
131
|
+
# Collect key-value pairs with duplicate keys in one_to_one mode and verify the last value is retained
|
|
132
|
+
def test_collect_duplicate_keys_one_to_one(self):
|
|
133
|
+
# Arrange
|
|
134
|
+
collector = MappingCollector()
|
|
135
|
+
iterable = [('key1', 'value1'), ('key1', 'value2')]
|
|
136
|
+
|
|
137
|
+
# Act
|
|
138
|
+
collector.collect(iterable)
|
|
139
|
+
result = collector.mapping
|
|
140
|
+
|
|
141
|
+
# Assert
|
|
142
|
+
assert result == {'key1': 'value2'}
|
|
143
|
+
|
|
144
|
+
# Collect key-value pairs with duplicate keys in one_to_many mode and verify all values are appended
|
|
145
|
+
def test_collect_duplicate_keys_one_to_many(self):
|
|
146
|
+
# Arrange
|
|
147
|
+
collector = MappingCollector(MappingCollectorMode.one_to_many)
|
|
148
|
+
iterable = [('key1', 'value1'), ('key1', 'value2')]
|
|
149
|
+
|
|
150
|
+
# Act
|
|
151
|
+
collector.collect(iterable)
|
|
152
|
+
result = collector.mapping
|
|
153
|
+
|
|
154
|
+
# Assert
|
|
155
|
+
assert result == {'key1': ['value1', 'value2']}
|
|
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
|