Skip to content
Back to top

# Source code for bidict._bidict

    # Copyright 2009-2024 Joshua Bronson. All rights reserved.
    #
    # This Source Code Form is subject to the terms of the Mozilla Public
    # License, v. 2.0. If a copy of the MPL was not distributed with this
    # file, You can obtain one at http://mozilla.org/MPL/2.0/.

    #                             * Code review nav *
    #                        (see comments in __init__.py)
    # ============================================================================
    # ← Prev: _frozen.py          Current: _bidict.py      Next: _orderedbase.py →
    # ============================================================================

    """Provide :class:`MutableBidict` and :class:`bidict`."""

    from __future__ import annotations

    import typing as t

    from ._abc import MutableBidirectionalMapping
    from ._base import BidictBase
    from ._dup import ON_DUP_DROP_OLD
    from ._dup import ON_DUP_RAISE
    from ._dup import OnDup
    from ._typing import DT
    from ._typing import KT
    from ._typing import MISSING
    from ._typing import ODT
    from ._typing import VT
    from ._typing import MapOrItems

    class MutableBidict(BidictBase[KT, VT], MutableBidirectionalMapping[KT, VT]):
        """Base class for mutable bidirectional mappings."""

        if t.TYPE_CHECKING:

            @property
            def inverse(self) -> MutableBidict[VT, KT]: ...

            @property
            def inv(self) -> MutableBidict[VT, KT]: ...

        def _pop(self, key: KT) -> VT:
            val = self._fwdm.pop(key)
            del self._invm[val]
            return val

        def __delitem__(self, key: KT) -> None:
            """*x.__delitem__(y)　⟺　del x[y]*"""
            self._pop(key)

        def __setitem__(self, key: KT, val: VT) -> None:
            """Set the value for *key* to *val*.

            If *key* is already associated with *val*, this is a no-op.

            If *key* is already associated with a different value,
            the old value will be replaced with *val*,
            as with dict's :meth:`__setitem__`.

            If *val* is already associated with a different key,
            an exception is raised
            to protect against accidental removal of the key
            that's currently associated with *val*.

            Use :meth:`put` instead if you want to specify different behavior in
            the case that the provided key or value duplicates an existing one.
            Or use :meth:`forceput` to unconditionally associate *key* with *val*,
            replacing any existing items as necessary to preserve uniqueness.

            :raises bidict.ValueDuplicationError: if *val* duplicates that of an
                existing item.

            :raises bidict.KeyAndValueDuplicationError: if *key* duplicates the key of an
                existing item and *val* duplicates the value of a different
                existing item.
            """
            self.put(key, val, on_dup=self.on_dup)

        def put(self, key: KT, val: VT, on_dup: OnDup = ON_DUP_RAISE) -> None:
            """Associate *key* with *val*, honoring the :class:`OnDup` given in *on_dup*.

            For example, if *on_dup* is :attr:`~bidict.ON_DUP_RAISE`,
            then *key* will be associated with *val* if and only if
            *key* is not already associated with an existing value and
            *val* is not already associated with an existing key,
            otherwise an exception will be raised.

            If *key* is already associated with *val*, this is a no-op.

            :raises bidict.KeyDuplicationError: if attempting to insert an item
                whose key only duplicates an existing item's, and *on_dup.key* is
                :attr:`~bidict.RAISE`.

            :raises bidict.ValueDuplicationError: if attempting to insert an item
                whose value only duplicates an existing item's, and *on_dup.val* is
                :attr:`~bidict.RAISE`.

            :raises bidict.KeyAndValueDuplicationError: if attempting to insert an
                item whose key duplicates one existing item's, and whose value
                duplicates another existing item's, and *on_dup.val* is
                :attr:`~bidict.RAISE`.
            """
            self._update(((key, val),), on_dup=on_dup)

        def forceput(self, key: KT, val: VT) -> None:
            """Associate *key* with *val* unconditionally.

            Replace any existing mappings containing key *key* or value *val*
            as necessary to preserve uniqueness.
            """
            self.put(key, val, on_dup=ON_DUP_DROP_OLD)

        def clear(self) -> None:
            """Remove all items."""
            self._fwdm.clear()
            self._invm.clear()

        @t.overload
        def pop(self, key: KT, /) -> VT: ...
        @t.overload
        def pop(self, key: KT, default: DT = ..., /) -> VT | DT: ...

        def pop(self, key: KT, default: ODT[DT] = MISSING, /) -> VT | DT:
            """*x.pop(k[, d]) → v*

            Remove specified key and return the corresponding value.

            :raises KeyError: if *key* is not found and no *default* is provided.
            """
            try:
                return self._pop(key)
            except KeyError:
                if default is MISSING:
                    raise
                return default

        def popitem(self) -> tuple[KT, VT]:
            """*x.popitem() → (k, v)*

            Remove and return some item as a (key, value) pair.

            :raises KeyError: if *x* is empty.
            """
            key, val = self._fwdm.popitem()
            del self._invm[val]
            return key, val

        def update(self, arg: MapOrItems[KT, VT] = (), /, **kw: VT) -> None:
            """Like calling :meth:`putall` with *self.on_dup* passed for *on_dup*."""
            self._update(arg, kw=kw)

        def forceupdate(self, arg: MapOrItems[KT, VT] = (), /, **kw: VT) -> None:
            """Like a bulk :meth:`forceput`."""
            self._update(arg, kw=kw, on_dup=ON_DUP_DROP_OLD)

        def putall(self, items: MapOrItems[KT, VT], on_dup: OnDup = ON_DUP_RAISE) -> None:
            """Like a bulk :meth:`put`.

            If one of the given items causes an exception to be raised,
            none of the items is inserted.
            """
            self._update(items, on_dup=on_dup)

        # other's type is Mapping rather than Maplike since bidict() |= SupportsKeysAndGetItem({})
        # raises a TypeError, just like dict() |= SupportsKeysAndGetItem({}) does.

        def __ior__(self, other: t.Mapping[KT, VT]) -> MutableBidict[KT, VT]:
            """Return self|=other."""
            self.update(other)
            return self

    class bidict(MutableBidict[KT, VT]):
        """The main bidirectional mapping type.

        See :ref:`intro:Introduction` and :ref:`basic-usage:Basic Usage`
        to get started (also available at https://bidict.rtfd.io).
        """

        if t.TYPE_CHECKING:

            @property
            def inverse(self) -> bidict[VT, KT]: ...

            @property
            def inv(self) -> bidict[VT, KT]: ...

    #                             * Code review nav *
    # ============================================================================
    # ← Prev: _frozen.py          Current: _bidict.py      Next: _orderedbase.py →
    # ============================================================================