Skip to content

Abstract Base Classes

The multicollections.abc module provides abstract base classes for multi-mapping collections. These classes define the interface for multi-mappings and provide useful default implementations for common operations.

Overview

Multi-mappings are collections that can hold multiple values for the same key. The abstract base classes in this module provide a foundation for implementing such collections:

  • MultiMapping: Read-only interface for multi-mappings
  • MutableMultiMapping: Mutable interface that extends MultiMapping

API Reference

multicollections.abc

Abstract base classes for multi-mapping collections.

MultiMapping

Bases: Mapping[_K, _V]

Abstract base class for multi-mapping collections.

A multi-mapping is a mapping that can hold multiple values for the same key. This class provides a read-only interface to such collections.

Source code in multicollections/abc.py
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
class MultiMapping(Mapping[_K, _V]):
    """Abstract base class for multi-mapping collections.

    A multi-mapping is a mapping that can hold multiple values for the same key.
    This class provides a read-only interface to such collections.
    """

    @abstractmethod
    @with_default
    def getall(self, key: _K, /) -> Collection[_V]:
        """Get all values for a key.

        Raises a `KeyError` if the key is not found and no default is provided.
        """
        raise NotImplementedError  # pragma: no cover

    @abstractmethod
    @override
    def __iter__(self) -> Iterator[_K]:
        """Return an iterator over the keys.

        Keys with multiple values will be yielded multiple times.
        """
        raise NotImplementedError  # pragma: no cover

    @abstractmethod
    @override
    def __len__(self) -> int:
        """Return the total number of items (key-value pairs)."""
        raise NotImplementedError  # pragma: no cover

    @with_default
    def getone(self, key: _K, /) -> _V:
        """Get the first value for a key.

        Raises a `KeyError` if the key is not found and no default is provided.
        """
        try:
            return next(iter(self.getall(key)))
        except StopIteration as e:  # pragma: no cover
            msg = "MultiMapping.getall returned an empty collection"
            raise RuntimeError(msg) from e

    @override
    def __getitem__(self, key: _K, /) -> _V:
        """Get the first value for a key.

        Raises a `KeyError` if the key is not found.
        """
        return self.getone(key)

    @override
    def keys(self) -> KeysView[_K]:
        """Return a view of the keys in the MultiMapping."""
        return KeysView(self)

    @override
    def items(self) -> ItemsView[_K, _V]:
        """Return a view of the items (key-value pairs) in the MultiMapping."""
        return ItemsView(self)

    @override
    def values(self) -> ValuesView[_V]:
        """Return a view of the values in the MultiMapping."""
        return ValuesView(self)

__getitem__(key: _K) -> _V

Get the first value for a key.

Raises a KeyError if the key is not found.

Source code in multicollections/abc.py
212
213
214
215
216
217
218
@override
def __getitem__(self, key: _K, /) -> _V:
    """Get the first value for a key.

    Raises a `KeyError` if the key is not found.
    """
    return self.getone(key)

__iter__() -> Iterator[_K] abstractmethod

Return an iterator over the keys.

Keys with multiple values will be yielded multiple times.

Source code in multicollections/abc.py
185
186
187
188
189
190
191
192
@abstractmethod
@override
def __iter__(self) -> Iterator[_K]:
    """Return an iterator over the keys.

    Keys with multiple values will be yielded multiple times.
    """
    raise NotImplementedError  # pragma: no cover

__len__() -> int abstractmethod

Return the total number of items (key-value pairs).

Source code in multicollections/abc.py
194
195
196
197
198
@abstractmethod
@override
def __len__(self) -> int:
    """Return the total number of items (key-value pairs)."""
    raise NotImplementedError  # pragma: no cover

getall(key: _K) -> Collection[_V] abstractmethod

Get all values for a key.

Raises a KeyError if the key is not found and no default is provided.

Source code in multicollections/abc.py
176
177
178
179
180
181
182
183
@abstractmethod
@with_default
def getall(self, key: _K, /) -> Collection[_V]:
    """Get all values for a key.

    Raises a `KeyError` if the key is not found and no default is provided.
    """
    raise NotImplementedError  # pragma: no cover

getone(key: _K) -> _V

Get the first value for a key.

Raises a KeyError if the key is not found and no default is provided.

Source code in multicollections/abc.py
200
201
202
203
204
205
206
207
208
209
210
@with_default
def getone(self, key: _K, /) -> _V:
    """Get the first value for a key.

    Raises a `KeyError` if the key is not found and no default is provided.
    """
    try:
        return next(iter(self.getall(key)))
    except StopIteration as e:  # pragma: no cover
        msg = "MultiMapping.getall returned an empty collection"
        raise RuntimeError(msg) from e

items() -> ItemsView[_K, _V]

Return a view of the items (key-value pairs) in the MultiMapping.

