forked from bton/matekasse
tests versuch 2
This commit is contained in:
parent
fdf385fe06
commit
c88f7df83a
2363 changed files with 408191 additions and 0 deletions
100
venv/lib/python3.11/site-packages/bidict/__init__.py
Normal file
100
venv/lib/python3.11/site-packages/bidict/__init__.py
Normal file
|
@ -0,0 +1,100 @@
|
|||
# Copyright 2009-2022 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/.
|
||||
|
||||
|
||||
#==============================================================================
|
||||
# * Welcome to the bidict source code *
|
||||
#==============================================================================
|
||||
|
||||
# Reading through the code? You'll find a "Code review nav" comment like the one
|
||||
# below at the top and bottom of the key source files. Follow these cues to take
|
||||
# a path through the code that's optimized for familiarizing yourself with it.
|
||||
#
|
||||
# If you're not reading this on https://github.com/jab/bidict already, go there
|
||||
# to ensure you have the latest version of the code. While there, you can also
|
||||
# star the project, watch it for updates, fork the code, and submit an issue or
|
||||
# pull request with any proposed changes. More information can be found linked
|
||||
# from README.rst, which is also shown on https://github.com/jab/bidict.
|
||||
|
||||
# * Code review nav *
|
||||
#==============================================================================
|
||||
# Current: __init__.py Next: _abc.py →
|
||||
#==============================================================================
|
||||
|
||||
|
||||
"""The bidirectional mapping library for Python.
|
||||
|
||||
----
|
||||
|
||||
bidict by example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> from bidict import bidict
|
||||
>>> element_by_symbol = bidict({'H': 'hydrogen'})
|
||||
>>> element_by_symbol['H']
|
||||
'hydrogen'
|
||||
>>> element_by_symbol.inverse['hydrogen']
|
||||
'H'
|
||||
|
||||
|
||||
Please see https://github.com/jab/bidict for the most up-to-date code and
|
||||
https://bidict.readthedocs.io for the most up-to-date documentation
|
||||
if you are reading this elsewhere.
|
||||
|
||||
----
|
||||
|
||||
.. :copyright: (c) 2009-2022 Joshua Bronson.
|
||||
.. :license: MPLv2. See LICENSE for details.
|
||||
"""
|
||||
|
||||
# Use private aliases to not re-export these publicly (for Sphinx automodule with imported-members).
|
||||
from __future__ import annotations as _annotations
|
||||
from sys import version_info as _version_info
|
||||
|
||||
|
||||
if _version_info < (3, 7): # pragma: no cover
|
||||
raise ImportError('Python 3.7+ is required.')
|
||||
|
||||
|
||||
from contextlib import suppress as _suppress
|
||||
|
||||
from ._abc import BidirectionalMapping as BidirectionalMapping, MutableBidirectionalMapping as MutableBidirectionalMapping
|
||||
from ._base import BidictBase as BidictBase, GeneratedBidictInverse as GeneratedBidictInverse, BidictKeysView as BidictKeysView
|
||||
from ._bidict import MutableBidict as MutableBidict, bidict as bidict
|
||||
from ._frozenbidict import frozenbidict as frozenbidict
|
||||
from ._frozenordered import FrozenOrderedBidict as FrozenOrderedBidict
|
||||
from ._named import NamedBidictBase as NamedBidictBase, namedbidict as namedbidict
|
||||
from ._orderedbase import OrderedBidictBase as OrderedBidictBase
|
||||
from ._orderedbidict import OrderedBidict as OrderedBidict
|
||||
from ._dup import ON_DUP_DEFAULT as ON_DUP_DEFAULT, ON_DUP_RAISE as ON_DUP_RAISE, ON_DUP_DROP_OLD as ON_DUP_DROP_OLD
|
||||
from ._dup import RAISE as RAISE, DROP_OLD as DROP_OLD, DROP_NEW as DROP_NEW, OnDup as OnDup, OD as OD
|
||||
from ._exc import BidictException as BidictException, DuplicationError as DuplicationError
|
||||
from ._exc import KeyDuplicationError as KeyDuplicationError, ValueDuplicationError as ValueDuplicationError, KeyAndValueDuplicationError as KeyAndValueDuplicationError
|
||||
from ._iter import inverted as inverted
|
||||
from .metadata import (
|
||||
__author__ as __author__, __copyright__ as __copyright__, __description__ as __description__,
|
||||
__license__ as __license__, __url__ as __url__, __version__ as __version__,
|
||||
)
|
||||
|
||||
|
||||
#: Alias
|
||||
OnDupAction = OD
|
||||
|
||||
|
||||
# Set __module__ of re-exported classes to the 'bidict' top-level module, so that e.g.
|
||||
# 'bidict.bidict' shows up as 'bidict.bidict` rather than 'bidict._bidict.bidict'.
|
||||
for _obj in tuple(locals().values()): # pragma: no cover
|
||||
if not getattr(_obj, '__module__', '').startswith('bidict.'):
|
||||
continue
|
||||
with _suppress(AttributeError):
|
||||
_obj.__module__ = 'bidict'
|
||||
|
||||
|
||||
# * Code review nav *
|
||||
#==============================================================================
|
||||
# Current: __init__.py Next: _abc.py →
|
||||
#==============================================================================
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
77
venv/lib/python3.11/site-packages/bidict/_abc.py
Normal file
77
venv/lib/python3.11/site-packages/bidict/_abc.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
# Copyright 2009-2022 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: __init__.py Current: _abc.py Next: _base.py →
|
||||
#==============================================================================
|
||||
|
||||
|
||||
"""Provide the :class:`BidirectionalMapping` abstract base class."""
|
||||
|
||||
from __future__ import annotations
|
||||
from abc import abstractmethod
|
||||
import typing as t
|
||||
|
||||
from ._typing import KT, VT
|
||||
|
||||
|
||||
class BidirectionalMapping(t.Mapping[KT, VT]):
|
||||
"""Abstract base class for bidirectional mapping types.
|
||||
|
||||
Extends :class:`collections.abc.Mapping` primarily by adding the
|
||||
(abstract) :attr:`inverse` property,
|
||||
which implementors of :class:`BidirectionalMapping`
|
||||
should override to return a reference to the inverse
|
||||
:class:`BidirectionalMapping` instance.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def inverse(self) -> BidirectionalMapping[VT, KT]:
|
||||
"""The inverse of this bidirectional mapping instance.
|
||||
|
||||
*See also* :attr:`bidict.BidictBase.inverse`, :attr:`bidict.BidictBase.inv`
|
||||
|
||||
:raises NotImplementedError: Meant to be overridden in subclasses.
|
||||
"""
|
||||
# The @abstractmethod decorator prevents BidirectionalMapping subclasses from being
|
||||
# instantiated unless they override ``.inverse``. So this implementation of ``.inverse``
|
||||
# should never be unintentionally resolved from subclass instances. But raise here
|
||||
# anyway, so it's extra clear that this implementation should never be called.
|
||||
raise NotImplementedError
|
||||
|
||||
def __inverted__(self) -> t.Iterator[tuple[VT, KT]]:
|
||||
"""Get an iterator over the items in :attr:`inverse`.
|
||||
|
||||
This is functionally equivalent to iterating over the items in the
|
||||
forward mapping and inverting each one on the fly, but this provides a
|
||||
more efficient implementation: Assuming the already-inverted items
|
||||
are stored in :attr:`inverse`, just return an iterator over them directly.
|
||||
|
||||
Providing this default implementation enables external functions,
|
||||
particularly :func:`~bidict.inverted`, to use this optimized
|
||||
implementation when available, instead of having to invert on the fly.
|
||||
|
||||
*See also* :func:`bidict.inverted`
|
||||
"""
|
||||
return iter(self.inverse.items())
|
||||
|
||||
|
||||
class MutableBidirectionalMapping(BidirectionalMapping[KT, VT], t.MutableMapping[KT, VT]):
|
||||
"""Abstract base class for mutable bidirectional mapping types."""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
# * Code review nav *
|
||||
#==============================================================================
|
||||
# ← Prev: __init__.py Current: _abc.py Next: _base.py →
|
||||
#==============================================================================
|
552
venv/lib/python3.11/site-packages/bidict/_base.py
Normal file
552
venv/lib/python3.11/site-packages/bidict/_base.py
Normal file
|
@ -0,0 +1,552 @@
|
|||
# Copyright 2009-2022 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: _abc.py Current: _base.py Next: _frozenbidict.py →
|
||||
#==============================================================================
|
||||
|
||||
|
||||
"""Provide :class:`BidictBase`."""
|
||||
|
||||
from __future__ import annotations
|
||||
from functools import partial
|
||||
from itertools import starmap
|
||||
from operator import eq
|
||||
from types import MappingProxyType
|
||||
import typing as t
|
||||
import weakref
|
||||
|
||||
from ._abc import BidirectionalMapping
|
||||
from ._dup import ON_DUP_DEFAULT, RAISE, DROP_OLD, DROP_NEW, OnDup
|
||||
from ._exc import DuplicationError, KeyDuplicationError, ValueDuplicationError, KeyAndValueDuplicationError
|
||||
from ._iter import iteritems, inverted
|
||||
from ._typing import KT, VT, MISSING, OKT, OVT, Items, MapOrItems, TypeAlias
|
||||
|
||||
|
||||
OldKV: TypeAlias = 'tuple[OKT[KT], OVT[VT]]'
|
||||
DedupResult: TypeAlias = 'OldKV[KT, VT] | None'
|
||||
Write: TypeAlias = 'list[t.Callable[[], None]]'
|
||||
Unwrite: TypeAlias = Write
|
||||
PreparedWrite: TypeAlias = 'tuple[Write, Unwrite]'
|
||||
BT = t.TypeVar('BT', bound='BidictBase[t.Any, t.Any]')
|
||||
|
||||
|
||||
class BidictKeysView(t.KeysView[KT], t.ValuesView[KT]):
|
||||
"""Since the keys of a bidict are the values of its inverse (and vice versa),
|
||||
the :class:`~collections.abc.ValuesView` result of calling *bi.values()*
|
||||
is also a :class:`~collections.abc.KeysView` of *bi.inverse*.
|
||||
"""
|
||||
|
||||
|
||||
def get_arg(*args: MapOrItems[KT, VT]) -> MapOrItems[KT, VT]:
|
||||
"""Ensure there's only a single arg in *args*, then return it."""
|
||||
if len(args) > 1:
|
||||
raise TypeError(f'Expected at most 1 positional argument, got {len(args)}')
|
||||
return args[0] if args else ()
|
||||
|
||||
|
||||
class BidictBase(BidirectionalMapping[KT, VT]):
|
||||
"""Base class implementing :class:`BidirectionalMapping`."""
|
||||
|
||||
#: The default :class:`~bidict.OnDup`
|
||||
#: that governs behavior when a provided item
|
||||
#: duplicates the key or value of other item(s).
|
||||
#:
|
||||
#: *See also*
|
||||
#: :ref:`basic-usage:Values Must Be Unique` (https://bidict.rtfd.io/basic-usage.html#values-must-be-unique),
|
||||
#: :doc:`extending` (https://bidict.rtfd.io/extending.html)
|
||||
on_dup = ON_DUP_DEFAULT
|
||||
|
||||
_fwdm: t.MutableMapping[KT, VT] #: the backing forward mapping (*key* → *val*)
|
||||
_invm: t.MutableMapping[VT, KT] #: the backing inverse mapping (*val* → *key*)
|
||||
|
||||
# Use Any rather than KT/VT in the following to avoid "ClassVar cannot contain type variables" errors:
|
||||
_fwdm_cls: t.ClassVar[t.Type[t.MutableMapping[t.Any, t.Any]]] = dict #: class of the backing forward mapping
|
||||
_invm_cls: t.ClassVar[t.Type[t.MutableMapping[t.Any, t.Any]]] = dict #: class of the backing inverse mapping
|
||||
|
||||
#: The class of the inverse bidict instance.
|
||||
_inv_cls: t.ClassVar[t.Type[BidictBase[t.Any, t.Any]]]
|
||||
|
||||
#: Used by :meth:`__repr__` for the contained items.
|
||||
_repr_delegate: t.ClassVar[t.Any] = dict
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
cls._init_class()
|
||||
|
||||
@classmethod
|
||||
def _init_class(cls) -> None:
|
||||
cls._ensure_inv_cls()
|
||||
cls._set_reversed()
|
||||
|
||||
__reversed__: t.Any
|
||||
|
||||
@classmethod
|
||||
def _set_reversed(cls) -> None:
|
||||
"""Set __reversed__ for subclasses that do not set it explicitly
|
||||
according to whether backing mappings are reversible.
|
||||
"""
|
||||
if cls is not BidictBase:
|
||||
resolved = cls.__reversed__
|
||||
overridden = resolved is not BidictBase.__reversed__
|
||||
if overridden: # E.g. OrderedBidictBase, OrderedBidict, FrozenOrderedBidict
|
||||
return
|
||||
# The following will be False for MutableBidict, bidict, and frozenbidict on Python < 3.8,
|
||||
# and True for them on 3.8+ (where dicts are reversible). Will also be True for custom
|
||||
# subclasses like SortedBidict (see https://bidict.rtfd.io/extending.html#sortedbidict-recipes).
|
||||
backing_reversible = all(issubclass(i, t.Reversible) for i in (cls._fwdm_cls, cls._invm_cls))
|
||||
cls.__reversed__ = _fwdm_reversed if backing_reversible else None
|
||||
|
||||
@classmethod
|
||||
def _ensure_inv_cls(cls) -> None:
|
||||
"""Ensure :attr:`_inv_cls` is set, computing it dynamically if necessary.
|
||||
|
||||
See: :ref:`extending:Dynamic Inverse Class Generation`
|
||||
(https://bidict.rtfd.io/extending.html#dynamic-inverse-class-generation)
|
||||
|
||||
Most subclasses will be their own inverse classes, but some
|
||||
(e.g. those created via namedbidict) will have distinct inverse classes.
|
||||
"""
|
||||
if cls.__dict__.get('_inv_cls'):
|
||||
return # Already set, nothing to do.
|
||||
cls._inv_cls = cls._make_inv_cls()
|
||||
|
||||
@classmethod
|
||||
def _make_inv_cls(cls: t.Type[BT], _miss: t.Any = object()) -> t.Type[BT]:
|
||||
diff = cls._inv_cls_dict_diff()
|
||||
cls_is_own_inv = all(getattr(cls, k, _miss) == v for (k, v) in diff.items())
|
||||
if cls_is_own_inv:
|
||||
return cls
|
||||
# Suppress auto-calculation of _inv_cls's _inv_cls since we know it already.
|
||||
# Works with the guard in BidictBase._ensure_inv_cls() to prevent infinite recursion.
|
||||
diff['_inv_cls'] = cls
|
||||
inv_cls = type(f'{cls.__name__}Inv', (cls, GeneratedBidictInverse), diff)
|
||||
inv_cls.__module__ = cls.__module__
|
||||
return t.cast(t.Type[BT], inv_cls)
|
||||
|
||||
@classmethod
|
||||
def _inv_cls_dict_diff(cls) -> dict[str, t.Any]:
|
||||
return {
|
||||
'_fwdm_cls': cls._invm_cls,
|
||||
'_invm_cls': cls._fwdm_cls,
|
||||
}
|
||||
|
||||
@t.overload
|
||||
def __init__(self, **kw: VT) -> None: ...
|
||||
@t.overload
|
||||
def __init__(self, __m: t.Mapping[KT, VT], **kw: VT) -> None: ...
|
||||
@t.overload
|
||||
def __init__(self, __i: Items[KT, VT], **kw: VT) -> None: ...
|
||||
|
||||
def __init__(self, *args: MapOrItems[KT, VT], **kw: VT) -> None:
|
||||
"""Make a new bidirectional mapping.
|
||||
The signature behaves like that of :class:`dict`.
|
||||
Items passed in are added in the order they are passed,
|
||||
respecting the :attr:`on_dup` class attribute in the process.
|
||||
"""
|
||||
self._fwdm = self._fwdm_cls()
|
||||
self._invm = self._invm_cls()
|
||||
if args or kw:
|
||||
self._update(get_arg(*args), kw, rbof=False)
|
||||
|
||||
# If Python ever adds support for higher-kinded types, `inverse` could use them, e.g.
|
||||
# def inverse(self: BT[KT, VT]) -> BT[VT, KT]:
|
||||
# Ref: https://github.com/python/typing/issues/548#issuecomment-621571821
|
||||
@property
|
||||
def inverse(self) -> BidictBase[VT, KT]:
|
||||
"""The inverse of this bidirectional mapping instance."""
|
||||
# When `bi.inverse` is called for the first time, this method
|
||||
# computes the inverse instance, stores it for subsequent use, and then
|
||||
# returns it. It also stores a reference on `bi.inverse` back to `bi`,
|
||||
# but uses a weakref to avoid creating a reference cycle. Strong references
|
||||
# to inverse instances are stored in ._inv, and weak references are stored
|
||||
# in ._invweak.
|
||||
|
||||
# First check if a strong reference is already stored.
|
||||
inv: BidictBase[VT, KT] | None = getattr(self, '_inv', None)
|
||||
if inv is not None:
|
||||
return inv
|
||||
# Next check if a weak reference is already stored.
|
||||
invweak = getattr(self, '_invweak', None)
|
||||
if invweak is not None:
|
||||
inv = invweak() # Try to resolve a strong reference and return it.
|
||||
if inv is not None:
|
||||
return inv
|
||||
# No luck. Compute the inverse reference and store it for subsequent use.
|
||||
inv = self._make_inverse()
|
||||
self._inv: BidictBase[VT, KT] | None = inv
|
||||
self._invweak: weakref.ReferenceType[BidictBase[VT, KT]] | None = None
|
||||
# Also store a weak reference back to `instance` on its inverse instance, so that
|
||||
# the second `.inverse` access in `bi.inverse.inverse` hits the cached weakref.
|
||||
inv._inv = None
|
||||
inv._invweak = weakref.ref(self)
|
||||
# In e.g. `bidict().inverse.inverse`, this design ensures that a strong reference
|
||||
# back to the original instance is retained before its refcount drops to zero,
|
||||
# avoiding an unintended potential deallocation.
|
||||
return inv
|
||||
|
||||
def _make_inverse(self) -> BidictBase[VT, KT]:
|
||||
inv: BidictBase[VT, KT] = self._inv_cls()
|
||||
inv._fwdm = self._invm
|
||||
inv._invm = self._fwdm
|
||||
return inv
|
||||
|
||||
@property
|
||||
def inv(self) -> BidictBase[VT, KT]:
|
||||
"""Alias for :attr:`inverse`."""
|
||||
return self.inverse
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""See :func:`repr`."""
|
||||
clsname = self.__class__.__name__
|
||||
items = self._repr_delegate(self.items()) if self else ''
|
||||
return f'{clsname}({items})'
|
||||
|
||||
def values(self) -> BidictKeysView[VT]:
|
||||
"""A set-like object providing a view on the contained values.
|
||||
|
||||
Since the values of a bidict are equivalent to the keys of its inverse,
|
||||
this method returns a set-like object for this bidict's values
|
||||
rather than just a collections.abc.ValuesView.
|
||||
This object supports set operations like union and difference,
|
||||
and constant- rather than linear-time containment checks,
|
||||
and is no more expensive to provide than the less capable
|
||||
collections.abc.ValuesView would be.
|
||||
|
||||
See :meth:`keys` for more information.
|
||||
"""
|
||||
return t.cast(BidictKeysView[VT], self.inverse.keys())
|
||||
|
||||
def keys(self) -> t.KeysView[KT]:
|
||||
"""A set-like object providing a view on the contained keys.
|
||||
|
||||
When *b._fwdm* is a :class:`dict`, *b.keys()* returns a
|
||||
*dict_keys* object that behaves exactly the same as
|
||||
*collections.abc.KeysView(b)*, except for
|
||||
|
||||
- offering better performance
|
||||
|
||||
- being reversible on Python 3.8+
|
||||
|
||||
- having a .mapping attribute in Python 3.10+
|
||||
that exposes a mappingproxy to *b._fwdm*.
|
||||
"""
|
||||
fwdm = self._fwdm
|
||||
kv = fwdm.keys() if isinstance(fwdm, dict) else BidictKeysView(self)
|
||||
return kv
|
||||
|
||||
def items(self) -> t.ItemsView[KT, VT]:
|
||||
"""A set-like object providing a view on the contained items.
|
||||
|
||||
When *b._fwdm* is a :class:`dict`, *b.items()* returns a
|
||||
*dict_items* object that behaves exactly the same as
|
||||
*collections.abc.ItemsView(b)*, except for:
|
||||
|
||||
- offering better performance
|
||||
|
||||
- being reversible on Python 3.8+
|
||||
|
||||
- having a .mapping attribute in Python 3.10+
|
||||
that exposes a mappingproxy to *b._fwdm*.
|
||||
"""
|
||||
return self._fwdm.items() if isinstance(self._fwdm, dict) else super().items()
|
||||
|
||||
# The inherited collections.abc.Mapping.__contains__() method is implemented by doing a `try`
|
||||
# `except KeyError` around `self[key]`. The following implementation is much faster,
|
||||
# especially in the missing case.
|
||||
def __contains__(self, key: t.Any) -> bool:
|
||||
"""True if the mapping contains the specified key, else False."""
|
||||
return key in self._fwdm
|
||||
|
||||
# The inherited collections.abc.Mapping.__eq__() method is implemented in terms of an inefficient
|
||||
# `dict(self.items()) == dict(other.items())` comparison, so override it with a
|
||||
# more efficient implementation.
|
||||
def __eq__(self, other: object) -> bool:
|
||||
"""*x.__eq__(other) ⟺ x == other*
|
||||
|
||||
Equivalent to *dict(x.items()) == dict(other.items())*
|
||||
but more efficient.
|
||||
|
||||
Note that :meth:`bidict's __eq__() <bidict.bidict.__eq__>` implementation
|
||||
is inherited by subclasses,
|
||||
in particular by the ordered bidict subclasses,
|
||||
so even with ordered bidicts,
|
||||
:ref:`== comparison is order-insensitive <eq-order-insensitive>`
|
||||
(https://bidict.rtfd.io/other-bidict-types.html#eq-is-order-insensitive).
|
||||
|
||||
*See also* :meth:`equals_order_sensitive`
|
||||
"""
|
||||
if isinstance(other, t.Mapping):
|
||||
return self._fwdm.items() == other.items()
|
||||
# Ref: https://docs.python.org/3/library/constants.html#NotImplemented
|
||||
return NotImplemented
|
||||
|
||||
def equals_order_sensitive(self, other: object) -> bool:
|
||||
"""Order-sensitive equality check.
|
||||
|
||||
*See also* :ref:`eq-order-insensitive`
|
||||
(https://bidict.rtfd.io/other-bidict-types.html#eq-is-order-insensitive)
|
||||
"""
|
||||
if not isinstance(other, t.Mapping) or len(self) != len(other):
|
||||
return False
|
||||
return all(starmap(eq, zip(self.items(), other.items())))
|
||||
|
||||
def _dedup(self, key: KT, val: VT, on_dup: OnDup) -> DedupResult[KT, VT]:
|
||||
"""Check *key* and *val* for any duplication in self.
|
||||
|
||||
Handle any duplication as per the passed in *on_dup*.
|
||||
|
||||
If (key, val) is already present, return None
|
||||
since writing (key, val) would be a no-op.
|
||||
|
||||
If duplication is found and the corresponding :class:`~bidict.OnDupAction` is
|
||||
:attr:`~bidict.DROP_NEW`, return None.
|
||||
|
||||
If duplication is found and the corresponding :class:`~bidict.OnDupAction` is
|
||||
:attr:`~bidict.RAISE`, raise the appropriate exception.
|
||||
|
||||
If duplication is found and the corresponding :class:`~bidict.OnDupAction` is
|
||||
:attr:`~bidict.DROP_OLD`, or if no duplication is found,
|
||||
return *(oldkey, oldval)*.
|
||||
"""
|
||||
fwdm, invm = self._fwdm, self._invm
|
||||
oldval: OVT[VT] = fwdm.get(key, MISSING)
|
||||
oldkey: OKT[KT] = invm.get(val, MISSING)
|
||||
isdupkey, isdupval = oldval is not MISSING, oldkey is not MISSING
|
||||
if isdupkey and isdupval:
|
||||
if key == oldkey:
|
||||
assert val == oldval
|
||||
# (key, val) duplicates an existing item -> no-op.
|
||||
return None
|
||||
# key and val each duplicate a different existing item.
|
||||
if on_dup.kv is RAISE:
|
||||
raise KeyAndValueDuplicationError(key, val)
|
||||
if on_dup.kv is DROP_NEW:
|
||||
return None
|
||||
assert on_dup.kv is DROP_OLD
|
||||
# Fall through to the return statement on the last line.
|
||||
elif isdupkey:
|
||||
if on_dup.key is RAISE:
|
||||
raise KeyDuplicationError(key)
|
||||
if on_dup.key is DROP_NEW:
|
||||
return None
|
||||
assert on_dup.key is DROP_OLD
|
||||
# Fall through to the return statement on the last line.
|
||||
elif isdupval:
|
||||
if on_dup.val is RAISE:
|
||||
raise ValueDuplicationError(val)
|
||||
if on_dup.val is DROP_NEW:
|
||||
return None
|
||||
assert on_dup.val is DROP_OLD
|
||||
# Fall through to the return statement on the last line.
|
||||
# else neither isdupkey nor isdupval.
|
||||
return oldkey, oldval
|
||||
|
||||
def _prep_write(self, newkey: KT, newval: VT, oldkey: OKT[KT], oldval: OVT[VT], save_unwrite: bool) -> PreparedWrite:
|
||||
"""Given (newkey, newval) to insert, return the list of operations necessary to perform the write.
|
||||
|
||||
*oldkey* and *oldval* are as returned by :meth:`_dedup`.
|
||||
|
||||
If *save_unwrite* is true, also return the list of inverse operations necessary to undo the write.
|
||||
This design allows :meth:`_update` to roll back a partially applied update that fails part-way through
|
||||
when necessary. This design also allows subclasses that require additional operations to complete
|
||||
a write to easily extend this implementation. For example, :class:`bidict.OrderedBidictBase` calls this
|
||||
inherited implementation, and then extends the list of ops returned with additional operations
|
||||
needed to keep its internal linked list nodes consistent with its items' order as changes are made.
|
||||
"""
|
||||
fwdm, invm = self._fwdm, self._invm
|
||||
write: list[t.Callable[[], None]] = [
|
||||
partial(fwdm.__setitem__, newkey, newval),
|
||||
partial(invm.__setitem__, newval, newkey),
|
||||
]
|
||||
unwrite: list[t.Callable[[], None]]
|
||||
if oldval is MISSING and oldkey is MISSING: # no key or value duplication
|
||||
# {0: 1, 2: 3} + (4, 5) => {0: 1, 2: 3, 4: 5}
|
||||
unwrite = [
|
||||
partial(fwdm.__delitem__, newkey),
|
||||
partial(invm.__delitem__, newval),
|
||||
] if save_unwrite else []
|
||||
elif oldval is not MISSING and oldkey is not MISSING: # key and value duplication across two different items
|
||||
# {0: 1, 2: 3} + (0, 3) => {0: 3}
|
||||
write.extend((
|
||||
partial(fwdm.__delitem__, oldkey),
|
||||
partial(invm.__delitem__, oldval),
|
||||
))
|
||||
unwrite = [
|
||||
partial(fwdm.__setitem__, newkey, oldval),
|
||||
partial(invm.__setitem__, oldval, newkey),
|
||||
partial(fwdm.__setitem__, oldkey, newval),
|
||||
partial(invm.__setitem__, newval, oldkey),
|
||||
] if save_unwrite else []
|
||||
elif oldval is not MISSING: # just key duplication
|
||||
# {0: 1, 2: 3} + (2, 4) => {0: 1, 2: 4}
|
||||
write.append(partial(invm.__delitem__, oldval))
|
||||
unwrite = [
|
||||
partial(fwdm.__setitem__, newkey, oldval),
|
||||
partial(invm.__setitem__, oldval, newkey),
|
||||
partial(invm.__delitem__, newval),
|
||||
] if save_unwrite else []
|
||||
else:
|
||||
assert oldkey is not MISSING # just value duplication
|
||||
# {0: 1, 2: 3} + (4, 3) => {0: 1, 4: 3}
|
||||
write.append(partial(fwdm.__delitem__, oldkey))
|
||||
unwrite = [
|
||||
partial(fwdm.__setitem__, oldkey, newval),
|
||||
partial(invm.__setitem__, newval, oldkey),
|
||||
partial(fwdm.__delitem__, newkey),
|
||||
] if save_unwrite else []
|
||||
return write, unwrite
|
||||
|
||||
def _update(
|
||||
self,
|
||||
arg: MapOrItems[KT, VT],
|
||||
kw: t.Mapping[str, VT] = MappingProxyType({}),
|
||||
*,
|
||||
rbof: bool | None = None,
|
||||
on_dup: OnDup | None = None,
|
||||
) -> None:
|
||||
"""Update, possibly rolling back on failure as per *rbof*."""
|
||||
# Must process input in a single pass, since arg may be a generator.
|
||||
if not arg and not kw:
|
||||
return
|
||||
if on_dup is None:
|
||||
on_dup = self.on_dup
|
||||
if rbof is None:
|
||||
rbof = RAISE in on_dup
|
||||
if not self and not kw:
|
||||
if isinstance(arg, BidictBase): # can skip dup check
|
||||
self._init_from(arg)
|
||||
return
|
||||
# If arg is not a BidictBase, fall through to the general treatment below,
|
||||
# which includes duplication checking. (If arg is some BidirectionalMapping
|
||||
# that does not inherit from BidictBase, it's a foreign implementation, so we
|
||||
# perform duplication checking to err on the safe side.)
|
||||
|
||||
# If we roll back on failure and we know that there are more updates to process than
|
||||
# already-contained items, our rollback strategy is to update a copy of self (without
|
||||
# rolling back on failure), and then to become the copy if all updates succeed.
|
||||
if rbof and isinstance(arg, t.Sized) and len(arg) + len(kw) > len(self):
|
||||
target = self.copy()
|
||||
target._update(arg, kw, rbof=False, on_dup=on_dup)
|
||||
self._init_from(target)
|
||||
return
|
||||
|
||||
# There are more already-contained items than updates to process, or we don't know
|
||||
# how many updates there are to process. If we need to roll back on failure,
|
||||
# save a log of Unwrites as we update so we can undo changes if the update fails.
|
||||
unwrites: list[Unwrite] = []
|
||||
append_unwrite = unwrites.append
|
||||
prep_write = self._prep_write
|
||||
for (key, val) in iteritems(arg, **kw):
|
||||
try:
|
||||
dedup_result = self._dedup(key, val, on_dup)
|
||||
except DuplicationError:
|
||||
if rbof:
|
||||
while unwrites: # apply saved unwrites
|
||||
unwrite = unwrites.pop()
|
||||
for unwriteop in unwrite:
|
||||
unwriteop()
|
||||
raise
|
||||
if dedup_result is None: # no-op
|
||||
continue
|
||||
write, unwrite = prep_write(key, val, *dedup_result, save_unwrite=rbof)
|
||||
for writeop in write: # apply the write
|
||||
writeop()
|
||||
if rbof and unwrite: # save the unwrite for later application if needed
|
||||
append_unwrite(unwrite)
|
||||
|
||||
def copy(self: BT) -> BT:
|
||||
"""Make a (shallow) copy of this bidict."""
|
||||
# Could just `return self.__class__(self)` here, but the below is faster. The former
|
||||
# would copy this bidict's items into a new instance one at a time (checking for duplication
|
||||
# for each item), whereas the below copies from the backing mappings all at once, and foregoes
|
||||
# item-by-item duplication checking since the backing mappings have been checked already.
|
||||
return self._from_other(self.__class__, self)
|
||||
|
||||
@staticmethod
|
||||
def _from_other(bt: t.Type[BT], other: MapOrItems[KT, VT], inv: bool = False) -> BT:
|
||||
"""Fast, private constructor based on :meth:`_init_from`.
|
||||
|
||||
If *inv* is true, return the inverse of the instance instead of the instance itself.
|
||||
(Useful for pickling with dynamically-generated inverse classes -- see :meth:`__reduce__`.)
|
||||
"""
|
||||
inst = bt()
|
||||
inst._init_from(other)
|
||||
return t.cast(BT, inst.inverse) if inv else inst
|
||||
|
||||
def _init_from(self, other: MapOrItems[KT, VT]) -> None:
|
||||
"""Fast init from *other*, bypassing item-by-item duplication checking."""
|
||||
self._fwdm.clear()
|
||||
self._invm.clear()
|
||||
self._fwdm.update(other)
|
||||
# If other is a bidict, use its existing backing inverse mapping, otherwise
|
||||
# other could be a generator that's now exhausted, so invert self._fwdm on the fly.
|
||||
inv = other.inverse if isinstance(other, BidictBase) else inverted(self._fwdm)
|
||||
self._invm.update(inv)
|
||||
|
||||
#: Used for the copy protocol.
|
||||
#: *See also* the :mod:`copy` module
|
||||
__copy__ = copy
|
||||
|
||||
def __or__(self: BT, other: t.Mapping[KT, VT]) -> BT:
|
||||
"""Return self|other."""
|
||||
if not isinstance(other, t.Mapping):
|
||||
return NotImplemented
|
||||
new = self.copy()
|
||||
new._update(other, rbof=False)
|
||||
return new
|
||||
|
||||
def __ror__(self: BT, other: t.Mapping[KT, VT]) -> BT:
|
||||
"""Return other|self."""
|
||||
if not isinstance(other, t.Mapping):
|
||||
return NotImplemented
|
||||
new = self.__class__(other)
|
||||
new._update(self, rbof=False)
|
||||
return new
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""The number of contained items."""
|
||||
return len(self._fwdm)
|
||||
|
||||
def __iter__(self) -> t.Iterator[KT]:
|
||||
"""Iterator over the contained keys."""
|
||||
return iter(self._fwdm)
|
||||
|
||||
def __getitem__(self, key: KT) -> VT:
|
||||
"""*x.__getitem__(key) ⟺ x[key]*"""
|
||||
return self._fwdm[key]
|
||||
|
||||
def __reduce__(self) -> tuple[t.Any, ...]:
|
||||
"""Return state information for pickling."""
|
||||
# If this bidict's class is dynamically generated, pickle the inverse instead, whose
|
||||
# (presumably not dynamically generated) class the caller is more likely to have a reference to
|
||||
# somewhere in sys.modules that pickle can discover.
|
||||
should_invert = isinstance(self, GeneratedBidictInverse)
|
||||
cls, init_from = (self._inv_cls, self.inverse) if should_invert else (self.__class__, self)
|
||||
return self._from_other, (cls, dict(init_from), should_invert) # type: ignore [call-overload]
|
||||
|
||||
|
||||
# See BidictBase._set_reversed() above.
|
||||
def _fwdm_reversed(self: BidictBase[KT, t.Any]) -> t.Iterator[KT]:
|
||||
"""Iterator over the contained keys in reverse order."""
|
||||
assert isinstance(self._fwdm, t.Reversible)
|
||||
return reversed(self._fwdm)
|
||||
|
||||
|
||||
BidictBase._init_class()
|
||||
|
||||
|
||||
class GeneratedBidictInverse:
|
||||
"""Base class for dynamically-generated inverse bidict classes."""
|
||||
|
||||
|
||||
# * Code review nav *
|
||||
#==============================================================================
|
||||
# ← Prev: _abc.py Current: _base.py Next: _frozenbidict.py →
|
||||
#==============================================================================
|
198
venv/lib/python3.11/site-packages/bidict/_bidict.py
Normal file
198
venv/lib/python3.11/site-packages/bidict/_bidict.py
Normal file
|
@ -0,0 +1,198 @@
|
|||
# Copyright 2009-2022 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: _frozenbidict.py Current: _bidict.py Next: _orderedbase.py →
|
||||
#==============================================================================
|
||||
|
||||
|
||||
"""Provide :class:`MutableBidict`."""
|
||||
|
||||
from __future__ import annotations
|
||||
import typing as t
|
||||
|
||||
from ._abc import MutableBidirectionalMapping
|
||||
from ._base import BidictBase, get_arg
|
||||
from ._dup import OnDup, ON_DUP_RAISE, ON_DUP_DROP_OLD
|
||||
from ._typing import KT, VT, DT, ODT, MISSING, Items, 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]: ...
|
||||
|
||||
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.kv* 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
|
||||
|
||||
@t.overload # type: ignore [override] # https://github.com/jab/bidict/pull/242#discussion_r825464731
|
||||
def update(self, __m: t.Mapping[KT, VT], **kw: VT) -> None: ...
|
||||
@t.overload
|
||||
def update(self, __i: Items[KT, VT], **kw: VT) -> None: ...
|
||||
@t.overload
|
||||
def update(self, **kw: VT) -> None: ...
|
||||
|
||||
def update(self, *args: MapOrItems[KT, VT], **kw: VT) -> None:
|
||||
"""Like calling :meth:`putall` with *self.on_dup* passed for *on_dup*."""
|
||||
if args or kw:
|
||||
self._update(get_arg(*args), kw)
|
||||
|
||||
@t.overload
|
||||
def forceupdate(self, __m: t.Mapping[KT, VT], **kw: VT) -> None: ...
|
||||
@t.overload
|
||||
def forceupdate(self, __i: Items[KT, VT], **kw: VT) -> None: ...
|
||||
@t.overload
|
||||
def forceupdate(self, **kw: VT) -> None: ...
|
||||
|
||||
def forceupdate(self, *args: MapOrItems[KT, VT], **kw: VT) -> None:
|
||||
"""Like a bulk :meth:`forceput`."""
|
||||
if args or kw:
|
||||
self._update(get_arg(*args), kw, on_dup=ON_DUP_DROP_OLD)
|
||||
|
||||
def __ior__(self, other: t.Mapping[KT, VT]) -> MutableBidict[KT, VT]:
|
||||
"""Return self|=other."""
|
||||
self.update(other)
|
||||
return self
|
||||
|
||||
@t.overload
|
||||
def putall(self, items: t.Mapping[KT, VT], on_dup: OnDup) -> None: ...
|
||||
@t.overload
|
||||
def putall(self, items: Items[KT, VT], on_dup: OnDup = ...) -> None: ...
|
||||
|
||||
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.
|
||||
"""
|
||||
if items:
|
||||
self._update(items, on_dup=on_dup)
|
||||
|
||||
|
||||
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]: ...
|
||||
|
||||
|
||||
# * Code review nav *
|
||||
#==============================================================================
|
||||
# ← Prev: _frozenbidict.py Current: _bidict.py Next: _orderedbase.py →
|
||||
#==============================================================================
|
59
venv/lib/python3.11/site-packages/bidict/_dup.py
Normal file
59
venv/lib/python3.11/site-packages/bidict/_dup.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
# Copyright 2009-2022 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/.
|
||||
|
||||
|
||||
"""Provide :class:`OnDup` and related functionality."""
|
||||
|
||||
|
||||
from __future__ import annotations
|
||||
from enum import Enum
|
||||
import typing as t
|
||||
|
||||
|
||||
class OD(Enum):
|
||||
"""An action to take to prevent duplication from occurring."""
|
||||
|
||||
#: Raise a :class:`~bidict.DuplicationError`.
|
||||
RAISE = 'RAISE'
|
||||
#: Overwrite existing items with new items.
|
||||
DROP_OLD = 'DROP_OLD'
|
||||
#: Keep existing items and drop new items.
|
||||
DROP_NEW = 'DROP_NEW'
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'{self.__class__.__name__}.{self.name}'
|
||||
|
||||
|
||||
RAISE: t.Final[OD] = OD.RAISE
|
||||
DROP_OLD: t.Final[OD] = OD.DROP_OLD
|
||||
DROP_NEW: t.Final[OD] = OD.DROP_NEW
|
||||
|
||||
|
||||
class OnDup(t.NamedTuple('_OnDup', [('key', OD), ('val', OD), ('kv', OD)])):
|
||||
r"""A 3-tuple of :class:`OD`\s specifying how to handle the 3 kinds of duplication.
|
||||
|
||||
*See also* :ref:`basic-usage:Values Must Be Unique`
|
||||
(https://bidict.rtfd.io/basic-usage.html#values-must-be-unique)
|
||||
|
||||
If *kv* is not specified, *val* will be used for *kv*.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __new__(cls, key: OD = DROP_OLD, val: OD = RAISE, kv: OD | None = None) -> OnDup:
|
||||
"""Override to provide user-friendly default values."""
|
||||
return super().__new__(cls, key, val, kv or val)
|
||||
|
||||
|
||||
#: Default :class:`OnDup` used for the
|
||||
#: :meth:`~bidict.bidict.__init__`,
|
||||
#: :meth:`~bidict.bidict.__setitem__`, and
|
||||
#: :meth:`~bidict.bidict.update` methods.
|
||||
ON_DUP_DEFAULT: t.Final[OnDup] = OnDup(key=DROP_OLD, val=RAISE, kv=RAISE)
|
||||
#: An :class:`OnDup` whose members are all :obj:`RAISE`.
|
||||
ON_DUP_RAISE: t.Final[OnDup] = OnDup(key=RAISE, val=RAISE, kv=RAISE)
|
||||
#: An :class:`OnDup` whose members are all :obj:`DROP_OLD`.
|
||||
ON_DUP_DROP_OLD: t.Final[OnDup] = OnDup(key=DROP_OLD, val=DROP_OLD, kv=DROP_OLD)
|
36
venv/lib/python3.11/site-packages/bidict/_exc.py
Normal file
36
venv/lib/python3.11/site-packages/bidict/_exc.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
# Copyright 2009-2022 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/.
|
||||
|
||||
|
||||
"""Provide all bidict exceptions."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class BidictException(Exception):
|
||||
"""Base class for bidict exceptions."""
|
||||
|
||||
|
||||
class DuplicationError(BidictException):
|
||||
"""Base class for exceptions raised when uniqueness is violated
|
||||
as per the :attr:~bidict.RAISE` :class:`~bidict.OnDupAction`.
|
||||
"""
|
||||
|
||||
|
||||
class KeyDuplicationError(DuplicationError):
|
||||
"""Raised when a given key is not unique."""
|
||||
|
||||
|
||||
class ValueDuplicationError(DuplicationError):
|
||||
"""Raised when a given value is not unique."""
|
||||
|
||||
|
||||
class KeyAndValueDuplicationError(KeyDuplicationError, ValueDuplicationError):
|
||||
"""Raised when a given item's key and value are not unique.
|
||||
|
||||
That is, its key duplicates that of another item,
|
||||
and its value duplicates that of a different other item.
|
||||
"""
|
46
venv/lib/python3.11/site-packages/bidict/_frozenbidict.py
Normal file
46
venv/lib/python3.11/site-packages/bidict/_frozenbidict.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
# Copyright 2009-2022 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: _base.py Current: _frozenbidict.py Next: _bidict.py →
|
||||
#==============================================================================
|
||||
|
||||
"""Provide :class:`frozenbidict`, an immutable, hashable bidirectional mapping type."""
|
||||
|
||||
from __future__ import annotations
|
||||
import typing as t
|
||||
|
||||
from ._base import BidictBase
|
||||
from ._typing import KT, VT
|
||||
|
||||
|
||||
class frozenbidict(BidictBase[KT, VT]):
|
||||
"""Immutable, hashable bidict type."""
|
||||
|
||||
_hash: int
|
||||
|
||||
# Work around lack of support for higher-kinded types in Python.
|
||||
# Ref: https://github.com/python/typing/issues/548#issuecomment-621571821
|
||||
if t.TYPE_CHECKING:
|
||||
@property
|
||||
def inverse(self) -> frozenbidict[VT, KT]: ...
|
||||
|
||||
def __hash__(self) -> int:
|
||||
"""The hash of this bidict as determined by its items."""
|
||||
if getattr(self, '_hash', None) is None:
|
||||
# The following is like hash(frozenset(self.items()))
|
||||
# but more memory efficient. See also: https://bugs.python.org/issue46684
|
||||
self._hash = t.ItemsView(self)._hash()
|
||||
return self._hash
|
||||
|
||||
|
||||
# * Code review nav *
|
||||
#==============================================================================
|
||||
# ← Prev: _base.py Current: _frozenbidict.py Next: _bidict.py →
|
||||
#==============================================================================
|
50
venv/lib/python3.11/site-packages/bidict/_frozenordered.py
Normal file
50
venv/lib/python3.11/site-packages/bidict/_frozenordered.py
Normal file
|
@ -0,0 +1,50 @@
|
|||
# Copyright 2009-2022 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: _orderedbase.py Current: _frozenordered.py Next: _orderedbidict.py →
|
||||
#==============================================================================
|
||||
|
||||
"""Provide :class:`FrozenOrderedBidict`, an immutable, hashable, ordered bidict."""
|
||||
|
||||
from __future__ import annotations
|
||||
import typing as t
|
||||
|
||||
from ._frozenbidict import frozenbidict
|
||||
from ._orderedbase import OrderedBidictBase
|
||||
from ._typing import KT, VT
|
||||
|
||||
|
||||
class FrozenOrderedBidict(OrderedBidictBase[KT, VT]):
|
||||
"""Hashable, immutable, ordered bidict type.
|
||||
|
||||
Like a hashable :class:`bidict.OrderedBidict`
|
||||
without the mutating APIs, or like a
|
||||
reversible :class:`bidict.frozenbidict` even on Python < 3.8.
|
||||
(All bidicts are order-preserving when never mutated, so frozenbidict is
|
||||
already order-preserving, but only on Python 3.8+, where dicts are
|
||||
reversible, are all bidicts (including frozenbidict) also reversible.)
|
||||
|
||||
If you are using Python 3.8+, frozenbidict gives you everything that
|
||||
FrozenOrderedBidict gives you, but with less space overhead.
|
||||
On the other hand, using FrozenOrderedBidict when you are depending on
|
||||
the ordering of the items can make the ordering dependence more explicit.
|
||||
"""
|
||||
|
||||
__hash__: t.Callable[[t.Any], int] = frozenbidict.__hash__
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
@property
|
||||
def inverse(self) -> FrozenOrderedBidict[VT, KT]: ...
|
||||
|
||||
|
||||
# * Code review nav *
|
||||
#==============================================================================
|
||||
#← Prev: _orderedbase.py Current: _frozenordered.py Next: _orderedbidict.py →
|
||||
#==============================================================================
|
46
venv/lib/python3.11/site-packages/bidict/_iter.py
Normal file
46
venv/lib/python3.11/site-packages/bidict/_iter.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
# Copyright 2009-2022 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/.
|
||||
|
||||
|
||||
"""Functions for iterating over items in a mapping."""
|
||||
|
||||
from __future__ import annotations
|
||||
from operator import itemgetter
|
||||
import typing as t
|
||||
|
||||
from ._typing import KT, VT, ItemsIter, MapOrItems
|
||||
|
||||
|
||||
def iteritems_mapping_or_iterable(arg: MapOrItems[KT, VT]) -> ItemsIter[KT, VT]:
|
||||
"""Yield the items in *arg* based on whether it's a mapping."""
|
||||
yield from arg.items() if isinstance(arg, t.Mapping) else arg
|
||||
|
||||
|
||||
def iteritems(__arg: MapOrItems[KT, VT], **kw: VT) -> ItemsIter[KT, VT]:
|
||||
"""Yield the items from *arg* and then any from *kw* in the order given."""
|
||||
yield from iteritems_mapping_or_iterable(__arg)
|
||||
yield from kw.items() # type: ignore [misc]
|
||||
|
||||
|
||||
swap = itemgetter(1, 0)
|
||||
|
||||
|
||||
def inverted(arg: MapOrItems[KT, VT]) -> ItemsIter[VT, KT]:
|
||||
"""Yield the inverse items of the provided object.
|
||||
|
||||
If *arg* has a :func:`callable` ``__inverted__`` attribute,
|
||||
return the result of calling it.
|
||||
|
||||
Otherwise, return an iterator over the items in `arg`,
|
||||
inverting each item on the fly.
|
||||
|
||||
*See also* :attr:`bidict.BidirectionalMapping.__inverted__`
|
||||
"""
|
||||
invattr = getattr(arg, '__inverted__', None)
|
||||
if callable(invattr):
|
||||
inv: ItemsIter[VT, KT] = invattr()
|
||||
return inv
|
||||
return map(swap, iteritems_mapping_or_iterable(arg))
|
97
venv/lib/python3.11/site-packages/bidict/_named.py
Normal file
97
venv/lib/python3.11/site-packages/bidict/_named.py
Normal file
|
@ -0,0 +1,97 @@
|
|||
# Copyright 2009-2022 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/.
|
||||
|
||||
"""Provide :func:`bidict.namedbidict`."""
|
||||
|
||||
from __future__ import annotations
|
||||
from sys import _getframe
|
||||
import typing as t
|
||||
|
||||
from ._base import BidictBase
|
||||
from ._bidict import bidict
|
||||
from ._typing import KT, VT
|
||||
|
||||
|
||||
class NamedBidictBase:
|
||||
"""Base class that namedbidicts derive from."""
|
||||
|
||||
|
||||
def namedbidict(
|
||||
typename: str,
|
||||
keyname: str,
|
||||
valname: str,
|
||||
*,
|
||||
base_type: t.Type[BidictBase[KT, VT]] = bidict,
|
||||
) -> t.Type[BidictBase[KT, VT]]:
|
||||
r"""Create a new subclass of *base_type* with custom accessors.
|
||||
|
||||
Like :func:`collections.namedtuple` for bidicts.
|
||||
|
||||
The new class's ``__name__`` and ``__qualname__`` will be set to *typename*,
|
||||
and its ``__module__`` will be set to the caller's module.
|
||||
|
||||
Instances of the new class will provide access to their
|
||||
:attr:`inverse <BidirectionalMapping.inverse>` instances
|
||||
via the custom *keyname*\_for property,
|
||||
and access to themselves
|
||||
via the custom *valname*\_for property.
|
||||
|
||||
*See also* the :ref:`namedbidict usage documentation
|
||||
<other-bidict-types:\:func\:\`~bidict.namedbidict\`>`
|
||||
(https://bidict.rtfd.io/other-bidict-types.html#namedbidict)
|
||||
|
||||
:raises ValueError: if any of the *typename*, *keyname*, or *valname*
|
||||
strings is not a valid Python identifier, or if *keyname == valname*.
|
||||
|
||||
:raises TypeError: if *base_type* is not a :class:`bidict.BidictBase` subclass.
|
||||
Any of the concrete bidict types pictured in the
|
||||
:ref:`other-bidict-types:Bidict Types Diagram` may be provided
|
||||
(https://bidict.rtfd.io/other-bidict-types.html#bidict-types-diagram).
|
||||
"""
|
||||
if not issubclass(base_type, BidictBase):
|
||||
raise TypeError(f'{base_type} is not a BidictBase subclass')
|
||||
names = (typename, keyname, valname)
|
||||
if not all(map(str.isidentifier, names)) or keyname == valname:
|
||||
raise ValueError(names)
|
||||
|
||||
basename = base_type.__name__
|
||||
get_keyname = property(lambda self: keyname, doc='The keyname of this namedbidict.')
|
||||
get_valname = property(lambda self: valname, doc='The valname of this namedbidict.')
|
||||
val_by_key_name = f'{valname}_for'
|
||||
key_by_val_name = f'{keyname}_for'
|
||||
val_by_key_doc = f'{typename} forward {basename}: {keyname} -> {valname}'
|
||||
key_by_val_doc = f'{typename} inverse {basename}: {valname} -> {keyname}'
|
||||
get_val_by_key = property(lambda self: self, doc=val_by_key_doc)
|
||||
get_key_by_val = property(lambda self: self.inverse, doc=key_by_val_doc)
|
||||
|
||||
class NamedBidict(base_type, NamedBidictBase): # type: ignore [valid-type,misc] # https://github.com/python/mypy/issues/5865
|
||||
"""NamedBidict."""
|
||||
|
||||
keyname = get_keyname
|
||||
valname = get_valname
|
||||
|
||||
@classmethod
|
||||
def _inv_cls_dict_diff(cls) -> dict[str, t.Any]:
|
||||
base_diff = super()._inv_cls_dict_diff()
|
||||
return {
|
||||
**base_diff,
|
||||
'keyname': get_valname,
|
||||
'valname': get_keyname,
|
||||
val_by_key_name: get_key_by_val,
|
||||
key_by_val_name: get_val_by_key,
|
||||
}
|
||||
|
||||
NamedInv = NamedBidict._inv_cls
|
||||
assert NamedInv is not NamedBidict, 'namedbidict classes are not their own inverses'
|
||||
setattr(NamedBidict, val_by_key_name, get_val_by_key)
|
||||
setattr(NamedBidict, key_by_val_name, get_key_by_val)
|
||||
NamedBidict.__name__ = NamedBidict.__qualname__ = typename
|
||||
NamedInv.__name__ = NamedInv.__qualname__ = f'{typename}Inv'
|
||||
NamedBidict.__doc__ = f'NamedBidict({basename}) {typename!r}: {keyname} -> {valname}'
|
||||
NamedInv.__doc__ = f'NamedBidictInv({basename}) {typename!r}: {valname} -> {keyname}'
|
||||
caller_module = _getframe(1).f_globals.get('__name__', '__main__')
|
||||
NamedBidict.__module__ = NamedInv.__module__ = caller_module
|
||||
return NamedBidict
|
238
venv/lib/python3.11/site-packages/bidict/_orderedbase.py
Normal file
238
venv/lib/python3.11/site-packages/bidict/_orderedbase.py
Normal file
|
@ -0,0 +1,238 @@
|
|||
# Copyright 2009-2022 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: _bidict.py Current: _orderedbase.py Next: _frozenordered.py →
|
||||
#==============================================================================
|
||||
|
||||
|
||||
"""Provide :class:`OrderedBidictBase`."""
|
||||
|
||||
from __future__ import annotations
|
||||
from functools import partial
|
||||
from weakref import ref as weakref
|
||||
import typing as t
|
||||
|
||||
from ._base import BidictBase, PreparedWrite
|
||||
from ._bidict import bidict
|
||||
from ._iter import iteritems
|
||||
from ._typing import KT, VT, OKT, OVT, MISSING, Items, MapOrItems
|
||||
|
||||
|
||||
IT = t.TypeVar('IT') # instance type
|
||||
AT = t.TypeVar('AT') # attr type
|
||||
|
||||
|
||||
class WeakAttr(t.Generic[IT, AT]):
|
||||
"""Descriptor to automatically manage (de)referencing the given slot as a weakref.
|
||||
|
||||
See https://docs.python.org/3/howto/descriptor.html#managed-attributes
|
||||
for an intro to using descriptors like this for managed attributes.
|
||||
"""
|
||||
|
||||
def __init__(self, *, slot: str) -> None:
|
||||
self.slot = slot
|
||||
|
||||
def __set__(self, instance: IT, value: AT) -> None:
|
||||
setattr(instance, self.slot, weakref(value))
|
||||
|
||||
def __get__(self, instance: IT, owner: t.Any) -> AT:
|
||||
return getattr(instance, self.slot)() # type: ignore [no-any-return]
|
||||
|
||||
|
||||
class Node:
|
||||
"""A node in a circular doubly-linked list
|
||||
used to encode the order of items in an ordered bidict.
|
||||
|
||||
A weak reference to the previous node is stored
|
||||
to avoid creating strong reference cycles.
|
||||
Referencing/dereferencing the weakref is handled automatically by :class:`WeakAttr`.
|
||||
"""
|
||||
|
||||
prv: WeakAttr[Node, Node] = WeakAttr(slot='_prv_weak')
|
||||
__slots__ = ('_prv_weak', 'nxt', '__weakref__')
|
||||
|
||||
def __init__(self, prv: Node, nxt: Node) -> None:
|
||||
self.prv = prv
|
||||
self.nxt = nxt
|
||||
|
||||
def unlink(self) -> None:
|
||||
"""Remove self from in between prv and nxt.
|
||||
Self's references to prv and nxt are retained so it can be relinked (see below).
|
||||
"""
|
||||
self.prv.nxt = self.nxt
|
||||
self.nxt.prv = self.prv
|
||||
|
||||
def relink(self) -> None:
|
||||
"""Restore self between prv and nxt after unlinking (see above)."""
|
||||
self.prv.nxt = self.nxt.prv = self
|
||||
|
||||
|
||||
class SentinelNode(Node):
|
||||
"""Special node in a circular doubly-linked list
|
||||
that links the first node with the last node.
|
||||
When its next and previous references point back to itself
|
||||
it represents an empty list.
|
||||
"""
|
||||
|
||||
nxt: WeakAttr['SentinelNode', Node] = WeakAttr(slot='_nxt_weak') # type: ignore [assignment]
|
||||
__slots__ = ('_nxt_weak',)
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(self, self)
|
||||
|
||||
def iternodes(self, *, reverse: bool = False) -> t.Iterator[Node]:
|
||||
"""Iterator yielding nodes in the requested order."""
|
||||
attr = 'prv' if reverse else 'nxt'
|
||||
node = getattr(self, attr)
|
||||
while node is not self:
|
||||
yield node
|
||||
node = getattr(node, attr)
|
||||
|
||||
def new_last_node(self) -> Node:
|
||||
"""Create and return a new terminal node."""
|
||||
old_last = self.prv
|
||||
new_last = Node(old_last, self)
|
||||
old_last.nxt = self.prv = new_last
|
||||
return new_last
|
||||
|
||||
|
||||
class OrderedBidictBase(BidictBase[KT, VT]):
|
||||
"""Base class implementing an ordered :class:`BidirectionalMapping`."""
|
||||
|
||||
_repr_delegate: t.ClassVar[t.Any] = list
|
||||
|
||||
_node_by_korv: bidict[t.Any, Node]
|
||||
_bykey: bool
|
||||
|
||||
@t.overload
|
||||
def __init__(self, __m: t.Mapping[KT, VT], **kw: VT) -> None: ...
|
||||
@t.overload
|
||||
def __init__(self, __i: Items[KT, VT], **kw: VT) -> None: ...
|
||||
@t.overload
|
||||
def __init__(self, **kw: VT) -> None: ...
|
||||
|
||||
def __init__(self, *args: MapOrItems[KT, VT], **kw: VT) -> None:
|
||||
"""Make a new ordered bidirectional mapping.
|
||||
The signature behaves like that of :class:`dict`.
|
||||
Items passed in are added in the order they are passed,
|
||||
respecting the :attr:`on_dup` class attribute in the process.
|
||||
|
||||
The order in which items are inserted is remembered,
|
||||
similar to :class:`collections.OrderedDict`.
|
||||
"""
|
||||
self._sntl = SentinelNode()
|
||||
self._node_by_korv = bidict()
|
||||
self._bykey = True
|
||||
super().__init__(*args, **kw)
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
@property
|
||||
def inverse(self) -> OrderedBidictBase[VT, KT]: ...
|
||||
|
||||
def _make_inverse(self) -> OrderedBidictBase[VT, KT]:
|
||||
inv = t.cast(OrderedBidictBase[VT, KT], super()._make_inverse())
|
||||
inv._sntl = self._sntl
|
||||
inv._node_by_korv = self._node_by_korv
|
||||
inv._bykey = not self._bykey
|
||||
return inv
|
||||
|
||||
def _assoc_node(self, node: Node, key: KT, val: VT) -> None:
|
||||
korv = key if self._bykey else val
|
||||
self._node_by_korv.forceput(korv, node)
|
||||
|
||||
def _dissoc_node(self, node: Node) -> None:
|
||||
del self._node_by_korv.inverse[node]
|
||||
node.unlink()
|
||||
|
||||
def _init_from(self, other: MapOrItems[KT, VT]) -> None:
|
||||
"""See :meth:`BidictBase._init_from`."""
|
||||
super()._init_from(other)
|
||||
bykey = self._bykey
|
||||
korv_by_node = self._node_by_korv.inverse
|
||||
korv_by_node.clear()
|
||||
korv_by_node_set = korv_by_node.__setitem__
|
||||
self._sntl.nxt = self._sntl.prv = self._sntl
|
||||
new_node = self._sntl.new_last_node
|
||||
for (k, v) in iteritems(other):
|
||||
korv_by_node_set(new_node(), k if bykey else v)
|
||||
|
||||
def _prep_write(self, newkey: KT, newval: VT, oldkey: OKT[KT], oldval: OVT[VT], save_unwrite: bool) -> PreparedWrite:
|
||||
"""See :meth:`bidict.BidictBase._prep_write`."""
|
||||
write, unwrite = super()._prep_write(newkey, newval, oldkey, oldval, save_unwrite)
|
||||
assoc, dissoc = self._assoc_node, self._dissoc_node
|
||||
node_by_korv, bykey = self._node_by_korv, self._bykey
|
||||
if oldval is MISSING and oldkey is MISSING: # no key or value duplication
|
||||
# {0: 1, 2: 3} + (4, 5) => {0: 1, 2: 3, 4: 5}
|
||||
newnode = self._sntl.new_last_node()
|
||||
write.append(partial(assoc, newnode, newkey, newval))
|
||||
if save_unwrite:
|
||||
unwrite.append(partial(dissoc, newnode))
|
||||
elif oldval is not MISSING and oldkey is not MISSING: # key and value duplication across two different items
|
||||
# {0: 1, 2: 3} + (0, 3) => {0: 3}
|
||||
# n1, n2 => n1 (collapse n1 and n2 into n1)
|
||||
# oldkey: 2, oldval: 1, oldnode: n2, newkey: 0, newval: 3, newnode: n1
|
||||
if bykey:
|
||||
oldnode = node_by_korv[oldkey]
|
||||
newnode = node_by_korv[newkey]
|
||||
else:
|
||||
oldnode = node_by_korv[newval]
|
||||
newnode = node_by_korv[oldval]
|
||||
write.extend((
|
||||
partial(dissoc, oldnode),
|
||||
partial(assoc, newnode, newkey, newval),
|
||||
))
|
||||
if save_unwrite:
|
||||
unwrite.extend((
|
||||
partial(assoc, newnode, newkey, oldval),
|
||||
partial(assoc, oldnode, oldkey, newval),
|
||||
partial(oldnode.relink,),
|
||||
))
|
||||
elif oldval is not MISSING: # just key duplication
|
||||
# {0: 1, 2: 3} + (2, 4) => {0: 1, 2: 4}
|
||||
# oldkey: MISSING, oldval: 3, newkey: 2, newval: 4
|
||||
node = node_by_korv[newkey if bykey else oldval]
|
||||
write.append(partial(assoc, node, newkey, newval))
|
||||
if save_unwrite:
|
||||
unwrite.append(partial(assoc, node, newkey, oldval))
|
||||
else:
|
||||
assert oldkey is not MISSING # just value duplication
|
||||
# {0: 1, 2: 3} + (4, 3) => {0: 1, 4: 3}
|
||||
# oldkey: 2, oldval: MISSING, newkey: 4, newval: 3
|
||||
node = node_by_korv[oldkey if bykey else newval]
|
||||
write.append(partial(assoc, node, newkey, newval))
|
||||
if save_unwrite:
|
||||
unwrite.append(partial(assoc, node, oldkey, newval))
|
||||
return write, unwrite
|
||||
|
||||
def __iter__(self) -> t.Iterator[KT]:
|
||||
"""Iterator over the contained keys in insertion order."""
|
||||
return self._iter(reverse=False)
|
||||
|
||||
def __reversed__(self) -> t.Iterator[KT]:
|
||||
"""Iterator over the contained keys in reverse insertion order."""
|
||||
return self._iter(reverse=True)
|
||||
|
||||
def _iter(self, *, reverse: bool = False) -> t.Iterator[KT]:
|
||||
nodes = self._sntl.iternodes(reverse=reverse)
|
||||
korv_by_node = self._node_by_korv.inverse
|
||||
if self._bykey:
|
||||
for node in nodes:
|
||||
yield korv_by_node[node]
|
||||
else:
|
||||
key_by_val = self._invm
|
||||
for node in nodes:
|
||||
val = korv_by_node[node]
|
||||
yield key_by_val[val]
|
||||
|
||||
|
||||
# * Code review nav *
|
||||
#==============================================================================
|
||||
# ← Prev: _bidict.py Current: _orderedbase.py Next: _frozenordered.py →
|
||||
#==============================================================================
|
160
venv/lib/python3.11/site-packages/bidict/_orderedbidict.py
Normal file
160
venv/lib/python3.11/site-packages/bidict/_orderedbidict.py
Normal file
|
@ -0,0 +1,160 @@
|
|||
# Copyright 2009-2022 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: _frozenordered.py Current: _orderedbidict.py <FIN>
|
||||
#==============================================================================
|
||||
|
||||
|
||||
"""Provide :class:`OrderedBidict`."""
|
||||
|
||||
from __future__ import annotations
|
||||
from collections.abc import Set
|
||||
import typing as t
|
||||
|
||||
from ._base import BidictKeysView
|
||||
from ._bidict import MutableBidict
|
||||
from ._orderedbase import OrderedBidictBase
|
||||
from ._typing import KT, VT
|
||||
|
||||
|
||||
class OrderedBidict(OrderedBidictBase[KT, VT], MutableBidict[KT, VT]):
|
||||
"""Mutable bidict type that maintains items in insertion order."""
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
@property
|
||||
def inverse(self) -> OrderedBidict[VT, KT]: ...
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Remove all items."""
|
||||
super().clear()
|
||||
self._node_by_korv.clear()
|
||||
self._sntl.nxt = self._sntl.prv = self._sntl
|
||||
|
||||
def _pop(self, key: KT) -> VT:
|
||||
val = super()._pop(key)
|
||||
node = self._node_by_korv[key if self._bykey else val]
|
||||
self._dissoc_node(node)
|
||||
return val
|
||||
|
||||
def popitem(self, last: bool = True) -> tuple[KT, VT]:
|
||||
"""*b.popitem() → (k, v)*
|
||||
|
||||
If *last* is true,
|
||||
remove and return the most recently added item as a (key, value) pair.
|
||||
Otherwise, remove and return the least recently added item.
|
||||
|
||||
:raises KeyError: if *b* is empty.
|
||||
"""
|
||||
if not self:
|
||||
raise KeyError('OrderedBidict is empty')
|
||||
node = getattr(self._sntl, 'prv' if last else 'nxt')
|
||||
korv = self._node_by_korv.inverse[node]
|
||||
if self._bykey:
|
||||
return korv, self._pop(korv)
|
||||
return self.inverse._pop(korv), korv
|
||||
|
||||
def move_to_end(self, key: KT, last: bool = True) -> None:
|
||||
"""Move the item with the given key to the end if *last* is true, else to the beginning.
|
||||
|
||||
:raises KeyError: if *key* is missing
|
||||
"""
|
||||
korv = key if self._bykey else self._fwdm[key]
|
||||
node = self._node_by_korv[korv]
|
||||
node.prv.nxt = node.nxt
|
||||
node.nxt.prv = node.prv
|
||||
sntl = self._sntl
|
||||
if last:
|
||||
lastnode = sntl.prv
|
||||
node.prv = lastnode
|
||||
node.nxt = sntl
|
||||
sntl.prv = lastnode.nxt = node
|
||||
else:
|
||||
firstnode = sntl.nxt
|
||||
node.prv = sntl
|
||||
node.nxt = firstnode
|
||||
sntl.nxt = firstnode.prv = node
|
||||
|
||||
# Override the keys() and items() implementations inherited from BidictBase,
|
||||
# which may delegate to the backing _fwdm dict, since this is a mutable ordered bidict,
|
||||
# and therefore the ordering of items can get out of sync with the backing mappings
|
||||
# after mutation. (Need not override values() because it delegates to .inverse.keys().)
|
||||
def keys(self) -> t.KeysView[KT]:
|
||||
"""A set-like object providing a view on the contained keys."""
|
||||
return _OrderedBidictKeysView(self)
|
||||
|
||||
def items(self) -> t.ItemsView[KT, VT]:
|
||||
"""A set-like object providing a view on the contained items."""
|
||||
return _OrderedBidictItemsView(self)
|
||||
|
||||
|
||||
# The following MappingView implementations use the __iter__ implementations
|
||||
# inherited from their superclass counterparts in collections.abc, so they
|
||||
# continue to yield items in the correct order even after an OrderedBidict
|
||||
# is mutated. They also provide a __reversed__ implementation, which is not
|
||||
# provided by the collections.abc superclasses.
|
||||
class _OrderedBidictKeysView(BidictKeysView[KT]):
|
||||
_mapping: OrderedBidict[KT, t.Any]
|
||||
|
||||
def __reversed__(self) -> t.Iterator[KT]:
|
||||
return reversed(self._mapping)
|
||||
|
||||
|
||||
class _OrderedBidictItemsView(t.ItemsView[KT, VT]):
|
||||
_mapping: OrderedBidict[KT, VT]
|
||||
|
||||
def __reversed__(self) -> t.Iterator[tuple[KT, VT]]:
|
||||
ob = self._mapping
|
||||
for key in reversed(ob):
|
||||
yield key, ob[key]
|
||||
|
||||
|
||||
# For better performance, make _OrderedBidictKeysView and _OrderedBidictItemsView delegate
|
||||
# to backing dicts for the methods they inherit from collections.abc.Set. (Cannot delegate
|
||||
# for __iter__ and __reversed__ since they are order-sensitive.) See also: https://bugs.python.org/issue46713
|
||||
def _override_set_methods_to_use_backing_dict(
|
||||
cls: t.Type[_OrderedBidictKeysView[KT]] | t.Type[_OrderedBidictItemsView[KT, t.Any]],
|
||||
viewname: str,
|
||||
_setmethodnames: t.Iterable[str] = (
|
||||
'__lt__', '__le__', '__gt__', '__ge__', '__eq__', '__ne__', '__sub__', '__rsub__',
|
||||
'__or__', '__ror__', '__xor__', '__rxor__', '__and__', '__rand__', 'isdisjoint',
|
||||
)
|
||||
) -> None:
|
||||
def make_proxy_method(methodname: str) -> t.Any:
|
||||
def method(self: _OrderedBidictKeysView[KT] | _OrderedBidictItemsView[KT, t.Any], *args: t.Any) -> t.Any:
|
||||
fwdm = self._mapping._fwdm
|
||||
if not isinstance(fwdm, dict): # dict view speedup not available, fall back to Set's implementation.
|
||||
return getattr(Set, methodname)(self, *args)
|
||||
fwdm_dict_view = getattr(fwdm, viewname)()
|
||||
fwdm_dict_view_method = getattr(fwdm_dict_view, methodname)
|
||||
if len(args) != 1 or not isinstance(args[0], self.__class__) or not isinstance(args[0]._mapping._fwdm, dict):
|
||||
return fwdm_dict_view_method(*args)
|
||||
# self and arg are both _OrderedBidictKeysViews or _OrderedBidictItemsViews whose bidicts are backed by a dict.
|
||||
# Use arg's backing dict's corresponding view instead of arg. Otherwise, e.g. `ob1.keys() < ob2.keys()` would give
|
||||
# "TypeError: '<' not supported between instances of '_OrderedBidictKeysView' and '_OrderedBidictKeysView'", because
|
||||
# both `dict_keys(ob1).__lt__(ob2.keys()) is NotImplemented` and `dict_keys(ob2).__gt__(ob1.keys()) is NotImplemented`.
|
||||
arg_dict = args[0]._mapping._fwdm
|
||||
arg_dict_view = getattr(arg_dict, viewname)()
|
||||
return fwdm_dict_view_method(arg_dict_view)
|
||||
method.__name__ = methodname
|
||||
method.__qualname__ = f'{cls.__qualname__}.{methodname}'
|
||||
return method
|
||||
|
||||
for name in _setmethodnames:
|
||||
setattr(cls, name, make_proxy_method(name))
|
||||
|
||||
|
||||
_override_set_methods_to_use_backing_dict(_OrderedBidictKeysView, 'keys')
|
||||
_override_set_methods_to_use_backing_dict(_OrderedBidictItemsView, 'items')
|
||||
|
||||
|
||||
# * Code review nav *
|
||||
#==============================================================================
|
||||
# ← Prev: _frozenordered.py Current: _orderedbidict.py <FIN>
|
||||
#==============================================================================
|
43
venv/lib/python3.11/site-packages/bidict/_typing.py
Normal file
43
venv/lib/python3.11/site-packages/bidict/_typing.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
# Copyright 2009-2022 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/.
|
||||
|
||||
|
||||
"""Provide typing-related objects."""
|
||||
|
||||
from __future__ import annotations
|
||||
from enum import Enum
|
||||
import typing as t
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from typing_extensions import TypeAlias as TypeAlias
|
||||
else:
|
||||
TypeAlias = 'TypeAlias'
|
||||
|
||||
|
||||
KT = t.TypeVar('KT')
|
||||
VT = t.TypeVar('VT')
|
||||
|
||||
|
||||
Items: TypeAlias = 't.Iterable[tuple[KT, VT]]'
|
||||
MapOrItems: TypeAlias = 't.Mapping[KT, VT] | Items[KT, VT]'
|
||||
ItemsIter: TypeAlias = 't.Iterator[tuple[KT, VT]]'
|
||||
|
||||
|
||||
class MissingT(Enum):
|
||||
"""Sentinel used to represent none/missing when None itself can't be used."""
|
||||
|
||||
MISSING = 'MISSING'
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return '<MISSING>'
|
||||
|
||||
|
||||
MISSING: t.Final[MissingT] = MissingT.MISSING
|
||||
OKT: TypeAlias = 'KT | MissingT' #: optional key type
|
||||
OVT: TypeAlias = 'VT | MissingT' #: optional value type
|
||||
|
||||
DT = t.TypeVar('DT') #: for default arguments
|
||||
ODT: TypeAlias = 'DT | MissingT' #: optional default arg type
|
15
venv/lib/python3.11/site-packages/bidict/metadata.py
Normal file
15
venv/lib/python3.11/site-packages/bidict/metadata.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
# Copyright 2009-2022 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/.
|
||||
|
||||
"""Define bidict package metadata."""
|
||||
|
||||
|
||||
__version__ = '0.22.1'
|
||||
__author__ = {'name': 'Joshua Bronson', 'email': 'jabronson@gmail.com'}
|
||||
__copyright__ = '© 2009-2022 Joshua Bronson'
|
||||
__description__ = 'The bidirectional mapping library for Python.'
|
||||
__license__ = 'MPL 2.0'
|
||||
__url__ = 'https://bidict.readthedocs.io'
|
1
venv/lib/python3.11/site-packages/bidict/py.typed
Normal file
1
venv/lib/python3.11/site-packages/bidict/py.typed
Normal file
|
@ -0,0 +1 @@
|
|||
PEP-561 marker.
|
Loading…
Add table
Add a link
Reference in a new issue