summaryrefslogtreecommitdiffhomepage
path: root/libs/bidict
diff options
context:
space:
mode:
authormorpheus65535 <[email protected]>2021-05-08 10:25:29 -0400
committerGitHub <[email protected]>2021-05-08 10:25:29 -0400
commit72b6ab3c6a11e1c12d86563989d88d73e4e64377 (patch)
tree3739d75ba8814a226f241828afb888826977ef78 /libs/bidict
parent09a31cf9a42f4b08ef9953fc9dc3af47bbc39217 (diff)
downloadbazarr-72b6ab3c6a11e1c12d86563989d88d73e4e64377.tar.gz
bazarr-72b6ab3c6a11e1c12d86563989d88d73e4e64377.zip
Added live update of UI using websocket. Make sure your reverse proxy upgrade the connection!
Diffstat (limited to 'libs/bidict')
-rw-r--r--libs/bidict/__init__.py111
-rw-r--r--libs/bidict/_abc.py81
-rw-r--r--libs/bidict/_base.py462
-rw-r--r--libs/bidict/_bidict.py46
-rw-r--r--libs/bidict/_delegating_mixins.py92
-rw-r--r--libs/bidict/_dup.py36
-rw-r--r--libs/bidict/_exc.py35
-rw-r--r--libs/bidict/_frozenbidict.py51
-rw-r--r--libs/bidict/_frozenordered.py65
-rw-r--r--libs/bidict/_marker.py19
-rw-r--r--libs/bidict/_miss.py14
-rw-r--r--libs/bidict/_mut.py177
-rw-r--r--libs/bidict/_named.py103
-rw-r--r--libs/bidict/_noop.py14
-rw-r--r--libs/bidict/_orderedbase.py302
-rw-r--r--libs/bidict/_orderedbidict.py87
-rw-r--r--libs/bidict/_util.py61
-rw-r--r--libs/bidict/compat.py78
-rw-r--r--libs/bidict/metadata.py49
19 files changed, 1883 insertions, 0 deletions
diff --git a/libs/bidict/__init__.py b/libs/bidict/__init__.py
new file mode 100644
index 000000000..725e18750
--- /dev/null
+++ b/libs/bidict/__init__.py
@@ -0,0 +1,111 @@
+# -*- coding: utf-8 -*-
+# Copyright 2009-2019 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 *
+#==============================================================================
+
+# Doing a code review? You'll find a "Code review nav" comment like the one
+# below at the top and bottom of the most important source files. This provides
+# a suggested initial path through the source when reviewing.
+#
+# Note: If you aren't reading this on https://github.com/jab/bidict, you may be
+# viewing an outdated version of the code. Please head to GitHub to review the
+# latest version, which contains important improvements over older versions.
+#
+# Thank you for reading and for any feedback you provide.
+
+# * Code review nav *
+#==============================================================================
+# Current: __init__.py Next: _abc.py →
+#==============================================================================
+
+
+"""
+Efficient, Pythonic bidirectional map implementation and related functionality.
+
+.. 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) 2019 Joshua Bronson.
+.. :license: MPLv2. See LICENSE for details.
+"""
+
+# This __init__.py only collects functionality implemented in the rest of the
+# source and exports it under the `bidict` module namespace (via `__all__`).
+
+from ._abc import BidirectionalMapping
+from ._base import BidictBase
+from ._mut import MutableBidict
+from ._bidict import bidict
+from ._dup import DuplicationPolicy, IGNORE, OVERWRITE, RAISE
+from ._exc import (
+ BidictException, DuplicationError,
+ KeyDuplicationError, ValueDuplicationError, KeyAndValueDuplicationError)
+from ._util import inverted
+from ._frozenbidict import frozenbidict
+from ._frozenordered import FrozenOrderedBidict
+from ._named import namedbidict
+from ._orderedbase import OrderedBidictBase
+from ._orderedbidict import OrderedBidict
+from .metadata import (
+ __author__, __maintainer__, __copyright__, __email__, __credits__, __url__,
+ __license__, __status__, __description__, __keywords__, __version__, __version_info__)
+
+
+__all__ = (
+ '__author__',
+ '__maintainer__',
+ '__copyright__',
+ '__email__',
+ '__credits__',
+ '__license__',
+ '__status__',
+ '__description__',
+ '__keywords__',
+ '__url__',
+ '__version__',
+ '__version_info__',
+ 'BidirectionalMapping',
+ 'BidictException',
+ 'DuplicationPolicy',
+ 'IGNORE',
+ 'OVERWRITE',
+ 'RAISE',
+ 'DuplicationError',
+ 'KeyDuplicationError',
+ 'ValueDuplicationError',
+ 'KeyAndValueDuplicationError',
+ 'BidictBase',
+ 'MutableBidict',
+ 'frozenbidict',
+ 'bidict',
+ 'namedbidict',
+ 'FrozenOrderedBidict',
+ 'OrderedBidictBase',
+ 'OrderedBidict',
+ 'inverted',
+)
+
+
+# * Code review nav *
+#==============================================================================
+# Current: __init__.py Next: _abc.py →
+#==============================================================================
diff --git a/libs/bidict/_abc.py b/libs/bidict/_abc.py
new file mode 100644
index 000000000..268b00480
--- /dev/null
+++ b/libs/bidict/_abc.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+# Copyright 2009-2019 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 *
+#==============================================================================
+
+# Doing a code review? You'll find a "Code review nav" comment like the one
+# below at the top and bottom of the most important source files. This provides
+# a suggested initial path through the source when reviewing.
+#
+# Note: If you aren't reading this on https://github.com/jab/bidict, you may be
+# viewing an outdated version of the code. Please head to GitHub to review the
+# latest version, which contains important improvements over older versions.
+#
+# Thank you for reading and for any feedback you provide.
+
+# * Code review nav *
+#==============================================================================
+# ← Prev: __init__.py Current: _abc.py Next: _base.py →
+#==============================================================================
+
+
+"""Provides the :class:`BidirectionalMapping` abstract base class."""
+
+from .compat import Mapping, abstractproperty, iteritems
+
+
+class BidirectionalMapping(Mapping): # pylint: disable=abstract-method,no-init
+ """Abstract base class (ABC) 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__ = ()
+
+ @abstractproperty
+ def inverse(self):
+ """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 @abstractproperty decorator prevents BidirectionalMapping subclasses from being
+ # instantiated unless they override this method. So users shouldn't be able to get to the
+ # point where they can unintentionally call this implementation of .inverse on something
+ # anyway. Could leave the method body empty, but raise NotImplementedError so it's extra
+ # clear there's no reason to call this implementation (e.g. via super() after overriding).
+ raise NotImplementedError
+
+ def __inverted__(self):
+ """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 iteritems(self.inverse)
+
+
+# * Code review nav *
+#==============================================================================
+# ← Prev: __init__.py Current: _abc.py Next: _base.py →
+#==============================================================================
diff --git a/libs/bidict/_base.py b/libs/bidict/_base.py
new file mode 100644
index 000000000..b2a852df2
--- /dev/null
+++ b/libs/bidict/_base.py
@@ -0,0 +1,462 @@
+# -*- coding: utf-8 -*-
+# Copyright 2009-2019 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 *
+#==============================================================================
+
+# Doing a code review? You'll find a "Code review nav" comment like the one
+# below at the top and bottom of the most important source files. This provides
+# a suggested initial path through the source when reviewing.
+#
+# Note: If you aren't reading this on https://github.com/jab/bidict, you may be
+# viewing an outdated version of the code. Please head to GitHub to review the
+# latest version, which contains important improvements over older versions.
+#
+# Thank you for reading and for any feedback you provide.
+
+# * Code review nav *
+#==============================================================================
+# ← Prev: _abc.py Current: _base.py Next: _delegating_mixins.py →
+#==============================================================================
+
+
+"""Provides :class:`BidictBase`."""
+
+from collections import namedtuple
+from weakref import ref
+
+from ._abc import BidirectionalMapping
+from ._dup import RAISE, OVERWRITE, IGNORE, _OnDup
+from ._exc import (
+ DuplicationError, KeyDuplicationError, ValueDuplicationError, KeyAndValueDuplicationError)
+from ._miss import _MISS
+from ._noop import _NOOP
+from ._util import _iteritems_args_kw
+from .compat import PY2, KeysView, ItemsView, Mapping, iteritems
+
+
+_DedupResult = namedtuple('_DedupResult', 'isdupkey isdupval invbyval fwdbykey')
+_WriteResult = namedtuple('_WriteResult', 'key val oldkey oldval')
+_NODUP = _DedupResult(False, False, _MISS, _MISS)
+
+
+class BidictBase(BidirectionalMapping):
+ """Base class implementing :class:`BidirectionalMapping`."""
+
+ __slots__ = ('_fwdm', '_invm', '_inv', '_invweak', '_hash') + (() if PY2 else ('__weakref__',))
+
+ #: The default :class:`DuplicationPolicy`
+ #: (in effect during e.g. :meth:`~bidict.bidict.__init__` calls)
+ #: that governs behavior when a provided item
+ #: duplicates only the key of another item.
+ #:
+ #: Defaults to :attr:`~bidict.OVERWRITE`
+ #: to match :class:`dict`'s behavior.
+ #:
+ #: *See also* :ref:`basic-usage:Values Must Be Unique`, :doc:`extending`
+ on_dup_key = OVERWRITE
+
+ #: The default :class:`DuplicationPolicy`
+ #: (in effect during e.g. :meth:`~bidict.bidict.__init__` calls)
+ #: that governs behavior when a provided item
+ #: duplicates only the value of another item.
+ #:
+ #: Defaults to :attr:`~bidict.RAISE`
+ #: to prevent unintended overwrite of another item.
+ #:
+ #: *See also* :ref:`basic-usage:Values Must Be Unique`, :doc:`extending`
+ on_dup_val = RAISE
+
+ #: The default :class:`DuplicationPolicy`
+ #: (in effect during e.g. :meth:`~bidict.bidict.__init__` calls)
+ #: that governs behavior when a provided item
+ #: duplicates the key of another item and the value of a third item.
+ #:
+ #: Defaults to ``None``, which causes the *on_dup_kv* policy to match
+ #: whatever *on_dup_val* policy is in effect.
+ #:
+ #: *See also* :ref:`basic-usage:Values Must Be Unique`, :doc:`extending`
+ on_dup_kv = None
+
+ _fwdm_cls = dict
+ _invm_cls = dict
+
+ #: The object used by :meth:`__repr__` for printing the contained items.
+ _repr_delegate = dict
+
+ def __init__(self, *args, **kw): # pylint: disable=super-init-not-called
+ """Make a new bidirectional dictionary.
+ The signature is the same as that of regular dictionaries.
+ Items passed in are added in the order they are passed,
+ respecting the current duplication policies in the process.
+
+ *See also* :attr:`on_dup_key`, :attr:`on_dup_val`, :attr:`on_dup_kv`
+ """
+ #: The backing :class:`~collections.abc.Mapping`
+ #: storing the forward mapping data (*key* → *value*).
+ self._fwdm = self._fwdm_cls()
+ #: The backing :class:`~collections.abc.Mapping`
+ #: storing the inverse mapping data (*value* → *key*).
+ self._invm = self._invm_cls()
+ self._init_inv() # lgtm [py/init-calls-subclass]
+ if args or kw:
+ self._update(True, None, *args, **kw)
+
+ def _init_inv(self):
+ # Compute the type for this bidict's inverse bidict (will be different from this
+ # bidict's type if _fwdm_cls and _invm_cls are different).
+ inv_cls = self._inv_cls()
+ # Create the inverse bidict instance via __new__, bypassing its __init__ so that its
+ # _fwdm and _invm can be assigned to this bidict's _invm and _fwdm. Store it in self._inv,
+ # which holds a strong reference to a bidict's inverse, if one is available.
+ self._inv = inv = inv_cls.__new__(inv_cls)
+ inv._fwdm = self._invm # pylint: disable=protected-access
+ inv._invm = self._fwdm # pylint: disable=protected-access
+ # Only give the inverse a weak reference to this bidict to avoid creating a reference cycle,
+ # stored in the _invweak attribute. See also the docs in
+ # :ref:`addendum:Bidict Avoids Reference Cycles`
+ inv._inv = None # pylint: disable=protected-access
+ inv._invweak = ref(self) # pylint: disable=protected-access
+ # Since this bidict has a strong reference to its inverse already, set its _invweak to None.
+ self._invweak = None
+
+ @classmethod
+ def _inv_cls(cls):
+ """The inverse of this bidict type, i.e. one with *_fwdm_cls* and *_invm_cls* swapped."""
+ if cls._fwdm_cls is cls._invm_cls:
+ return cls
+ if not getattr(cls, '_inv_cls_', None):
+ class _Inv(cls):
+ _fwdm_cls = cls._invm_cls
+ _invm_cls = cls._fwdm_cls
+ _inv_cls_ = cls
+ _Inv.__name__ = cls.__name__ + 'Inv'
+ cls._inv_cls_ = _Inv
+ return cls._inv_cls_
+
+ @property
+ def _isinv(self):
+ return self._inv is None
+
+ @property
+ def inverse(self):
+ """The inverse of this bidict.
+
+ *See also* :attr:`inv`
+ """
+ # Resolve and return a strong reference to the inverse bidict.
+ # One may be stored in self._inv already.
+ if self._inv is not None:
+ return self._inv
+ # Otherwise a weakref is stored in self._invweak. Try to get a strong ref from it.
+ inv = self._invweak()
+ if inv is not None:
+ return inv
+ # Refcount of referent must have dropped to zero, as in `bidict().inv.inv`. Init a new one.
+ self._init_inv() # Now this bidict will retain a strong ref to its inverse.
+ return self._inv
+
+ @property
+ def inv(self):
+ """Alias for :attr:`inverse`."""
+ return self.inverse
+
+ def __getstate__(self):
+ """Needed to enable pickling due to use of :attr:`__slots__` and weakrefs.
+
+ *See also* :meth:`object.__getstate__`
+ """
+ state = {}
+ for cls in self.__class__.__mro__:
+ slots = getattr(cls, '__slots__', ())
+ for slot in slots:
+ if hasattr(self, slot):
+ state[slot] = getattr(self, slot)
+ # weakrefs can't be pickled.
+ state.pop('_invweak', None) # Added back in __setstate__ via _init_inv call.
+ state.pop('__weakref__', None) # Not added back in __setstate__. Python manages this one.
+ return state
+
+ def __setstate__(self, state):
+ """Implemented because use of :attr:`__slots__` would prevent unpickling otherwise.
+
+ *See also* :meth:`object.__setstate__`
+ """
+ for slot, value in iteritems(state):
+ setattr(self, slot, value)
+ self._init_inv()
+
+ def __repr__(self):
+ """See :func:`repr`."""
+ clsname = self.__class__.__name__
+ if not self:
+ return '%s()' % clsname
+ return '%s(%r)' % (clsname, self._repr_delegate(iteritems(self)))
+
+ # The inherited Mapping.__eq__ implementation would work, but it's 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):
+ u"""*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>`.
+
+ *See also* :meth:`bidict.FrozenOrderedBidict.equals_order_sensitive`
+ """
+ if not isinstance(other, Mapping) or len(self) != len(other):
+ return False
+ selfget = self.get
+ return all(selfget(k, _MISS) == v for (k, v) in iteritems(other))
+
+ # The following methods are mutating and so are not public. But they are implemented in this
+ # non-mutable base class (rather than the mutable `bidict` subclass) because they are used here
+ # during initialization (starting with the `_update` method). (Why is this? Because `__init__`
+ # and `update` share a lot of the same behavior (inserting the provided items while respecting
+ # the active duplication policies), so it makes sense for them to share implementation too.)
+ def _pop(self, key):
+ val = self._fwdm.pop(key)
+ del self._invm[val]
+ return val
+
+ def _put(self, key, val, on_dup):
+ dedup_result = self._dedup_item(key, val, on_dup)
+ if dedup_result is not _NOOP:
+ self._write_item(key, val, dedup_result)
+
+ def _dedup_item(self, key, val, on_dup):
+ """
+ Check *key* and *val* for any duplication in self.
+
+ Handle any duplication as per the duplication policies given in *on_dup*.
+
+ (key, val) already present is construed as a no-op, not a duplication.
+
+ If duplication is found and the corresponding duplication policy is
+ :attr:`~bidict.RAISE`, raise the appropriate error.
+
+ If duplication is found and the corresponding duplication policy is
+ :attr:`~bidict.IGNORE`, return *None*.
+
+ If duplication is found and the corresponding duplication policy is
+ :attr:`~bidict.OVERWRITE`,
+ or if no duplication is found,
+ return the _DedupResult *(isdupkey, isdupval, oldkey, oldval)*.
+ """
+ fwdm = self._fwdm
+ invm = self._invm
+ oldval = fwdm.get(key, _MISS)
+ oldkey = invm.get(val, _MISS)
+ isdupkey = oldval is not _MISS
+ isdupval = oldkey is not _MISS
+ dedup_result = _DedupResult(isdupkey, isdupval, oldkey, oldval)
+ if isdupkey and isdupval:
+ if self._isdupitem(key, val, dedup_result):
+ # (key, val) duplicates an existing item -> no-op.
+ return _NOOP
+ # key and val each duplicate a different existing item.
+ if on_dup.kv is RAISE:
+ raise KeyAndValueDuplicationError(key, val)
+ elif on_dup.kv is IGNORE:
+ return _NOOP
+ assert on_dup.kv is OVERWRITE, 'invalid on_dup_kv: %r' % on_dup.kv
+ # Fall through to the return statement on the last line.
+ elif isdupkey:
+ if on_dup.key is RAISE:
+ raise KeyDuplicationError(key)
+ elif on_dup.key is IGNORE:
+ return _NOOP
+ assert on_dup.key is OVERWRITE, 'invalid on_dup.key: %r' % on_dup.key
+ # Fall through to the return statement on the last line.
+ elif isdupval:
+ if on_dup.val is RAISE:
+ raise ValueDuplicationError(val)
+ elif on_dup.val is IGNORE:
+ return _NOOP
+ assert on_dup.val is OVERWRITE, 'invalid on_dup.val: %r' % on_dup.val
+ # Fall through to the return statement on the last line.
+ # else neither isdupkey nor isdupval.
+ return dedup_result
+
+ @staticmethod
+ def _isdupitem(key, val, dedup_result):
+ isdupkey, isdupval, oldkey, oldval = dedup_result
+ isdupitem = oldkey == key
+ assert isdupitem == (oldval == val), '%r %r %r' % (key, val, dedup_result)
+ if isdupitem:
+ assert isdupkey
+ assert isdupval
+ return isdupitem
+
+ @classmethod
+ def _get_on_dup(cls, on_dup=None):
+ if on_dup is None:
+ on_dup = _OnDup(cls.on_dup_key, cls.on_dup_val, cls.on_dup_kv)
+ elif not isinstance(on_dup, _OnDup):
+ on_dup = _OnDup(*on_dup)
+ if on_dup.kv is None:
+ on_dup = on_dup._replace(kv=on_dup.val)
+ return on_dup
+
+ def _write_item(self, key, val, dedup_result):
+ isdupkey, isdupval, oldkey, oldval = dedup_result
+ fwdm = self._fwdm
+ invm = self._invm
+ fwdm[key] = val
+ invm[val] = key
+ if isdupkey:
+ del invm[oldval]
+ if isdupval:
+ del fwdm[oldkey]
+ return _WriteResult(key, val, oldkey, oldval)
+
+ def _update(self, init, on_dup, *args, **kw):
+ # args[0] may be a generator that yields many items, so process input in a single pass.
+ if not args and not kw:
+ return
+ can_skip_dup_check = not self and not kw and isinstance(args[0], BidirectionalMapping)
+ if can_skip_dup_check:
+ self._update_no_dup_check(args[0])
+ return
+ on_dup = self._get_on_dup(on_dup)
+ can_skip_rollback = init or RAISE not in on_dup
+ if can_skip_rollback:
+ self._update_no_rollback(on_dup, *args, **kw)
+ else:
+ self._update_with_rollback(on_dup, *args, **kw)
+
+ def _update_no_dup_check(self, other, _nodup=_NODUP):
+ write_item = self._write_item
+ for (key, val) in iteritems(other):
+ write_item(key, val, _nodup)
+
+ def _update_no_rollback(self, on_dup, *args, **kw):
+ put = self._put
+ for (key, val) in _iteritems_args_kw(*args, **kw):
+ put(key, val, on_dup)
+
+ def _update_with_rollback(self, on_dup, *args, **kw):
+ """Update, rolling back on failure."""
+ writelog = []
+ appendlog = writelog.append
+ dedup_item = self._dedup_item
+ write_item = self._write_item
+ for (key, val) in _iteritems_args_kw(*args, **kw):
+ try:
+ dedup_result = dedup_item(key, val, on_dup)
+ except DuplicationError:
+ undo_write = self._undo_write
+ for dedup_result, write_result in reversed(writelog):
+ undo_write(dedup_result, write_result)
+ raise
+ if dedup_result is not _NOOP:
+ write_result = write_item(key, val, dedup_result)
+ appendlog((dedup_result, write_result))
+
+ def _undo_write(self, dedup_result, write_result):
+ isdupkey, isdupval, _, _ = dedup_result
+ key, val, oldkey, oldval = write_result
+ if not isdupkey and not isdupval:
+ self._pop(key)
+ return
+ fwdm = self._fwdm
+ invm = self._invm
+ if isdupkey:
+ fwdm[key] = oldval
+ invm[oldval] = key
+ if not isdupval:
+ del invm[val]
+ if isdupval:
+ invm[val] = oldkey
+ fwdm[oldkey] = val
+ if not isdupkey:
+ del fwdm[key]
+
+ def copy(self):
+ """A shallow copy."""
+ # Could just ``return self.__class__(self)`` here instead, but the below is faster. It uses
+ # __new__ to create a copy instance while bypassing its __init__, which would result
+ # in copying this bidict's items into the copy instance one at a time. Instead, make whole
+ # copies of each of the backing mappings, and make them the backing mappings of the copy,
+ # avoiding copying items one at a time.
+ copy = self.__class__.__new__(self.__class__)
+ copy._fwdm = self._fwdm.copy() # pylint: disable=protected-access
+ copy._invm = self._invm.copy() # pylint: disable=protected-access
+ copy._init_inv() # pylint: disable=protected-access
+ return copy
+
+ def __copy__(self):
+ """Used for the copy protocol.
+
+ *See also* the :mod:`copy` module
+ """
+ return self.copy()
+
+ def __len__(self):
+ """The number of contained items."""
+ return len(self._fwdm)
+
+ def __iter__(self): # lgtm [py/inheritance/incorrect-overridden-signature]
+ """Iterator over the contained items."""
+ # No default implementation for __iter__ inherited from Mapping ->
+ # always delegate to _fwdm.
+ return iter(self._fwdm)
+
+ def __getitem__(self, key):
+ u"""*x.__getitem__(key) ⟺ x[key]*"""
+ return self._fwdm[key]
+
+ def values(self):
+ """A set-like object providing a view on the contained values.
+
+ Note that because the values of a :class:`~bidict.BidirectionalMapping`
+ are the keys of its inverse,
+ this returns a :class:`~collections.abc.KeysView`
+ rather than a :class:`~collections.abc.ValuesView`,
+ which has the advantages of constant-time containment checks
+ and supporting set operations.
+ """
+ return self.inverse.keys()
+
+ if PY2:
+ # For iterkeys and iteritems, inheriting from Mapping already provides
+ # the best default implementations so no need to define here.
+
+ def itervalues(self):
+ """An iterator over the contained values."""
+ return self.inverse.iterkeys()
+
+ def viewkeys(self): # noqa: D102; pylint: disable=missing-docstring
+ return KeysView(self)
+
+ def viewvalues(self): # noqa: D102; pylint: disable=missing-docstring
+ return self.inverse.viewkeys()
+
+ viewvalues.__doc__ = values.__doc__
+ values.__doc__ = 'A list of the contained values.'
+
+ def viewitems(self): # noqa: D102; pylint: disable=missing-docstring
+ return ItemsView(self)
+
+ # __ne__ added automatically in Python 3 when you implement __eq__, but not in Python 2.
+ def __ne__(self, other): # noqa: N802
+ u"""*x.__ne__(other) ⟺ x != other*"""
+ return not self == other # Implement __ne__ in terms of __eq__.
+
+
+# * Code review nav *
+#==============================================================================
+# ← Prev: _abc.py Current: _base.py Next: _delegating_mixins.py →
+#==============================================================================
diff --git a/libs/bidict/_bidict.py b/libs/bidict/_bidict.py
new file mode 100644
index 000000000..9082775b6
--- /dev/null
+++ b/libs/bidict/_bidict.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+# Copyright 2009-2019 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 *
+#==============================================================================
+
+# Doing a code review? You'll find a "Code review nav" comment like the one
+# below at the top and bottom of the most important source files. This provides
+# a suggested initial path through the source when reviewing.
+#
+# Note: If you aren't reading this on https://github.com/jab/bidict, you may be
+# viewing an outdated version of the code. Please head to GitHub to review the
+# latest version, which contains important improvements over older versions.
+#
+# Thank you for reading and for any feedback you provide.
+
+# * Code review nav *
+#==============================================================================
+# ← Prev: _mut.py Current: _bidict.py Next: _orderedbase.py →
+#==============================================================================
+
+
+"""Provides :class:`bidict`."""
+
+from ._mut import MutableBidict
+from ._delegating_mixins import _DelegateKeysAndItemsToFwdm
+
+
+class bidict(_DelegateKeysAndItemsToFwdm, MutableBidict): # noqa: N801,E501; pylint: disable=invalid-name
+ """Base class for mutable bidirectional mappings."""
+
+ __slots__ = ()
+
+ __hash__ = None # since this class is mutable; explicit > implicit.
+
+
+# * Code review nav *
+#==============================================================================
+# ← Prev: _mut.py Current: _bidict.py Next: _orderedbase.py →
+#==============================================================================
diff --git a/libs/bidict/_delegating_mixins.py b/libs/bidict/_delegating_mixins.py
new file mode 100644
index 000000000..8772490c7
--- /dev/null
+++ b/libs/bidict/_delegating_mixins.py
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+# Copyright 2009-2019 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 *
+#==============================================================================
+
+# Doing a code review? You'll find a "Code review nav" comment like the one
+# below at the top and bottom of the most important source files. This provides
+# a suggested initial path through the source when reviewing.
+#
+# Note: If you aren't reading this on https://github.com/jab/bidict, you may be
+# viewing an outdated version of the code. Please head to GitHub to review the
+# latest version, which contains important improvements over older versions.
+#
+# Thank you for reading and for any feedback you provide.
+
+# * Code review nav *
+#==============================================================================
+# ← Prev: _base.py Current: _delegating_mixins.py Next: _frozenbidict.py →
+#==============================================================================
+
+
+r"""Provides mixin classes that delegate to ``self._fwdm`` for various operations.
+
+This allows methods such as :meth:`bidict.bidict.items`
+to be implemented in terms of a ``self._fwdm.items()`` call,
+which is potentially much more efficient (e.g. in CPython 2)
+compared to the implementation inherited from :class:`~collections.abc.Mapping`
+(which returns ``[(key, self[key]) for key in self]`` in Python 2).
+
+Because this depends on implementation details that aren't necessarily true
+(such as the bidict's values being the same as its ``self._fwdm.values()``,
+which is not true for e.g. ordered bidicts where ``_fwdm``\'s values are nodes),
+these should always be mixed in at a layer below a more general layer,
+as they are in e.g. :class:`~bidict.frozenbidict`
+which extends :class:`~bidict.BidictBase`.
+
+See the :ref:`extending:Sorted Bidict Recipes`
+for another example of where this comes into play.
+``SortedBidict`` extends :class:`bidict.MutableBidict`
+rather than :class:`bidict.bidict`
+to avoid inheriting these mixins,
+which are incompatible with the backing
+:class:`sortedcontainers.SortedDict`s.
+"""
+
+from .compat import PY2
+
+
+_KEYS_METHODS = ('keys',) + (('viewkeys', 'iterkeys') if PY2 else ())
+_ITEMS_METHODS = ('items',) + (('viewitems', 'iteritems') if PY2 else ())
+_DOCSTRING_BY_METHOD = {
+ 'keys': 'A set-like object providing a view on the contained keys.',
+ 'items': 'A set-like object providing a view on the contained items.',
+}
+if PY2:
+ _DOCSTRING_BY_METHOD['viewkeys'] = _DOCSTRING_BY_METHOD['keys']
+ _DOCSTRING_BY_METHOD['viewitems'] = _DOCSTRING_BY_METHOD['items']
+ _DOCSTRING_BY_METHOD['keys'] = 'A list of the contained keys.'
+ _DOCSTRING_BY_METHOD['items'] = 'A list of the contained items.'
+
+
+def _make_method(methodname):
+ def method(self):
+ return getattr(self._fwdm, methodname)() # pylint: disable=protected-access
+ method.__name__ = methodname
+ method.__doc__ = _DOCSTRING_BY_METHOD.get(methodname, '')
+ return method
+
+
+def _make_fwdm_delegating_mixin(clsname, methodnames):
+ clsdict = dict({name: _make_method(name) for name in methodnames}, __slots__=())
+ return type(clsname, (object,), clsdict)
+
+
+_DelegateKeysToFwdm = _make_fwdm_delegating_mixin('_DelegateKeysToFwdm', _KEYS_METHODS)
+_DelegateItemsToFwdm = _make_fwdm_delegating_mixin('_DelegateItemsToFwdm', _ITEMS_METHODS)
+_DelegateKeysAndItemsToFwdm = type(
+ '_DelegateKeysAndItemsToFwdm',
+ (_DelegateKeysToFwdm, _DelegateItemsToFwdm),
+ {'__slots__': ()})
+
+# * Code review nav *
+#==============================================================================
+# ← Prev: _base.py Current: _delegating_mixins.py Next: _frozenbidict.py →
+#==============================================================================
diff --git a/libs/bidict/_dup.py b/libs/bidict/_dup.py
new file mode 100644
index 000000000..4670dcc57
--- /dev/null
+++ b/libs/bidict/_dup.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+# Copyright 2009-2019 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/.
+
+
+"""Provides bidict duplication policies and the :class:`_OnDup` class."""
+
+
+from collections import namedtuple
+
+from ._marker import _Marker
+
+
+_OnDup = namedtuple('_OnDup', 'key val kv')
+
+
+class DuplicationPolicy(_Marker):
+ """Base class for bidict's duplication policies.
+
+ *See also* :ref:`basic-usage:Values Must Be Unique`
+ """
+
+ __slots__ = ()
+
+
+#: Raise an exception when a duplication is encountered.
+RAISE = DuplicationPolicy('DUP_POLICY.RAISE')
+
+#: Overwrite an existing item when a duplication is encountered.
+OVERWRITE = DuplicationPolicy('DUP_POLICY.OVERWRITE')
+
+#: Keep the existing item and ignore the new item when a duplication is encountered.
+IGNORE = DuplicationPolicy('DUP_POLICY.IGNORE')
diff --git a/libs/bidict/_exc.py b/libs/bidict/_exc.py
new file mode 100644
index 000000000..5370361e6
--- /dev/null
+++ b/libs/bidict/_exc.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+# Copyright 2009-2019 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/.
+
+
+"""Provides all bidict exceptions."""
+
+
+class BidictException(Exception):
+ """Base class for bidict exceptions."""
+
+
+class DuplicationError(BidictException):
+ """Base class for exceptions raised when uniqueness is violated
+ as per the RAISE duplication policy.
+ """
+
+
+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.
+ """
diff --git a/libs/bidict/_frozenbidict.py b/libs/bidict/_frozenbidict.py
new file mode 100644
index 000000000..07831fd91
--- /dev/null
+++ b/libs/bidict/_frozenbidict.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+# Copyright 2009-2019 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 *
+#==============================================================================
+
+# Doing a code review? You'll find a "Code review nav" comment like the one
+# below at the top and bottom of the most important source files. This provides
+# a suggested initial path through the source when reviewing.
+#
+# Note: If you aren't reading this on https://github.com/jab/bidict, you may be
+# viewing an outdated version of the code. Please head to GitHub to review the
+# latest version, which contains important improvements over older versions.
+#
+# Thank you for reading and for any feedback you provide.
+
+# * Code review nav *
+#==============================================================================
+# ← Prev: _delegating_mixins.py Current: _frozenbidict.py Next: _mut.py →
+#==============================================================================
+
+"""Provides :class:`frozenbidict`, an immutable, hashable bidirectional mapping type."""
+
+from ._base import BidictBase
+from ._delegating_mixins import _DelegateKeysAndItemsToFwdm
+from .compat import ItemsView
+
+
+class frozenbidict(_DelegateKeysAndItemsToFwdm, BidictBase): # noqa: N801,E501; pylint: disable=invalid-name
+ """Immutable, hashable bidict type."""
+
+ __slots__ = ()
+
+ def __hash__(self): # lgtm [py/equals-hash-mismatch]
+ """The hash of this bidict as determined by its items."""
+ if getattr(self, '_hash', None) is None:
+ # pylint: disable=protected-access,attribute-defined-outside-init
+ self._hash = ItemsView(self)._hash()
+ return self._hash
+
+
+# * Code review nav *
+#==============================================================================
+# ← Prev: _delegating_mixins.py Current: _frozenbidict.py Next: _mut.py →
+#==============================================================================
diff --git a/libs/bidict/_frozenordered.py b/libs/bidict/_frozenordered.py
new file mode 100644
index 000000000..25cbace3b
--- /dev/null
+++ b/libs/bidict/_frozenordered.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+# Copyright 2009-2019 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 *
+#==============================================================================
+
+# Doing a code review? You'll find a "Code review nav" comment like the one
+# below at the top and bottom of the most important source files. This provides
+# a suggested initial path through the source when reviewing.
+#
+# Note: If you aren't reading this on https://github.com/jab/bidict, you may be
+# viewing an outdated version of the code. Please head to GitHub to review the
+# latest version, which contains important improvements over older versions.
+#
+# Thank you for reading and for any feedback you provide.
+
+# * Code review nav *
+#==============================================================================
+#← Prev: _orderedbase.py Current: _frozenordered.py Next: _orderedbidict.py →
+#==============================================================================
+
+"""Provides :class:`FrozenOrderedBidict`, an immutable, hashable, ordered bidict."""
+
+from ._delegating_mixins import _DelegateKeysToFwdm
+from ._frozenbidict import frozenbidict
+from ._orderedbase import OrderedBidictBase
+from .compat import DICTS_ORDERED, PY2, izip
+
+
+# If the Python implementation's dict type is ordered (e.g. PyPy or CPython >= 3.6), then
+# `FrozenOrderedBidict` can delegate to `_fwdm` for keys: Both `_fwdm` and `_invm` will always
+# be initialized with the provided items in the correct order, and since `FrozenOrderedBidict`
+# is immutable, their respective orders can't get out of sync after a mutation. (Can't delegate
+# to `_fwdm` for items though because values in `_fwdm` are nodes.)
+_BASES = ((_DelegateKeysToFwdm,) if DICTS_ORDERED else ()) + (OrderedBidictBase,)
+_CLSDICT = dict(
+ __slots__=(),
+ # Must set __hash__ explicitly, Python prevents inheriting it.
+ # frozenbidict.__hash__ can be reused for FrozenOrderedBidict:
+ # FrozenOrderedBidict inherits BidictBase.__eq__ which is order-insensitive,
+ # and frozenbidict.__hash__ is consistent with BidictBase.__eq__.
+ __hash__=frozenbidict.__hash__.__func__ if PY2 else frozenbidict.__hash__,
+ __doc__='Hashable, immutable, ordered bidict type.',
+ __module__=__name__, # Otherwise unpickling fails in Python 2.
+)
+
+# When PY2 (so we provide iteritems) and DICTS_ORDERED, e.g. on PyPy, the following implementation
+# of iteritems may be more efficient than that inherited from `Mapping`. This exploits the property
+# that the keys in `_fwdm` and `_invm` are already in the right order:
+if PY2 and DICTS_ORDERED:
+ _CLSDICT['iteritems'] = lambda self: izip(self._fwdm, self._invm) # noqa: E501; pylint: disable=protected-access
+
+FrozenOrderedBidict = type('FrozenOrderedBidict', _BASES, _CLSDICT) # pylint: disable=invalid-name
+
+
+# * Code review nav *
+#==============================================================================
+#← Prev: _orderedbase.py Current: _frozenordered.py Next: _orderedbidict.py →
+#==============================================================================
diff --git a/libs/bidict/_marker.py b/libs/bidict/_marker.py
new file mode 100644
index 000000000..f2f9c57cb
--- /dev/null
+++ b/libs/bidict/_marker.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Copyright 2009-2019 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/.
+
+
+"""Provides :class:`_Marker`, an internal type for representing singletons."""
+
+from collections import namedtuple
+
+
+class _Marker(namedtuple('_Marker', 'name')):
+
+ __slots__ = ()
+
+ def __repr__(self):
+ return '<%s>' % self.name # pragma: no cover
diff --git a/libs/bidict/_miss.py b/libs/bidict/_miss.py
new file mode 100644
index 000000000..32d02c584
--- /dev/null
+++ b/libs/bidict/_miss.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+# Copyright 2009-2019 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/.
+
+
+"""Provides the :obj:`_MISS` sentinel, for internally signaling "missing/not found"."""
+
+from ._marker import _Marker
+
+
+_MISS = _Marker('MISSING')
diff --git a/libs/bidict/_mut.py b/libs/bidict/_mut.py
new file mode 100644
index 000000000..1a117c2ab
--- /dev/null
+++ b/libs/bidict/_mut.py
@@ -0,0 +1,177 @@
+# -*- coding: utf-8 -*-
+# Copyright 2009-2019 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 *
+#==============================================================================
+
+# Doing a code review? You'll find a "Code review nav" comment like the one
+# below at the top and bottom of the most important source files. This provides
+# a suggested initial path through the source when reviewing.
+#
+# Note: If you aren't reading this on https://github.com/jab/bidict, you may be
+# viewing an outdated version of the code. Please head to GitHub to review the
+# latest version, which contains important improvements over older versions.
+#
+# Thank you for reading and for any feedback you provide.
+
+# * Code review nav *
+#==============================================================================
+# ← Prev: _frozenbidict.py Current: _mut.py Next: _bidict.py →
+#==============================================================================
+
+
+"""Provides :class:`bidict`."""
+
+from ._base import BidictBase
+from ._dup import OVERWRITE, RAISE, _OnDup
+from ._miss import _MISS
+from .compat import MutableMapping
+
+
+# Extend MutableMapping explicitly because it doesn't implement __subclasshook__, as well as to
+# inherit method implementations it provides that we can reuse (namely `setdefault`).
+class MutableBidict(BidictBase, MutableMapping):
+ """Base class for mutable bidirectional mappings."""
+
+ __slots__ = ()
+
+ __hash__ = None # since this class is mutable; explicit > implicit.
+
+ _ON_DUP_OVERWRITE = _OnDup(key=OVERWRITE, val=OVERWRITE, kv=OVERWRITE)
+
+ def __delitem__(self, key):
+ u"""*x.__delitem__(y) ⟺ del x[y]*"""
+ self._pop(key)
+
+ def __setitem__(self, key, val):
+ """
+ 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 policy 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.
+ """
+ on_dup = self._get_on_dup()
+ self._put(key, val, on_dup)
+
+ def put(self, key, val, on_dup_key=RAISE, on_dup_val=RAISE, on_dup_kv=None):
+ """
+ Associate *key* with *val* with the specified duplication policies.
+
+ If *on_dup_kv* is ``None``, the *on_dup_val* policy will be used for it.
+
+ For example, if all given duplication policies are :attr:`~bidict.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`.
+ """
+ on_dup = self._get_on_dup((on_dup_key, on_dup_val, on_dup_kv))
+ self._put(key, val, on_dup)
+
+ def forceput(self, key, val):
+ """
+ Associate *key* with *val* unconditionally.
+
+ Replace any existing mappings containing key *key* or value *val*
+ as necessary to preserve uniqueness.
+ """
+ self._put(key, val, self._ON_DUP_OVERWRITE)
+
+ def clear(self):
+ """Remove all items."""
+ self._fwdm.clear()
+ self._invm.clear()
+
+ def pop(self, key, default=_MISS):
+ u"""*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 _MISS:
+ raise
+ return default
+
+ def popitem(self):
+ u"""*x.popitem() → (k, v)*
+
+ Remove and return some item as a (key, value) pair.
+
+ :raises KeyError: if *x* is empty.
+ """
+ if not self:
+ raise KeyError('mapping is empty')
+ key, val = self._fwdm.popitem()
+ del self._invm[val]
+ return key, val
+
+ def update(self, *args, **kw):
+ """Like :meth:`putall` with default duplication policies."""
+ if args or kw:
+ self._update(False, None, *args, **kw)
+
+ def forceupdate(self, *args, **kw):
+ """Like a bulk :meth:`forceput`."""
+ self._update(False, self._ON_DUP_OVERWRITE, *args, **kw)
+
+ def putall(self, items, on_dup_key=RAISE, on_dup_val=RAISE, on_dup_kv=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:
+ on_dup = self._get_on_dup((on_dup_key, on_dup_val, on_dup_kv))
+ self._update(False, on_dup, items)
+
+
+# * Code review nav *
+#==============================================================================
+# ← Prev: _frozenbidict.py Current: _mut.py Next: _bidict.py →
+#==============================================================================
diff --git a/libs/bidict/_named.py b/libs/bidict/_named.py
new file mode 100644
index 000000000..8748b98cc
--- /dev/null
+++ b/libs/bidict/_named.py
@@ -0,0 +1,103 @@
+# -*- coding: utf-8 -*-
+# Copyright 2009-2019 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/.
+
+"""Provides :func:`bidict.namedbidict`."""
+
+import re
+
+from ._abc import BidirectionalMapping
+from ._bidict import bidict
+from .compat import PY2
+
+
+_isidentifier = ( # pylint: disable=invalid-name
+ re.compile('[A-Za-z_][A-Za-z0-9_]*$').match if PY2 else str.isidentifier
+)
+
+
+def namedbidict(typename, keyname, valname, base_type=bidict):
+ r"""Create a new subclass of *base_type* with custom accessors.
+
+ Analagous to :func:`collections.namedtuple`.
+
+ The new class's ``__name__`` and ``__qualname__``
+ will be set based on *typename*.
+
+ Instances of it will provide access to their
+ :attr:`inverse <BidirectionalMapping.inverse>`\s
+ 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\`>`
+
+ :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 subclass of
+ :class:`BidirectionalMapping`.
+ (This function requires slightly more of *base_type*,
+ e.g. the availability of an ``_isinv`` attribute,
+ but all the :ref:`concrete bidict types
+ <other-bidict-types:Bidict Types Diagram>`
+ that the :mod:`bidict` module provides can be passed in.
+ Check out the code if you actually need to pass in something else.)
+ """
+ # Re the `base_type` docs above:
+ # The additional requirements (providing _isinv and __getstate__) do not belong in the
+ # BidirectionalMapping interface, and it's overkill to create additional interface(s) for this.
+ # On the other hand, it's overkill to require that base_type be a subclass of BidictBase, since
+ # that's too specific. The BidirectionalMapping check along with the docs above should suffice.
+ if not issubclass(base_type, BidirectionalMapping):
+ raise TypeError(base_type)
+ names = (typename, keyname, valname)
+ if not all(map(_isidentifier, names)) or keyname == valname:
+ raise ValueError(names)
+
+ class _Named(base_type): # pylint: disable=too-many-ancestors
+
+ __slots__ = ()
+
+ def _getfwd(self):
+ return self.inverse if self._isinv else self
+
+ def _getinv(self):
+ return self if self._isinv else self.inverse
+
+ @property
+ def _keyname(self):
+ return valname if self._isinv else keyname
+
+ @property
+ def _valname(self):
+ return keyname if self._isinv else valname
+
+ def __reduce__(self):
+ return (_make_empty, (typename, keyname, valname, base_type), self.__getstate__())
+
+ bname = base_type.__name__
+ fname = valname + '_for'
+ iname = keyname + '_for'
+ names = dict(typename=typename, bname=bname, keyname=keyname, valname=valname)
+ fdoc = u'{typename} forward {bname}: {keyname} → {valname}'.format(**names)
+ idoc = u'{typename} inverse {bname}: {valname} → {keyname}'.format(**names)
+ setattr(_Named, fname, property(_Named._getfwd, doc=fdoc)) # pylint: disable=protected-access
+ setattr(_Named, iname, property(_Named._getinv, doc=idoc)) # pylint: disable=protected-access
+
+ if not PY2:
+ _Named.__qualname__ = _Named.__qualname__[:-len(_Named.__name__)] + typename
+ _Named.__name__ = typename
+ return _Named
+
+
+def _make_empty(typename, keyname, valname, base_type):
+ """Create a named bidict with the indicated arguments and return an empty instance.
+ Used to make :func:`bidict.namedbidict` instances picklable.
+ """
+ cls = namedbidict(typename, keyname, valname, base_type=base_type)
+ return cls()
diff --git a/libs/bidict/_noop.py b/libs/bidict/_noop.py
new file mode 100644
index 000000000..b045e8d72
--- /dev/null
+++ b/libs/bidict/_noop.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+# Copyright 2009-2019 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/.
+
+
+"""Provides the :obj:`_NOOP` sentinel, for internally signaling "no-op"."""
+
+from ._marker import _Marker
+
+
+_NOOP = _Marker('NO-OP')
diff --git a/libs/bidict/_orderedbase.py b/libs/bidict/_orderedbase.py
new file mode 100644
index 000000000..aa085a2d5
--- /dev/null
+++ b/libs/bidict/_orderedbase.py
@@ -0,0 +1,302 @@
+# -*- coding: utf-8 -*-
+# Copyright 2009-2019 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 *
+#==============================================================================
+
+# Doing a code review? You'll find a "Code review nav" comment like the one
+# below at the top and bottom of the most important source files. This provides
+# a suggested initial path through the source when reviewing.
+#
+# Note: If you aren't reading this on https://github.com/jab/bidict, you may be
+# viewing an outdated version of the code. Please head to GitHub to review the
+# latest version, which contains important improvements over older versions.
+#
+# Thank you for reading and for any feedback you provide.
+
+# * Code review nav *
+#==============================================================================
+# ← Prev: _bidict.py Current: _orderedbase.py Next: _frozenordered.py →
+#==============================================================================
+
+
+"""Provides :class:`OrderedBidictBase`."""
+
+from weakref import ref
+
+from ._base import _WriteResult, BidictBase
+from ._bidict import bidict
+from ._miss import _MISS
+from .compat import Mapping, PY2, iteritems, izip
+
+
+class _Node(object): # pylint: disable=too-few-public-methods
+ """A node in a circular doubly-linked list
+ used to encode the order of items in an ordered bidict.
+
+ Only weak references to the next and previous nodes
+ are held to avoid creating strong reference cycles.
+
+ Because an ordered bidict retains two strong references
+ to each node instance (one from its backing `_fwdm` mapping
+ and one from its `_invm` mapping), a node's refcount will not
+ drop to zero (and so will not be garbage collected) as long as
+ the ordered bidict that contains it is still alive.
+ Because nodes don't have strong reference cycles,
+ once their containing bidict is freed,
+ they too are immediately freed.
+ """
+
+ __slots__ = ('_prv', '_nxt', '__weakref__')
+
+ def __init__(self, prv=None, nxt=None):
+ self._setprv(prv)
+ self._setnxt(nxt)
+
+ def __repr__(self): # pragma: no cover
+ clsname = self.__class__.__name__
+ prv = id(self.prv)
+ nxt = id(self.nxt)
+ return '%s(prv=%s, self=%s, nxt=%s)' % (clsname, prv, id(self), nxt)
+
+ def _getprv(self):
+ return self._prv() if isinstance(self._prv, ref) else self._prv
+
+ def _setprv(self, prv):
+ self._prv = prv and ref(prv)
+
+ prv = property(_getprv, _setprv)
+
+ def _getnxt(self):
+ return self._nxt() if isinstance(self._nxt, ref) else self._nxt
+
+ def _setnxt(self, nxt):
+ self._nxt = nxt and ref(nxt)
+
+ nxt = property(_getnxt, _setnxt)
+
+ def __getstate__(self):
+ """Return the instance state dictionary
+ but with weakrefs converted to strong refs
+ so that it can be pickled.
+
+ *See also* :meth:`object.__getstate__`
+ """
+ return dict(_prv=self.prv, _nxt=self.nxt)
+
+ def __setstate__(self, state):
+ """Set the instance state from *state*."""
+ self._setprv(state['_prv'])
+ self._setnxt(state['_nxt'])
+
+
+class _Sentinel(_Node): # pylint: disable=too-few-public-methods
+ """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.
+ """
+
+ __slots__ = ()
+
+ def __init__(self, prv=None, nxt=None):
+ super(_Sentinel, self).__init__(prv or self, nxt or self)
+
+ def __repr__(self): # pragma: no cover
+ return '<SENTINEL>'
+
+ def __bool__(self):
+ return False
+
+ if PY2:
+ __nonzero__ = __bool__
+
+ def __iter__(self, reverse=False):
+ """Iterator yielding nodes in the requested order,
+ i.e. traverse the linked list via :attr:`nxt`
+ (or :attr:`prv` if *reverse* is truthy)
+ until reaching a falsy (i.e. sentinel) node.
+ """
+ attr = 'prv' if reverse else 'nxt'
+ node = getattr(self, attr)
+ while node:
+ yield node
+ node = getattr(node, attr)
+
+
+class OrderedBidictBase(BidictBase):
+ """Base class implementing an ordered :class:`BidirectionalMapping`."""
+
+ __slots__ = ('_sntl',)
+
+ _fwdm_cls = bidict
+ _invm_cls = bidict
+
+ #: The object used by :meth:`__repr__` for printing the contained items.
+ _repr_delegate = list
+
+ def __init__(self, *args, **kw):
+ """Make a new ordered bidirectional mapping.
+ The signature is the same as that of regular dictionaries.
+ Items passed in are added in the order they are passed,
+ respecting this bidict type's duplication policies along the way.
+ The order in which items are inserted is remembered,
+ similar to :class:`collections.OrderedDict`.
+ """
+ self._sntl = _Sentinel()
+
+ # Like unordered bidicts, ordered bidicts also store two backing one-directional mappings
+ # `_fwdm` and `_invm`. But rather than mapping `key` to `val` and `val` to `key`
+ # (respectively), they map `key` to `nodefwd` and `val` to `nodeinv` (respectively), where
+ # `nodefwd` is `nodeinv` when `key` and `val` are associated with one another.
+
+ # To effect this difference, `_write_item` and `_undo_write` are overridden. But much of the
+ # rest of BidictBase's implementation, including BidictBase.__init__ and BidictBase._update,
+ # are inherited and are able to be reused without modification.
+ super(OrderedBidictBase, self).__init__(*args, **kw)
+
+ def _init_inv(self):
+ super(OrderedBidictBase, self)._init_inv()
+ self.inverse._sntl = self._sntl # pylint: disable=protected-access
+
+ # Can't reuse BidictBase.copy since ordered bidicts have different internal structure.
+ def copy(self):
+ """A shallow copy of this ordered bidict."""
+ # Fast copy implementation bypassing __init__. See comments in :meth:`BidictBase.copy`.
+ copy = self.__class__.__new__(self.__class__)
+ sntl = _Sentinel()
+ fwdm = self._fwdm.copy()
+ invm = self._invm.copy()
+ cur = sntl
+ nxt = sntl.nxt
+ for (key, val) in iteritems(self):
+ nxt = _Node(cur, sntl)
+ cur.nxt = fwdm[key] = invm[val] = nxt
+ cur = nxt
+ sntl.prv = nxt
+ copy._sntl = sntl # pylint: disable=protected-access
+ copy._fwdm = fwdm # pylint: disable=protected-access
+ copy._invm = invm # pylint: disable=protected-access
+ copy._init_inv() # pylint: disable=protected-access
+ return copy
+
+ def __getitem__(self, key):
+ nodefwd = self._fwdm[key]
+ val = self._invm.inverse[nodefwd]
+ return val
+
+ def _pop(self, key):
+ nodefwd = self._fwdm.pop(key)
+ val = self._invm.inverse.pop(nodefwd)
+ nodefwd.prv.nxt = nodefwd.nxt
+ nodefwd.nxt.prv = nodefwd.prv
+ return val
+
+ def _isdupitem(self, key, val, dedup_result):
+ """Return whether (key, val) duplicates an existing item."""
+ isdupkey, isdupval, nodeinv, nodefwd = dedup_result
+ isdupitem = nodeinv is nodefwd
+ if isdupitem:
+ assert isdupkey
+ assert isdupval
+ return isdupitem
+
+ def _write_item(self, key, val, dedup_result): # pylint: disable=too-many-locals
+ fwdm = self._fwdm # bidict mapping keys to nodes
+ invm = self._invm # bidict mapping vals to nodes
+ isdupkey, isdupval, nodeinv, nodefwd = dedup_result
+ if not isdupkey and not isdupval:
+ # No key or value duplication -> create and append a new node.
+ sntl = self._sntl
+ last = sntl.prv
+ node = _Node(last, sntl)
+ last.nxt = sntl.prv = fwdm[key] = invm[val] = node
+ oldkey = oldval = _MISS
+ elif isdupkey and isdupval:
+ # Key and value duplication across two different nodes.
+ assert nodefwd is not nodeinv
+ oldval = invm.inverse[nodefwd]
+ oldkey = fwdm.inverse[nodeinv]
+ assert oldkey != key
+ assert oldval != val
+ # We have to collapse nodefwd and nodeinv into a single node, i.e. drop one of them.
+ # Drop nodeinv, so that the item with the same key is the one overwritten in place.
+ nodeinv.prv.nxt = nodeinv.nxt
+ nodeinv.nxt.prv = nodeinv.prv
+ # Don't remove nodeinv's references to its neighbors since
+ # if the update fails, we'll need them to undo this write.
+ # Update fwdm and invm.
+ tmp = fwdm.pop(oldkey)
+ assert tmp is nodeinv
+ tmp = invm.pop(oldval)
+ assert tmp is nodefwd
+ fwdm[key] = invm[val] = nodefwd
+ elif isdupkey:
+ oldval = invm.inverse[nodefwd]
+ oldkey = _MISS
+ oldnodeinv = invm.pop(oldval)
+ assert oldnodeinv is nodefwd
+ invm[val] = nodefwd
+ else: # isdupval
+ oldkey = fwdm.inverse[nodeinv]
+ oldval = _MISS
+ oldnodefwd = fwdm.pop(oldkey)
+ assert oldnodefwd is nodeinv
+ fwdm[key] = nodeinv
+ return _WriteResult(key, val, oldkey, oldval)
+
+ def _undo_write(self, dedup_result, write_result): # pylint: disable=too-many-locals
+ fwdm = self._fwdm
+ invm = self._invm
+ isdupkey, isdupval, nodeinv, nodefwd = dedup_result
+ key, val, oldkey, oldval = write_result
+ if not isdupkey and not isdupval:
+ self._pop(key)
+ elif isdupkey and isdupval:
+ # Restore original items.
+ nodeinv.prv.nxt = nodeinv.nxt.prv = nodeinv
+ fwdm[oldkey] = invm[val] = nodeinv
+ invm[oldval] = fwdm[key] = nodefwd
+ elif isdupkey:
+ tmp = invm.pop(val)
+ assert tmp is nodefwd
+ invm[oldval] = nodefwd
+ assert fwdm[key] is nodefwd
+ else: # isdupval
+ tmp = fwdm.pop(key)
+ assert tmp is nodeinv
+ fwdm[oldkey] = nodeinv
+ assert invm[val] is nodeinv
+
+ def __iter__(self, reverse=False):
+ """An iterator over this bidict's items in order."""
+ fwdm_inv = self._fwdm.inverse
+ for node in self._sntl.__iter__(reverse=reverse):
+ yield fwdm_inv[node]
+
+ def __reversed__(self):
+ """An iterator over this bidict's items in reverse order."""
+ for key in self.__iter__(reverse=True):
+ yield key
+
+ def equals_order_sensitive(self, other):
+ """Order-sensitive equality check.
+
+ *See also* :ref:`eq-order-insensitive`
+ """
+ # Same short-circuit as BidictBase.__eq__. Factoring out not worth function call overhead.
+ if not isinstance(other, Mapping) or len(self) != len(other):
+ return False
+ return all(i == j for (i, j) in izip(iteritems(self), iteritems(other)))
+
+
+# * Code review nav *
+#==============================================================================
+# ← Prev: _bidict.py Current: _orderedbase.py Next: _frozenordered.py →
+#==============================================================================
diff --git a/libs/bidict/_orderedbidict.py b/libs/bidict/_orderedbidict.py
new file mode 100644
index 000000000..874954838
--- /dev/null
+++ b/libs/bidict/_orderedbidict.py
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+# Copyright 2009-2019 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 *
+#==============================================================================
+
+# Doing a code review? You'll find a "Code review nav" comment like the one
+# below at the top and bottom of the most important source files. This provides
+# a suggested initial path through the source when reviewing.
+#
+# Note: If you aren't reading this on https://github.com/jab/bidict, you may be
+# viewing an outdated version of the code. Please head to GitHub to review the
+# latest version, which contains important improvements over older versions.
+#
+# Thank you for reading and for any feedback you provide.
+
+# * Code review nav *
+#==============================================================================
+# ← Prev: _frozenordered.py Current: _orderedbidict.py <FIN>
+#==============================================================================
+
+
+"""Provides :class:`OrderedBidict`."""
+
+from ._mut import MutableBidict
+from ._orderedbase import OrderedBidictBase
+
+
+class OrderedBidict(OrderedBidictBase, MutableBidict):
+ """Mutable bidict type that maintains items in insertion order."""
+
+ __slots__ = ()
+ __hash__ = None # since this class is mutable; explicit > implicit.
+
+ def clear(self):
+ """Remove all items."""
+ self._fwdm.clear()
+ self._invm.clear()
+ self._sntl.nxt = self._sntl.prv = self._sntl
+
+ def popitem(self, last=True): # pylint: disable=arguments-differ
+ u"""*x.popitem() → (k, v)*
+
+ Remove and return the most recently added item as a (key, value) pair
+ if *last* is True, else the least recently added item.
+
+ :raises KeyError: if *x* is empty.
+ """
+ if not self:
+ raise KeyError('mapping is empty')
+ key = next((reversed if last else iter)(self))
+ val = self._pop(key)
+ return key, val
+
+ def move_to_end(self, key, last=True):
+ """Move an existing key to the beginning or end of this ordered bidict.
+
+ The item is moved to the end if *last* is True, else to the beginning.
+
+ :raises KeyError: if the key does not exist
+ """
+ node = self._fwdm[key]
+ node.prv.nxt = node.nxt
+ node.nxt.prv = node.prv
+ sntl = self._sntl
+ if last:
+ last = sntl.prv
+ node.prv = last
+ node.nxt = sntl
+ sntl.prv = last.nxt = node
+ else:
+ first = sntl.nxt
+ node.prv = sntl
+ node.nxt = first
+ sntl.nxt = first.prv = node
+
+
+# * Code review nav *
+#==============================================================================
+# ← Prev: _frozenordered.py Current: _orderedbidict.py <FIN>
+#==============================================================================
diff --git a/libs/bidict/_util.py b/libs/bidict/_util.py
new file mode 100644
index 000000000..89636e66c
--- /dev/null
+++ b/libs/bidict/_util.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+# Copyright 2009-2019 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/.
+
+
+"""Useful functions for working with bidirectional mappings and related data."""
+
+from itertools import chain, repeat
+
+from .compat import iteritems, Mapping
+
+
+_NULL_IT = repeat(None, 0) # repeat 0 times -> raise StopIteration from the start
+
+
+def _iteritems_mapping_or_iterable(arg):
+ """Yield the items in *arg*.
+
+ If *arg* is a :class:`~collections.abc.Mapping`, return an iterator over its items.
+ Otherwise return an iterator over *arg* itself.
+ """
+ return iteritems(arg) if isinstance(arg, Mapping) else iter(arg)
+
+
+def _iteritems_args_kw(*args, **kw):
+ """Yield the items from the positional argument (if given) and then any from *kw*.
+
+ :raises TypeError: if more than one positional argument is given.
+ """
+ args_len = len(args)
+ if args_len > 1:
+ raise TypeError('Expected at most 1 positional argument, got %d' % args_len)
+ itemchain = None
+ if args:
+ arg = args[0]
+ if arg:
+ itemchain = _iteritems_mapping_or_iterable(arg)
+ if kw:
+ iterkw = iteritems(kw)
+ itemchain = chain(itemchain, iterkw) if itemchain else iterkw
+ return itemchain or _NULL_IT
+
+
+def inverted(arg):
+ """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__`
+ """
+ inv = getattr(arg, '__inverted__', None)
+ if callable(inv):
+ return inv()
+ return ((val, key) for (key, val) in _iteritems_mapping_or_iterable(arg))
diff --git a/libs/bidict/compat.py b/libs/bidict/compat.py
new file mode 100644
index 000000000..dc095c920
--- /dev/null
+++ b/libs/bidict/compat.py
@@ -0,0 +1,78 @@
+# -*- coding: utf-8 -*-
+# Copyright 2009-2019 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/.
+
+
+"""Compatibility helpers."""
+
+from operator import methodcaller
+from platform import python_implementation
+from sys import version_info
+from warnings import warn
+
+
+# Use #: (before or) at the end of each line with a member we want to show up in the docs,
+# otherwise Sphinx won't include (even though we configure automodule with undoc-members).
+
+PYMAJOR, PYMINOR = version_info[:2] #:
+PY2 = PYMAJOR == 2 #:
+PYIMPL = python_implementation() #:
+CPY = PYIMPL == 'CPython' #:
+PYPY = PYIMPL == 'PyPy' #:
+DICTS_ORDERED = PYPY or (CPY and (PYMAJOR, PYMINOR) >= (3, 6)) #:
+
+# Without the following, pylint gives lots of false positives.
+# pylint: disable=invalid-name,unused-import,ungrouped-imports,no-name-in-module
+
+if PY2:
+ if PYMINOR < 7: # pragma: no cover
+ raise ImportError('Python 2.7 or 3.5+ is required.')
+ warn('Python 2 support will be dropped in a future release.')
+
+ # abstractproperty deprecated in Python 3.3 in favor of using @property with @abstractmethod.
+ # Before 3.3, this silently fails to detect when an abstract property has not been overridden.
+ from abc import abstractproperty #:
+
+ from itertools import izip #:
+
+ # In Python 3, the collections ABCs were moved into collections.abc, which does not exist in
+ # Python 2. Support for importing them directly from collections is dropped in Python 3.8.
+ import collections as collections_abc # noqa: F401 (imported but unused)
+ from collections import ( # noqa: F401 (imported but unused)
+ Mapping, MutableMapping, KeysView, ValuesView, ItemsView)
+
+ viewkeys = lambda m: m.viewkeys() if hasattr(m, 'viewkeys') else KeysView(m) #:
+ viewvalues = lambda m: m.viewvalues() if hasattr(m, 'viewvalues') else ValuesView(m) #:
+ viewitems = lambda m: m.viewitems() if hasattr(m, 'viewitems') else ItemsView(m) #:
+
+ iterkeys = lambda m: m.iterkeys() if hasattr(m, 'iterkeys') else iter(m.keys()) #:
+ itervalues = lambda m: m.itervalues() if hasattr(m, 'itervalues') else iter(m.values()) #:
+ iteritems = lambda m: m.iteritems() if hasattr(m, 'iteritems') else iter(m.items()) #:
+
+else:
+ # Assume Python 3 when not PY2, but explicitly check before showing this warning.
+ if PYMAJOR == 3 and PYMINOR < 5: # pragma: no cover
+ warn('Python 3.4 and below are not supported.')
+
+ import collections.abc as collections_abc # noqa: F401 (imported but unused)
+ from collections.abc import ( # noqa: F401 (imported but unused)
+ Mapping, MutableMapping, KeysView, ValuesView, ItemsView)
+
+ viewkeys = methodcaller('keys') #:
+ viewvalues = methodcaller('values') #:
+ viewitems = methodcaller('items') #:
+
+ def _compose(f, g):
+ return lambda x: f(g(x))
+
+ iterkeys = _compose(iter, viewkeys) #:
+ itervalues = _compose(iter, viewvalues) #:
+ iteritems = _compose(iter, viewitems) #:
+
+ from abc import abstractmethod
+ abstractproperty = _compose(property, abstractmethod) #:
+
+ izip = zip #:
diff --git a/libs/bidict/metadata.py b/libs/bidict/metadata.py
new file mode 100644
index 000000000..95ec8af78
--- /dev/null
+++ b/libs/bidict/metadata.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+# Copyright 2009-2019 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.0.0.VERSION_NOT_FOUND'
+
+# _version.py is generated by setuptools_scm (via its `write_to` param, see setup.py)
+try:
+ from ._version import version as __version__ # pylint: disable=unused-import
+except (ImportError, ValueError, SystemError): # pragma: no cover
+ try:
+ import pkg_resources
+ except ImportError:
+ pass
+ else:
+ try:
+ __version__ = pkg_resources.get_distribution('bidict').version
+ except pkg_resources.DistributionNotFound:
+ pass
+
+try:
+ __version_info__ = tuple(int(p) if i < 3 else p for (i, p) in enumerate(__version__.split('.')))
+except Exception: # noqa: E722; pragma: no cover; pylint: disable=broad-except
+ __vesion_info__ = (0, 0, 0, 'PARSE FAILURE: __version__=%s' % __version__)
+
+__author__ = u'Joshua Bronson'
+__maintainer__ = u'Joshua Bronson'
+__copyright__ = u'Copyright 2019 Joshua Bronson'
+__email__ = u'[email protected]'
+
+# See: ../docs/thanks.rst
+__credits__ = [i.strip() for i in u"""
+Joshua Bronson, Michael Arntzenius, Francis Carr, Gregory Ewing, Raymond Hettinger, Jozef Knaperek,
+Daniel Pope, Terry Reedy, David Turner, Tom Viner, Richard Sanger, Zeyi Wang
+""".split(u',')]
+
+__description__ = u'Efficient, Pythonic bidirectional map implementation and related functionality'
+__keywords__ = 'dict dictionary mapping datastructure bimap bijection bijective ' \
+ 'injective inverse reverse bidirectional two-way 2-way'
+
+__license__ = u'MPL 2.0'
+__status__ = u'Beta'
+__url__ = u'https://bidict.readthedocs.io'