multicollections 0.2.1__tar.gz → 0.3.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.
- {multicollections-0.2.1 → multicollections-0.3.0}/.github/workflows/ci.yml +2 -2
- {multicollections-0.2.1 → multicollections-0.3.0}/PKG-INFO +2 -2
- {multicollections-0.2.1 → multicollections-0.3.0}/README.md +1 -1
- {multicollections-0.2.1 → multicollections-0.3.0}/multicollections/__init__.py +19 -18
- {multicollections-0.2.1 → multicollections-0.3.0}/multicollections/_util.py +3 -0
- {multicollections-0.2.1 → multicollections-0.3.0}/multicollections/abc.py +64 -44
- {multicollections-0.2.1 → multicollections-0.3.0}/pyproject.toml +9 -4
- {multicollections-0.2.1 → multicollections-0.3.0}/tests/minimalimpl.py +2 -2
- {multicollections-0.2.1 → multicollections-0.3.0}/tests/test_multicollections.py +118 -130
- {multicollections-0.2.1 → multicollections-0.3.0}/.github/dependabot.yml +0 -0
- {multicollections-0.2.1 → multicollections-0.3.0}/.github/workflows/pypi-publish.yml +0 -0
- {multicollections-0.2.1 → multicollections-0.3.0}/.gitignore +0 -0
- {multicollections-0.2.1 → multicollections-0.3.0}/.readthedocs.yaml +0 -0
- {multicollections-0.2.1 → multicollections-0.3.0}/LICENSE.txt +0 -0
- {multicollections-0.2.1 → multicollections-0.3.0}/docs/api/abc.md +0 -0
- {multicollections-0.2.1 → multicollections-0.3.0}/docs/api/multicollections.md +0 -0
- {multicollections-0.2.1 → multicollections-0.3.0}/docs/index.md +0 -0
- {multicollections-0.2.1 → multicollections-0.3.0}/mkdocs.yml +0 -0
- {multicollections-0.2.1 → multicollections-0.3.0}/multicollections/py.typed +0 -0
- {multicollections-0.2.1 → multicollections-0.3.0}/tests/__init__.py +0 -0
- {multicollections-0.2.1 → multicollections-0.3.0}/tests/ruff.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: multicollections
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Fully generic MultiDict class
|
|
5
5
|
Project-URL: Homepage, https://github.com/gerlero/multicollections
|
|
6
6
|
Project-URL: Repository, https://github.com/gerlero/multicollections
|
|
@@ -36,8 +36,8 @@ A fully generic [`MultiDict`](https://multicollections.readthedocs.io/en/latest/
|
|
|
36
36
|
[](https://multicollections.readthedocs.io/)
|
|
37
37
|
[](https://github.com/gerlero/multicollections/actions/workflows/ci.yml)
|
|
38
38
|
[](https://codecov.io/gh/gerlero/multicollections)
|
|
39
|
+
[](http://mypy-lang.org/)
|
|
39
40
|
[](https://github.com/astral-sh/ruff)
|
|
40
|
-
[](https://github.com/astral-sh/ty)
|
|
41
41
|
[](https://github.com/astral-sh/uv)
|
|
42
42
|
[](https://github.com/gerlero/multicollections/actions/workflows/pypi-publish.yml)
|
|
43
43
|
[](https://pypi.org/project/multicollections/)
|
|
@@ -5,8 +5,8 @@ A fully generic [`MultiDict`](https://multicollections.readthedocs.io/en/latest/
|
|
|
5
5
|
[](https://multicollections.readthedocs.io/)
|
|
6
6
|
[](https://github.com/gerlero/multicollections/actions/workflows/ci.yml)
|
|
7
7
|
[](https://codecov.io/gh/gerlero/multicollections)
|
|
8
|
+
[](http://mypy-lang.org/)
|
|
8
9
|
[](https://github.com/astral-sh/ruff)
|
|
9
|
-
[](https://github.com/astral-sh/ty)
|
|
10
10
|
[](https://github.com/astral-sh/uv)
|
|
11
11
|
[](https://github.com/gerlero/multicollections/actions/workflows/pypi-publish.yml)
|
|
12
12
|
[](https://pypi.org/project/multicollections/)
|
|
@@ -45,10 +45,10 @@ class MultiDict(MutableMultiMapping[_K, _V]):
|
|
|
45
45
|
if isinstance(iterable, Mapping):
|
|
46
46
|
self._items.extend((key, value) for key, value in iterable.items())
|
|
47
47
|
else:
|
|
48
|
-
self._items.extend((key, value) for key, value in iterable)
|
|
48
|
+
self._items.extend((key, value) for key, value in iterable) # type: ignore[misc]
|
|
49
49
|
|
|
50
50
|
# Add kwargs items
|
|
51
|
-
self._items.extend((key, value) for key, value in kwargs.items())
|
|
51
|
+
self._items.extend((key, value) for key, value in kwargs.items()) # type: ignore[misc]
|
|
52
52
|
|
|
53
53
|
# Build indices in one pass for better performance
|
|
54
54
|
if self._items:
|
|
@@ -84,7 +84,7 @@ class MultiDict(MutableMultiMapping[_K, _V]):
|
|
|
84
84
|
if len(indices) > 1:
|
|
85
85
|
# Remove duplicates efficiently by marking items as None and filtering
|
|
86
86
|
for idx in indices[1:]:
|
|
87
|
-
self._items[idx] = None
|
|
87
|
+
self._items[idx] = None # type: ignore[call-overload]
|
|
88
88
|
|
|
89
89
|
# Filter out None items and rebuild indices
|
|
90
90
|
self._items = [item for item in self._items if item is not None]
|
|
@@ -122,7 +122,7 @@ class MultiDict(MutableMultiMapping[_K, _V]):
|
|
|
122
122
|
value = self._items[first_index][1]
|
|
123
123
|
|
|
124
124
|
# Mark the first item for removal
|
|
125
|
-
self._items[first_index] = None
|
|
125
|
+
self._items[first_index] = None # type: ignore[call-overload]
|
|
126
126
|
|
|
127
127
|
# Filter out None items and rebuild indices
|
|
128
128
|
self._items = [item for item in self._items if item is not None]
|
|
@@ -142,7 +142,7 @@ class MultiDict(MutableMultiMapping[_K, _V]):
|
|
|
142
142
|
# Mark items for removal
|
|
143
143
|
indices_to_remove = self._key_indices[key]
|
|
144
144
|
for idx in indices_to_remove:
|
|
145
|
-
self._items[idx] = None
|
|
145
|
+
self._items[idx] = None # type: ignore[call-overload]
|
|
146
146
|
|
|
147
147
|
# Filter out None items and rebuild indices
|
|
148
148
|
self._items = [item for item in self._items if item is not None]
|
|
@@ -173,7 +173,7 @@ class MultiDict(MutableMultiMapping[_K, _V]):
|
|
|
173
173
|
existing_keys: set[_K],
|
|
174
174
|
) -> tuple[dict[_K, list[_V]], list[tuple[_K, _V]]]:
|
|
175
175
|
"""Separate items into updates and additions."""
|
|
176
|
-
updates_by_key = {} # key -> list of values to replace with
|
|
176
|
+
updates_by_key: dict[_K, list[_V]] = {} # key -> list of values to replace with
|
|
177
177
|
additions = [] # list of (key, value) for new keys
|
|
178
178
|
|
|
179
179
|
for key, value in all_items:
|
|
@@ -195,7 +195,7 @@ class MultiDict(MutableMultiMapping[_K, _V]):
|
|
|
195
195
|
|
|
196
196
|
# Mark items for removal
|
|
197
197
|
for idx in items_to_remove:
|
|
198
|
-
self._items[idx] = None
|
|
198
|
+
self._items[idx] = None # type: ignore[call-overload]
|
|
199
199
|
|
|
200
200
|
# Filter out None items
|
|
201
201
|
self._items = [item for item in self._items if item is not None]
|
|
@@ -206,7 +206,7 @@ class MultiDict(MutableMultiMapping[_K, _V]):
|
|
|
206
206
|
self._items.append((key, value))
|
|
207
207
|
|
|
208
208
|
@override
|
|
209
|
-
def update(
|
|
209
|
+
def update( # type: ignore[override]
|
|
210
210
|
self,
|
|
211
211
|
other: Mapping[_K, _V] | Iterable[Sequence[_K | _V]] = (),
|
|
212
212
|
**kwargs: _V,
|
|
@@ -218,7 +218,7 @@ class MultiDict(MutableMultiMapping[_K, _V]):
|
|
|
218
218
|
"""
|
|
219
219
|
# Collect all items first
|
|
220
220
|
items = other.items() if isinstance(other, Mapping) else other
|
|
221
|
-
items = itertools.chain(items, kwargs.items())
|
|
221
|
+
items = itertools.chain(items, kwargs.items()) # type: ignore[arg-type]
|
|
222
222
|
all_items = list(items)
|
|
223
223
|
|
|
224
224
|
if not all_items:
|
|
@@ -228,7 +228,7 @@ class MultiDict(MutableMultiMapping[_K, _V]):
|
|
|
228
228
|
existing_keys = set(self._key_indices.keys())
|
|
229
229
|
|
|
230
230
|
# Separate items into updates and additions
|
|
231
|
-
updates_by_key, additions = self._collect_update_items(all_items, existing_keys)
|
|
231
|
+
updates_by_key, additions = self._collect_update_items(all_items, existing_keys) # type: ignore[arg-type]
|
|
232
232
|
|
|
233
233
|
# Process updates efficiently
|
|
234
234
|
if updates_by_key:
|
|
@@ -258,7 +258,7 @@ class MultiDict(MutableMultiMapping[_K, _V]):
|
|
|
258
258
|
|
|
259
259
|
# Collect all items and filter out existing keys
|
|
260
260
|
items = other.items() if isinstance(other, Mapping) else other
|
|
261
|
-
items = itertools.chain(items, kwargs.items())
|
|
261
|
+
items = itertools.chain(items, kwargs.items()) # type: ignore[arg-type]
|
|
262
262
|
new_items = [(key, value) for key, value in items if key not in existing_keys]
|
|
263
263
|
|
|
264
264
|
if not new_items:
|
|
@@ -266,13 +266,13 @@ class MultiDict(MutableMultiMapping[_K, _V]):
|
|
|
266
266
|
|
|
267
267
|
# Add all items to the list at once
|
|
268
268
|
start_index = len(self._items)
|
|
269
|
-
self._items.extend(new_items)
|
|
269
|
+
self._items.extend(new_items) # type: ignore[arg-type]
|
|
270
270
|
|
|
271
271
|
# Update indices incrementally for better performance
|
|
272
272
|
for i, (key, _) in enumerate(new_items, start_index):
|
|
273
273
|
if key not in self._key_indices:
|
|
274
|
-
self._key_indices[key] = []
|
|
275
|
-
self._key_indices[key].append(i)
|
|
274
|
+
self._key_indices[key] = [] # type: ignore[index]
|
|
275
|
+
self._key_indices[key].append(i) # type: ignore[index]
|
|
276
276
|
|
|
277
277
|
@override
|
|
278
278
|
def extend(
|
|
@@ -287,7 +287,7 @@ class MultiDict(MutableMultiMapping[_K, _V]):
|
|
|
287
287
|
"""
|
|
288
288
|
# Collect all new items first
|
|
289
289
|
items = other.items() if isinstance(other, Mapping) else other
|
|
290
|
-
items = itertools.chain(items, kwargs.items())
|
|
290
|
+
items = itertools.chain(items, kwargs.items()) # type: ignore[arg-type]
|
|
291
291
|
new_items = list(items)
|
|
292
292
|
|
|
293
293
|
if not new_items:
|
|
@@ -295,13 +295,13 @@ class MultiDict(MutableMultiMapping[_K, _V]):
|
|
|
295
295
|
|
|
296
296
|
# Add all items to the list at once
|
|
297
297
|
start_index = len(self._items)
|
|
298
|
-
self._items.extend(new_items)
|
|
298
|
+
self._items.extend(new_items) # type: ignore[arg-type]
|
|
299
299
|
|
|
300
300
|
# Update indices incrementally for better performance
|
|
301
301
|
for i, (key, _) in enumerate(new_items, start_index):
|
|
302
302
|
if key not in self._key_indices:
|
|
303
|
-
self._key_indices[key] = []
|
|
304
|
-
self._key_indices[key].append(i)
|
|
303
|
+
self._key_indices[key] = [] # type: ignore[index]
|
|
304
|
+
self._key_indices[key].append(i) # type: ignore[index]
|
|
305
305
|
|
|
306
306
|
def copy(self) -> MultiDict[_K, _V]:
|
|
307
307
|
"""Return a shallow copy of the MultiDict."""
|
|
@@ -310,6 +310,7 @@ class MultiDict(MutableMultiMapping[_K, _V]):
|
|
|
310
310
|
new_md._key_indices = {k: v.copy() for k, v in self._key_indices.items()} # noqa: SLF001
|
|
311
311
|
return new_md
|
|
312
312
|
|
|
313
|
+
@override
|
|
313
314
|
def __repr__(self) -> str:
|
|
314
315
|
"""Return a string representation of the MultiDict."""
|
|
315
316
|
return f"{self.__class__.__name__}({list(self._items)!r})"
|
|
@@ -8,7 +8,7 @@ import itertools
|
|
|
8
8
|
import sys
|
|
9
9
|
from abc import abstractmethod
|
|
10
10
|
from collections import defaultdict
|
|
11
|
-
from typing import TYPE_CHECKING, Generic, TypeVar, overload
|
|
11
|
+
from typing import TYPE_CHECKING, Generic, Tuple, TypeVar, overload
|
|
12
12
|
|
|
13
13
|
if sys.version_info >= (3, 9):
|
|
14
14
|
from collections.abc import (
|
|
@@ -17,9 +17,9 @@ if sys.version_info >= (3, 9):
|
|
|
17
17
|
Iterable,
|
|
18
18
|
Iterator,
|
|
19
19
|
Mapping,
|
|
20
|
-
MappingView,
|
|
21
20
|
MutableMapping,
|
|
22
21
|
Sequence,
|
|
22
|
+
Sized,
|
|
23
23
|
)
|
|
24
24
|
else:
|
|
25
25
|
from typing import (
|
|
@@ -28,14 +28,15 @@ else:
|
|
|
28
28
|
Iterable,
|
|
29
29
|
Iterator,
|
|
30
30
|
Mapping,
|
|
31
|
-
MappingView,
|
|
32
31
|
MutableMapping,
|
|
33
32
|
Sequence,
|
|
33
|
+
Sized,
|
|
34
34
|
)
|
|
35
35
|
|
|
36
|
-
if TYPE_CHECKING:
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
37
|
from typing import Protocol
|
|
38
38
|
|
|
39
|
+
|
|
39
40
|
from ._util import override
|
|
40
41
|
|
|
41
42
|
_K = TypeVar("_K")
|
|
@@ -44,21 +45,25 @@ _D = TypeVar("_D")
|
|
|
44
45
|
_Self = TypeVar("_Self")
|
|
45
46
|
|
|
46
47
|
|
|
47
|
-
class MultiMappingView(
|
|
48
|
+
class MultiMappingView(Generic[_K, _V], Sized):
|
|
48
49
|
"""Base class for MultiMapping views."""
|
|
49
50
|
|
|
50
|
-
@override
|
|
51
51
|
def __init__(self, mapping: MultiMapping[_K, _V]) -> None:
|
|
52
52
|
"""Initialize the view with the given mapping."""
|
|
53
|
-
|
|
53
|
+
self._mapping = mapping
|
|
54
|
+
|
|
55
|
+
@override
|
|
56
|
+
def __len__(self) -> int:
|
|
57
|
+
"""Return the number of elements in the multi-mapping."""
|
|
58
|
+
return len(self._mapping)
|
|
54
59
|
|
|
55
60
|
|
|
56
|
-
class KeysView(MultiMappingView):
|
|
61
|
+
class KeysView(MultiMappingView[_K, _V], Collection[_K]):
|
|
57
62
|
"""View for the keys in a MultiMapping."""
|
|
58
63
|
|
|
59
64
|
@override
|
|
60
65
|
def __contains__(self, key: object) -> bool:
|
|
61
|
-
"""Check if the key is in the mapping."""
|
|
66
|
+
"""Check if the key is in the multi-mapping."""
|
|
62
67
|
return key in self._mapping
|
|
63
68
|
|
|
64
69
|
@override
|
|
@@ -67,18 +72,18 @@ class KeysView(MultiMappingView):
|
|
|
67
72
|
return iter(self._mapping)
|
|
68
73
|
|
|
69
74
|
|
|
70
|
-
class ItemsView(MultiMappingView):
|
|
75
|
+
class ItemsView(MultiMappingView[_K, _V], Collection[Tuple[_K, _V]]):
|
|
71
76
|
"""View for the items (key-value pairs) in a MultiMapping."""
|
|
72
77
|
|
|
73
78
|
@override
|
|
74
79
|
def __contains__(self, item: object) -> bool:
|
|
75
|
-
"""Check if the item is in the mapping."""
|
|
80
|
+
"""Check if the item is in the multi-mapping."""
|
|
76
81
|
try:
|
|
77
|
-
key, value = item #
|
|
82
|
+
key, value = item # type: ignore[misc]
|
|
78
83
|
except TypeError:
|
|
79
84
|
return False
|
|
80
85
|
try:
|
|
81
|
-
return value in self._mapping.getall(key)
|
|
86
|
+
return value in self._mapping.getall(key) # type: ignore[has-type]
|
|
82
87
|
except KeyError:
|
|
83
88
|
return False
|
|
84
89
|
|
|
@@ -87,17 +92,22 @@ class ItemsView(MultiMappingView):
|
|
|
87
92
|
"""Return an iterator over the items (key-value pairs)."""
|
|
88
93
|
counts: defaultdict[_K, int] = defaultdict(int)
|
|
89
94
|
for k in self._mapping:
|
|
90
|
-
yield (
|
|
95
|
+
yield (
|
|
96
|
+
k,
|
|
97
|
+
next(
|
|
98
|
+
itertools.islice(self._mapping.getall(k), counts[k], counts[k] + 1)
|
|
99
|
+
),
|
|
100
|
+
)
|
|
91
101
|
counts[k] += 1
|
|
92
102
|
|
|
93
103
|
|
|
94
|
-
class ValuesView(MultiMappingView):
|
|
104
|
+
class ValuesView(MultiMappingView[_K, _V], Collection[_V]):
|
|
95
105
|
"""View for the values in a MultiMapping."""
|
|
96
106
|
|
|
97
107
|
@override
|
|
98
108
|
def __contains__(self, value: object) -> bool:
|
|
99
109
|
"""Check if the value is in the mapping."""
|
|
100
|
-
return value in
|
|
110
|
+
return any(v == value for v in self)
|
|
101
111
|
|
|
102
112
|
@override
|
|
103
113
|
def __iter__(self) -> Iterator[_V]:
|
|
@@ -112,27 +122,30 @@ class _NoDefault:
|
|
|
112
122
|
_NO_DEFAULT = _NoDefault()
|
|
113
123
|
|
|
114
124
|
if TYPE_CHECKING: # pragma: no cover
|
|
125
|
+
_Self_co = TypeVar("_Self_co", covariant=True)
|
|
126
|
+
_K_contra = TypeVar("_K_contra", contravariant=True)
|
|
127
|
+
_V_co = TypeVar("_V_co", covariant=True)
|
|
115
128
|
|
|
116
|
-
class _CallableWithDefault(Protocol[
|
|
129
|
+
class _CallableWithDefault(Protocol[_Self_co, _K_contra, _V_co]):
|
|
117
130
|
@overload
|
|
118
|
-
def __call__(self:
|
|
131
|
+
def __call__(self: _Self_co, key: _K_contra) -> _V_co: ...
|
|
119
132
|
|
|
120
133
|
@overload
|
|
121
|
-
def __call__(self:
|
|
134
|
+
def __call__(self: _Self_co, key: _K_contra, default: _D) -> _V_co | _D: ...
|
|
122
135
|
|
|
123
136
|
|
|
124
137
|
def with_default(
|
|
125
138
|
meth: Callable[[_Self, _K], _V],
|
|
126
|
-
) -> _CallableWithDefault[_Self, _K, _V
|
|
139
|
+
) -> _CallableWithDefault[_Self, _K, _V]:
|
|
127
140
|
"""Add a default value argument to a method that can raise a `KeyError`."""
|
|
128
141
|
|
|
129
142
|
@overload
|
|
130
|
-
def wrapper(self: _Self, key: _K) -> _V: ...
|
|
143
|
+
def wrapper(self: _Self, key: _K) -> _V: ...
|
|
131
144
|
|
|
132
145
|
@overload
|
|
133
|
-
def wrapper(self: _Self, key: _K, default: _D) -> _V | _D: ...
|
|
146
|
+
def wrapper(self: _Self, key: _K, default: _D) -> _V | _D: ...
|
|
134
147
|
|
|
135
|
-
@functools.wraps(meth)
|
|
148
|
+
@functools.wraps(meth) # type: ignore[misc]
|
|
136
149
|
def wrapper(
|
|
137
150
|
self: _Self, key: _K, default: _D | _NoDefault = _NO_DEFAULT
|
|
138
151
|
) -> _V | _D:
|
|
@@ -141,12 +154,12 @@ def with_default(
|
|
|
141
154
|
except KeyError:
|
|
142
155
|
if default is _NO_DEFAULT:
|
|
143
156
|
raise
|
|
144
|
-
return default #
|
|
157
|
+
return default # type: ignore[return-value]
|
|
145
158
|
|
|
146
|
-
return wrapper
|
|
159
|
+
return wrapper # type: ignore[return-value]
|
|
147
160
|
|
|
148
161
|
|
|
149
|
-
class MultiMapping(Mapping[_K, _V]
|
|
162
|
+
class MultiMapping(Mapping[_K, _V]):
|
|
150
163
|
"""Abstract base class for multi-mapping collections.
|
|
151
164
|
|
|
152
165
|
A multi-mapping is a mapping that can hold multiple values for the same key.
|
|
@@ -155,7 +168,7 @@ class MultiMapping(Mapping[_K, _V], Generic[_K, _V]):
|
|
|
155
168
|
|
|
156
169
|
@abstractmethod
|
|
157
170
|
@with_default
|
|
158
|
-
def getall(self, key: _K) ->
|
|
171
|
+
def getall(self, key: _K) -> Collection[_V]:
|
|
159
172
|
"""Get all values for a key.
|
|
160
173
|
|
|
161
174
|
Raises a `KeyError` if the key is not found and no default is provided.
|
|
@@ -163,6 +176,7 @@ class MultiMapping(Mapping[_K, _V], Generic[_K, _V]):
|
|
|
163
176
|
raise NotImplementedError # pragma: no cover
|
|
164
177
|
|
|
165
178
|
@abstractmethod
|
|
179
|
+
@override
|
|
166
180
|
def __iter__(self) -> Iterator[_K]:
|
|
167
181
|
"""Return an iterator over the keys.
|
|
168
182
|
|
|
@@ -171,6 +185,7 @@ class MultiMapping(Mapping[_K, _V], Generic[_K, _V]):
|
|
|
171
185
|
raise NotImplementedError # pragma: no cover
|
|
172
186
|
|
|
173
187
|
@abstractmethod
|
|
188
|
+
@override
|
|
174
189
|
def __len__(self) -> int:
|
|
175
190
|
"""Return the total number of items (key-value pairs)."""
|
|
176
191
|
raise NotImplementedError # pragma: no cover
|
|
@@ -181,7 +196,11 @@ class MultiMapping(Mapping[_K, _V], Generic[_K, _V]):
|
|
|
181
196
|
|
|
182
197
|
Raises a `KeyError` if the key is not found and no default is provided.
|
|
183
198
|
"""
|
|
184
|
-
|
|
199
|
+
try:
|
|
200
|
+
return next(iter(self.getall(key)))
|
|
201
|
+
except StopIteration as e: # pragma: no cover
|
|
202
|
+
msg = "MultiMapping.getall returned an empty collection"
|
|
203
|
+
raise RuntimeError(msg) from e
|
|
185
204
|
|
|
186
205
|
@override
|
|
187
206
|
def __getitem__(self, key: _K) -> _V:
|
|
@@ -192,17 +211,17 @@ class MultiMapping(Mapping[_K, _V], Generic[_K, _V]):
|
|
|
192
211
|
return self.getone(key)
|
|
193
212
|
|
|
194
213
|
@override
|
|
195
|
-
def keys(self) -> KeysView[_K]:
|
|
214
|
+
def keys(self) -> KeysView[_K, _V]: # type: ignore[override]
|
|
196
215
|
"""Return a view of the keys in the MultiMapping."""
|
|
197
216
|
return KeysView(self)
|
|
198
217
|
|
|
199
218
|
@override
|
|
200
|
-
def items(self) -> ItemsView[_K, _V]:
|
|
219
|
+
def items(self) -> ItemsView[_K, _V]: # type: ignore[override]
|
|
201
220
|
"""Return a view of the items (key-value pairs) in the MultiMapping."""
|
|
202
221
|
return ItemsView(self)
|
|
203
222
|
|
|
204
223
|
@override
|
|
205
|
-
def values(self) -> ValuesView[_V]:
|
|
224
|
+
def values(self) -> ValuesView[_K, _V]: # type: ignore[override]
|
|
206
225
|
"""Return a view of the values in the MultiMapping."""
|
|
207
226
|
return ValuesView(self)
|
|
208
227
|
|
|
@@ -214,6 +233,7 @@ class MutableMultiMapping(MultiMapping[_K, _V], MutableMapping[_K, _V]):
|
|
|
214
233
|
"""
|
|
215
234
|
|
|
216
235
|
@abstractmethod
|
|
236
|
+
@override
|
|
217
237
|
def __setitem__(self, key: _K, value: _V) -> None:
|
|
218
238
|
"""Set the value for a key.
|
|
219
239
|
|
|
@@ -239,8 +259,8 @@ class MutableMultiMapping(MultiMapping[_K, _V], MutableMapping[_K, _V]):
|
|
|
239
259
|
raise NotImplementedError # pragma: no cover
|
|
240
260
|
|
|
241
261
|
@with_default
|
|
242
|
-
def popall(self, key: _K) ->
|
|
243
|
-
"""Remove and return all values for a key
|
|
262
|
+
def popall(self, key: _K) -> Collection[_V]:
|
|
263
|
+
"""Remove and return all values for a key.
|
|
244
264
|
|
|
245
265
|
Raises a `KeyError` if the key is not found and no default is provided.
|
|
246
266
|
"""
|
|
@@ -251,10 +271,12 @@ class MutableMultiMapping(MultiMapping[_K, _V], MutableMapping[_K, _V]):
|
|
|
251
271
|
return ret
|
|
252
272
|
|
|
253
273
|
@with_default
|
|
274
|
+
@override
|
|
254
275
|
def pop(self, key: _K) -> _V:
|
|
255
276
|
"""Same as `popone`."""
|
|
256
277
|
return self.popone(key)
|
|
257
278
|
|
|
279
|
+
@override
|
|
258
280
|
def popitem(self) -> tuple[_K, _V]:
|
|
259
281
|
"""Remove and return a (key, value) pair."""
|
|
260
282
|
key = next(iter(self))
|
|
@@ -275,7 +297,6 @@ class MutableMultiMapping(MultiMapping[_K, _V], MutableMapping[_K, _V]):
|
|
|
275
297
|
for key in set(self.keys()):
|
|
276
298
|
self.popall(key)
|
|
277
299
|
|
|
278
|
-
@override
|
|
279
300
|
def extend(
|
|
280
301
|
self,
|
|
281
302
|
other: Mapping[_K, _V] | Iterable[Sequence[_K | _V]] = (),
|
|
@@ -283,11 +304,10 @@ class MutableMultiMapping(MultiMapping[_K, _V], MutableMapping[_K, _V]):
|
|
|
283
304
|
) -> None:
|
|
284
305
|
"""Extend the multi-mapping with items from another object."""
|
|
285
306
|
items = other.items() if isinstance(other, Mapping) else other
|
|
286
|
-
items = itertools.chain(items, kwargs.items())
|
|
307
|
+
items = itertools.chain(items, kwargs.items()) # type: ignore[arg-type]
|
|
287
308
|
for key, value in items:
|
|
288
|
-
self.add(key, value)
|
|
309
|
+
self.add(key, value) # type: ignore[arg-type]
|
|
289
310
|
|
|
290
|
-
@override
|
|
291
311
|
def merge(
|
|
292
312
|
self,
|
|
293
313
|
other: Mapping[_K, _V] | Iterable[Sequence[_K | _V]] = (),
|
|
@@ -299,13 +319,13 @@ class MutableMultiMapping(MultiMapping[_K, _V], MutableMapping[_K, _V]):
|
|
|
299
319
|
"""
|
|
300
320
|
existing_keys = set(self.keys())
|
|
301
321
|
items = other.items() if isinstance(other, Mapping) else other
|
|
302
|
-
items = itertools.chain(items, kwargs.items())
|
|
322
|
+
items = itertools.chain(items, kwargs.items()) # type: ignore[arg-type]
|
|
303
323
|
for key, value in items:
|
|
304
324
|
if key not in existing_keys:
|
|
305
|
-
self.add(key, value)
|
|
325
|
+
self.add(key, value) # type: ignore[arg-type]
|
|
306
326
|
|
|
307
327
|
@override
|
|
308
|
-
def update(
|
|
328
|
+
def update( # type: ignore[override]
|
|
309
329
|
self,
|
|
310
330
|
other: Mapping[_K, _V] | Iterable[Sequence[_K | _V]] = (),
|
|
311
331
|
**kwargs: _V,
|
|
@@ -316,10 +336,10 @@ class MutableMultiMapping(MultiMapping[_K, _V], MutableMapping[_K, _V]):
|
|
|
316
336
|
"""
|
|
317
337
|
existing_keys = set(self.keys())
|
|
318
338
|
items = other.items() if isinstance(other, Mapping) else other
|
|
319
|
-
items = itertools.chain(items, kwargs.items())
|
|
339
|
+
items = itertools.chain(items, kwargs.items()) # type: ignore[arg-type]
|
|
320
340
|
for key, value in items:
|
|
321
341
|
if key in existing_keys:
|
|
322
|
-
self[key] = value
|
|
323
|
-
existing_keys.remove(key)
|
|
342
|
+
self[key] = value # type: ignore[index, assignment]
|
|
343
|
+
existing_keys.remove(key) # type: ignore[arg-type]
|
|
324
344
|
else:
|
|
325
|
-
self.add(key, value)
|
|
345
|
+
self.add(key, value) # type: ignore[arg-type]
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "multicollections"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name = "Gabriel S. Gerlero", email = "ggerlero@cimec.unl.edu.ar" },
|
|
10
10
|
]
|
|
@@ -32,7 +32,7 @@ requires-python = ">=3.7"
|
|
|
32
32
|
[dependency-groups]
|
|
33
33
|
lint = ["ruff"]
|
|
34
34
|
typing = [
|
|
35
|
-
"
|
|
35
|
+
"mypy>=1.4.1,<2",
|
|
36
36
|
"typing_extensions >= 4.4.0; python_version<'3.12'"
|
|
37
37
|
]
|
|
38
38
|
test = [
|
|
@@ -61,8 +61,13 @@ Homepage = "https://github.com/gerlero/multicollections"
|
|
|
61
61
|
Repository = "https://github.com/gerlero/multicollections"
|
|
62
62
|
Documentation = "https://multicollections.readthedocs.io"
|
|
63
63
|
|
|
64
|
-
[tool.
|
|
65
|
-
|
|
64
|
+
[tool.mypy]
|
|
65
|
+
packages = [
|
|
66
|
+
"multicollections",
|
|
67
|
+
"tests",
|
|
68
|
+
]
|
|
69
|
+
strict = true
|
|
70
|
+
enable_error_code = "explicit-override"
|
|
66
71
|
|
|
67
72
|
[tool.ruff.lint]
|
|
68
73
|
extend-select = ["ALL"]
|
|
@@ -35,9 +35,9 @@ class ListMultiDict(MutableMultiMapping[_K, _V]):
|
|
|
35
35
|
self._items.append((key, value))
|
|
36
36
|
else:
|
|
37
37
|
for key, value in iterable:
|
|
38
|
-
self._items.append((key, value))
|
|
38
|
+
self._items.append((key, value)) # type: ignore[arg-type]
|
|
39
39
|
for key, value in kwargs.items():
|
|
40
|
-
self._items.append((key, value))
|
|
40
|
+
self._items.append((key, value)) # type: ignore[arg-type]
|
|
41
41
|
|
|
42
42
|
@override
|
|
43
43
|
@with_default
|