Source code in multicollections/abc.py
225
226
227
228
@override
def items(self) -> ItemsView[_K, _V]:
    """Return a view of the items (key-value pairs) in the MultiMapping."""
    return ItemsView(self)

keys() -> KeysView[_K]

Return a view of the keys in the MultiMapping.

Source code in multicollections/abc.py
220
221
222
223
@override
def keys(self) -> KeysView[_K]:
    """Return a view of the keys in the MultiMapping."""
    return KeysView(self)

values() -> ValuesView[_V]

Return a view of the values in the MultiMapping.

Source code in multicollections/abc.py
230
231
232
233
@override
def values(self) -> ValuesView[_V]:
    """Return a view of the values in the MultiMapping."""
    return ValuesView(self)

MultiMappingView

Bases: MappingView

Base class for MultiMapping views.

Source code in multicollections/abc.py
51
52
53
54
55
56
57
58
59
60
61
62
63
class MultiMappingView(MappingView):
    """Base class for MultiMapping views."""

    _mapping: MultiMapping[Any, Any]

    def __init__(self, mapping: MultiMapping[Any, Any], /) -> None:
        """Initialize the view with the given mapping."""
        super().__init__(mapping)

    @override
    def __len__(self) -> int:
        """Return the number of items in the view."""
        return len(self._mapping)

__init__(mapping: MultiMapping[Any, Any]) -> None

Initialize the view with the given mapping.

Source code in multicollections/abc.py
56
57
58
def __init__(self, mapping: MultiMapping[Any, Any], /) -> None:
    """Initialize the view with the given mapping."""
    super().__init__(mapping)

__len__() -> int

Return the number of items in the view.

Source code in multicollections/abc.py
60
61
62
63
@override
def __len__(self) -> int:
    """Return the number of items in the view."""
    return len(self._mapping)

MutableMultiMapping

Bases: MultiMapping[_K, _V], MutableMapping[_K, _V]

Abstract base class for mutable multi-mapping collections.

A mutable multi-mapping extends MultiMapping with methods to modify the collection.

Source code in multicollections/abc.py
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
class MutableMultiMapping(MultiMapping[_K, _V], MutableMapping[_K, _V]):
    """Abstract base class for mutable multi-mapping collections.

    A mutable multi-mapping extends MultiMapping with methods to modify the collection.
    """

    @abstractmethod
    @override
    def __setitem__(self, key: _K, value: _V, /) -> None:
        """Set the value for a key.

        If the key does not exist, it is added with the specified value.

        If the key already exists, the first item is assigned the new value,
        and any other items with the same key are removed.
        """
        raise NotImplementedError  # pragma: no cover

    @abstractmethod
    def add(self, key: _K, value: _V, /) -> None:
        """Add a new value for a key."""
        raise NotImplementedError  # pragma: no cover

    @abstractmethod
    @with_default
    def popone(self, key: _K, /) -> _V:
        """Remove and return the first value for a key.

        Raises a `KeyError` if the key is not found.
        """
        raise NotImplementedError  # pragma: no cover

    @with_default
    def popall(self, key: _K, /) -> Collection[_V]:
        """Remove and return all values for a key.

        Raises a `KeyError` if the key is not found and no default is provided.
        """
        ret = [self.popone(key)]
        with contextlib.suppress(KeyError):
            while True:
                ret.append(self.popone(key))
        return ret

    @with_default
    @override
    def pop(self, key: _K, /) -> _V:
        """Same as `popone`."""
        return self.popone(key)

    @override
    def popitem(self) -> tuple[_K, _V]:
        """Remove and return a (key, value) pair."""
        key = next(iter(self))
        value = self.popone(key)
        return key, value

    @override
    def __delitem__(self, key: _K, /) -> None:
        """Remove all values for a key.

        Raises a `KeyError` if the key is not found.
        """
        self.popall(key)

    @override
    def clear(self) -> None:
        """Remove all items from the multi-mapping."""
        for key in set(self.keys()):
            self.popall(key)

    def extend(
        self,
        other: SupportsKeysAndGetItem[_K, _V] | Iterable[tuple[_K, _V]] = (),
        /,
        **kwargs: _V,
    ) -> None:
        """Extend the multi-mapping with items from another object."""
        for key, value in _yield_items(other, **kwargs):
            self.add(key, value)

    def merge(
        self,
        other: SupportsKeysAndGetItem[_K, _V] | Iterable[tuple[_K, _V]] = (),
        /,
        **kwargs: _V,
    ) -> None:
        """Merge another object into the multi-mapping.

        Keys from `other` that already exist in the multi-mapping will not be replaced.
        """
        existing_keys = set(self.keys())
        for key, value in _yield_items(other, **kwargs):
            if key not in existing_keys:
                self.add(key, value)

    @override
    def update(
        self,
        other: SupportsKeysAndGetItem[_K, _V] | Iterable[tuple[_K, _V]] = (),
        /,
        **kwargs: _V,
    ) -> None:
        """Update the multi-mapping with items from another object.

        This replaces existing values for keys found in the other object.
        """
        existing_keys = set(self.keys())
        for key, value in _yield_items(other, **kwargs):
            if key in existing_keys:
                self[key] = value
                existing_keys.remove(key)
            else:
                self.add(key, value)

