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.
Files changed (21) hide show
  1. {multicollections-0.2.1 → multicollections-0.3.0}/.github/workflows/ci.yml +2 -2
  2. {multicollections-0.2.1 → multicollections-0.3.0}/PKG-INFO +2 -2
  3. {multicollections-0.2.1 → multicollections-0.3.0}/README.md +1 -1
  4. {multicollections-0.2.1 → multicollections-0.3.0}/multicollections/__init__.py +19 -18
  5. {multicollections-0.2.1 → multicollections-0.3.0}/multicollections/_util.py +3 -0
  6. {multicollections-0.2.1 → multicollections-0.3.0}/multicollections/abc.py +64 -44
  7. {multicollections-0.2.1 → multicollections-0.3.0}/pyproject.toml +9 -4
  8. {multicollections-0.2.1 → multicollections-0.3.0}/tests/minimalimpl.py +2 -2
  9. {multicollections-0.2.1 → multicollections-0.3.0}/tests/test_multicollections.py +118 -130
  10. {multicollections-0.2.1 → multicollections-0.3.0}/.github/dependabot.yml +0 -0
  11. {multicollections-0.2.1 → multicollections-0.3.0}/.github/workflows/pypi-publish.yml +0 -0
  12. {multicollections-0.2.1 → multicollections-0.3.0}/.gitignore +0 -0
  13. {multicollections-0.2.1 → multicollections-0.3.0}/.readthedocs.yaml +0 -0
  14. {multicollections-0.2.1 → multicollections-0.3.0}/LICENSE.txt +0 -0
  15. {multicollections-0.2.1 → multicollections-0.3.0}/docs/api/abc.md +0 -0
  16. {multicollections-0.2.1 → multicollections-0.3.0}/docs/api/multicollections.md +0 -0
  17. {multicollections-0.2.1 → multicollections-0.3.0}/docs/index.md +0 -0
  18. {multicollections-0.2.1 → multicollections-0.3.0}/mkdocs.yml +0 -0
  19. {multicollections-0.2.1 → multicollections-0.3.0}/multicollections/py.typed +0 -0
  20. {multicollections-0.2.1 → multicollections-0.3.0}/tests/__init__.py +0 -0
  21. {multicollections-0.2.1 → multicollections-0.3.0}/tests/ruff.toml +0 -0
@@ -33,9 +33,9 @@ jobs:
33
33
  uses: actions/setup-python@v5
34
34
  with:
35
35
  python-version-file: pyproject.toml
36
- - name: Check types with ty
36
+ - name: Check types with mypy
37
37
  run: |
38
- uv run ty check
38
+ uv run mypy
39
39
 
40
40
  test:
41
41
  runs-on: ubuntu-22.04
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multicollections
3
- Version: 0.2.1
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
  [![Documentation](https://img.shields.io/readthedocs/multicollections)](https://multicollections.readthedocs.io/)
37
37
  [![CI](https://github.com/gerlero/multicollections/actions/workflows/ci.yml/badge.svg)](https://github.com/gerlero/multicollections/actions/workflows/ci.yml)
38
38
  [![Codecov](https://codecov.io/gh/gerlero/multicollections/branch/main/graph/badge.svg)](https://codecov.io/gh/gerlero/multicollections)
39
+ [![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)
39
40
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
40
- [![ty](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ty/main/assets/badge/v0.json)](https://github.com/astral-sh/ty)
41
41
  [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
42
42
  [![Publish](https://github.com/gerlero/multicollections/actions/workflows/pypi-publish.yml/badge.svg)](https://github.com/gerlero/multicollections/actions/workflows/pypi-publish.yml)
43
43
  [![PyPI](https://img.shields.io/pypi/v/multicollections)](https://pypi.org/project/multicollections/)
@@ -5,8 +5,8 @@ A fully generic [`MultiDict`](https://multicollections.readthedocs.io/en/latest/
5
5
  [![Documentation](https://img.shields.io/readthedocs/multicollections)](https://multicollections.readthedocs.io/)
6
6
  [![CI](https://github.com/gerlero/multicollections/actions/workflows/ci.yml/badge.svg)](https://github.com/gerlero/multicollections/actions/workflows/ci.yml)
7
7
  [![Codecov](https://codecov.io/gh/gerlero/multicollections/branch/main/graph/badge.svg)](https://codecov.io/gh/gerlero/multicollections)
8
+ [![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)
8
9
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
9
- [![ty](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ty/main/assets/badge/v0.json)](https://github.com/astral-sh/ty)
10
10
  [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
11
11
  [![Publish](https://github.com/gerlero/multicollections/actions/workflows/pypi-publish.yml/badge.svg)](https://github.com/gerlero/multicollections/actions/workflows/pypi-publish.yml)
12
12
  [![PyPI](https://img.shields.io/pypi/v/multicollections)](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})"
@@ -14,3 +14,6 @@ else:
14
14
  def override(func: Callable) -> Callable:
15
15
  """Fallback override decorator that does nothing."""
16
16
  return func
17
+
18
+
19
+ __all__ = ["override"]
@@ -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: # pragma: no cover
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(MappingView, Collection):
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
- super().__init__(mapping)
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 # ty: ignore[not-iterable]
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 (k, self._mapping.getall(k)[counts[k]])
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 iter(self)
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[_Self, _K, _V, _D]):
129
+ class _CallableWithDefault(Protocol[_Self_co, _K_contra, _V_co]):
117
130
  @overload
118
- def __call__(self: _Self, key: _K) -> _V: ...
131
+ def __call__(self: _Self_co, key: _K_contra) -> _V_co: ...
119
132
 
120
133
  @overload
121
- def __call__(self: _Self, key: _K, default: _D) -> _V | _D: ...
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, _D]:
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: ... # pragma: no cover
143
+ def wrapper(self: _Self, key: _K) -> _V: ...
131
144
 
132
145
  @overload
133
- def wrapper(self: _Self, key: _K, default: _D) -> _V | _D: ... # pragma: no cover
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 # ty: ignore [invalid-return-type]
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], Generic[_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) -> list[_V]:
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
- return self.getall(key)[0]
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) -> list[_V]:
243
- """Remove and return all values for a key as a list.
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.2.1"
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
- "ty; python_version>='3.8'",
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.uv.build-backend]
65
- module-root = "."
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