__delitem__(key: _K) -> None

Remove all values for a key.

Raises a KeyError if the key is not found.

Source code in multicollections/abc.py
293
294
295
296
297
298
299
@override
def __delitem__(self, key: _K, /) -> None:
    """Remove all values for a key.

    Raises a `KeyError` if the key is not found.
    """
    self.popall(key)

__getitem__(key: _K) -> _V

Get the first value for a key.

Raises a KeyError if the key is not found.

Source code in multicollections/abc.py
212
213
214
215
216
217
218
@override
def __getitem__(self, key: _K, /) -> _V:
    """Get the first value for a key.

    Raises a `KeyError` if the key is not found.
    """
    return self.getone(key)

__iter__() -> Iterator[_K] abstractmethod

Return an iterator over the keys.

Keys with multiple values will be yielded multiple times.

Source code in multicollections/abc.py
185
186
187
188
189
190
191
192
@abstractmethod
@override
def __iter__(self) -> Iterator[_K]:
    """Return an iterator over the keys.

    Keys with multiple values will be yielded multiple times.
    """
    raise NotImplementedError  # pragma: no cover

__len__() -> int abstractmethod

Return the total number of items (key-value pairs).

Source code in multicollections/abc.py
194
195
196
197
198
@abstractmethod
@override
def __len__(self) -> int:
    """Return the total number of items (key-value pairs)."""
    raise NotImplementedError  # pragma: no cover

__setitem__(key: _K, value: _V) -> None abstractmethod

Set the value for a key.

If the key does not exist, it is added with the specified value.

If the key already exists, the first item is assigned the new value, and any other items with the same key are removed.

Source code in multicollections/abc.py
242
243
244
245
246
247
248
249
250
251
252
@abstractmethod
@override
def __setitem__(self, key: _K, value: _V, /) -> None:
    """Set the value for a key.

    If the key does not exist, it is added with the specified value.

    If the key already exists, the first item is assigned the new value,
    and any other items with the same key are removed.
    """
    raise NotImplementedError  # pragma: no cover

add(key: _K, value: _V) -> None abstractmethod

Add a new value for a key.

Source code in multicollections/abc.py
254
255
256
257
@abstractmethod
def add(self, key: _K, value: _V, /) -> None:
    """Add a new value for a key."""
    raise NotImplementedError  # pragma: no cover

clear() -> None

Remove all items from the multi-mapping.

Source code in multicollections/abc.py
301
302
303
304
305
@override
def clear(self) -> None:
    """Remove all items from the multi-mapping."""
    for key in set(self.keys()):
        self.popall(key)

extend(other: SupportsKeysAndGetItem[_K, _V] | Iterable[tuple[_K, _V]] = (), /, **kwargs: _V) -> None

Extend the multi-mapping with items from another object.

Source code in multicollections/abc.py
307
308
309
310
311
312
313
314
315
def extend(
    self,
    other: SupportsKeysAndGetItem[_K, _V] | Iterable[tuple[_K, _V]] = (),
    /,
    **kwargs: _V,
) -> None:
    """Extend the multi-mapping with items from another object."""
    for key, value in _yield_items(other, **kwargs):
        self.add(key, value)

getall(key: _K) -> Collection[_V] abstractmethod

Get all values for a key.

Raises a KeyError if the key is not found and no default is provided.

Source code in multicollections/abc.py
176
177
178
179
180
181
182
183
@abstractmethod
@with_default
def getall(self, key: _K, /) -> Collection[_V]:
    """Get all values for a key.

    Raises a `KeyError` if the key is not found and no default is provided.
    """
    raise NotImplementedError  # pragma: no cover

getone(key: _K) -> _V

Get the first value for a key.

Raises a KeyError if the key is not found and no default is provided.

Source code in multicollections/abc.py
200
201
202
203
204
205
206
207
208
209
210
@with_default
def getone(self, key: _K, /) -> _V:
    """Get the first value for a key.

    Raises a `KeyError` if the key is not found and no default is provided.
    """
    try:
        return next(iter(self.getall(key)))
    except StopIteration as e:  # pragma: no cover
        msg = "MultiMapping.getall returned an empty collection"
        raise RuntimeError(msg) from e

items() -> ItemsView[_K, _V]

Return a view of the items (key-value pairs) in the MultiMapping.

Source code in multicollections/abc.py
225
226
227
228
@override
def items(self) -> ItemsView[_K, _V]:
    """Return a view of the items (key-value pairs) in the MultiMapping."""
    return ItemsView(self)

keys() -> KeysView[_K]

Return a view of the keys in the MultiMapping.

Source code in multicollections/abc.py
220
221
222
223
@override
def keys(self) -> KeysView[_K]:
    """Return a view of the keys in the MultiMapping."""
    return KeysView(self)

merge(other: SupportsKeysAndGetItem[_K, _V] | Iterable[tuple[_K, _V]] = (), /, **kwargs: _V) -> None

Merge another object into the multi-mapping.

Keys from other that already exist in the multi-mapping will not be replaced.

Source code in multicollections/abc.py
317
318
319
320
321
322
323
324
325
326
327
328
329
330
def merge(
    self,
    other: SupportsKeysAndGetItem[_K, _V] | Iterable[tuple[_K, _V]] = (),
    /,
    **kwargs: _V,
) -> None:
    """Merge another object into the multi-mapping.

    Keys from `other` that already exist in the multi-mapping will not be replaced.
    """
    existing_keys = set(self.keys())
    for key, value in _yield_items(other, **kwargs):
        if key not in existing_keys:
            self.add(key, value)

pop(key: _K) -> _V

Same as popone.

Source code in multicollections/abc.py
280
281
282
283
284
@with_default
@override
def pop(self, key: _K, /) -> _V:
    """Same as `popone`."""
    return self.popone(key)

popall(key: _K) -> Collection[_V]

Remove and return all values for a key.

Raises a KeyError if the key is not found and no default is provided.

Source code in multicollections/abc.py
268
269
270
271
272
273
274
275
276
277
278
@with_default
def popall(self, key: _K, /) -> Collection[_V]:
    """Remove and return all values for a key.

    Raises a `KeyError` if the key is not found and no default is provided.
    """
    ret = [self.popone(key)]
    with contextlib.suppress(KeyError):
        while True:
            ret.append(self.popone(key))
    return ret

popitem() -> tuple[_K, _V]

Remove and return a (key, value) pair.

Source code in multicollections/abc.py
286
287
288
289
290
291
@override
def popitem(self) -> tuple[_K, _V]:
    """Remove and return a (key, value) pair."""
    key = next(iter(self))
    value = self.popone(key)
    return key, value

popone(key: _K) -> _V abstractmethod

Remove and return the first value for a key.

Raises a KeyError if the key is not found.

Source code in multicollections/abc.py
259
260
261
262
263
264
265
266
@abstractmethod
@with_default
def popone(self, key: _K, /) -> _V:
    """Remove and return the first value for a key.

    Raises a `KeyError` if the key is not found.
    """
    raise NotImplementedError  # pragma: no cover

update(other: SupportsKeysAndGetItem[_K, _V] | Iterable[tuple[_K, _V]] = (), /, **kwargs: _V) -> None

Update the multi-mapping with items from another object.

This replaces existing values for keys found in the other object.

Source code in multicollections/abc.py
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
@override
def update(
    self,
    other: SupportsKeysAndGetItem[_K, _V] | Iterable[tuple[_K, _V]] = (),
    /,
    **kwargs: _V,
) -> None:
    """Update the multi-mapping with items from another object.

    This replaces existing values for keys found in the other object.
    """
    existing_keys = set(self.keys())
    for key, value in _yield_items(other, **kwargs):
        if key in existing_keys:
            self[key] = value
            existing_keys.remove(key)
        else:
            self.add(key, value)

values() -> ValuesView[_V]

Return a view of the values in the MultiMapping.

Source code in multicollections/abc.py
230
231
232
233
@override
def values(self) -> ValuesView[_V]:
    """Return a view of the values in the MultiMapping."""
    return ValuesView(self)

with_default(meth: Callable[[_Self, _K], _V]) -> MethodWithDefault[_K, _V]

Add a default value argument to a method that can raise a KeyError.

Source code in multicollections/abc.py
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
def with_default(
    meth: Callable[[_Self, _K], _V],
    /,
) -> MethodWithDefault[_K, _V]:
    """Add a default value argument to a method that can raise a `KeyError`."""

    @overload
    def wrapper(self: _Self, key: _K, /) -> _V: ...

    @overload
    def wrapper(self: _Self, key: _K, /, default: _D) -> _V | _D: ...

    @functools.wraps(meth)
    def wrapper(
        self: _Self, key: _K, /, default: _D | _NoDefault = _NO_DEFAULT
    ) -> _V | _D:
        try:
            return meth(self, key)
        except KeyError:
            if isinstance(default, _NoDefault):
                raise
            return default

    return wrapper  # ty: ignore[invalid-return-type]