././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1630855987.643365 bidict-0.21.3/0000755000175100001710000000000000000000000012373 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/.LICENSE_HEADER0000644000175100001710000000037300000000000014471 0ustar00runnerdockerCopyright 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/. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/.coveragerc0000644000175100001710000000026100000000000014513 0ustar00runnerdocker[run] branch = True source = bidict dynamic_context = test_function [report] precision = 1 exclude_lines = pragma: no cover def __repr__ @.*overload if .*TYPE_CHECKING ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/.editorconfig0000644000175100001710000000035300000000000015051 0ustar00runnerdocker# http://EditorConfig.org # top-most EditorConfig file root = true # Unix-style newlines with a newline ending every file [*] end_of_line = lf insert_final_newline = true indent_style = space indent_size = 4 [*.yml] indent_size = 2 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/.lgtm.yml0000644000175100001710000000022400000000000014135 0ustar00runnerdockerqueries: - exclude: py/missing-equals - exclude: py/conflicting-attributes - exclude: py/unused-import - exclude: py/import-and-import-from ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/.pre-commit-config.yaml0000644000175100001710000000257600000000000016666 0ustar00runnerdockerrepos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.0.1 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: mixed-line-ending - id: double-quote-string-fixer # This stopped working so comment out for now (started applying to files that didn't need it): # - id: fix-encoding-pragma - id: check-yaml - repo: https://github.com/codespell-project/codespell rev: v2.1.0 hooks: - id: codespell - repo: https://github.com/pre-commit/mirrors-mypy rev: v0.910 hooks: - id: mypy - repo: https://github.com/pycqa/pydocstyle rev: 6.1.1 hooks: - id: pydocstyle exclude: bidict/_version.py - repo: https://github.com/pycqa/flake8 rev: 3.9.2 hooks: - id: flake8 # https://pre-commit.com/#repository-local-hooks - repo: local hooks: - id: pylint name: pylint entry: pylint language: system types: [python] - repo: https://github.com/Lucas-C/pre-commit-hooks rev: v1.1.10 hooks: - id: forbid-crlf - id: remove-crlf - id: forbid-tabs - id: remove-tabs # This stopped working so comment out for now (started applying to files that didn't need it): # - id: insert-license # files: \.py$ # args: # # Renamed LICENSE_HEADER -> .LICENSE_HEADER to avoid confusing licensee # # (https://github.com/benbalter/licensee - what GitHub uses to detect license) # - --license-filepath=.LICENSE_HEADER ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/.pylintrc0000644000175100001710000000170600000000000014244 0ustar00runnerdocker# https://docs.pylint.org/en/latest/technical_reference/features.html [MASTER] jobs = 0 ignore = conf.py, run_tests.py, _version.py, [MESSAGES CONTROL] disable = abstract-method, arguments-differ, arguments-renamed, attribute-defined-outside-init, bad-continuation, broad-except, duplicate-code, import-error, invalid-name, isinstance-second-argument-not-valid-type, # https://github.com/PyCQA/pylint/issues/3507 line-too-long, missing-function-docstring, # prevents idiomatic type hints multiple-statements, # prevents idiomatic type hints no-init, no-member, no-self-use, # prevents idiomatic type hints not-callable, protected-access, redefined-builtin, signature-differs, super-init-not-called, too-few-public-methods, too-many-ancestors, too-many-branches, too-many-locals, unsubscriptable-object, # pylint bug with generic types unused-argument, wrong-import-order, wrong-import-position, ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/.readthedocs.yml0000644000175100001710000000033200000000000015457 0ustar00runnerdocker# https://docs.readthedocs.io/en/latest/config-file/ version: 2 formats: - htmlzip build: image: latest python: version: 3.8 install: - method: pip path: . extra_requirements: - docs ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/CHANGELOG.rst0000644000175100001710000010647100000000000014425 0ustar00runnerdocker.. Forward declarations for all the custom interpreted text roles that Sphinx defines and that are used below. This helps Sphinx-unaware tools (e.g. rst2html, PyPI's and GitHub's renderers, etc.). .. role:: doc .. role:: ref Changelog ========= Release Notifications --------------------- .. duplicated in README.rst (would use `.. include::` but GitHub doesn't understand it) .. image:: https://img.shields.io/badge/libraries.io-subscribe-5BC0DF.svg :target: https://libraries.io/pypi/bidict :alt: Follow on libraries.io Tip: Subscribe to releases `on GitHub `__ or `libraries.io `__ to be notified when new versions of ``bidict`` are released. 0.21.3 (2021-09-05) ------------------- - All bidicts now provide the :meth:`~bidict.BidictBase.equals_order_sensitive` method, not just :class:`bidict.OrderedBidict`\s. Since support for Python < 3.6 was dropped in v0.21.0, non-:class:`Ordered ` bidicts preserve a deterministic ordering on all supported Python versions, so all bidicts can now provide :meth:`~bidict.BidictBase.equals_order_sensitive`. - Take better advantage of the fact that dicts are reversible in Python 3.8+. This allows even non-:class:`Ordered ` bidicts to efficiently provide a :meth:`~bidict.BidictBase.__reversed__` implementation, which they now do. As a result, if you are using Python 3.8+, :class:`~bidict.frozenbidict` now gives you everything that :class:`~bidict.FrozenOrderedBidict` gives you with less space overhead. - Drop `setuptools_scm `__ as a ``setup_requires`` dependency. - Remove the ``bidict.__version_info__`` attribute. 0.21.2 (2020-09-07) ------------------- - Include `py.typed `__ file to mark :mod:`bidict` as type hinted. 0.21.1 (2020-09-07) ------------------- This release was yanked and replaced with the 0.21.2 release, which actually provides the intended changes. 0.21.0 (2020-08-22) ------------------- - :mod:`bidict` now provides `type hints `__! ⌨️ ✅ Adding type hints to :mod:`bidict` poses particularly interesting challenges due to the combination of generic types, dynamically-generated types (such as :ref:`inverse bidict classes ` and :func:`namedbidicts `), and complicating optimizations such as the use of slots and weakrefs. It didn't take long to hit bugs and missing features in the state of the art for type hinting in Python today, e.g. missing higher-kinded types support (`python/typing#548 `__), too-narrow type hints for :class:`collections.abc.Mapping` (`python/typeshed#4435 `__), a :class:`typing.Generic` bug in Python 3.6 (`BPO-41451 `__), etc. That said, this release should provide a solid foundation for code using :mod:`bidict` that enables static type checking. As always, if you spot any opportunities to improve :mod:`bidict` (including its new type hints), please don't hesitate to submit a PR! - Add :class:`bidict.MutableBidirectionalMapping` ABC. The :ref:`other-bidict-types:Bidict Types Diagram` has been updated accordingly. - Drop support for Python 3.5, which reaches end of life on 2020-09-13, represents a tiny percentage of bidict downloads on `PyPI Stats `__, and lacks support for `variable type hint syntax `__, `ordered dicts `__, and :attr:`object.__init_subclass__`. - Remove the no-longer-needed ``bidict.compat`` module. - Move :ref:`inverse bidict class access ` from a property to an attribute set in :attr:`~bidict.BidictBase.__init_subclass__`, to save function call overhead on repeated access. - :meth:`bidict.OrderedBidictBase.__iter__` no longer accepts a ``reverse`` keyword argument so that it matches the signature of :meth:`container.__iter__`. - Set the ``__module__`` attribute of various :mod:`bidict` types (using :func:`sys._getframe` when necessary) so that private, internal modules are not exposed e.g. in classes' repr strings. - :func:`~bidict.namedbidict` now immediately raises :class:`TypeError` if the provided ``base_type`` does not provide ``_isinv`` or :meth:`~object.__getstate__`, rather than succeeding with a class whose instances may raise :class:`AttributeError` when these attributes are accessed. 0.20.0 (2020-07-23) ------------------- The following breaking changes are expected to affect few if any users. Remove APIs deprecated in the previous release: - ``bidict.OVERWRITE`` and ``bidict.IGNORE``. - The ``on_dup_key``, ``on_dup_val``, and ``on_dup_kv`` arguments of :meth:`~bidict.bidict.put` and :meth:`~bidict.bidict.putall`. - The ``on_dup_key``, ``on_dup_val``, and ``on_dup_kv`` :class:`~bidict.bidict` class attributes. - Remove :meth:`bidict.BidirectionalMapping.__subclasshook__` due to lack of use and maintenance cost. Fixes a bug introduced in 0.15.0 that caused any class with an ``inverse`` attribute to be incorrectly considered a subclass of :class:`collections.abc.Mapping`. `#111 `__ 0.19.0 (2020-01-09) ------------------- - Drop support for Python 2 :ref:`as promised in v0.18.2 `. The ``bidict.compat`` module has been pruned accordingly. This makes bidict more efficient on Python 3 and enables further improvement to bidict in the future. - Deprecate ``bidict.OVERWRITE`` and ``bidict.IGNORE``. A :class:`UserWarning` will now be emitted if these are used. :attr:`bidict.DROP_OLD` and :attr:`bidict.DROP_NEW` should be used instead. - Rename ``DuplicationPolicy`` to :class:`~bidict.OnDupAction` (and implement it via an :class:`~enum.Enum`). An :class:`~bidict.OnDupAction` may be one of :attr:`~bidict.RAISE`, :attr:`~bidict.DROP_OLD`, or :attr:`~bidict.DROP_NEW`. - Expose the new :class:`~bidict.OnDup` class to contain the three :class:`~bidict.OnDupAction`\s that should be taken upon encountering the three kinds of duplication that can occur (*key*, *val*, *kv*). - Provide the :attr:`~bidict.ON_DUP_DEFAULT`, :attr:`~bidict.ON_DUP_RAISE`, and :attr:`~bidict.ON_DUP_DROP_OLD` :class:`~bidict.OnDup` convenience instances. - Deprecate the ``on_dup_key``, ``on_dup_val``, and ``on_dup_kv`` arguments of :meth:`~bidict.bidict.put` and :meth:`~bidict.bidict.putall`. A :class:`UserWarning` will now be emitted if these are used. These have been subsumed by the new *on_dup* argument, which takes an :class:`~bidict.OnDup` instance. Use it like this: ``bi.put(1, 2, OnDup(key=RAISE, val=...))``. Or pass one of the instances already provided, such as :attr:`~bidict.ON_DUP_DROP_OLD`. Or just don't pass an *on_dup* argument to use the default value of :attr:`~bidict.ON_DUP_RAISE`. The :ref:`basic-usage:Values Must Be Unique` docs have been updated accordingly. - Deprecate the ``on_dup_key``, ``on_dup_val``, and ``on_dup_kv`` :class:`~bidict.bidict` class attributes. A :class:`UserWarning` will now be emitted if these are used. These have been subsumed by the new :attr:`~bidict.bidict.on_dup` class attribute, which takes an :class:`~bidict.OnDup` instance. See the updated :doc:`extending` docs for example usage. - Improve the more efficient implementations of :meth:`~bidict.BidirectionalMapping.keys`, :meth:`~bidict.BidirectionalMapping.values`, and :meth:`~bidict.BidirectionalMapping.items`, and now also provide a more efficient implementation of :meth:`~bidict.BidirectionalMapping.__iter__` by delegating to backing :class:`dict`\s in the bidict types for which this is possible. - Move :meth:`bidict.BidictBase.values` to :meth:`bidict.BidirectionalMapping.values`, since the implementation is generic. - No longer use ``__all__`` in :mod:`bidict`'s ``__init__.py``. 0.18.4 (2020-11-02) ------------------- - Backport fix from v0.20.0 that removes :meth:`bidict.BidirectionalMapping.__subclasshook__` due to lack of use and maintenance cost. 0.18.3 (2019-09-22) ------------------- - Improve validation of names passed to :func:`~bidict.namedbidict`: Use :meth:`str.isidentifier` on Python 3, and a better regex on Python 2. - On Python 3, set :attr:`~definition.__qualname__` on :func:`~bidict.namedbidict` classes based on the provided ``typename`` argument. 0.18.2 (2019-09-08) ------------------- - Warn that Python 2 support will be dropped in a future release when Python 2 is detected. 0.18.1 (2019-09-03) ------------------- - Fix a regression introduced by the memory optimizations added in 0.15.0 which caused :func:`deepcopied ` and :func:`unpickled ` bidicts to have their inverses set incorrectly. `#94 `__ 0.18.0 (2019-02-14) ------------------- - Rename ``bidict.BidirectionalMapping.inv`` to :attr:`~bidict.BidirectionalMapping.inverse` and make :attr:`bidict.BidictBase.inv` an alias for :attr:`~bidict.BidictBase.inverse`. `#86 `__ - :meth:`bidict.BidirectionalMapping.__subclasshook__` now requires an ``inverse`` attribute rather than an ``inv`` attribute for a class to qualify as a virtual subclass. This breaking change is expected to affect few if any users. - Add Python 2/3-compatible ``bidict.compat.collections_abc`` alias. - Stop testing Python 3.4 on CI, and warn when Python 3 < 3.5 is detected rather than Python 3 < 3.3. Python 3.4 reaches `end of life `__ on 2019-03-18. As of January 2019, 3.4 represents only about 3% of bidict downloads on `PyPI Stats `__. 0.17.5 (2018-11-19) ------------------- Improvements to performance and delegation logic, with minor breaking changes to semi-private APIs. - Remove the ``__delegate__`` instance attribute added in the previous release. It was overly general and not worth the cost. Instead of checking ``self.__delegate__`` and delegating accordingly each time a possibly-delegating method is called, revert back to using "delegated-to-fwdm" mixin classes (now found in ``bidict._delegating_mixins``), and resurrect a mutable bidict parent class that omits the mixins as :class:`bidict.MutableBidict`. - Rename ``__repr_delegate__`` to :class:`~bidict.BidictBase._repr_delegate`. 0.17.4 (2018-11-14) ------------------- Minor code, interop, and (semi-)private API improvements. - :class:`~bidict.OrderedBidict` optimizations and code improvements. Use ``bidict``\s for the backing ``_fwdm`` and ``_invm`` mappings, obviating the need to store key and value data in linked list nodes. - Refactor proxied- (i.e. delegated-) to-``_fwdm`` logic for better composability and interoperability. Drop the ``_Proxied*`` mixin classes and instead move their methods into :class:`~bidict.BidictBase`, which now checks for an object defined by the ``BidictBase.__delegate__`` attribute. The ``BidictBase.__delegate__`` object will be delegated to if the method is available on it, otherwise a default implementation (e.g. inherited from :class:`~collections.abc.Mapping`) will be used otherwise. Subclasses may set ``__delegate__ = None`` to opt out. Consolidate ``_MutableBidict`` into :class:`bidict.bidict` now that the dropped mixin classes make it unnecessary. - Change ``__repr_delegate__`` to simply take a type like :class:`dict` or :class:`list`. - Upgrade to latest major `sortedcontainers `__ version (from v1 to v2) for the :ref:`extending:\`\`SortedBidict\`\` Recipes`. - ``bidict.compat.{view,iter}{keys,values,items}`` on Python 2 no longer assumes the target object implements these methods, as they're not actually part of the :class:`~collections.abc.Mapping` interface, and provides fallback implementations when the methods are unavailable. This allows the :ref:`extending:\`\`SortedBidict\`\` Recipes` to continue to work with sortedcontainers v2 on Python 2. 0.17.3 (2018-09-18) ------------------- - Improve packaging by adding a pyproject.toml and by including more supporting files in the distribution. `#81 `__ - Drop pytest-runner and support for running tests via ``python setup.py test`` in preference to ``pytest`` or ``python -m pytest``. 0.17.2 (2018-04-30) ------------------- Memory usage improvements +++++++++++++++++++++++++ - Use less memory in the linked lists that back :class:`~bidict.OrderedBidict`\s by storing node data unpacked rather than in (key, value) tuple objects. 0.17.1 (2018-04-28) ------------------- Bugfix Release ++++++++++++++ Fix a regression in 0.17.0 that could cause erroneous behavior when updating items of an :class:`~bidict.Orderedbidict`'s inverse, e.g. ``some_ordered_bidict.inv[foo] = bar``. 0.17.0 (2018-04-25) ------------------- Speedups and memory usage improvements ++++++++++++++++++++++++++++++++++++++ - Pass :meth:`~bidict.bidict.keys`, :meth:`~bidict.bidict.values`, and :meth:`~bidict.bidict.items` calls (as well as their ``iter*`` and ``view*`` counterparts on Python 2) through to the backing ``_fwdm`` and ``_invm`` dicts so that they run as fast as possible (i.e. at C speed on CPython), rather than using the slower implementations inherited from :class:`collections.abc.Mapping`. - Use weakrefs in the linked lists that back :class:`~bidict.OrderedBidict`\s to avoid creating strong reference cycles. Memory for an ordered bidict that you create can now be reclaimed in CPython as soon as you no longer hold any references to it, rather than having to wait until the next garbage collection. `#71 `__ Misc ++++ - Add ``bidict.__version_info__`` attribute to complement :attr:`bidict.__version__`. 0.16.0 (2018-04-06) ------------------- Minor code and efficiency improvements to :func:`~bidict.inverted` and :func:`~bidict._iter._iteritems_args_kw` (formerly ``bidict.pairs()``). Minor Breaking API Changes ++++++++++++++++++++++++++ The following breaking changes are expected to affect few if any users. - Rename ``bidict.pairs()`` → ``bidict._util._iteritems_args_kw``. 0.15.0 (2018-03-29) ------------------- Speedups and memory usage improvements ++++++++++++++++++++++++++++++++++++++ - Use :ref:`slots` to speed up bidict attribute access and reduce memory usage. On Python 3, instantiating a large number of bidicts now uses ~57% the amount of memory that it used before, and on Python 2 only ~33% the amount of memory that it used before, in a simple but representative `benchmark `__. - Use weakrefs to refer to a bidict's inverse internally, no longer creating a strong reference cycle. Memory for a bidict that you create can now be reclaimed in CPython as soon as you no longer hold any references to it, rather than having to wait for the next garbage collection. See the new :ref:`addendum:\`\`bidict\`\` Avoids Reference Cycles` documentation. `#24 `__ - Make :func:`bidict.BidictBase.__eq__` significantly more speed- and memory-efficient when comparing to a non-:class:`dict` :class:`~collections.abc.Mapping`. (``Mapping.__eq__()``\'s inefficient implementation will now never be used.) The implementation is now more reusable as well. - Make :func:`bidict.OrderedBidictBase.__iter__` as well as equality comparison slightly faster for ordered bidicts. Minor Bugfixes ++++++++++++++ - :func:`~bidict.namedbidict` now verifies that the provided ``keyname`` and ``valname`` are distinct, raising :class:`ValueError` if they are equal. - :func:`~bidict.namedbidict` now raises :class:`TypeError` if the provided ``base_type`` is not a :class:`~bidict.BidirectionalMapping`. - If you create a custom bidict subclass whose ``_fwdm_cls`` differs from its ``_invm_cls`` (as in the ``FwdKeySortedBidict`` example from the :ref:`extending:\`\`SortedBidict\`\` Recipes`), the inverse bidirectional mapping type (with ``_fwdm_cls`` and ``_invm_cls`` swapped) is now correctly computed and used automatically for your custom bidict's :attr:`~bidict.BidictBase.inverse` bidict. Miscellaneous +++++++++++++ - Classes no longer have to provide an ``__inverted__`` attribute to be considered virtual subclasses of :class:`~bidict.BidirectionalMapping`. - If :func:`bidict.inverted` is passed an object with an ``__inverted__`` attribute, it now ensures it is :func:`callable` before returning the result of calling it. - :func:`~bidict.BidictBase.__repr__` no longer checks for a ``__reversed__`` method to determine whether to use an ordered or unordered-style repr. It now calls the new ``__repr_delegate__`` instead (which may be overridden if needed), for better composability. Minor Breaking API Changes ++++++++++++++++++++++++++ The following breaking changes are expected to affect few if any users. - Split back out the :class:`~bidict.BidictBase` class from :class:`~bidict.frozenbidict` and :class:`~bidict.OrderedBidictBase` from :class:`~bidict.FrozenOrderedBidict`, reverting the merging of these in 0.14.0. Having e.g. ``issubclass(bidict, frozenbidict) == True`` was confusing, so this change restores ``issubclass(bidict, frozenbidict) == False``. See the updated :ref:`other-bidict-types:Bidict Types Diagram` and :ref:`other-bidict-types:Polymorphism` documentation. - Rename: - ``bidict.BidictBase.fwdm`` → ``._fwdm`` - ``bidict.BidictBase.invm`` → ``._invm`` - ``bidict.BidictBase.fwd_cls`` → ``._fwdm_cls`` - ``bidict.BidictBase.inv_cls`` → ``._invm_cls`` - ``bidict.BidictBase.isinv`` → ``._isinv`` Though overriding ``_fwdm_cls`` and ``_invm_cls`` remains supported (see :doc:`extending`), this is not a common enough use case to warrant public names. Most users do not need to know or care about any of these. - The :attr:`~bidict.RAISE`, ``OVERWRITE``, and ``IGNORE`` duplication policies are no longer available as attributes of ``DuplicationPolicy``, and can now only be accessed as attributes of the :mod:`bidict` module namespace, which was the canonical way to refer to them anyway. It is now no longer possible to create an infinite chain like ``DuplicationPolicy.RAISE.RAISE.RAISE...`` - Make ``bidict.pairs()`` and :func:`bidict.inverted` no longer importable from ``bidict.util``, and now only importable from the top-level :mod:`bidict` module. (``bidict.util`` was renamed ``bidict._util``.) - Pickling ordered bidicts now requires at least version 2 of the pickle protocol. If you are using Python 3, :attr:`pickle.DEFAULT_PROTOCOL` is 3 anyway, so this will not affect you. However if you are using in Python 2, :attr:`~pickle.DEFAULT_PROTOCOL` is 0, so you must now explicitly specify the version in your :func:`pickle.dumps` calls, e.g. ``pickle.dumps(ob, 2)``. 0.14.2 (2017-12-06) ------------------- - Make initializing (or updating an empty bidict) from only another :class:`~bidict.BidirectionalMapping` more efficient by skipping unnecessary duplication checking. - Fix accidental ignoring of specified ``base_type`` argument when (un)pickling a :func:`~bidict.namedbidict`. - Fix incorrect inversion of ``some_named_bidict.inv._for`` and ``some_named_bidict.inv._for``. - Only warn when an unsupported Python version is detected (e.g. Python < 2.7) rather than raising :class:`AssertionError`. 0.14.1 (2017-11-28) ------------------- - Fix a bug introduced in 0.14.0 where hashing a :class:`~bidict.frozenbidict`\’s inverse (e.g. ``f = frozenbidict(); {f.inv: '...'}``) would cause an ``AttributeError``. - Fix a bug introduced in 0.14.0 for Python 2 users where attempting to call ``viewitems()`` would cause a ``TypeError``. `#48 `__ 0.14.0 (2017-11-20) ------------------- - Fix a bug where :class:`~bidict.bidict`\’s default *on_dup_kv* policy was set to :attr:`~bidict.RAISE`, rather than matching whatever *on_dup_val* policy was in effect as was :ref:`documented `. - Fix a bug that could happen when using Python's optimization (``-O``) flag that could leave an ordered bidict in an inconsistent state when dealing with duplicated, overwritten keys or values. If you do not use optimizations (specifically, skipping ``assert`` statements), this would not have affected you. - Fix a bug introduced by the optimizations in 0.13.0 that could cause a frozen bidict that compared equal to another mapping to have a different hash value from the other mapping, violating Python's object model. This would only have affected you if you were inserting a frozen bidict and some other immutable mapping that it compared equal to into the same set or mapping. - Add :meth:`~bidict.OrderedBidictBase.equals_order_sensitive`. - Reduce the memory usage of ordered bidicts. - Make copying of ordered bidicts faster. - Improvements to tests and CI, including: - Test on Windows - Test with PyPy3 - Test with CPython 3.7-dev - Test with optimization flags - Require pylint to pass Breaking API Changes ++++++++++++++++++++ This release includes multiple API simplifications and improvements. - Rename: - ``orderedbidict`` → :class:`~bidict.OrderedBidict` - ``frozenorderedbidict`` → :class:`~bidict.FrozenOrderedBidict` so that these now match the case of :class:`collections.OrderedDict`. The names of the :class:`~bidict.bidict`, :func:`~bidict.namedbidict`, and :class:`~bidict.frozenbidict` classes have been retained as all-lowercase so that they continue to match the case of :class:`dict`, :func:`~collections.namedtuple`, and :class:`frozenset`, respectively. - The ``ON_DUP_VAL`` duplication policy value for *on_dup_kv* has been removed. Use ``None`` instead. - Merge :class:`~bidict.frozenbidict` and ``BidictBase`` together and remove ``BidictBase``. :class:`~bidict.frozenbidict` is now the concrete base class that all other bidict types derive from. See the updated :ref:`other-bidict-types:Bidict Types Diagram`. - Merge :class:`~bidict.frozenbidict` and ``FrozenBidictBase`` together and remove ``FrozenBidictBase``. See the updated :ref:`other-bidict-types:Bidict Types Diagram`. - Merge ``frozenorderedbidict`` and ``OrderedBidictBase`` together into a single :class:`~bidict.FrozenOrderedBidict` class and remove ``OrderedBidictBase``. :class:`~bidict.OrderedBidict` now extends :class:`~bidict.FrozenOrderedBidict` to add mutable behavior. See the updated :ref:`other-bidict-types:Bidict Types Diagram`. - Make :meth:`~bidict.OrderedBidictBase.__eq__` always perform an order-insensitive equality test, even if the other mapping is ordered. Previously, :meth:`~bidict.OrderedBidictBase.__eq__` was only order-sensitive for other ``OrderedBidictBase`` subclasses, and order-insensitive otherwise. Use the new :meth:`~bidict.OrderedBidictBase.equals_order_sensitive` method for order-sensitive equality comparison. - ``orderedbidict._should_compare_order_sensitive()`` has been removed. - ``frozenorderedbidict._HASH_NITEMS_MAX`` has been removed. Since its hash value must be computed from all contained items (so that hash results are consistent with equality comparisons against unordered mappings), the number of items that influence the hash value should not be limitable. - ``frozenbidict._USE_ITEMSVIEW_HASH`` has been removed, and ``frozenbidict.compute_hash()`` now uses ``collections.ItemsView._hash()`` to compute the hash always, not just when running on PyPy. Override ``frozenbidict.compute_hash()`` to return ``hash(frozenset(iteritems(self)))`` if you prefer the old default behavior on CPython, which takes linear rather than constant space, but which uses the ``frozenset_hash`` routine (implemented in ``setobject.c``) rather than the pure Python ``ItemsView._hash()`` routine. - ``loosebidict`` and ``looseorderedbidict`` have been removed. A simple recipe to implement equivalents yourself is now given in :doc:`extending`. - Rename ``FrozenBidictBase._compute_hash()`` → ``frozenbidict.compute_hash()``. - Rename ``DuplicationBehavior`` → ``DuplicationPolicy``. - Rename: - ``BidictBase._fwd_class`` → ``.fwd_cls`` - ``BidictBase._inv_class`` → ``.inv_cls`` - ``BidictBase._on_dup_key`` → ``on_dup_key`` - ``BidictBase._on_dup_val`` → ``on_dup_val`` - ``BidictBase._on_dup_kv`` → ``on_dup_kv`` 0.13.1 (2017-03-15) ------------------- - Fix regression introduced by the new :meth:`~bidict.BidirectionalMapping.__subclasshook__` functionality in 0.13.0 so that ``issubclass(OldStyleClass, BidirectionalMapping)`` once again works with old-style classes, returning ``False`` rather than raising :class:`AttributeError` `#41 `__ 0.13.0 (2017-01-19) ------------------- - Support Python 3.6. (Earlier versions of bidict should work fine on 3.6, but it is officially supported starting in this version.) - :class:`~bidict.BidirectionalMapping` has been refactored into an abstract base class, following the way :class:`collections.abc.Mapping` works. The concrete method implementations it used to provide have been moved into a new ``BidictBase`` subclass. :class:`~bidict.BidirectionalMapping` now also implements :meth:`~bidict.BidirectionalMapping.__subclasshook__`, so any class that provides a conforming set of attributes (enumerated in :attr:`~bidict.BidirectionalMapping._subclsattrs`) will be considered a :class:`~bidict.BidirectionalMapping` subclass automatically. - ``OrderedBidirectionalMapping`` has been renamed to ``OrderedBidictBase``, to better reflect its function. (It is not an ABC.) - A new ``FrozenBidictBase`` class has been factored out of :class:`~bidict.frozenbidict` and :class:`frozenorderedbidict `. This implements common behavior such as caching the result of ``__hash__`` after the first call. - The hash implementations of :class:`~bidict.frozenbidict` and :class:`frozenorderedbidict `. have been reworked to improve performance and flexibility. :class:`frozenorderedbidict `\’s hash implementation is now order-sensitive. See ``frozenbidict._compute_hash()`` and ``frozenorderedbidict._compute_hash`` for more documentation of the changes, including the new ``frozenbidict._USE_ITEMSVIEW_HASH`` and ``frozenorderedbidict._HASH_NITEMS_MAX`` attributes. If you have an interesting use case that requires overriding these, or suggestions for an alternative implementation, please `share your feedback `__. - Add ``_fwd_class`` and ``_inv_class`` attributes representing the backing :class:`~collections.abc.Mapping` types used internally to store the forward and inverse dictionaries, respectively. This allows creating custom bidict types with extended functionality simply by overriding these attributes in a subclass. See the new :doc:`extending` documentation for examples. - Pass any parameters passed to :meth:`~bidict.bidict.popitem` through to ``_fwd.popitem`` for greater extensibility. - More concise repr strings for empty bidicts. e.g. ``bidict()`` rather than ``bidict({})`` and ``orderedbidict()`` rather than ``orderedbidict([])``. - Add ``bidict.compat.PYPY`` and remove unused ``bidict.compat.izip_longest``. 0.12.0 (2016-07-03) ------------------- - New/renamed exceptions: - :class:`~bidict.KeyDuplicationError` - :class:`~bidict.ValueDuplicationError` - :class:`~bidict.KeyAndValueDuplicationError` - :class:`~bidict.DuplicationError` (base class for the above) - :func:`~bidict.bidict.put` now accepts ``on_dup_key``, ``on_dup_val``, and ``on_dup_kv`` keyword args which allow you to override the default policy when the key or value of a given item duplicates any existing item's. These can take the following values: - :attr:`~bidict.RAISE` - ``OVERWRITE`` - ``IGNORE`` ``on_dup_kv`` can also take ``ON_DUP_VAL``. If not provided, :func:`~bidict.bidict.put` uses the :attr:`~bidict.RAISE` policy by default. - New :func:`~bidict.bidict.putall` method provides a bulk :func:`~bidict.bidict.put` API, allowing you to override the default duplication handling policy that :func:`~bidict.bidict.update` uses. - :func:`~bidict.bidict.update` now fails clean, so if an :func:`~bidict.bidict.update` call raises a :class:`~bidict.DuplicationError`, you can now be sure that none of the given items was inserted. Previously, all of the given items that were processed before the one causing the failure would have been inserted, and no facility was provided to recover which items were inserted and which weren't, nor to revert any changes made by the failed :func:`~bidict.bidict.update` call. The new behavior makes it easier to reason about and control the effects of failed :func:`~bidict.bidict.update` calls. The new :func:`~bidict.bidict.putall` method also fails clean. Internally, this is implemented by storing a log of changes made while an update is being processed, and rolling back the changes when one of them is found to cause an error. This required reimplementing :class:`orderedbidict ` on top of two dicts and a linked list, rather than two OrderedDicts, since :class:`~collections.OrderedDict` does not expose its backing linked list. - :func:`orderedbidict.move_to_end() ` now works on Python < 3.2 as a result of the new :class:`orderedbidict ` implementation. - Add - ``bidict.compat.viewkeys`` - ``bidict.compat.viewvalues`` - ``bidict.compat.iterkeys`` - ``bidict.compat.itervalues`` - ``bidict.compat.izip`` - ``bidict.compat.izip_longest`` to complement the existing ``bidict.compat.iteritems`` and ``bidict.compat.viewitems`` compatibility helpers. - More efficient implementations of ``bidict.pairs()``, :func:`~bidict.inverted`, and :func:`~bidict.BidictBase.copy`. - Implement :func:`~bidict.BidictBase.__copy__` for use with the :mod:`copy` module. - Fix issue preventing a client class from inheriting from ``loosebidict``. `#34 `__ - Add benchmarking to tests. - Drop official support for CPython 3.3. (It may continue to work, but is no longer being tested.) Breaking API Changes ++++++++++++++++++++ - Rename ``KeyExistsException`` → :class:`~bidict.KeyDuplicationError` and ``ValueExistsException`` → :class:`~bidict.ValueDuplicationError`. - When overwriting the key of an existing value in an :class:`orderedbidict `, the position of the existing item is now preserved, overwriting the key of the existing item in place, rather than moving the item to the end. This now matches the behavior of overwriting the value of an existing key, which has always preserved the position of the existing item. (If inserting an item whose key duplicates that of one existing item and whose value duplicates that of another, the existing item whose value is duplicated is still dropped, and the existing item whose key is duplicated still gets its value overwritten in place, as before.) For example: .. code:: python >>> from bidict import orderedbidict # doctest: +SKIP >>> o = orderedbidict([(0, 1), (2, 3)]) # doctest: +SKIP >>> o.forceput(4, 1) # doctest: +SKIP previously would have resulted in: .. code:: python >>> o # doctest: +SKIP orderedbidict([(2, 3), (4, 1)]) but now results in: .. code:: python >>> o # doctest: +SKIP orderedbidict([(4, 1), (2, 3)]) 0.11.0 (2016-02-05) ------------------- - Add :class:`orderedbidict `, ``looseorderedbidict``, and :class:`frozenorderedbidict `. - Add :doc:`code-of-conduct`. - Drop official support for pypy3. (It still may work but is no longer being tested. Support may be added back once pypy3 has made more progress.) 0.10.0.post1 (2015-12-23) ------------------------- - Minor documentation fixes and improvements. 0.10.0 (2015-12-23) ------------------- - Remove several features in favor of keeping the API simpler and the code more maintainable. - In the interest of protecting data safety more proactively, by default bidict now raises an error on attempting to insert a non-unique value, rather than allowing its associated key to be silently overwritten. See discussion in `#21 `__. - New :meth:`~bidict.bidict.forceupdate` method provides a bulk :meth:`~bidict.bidict.forceput` operation. - Fix bugs in :attr:`~bidict.bidict.pop` and :attr:`~bidict.bidict.setdefault` which could leave a bidict in an inconsistent state. Breaking API Changes ++++++++++++++++++++ - Remove ``bidict.__invert__``, and with it, support for the ``~b`` syntax. Use :attr:`~bidict.BidictBase.inv` instead. `#19 `__ - Remove support for the slice syntax. Use ``b.inv[val]`` rather than ``b[:val]``. `#19 `__ - Remove ``bidict.invert``. Use :attr:`~bidict.BidictBase.inv` rather than inverting a bidict in place. `#20 `__ - Raise ``ValueExistsException`` when attempting to insert a mapping with a non-unique key. `#21 `__ - Rename ``collapsingbidict`` → ``loosebidict`` now that it suppresses ``ValueExistsException`` rather than the less general ``CollapseException``. `#21 `__ - ``CollapseException`` has been subsumed by ``ValueExistsException``. `#21 `__ - :meth:`~bidict.bidict.put` now raises ``KeyExistsException`` when attempting to insert an already-existing key, and ``ValueExistsException`` when attempting to insert an already-existing value. 0.9.0.post1 (2015-06-06) ------------------------ - Fix metadata missing in the 0.9.0rc0 release. 0.9.0rc0 (2015-05-30) --------------------- - Add this changelog, `Contributors' Guide `__, `Gitter chat room `__, and other community-oriented improvements. - Adopt Pytest. - Add property-based tests via `hypothesis `__. - Other code, tests, and docs improvements. Breaking API Changes ++++++++++++++++++++ - Move ``bidict.iteritems()`` and ``bidict.viewitems()`` to new ``bidict.compat`` module. - Move :class:`bidict.inverted` to new ``bidict.util`` module (still available from top-level :mod:`bidict` module as well). - Move ``bidict.fancy_iteritems()`` → ``bidict.util.pairs()`` (also available from top level as ``bidict.pairs()``). - Rename :func:`bidict.namedbidict`\'s ``bidict_type`` argument → ``base_type``. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/CODE_OF_CONDUCT.rst0000644000175100001710000000631700000000000015411 0ustar00runnerdockerCode of Conduct =============== Our Pledge ---------- In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. Our Standards ------------- Examples of behavior that contributes to creating a positive environment include: - Using welcoming and inclusive language - Being respectful of differing viewpoints and experiences - Gracefully accepting constructive criticism - Focusing on what is best for the community - Showing empathy towards other community members Examples of unacceptable behavior by participants include: - The use of sexualized language or imagery and unwelcome sexual attention or advances - Trolling, insulting/derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or electronic address, without explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting Our Responsibilities -------------------- Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. Scope ----- This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. Enforcement ----------- Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at . All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. Attribution ----------- This Code of Conduct is adapted from the `Contributor Covenant `__, version 1.4, available at `https://www.contributor-covenant.org/version/1/4 `__. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/CONTRIBUTING.rst0000644000175100001710000001427400000000000015044 0ustar00runnerdocker.. Forward declarations for all the custom interpreted text roles that Sphinx defines and that are used below. This helps Sphinx-unaware tools (e.g. rst2html, PyPI's and GitHub's renderers, etc.). .. role:: doc .. role:: ref Contributors' Guide =================== Bug reports, feature requests, patches, and other contributions are warmly welcomed. Contribution should be as easy and friendly as possible. Below are a few guidelines contributors should follow to facilitate the process. Getting Started --------------- - `Create a GitHub account `__ if you don't have one already. - Search through the `issue tracker `__ to see if an issue or pull request has already been created for what you're interested in. If so, feel free to add comments to it or just hit the "subscribe" button to follow progress. If not, you can `join the chat room `__ to discuss there, or go ahead and `create a new issue `__: - Clearly describe the issue giving as much relevant context as possible. - If it is a bug, include reproduction steps, all known environments in which the bug is exhibited, and ideally a failing test case. - If you would like to contribute a patch, make sure you've `created your own fork `__ and have cloned it to your computer. Making Changes -------------- - Before making changes, please (create a `virtualenv `__ and) install the extra packages required for development: ``pip install -r requirements/dev.txt`` We use `EditorConfig `__ and `pre-commit `__ to help achieve uniform style and quality standards across a diversity of development environments. pre-commit gets installed when you run the command above and ensures that various code checks are run before every commit (look in ``.pre-commit-config.yaml`` to see which hooks are run). Ensure the configured hooks are installed by running ``pre-commit install --install-hooks``. EditorConfig allows us to provide a single ``.editorconfig`` file to configure settings like indentation consistently across a variety of supported editors. See https://editorconfig.org/#download to install the plugin for your editor. - Create a topic branch off of main for your changes: ``git checkout -b main`` - Make commits of logical units. - Match the existing code style and quality standards. If you're adding a feature, include accompanying tests and documentation demonstrating its correctness and usage. - Run the tests locally with `tox `__ to make sure they pass for all supported Python versions (see ``envlist`` in ``tox.ini`` for the complete list). If you do not have all the referenced Python versions available locally, you can also push the changes on your branch to GitHub to automatically trigger a new `GitHub Actions `__ build, which should run the tests for all supported Python versions. - Create a concise but comprehensive commit message in the following style:: Include an example commit message in CONTRIBUTING guide #9999 Without this patch the CONTRIBUTING guide would contain no examples of a model commit message. This is a problem because the contributor is left to imagine what the commit message should look like and may not get it right. This patch fixes the problem by providing a concrete example. The first line is an imperative statement summarizing the changes with an issue number from the tracker. The body describes the behavior without the patch, why it's a problem, and how the patch fixes the problem. Submitting Changes ------------------ - Push your changes to a topic branch in your fork of the repository: ``git push --set-upstream origin `` - Submit a pull request providing any additional relevant details necessary. - Acknowledgment should typically be fast but please allow 1-2 weeks for a full response / code review. - The code review process often involves some back-and-forth to get everything right before merging. This is typical of quality software projects that accept patches. - All communication should be supportive and appreciative of good faith efforts to contribute, creating a welcoming and inclusive community. Expect nothing less of any project. Sponsoring ---------- .. image:: https://img.shields.io/badge/GitHub-sponsor-ff69b4 :target: https://github.com/sponsors/jab :alt: Sponsor through GitHub .. image:: https://img.shields.io/badge/Gumroad-sponsor-55a0a4.svg :target: https://gumroad.com/l/bidict :alt: Sponsor through Gumroad .. image:: https://img.shields.io/badge/PayPal-sponsor-blue.svg :target: https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=jabronson%40gmail%2ecom&lc=US&item_name=Sponsor%20bidict%20(name%20a%20fair%20price) :alt: Sponsor through PayPal .. duplicated in README.rst (would use `.. include::` but GitHub doesn't understand it) Bidict is the product of thousands of hours of my unpaid work over the 12+ years I've been maintaining it. If bidict has helped you accomplish your work, especially work you've been paid for, it's easy to `sponsor me through GitHub `__. Choose a tier and GitHub handles everything else. The sponsorship just goes on your regular GitHub bill; there's nothing extra to do. You can also sponsor me through `Gumroad `__ or `PayPal `__. Read more about `companies supporting open source developers `__. Code of Conduct --------------- All participation in this project should respect the :doc:`code-of-conduct`. [#fn-coc]_ By participating, you are expected to honor this code. .. [#fn-let-me-know] ``__ .. [#fn-coc] ``_ | ``__ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/LICENSE0000644000175100001710000004052500000000000013406 0ustar00runnerdockerMozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- 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/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/MANIFEST.in0000644000175100001710000000125200000000000014131 0ustar00runnerdocker# Tip: pip install check-manifest && check-manifest -uv include .LICENSE_HEADER include LICENSE include *.py include *.rst include *.sh include *.yaml include *.yml include .coveragerc include .editorconfig include .pre-commit-config.yaml include .pylintrc include mypy.ini include pyproject.toml include pytest.ini include tox.ini include docs/_static/custom.css include docs/_static/bidict-types-diagram.dot include bidict/py.typed recursive-include assets *.* recursive-include docs/_static *.* recursive-include docs *.py recursive-include docs *.rst recursive-include docs Makefile recursive-include requirements *.* recursive-include tests *.py recursive-include tests *.txt ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1630855987.643365 bidict-0.21.3/PKG-INFO0000644000175100001710000002624700000000000013503 0ustar00runnerdockerMetadata-Version: 2.1 Name: bidict Version: 0.21.3 Summary: The bidirectional mapping library for Python. Home-page: https://bidict.readthedocs.io Author: Joshua Bronson Author-email: jabronson@gmail.com License: MPL 2.0 Description: .. Forward declarations for all the custom interpreted text roles that Sphinx defines and that are used below. This helps Sphinx-unaware tools (e.g. rst2html, PyPI's and GitHub's renderers, etc.). .. role:: doc .. Use :doc: rather than :ref: references below for better interop as well. bidict ====== The bidirectional mapping library for Python. .. image:: https://raw.githubusercontent.com/jab/bidict/main/assets/logo-sm-white-bg.jpg :target: https://bidict.readthedocs.io/ :alt: bidict logo Status ------ .. image:: https://img.shields.io/pypi/v/bidict.svg :target: https://pypi.org/project/bidict :alt: Latest release .. image:: https://img.shields.io/readthedocs/bidict/main.svg :target: https://bidict.readthedocs.io/en/main/ :alt: Documentation .. image:: https://github.com/jab/bidict/workflows/Tests/badge.svg :target: https://github.com/jab/bidict/actions :alt: GitHub Actions CI status .. image:: https://codecov.io/gh/jab/bidict/branch/main/graph/badge.svg :target: https://codecov.io/gh/jab/bidict :alt: Test coverage .. Hide to reduce clutter .. image:: https://img.shields.io/lgtm/alerts/github/jab/bidict.svg :target: https://lgtm.com/projects/g/jab/bidict/ :alt: LGTM alerts .. image:: https://bestpractices.coreinfrastructure.org/projects/2354/badge :target: https://bestpractices.coreinfrastructure.org/en/projects/2354 :alt: CII best practices badge .. image:: https://img.shields.io/badge/tidelift-pro%20support-orange.svg :target: https://tidelift.com/subscription/pkg/pypi-bidict?utm_source=pypi-bidict&utm_medium=referral&utm_campaign=docs :alt: Paid support available via Tidelift .. image:: https://img.shields.io/pypi/pyversions/bidict.svg :target: https://pypi.org/project/bidict :alt: Supported Python versions .. image:: https://img.shields.io/pypi/implementation/bidict.svg :target: https://pypi.org/project/bidict :alt: Supported Python implementations .. image:: https://img.shields.io/pypi/l/bidict.svg :target: https://raw.githubusercontent.com/jab/bidict/main/LICENSE :alt: License .. image:: https://static.pepy.tech/badge/bidict :target: https://pepy.tech/project/bidict :alt: PyPI Downloads .. image:: https://img.shields.io/badge/GitHub-sponsor-ff69b4 :target: https://github.com/sponsors/jab :alt: Sponsor through GitHub bidict: ^^^^^^^ - has been used for many years by several teams at **Google, Venmo, CERN, Bank of America Merrill Lynch, Bloomberg, Two Sigma,** and many others - has carefully designed APIs for **safety, simplicity, flexibility, and ergonomics** - is **fast, lightweight, and has no runtime dependencies** other than Python's standard library - **integrates natively** with Python’s ``collections.abc`` interfaces - provides **type hints** for all public APIs - is implemented in **concise, well-factored, pure (PyPy-compatible) Python code** that is **optimized for running efficiently** as well as for **reading and learning** [#fn-learning]_ - has **extensive docs and test coverage** (including property-based tests and benchmarks) run continuously on all supported Python versions Installation ------------ ``pip install bidict`` Quick Start ----------- .. code:: python >>> from bidict import bidict >>> element_by_symbol = bidict({'H': 'hydrogen'}) >>> element_by_symbol['H'] 'hydrogen' >>> element_by_symbol.inverse['hydrogen'] 'H' For more usage documentation, head to the :doc:`intro` [#fn-intro]_ and proceed from there. Voluntary Community Support --------------------------- .. image:: https://img.shields.io/badge/gitter-chat-5AB999.svg?logo=gitter-white :target: https://gitter.im/jab/bidict :alt: Chat Please feel free to leave a message in the `bidict chatroom `__ or open a new issue on GitHub for voluntary community support. You can search through `existing issues `__ before creating a new one in case your issue has been addressed already. Enterprise Support ------------------ .. image:: https://img.shields.io/badge/tidelift-enterprise%20support-orange.svg :target: https://tidelift.com/subscription/pkg/pypi-bidict?utm_source=pypi-bidict&utm_medium=referral&utm_campaign=readme :alt: Enterprise support via Tidelift Enterprise-level support for bidict can be obtained via the `Tidelift subscription `__. Notice of Usage --------------- If you use bidict, and especially if your usage or your organization is significant in some way, please let me know in any of the following ways: - `star bidict on GitHub `__ - `create an issue `__ - leave a message in the `chat room `__ - `email me `__ Changelog --------- See the :doc:`changelog` [#fn-changelog]_ for a history of notable changes to bidict. Release Notifications --------------------- .. duplicated in CHANGELOG.rst: (would use `.. include::` but GitHub doesn't understand it) .. image:: https://img.shields.io/badge/libraries.io-subscribe-5BC0DF.svg :target: https://libraries.io/pypi/bidict :alt: Follow on libraries.io Watch releases `on GitHub `__ or `libraries.io `__ to be notified when new versions of bidict are released. Learning from bidict -------------------- One of the best things about bidict is that it touches a surprising number of interesting Python corners, especially given its small size and scope. Check out :doc:`learning-from-bidict` [#fn-learning]_ if you're interested in learning more. Contributing ------------ bidict is currently a one-person operation maintained on a voluntary basis. Your help would be most welcome! See the :doc:`contributors-guide` [#fn-contributing]_ for more information. Sponsoring ^^^^^^^^^^ .. duplicated in CONTRIBUTING.rst (would use `.. include::` but GitHub doesn't understand it) .. image:: https://img.shields.io/badge/GitHub-sponsor-ff69b4 :target: https://github.com/sponsors/jab :alt: Sponsor through GitHub Bidict is the product of thousands of hours of my unpaid work over the 12+ years I've been maintaining it. If bidict has helped you accomplish your work, especially work you've been paid for, it's easy to `sponsor me through GitHub `__. Choose a tier and GitHub handles everything else. The sponsorship just goes on your regular GitHub bill; there's nothing extra to do. You can also sponsor me through `Gumroad `__ or `PayPal `__. Read more about `companies supporting open source developers `__. Finding Documentation --------------------- If you're viewing this on ``__, note that multiple versions of the documentation are available, and you can choose a different version using the popup menu at the bottom-right. Please make sure you're viewing the version of the documentation that corresponds to the version of bidict you'd like to use. If you're viewing this on GitHub, PyPI, or some other place that can't render and link this documentation properly and are seeing broken links, try these alternate links instead: .. [#fn-learning] ``__ | ``__ .. [#fn-changelog] ``__ | ``__ .. [#fn-intro] ``__ | ``__ .. [#fn-contributing] ``__ | ``__ ---- Next: :doc:`intro` [#fn-intro]_ Keywords: dict dictionary mapping datastructure bimap bijection bijective injective inverse reverse bidirectional two-way 2-way Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0) Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Typing :: Typed Requires-Python: >=3.6 Description-Content-Type: text/x-rst ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/PYPI_DOWNLOAD_STATS.rst0000644000175100001710000000125000000000000016151 0ustar00runnerdockerRetrieving PyPI Download Stats ------------------------------ As of May 2016, PyPI download stats are now available from BigQuery at: https://bigquery.cloud.google.com/table/the-psf:pypi.downloads Here is an example query for number of downloads in the last 30 days: .. code:: sql SELECT DATE(timestamp) as day, file.project, file.version, COUNT(*) as total_downloads, FROM TABLE_DATE_RANGE( [the-psf:pypi.downloads], DATE_ADD(CURRENT_TIMESTAMP(), -1, 'month'), CURRENT_TIMESTAMP() ) WHERE file.project = 'bidict' GROUP BY day, file.project, file.version ORDER BY day asc LIMIT 99999999 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/README.rst0000644000175100001710000002011200000000000014056 0ustar00runnerdocker.. Forward declarations for all the custom interpreted text roles that Sphinx defines and that are used below. This helps Sphinx-unaware tools (e.g. rst2html, PyPI's and GitHub's renderers, etc.). .. role:: doc .. Use :doc: rather than :ref: references below for better interop as well. bidict ====== The bidirectional mapping library for Python. .. image:: https://raw.githubusercontent.com/jab/bidict/main/assets/logo-sm-white-bg.jpg :target: https://bidict.readthedocs.io/ :alt: bidict logo Status ------ .. image:: https://img.shields.io/pypi/v/bidict.svg :target: https://pypi.org/project/bidict :alt: Latest release .. image:: https://img.shields.io/readthedocs/bidict/main.svg :target: https://bidict.readthedocs.io/en/main/ :alt: Documentation .. image:: https://github.com/jab/bidict/workflows/Tests/badge.svg :target: https://github.com/jab/bidict/actions :alt: GitHub Actions CI status .. image:: https://codecov.io/gh/jab/bidict/branch/main/graph/badge.svg :target: https://codecov.io/gh/jab/bidict :alt: Test coverage .. Hide to reduce clutter .. image:: https://img.shields.io/lgtm/alerts/github/jab/bidict.svg :target: https://lgtm.com/projects/g/jab/bidict/ :alt: LGTM alerts .. image:: https://bestpractices.coreinfrastructure.org/projects/2354/badge :target: https://bestpractices.coreinfrastructure.org/en/projects/2354 :alt: CII best practices badge .. image:: https://img.shields.io/badge/tidelift-pro%20support-orange.svg :target: https://tidelift.com/subscription/pkg/pypi-bidict?utm_source=pypi-bidict&utm_medium=referral&utm_campaign=docs :alt: Paid support available via Tidelift .. image:: https://img.shields.io/pypi/pyversions/bidict.svg :target: https://pypi.org/project/bidict :alt: Supported Python versions .. image:: https://img.shields.io/pypi/implementation/bidict.svg :target: https://pypi.org/project/bidict :alt: Supported Python implementations .. image:: https://img.shields.io/pypi/l/bidict.svg :target: https://raw.githubusercontent.com/jab/bidict/main/LICENSE :alt: License .. image:: https://static.pepy.tech/badge/bidict :target: https://pepy.tech/project/bidict :alt: PyPI Downloads .. image:: https://img.shields.io/badge/GitHub-sponsor-ff69b4 :target: https://github.com/sponsors/jab :alt: Sponsor through GitHub bidict: ^^^^^^^ - has been used for many years by several teams at **Google, Venmo, CERN, Bank of America Merrill Lynch, Bloomberg, Two Sigma,** and many others - has carefully designed APIs for **safety, simplicity, flexibility, and ergonomics** - is **fast, lightweight, and has no runtime dependencies** other than Python's standard library - **integrates natively** with Python’s ``collections.abc`` interfaces - provides **type hints** for all public APIs - is implemented in **concise, well-factored, pure (PyPy-compatible) Python code** that is **optimized for running efficiently** as well as for **reading and learning** [#fn-learning]_ - has **extensive docs and test coverage** (including property-based tests and benchmarks) run continuously on all supported Python versions Installation ------------ ``pip install bidict`` Quick Start ----------- .. code:: python >>> from bidict import bidict >>> element_by_symbol = bidict({'H': 'hydrogen'}) >>> element_by_symbol['H'] 'hydrogen' >>> element_by_symbol.inverse['hydrogen'] 'H' For more usage documentation, head to the :doc:`intro` [#fn-intro]_ and proceed from there. Voluntary Community Support --------------------------- .. image:: https://img.shields.io/badge/gitter-chat-5AB999.svg?logo=gitter-white :target: https://gitter.im/jab/bidict :alt: Chat Please feel free to leave a message in the `bidict chatroom `__ or open a new issue on GitHub for voluntary community support. You can search through `existing issues `__ before creating a new one in case your issue has been addressed already. Enterprise Support ------------------ .. image:: https://img.shields.io/badge/tidelift-enterprise%20support-orange.svg :target: https://tidelift.com/subscription/pkg/pypi-bidict?utm_source=pypi-bidict&utm_medium=referral&utm_campaign=readme :alt: Enterprise support via Tidelift Enterprise-level support for bidict can be obtained via the `Tidelift subscription `__. Notice of Usage --------------- If you use bidict, and especially if your usage or your organization is significant in some way, please let me know in any of the following ways: - `star bidict on GitHub `__ - `create an issue `__ - leave a message in the `chat room `__ - `email me `__ Changelog --------- See the :doc:`changelog` [#fn-changelog]_ for a history of notable changes to bidict. Release Notifications --------------------- .. duplicated in CHANGELOG.rst: (would use `.. include::` but GitHub doesn't understand it) .. image:: https://img.shields.io/badge/libraries.io-subscribe-5BC0DF.svg :target: https://libraries.io/pypi/bidict :alt: Follow on libraries.io Watch releases `on GitHub `__ or `libraries.io `__ to be notified when new versions of bidict are released. Learning from bidict -------------------- One of the best things about bidict is that it touches a surprising number of interesting Python corners, especially given its small size and scope. Check out :doc:`learning-from-bidict` [#fn-learning]_ if you're interested in learning more. Contributing ------------ bidict is currently a one-person operation maintained on a voluntary basis. Your help would be most welcome! See the :doc:`contributors-guide` [#fn-contributing]_ for more information. Sponsoring ^^^^^^^^^^ .. duplicated in CONTRIBUTING.rst (would use `.. include::` but GitHub doesn't understand it) .. image:: https://img.shields.io/badge/GitHub-sponsor-ff69b4 :target: https://github.com/sponsors/jab :alt: Sponsor through GitHub Bidict is the product of thousands of hours of my unpaid work over the 12+ years I've been maintaining it. If bidict has helped you accomplish your work, especially work you've been paid for, it's easy to `sponsor me through GitHub `__. Choose a tier and GitHub handles everything else. The sponsorship just goes on your regular GitHub bill; there's nothing extra to do. You can also sponsor me through `Gumroad `__ or `PayPal `__. Read more about `companies supporting open source developers `__. Finding Documentation --------------------- If you're viewing this on ``__, note that multiple versions of the documentation are available, and you can choose a different version using the popup menu at the bottom-right. Please make sure you're viewing the version of the documentation that corresponds to the version of bidict you'd like to use. If you're viewing this on GitHub, PyPI, or some other place that can't render and link this documentation properly and are seeing broken links, try these alternate links instead: .. [#fn-learning] ``__ | ``__ .. [#fn-changelog] ``__ | ``__ .. [#fn-intro] ``__ | ``__ .. [#fn-contributing] ``__ | ``__ ---- Next: :doc:`intro` [#fn-intro]_ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/SECURITY.rst0000644000175100001710000000020600000000000014372 0ustar00runnerdockerSecurity ======== Please see `tidelift.com/security `__ for how to report a security issue in bidict. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1630855987.6353645 bidict-0.21.3/assets/0000755000175100001710000000000000000000000013675 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/assets/bidict-types-diagram.dot0000644000175100001710000000342000000000000020406 0ustar00runnerdocker// Copyright 2009-2021 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/. // See ../build-docs.sh for how to generate an image from this file. digraph G { rankdir=BT dpi=300 node [fontsize="12", shape="box"] subgraph ABCs { node [fillcolor="#EFEFEF", color="#666666", fontcolor="#333333", style="filled", fontname="Cousine Nerd Font Italic"] Mapping [label="collections.abc.Mapping"] MutableMapping [label="collections.abc.MutableMapping"] Hashable [label="collections.abc.Hashable"] MutableMapping -> Mapping { rank=same Mapping MutableMapping Hashable } BidirectionalMapping [label="bidict._abc.BidirectionalMapping", style="filled, bold", fontcolor="black", fontname="Cousine Nerd Font Bold Italic"] MutableBidirectionalMapping [label="bidict._abc.MutableBidirectionalMapping", style="filled, bold", fontcolor="black", fontname="Cousine Nerd Font Bold Italic"] BidirectionalMapping -> Mapping MutableBidirectionalMapping -> BidirectionalMapping MutableBidirectionalMapping -> MutableMapping } subgraph { node [style="bold", fontname="FiraMono Nerd Font Bold"] bidict [label="bidict.bidict"] frozenbidict [label="bidict.frozenbidict"] OrderedBidict [label="bidict.OrderedBidict"] FrozenOrderedBidict [label="bidict.FrozenOrderedBidict"] bidict -> { MutableBidirectionalMapping } OrderedBidict -> { MutableBidirectionalMapping } FrozenOrderedBidict -> { BidirectionalMapping, Hashable } frozenbidict -> { BidirectionalMapping, Hashable } { rank=same bidict frozenbidict OrderedBidict FrozenOrderedBidict } } } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/assets/bidict-types-diagram.png0000644000175100001710000023513600000000000020417 0ustar00runnerdockerPNG  IHDR ;bKGD̿:IDATxg$eDsNG'^rI(HV  *IQPd$gX`wL[nt0;]=SOj$@PiTa>G@PqTa>G@PqTa>G@PqD\D$[{hTH]t4$ɡx0I%#IzaLB¦s1&Ių|$)$|0_/'I|0_O0 $I!\0/D\]Q'I݄1$|Q"I.]5N_ I0_O I|a0$I I|a>I|I I|a>IR/$Ia0$)$|a>I$|$|$)$|0_/'I|$$I !'IR/'I $Ia0_O$|0_O $IRO I $I $Ia0$) I0_O I|a0$I I|a>I|a>I I|a>IR/$Ia>IR/'IRa>I|a>IR/'I |$)$|0_/'I|0_O0_O$|0_O $IRO0$Ia0_O0_O I|a0$I $Ia0$) I0$)$) I I|a>IR/$Ia0$)$|a>I$|$|$)$|0_/'I|0_O0|$|0_O $IR/'I $Ia0_O$$Ia0$Ia0$I $Ia0$) I0($|0_/'IR/'I $Ia0_O$|0_O $IRO ICO0_O I|a0$I $Ia0$) I0$)$)$Ia0$)$|a>I$|$$)$|0_/'I|a>IR/'I |$)'I $I $IR/'I $Ia0_O$|0_O $I $Ia0$) I0_O I|a0?-c[7+$8cN՘Fcf1 O0+X{Xyߊ0qa~o\?dwuc^J/̯hK:i ߥ*_h2f8aiVfk+[7.p6C 6`ar揲/43NYʃV C!hS1 %Zy98/zmkI`6EGǬ Lm '̯ʣ B1sߊ0_?Cp:}Zyf(a0V^Lpll]wz9앯 ЏdU޽V{}[PkV}cV,SQ+pz<(f.CP?;}`4leءu߹c^J/̯e`u91;z⛙ktf B/|̌ 9G{c6YJV͟u^yqQ\la塋[~NQ]wG_ٺՀ1 %{s]m^XɑyS7)f0qYG\:4h/~̆5TgX08$W]tZ{P/CLw|?G~nǼ30__[jsN<-򞝎k| |N|xWGt?fÚe3jK#!aPNk ~{=߼#G~kǼ30__[YIϷiѕlSJa1+f?`lXLuF Gb$bfY?=<<ڃ|a~ywk*xha k^GJغ /9JKöٍ\1c5h/~̆3TgX0ϊMOaNk ~)?c[ُ}Ƽ30_ߗWWbFlCō=l}_$&NlaqJl+oGqP|*6uc=W81Nclw?1iYw=qr1+85=b*v)^q:{X7ЖWs]5˭xlkFK7 oÁX9LgGg;smȍh3jōpg"F-OJrW/#cx.qx\?pPYq`68.K?9% E~ka<_[d|7}yGq{ۛݞ4~ hԊ2fhj[=-mo+j:4޹vq1c/E.̟+cra~+2e<[{}Qvʣ. ս CP?rF-<''au?/|,3,*\{=-liiv{ZcV,gfZ6۾/ d|3\37IoyȿA9~S0?|X<[{{Qv+cQvۅņÄ~SA6i^\VxoO=qMЂXwpyBa>3Ǟ|rҟܺN\&|eȄ?}^>t>nqr|1P~b)Niy1rG⋱Nx >> >#RNYiZ֞qHĎS|t"Y7]+qh8a<[˷|X&Jߟ?2t;&, n)''=Z1+k?jf0~d{8:s\?pg&E+.qH|-fo\dW y)J̇UZy {Qʣ̵GWewrVew]]l=L~%tnRd>wu|<4ni[%sN.o^(@ҁN6>'o{s1-YO&|ua{O-z-m}tڻؿ3c~@*H<t-lF,t|c6~>vqm?VN3h_魑xtOnu;^n,vN7s{WƺIj Z1,8~h}7w~C1+qbG{Q;fe2G}xoƇ'ySlalo;C=0[NL8M80{~a>zהG7jN.o^(@3gt|z5fZgߡٴWo{,𬎧 `9_ȞC]<6'}n<;.t˔jn}z{F˟|vϯoR]⤻'G3-S\aK߀[w3&TϱneN s|cV,N 7oS{[P/5]gdhC?#X3/C͇Zy sQ0]yZyvv2z0_?CN_}w۫{| 4̿'}RUϞR7i#>gntx5l}O=v6o5-s]1g”i>'=O|=bKǬ1g<.bn;NIM|oʜeZޣ8ⶭ;fy0OZ-VNJ@gLqךmBV7ݫoW=#7y+gٱb7f׵JN6~[IG↖?o5G w#61yNK.Xv+ܭ}S?t{hh7jƬY&=^2m^W#n(x|3\Q3Ԫmkz "Ύ'ڣZ+a=((Bb0a~a{?OH3|c[6 hçt/x~4tGK7xLض9uܸ6HSf~{[e?ݯ%ԟ._6GE1ywX!f4{d}S|ն]LQ3f2G->th\Qyf3= uXz6.̟c$07Vm1Yj+a=l+sQvۅa|P?lٮnkuy]q_Ԙ| 8r붾?0m{̿ԯx)#Z0}l?3dֿtzC8{{=w ڝ$vM:ei+q|\?EO+6K@mʿms⎘_Y[w:궓_8Z1+w?jRWxP۫: wܸ𥎷o1oW*8X4+ǖê<7+To1GWet2Wew]]&CP?_Y7L8 #ߘ=N.o^(ǀ<-dNϤo<bN&Wn{W8%˓%Ŏ'&҉_J| wvh;;ɳ|8'˦^ҿ˝ =ï;[r?jyƬY&;_C¶p˷u. uy'|VbŸHk<7+Tm1̵GWeu2Wew]]&CP?lfqjßa_>N.o^(ǀ;N+SMWۏ.񇹾-{+7w\ync6>Tm8I9n7 ,}ci[,oeUk1{ ɳ|c'|;`{Y;ט:=Ϩrgh?+\pygx\26o oQf<ʣ.Vʣ. յÄ~ꇭ}4]>f,]]1{U|/3\޼P?Aaku# ]vڅ[.`k<5+To1̵G}Wu2Ww]]&CP?li1[Lvwu|\G\2 pO:usݏH[O*+r /..rRԘmaIxc8%vw.Pn%5Z^-)??w_zt|x˚_)HI11h;jYLQxfʷu3\jg[}s83Oi:Fc;}ՀbW;bBo>cXBV\{uQf+sQ~ۅa|P?̿0&9e˗ 10.O9TNf\5׷E_-rpӞI1c6+~6[,MϺAX7\m)>x8C rX4⎘Nz-_/XTs~|gʝeZޣ}5DoeXe: zwg= )=+"7Vo1Yz+=((Bb0a~ak8&/"ﻺ|c>sU|3\޼Potj__wޡgCotM'Rq_?턜cfS+:~}6y?PxMW>;֘23_̶9{ɳ|th+Ͷb9Z1+w7j=pmȷu. 5۾\zWݬZ+nê<7+To1G]Wv2Wew]]&CP?lVo_#~3|c~e#j{| 8̟վ=Cm~vU_sf{tvot_+r^tlZ˷mmqJ?8uY>0N\Nm ,h;jyƬY&ߨ?OX#/x<3\ڝ]:vK&?a_n f{\?䐶4}mem^?泥TntܮO?fm_#WCfK^s:|X;U'h|#vO k_aw\o񳯧&9*s=2g|6}Fܣog;C=>ڽ͛wtq z/-xjg-27Vm1GV\{Tg1GuV\{ nSwv2{0_?CQ|@o;f|y0=N9|?Wӝ:~ufWCo 6n3b8mC[yO{xTckjN.o^(ǀ״&rqEM?Cޔ.g\'g:,n~+e@*gq>C7f;^[ܶ'ŵOVh퐛VB&4Nd6=~v{lm2HOOϱf<+_>q{Okm & =Z1+o7jٱ”{(-˔o|3\j7c⽱C;Xb81:ųڣZ+=ڣJ+a=-os+ CP?[yhzm>kuywޏ꽓m˛ 10bc7f#v]Ԗ+҉3klU {[zZz$te}Ty~"=-'n_;UԘuW|T+BcoO fMbҳ-[}w;|߷#W>|k-~6 Y' b45w|cV,oMa J?Q[Pc1gX/.p|8l+#_4V#Zlo>ckjS ި7kMflH-n8rg3[v}'w[qM̿hbjIWڻ.]md›y7HytGL|l^h\=6mN[oָG{Q7fe2yG-~P;!+&{ZOi̳u. 5cq|ߵMc<#pV+nY͇<(gQڣ:+a=#_wz0_?Cpao!{[}.ﻺ|c+ő| 8'i[=fm^6[n3 X8@'Nmiq+7/N9pw^Uؘszr{]o)G)ћ]f^*ck>˝qllо.㮮+ ۞w&Sq7bmI_]Cc_}Jka~p+=({QpUZy ft2z0_?Cpau)ϻ|c+g̋| 8ˮ`8'츼d'^8=.6;U&~3{nrsqI?eqN֩֘O8&NsҸ((񫸧ŧ7qk7<9~7Ը<Ssi3˾~F1+{~CZ\8rt|[W?DgzqZ$<4{N [Wk5o|hGÅ=$Ckrl$)̇0$YK7':;.đW|O礛"n1=(&Sw擜=]&'IRa>Iout [jogI7hyd!'9zXX=LO0|н9]-o]HI>;1_D|zX]]&'IRa>Ir_n{|b߸*YqM[6f I=]&'IRa>Ir_wv|4E7  8dy=]&'IRa>Ir~<{˿C [Wb|od=]&'IRa>Ir讕?8/e&E [W!na>IÚa|$$~u|4n_ŕq0$aMba|$$9$0$Ia>$I $Ia0$) I0_O I|a0$I I|a>I|a>I I|a>IR/$Ia0$)$|a>I$|0_/'I|a>IR/'I |$)'I $I !'IR/'I $Ia0_O$|0_O $IRO I I0_O I|a0$I I$)̇0$Ia0$)$|a>I I|a>IR/$Ia>IR/'IR/w$)$|0_/'I|a>IR/'I |$)'I $Ia0_O$|0_O $IRO ICO0_O I|a0$I $Ia0$) I0$)$)$Ia0$)$|a>I$)'IRa>I|a>IR/'I |$)$|0_/'I|0_O0_/'IR/'I $Ia0_O$|0_O $IRO I|a0$I $Ia0$) I0$)$)̇0$Ia0$)$|a>I I|a>IR/$Ia>IR/'IR/'I|a>IR/'I |$)'IRO0|$|0_O $IR/'I $Ia0_O$$Ia0$Ia0_O0_O I|a0$I >0$Ia^O(}@ dΊUԀ $vxHHa0,oUU |a>ɩ|YlՁ B]=Fru d-1fă%ÜxUln,Ha08$ԁ|Q"X4Fb ?va:kcxflT~#8݈0a>INb$FbY kfu|1# [8]|n3c$/#1+k|HH0_O! dE6%~؟Hċ )d{_-w)^[Gb dhKn5뗾G}$م+Eⱓz[{Q!3GSo7 \jYq-%xMj7#K/3qϏ):.*2̍3͓U`N{0I˸W]J@9 pIP?'J߳蛆 h|(o.{SVfI}= W-+wMڣg&!"Beo@_ܼ@W]<P* Gٕ2 5 h|(XЭO* }0W"LWbQH|7 P$W\msy2^POXYpoSry5-;JxK%.zk0e<KiO_!ĎWwH|{}Xo P,S-B xCAH4Q=G{~ͰM@)x/\6E7Qyx{OAHľ񌊟9 '|(:,^(Օ*1\`%)q 4a>˺]i20LKP"@\kHlSW{y!0],V СDB16EGⷕx ˝ 3PP,j~ߗʀ#b٧ǔ w#L7v%r#1We'pXzy{wEW&@NbɜAH1y]}]ɠuGr\˾# x:^Exn $F芼 5=.* *\Q3yàuFhK׋#?U.@nn}WU^hЁ"by'S:ڞ{ PP,aw3@}@Wh} 9PGP45f0Eߝ.u){E#kths1h\LC;r焛_sb)Uwmk}[ ;P?P4c!+V:4ϱTa}Ɂ<3fsBO̾; P7P4_wnRݸ滱h̷SB]gJf:|(tlzi+qc b$C }rv,-~t|bRY2fUhk9!e6 @DD K٩Qq_.;z!bX6gţJ@</﯌.^:NS>X$zqBEtd3?ñD*AH2z>L2vx ^ ;+|(,tqvZ~8 0.N88^slcX;֌$^jFܩ'^zȆd[KĹz 7uaƫcX3[ @@nbEG1駗TLx|^cN̉C\WqA(kfwek??\ݒxSkzEl;^t }ǜ=#y{:f P<3c㸠Ͽ.pb0e=5J(b5f7uޖ^? :P^磆h|(Og˪Ɵ|?oQ @nS-H:A1hYN)}pe<7Ef>a4a>Zluʝ37ċRu̝f+ρ4a>Yiiyb[g}rm~kxjڽ-׾Ch|(߲B|0('OFb4٫h|(C3!`ޗƛSBmZ`t!A2'SІu.yRobsi>U`cP6fKW$VϺQJ])4>z,u@s@\+X)3O;q%ΪNP>+ZdВyR`n:y$^7MZ|;2 9|S &֬C~_)]kzovY%0xu^1yR:w_Mju 9 웖kyR:pT7"rC0x:B1H<돿P d=c1g+W4a>TMi YT)Spf<#ıOh0*%P'0ΒYwF)m HU;SM@S@UZ(fxNQĬTY4a>TIT "Cܪ\/J1ܩ@S@U{8[jD_R qbZpk"6$[^1 0#[j5 ] 0Rxo< TV+h |wbbkbϊXGSX- Ҭ:P )J-s0-4T $K,QkVsh |ŊܘnCnDHB?P )J̖̍[+`smP uSڸ[AڰLVo+P-̖[ψ{0͹,S @`_7+H[ޙUh0_yقs\17OQ:Q )j|<[pӜd앢嘒J}^) X$[ri͏DDėR8O9:uVݕh |gK0A߬NbqrtY>@S@QUƜQďb/,+GkK0Slѵb1fpEib/+GWJ4a>TE1K1Ӗ~R4xvS.9,ػh |"ĒٲKQ 6^kl0Vٲb)fpCi-oO+Hל3@@56-WT 4sY/T)`rOkzoA< =pzVR ),-P 4eϬn0My8+E?'*0Iizb%:vJӒ?wE_G.j"0ʐT d0 y,VIQ[b5YT )-R 4d0x<ޓ H|J1f r,-R 4CYG)`TlW wE1f *lxZ1ӎe}iX_7*HTcע vuJӆ(r9Y-/W </ʖ_{*`tOP &fs"m˳j\)f kza<i[xRxF^W=YJ4a>TwSW Y`oW YԉgKp.K_4bl_4NUr~VW+ P'.K`9[1 t[D@sq<#1`.j0blm4oF)1ԋoܬFA)!\IQgc1 0?eblg4dJxQ?CT10Ƨsϊh0'eoT)Lf 9+P7Ų`݊J++W!x-4a>ԏ \4~R@y$ޔBo 2D~RM@ゴP1Y{R@s,?n2*k0[FJh,fnCxg)ȐyUV)PG-;P>uMj^_w)yCV3h|#M'K~F1 eϬmPS)Yܬ %@@=)[$FmPSvIQ7Q +f#0S)& zrK,-NP @#>t;)ԒϤ(D9Jbl @@eF2~iJ5?3U h|+gh >Jt&b(m h|+Oǿe˲@6J5X,㔣T>ħh|/VR @X's*Ԋ btaQ27!RM@eN,;ƲF循P#.g(Q:CJ4a>ԙm٫ 4.7S)6.^O*G8(h|3ץhKg=pk"Cԝ}R1[1 YP)=ޑģ R!.J4a>ԝblyvb kJDR\h|?'V @cX2o(T)Uܮ ,@@$-]Uͺ Jenlލ Sqk*PF:JhOʲgKܣPQJs3|$O&!&xq@M1>R $H3bqrT'(ݬ@@3S7@%4&N $N toE[QaӵJX,[T T/v$كLaGTdW^d5DΎ<}rTf+l昫擬H-H ƥ4K_:O)a>*+$kֱzrjAR_1;)Gl~0PEƊa>Ic 1^-\'Ñ"qb@O$({)0Tr6^( Te|$)GJgOkg#e*lR kI0tX. 5al̾0P%d S?$IpaPy,]e$6l^JblRac$I2 !T}6n;* T%Ua>IWǝ R+vFn-_-նV I0MQKFnSqPT["f)$IR잢Q;zJUX2[}I1 'I|b?+~5lVS @[eƓa>Io%FpYqmZp$Ia>X4{gh|O9j1|R|@Y.[$Ia>xFb8F9j(R|@9)}7ŀ0$I 3{qQ둜7/S @"˳'|$)G\MQ\㳕ق9'ŀ0$I 7ċRuUŒ$ Tclvb@O$)(Rs{iŀ0$I ǿwԞx>0PM.JK*$IR.x4ޜGS4w) TeK|$)GG+m1GAӱH67* Tc%ۢqb@O$])c< Y٨^0PUe=|$)<OQ KxI6T @.fca>I sc4nRFld0P]nEea>I (qr44*lv0$I ђSTJ9~G)a>ʜV I0 qTzϰx dl|0Ped )$IR8!1,WF2~J-|$)Όgdc| }X<;[$Ia>;\x]t{e<$Ia>""xNQl?0P>]Tŀ0$I NjR/m63qJ)a>ٻX@(KmAƵƲ&$q5ovbԸF]KډcDzYQ鰿wv{|?o^3w}{nDEQĖ>ZHՙwHh!POOQEQ2ַ!%pW hoiEQE/k4ĶBI̻'Q4qpYul|(h旭yf[;{)w{{h`ՄEQE/S *4X3,4w|Q4pYeEQE/CKSIR&ms4]! ̧((f~Yc ̻ޏ(, ̧((f~i[Hy˞A |@Lp@3((eֶ{hq_l "7EQE/#WV~=Eef} f> ..4KU50@3((eNU:8,̟N|@\LRG0@3((eQW@n'246" f> >3!j! ̧((f~={-qu0#Ch90@3(( zڿ.!: f> NeDEQEOm8Xh&{ꫯ 4)(XiMR60` Q4qzEEQEOomLK maFƒDf!O 4)(@3múhDg4K4)(8i;I?HیRn;|(h'|n[C5@f<\I|@[@3O^DX7{5]ھZzkn=Zˠ)]}йhh1)uzjin5E|b35 ̛&8d^ڱ_؍(xstfTi38R-Vnm|U|+(L`Vhg%/Hsis;Һ{eE3?ڞP';o`ቀ؎(xZfIwa |iSs;+s9oDydg/}2ڙk񭇜1nksmw%_}9N@3WufICs1mM"bu[CD!_}?)OGLihg%/#%^1_Y=-[p74ulMnYˬ\e{:g8-O>랮f5m仏 "ܺ{NtPַjd'Yf Vi Niia- ~v9964m3>d:"`ֶz(3V-v9)owsլZ#= MUH35ӗY^g>s.֍t6Z:/O?]:G)kT.lĄ?Jge'ŗ4 լm<&ƎymS@nRf c]HyJ)ãOulqk] ћ5KGdlϲذz%R_-|FLZ*p矬~sBq_7<ߵ{e]LeVW~֞+ԬfihW'|3W%/օ :#B4&ҚcGThk|@9mQۦ8Kq=< ƝK^ ѧmځc\ӵfFyuN?:(# G f\} o^Hqls:Bݜ~K3-VHnO!Ժ)6 /sz7/ˬt|F:W[5YǛ \n̊z"[6b }ǫO_t6k3g pC۲5&2XdoͲ~$ Ĭfc9j,2pImWw#W}AլO_Yun(YGvfg|tඟGt.lճz9ΥdE#Xv:ivp@6NSl紾שsL틉j5;F}f5kkz&pӝ[^ g[Ƣ̵rùIFΥnis#x8~ uˇ\ /qu׷Sa|߹֡޺S{~7z.{!p_ϧ2YikD!faZ1jh"Is\Ĵ=wT3;!Wˣ;f6g{ϵG4Gړv(wmaGFLXWνw6ֳ_ȶA}Gb]xc&)~0[zQc5ͣ;:Ǯ1bI=:6ѹg7Y3S?J_[;GRe7ײmeJe`[fMs|F0`]+?^k=iF߯fmkC}%d`?~)!޺V{~g'߽F3 {J2KLdm#@3k-dՄ5pɏ|̓3ܟDSk\shd3|'b n2MMz]6v/_Q:r\lm-öJRϷ\$}EMzMmvMyk#5ײ皻N4F=Ĺl >L 1Yݜ/_b\OP2yiڅݺV{Bf~h, L<1@3w'bi 7928wo\Ͷ }3 +j{[I[¬F[|FzJ8Wέո6=keۮ#ޚ0K9};S5ySӴ{9&M|ճ~\6 i9o4Dp mADNZv;Q4q4{;{[Nחަ86,5t)M(-(}fUehf[5<}/uzm#&d{Bo=Wԥfvf3ײSR6?e:ӦL]۷7S+7EMg{;7i\Fx>͹ ua3>/5ntQK3?oۯv+9nf> Zڽb/Özws\Kws#x5?l\9v̢-{O#&}Q_h_uq.bCxԛejwOa{\>k$uJK#2)ԙi{wyxZǫ}I|s2fvo]L+=?¹Œ;O3?Oy%rv=@31gbً36'cO?{lk\,^MM3nx(,˶RϷ~aS N;zӘTuI5ײ5G{i~ ƛeؾˬ6v[QgZU4ʑ> xfZaέ˸#G4+ƋXX"ǚ{h`{ f;8&c\sL\,^Mngsc9{ѷimXӊ5ײ/eεJhO5Yaοh.{\>_i/u\cp_Bl92ꯙ(L'-gsu_JsBo]L+=ĹlX8f~ξu/fQ<]d@3ۣf"`t3εKpyBdjfD _]4Y;U ۧmͺ +7͢q+ꄬ_YS=geGw-՗?8רMK2n{PgjS,5\m#nB{~8ùq!޺V{d@cgU#Tfis.%%gI f> !,t\n?kW׹ 4o.Yݿ rMmnhNOik}N|^4_SwVm{lo-3.!um<P_{\>~2W:x#/!.ϸPgjn_d5ϙz=R~T37_ 4WOuAa.lbW^ٻ4s25jfAh?hȫAZJt3i/Vs3iIsپiPc&Goeu|syus=|ӟ=fQ9OsBh&:bI=vv)z:94!}=,1sHWƻzC_{\->a˔blmo6 y6gLW~p[}R~T3͇}ѹtPo]؈)=?\o=@{6t(Hxa a-GK{^wss-ptx.95]%fν<[fۘ\_>bI=jcRlLh~TcbC[4@:iM {vs~s骾_B:m8ޏykǬ_@{ߧ{m|W3?w-w_7}MGCua#}3?Ӯ}E n/۵&D@3;E޾DH7<&ߥd9s}=|T=|ϫ%Gz&5ĹmVkۃif4}}|UN%ﱯn$'f7YɸH[u 9iEzØkbӮB9l9iSmo. y_y,_=zwS|mߵzy60z!ߺS{>۹}*t5FQWU}:f~Zt(?xژ(XG6'Z+=8Ovk+|>GGPk.5"uvlZks/уǘo5d+-9G_ݢ:PmUwɹE3gku9b_FLXFb97Yd횥m}~_ݡ5VP̵BsзRO].O~NZkaB^c}Yy!>f}pŖ2ҿuW3[n]؈)=oֱi>zf~lN_ l4؞,@dn~m %h>[kW?z>vinY5o.\>5ikW=WB=~H#&KF˺oF{ԏ7IWe z"ϵ0r*[Y~ Q\gja.߈TgLq..S&}!.t7+ytoG7ig"DZD(c@3 g70ftu ǝfS}6E -QfݜVif k3K߅_Vf|uEf 1aOr{e=Vz>2a3>_<׹Fns|;)7x:5s uʏ*ѿ VWܺS{<}ʯUV6w>(g,"w헂hd=F@D<:.EaOqsn{I/ %b ۼŖz,59)θx]՚ @NvnIQ} uvS۞s8q*2vo篋oٷecسRLH#&sKôsNq\~q^f_~)N6xG}tK[kܞ]L-c;;̛lI3Ԭi:O[6byW>^Gx]vv=|Yx~RDع80$y.! DVMXݬt^$,G ݥB8 ۚu. _/:sѧzYzHIelz[Z]{R*u=5#WQ]L7Q)%i7>ѓ._tƕ s3f˄ m9F]G4!cvh{hgU[@3,[#X4)(=_s)ѮA)ߖaf>w2S3ɏXԦ镧[/u,'?ǡZE1~if> aPػ0@3ʻɏXl1Ӵޤn4rMS#\_NLfLO^{k!F&f=F|@d{hSEQT䴭Kz~6Dhv6=F6fC|@V0@3(RֵN%uLPѓ_=lVC5%#hW3n% f> yZ4,% ̧(uӺWIo5CUikz,ǿD{‚Ek@3Dי^g}KOQEQ)jkORJzhԓ/Ư ]E~7:"p3.# f> ,.! ̧(wl::?-?J}z]Jn淨ƶ](m.$ f> j쏤hSEQw3WY*۝M|@BPY=@OQEQP\n[]4 Eyf̝J|@Ra|hSEQE3?V~G=2%3! f>  4)(_((+̸;(Zfwaf>EQEQ4 2F2׳DD Ō}HK͢fhSEQE3?om_%uy?! f> fY]MOQEQ<>6CfmI|@o}iaf>EQEQ4ִ%}@3d?;hSEQE3?gh]S,Qrϙ&Q4ɶY@OQEQЦ4D;x݌UH;¯RhSEQE3?i[I?0Dxߌl$"7KEQE|f[[jB/8\H|@]h~jaf>EQEQ4XƚƢf HIh~7hSEQE3? Tm5KBXD|@f~Cc4)(QN~!2;'@3tEQE7vKoDfxhoYAOQEQ.z "/@3|7_G|(hgrPge)HX׌'H,." ̧((f~QnY""63ah^ 4)(dV͕@D03.G@3P>SYEOQEQiF+T"@3P3 4)(VsX"R4#h74)(Vs36/! f><,f xaf>EQEQ4/me,1'yQ4*EQE̗$MTh?-bg@3P.檇Y^FOQEQ|ImY!~c@3P6N1u0@3(o]l++eH،jhǻO 4)(*fboǛ%"uԟ@3PNv5=4)(*f2ܶj">D_8݋(rr}|(ʶbE"f@|@9Yf!xaf>EQEQe?϶{|Xݜ(Rg=40@3(lm+,qch](2URz|(ʰmw֓,y/ Rp#-' ̧((̚Ui =w$h8{$!f>EQEQ:pncxtp,f> 34)(*f~[fڎ (7Gv|JOQEQT4_*)yv$ f>,f1x>af>EQEQe@}mKLy0[5A3(2hlCxvGؓ603Qr0@3(7C0Mh!f9Z4)(Jp3VZ/@3PмDOQEQTb4̮|w,C<(rԢM͂0@3(6hO,K1Q4iYvd|(_V Mbi@3PW/$#af>EQEQk/1_Cle_(W2K50@3"sd@QTɛ-:ö{]u_\E|@J(0e3R5RE\FR(Y%jV~cQX6cbh>fQ@|tuF@QTUfEOC̝lFo(_?r" ̧ʤN:JG:@`)A UDc4D{HJQniQ,{g+h2o'hS1l~ݵV 5Hkz:ۯ0[2JPUPNȫYP'KRT,}"Q4r,TD(4muM: B%L% v*P?ѡ&L3Yu3ZsHK̈9Q4lᕄTjz[MjN#U&dsSfAQծKX!!1c@hIեTdj"+TȸWB^ fa:k- U8QT;4jv.3w' f>cy0@3LgO]~uHLK9 O=Yn6.:L2($<,wq=(rYKSuNIZ]t<閴Nq/1F]G N:B# iֳgٛ@3P|H"_Ei寣}4'1ay7E$neQ^;B+cYWQ= oDnS }a$0D"]gSt?}O|э {w-d ā2OG4pY"nC"3ժ_-_h F@{~u -.'-͈(8(ϢvXRe JaD.{DŽ:fc  oiU;kFN3bh`H<0u6=o#80û]i  v֜ Q61/D/,;[Pbդj.+Tu;t=INwĽO'V~w}ɀ2AvqWG4Ѭfx a(ij/4+kmC2'>J~Zhd4BUZD (#ǘf>XjlKl1a(qV@ks^g,k"aF Y5O+Ϲ mI fmcO8]4)fG@{@5A[ښT|GfΚE,Ifc 9@>QV417Vfp/^!z4f)ϕdxSz-c=_߾L([y QqD3P\Ǜz4dU 4&瓞.V^6'au9n0DL}տ|_8lDjDg%vlbf>^8! y:_BZGyo"r94"#rl毪a6n5khp1BH(kwā #b, 9[&Uc`C*5tӯl[`@I'$>V6^݁H;Wl%ՐoXx\W[ ā27̆>D|@ix0`^#kU~*T#nӒή:T૭ Uh6㴥z2PS7; "+|@-c$ YYq<~@rF|\#x3V~*tLbsvGm^{FiUu;igPU 8}jg41D3P|] j6 +{&caŽjWѢc|5S{rF8 +7ir6Ήd701^5x'uN^ Dk[\/6ufU@}?N 4Ί)|@)fC8A[ڱFN;f~Jl&vz",Pg uW1hj;b$I0(y3^5hQF-| Ug9s$a!fis;wяXg% hJcY2N-خV@4%V~*twSf9cQ3̐@[@fn((̒& >V約5XE8)&8T~T=M*GDY<@ +z.graenUJ_۵y5suA ]lhI޷}W;;9m7lHnH f~((͢_Mj2  l mhh$A#4X}k63^f>T>E@Z& AUjdi4G5Μ\Ku b\;{# D|@gPf^mֵTY4RuͣIu.= 1WޞPcٱCHW D|@ $x0Vo SZ]F7M9AbwyLN@rQC3PJs,/# SZT9 Uh5Rt.]TH9KAi1 z@}ng"f>}, +)aլmhR> DDkK(S'BOi1.㯇C@Φ940h YBO@Wz<GA`O>Ѿ'VzPP2ۑEOyv}C@ڟG^i!D3#-QB-OJLbA bG]G=L@^y aqC3^&YD6UUTϰQq:0JlF瓢Fi9cRw8PsYDn@[FU]U&f("Ehm"v0^;8qF د# QqC3~D@;D,+kcճmb`G3bo$vT*P'Ă"xU=88 4s)f>hS& ],VTkwTp<>b$3nEfF8!h4 d摒agPֿ% nh({dJ :'lmAԛ'QY/UD>՚vlȗ@5nf>=}oGJfTmOAZTF-%3E;[zu|tW BquN!̨74L\?z8M+A,}n,hw_@߹7&hB^BqS74kݭ^ja%ăf,B0F:1JnR8̪?74m QE3Wa[5p{[@jeĂ<̷ThK! $'yUK@GioPUc\ߠD8ƌs"Bg6zPŞm64BsY& nh2o'yj,UUw  '3 Hih|Tjf rX!V3@_QqC30ɮA@HƫF=!ŎHF3%ș  Т駏:(h,'$ ` 5Z]U&A2G~#Χ Ko+)mK3' -% V},Ab-'[+ѴT3F!u-q̰(xayYFUj5oS3"ȚΧ=L(hM/~Pw9#QqC3 ;DsjTɄpDic)5"{y:~A3ˆ74pCLM8Ԩeă{3%[:u|ZuW 5P7='J>8"z̴D |@TLQg06MWo Ujlͷ{lO1F)6܇Zݎ9u;Pֺ5#s 8,*7rRK5L O8(sQl;I(e mG|ٗvbf> :^gp| {F 3+.&XYў+A5lWx~s'Enq(,*% Hy<{#nk*xfۇ23[[gH @`Df> JnG}EW ZM8n7c(Q˞fn*g)#?jor%*@D|5 enjZ5pW颥KK4Jx>:k$Gh߾h:%̻h9,+WdL([Ԩ@@\0bkt>6Կ %v@ %f> ZP0P>S^3Hkm3_I6F;5PEwz }L @ 2s/Mr@YYFU9'\q*5jcfDsU.bbI[PRCk$ ^hf?250p<ڪe k63HO}mŚ +fI@DM屄[&US9'F Y VbFh&$ȍ?I@ifD |@\g0`TzɟGKr=w9a$DF8w7!Fm:AA֦W/4Q㽧Y\j9-  /L0|Nn %iVۈhGO4QTc wHf5h@jY'HjP/e'Q7Sj}%l<(xKfbqUYD;ӦF u!ț&io~Q|ܓ(xjWbeUeOZU1j!hZVFWx>a{^ˈ%i}v;͌ܞ(xs=@Gl|Zj9n(͜(ZJD-О}D@ʆ/4o a IkwPxafYDQZ4Zkx>q+5"h~jߣAD @$JB3e;EDHz8M+J #7\T'o_5Y,1O̼G@DٝX xNF0Jq3"2N[:CBaߗz@xD |@-$ D\҆6~oFɧv&&T瓸FrHI# B>Mqv2 VhB\U2^5x',SW3Ceg=gŠфήF=E@Lf> &Lyjm"uv(lΙKѝ4CG51'tf> 3!nj3QDQf^Oej ]uZD,Q{8Fn' rIbf> Ꭓ fFk@D8@ԙy QuY!{nyUku0ss=B3}CBp@XhVf1Q1Z׷dB)Bhz6i)'4qmE()\ Z6ybg >sT|AFUZQf6}8l(8bs;@,Wv*5j q<^Ǡ=;mg݉V7'4n骷zOXCf& 8hV|wH!mC5@nfD |@<,hSCoYI>jY?PJ( FJ @ e,7h:a ߩACm^{vw )ΧpM$-M W/̬=(8a 6^5vDw̌~(թ糿괘X֢m؞_y{QqB3:^ &j5y} QMi?_zP;z8ب53wQqB3?& ejZ9U 癹}4Q FR#4Prvk?{QqB3'%D-Rm.VZH ߒ(AF_>eĒmv903ך{QqB3'?' d3ժ_5p]0;w%K]?QqB3'K45!ը*UTFk\DKFcfvqs'4Rg=|z j5pk q@iM(in6ˉG٣|@LU켞0ІjRxAl̼(ޚ@(mxKڜFKO9<(8c T㯦pq'rDGgj!mN4@Lb0|@̌>>IhQ9- 9RNz5Il:7 1 QqB3?`ѬPosS`F^h=ߓmŚMm.UZD @l}l2_1B3?7J}JxըG$LM`gPzNEf@ @}cg<f> ~؝Z'6O :zp2Dx(OVz]=ie6穄|@,^ǵa7@kߝ- V55f> RDeaUJ_;7 νlLovЎ0%#T/$!8CMcj`{vV:]I+?0ݙΫ~$4DPX|W=.Bk5OPh#s93*IYBkhH׶SUj'~.zP1ojXP>@`MƌehϳUG{ Mʹ/b |#*3~M }/VP>QL4u Wڴ Ξ.ע wu?b@@˙v!EQQ>)FZ=ǙlЙ| ?]qm\\x coS| S=Q1UK{i>ȁ+M7V@Ah盪{ µ\rPS 0ʙ XQab@9L[Г0lyW-)X s oiO^ ( -j**jvIg?ѱZeHL-? +|(o643ʎԨbN1Դ*w`9S盬F?Z~ P\l0ʛYvik0[5P#*`<#D#M;"0V9h㲽~!ϥXZVPhnBR( iQ/﨡S j\jڗR5盭p¸y~-e`n2u}_(gbFBõjx55\1B~ƬP4 PYBa}CMT4#38P6ǝԄBev^DW m4J[h"Esii(hό %T̗ƞ}åp( ̇2 Cמy$nDRk{uOU[A(r_vm?W'7^՗d-:ɞPB!"/-皘n!2L֑dI psabHo | G!B6Sl?D&5?)|j) iZ0B!a~iy[ T8굸hGO\i | G!Be ,'qsWrys [X@!B0Ԭj&n!Qy*jfPm\i n | G!BfwT\˭?Dfթ'_I \v,;b~5+0!B旚ߘ$n!Œ΄;ꝂgXC_b<@9̴W`a>#B!Rg;38@Y۫NB>FxL=ǙX@!B0Ԍ1AEOpgj/{Zv ڴwVa>B!"/5r27P@/MiW}v 7¯0B!a~eg`F'zxC KLp4Va>B!"/=;(ԁ@WfOU{pi | G!BdU?MZ k]{`r#vB/ |B!D_r3QnC@i弗 LaG |B!D_zc2 qoszx=;>:l@B!Kϗ6ʜ?!-jԪ8J{?}'3 >@B!Kru5aC2Wuv ۄ꣑YDyf?LK|#B!(p PyN(Fc dA=Oaa>#B!(p +֧IWV22ȴaa>#B!( +.T$Oot~_5c -ۘ+0!BG6qxPHî+0!BGL\q]8LswP~"hL[q=Va>B!"̏@VIjj06@J0eX@!B0?\h#@E1Ilhٿ7i'Я7I`ڈ0B!a~4+6 k}ymwUc8ƴgaa>#B!hA| fkS)}5o$]@!B0?S ,>| !`msQZWV30 a(Va>B!"̏ k:P,~6Pn۬:].@B!>&N=tg8LF_LVa>B!"̏ X:Pʯ Yk'ί0LӺB!"̏ / 26W 8ɢ1fi | G!BQa /e-wГycwp;ea>B!"̏ b4(S$9@=})!o٧0B!a~dD 17eJaMZ k-&- Sa>B!"̏ Ldq (Cƨݯ i T8b׶ | G!BQfXH7ʎ7<_?z b | G!BQXT (+>T[Ԩ:*NAq՞uj߲m-TZU9uC4i:P6|ulsHj;Eb?@079'~h_ruASbPy\++/JFBEE]S(1A\[@0KۖgOT-U+ګX0_txos5yξ*sы_~$Oj' .+KF*'*UJ='/ :P6ٮHsjn 5r!*M]0s8&t~}k|&bΉADv)Q/G +j-UL ~նJ0~P3_wM~֚FݡeF*Oa89o]N Uj[?\@PVgf/hϸ&?0֯߮jۀZsn4G^ڳ=\%X,PB|[\W1%&Un򨓳Z{BXg4,ڨҴPU8=NWעEAZb?ajt ,u]K5B={ki]+_X@ :~[)]ÞEĤW򢒫9=JLW͍w+?UbٙQZtZ8D_:D:f\$i. @ű |ovFzsngEk}=nSABKLz]uup^G;WL =쯐S!01KEXʾECL]LyKW"LN<7gF<Av_0_.pFFc=I9 !%&j=W뿾:`{Sb¾jn9]46”}]J ema]K| Rq*֕. |o_MۛX9c$DĤ`{XtKֲV1%&F셜VkQia~N]r],v3z_;9[ .@p׿ |o?sWoԬf]_W\ylЦ9:MiSc4Ҏ]s;@GoVjaIi#t'IgQ'Ig@cLT5@[P;3Lq=f=1Yn tI~WKL,̫wazJ0MK_X?W~{Q=gV=Ѷ:@}Ⱦ} m\хhmmCtKi9[unj|jju-h g:5ْj5Dӵ)I'iomt;=0?gd5@4SUӨȜUsW\, ,S |k'լ봲/ [U7LzCVpg 1j߶-ћ)Qgnv)y4Ӆ/1A\1uCIsʟF8o<)1]Z},NJ:fB u)kVskLڨ`l)ggҾ ҷa/h{g~U2DGGN՞FKP\dW |i4Y'(Gog"{Kՠv#l0[uV)FsޔrC#^zªW^ u@{_8Z2]13Q~W,Wa:>i$bMAJL׃i;4WNjqun#N5-:U%&5 3մ j{j_?p %tknl\篲ɯmԐ@?lj !^1sO$MDT)& ZM޻޹7mႝ6CX:S̬x) `5ȩ}Y7/Z=DgG\iC\@2V;!a~ CYWROG):eN!fYkߤxެ|Gf}G:i0~ͳzV/Gr\Y)f}q=f{bI;=Ũ g;ި>ΈCn%nrk;j>W5W 9 H]|<9] u-h gHh?g+-B{-m.PBw@TNkۚ --^z.".&@08jNeIlBm:wfgir9$߲0]6Z%iq>[0oh:MÞqgIm?j v¯OPӚ0rck,99R'mi9 ևYyY6*cP| =jc.΍u/~p_sg{|[SԵ-\s[}~jS}7!˷ ـDgrOeu}@ .(@Y}!a?|L^?p1͖;r6?z~l{tqw Dz?Ŧe1.Ũ4AoE֬6GsAQbq=?e=SMܧfme43 ¯PӶpFlqcy} iYX6*Y͚^TB1{qLJY}[\{W|[SԵ-\sϷfw_Ե`F3o b-} @A[{V)ǥd߷R .+@0 ~~acl7g͒^,>MG[/yzi7jcz˱e=sV\O8ԇؑIs5uqFJtn/1Ykg˝Sns3}mӌA&z8iڋQ{>Me)1{w56],zIu'~=mႝ8CAZkkqiһ-!L TO]=ws:j2..@Y!aN7cO'g@g^,X{Yq^nG-CLG-0y Ąz~wOgJɞxgm8u'OQ_7vj4[yBisnμmj!JFezIxe=zc ?|꼺=Io>pkju-h ܇8[ܞ u-5)M|)y%@Y=KbO-v߾(~Op0389zη[Mqf/*8O2NfћO.[ s߲ jFi3ꪎ+߅\br=Oe⿫k3 gap7ߧ@%&kv=Ai49m=5|JFB35Qkwsg)}nsn)fj|kju-h 窇^)x>JA#2>qpiO@IY<)}Ug34a~2DomfC3<\ެ} @߳G1;MđY|>[ݝmf{}߿C-1ṞIJ+F7-W:ޔF B/1_k39FInF}G'kout^b:(Q;T!f]Z1{s16 }< qxgƄZSԵ-\sy}nԵ`pg>t4[W'򩦫ٶ0m}01K3vs۞=&w:[\ŧΚw(5ǧ eA%&,GWuŏn8{TxƐKLk~=)vYΡO0ɧq[ۨ쏝jS赏;we]mܑۦy"ՒƃNkA[`?ݾRׂ]uw?qFOv;@z>˾U**l]0#)uf˽2NUpY|>S爲v`^1s%mZ:r wRT|vR9ڧg_}|%&5 lF8:͚Q;T!L1{t;6'8\Yg3L{q55H] ;-3˟B~>!Jh1>=?tXqn':NC ̇/ffx|f~~KfܜI/DAS}ں3k\br=?j|KaY1^6٩N30Eř9_Ϸ̺m:}QӜJ;eҤYZQ;d9Pڃ1{ܒR%ezg!R-7N{st55H] ;=3>uk!?H] rݧX:{( Wgs==Ʌ$?0 >>Ozߒs3,1yݐnl3`!%?g9y |w҆mΫ\br=}"=1E@s{a~]C-1AY|S{״r~ƫ| NK\v+mT.N598;7)yb`g!6Jw_rMͿm႞ 뾔f0ޟ] vv^[#3 -ZT_I|.?@hVC ē;Ч9a/;[Ӥ-;ŧ?u f 6m=ϑLf'矋-y⨌<jM Rׂp>lqlRׂ]s5SZ?6gb/ @&\vE^)b={0A_n>Og؟iw}O=RI&b̌hmmO}࿫BiJb{+o^I?Pߣ>f g:TOg7< qUio&ZSԵ`-\sW[JWNcAkA>u'@yA=DШ,>MGoζ'>|l{W^][ /NO̳b9!p\Ϸ׫mǧ99eZmM>%&5 CM;[כ1wr}e-W7gC#FBgA9`G}չm0eiaڛCAZ.Ն}?H r͛ߙ?o^[WX (*ou1-jt.RfR"I ̇5na+:8㱻Ĵۮl{h_j|er[Sy31]7>߰jg o}z |oCo374"'\$,z}5RgۍcZ]Fε ;PS)v>Snr9v,GTӃ1Wpms.= > jԱkZ~owV|#?H r͛c]B.k}zd9](^lЉj٧oVJjD֩0M_G'{u{.ezgg}< >Z-ϒמywm]{^Osn5a޾nW:Hʃp\ǷImLm'l3GJnj z¯]si/xΫ{Yiä_6*c7Y8Ga|Rs= ژ4IO ˦\rMͿm႞{?6==Yl?7k:P5꤄oڧ2ݳ|3@qRKc8O @9ȫ0\ӡ;VMzTkԤ ٴjϤsz{=:wI8>h׭0m_gd c= ,'xK]ty/|LsOĄz>=4<u6_yfl|=j vJ]gi+;FDnx#Z7mTYd#^1s&^=|I BZ6uYS|-\spń2ғSFwK$G+1y ٞj*ٲ} FEaF:=Uyr8!Va;EI{oٚFu1hK#ܲnpЏެ=s]MzL[2^s'>Oso愺iJLx[}#^k?+#Ӝɾ#GZb]Rf>n."F[0Gz{)06wYOvp2Ěmႝþg6&A+&quoFҍt xQ?܍uZ~ja?ca>+?۹%k+?2l'iicˎzL7v!uffpx=*_Q0SUWͺ:%AJLqU[C9Ɵf+Si }{d}ԮgҷQa{Sºy|}/ess|3Rmʉ~af9Ψ`+Ys5|ޒsz}|}Sl^H?89kѬ\4#̰gjLy>N !p]շ\ӫ =;ŢI[5WKLkVzެfګ^:z'#Vؔ of#c8%{fIsz"IIOjM ɃpAݝWkZ?h zWߏ!=tT8so7y|֧%XڻaaF8 :^?a @ aa>)̟,y zg^BtK_܁>74 ;p3I{?+W<_Փ&8Rw0n~Ui 3wo> 7io (ՇY=a~HsW:P .42v qZ-:z%Va>yk#C!T9Ot]+iZY'#5E _<˴ƎQ:]fa @xԮr@"%!*6rWRRЗ;A#oB>r{)pm{t?vd\YV,Z>R-P`65nM |M=筗!PA&i p6,K'狼4MJkc}C_f;r; 057Va>B!P67CIOUkmɡ9a9t TWg+Kՠb2`@!BY'/8zǍsL-ySm;d]բuFL(?30!B(Npb &率4 Ǎs/mn'R x^F_a @lk@!B'ҷEM񫴖3a~-~eLYPk\[Ǧ`a>#Be?7;Jc5VQgJݫ74Njw_o‹@(|um$cH(|873*m@B!xbm Ćh>H&ܙ)!ԭX@!B0?|h'3dQjƐj{iB a>B!"̏21@,i`cHA[ML> | G!BdC_MD56`o0`Qxjj6fS | G!B&O C  S'F` @ 0 |B!DM~o@ޜgc^z;+̙pgw})bH |B!DMM|16PQ4A=q~ձ5@kjX@!B0?cZD6L|Zg|~=)y:ܕX@!B0?h]ӃQװ1@ 2@B!Z& \yTL?d,T:yblȉL9+0!BGLq]ȉ1z(1߭`h]T |B!DU7Ư@n68;"(T%05 |B!DUa@|lh|&vDf٩J [*Sk | G!BQe 0:i)Ȋ/k9Dqpg)i05f |B!DUo"ȂZǖC (,WVy֧v | G!Bѥu'@Ffjc著0$LS<~?5b @5ue0Va>B!"̏.{:yǰ/ig=)㦞@B!˙&8evf!gwpHSKba>#B!alGPx=M2Ku[1)LH5SCzba>#B!0rm9MZ k-8ojG; |B!D]ڈRӢax );^=q~7k1Xcfa>B!"̏.+ilUbGvk`g9@!B0?d"@ !WG~)kZԨi]= |B!D] :`yMlɸ;*u篡FLԄba>#B!܆/Ax19QANClԂG0B!a~ڄAIZ'C*Ξ8[ leX@!B0?lֲaZ!$@Oc Ď]L;Va>B!"̏2Wco:vfvLh'ЯdLX)7`a>#B!(]vvЏk{^j2lp) | G!BQf E5 T m93g|zS &oJp |B!De1^c{N@S1$Qx*j@ 8ݔ0B!a~wE-4Fkk -sTjOG#Ղ-P\`X@!B0?ibE)-:Ն 9c3nS?S@B!ͥ&8.BLm{-UzxcAd`1%+0!BGM.]Xrl%:(L S | G!B}cTk>WװT$w=Va>B!"̏6? 2IV>$^#B! 2+P{6`D1Ea>B!"/1A$ĈƳ`d;;PMpE^ +0!BGLq8z`\쀌:;!WB Va ToR +0!BG;M1NBLH+P$`d{uZC| Gl<@8Y2LR?jȁjPl2K[ba>#P9q= !S 2>PLѺ6ڗo gֶ %Wg2/ӭyfCkBViw\@L# =.n݄ g6N!/{QB PhK7@#PSu"> b8 r | `ț%jPwOAu8erN |P1u/uR'H!r 2P,?$cdt篧'1ʒ. @ Oxh+ Z]=Ӆh)m!r&݂nBŲLG;L&!Ѥu@eH/aa># :JhmU?VgJu(Ս a~Ly|t2: INwLj@h,P:zȻ^ʊL| Ge x CC6Z꫞ۧR'yt:K@@ǔt|{{iv@|}zSؔQX@:Y@5XVUw+Oyjw4E,"̏+&: w}PZԨTN@`F@)'b@!(>7B040pv0ĒB}7qZ!P"K/@7H2Rbglݷ0 lNNJ{c Ҥ;}))3e@2P3a STS \gWfg="իF@)c@!(߫C(86p ĐoynK5cD4ĹBc D;M+ a>@i86`', t;31lYuEZsYZL+ a>@i ȯbQm:Ďj^ƌ2e-@䘫:U{93Yj C m]@IyєU zB唴~Au*:(CZtfs^u@x۔.XeJ"5jcUw1/ToZǻ.@|ep]X3Eνj(I]a@t!(63TU]c_ȼ0 f@1:uܪW寤700t.;čOwQVizlQeBի[̲%p:fD|bHXoQ fYx.6Ċ%`Č2SD܎Pv|; @QY`KφD|³X]RD4߶Dl,d366wbE:oʔ& M3Sh? BPXA}pGcY;, YU?oLx]ee| Ϙ$Po5)n<} Ǘt-WhU 6e7g{X+.bx_+쳱ʞɹ݊hj=х00ZEA <0mfcfA ,XqiNJ2`VصZ!PQxEUK+ a>@,WvN1C6}.Mahx ]1=-ֶֿR as7Wj((VD|0Q#5WFo}Vmi!r.N73"L {' mܑO0 ݁х0 ,^}|pی;e||`jYs{.@XϴaEA[o#@D '::=M9+ a>@a5gy> )<> ̋9D %O,",Ю[z3XWsW_c SƮ BQ3eB mƜk|cF̂sAaX#.4XQ@bT8MZǹ37s?dD|YFHw19i 1O=L 2m&XI)5@ XzuܙvUc ɦt]х0 ?fjT y*"0!(?3 6afѢ*, 1buN1m6%t.zϯN U{µy4b<;̈z; f*j@H 7+ a>@nPՠ|_\$v#O =rQ`Đf9j2l)Sc@t!Ȗ%ݝbojW#Ć}L%VDovz; l1s)Oc@t!ȆyjZ`j!֔%yE %4bBj6aEGvrgx\ZsZa-Ҵ VD|L|4{z&cMrda٘n5-VXZ!m@kz-Ț )@d!XXըnF- a~o-FpiwĊҬ-7C2v\5L,ΖyUQ%͐"!p]1~Oh/ 'YIlhQVܹVV3`-5S1 @A ZvR> eیh%]5T' ןtFQ^ѻ ~83Jbo!Y3Wu]Jjrl ,% *_&N]}qj(# 5Y#uRl^bJX{Z|+LCZ}!9sz:CL 3}*OjQiՆjB9"V nӢ_{U)R5%J?l40eu*7I;%M$mJhhsKw oFL6iQ0`VuP5`Ĕ4|+ERϊ6/c@@^ >H<Q0T{7mUāT ]X>0/X7Œqպ;BcpCiL+ a>T65F5I3oF-H\ebbFx\U!ӤNF1T&3TU|A~w c'I+@{<(oo; cL#VD|<&>ߪeJo$|/Cwng`@AiQ3J N'lCXըo k +.2+@(0+ $nG0@:tB H`K̩34*P),(mjruX>V{kb (c{McJؔc 7PSbVrSBocFM"T EuP~Ėr) BΗS7_k @ݔ]$Io@Ӷ1LQӛZS0%dXUyWv\6P` ݦm I:|3 NJP{ػ 5CJhdW %T1%OI@D!dsG`SbH`ŊD5>nP[uQaK̸>'0ʏ5R}1~j@`Hҿq$3 (91+ϻ@D!bPT $k'\/IP&ϘVD|(0uh AgJVIU0#T.s]5;"S;O4&jbB@ƨFU k5j)@|nI:?o܎z ;"{z Sbxsc@T!H4 {[xVpfNJиh ,WVv`T8^ф0L j5k@4ے=f$vhGU'vDuF`K3^빘M |:u^ [> $n6ÊPNz("k4Vnx +{Cʌ&j T m8 p{Ъ^d"3ДG {"=1LYP,P:zzl]URQmz+ a>qU{_JV ލ`8ێ&|}ޞ CʖϵsW1\G @)Y&߭`B mF-(rw= Д[c*,KbYPШg{5on(> qK<{H\A` \+cV1i =z4 &\M)"V%|pykbһ;␶)Q8'Ye=i H0Ċؤ;&MCg[4-+G߽B cv{6qfi)4I ӚXKA1wh|@ ӱ2~A` ]ck(#EE?US@pSR85 ycq~LSbvx[Z׌2$ﺘcyBӐsPM@MуwV~1hmqfA/z4I ErƢi<=DS֭bևZk s2xƌ}nU2r*z$gMc2n4$\=HSZrѕOŒ֍?9 ~3ess&a>0bu</cuLkwrq)h͋[ cikK!/7DlԶ Gwjo=$vCLΥigiF)YpnO28`~pF-њ$tYLV G+!g4mk4<΋-[#4dj:b]XWj) M|.T]qqa|8{Y'h9]۷V/qE5bQlN$Ԣ) M|rbʽAgg&6Ss}W_>J[7)?(IfSk H0m1{b%,iQ4-懫5,lғ6 Dĝ1[7qfI֙E?m) M| Dl[ 7KMu6dwuQ-l}uߘ(IO( $W26- ȻuF_1hb~xlk}!PsqYV$Vݪ1 E|wkbY,.*=cY} xzk|@\AnؤmiL!Wr.oUR$zۨ|큱G#gO5m&8k`VK_(I7h H01;bby\0f6/35m>[;k孵 cuT4J2jR$X(őqa m_i1?,3:g9.G> (Ŗ1wiDR$6dž 5 cy~m/iw4еcf1Qu#"a>0u:pScŹiM 3j‰z2SmNsI, Xh H0X{b%]3#!OĒ*? qum{{TLkFY4HT]֭-bo'O*IM o-fh8FL`<(~Q_SR$f:/V& 8.4dř.M S o߭9ytiUy\ "a>t|#XS㐕Êm.f [آZ||NtFiӋ8^S@@Dm1poh2re?mZ+Ɨ5Pձˌ=F ?qqRq, 4 z]q\SPrb8%6mܸI薘vDܮY%EۿES@_جoq!co+4%OVOvC *2cOիv !_kؠq!s*gk J^__vcMN`@NcyI $̇ģJ180V)DdqMlu}m㧚 )Gk Jִ_xjk{\d?(w\"a>82,+|:)(UfG~Wu:\C~1厃7d{M@,m[c*,KbYP,5.fdtoo#79Fy۾vijr»h H011bQl+?|Mk JNhNs M2c[|Eۭ xIMJAc"4&aUs䛭oOS;Uo@V3RLS@0~IJ_(ž1wh+J@eY"英aqu>c"6j#&vRuD0FnxH)_O՚zpcqS**fH@B~O;~QjrqUoa>b2[ 7 M=ZlĿqUGv 11glϚj a\+ ^qd\i`A6-i *O1Kbuk]j$XKv[Ť[V{^1 =|ukؠmq-)8%;wke"O5J_[Sƀatխy1q/;ӗ56c{.]$Θ{7qffEK) =|M7d+n\_KP-AU_߶V_2jVb}bJ,v> (.S&bRX4Pb]뻊ã>k0ku702{%?1~Q!a|-XZ w:M5:Ntb-qPkǘ~_qI^yӘ4K|N&̇Q.*w/N5{fq)EbޥwגX40/)<:uJ^nNձӌF)C„?Dl[ 76X0@_\kot,xekWc ذmW1wi.R{4G)UMKAc"40P-Wj :2fl1mkt10΋f35JW-ZhMCIJX\ Uq[hXSq1SpLݭFU 阚9ű<,zY^oa>83cuLkco4=)>6GsrOH6St.0Vmguk:\S@zcU=bX?40D+j :bxYКxȌ=^) =|HoceܣoGƥ'bW|uˠE^ ;c2hi/qf1E+=OS@z<6,dܬih}ItbuďmJrnc}Xl-tMC8O44 ŕbI4GUkZ d˱댝XT|u;" 9|hҍ*XIJ8S@k}cUlZ.u:-1v{EiLme(f?MCS.غo+M[S\[j f[1W|rDcN dW;=vSS@zЄbElR wZMI8Xٳ#{VO[u'ձqF)|h4G5߈e?<=$y3sG_otu&beiw*w7EDwZS@z0f:v_ˮ BLFu:'dž 1G`=SS0s&qWx`N7DlԶ,&Z`ȿdbu֘=K1X?40V\jbR۱Ik.Х阊{s&?4?WDlS 7SsY=-ޓ{QlZe=6V vQ,b2ep܏*NHa1^UyG&NLhlQV{Bta>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC ,8/q~> `J7`pTFe_ })쫏.6X|g&r҄;0!\+|0.mc{xwt6>ڠ `J7`pTF%_?/ xq~d{=ǑqB|!~SngyZNa::8ћ҄>˜ǡOl{2?3>Q%w#7¥-0WD_mUOWOYߕCOӪl`G,ϕ&?̷A BӟXF׊09 3 =!^C{0xK8&&bU|).#orJ[ {㞳'asqg'Tor_THzr5adTbqico㰎8:NS;q\i|k: s]c}lG>7o]uC<~5~W"vuΡׄƓǓ;;>o Б7G)\iùքi>Þa|XN׊09oC㑱},E[nk|IݎwYbNo 02ך҆ };ìw@qjqlk'Xƪq{ `}qFdGLǹ1_4rzc]^_g-d^OO""Ւ-9=l;҆ S }_S8?>&}X09N08#lN_㪭0xq}l_vWū:{q>K{[.sZך0_o?I>jJ0}wg0߉4ta\uO""Tz ⫭Ǿ}>Kȫ 8?FGA5~ ;뽲_dO;i_QYjXf3Ra]D|( C98/?W@|"NvLR{Mo6)3/% f~}vW$N\WK u{ޏ|bң4 4~ [b+9(}s0'IA8䣯k~2'GsQVqa5~}a s'>׽ ow}nYx;9c]IGNmc|WSe/:߿~/.yR >˯r'i''^3~bьP5'xElWZ>6-n'y {|G|mNOņmV;X"wx0}tvrMㆽKcg07wjzx\;u^yO:^="gspzgZa$~y՜3f}w5'wcpӬV_{_kw-Tzt+Y+m5ac0X ;̯g>=4y@|qWR-ǰ9br\6X"̷~a~}=#~qi5Kag0u٭}o]v`N8rW޸-H+z-0xxrs^WsqㆮRoW5'ŽɷtOn_#oң&5 ԳkM0> ءUoCWJ:mH_+|;qccx>.\fvrpco8G?zzt~RTA'7+ۨtûku{?ߑϽ9ؒ83Y05a#>X >?QەlzN?sDogVc#|(eT?}\;&q:_'!xV+KK?E0NuDͯx@,> IsW}( z-0xx=㳞Sz+;6:ǫkO^YGڡ%F^#}8gcKQ86ך0a}ҷĎqTOvq}a~?sDpqL=fjlaŸ͎͌5kn'>7aⱫcʿT0o[cWcl=W^[U.6Eqœ㍥!,z,^;7=ɝ\8#Km^Pyӟ+&y݀OU}Z}CORORzw|KzrO ql/sk79Ѿ=/gvGhlyB)/GZ>3]+ZgW̨>JO^sn?\ztXU"w|^S59W{_+|;q;;aFbvojw7\&wrMpcW僧+O)={}|"~Suaz>ˡr'',[;>뭥g=Ԯq^;$}+{Z{~EDciy }Fa:WsF_g|/*c+.oJc8t63?M1wsYҵ"̷~E1A#0w?75׻N}\;&qFW?j=U jZ}CORO}`DKjBj9Sǥg|EソȻ*_,pT5a~aOaܣt ]yF>U[{ŵ#;O1̷z=xx_>n;&q ?3и|U'D^DM?^r5a${[gw:y}•]zソȻ]޽,k?ך0aOnaܣKcKxn;&q t޻ig2S5)뵺,^OO.;>Mg=˅z}vdJ|k^{~ϋb wx7{&|oW?(?Xy+O˺G74G :̷z+xۈx_>n;&q -=c]]A]}H}}aR)jzvGvmңwz GZ>̷+̟oާ@=[Yz8u`sĠ|1aa͍ㆽkrN}.0W1ו/"PO1 kuY&7;?zAY/r!y^>{n˰^#_;϶xhO,k?ך0aO^a|Jz_/=~sUF8G :̷z0I^Gzq5kv'>UVؕoPO?uZ}C ''\zw|VëBy^>{a˰wy_ٝ\8#\VaK?Ʈܷ쳆z"Pz(\g90xxY. [HgG {em^#xe': &|o#oWU37}/6.=nsĠ|1aaa`͍ㆽkrN}.0޺ҳ4鳥gotGZ}6&7zfv721]UyOsM ~GǍm-{~|m-JxW@a(>)X 9"^ .=q˼ia՘0|pj;f͍ㆽkrN}.0ZcWvC=~Y5v"]/+ xxY\pgk|Igf/#ʮ~0g)X Rz3 .]Jm ia՘0|pz;&zMuㆽkrN}.0?\cW]z 'burYK[%ku0xۈyPz;k[HgŒo7mk{/?Sy&: z, No⊵0wƅT}V>\y㑱ü}k{-oKcMcll\,{?z00a(X #W9'nlkJ叢/o2?;&qM:_[LJw.έ ?=VU>{xtm]ycwqg0yL/KY8"~.oKxwy>G~lOt|~ʽ"S⌸*"nG[c^#_۱w$4 {, Lo3 +F5z`|%k+hi90j,BoG1.;&~G&q5kvg0mT}cY'aDtm-ٹ}nq?}lǟ96^ƣׄƓT̅(qMhڡ+?|]7\|ソ{]&4 {, To0Q97iNsU=$y;̷KwQώ0}\;&q2;IoNC#S;zӧvS~lG &̷KqQώ0}\;&qM6:61^%w幱٬q<`g8Ԋ9[Nq۬?{ep2^ƣׄƓT̅(Wǃ}׏ld} ~Ǔz[Ɨ\kw,ޱFk"̷ka~yqyz7-ylsDaX30ߎbv͌ͅR5kf'>7asؾcS<ѕ_#_qP"*wٚYۣ#"b9~q?}ROBgk|Igf#㈸i@KƓ}*w)t_#ncy/*krJY@?0g4VM򾣝>G[=C(iGd}\;&qM2Lj]<}l֟+{Yq_C\& Y qLlzm'n-ƥׄƓTGp )fK~Ɠncgm5u-Y?z7/7s)Mu]i3 [Ɗ0?b:>sSD;G>G[=C(iGl}TvrMㆿKag0e;/vR#'q]# 8$)xw{;;3Caxe__vƛ88Lg0xꙹqBe_ϊWŇϦxw,~O<97*_4 4FR ga~=aϸXq~?^O☌ MOVci|x(MMV's0Oa>Ý|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% 6&RIA#oRj aK2ׯJ)Ƨ+F RJ RJuS+R[|| RJ RJ)aRJ/WJ)a0_)|RJ+ RJRJ J J)%+0_RJ R|R|aRJ J)0_)%WJ)%WJ)%WJ)2 OR#]JfiRK>^VJ#7ޟהRJeTI@8a>$N@8a>$N@8a>$η&2XIENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/assets/custom.css0000644000175100001710000000267500000000000015733 0ustar00runnerdocker/* * Copyright 2009-2021 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/. */ pre { padding: 10px; } @media only screen and (max-width: 875px) { p { width: calc(100% - 20px); } pre { max-width: calc(100% - 40px); } /* Make code text in side nav lighter on narrow viewports where it's moved to the footer. */ .sphinxsidebarwrapper code { color: #ccc !important; background: inherit !important; } } h1, h2, h3, h4, h5 { font-weight: bold !important; } ul, ol { margin: 10px 20px; } ul li { margin-bottom: 8px; } /* Float the logo to the right of the status badges on the homepage */ #bidict img[alt="bidict logo"] { float: right; } /* Can remove this when https://github.com/bitprophet/alabaster/pull/125 lands */ a.image-reference:hover { border-bottom: 0 !important; } div.sphinxsidebar img[alt="Donate"] { display: none; } /* override the inherited "max-width: 100%" in case a badge fails to load */ #status img { max-width: inherit; } .sphinxsidebar h3 { margin-top: 20px !important; } #codefund { margin-top: 50px; } #cf, .cf-wrapper { margin: 0 !important; } #cf * { border: none !important; } /* Make cf text lighter on narrow viewports where it's moved to the footer. */ @media only screen and (max-width: 34em) { #cf * { color: #aaa !important; } } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/assets/custom.js0000644000175100001710000000257600000000000015557 0ustar00runnerdocker'use strict'; document.addEventListener('DOMContentLoaded', function () { var sidebar = document.getElementsByClassName('sphinxsidebarwrapper')[0]; (function tweakSidebar() { try { var donateH3 = sidebar.querySelector('.donation'); donateH3.textContent = 'Sponsor'; var tideliftP = donateH3.nextElementSibling.nextElementSibling; var sponsorA = document.createElement('a'); sponsorA.href = 'https://github.com/sponsors/jab'; sponsorA.textContent = 'Sponsor bidict on GitHub.' sidebar.insertBefore(sponsorA, tideliftP); var tideliftH3 = document.createElement('h3'); tideliftH3.textContent = 'Enterprise'; tideliftP.innerHTML = 'Enterprise support for bidict is available via Tidelift.'; sidebar.insertBefore(tideliftH3, tideliftP); } catch (e) {} })(); function addDiv(propName, propVal) { var div = document.createElement('div'); div.style.marginTop = '20px'; div[propName] = propVal; sidebar.append(div); } function addScript(src) { var script = document.createElement('script'); script.src = src; script.async = true; document.body.append(script); } addDiv('className', 'rc-scout'); addScript('https://www.recurse-scout.com/loader.js?t=c17a917136a40c38f5ce6b80adbbfd19'); }); ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/assets/favicon.ico0000644000175100001710000002463600000000000016031 0ustar00runnerdocker )PNG  IHDR{`)OIDATxw|Ew\r$DTTPT lWEPE>*v `ÂX@A(Mz~redww\.߼gg>mfA"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$Mt+& qjw¨qn`v R=!~b6'R)Mhzs WQ /we/ùvnQB'9lV]#_,Fzf3dGg?mr48I2> E;MH7+)R&N}pHZRNK Gr&c^1c/!Is3?Qv34bP !pߘqԪ]W7\CrbBwL #lw.+eSîZ`JJ+]ߚٻ ݞ[NJI)986+38^NJa< #'4q]WCT \+>J,!m F @d4W⿜ҢJ 9-c:'tAv$1ӏi8m8=ZMmkoct+Cvy'_3o!j b>6u2>~ 홗#C"fk>kBc6~cR"WKf$ K_a ߍ|&p>c sZI G2i$D,IP )lTSԋd0[9[uXYLQB;\eӃ[Fp]wl. I!vt+dA:X0aˆQ nܸpJP!r8!ʩ82"1e~9_q5I-v=37+(\h9A3Зo+jA˒Dw.A>7QqY7QMYlXxķvl>u}!_sCyO [X)\b;nq<Z4Vq7SuOXݢC9<@[c(=d+t)dZ<ȧr*bÆ .\#&̘`!DH& hK: ē Ua`bcƳ[gߟa_#CA572FŤtI2gXN[^~&2D20Mt4.e4[PPV&z,;59]h-Gw>T:x.be\1$j `O)f1KFRDۨЃlJ"Dsx=;x^-mٯ&%d3C#hD 2\^3iB[a.q-r f3]bq) FfI/[Nbxb+DЙ1,$) { w^_-y1 !kj$g-FL/95s0U(my7ptmFR" f%{[) h/FSIR Lf.5([4~5+HFFx:M; uи*v(_dfenDLHg̷ё{9+#2 /b.׆#}lg#kY~0k_KH7P#g,b8H@ \ȊF:w"2 ub8A$r K4SR.387D2l:-e~9f& 5 {CI!5}ewDl!HA=y yLoK$U)7F{FBcbWdj|.c С"yΙ(kZe&d9D=8fmi*\([3E- ilb rFD (&糀Ku~T7T?qy0'x~ 2.(@7&Ac>'OM/ [}WDŽXD3T}νa-?経MA0ǯAw٨kKFVf9E~]l|3/렊"R3.R9^a;_| ìݸQpyMN{ 'TKhߊ"=oX43k@- ۜ$W1\|r U;w=^Q=&.Y̗((x=572qM^LO#l]-exb:*Mک#RDGm8tOל8gpjQx~ƅYz]"_bN*UXA \)Oj2YBWANf2-1rYr^rN:nHmb^!8zd~c/E^3N@wvўSSo}F99D &l䳖Oxq<{'7$M!41tF!3y}kzj]tfHjJNo1UMXQX>&,'j?F ϣW Y'Y5Ȕ-75;FܽМ0ۏ66Z, 9f_ۘ@:Ё~7Y/(CYN?|:AB0ج X֬5?jFTZ=>nr6:A]H6~Q9jFH0} ЖPPX^sBs59';y<" ;Xýa: 2oexP^vCt N?{a~/md_謰=Q/4:IRYd2kmoPOcC7z"J[~_d?a_3Xw_Qa|c'^;s[يk?UiDҦb`=)mmRO*QPXgձwPǰ৊qW( x{ySXe!]6& f GiN={7W'¾Ub_O5bnPKAydoiFr>qad& %j]݆2'|ߌ ~O@Oi&`GP/@"sb0s+1/I>K0TJ4:\ @gf1FVKҀAǼ3}%v5t>S]|. jZx1ўK .!7y,{S6>1媟wٙ+rHǚOe8+tWy-৊VswolFғ`gfrJ/@Wm7 <IG>fUoqJli$~~R ϑ8z2%JoÉ|߸3je:?vZpOoco4j(=5^ldjd27'|^g_rCÐL} uR!y)"ϝJWTf1t#>7i7*Z0뜿ii:N>D:w+5VN c><xTd+#H&i4PoJ5N MߢI ډ~k:ikA3F8Qp1^TcW% ˆl(6~JCofUq?(vf8+3FitJJf.$K jQ퉍x#㏀L#oOT\dâyǝ-(Lܡyd 4.F0fb((l+PP!v(ѼNYrҁ׹YGקW y>C6* on͔T͸%rϲݥvxb/™TZu y&ྼu,gNӘ~`(Gsɧ#wD0 Y2`f>-=Yu a U()-4hfײ&xc7o\Y.ӑh -g6h{6{*nfAB7"ŃG&#p\C52gG,I |UpS5nV1gU7M%o]|DK $Ha> \'O\yTm5'?q]/@U',zd r 2C^`1K IsU8GRy j!O<҆tRI"8,1 #W;p`N V(/xF ԅq`3PLp^u Zj Ou0[u zOXuc;Ct5䫀@mk|&,I&t$t,Da"*$K 'N)B)J! K1Gf'1$ŕaVB/S1ГWS|K-?sNS5a!ol@m*|qt\F߳jٴiŅrvLzNmPͨfN/u&v`΅5yΑ61ɕ:Ac~ { zďL`Q@wHnoF33 x8EةjQxY(Ċ?Fc&G'0K,b` BoFs-m'F_*Ь_H;[/2A:y"C8߸4,f=#Cȿu_17ZSf2hMtdЃY:wR+r( *Rn̨2` O"BmȢ-I'xMNS,Q66R~!s09TMdOrͣ+-E7T8 ~Vg0O5,cݐaXl܏tȵZ*-1߬G(x(q8Bb> !n0U=/4g12B, F` Io!Ƅ߉:gϫ+2J)jjXL )1~J7"RN' oFZMLL㐷Rg۹IgYxB*O7r1@gmty-Lܗx'g:rpW̡,g(Ҩ1ēD:ґv-h_pR@y"䲅Ogp&}*"m/9} `TBY@Z}y/`bY5P2O٧V!K F?%okxM^J y4Sϭ<,V07iJfr'8\Q &'[, EP>nS']i^U?:).ְ"[4+9x5F:cYM׊xLn2Tf~G6Q ɤ+'r"=N*q70aF`,qU@ ј9 tL |K sBcIjU͢Ǝ*zbV38Gɑʭ\g&a/;7q.`R)(bIB4QDItQ8|jVoDqIr*rjNXIӝK][= դ/hYtEp qưA^'kx(?) 0xCylMqb(TEvՅn\-:رc"(" )J؁8RɜĉGz wbSL8>}-.Ss\ gޥ[w в|Hyd9bjmf=&o>3r3/p8u7f's!BbDo/Q}̘5ذQC "pK2s:ғL+wa0RqEPKdVYFOr6q '+>A$Vʨ*Tclذ }ߩntQB7K,,ĉxH$^LJ5䰗 eDvJYEjxs*9\i! oPRF뾆.ėz.z11ۓSx4)QGȭX}Av'_8f (+b7hjT"tOL'tq؀h/4'UTNvR#>ۂY3摤݀n=ԓʕOb'S9k7F*1Mtⅲ(37.v+߭QόY[${9\[pɱD"_Pb:GNّ6x"@G5?3e!>Uky' cR3I$")R( l8#"n& c $HI$J2)$}#v(e[nvQF+Puw& p ͠K(KY&]Gd5p*7o)tPرb*J\bŊ] 0c! q$ImiG&dAQ"MlgZ}dfÍ*b[9r ˹~plALgN^5j7f TñZuEOTR-&_I$dҙ.tiE5\q:M" O p @&CSt3> ^ب<2y~3\ލ2Fu7ifhkC1hÆ)"DdQ)A7;]Dof wk`[D~5|-]b yuV3Y<()2)2ʨ*ljO&a[S={`O`LJNjj*EL"a!B&]EOӕtQa^^ENnJt^\HQ9u7aLCPRH!@"1BBM") |(Jh dҝxٺxFshڑff3Q\v*tRH#Fj5tS+}baBiۆa $HEIC.rC.c,m]d{ WN`?z\fsͅcսX`qPCPɼÎrϣPidA&,EG(bsTS];sMiğYX$(WeRvs"<'i{iDNR]1aE[hKP"%^vXIĀ+bMÕlA4<Í`-( cр9\B@D>~A* AP;# Y%`.lDNBBR$TM|~_R%Q2ܘX+#88iu)KTOQpǚcH} IHz 5w 7$xֿO'r|ePx>#cIl6g+ O5Q$^࠿no33/&U~sJ{Hkྟmxߴզf*LQP~I5/v> |J~hzou xk /{Z jkL~ιba7Lx܀[˒\uG8W6_' I h] u#kk\ ̈́~ Ƨɸ4dF?<۳:SPEGs_ \:h%܅U$@4WcοIo8_mw)Ӽ(xXx;gfݯ*u.o%a5Xoh=,"=0~%-Lj{fQL?TO辳2 V:)=@U6h?([৷#?o_yNO6 G')zgptWqwOdH@0#_(g'ӟtHQPgi+O)hUPQYb{{'Jﶱ ۵SZOF9eOjYj-wPѴF_D໶1[9 M1žC,SYW 9/ ]^ Po׈\"CUQi8a?GV?q|b oC,#`k iK}2a'?eijin_Mw0+_~u⛽74yo{)I<*÷ _6YȲ*GBYXd2r (/V_ٓ 79>*|tMK[̐vLTwq_:wP03}/Ÿᾫo,|-q}j3,f#,O ,B$_/'#V3+rcnֵT9Va_㩈e bV#IW3=54<(Nn.6!B!>q`oڧ !iA6׼9Oi#d9QYwvIYIz? _}ZߵomZ|;еXdTl/Yۓ$Wyӿ W}B~Ejn6aZ.?hleL@O֍x_u-| wz5W%4ɮbk)&FMHICa?gO5=v [[Ÿu#0@O&:{ S%Xj3Chݣf/;Tgؠ~9x2޲DYir?q)9\0; Z(\u?4t}S sM.A|4musYX)L'Pe"p ?+Q29^?߰}›k]ېj.nȖю"+)״; ᫯meXhI/br(syttcFe쏧lti4+m)`3֡9W6'<1q5KU_>]:[x>?.o(4xa`K$+H'_c:HvUs;岃#&F$~1~#T7WxEkMOӡKx!PEQ*e߆5oRkg!zѬ*ykhL!Ҭ'+[??/z_ —džnOXm`c 4LLx'!ޞ>fOG5j5ڄ1WQ1xRئVBK>̬-s']/r"8huO?pq*%u1.Ҁ?+Ѿ⟁uMOQ5;0X]7s;eW_/6 1>?d/Qt|Ww-,4X`G?P?446?f|خ2MKŬ?4 ӼdpD1mkKn׭~>߷gawjM|E-O3M0rټΓt'o8]G!uVx6u+xbۇW1S G ]uD\2g_0_Ұ!e9&?jM5cm ,686((K ȐtlRA1`[N5^o-\#sc_9KrᤵmQ 7|N|Aox&K-Er|{"yjx|I;O@1/@~ Z|tSm]}q.r6&_ ) 9O _2?ʹRU,Yٖ/;e>KB jBTܬ[ϭ.7xxQD=s0(%b D;]V)`V_?e _@ C} .&np>Uڠ("' ~>%oiQD7SiqS53BC ḄnS_[6z)°J g,zPg%5e20ȿ-ުtoK7 ȿO2Wys?|EեԵfKh̽n`$0'lH72)' #Xk <;{.y],iZKɲI)FXc2$ IQoF"Ujȍd[I1@l kg3F;H?1 ׶EQ_˯&_ao'߅m>2|p;{-COV#,O][s n4?%N$GVp:^m- !t1 UE|$~Ŀ%GWr_i;q'IBMB( gCÚ}mQ).RA*; 2F$Ĩ`9kn 2;# _#u^nUuY-%U>ĠPoşSjj:i;R{h@R?i࢟= |$#N (F9`|y~ /~!<+XnQkK5eTɒWC0_~?W~K ˩X onn$fX)w틮~?OįڷWy>bWb[-6:OS@ 5Ŗ~nN)`Fex!۞ \9ўƿ o[ a/ kdW>/׃.TDkx_0YQ_!ޕşjU lKK8ܴqv"_To#㵬i]=<lڷ52ɩ\#=m~7gD}Zw{mpH_Pk !{VΣ#Oy4>G_=?1;#LcEyknͼon~Kk}O}Al??烤ΌxN$B37,T0 Gൾ/1<뺴uɬėK_=@~PF_?Bh/ #mz &邲ƽIS~@<]oO,?[7/-jp頼*mр 783dPo'?uM'9+xosHħl/6 _5U6s?R8qq¸r8yOF,|BNz _A9v~.h:RDxAs pv'?bgؖ[ٶx!o"(wA(KwoW?~ռ |ca=v2@}oI걞 >s=~z=|muK_+Uv|_ ޑyrDW袗&!?^.=FܱQC-Dqux^[g<X?m7 uߍMbo oeּ/uk wmGIuk]Z  t6Q l?c(ǟce# kMǵu= GխnhefF'c?4#m F.Gmn,V{e]ġklB!dQg,O|"1L\y9|AGe}Ne8qܝ: ,v/_ )g~1i꺇k;6v0VVV$n`J:Ha8i>>[DvO5~_b?(?[ eeUEܪsn3 1zrQY4 ik>$E4;}M| O -+|Ik kV3wOv>~~?GT%9>7yCQ}c+K?iC>>^|%<9}?xvtn<՜lxBAprh$xE;׬@ VL?\W7?aܩIl_ j`+F^٠_߲NJ"|#j֋i+ Gif#2 JC_,ω_yױ^t"Q11;Mpgq lk/2'~d9uldk???AxM?5_;` qB%VybI$mK{t *J5ߏ9`cꖗWno4x֑5єh(k ~((޶' 3hЁPMSx,f5MB)>h~_1|~HGOEɴ5PEPEPVY^ƳC27Ն <GO?w:١c1|d+َINVǬ?G8=+%K!Ic2!״s7JρJP` R/Mm oO.,HdnR#4Qx g_7/'|9wx%AB9U oe|/ֶ~3B1&2;Fj(N|,c& 4l Ӽ+۷ X5 WL]CTcse}ޣi+h߱wƯ ΋^ZQ2O$hYb'捏N %5QK@'~h,?_?LxhtB~}IAGԢ@E!8&SN: ON_ í/i0|}n#7%;)OȊs(Afk7^o9~.̇YBq?o7Pt |&¶>$+;]V5 V6 xL٪ W.kH< +x.uW헳9׎x' gP[MgPEE@Bj/+:nkܣw %+ xͿ|jG\b A/, eW$%ُDu;@&kH.#M G C5+/_JyZh)?cxg -y?^_,F=mneE/zg9?Pox:Em-vw:IWmj^ObJT1x -cnj7bHH xl5g<3t=]i=M*`{GWh Z? wlQV~wTBs ~ /w|&ގMcH~ 7is_J(i?E'k {DC[h6uݍlC֟ ȃ5o 3Ix4{/7kɯo???QcoOh?.MGCX~1R[IVj((((:~Ͽ?iχ7h? 1֢Omq/U T_w ڶj4eO}~E>~4P|20.~V>XI5o\ZL&R9Zq?{࿃>jkq{cкh^K;fq2NQxd἟ ji/ʷ=vnKKnF랇#/O)GW{ 6|2oֻ9?c Fkt@bC"I"n@?]k Wՠ~z֍e(ncmbJ!]#M3y[\rIg6⑭F}?<K/~W^.Po-؋x^ɇ ~?-Nn/\Z£t/=c 1bk~_W@[|c>#[,abo =bk}%n23>Ǿ3 )h)l",klW릉~v~ͨ_[[񒲯?g;&IJ.~΀?/|k>|eԁ?7:GH mK'eXZ?#1;O_\ojVhDAK;'_bմQ 4_zq[d̒xbT1k!%b+hULj@ x:ãJy됓Mʀ?M#i{KGL* ~ocxx2o}pۏɱ_NL~(FS>m/7sWse+*M玍 + 8~_lI?e/+V,b7>}{GА/ߞnFE4:+C^Ds\z =?ï/Fk~"oD5ퟆХ~U%V8-Ŭogހ2~&|/|+^?'oM*|˧χ)A<ַA2SwmD$C~XMgC-?SZZ1~ +4:~Yo1^kxKMm3wxkv⋹{+Aq Ċg_g{ a}3>泪޷- ɲ$F*A'>73D\ hZT?bR;yu8 I?߿,#Ñ{aqKxÒM,Krl..V88_*G-ׯiV~#ĺh-o+K!u H"_߰e;|!T#7|c[BǻkʭegoYçٮȠEvUEPEPEPEPEP_)~_{|(a/hlXd>"[u$pRA :-bY7xHţlR< o-Gm?dýko2dLtӦvsy5ˢ60\a~WS_U){<_c?Hc;#+zI5:ODEjc"QWkk[$6ym{XL~wm^QFmS?a߉>ry_ֿ (7o$Π麿{7QIk52 xŴ_,MJ?[?dP{㏊}>ѧXk1̤wS\j6j#WEV:KB͠~dVó'RyFE~אgߍ^MoěPegB{şO ?+]NpLM>Uyo1>ޏ&~邿ҊMW 6s'gZ\ZO_h(?R4~Ͼ~<]MgTnlVu#Mu27mQ"8^(././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/assets/logo-sm.png0000644000175100001710000002274300000000000015770 0ustar00runnerdockerPNG  IHDR|~ gAMA a pHYs%%IR$(iTXtXML:com.adobe.xmp 2 0 144 1 144 253 1 124 2018:10:17 10:10:23 Pixelmator 3.7.5 fF!QIDATxw|߱gU3VjVQF{Kj֨MզVƪQZY[Q{XiB$Bܑ{cѾ:|/%.qK\%.q۔ԧ.E'@7NaD8W()]aG&M 4KI\I3#L*P$/+⻌X1,,.\~KAsO67AOƳ`5ǟGɼln3TL4D~POR3)KO}ݟ) M{H_]BF3d )O_+ϙDz0[)zЬSU =eaʺ&9R+NxJ$ <,02,eD jS4!:"x%kQ:"IXb&qZ|R%dюOq)ϥ)o1%|Og99 9LPh~Z Z#Xb L}DE:3=\S(B{[$\`3h0IASv-f17|j]?)Rx:N/1}Q+-4 HNb'>1SUzua>~N:+3 =*r15sJSԢ1P~d)9B4SGیxЕxƯTp3Kp.xdؔQ5Sy\ 7FӖx&!(HU:3Y =YE*^DI'?3=,jԳH9&LL,u?) ǞXA?*ݡ4RMjs0LkCMa?5Z{e|>Gv0$'99/&S_'ӞW11Aڊ3F=Z%C%;D*:+KZms3HvDYvbtE^KP]<ڪ~ :Q1 ͪ oe1*f3&F(PAj(Lq@=,󥽁n"h$V~8*Ρs ˊ*g9~v5Fm8衯 46ݓ2~Lkèl9Mho% %jG޾Hr'Ƈ?GZ9UKKcX3D0\aZU,_|ۅ:yMDrKF 0H\.z=d/F4669Jm9$%`a~jԞqQi$Go)FWq6*wi;*j??]8UTL`Pi]I3<g'gl{N5!uB>fEx30[fv%[T};<./-׊TfhUTbsT(_axk luP#`~ /Jyiϱ;fIngO B)GI7 f+0F2 WqSM SCLu0N[472R# lDۭGF"L%c;NJ@6Y@]Q ,j:+Yy8]J]D~ޥP$}O0<)9@2T(Kv#I V'K7Yk;,ELse Ɨn*{5P \0!tR@#z\s`y2VcB>taX8鄰JbFJrI8e~mFy>աxaM13;sFi[Z|Ϙ}Ҡxн,0#׺+?d#iM_~W- f8Q 9?UT9[{ӓP4r.ۡ/;+0a-X0 bⓑNxիme`NtWMkdiJy\!5uQ {v/aVo.H&9'5:+`pӗLNKxD>s%|*CZ.N[ }wݰ;p2*\XVPB-`ӆ^TƮOUOO7vIM%5w7Iȩ 17G׉~=yi,fMؕN5]j,07Khp]_y|ZV9Ib=wetֶks=fgaU!*mHk vZY޻v&)3:q%dazcxmHe.YoÚVWKvz]s[)e FEk#~»o :RW,<5QRі2#VQ;F< C4Is -%Q®w}g&{re4o~k49] ?,[t"΢n4IMi[7z|YF=PNwu _fKHaBVKIOC'64.Yt+j 4*(؂W툯]>L$Ĵ@_1eбY6:g/+ǀ8xȼH&ZHi89I`ۚW[F-5z4[j@-&tcwPe5{;E)sNqsu˱d H;gfM<&2x/\p~Ҽq߰$Yen^}\!эmݛQAde_1NV'[*,_8h{q6_43U,Q\b)ԧ̔Ye/Ct2ΙE3]$rŌњNRdM|Ӗ3A =~c%gV}k;l;&D٣UY4^IzbHSRt/1tf:?R D21\J/=#؆Tl J1E3x,Gol;>ƫ/1AaDifY+d{AJ`'+یq|Dx:&LLnQ'Wate4xS]EdFCI9~eɰ`9MI]ZA|O#֘agkaG3K.;>r*^fc!R,i@:i&[4?1qHb[8{%,00^'`"L柖PK tL-7Y5,B>w*!J~ F$+':Bge&!;qH^eH\v[U|Ci3l, r#Fx+x9VdsIb2_"0}2 [i"G=|I[n>Ή@%JZ " 3jNL`kKRKԑ#Y(8}eF^,U%?s͆p^;uF O ݚ812봱'$a8,W.;*:G!DUMC<:9uK+!vHӔt}p@f&ޒTѐx]C-Sʲ HI5*)§98N @&\\xai';_8٧{{b=I]by660 c᫡ t^UI'x+*1 v;$ 9\RE&n^; 5uib xSXStf*8] hݻ)Ÿ"iIJڰBm,PYX}^zC%]VL{&lLVY4D&a(WD2rt*rQ81 0B &"P[&B #VQ\gHYӜ\54$..OEɜm |޲$.3/·2zGb=  ߨ.2Fbg$=Y1 _I%lf!?_R2%LzRd$#I;Jn)Nyj?0l7+U;0FR@88RDȢ ^^W B Oc஧9饸Ѐ@*J2߹fъ6' t1(BVIOz2 OIf2;Hn+HAvJPa%G""Y'Hk7A23a:q99M0FG5Ll{3B@Zfк(n5,f&B~a8qq@}GdMv36AN SftcXzs\6)z/#y]ple5@oZQ o>R43P|%@5>Y#= =ZnI6Rf4)d 3i$7iP沎ݜ&mDa?*"FK9r`T:.)&!6W@eHZlԕ^ٰJ=ZW,kB+j~fg sW 0I-'<%犋miϸVmLV!yF7~0gQAt9eEiwHJڬV|:*fuřH786vsU0PVDWАj&?yɃ^!/ET 5GѝoLAYIa5#G~٥:w۫$ =(3#Yp*Պ>_G ./ֳ0|EC*S 3i,-p';(OY(Wo"9FɀٲS\{(IiaPe6X~Lw1'+Hӂ/+ NK^ѐ>f; #1O@O߇J+J:t4:^ÂovqG< O(xHO~eN `;҈"'uDRH;&-x.[D>=O{^%RbwieiI}jRr8E(HWj('9HUjQ5}xf5l '9uS/2GJ26T(ɤޗ`k8& Ux%7U?ToJ>ӟ#pA6)NCa%[ 6/ׄr"-ya4;apL(=/$8-܃.H22"-e-85yj@+R4O QU(_IҺō/5uw2=X'iFm$E&$=yc$O'1,<݋c+vm[1—9~y+}MC{SZO .8ǿnee#O/g&?JFҒܡy֜ >3Y!s ,b;/A-bcԤ Yq'9m)B8Q?Y"fES$Yl2Md~縫 =EÄL8Shnru X>Pr{z3)b ]iK#SL%E<ϒdUi ȴ+4mJ}7ӹnq3 ,fBo:ҒT4)D^rŦ D0IG84 xöo"q $lVTF-15(O)-XE{L3 !\նҞ{DWlP1Md沔le 2 5 72 1 72 507 1 507 2017-11-16T12:11:95 Pixelmator 3.7 P(BwIDATxwWǟ=DIQ"⧥)JPNCT)DQJ٣%JDFVPZG=3>?=s>?=A!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!?rP5 BdQ!TG iXH`jӾf.N. KQ[?a҄0!JqOl8_'Dqo(cg8גY)DrPUaOrB$2h(+Eq FZU|WR3]< Y73Rh!tW}ꖿI4w08ƒ\!"7?qR 5̻0㬦3gi=ﮧj٨Vdk"Va!+I#hJumg|?87\|1M4#^bfpEb"xg al%Wx3r3EJ/Jpz}Q]iڳd+"j򞫘nŬ7a ԫY*^[hrL7яޡCtJ&X}HCRì4-?&!-]i97%H_3caJ%9NW1zj zUg LkyTHWekCգIY ekƻ(OQ syohe}D\Opat=(=\՜BqOpEH1srҝ\jn:FD4Ϲ04_v{G%soȓO._SO(\k殝tKG9Ϻ Dz&My#`/#éZص1ELoc{ ro/|d#53IXZ*+[zDrq.E2bI\!懕F SA r01aǝB +_ϗ9z6ڞ|: hCO1u_??oW~k>g3y<Fя49O1 r /OR-,yτ5/#lkT3#Q47Xu vV0 %rBMB׬r?i\w]ʰ>F  w2y|( I;·,Yъ*ITE!]62, 2,hXϙ)ȏs"䗗d,-RhxY p KjuM[?ϩKF7<JВGXMĶYShG9ɫ4r-ADWWmk>L*Ӌ5-̨";:4O'06rI6!;35sf"vҭHtAy˱5p(v?OB9g}+ְa=hO rWR\Lq R\qPk#}df: {}%+&,J?e7^mvX̩)YB9Va=y͸*J6 p];E8gen8l.K,}@_.BS3fJ͚rw>pfj0}A61aA8V*DZҟ,Y2پmI9keLۆEfRfMvJjY͵ _TX(ZP6!} s8wf/Dm~K2&"1M B]eN͞rُFr9cC=F ᝵q':`:y# ?scVRL.~fPd<{(tᲤ1t]Ƌi30qmҨMx=qO&$gws{GLT8g=+}|j`gq13ur/'U8 ~^{nwpI؂\Mpt q}_1EW5 j۸"N~Llx%yht%K=\ XqG:0b㜦c?br.b{fsB[-"k Q;5\ߥID|2'\^FŠb6/$Մ{]ݏL܈y,lRFnVGw/S6bwI*Uëa^?V6T8Ud4qzИX/41,>#J{rڲ(bȄ8[׸Ndlք˃POlp,;kt?nh{UYVϗ)E>'Oƨ2^nI'oq!P}tD٘+ƣcn Ja\rgb&i̋M^B=l8nvqEk'b<tNKuBx#,p'Bd0OS˜N%\@ϟ/ Dwp`1W1x'WƸO-QG';$B&'#f7\-H"vQWM|m~:+(07ǼW߸VO&&)ͽR)+nHüa' MKVm|ۣ˿K^.1E;w;UPs]t͟ݍ+-CW! U=6"a.F,,wv 4LK'"|fļCoK,η} wr1O|=).J8Z'jU,Ȇ"_r0߶3#&ҏ %{FXR-׮HzQV fFuJu]ӅRPnMQi eZYKs)oޠe6qu&ǂW\|7;ްQ/НdrQ2 :&ëH׵}bS(6&9 o YI>1|E5s5s&[Fn@Iɶ ^SpJRn$M4if6&T tϞykpqՋpWy~jnmAc@weE_`7 +KTq gR0a>\Fʼn3 MƱμ3l4 "0\Eeүd"yQegHGVPs^2xݑҰ70\ROkA'H|뾍<zUX lrw Wz>gzX&\gm1;j|&֘'/)iA5o)"fdΛڍQ=Wm)y|99?x7W374H'Caw/zٰ_BO}t1qi;*3tY, aWnvbcΪ,c/_pـK҈$>w3C!'Z;{CJ6J'wƞWѢ)83TWjڋKۨ51ϻV7iD]f1LV'}zN/$75C.7U2З1CZne 1V|Zw8Xge,fhmvIFUnSg! >~etzӇBE2 FEϫr bmaCQ=j1M;#Rpz Ȉl1W1e t9 >-z#eq|ƈ:<(3)Ms#'J'LV:;=Q+:xO3fb,ĝ賞{1/\d|Vk( z"eV5H{[iNZķ;87{ڻT 3nw~M5GXzЍ.fhZ Uƚp$ĴU5ʐc1.]]ì/sDjp (f~E7|ʋ q |g߯<7%܎#iX#5nXʉL/;(&}ڛVR:1gC4{Yh"_²J@[]gtrmѴg"NpcfULrE4Xv,SٹڵUz?D.~{'ʲ5.zx\s? :Vuf&BϞE!h1'CF)k'k-{Ar:|F.O-t=mDegGKQmOU1> !/ΰ[=hI*r|J7Ϲ }CQ8gnrG 7h)K 6w,mfQb * fBO"a_lXCni9?~{eYp𗌲:?EP2~9e^*B@#&$rRILJwk5, -ΗG ׿!g6Bs_zu\@$_Y4H`4;A9Yx\$uϠ>sK? :(6G.tTRyhA[ۮ)7Lɇi_R%hHO=J +gόb a0ax'm '.t&bvrXY "klRL ?7*tݽ`QN~yh}R}w,@Qsn{uXmpYq)1cY ?XB g} 7Ex2 ĚߏNjkjNuyx?r$'LwFn|BS,;~%.'S'|ЇZ*ɿJIR5\tUpϱ[Vv -XꏇjǿҨ85YFYyKyRvBod)2=m'm -i_ƒ٨nJn+mw ?S-bs2tn FSxv8_j}gNI%g'G&;6?NWu]> ! !໸54G+sؘ )1k>#G;m*fåAYIg'#[B^[Od|h|n_Ge ?CSOeP3Y 5tDU 7CAF۪M* }~p>_}2E7eH-a=,K mwbFq^4jBu-u?(mtSG 5>┟?{{6όEXlPQn%뎓By͋\iɮ}!',{Qg&a^ϵڈ|*G(}kbQuT=a7.kCKc`tHopŜ\`9Ygas48h魄_ֽB8G;fd `Iݢ#Л`~UlsNnlz\z^ی-쇝@ HWBq%1֛fFWg7 og9BomȄٷ"^ nr垌>ҽjye $ËG]Փ)\86{N~c_P>U疸Rҷ2@%^x;)ڦh/)+B8U[fshiD|May}oPҊ|K4M, y(LI* 6vE_a"B9*%dx`D p>jS<ڙaESL!_&i̶n'[OD4L0-^-G^_۟Kԇ/“>SsE\dQ8g`}<'zTO;{2'pPK?۟_CЧ!^KyC _`IrVy9Ş,`)@v(7Qђv+a^\zop>4\N/k O(&~,`-2Q7Ͷ*۪D-\Չ{cbtj&6~-r|+f7f똻!Y#3B =- ha^u)@g˶ܥ ?gTP9}y3jtd?2,fl ?LC>Ssɐa˺) )VT/GkᎽ В~ Lzv_~ !%]{۟Ka)DvŬ;ސo</$g ~F7%\)Mynn9-iF ҐԢ(M1Ύr ^l[blsT}l돟IK0goBX;N{zl]Tĭgٹ4#l`gw;; ѝۨe!y}D6QXkЛa똛M*׽!.d˵ޤ(gx_Rqư/HSg2kkxGTଘ=K;,+u1K ;*8zN40= nnvhY!\>VPY_\TKigoi_lbhG(:Zgh:R-ZZRлIj2Ʒ3ON$^HA.ab<BɯhS#ђ(%8z `ģa 3MmFt,nD<h`e=rTuݤrp?{|c !Mbiců4$7φ܃J1a!\94d8oxx\BI@צeKh. -r3ΰV pgD.Wˣ;"Bez07J9'hG9UR,lHne*){1wbk}#b["2Z<8;dZa2?*lf-+xY+,d>BxldOW+s_IUWYR*/{evTuuMBPښ5.YzG1Ƒ϶F\FǙWv [>C,gDgq#)|r5>GQ1; j~b7{EWA.K̚>y|J9rj8.cuG>͌gъY%B|,Fp77S6I:S(DyӉQb?CshJxtOƃdpGޱC[^\KW ,׋+2~R5ҏFf1\P&l27.L[xڶ X~4 RfxH[Fw x% ͇9n`ߥ, ByWtm Lc.J\#XSAޠ%[?Kcqe9E)1"BƤOg2§0j;f餸3b66<{Vѐ?!J'd/'ᭌJ(.;Sg@{q%r-N8&+xd"E [QZWGk,[[kTrEDԙ-E39kz/be?+O~ +lհdXr ĵ?s 42"Ԣ;O23~4)<Ʃ=B/K. sӵދBLyEQEW1P<7"P`050W5 "׹YH'fKƂ~=s\ΰS L.=s;*v8v#!ᱝPMAP'>s%4ua Z1u>MVџ*SM--Z\CE.b.< S;΢PUAn:ҋ!<Ӽ[+7s52W2mlg"NR+36ǚL{8*::nПj6{漿wlI}l6 ^}J 6<%xJo73iG=*S[Q#?/bópG̣>';E$}/Ac*ZcI tc,/>bJfø_n*G#Ieg؞2Y? VqG^*g9V*Q>\pE}RﱟpQ}T$<_μ1^Sy6 rNі{CaӘoXˇlc>ekZ§|f6bo0xpГN)uN.H49 %>ЁF;G޷ASD{q/KL ǐ yQ#Ԧ?Q^N~r;3}؞+qNӁ~9^e59ʩܞOv)0z҆\KY 183i]Ar[ jEy~nϤjHjkm:J6 ;W{?Sv,"8Q/Z8{,iFҝԠ){(5te:By/eng;s&ș@YxfIqW=AIZ3mA5gIYQ>#<頌 sk8׳Mm΍k1|j_f*ӈlSiB(Ӷk\ W7ڇyL vÙjTWwr%r"%F7b+x6\zF`v8sKa:݄;K+uJi G(!TNRt3,~ZD݄y %WCQЫ+OKAK'nrXi((֥;Ws B gp!Mkg,3y3$ќ2d[qpQvt̕> vU@M5Z]*m~%.5PH6O -J$~*(ԡ=b6KGVI#8:jY; .^ 7J[G3z6̠/ )z&P\JEQzJ+ѓC c$3L౴(xqa4# ~34f (ɹ@jʅTR>Zn. %.G`Occ'da`'[Y[c:LwZs (K!VQFd"5bW(8j 3ŧ![ 0(#_f̄]Sqn Y3됲Gƾ9Ԧ54O1f+ְ錢QKBSk!bл1WVq1|oZ˞MM. 9J _ ,+?<a{GLuZ.nq7xf7|\͹Ϣ*<> a\;#DVr+|~2#95Glm\oXFK0VOvXLrsR3%`T9E 8:j[m5BIwF 6/^'yYQ6avYctf.4ϧ}&w^EcQ!3ZصT1COea[FaV0i\K2J4y(<-jt 1?VI71WV~ͼJXີlxF#JPvf>'L\Q+9Y2d1ͳLc F1< j1ьQ&3\^ug5Ʒ޸9K2fin98 w#ZMV(r6|f?̔SgFAj&F,k`ypБVz*R䏙n p!q5)c&YV~)N7vRsq̕>\,}<ګ۞$e|f2 p+.ˢqI7cx.I7c|lzqTT,3o9dz}i5CK:ђi!=̹T]L竈 #A-se7Sv:~hk,lWY~iOla%xG@gZp05R(LӇ'x/"4 =S. Q.ǔn8s]*%Nˊm~ /v,`#ɝԡFI啉b@ = c[TRi+}N/߮Kl.צ!qnj|SseF@䓻Rp: q^vLei5b(lQpM&}( {LhQ~f#Kyat!9/:҄QbM1ǝYwMRb\CipMhGO2,f3X̓ =)g v Kq'N W\Yfj8Qf)=ih\U62x ~Kvٌ;M¹ǃ\ǃ,q,+}P7l4pa'SKkz2i,d{}a'0GpMn љ96+}B$уA?-A(ȥԠ]g%_'n(:RKKY,1WVSBe_[xΥ<7Ӛ>S~dY$zӄE27\YҶ!XJVz}.-]1x3wҘ)A8'\I]џvD,If 'W!-u5{/J`2 >ӂzԠe(JǞ3)M n7y|xl+mqCV!0C"!Mwc+yŖ=ĭkp&wZxX"}#}PrAŦ( `.b3=9T] ϗ>OXs?>yo1"Y}T+8b`'!ї.Cd-\A=:2G8+eO&>D_N#W6^qsͨC ǠdogC< !'CP4;:~v %'-g.I</2 =\DCd"jҎdߦ&I5XJjd燠J~Fx惀=DO:po&1wG.>t6-!*$z;!]-{3<dOFβEy_mqtU};?=0tzZV KVKֳwx,MVrSˈj/}Rm{eMxY퓙|g޳l.*siA{ #yyWyl[vGtQm|Y퓝x7a#Bo&PkAGz31L9+ȟ H֒dO\aSx>l(euԥwэbx5|?/D9woQWV$&2+,'\B. Q˨J-Њ` 3e)6$͠϶eOod Xj%4]!qB^Υ45]3Y[||slڧ+q 0woivŌ~+&Ꞩp U=Hc:/*ﳞ|ڧs_& B?YV{!RZO*r=uiF[ÙSfﰖO_Li)1y)GUjѐVt'da. |QfzB$YyթMЅ> e+vPZn`"N4VZ~iB!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!vIIENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/assets/logo.png0000644000175100001710000004170600000000000015353 0ustar00runnerdockerPNG  IHDR75sRGB pHYs  $iTXtXML:com.adobe.xmp 2 5 72 1 72 507 1 247 2017-11-16T12:11:52 Pixelmator 3.7 ?;IDATxuEǿ^QCZ^RBiDRAJAIi)D@*}? g=u{<3<{@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!\AA! -SE\c/x$QâU'B zSxjG"b{AЀj'"~b&T B<_Zc0WSnDvT@F( 9^dVëhA+NwI@10kY0j̯BB9oBI f=- KF5 D|zsA OXK1+Tf([#-OCg^R{$a O#}Xcw1kPPR>7By9duDc6g%cɦ^B6I]ҟoy,C#bdg>"HӞܓG8((( ? #/.rD: 2J JAa;x=.1m"ڳW *M(fI,&%=$bJ\6u~Id. Q4X}G]BØ4KP= rՖSBqAkfƚ2BQx)(n >ǦLv!$P/WA %!* lK\ox\2}B L)~!H!8 Bad I»xIjTPBF>@Hn2ATqZe,:F(:r4|AEg>]#&B mO XtOgZj"(DsݢPhk\rYבNMliFČ2Aw(WW]isߡ)(x a4Ik` f!7`3)W"b&g"AL)=mCA)B+xxG迎`왒thTF߾GUpLAT;дj(#1saiɫkZ_u5 i-e!V{ԛ%TRdaa<2- .Qǣ+W.G!UmO$uhMOF0 vp_unpUw~g/Y"f1t1U(DZ)X+{$ R+޼=b͋(JvF'$8 tֲ߄ [8N34ҫeYeU"}]$HLT.ث3S#MĔ%XQn{IeC~c BsJVuc@t ̰PJ0?+5ţIt{ra)c$Vw̧/^;@uv $;o,N)G6# ДI'qtpyuz2 plb1(N/֚6| ZrB84{O<R'eof1n(EWATOcӑ_sÉqd WDм7[ϰcCA{P!?Nfғ$#)IC”UҜe3X|ǯܰՋKKQ"蟲Tak#C\5C?,"6I4I rbYTzш^!%K'F2ExnG'$ `0|Aߤ}yŎr!FEfa3c%)m.ڜS0V7\Pm0s1#>{x%`w̶aM 2F!%\z2:|: ՞Llp1?a`!DXD숐 8I[`c5]n dcA^cHE;zoY6Az!8os(J/h \> fp&--p:`Ubk? c6QKK^hQ&`P4 %AP#425>{h1hIz2^P_`o|Bsz*!v|J IoqR=pC& NJCW kKcxlDC=>\&=ՐPSnz8*1F#%/5r/i6Omeٻ~½vko#1akEj. ÄjCUo Ïd "j,#OQ*gbLr;vD\O9Av5X"0c@/ypLT7d}vQP䓱Exn?񙉾 _D>>S%5#,!m&W&gNҴY\]ΤU'3~c7I  O=$7F:u EOzJ=ʄW#i6[2=4n|l[1#^|$ACm|r1'Dpс s8oFr@XۄO}r+Ud'ܗuN`˷uɹ!'Du/1G+M>3|LJ~lI^ mq'mn?!l̒pN0}G(a Hv6L8%wW IZ1a3x;&ALsFص^I1xJ?ݘKfdGdiEes?^\}N:In9Aâ^F>6E[ߛ<2zP|(7x-[A=|vDl<ؐ}ͦnKD(- ff*S,7EZKS~7qU$>ɢn6T le7 ATpIYY&Aw(@ӒI" n龜4R|>24yj?9('eɮHHͿ4wrY7arE iQE:*oPb'Y|dK5W-?=NkoSgu|~B:f 01 vmYZ;]/,2]|ܧ/?/߻6!ૐXbw@3Boh.7u ?;>[alrԋ-lxy'.,M$CzcvΔ8,Y'Jo8Bu0t95ȣK5eۍr}esPJ=O ^JH"VJQPLOԨsSE٩:ipNAe35;Lk—>nw\a|·,; ģ"Z3 N:ew+ֲ,loA߿:%e$#8kL@!o>V9.YD22 aC>Q/`J)*?: !}79u3jfmu:M&ݾ>;WA}H *lPq ǘA}t5q BK<5 brPORU$Sހgm$ yKW L wCbW&1g:xY2$$v4B"(Jh0FcQR{k|>n 41}Vlg* oфOD_>@9c,nJv, l̓k1-!t0P.I:bÃĦ6 U1=5 ~6PR:\5{.&ϫvLJl > -H=zs?XbާX"1L$̆L~3o:% *.1^v|ĪS'd6yьM 9<|ıuz&n&9r6p z!0 ˏ`/fkػYPIHIOS*s ZxWm[>-X$^dҿg|5}W'`{_,esk?G"6=7ObTf0\Wn\CLbB1\Ղ>b(fH2.;w,Q_FBG[|'Z%8Oڦ* t*ʄHx,ț!~0&X ;>s,ȷ_H*0~_K\aInPz]A; Fm0Ov\Ӄ9ՙR׾UT˭šk7}ײ<)JeR4$RυRR#k% 7Rʠ׾Aa( >8Z IziHH%Rqc%#: Kj:൏Pɘz'c6P,a!UV7w|ZHj%n4RZ&o+M'o1nr8쇧K9¾BT9^u79o⒓iWBb2X-6%S#ilIHm feL]t 5[d kܱh)$j0$eoͣk1!nġ] InXM"refϼa<䑋_ޒ'CFk'^ix7rԾn*MCcegCp^fAe9tP?#ŘvKqtZ-#TpKとk`;|}y[eO+J\?#c->a) -;~ 0?$~ 2JK;bn 1SR/7LO+?u7V.grr_DC[A_H]qhuq}N6\L !}bk2#Π L$;ج䮛ud4 s]pǞ[~Ӂ,-rXkaGԃNڻ;X QmDrzcT#>CmO f'fDX{TH3e=w܄_ x%op#q%~YRkzР9qX'=21R1^R?NjkťjEp u \Sx3{| ~=PjO*s.KNz?:GJ>iF_HN*o ׆ko/ Jw{P -ݜrQIԷ[$a;w9? 4O#% ,Ҥne z{tG/8$s9ZKzhjV$IMqwepnP\t\٫# Y̓nJ}ctȼԐyFrw:]J]'n?Z`daHBAWh+9isY>63IG;m)>C^7VFW62H-u5{ ŤRe2I2 0&71]_ӬRF.›)%И]{R-wϚ%oAo^{3U>)U>@2 HV?],& }6D[)"EN$]8gH_tյ$u7m&h_Q¥/+tasy[CǁF\vb> xWjKA ,zRw8n07#E^~vXo޳M랛ޑHcNy9 z5Y|^ڋBKKQv/4C$LD1ҧt5QVsGW#瞅I ikBu5jk^MoYlzHKy*,L$fDya䖅㺩:7灮GI./ެ){,Ӑ>FM%9&knMIg4ZJLD;'=Qs3 )?ۛ`e0W KscL 3ng {wlAoҳ23Ŧ%ҋ46MI€fRDK'w7I0#Zo$JkQ?) ^Ao.od@ oamOMU±CY~) kg=CRє6xD4хm]>Bs<]`༪|gSSҝ+8lW *썹cAu 9/Ƞw@@zI!4,{U1M <Oa)F 8enAf"-%4}hB[XodЛK)7!^RLY8Uki33[MQaRȇX0$$%(N?A/˾fSi 734dxBFXpz86Flx/H\tV |z @oMTAJͻhhi@}T|hZ })&5{p=z*MQh+?-vE3\'3@=kmٟiäh5ZI,]&jVYM|p 6]>qʭ64n evTuFLzeBCf&L튶|Bއ+T҄D/C\Wc(0o-Z(tRَ1ID¥$ _o4 ztspRbm")l%#Yvɇ/% */tz 5OcP]YPRFehk`x I-ܤ!䲔KУBXF@D]>ݗT^nUZPҵ|B K2I4Ыb6Rx]~5c2V}^ MIN$%=9)Di*Qx49oЂ7hFRT,I^r ^HQɛ@ xX&~ЍǏN,,`y-al˩wߢW,\"h&GG3lf?d3{;L8|ӌ*4d٣ Z9WР7_707"MS& t,xSSqM/I(JC0iA8^V2Na^S]O٩3hРŠsPT7q2Xki!]UV@W}jh環s|XX~.Ro8*Ўr^ \u"4YM5 ^3<>APZێHZS񞅒_FQ^"HK-lZlѮ:j 4 PsC#B@p{B 49NJK|X]5|GzULzb`8qhG~*!Z#ٿ5v4Q% z" ! mRs]tK3"Fr ѪebBvWWe'9>v5_XjVc_ {8 P+XoMm<|*)WAn`nzPBk-knUo1H[@(ۈ509SߡkP~|i3}y<3!tR,=a$!(O=яq,sY|'K ɂZc]~s4~x-6A=*JflʚcԎRSڋWkvU bߏl_9KtKʷ |F&CRPtb K6uHc<0u5 5!j"hJV44=vko \nfXoxEAN[YKΠl8KLC )9Lg g=b?$rA|dbU 4jNA/nb]~.y6I; Vr]O8>̼JXgq5̙2d x9XG%.馡ax:U5If"7r{6r8ɏ`'[Xbf3!C[^2d<>\ri1O꫈F(kI^lh u6ep-N8j }gfZg3SQXd,h汑buq"GLgERJ-!-jpdڧWh. %Рw*W@4C}a.*0j`,Dz:LSTAқq۬-gC5l^ β匧+DNGVX007<7jQAoA`nڛ:"R2NzB#<}LVKDU4X0^g^`65,՗IJiZ32И]a?KAkʒ`/@vy?Q8떏7>Qko~"Q^:__'}+?w '|.H@)Kom"U·8z2:%ӐPuϳmW 07J{͐6=0Ħb'CKwyH9u6 ц)lQ):?t2B)8|]7eՇjld /oz:l'IsX8`50]טCyt#  KmLybq:ka@Рn\A BD(.i֡"bgm H44C6P,4gm&D8n6Ye oӖ&FP|d'#iII2y{&MaJQN :ҋaL#V%8UM{NKra2'84vXb>b& z҉V4(LvRy4f(rԡ@Mtܖh7CF\eh2Ay[TK\<:5XYc:_iRl ciI}:0 |p ^=uqobf02 c glkw1VFk 'o I)Kj{U~1]zП/`RЎu)785F=o'\4{XG;MHw+H@2cF`& ؠ7007{–UێozWtb vs ;}EwV0ek@Z%9&5/#0U嵷omnb2n)mxycҐ^~aѯq0!2Y%P 51 䆁k/X76|d{]Nz?r &6*z=,eLf  7k/|6 P:!Qy16|h`gEҘιqtMF ]]P5+fӇzӥ҄dg G"|'A3C_y-"-l48acBuR;4Nid;~˜0;|L3miNCPTe)E S,$)K9*QԡMi[M1|"V8)_?Z,pm|@jŭJCR^!f@6* 0[ x[W\P zQG`~r:A7Vl?Uu[PR$C9Vm8 IKJQtb35lGZFނwU)!滩kogT'4ۍ=u3+I96{.=ΰOMR > u D1ӊ>g!9E|$)0" h A`KiPTݣD\:WO+a|Ⱊl#ޥ)H$VJJ R a69%/r: Z "G0 U^{ζ36SzEx ㏹EǤ퀴&.VCIHJ2 x-ѓ =F0D&35Ma282 ]iOKSjTF8Pbr4gQ}v6 s=fYD7͋;8Hx6#IB۵egc>=x qHMRftbcBֱ9drKV1 ;x&qI(D=z2 w,7L %lc rC"'HM#OvwC=8b2{IAnXؤ(iE/05(|:gz142y,VIIZ2%y0A5A`;쬲˾Le|*p׸sa,ѹ{Gg"`DM0+߲I1I']0%JъQ⨅EpРV$;mN󴕜$0 tk:@*4/X~~w2|e' N*^ʨJjч3Y* z00Wyk|;&M-vҽvz4kAȰY w= ^LY3sdXuY>)YgS3t9@Qr> ׍C22'ØBֳ\+G7J62e*ב%6⛼'|tw^>#ng9b3Y,3^E3jS"d'̻ςt{3O"t'<$KEyy`oV^{b|DgApp -l8QeKM㻨0P:ta4 38|ޢQ` Uq;ECÉWVRmX&14*E!lCM1$sާ ę]vVh/bfckhzҒ#L^dg"tC" 0Wy}QJd f*iV;lfG[jQXJOԢ7AY~Abڤz|ϴdmb Zf3ԧ Y1,:}XWAHզ~B:۴}[45coyv9aMf)!әĸtց }џ a#8&1c0?ǧvεa7 Q¤"yy ln%Iylwq&YeW87`yVԡB2\ION Sԥ9oыg Y's= !6#D]r]L4` _[8N~G؜TC-+"{YXP⤳CGjrR4=4ȹ{Eg=2T'&x)AKfrPNa` cD!dd$iFW2Elb/' Rӝ xFae6a`bUX E_a˯DRQ'cZv3C-˙;4uvCGg^C 0Wy nXJ1=i!5h^C(KEѕa|jvqk~=|od1cHumlGT6H] xwC/KC!ъLd1_s~Y No/*}@"nJVMJsHpleK$ӗδ>U)MO -Q~"Ԥ*%1L9 k(n͋[`C4u\4fVeО&Ԣ"E:HDN*ҌLd;8mxs^U^+C:k" F 7g9^C(t''iA}wu!H*}$܊M-AR"g8noҔJBIM1ѕ,N ~v AyI5N[.y:yY.U1~tTԗ)H-:2F>&k.l`&yFԠaRf3y"=IB RL``;Kv܍kLhyd;x˘dQ :"YL;oxhI5hJ)}#6QL6bM/HsG|!*QC 9"zk>XW|-2mb[5: 6i@5>!Q50w>W>ذTq~@tKߺ=@3^{A؜q x|g0pcDYU k#OWDp 4g3WQ48PP|ߞpKCbCI>8@jJlb[1͙N~kT~ni{~h3QyYm3i2Cq*Q&;2|ħl`8o\qtQͷm,>QOC$|P UO :қc'*6 ZSW^|A1E>p")EQSFd氄u|^ ^Mq*}#2ևd&BӁ f,lb9y)?)}TC(%& }6RvA ӀVt/Øl/8n30G)d53r+c=ʮ%##y(A%jӔtg <>ep㜍FFK嵏P5^0Zq+(xdLO.R4-H&3kq_+k#+>)L~JQ4=Xf1+gs^Fu((b0NZӅ g"YZd?p6SPPJ-3$jнMIENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/assets/support-on-gumroad.png0000644000175100001710000002212100000000000020163 0ustar00runnerdockerPNG  IHDR7.-viCCPICC ProfileXyXUͷOqVnKnED QI6 ADEDEVnB̜YkwfMul@6%@-`D @ղ4cY# _{z{Y3#ō`8O>I$xk,H?N^# 2^@r[~'mA"/N{ D_[$ڴoQcvcLVr;>?Otq8}_(@c}}}Na #o6lwK!90SQ?:a"FGcX0@D M'ȿ,?oq[N#N/Zs~[9\Cn҆4>iA w%\ f-GG̬{:iy3 +&bcC脄ƒ||#ً(Cj'"hynC,u qYH v3X8Iڒa6,zgТ; I(@c`l#؏κ/BYGpL NbP *p\͠ t.`]3#+'Axb!^H H҃L!+r|`(JAPT C+P+ ݁BOW,L3ܰ0, Z l}08NBn.< Oe  H"*b8!IB2|Cn~L!ÇDק! $a0Ř &-#+7˅a>hl6{{ۇ cѽpgp.C4ndz%jx < Ok ;K#B>A!#EaAIKCQEq O"=QF! u>s"%%JUʽ~( )((_Qbҡrʦ:OEjZZډ::"u/ 44R4F444%4M4#4_h)hhh^}@NNDDWBJn^ނ>>{<0'C*C%C/4#(xq $dϔt>3s s s; "bȒreeUՋ5u'&[[=(*;{qfI 8^h}88wszpfp^|ssYqsUr q-spprqra93ȫ{3_ _!-9~.~CHr?wݑ~ǤQ@E[@ `%gBB*BBBB߄E텏7 a1$\ZTC4LBNLE,@ذ8,(+^"@P8#p'v;HRIjIFI^|%"e*",EZPItoE@* YYc rrr%r[$*+2*)QQDVSUTvS>DIR%Ke@ڦcҮ]Ww}-;`w{Dx3CV6Χ^>AҨx)yN󝖘Vmmuo:tut] z zzz/w_ҟ3P4727|bmathX8 IkSqS 3ss!`f `adqbR2^^˽%{ZZ%X[3ZZXhL؊Fڹ]fkg? pבϱ dtiY󌋢K>}1ݕ֕z fVF UݍOyxx|<9[; Y_ |O~:~~,܂ZoĄ< M v*llB> o`B_u"E#GR*m}->&8f(V<6=]~\u<&#'?`«D$(='Y 95y KIKY:dF*w/ѤӞ}(E3<33e23ײ<+<}?G)l..78w yyqy'N4;qr; ĂȂB–"ܢb\O;yfٺR2r J\eT*j8eu> .*_XUs yi֥v:ɺzە&W{\kj<}zF4<ոoJdjsϢ8~)3_D4~:4073O__Zd_<Գlb%h緌/Pѿjg~دM~?_Z_%IZaoo@#i@ivP:at8A<-GNeCESD^!qY%)u.N^ɿ ($,J'V"!YJ[zD[nQ!UCFUu{) o͏A:szAo ]6[d[.XNin/X4Bا5ϭ4)esȷ[`xЛ0<1+3)!qlK\Q|TEpϤUb:ڑOrx,H􌌤Lr1l]?O䵟(=y_Er!_he37^.--*pԬQ W>uBEKFUd]ڧ';ZZ-o(kh[j1}l[&`Xx݂{n s>=\yqh˜qd|i3 扻c&&_D}fվלǧX{NYC?65|o{r~lxeq%eWJpYMWf`Ybpx75јҘj/Mm7wF_Jwlq콜\ܝ., #* \TUFRXF!YqNU==5t ƍčcLL')-T,[Y?jGm/RimQiW MCKDL C~ _(rVaYkMIHMfL^90q;ԘiGгUzg,c?s&s;W>pab!pht:eز򱊁UUpb|M%Zjub k o<ځWr3[2[ȻYV^Qy+{_Y->bbwY߳`ϰCG4NjoF_ZDsI/_>*uشڙ/o=ߟ}B6O_0\-v.[[ZrܷgWگݹvD葫g,ۊ# E.ѓRJZQɌruP#@ OEw <$yJt̠g_Jʢ*ʪz,vcfn)ůMT^~hҸ$Lgڢ$+7km[^RG'ygjw.] q͑܋<:~~iAbAk!a.rBR|DMGgHtFfJlm)5AD_I7cX=ؖtH+I=| M5m壾BYbYsZX~>~]C'YO8US V𡰦ȯX]әgBZ*1-WT^9w|v5{/_֨SoBrŵ뇛BcZ[n|)Х{VCwlGA?_y5i2ԗo ߞ|9nvd@d 3XR` W>r84h6,\8f 8 y $h(.P8UA8 Kv9p#r9E0̘=hyae& & x| N"T(x)(*(fb`b %҉ rjK&f֙4] =}<Fwi&hƒ*f6͞5ʝcϻ7_#[ RMH]Gk/H酞 \2X9 O([\=j?4vh/Ewҙ̯["{5oڬ?0r{k>'7o1 ,s_L<$3+n4!'rPaGde);ptjoE[I֙2 5qomnںfSrj̠܃ #F^qv|gX=u_%Y6}*0j?Y =` | AhF>o^A24ahc0>kM6`_ 7{K gK A ?Db6qR2=SC440Wttj WԘY"Yljlc9xpqNQCFJ5" 1'q# ՝w|Ro5?8~tY&VKWolYۼ> *m/ϊ_hv6:N/lAUpٴ8XiTXtXML:com.adobe.xmp 232 729 1 &L, IDATxS[Kn~LftNIϤLt6i6ۀxƲyـ!< x@+!Aȋ,Hc {掳w޽|Wr%j-Xs{愯?SЏo/|WG>C<>Lg <>xzƿ22~b!]E)X|-ބ9ǐ:4S`AO9<(v8~Z<6a0ӹ5r h4az`vN8[puB9N' p8NNNN@bwǂ]_`b1mZ͖NX3Z>iLR?sI׹jSEˤ(XۂO\Wl|nh4jZ¾XJ8kx:k?kՓ bL_>/P8_&ۭ=z%J}###1f5""={rcc#{O]GJ@B)R{ي>k PG666I$BhŋSlp7CMZ=z/XR= \[ccc?~ѪjveYY&ueSNq ʹ#" ih\AulqO߅\8;/5\ͅ.>*pUTT,=_gJNV+N ؂r t }ݾ}`eeEV^v+=* 8CQ ݻwhvj"F-pNt<- _`T<` lS<ÇXdd$@DG+o믦n5O$PWX08 9a0XuZz`]b, I$¾XWWzS,}ӉSEKz@Y=::ʺ% ]]]Gb:50-p:%@.X N1ԀВ1H½(ח&j֦>x˧Ѥ$,§a`*#Pؗ8Fj233NWX+`U+J )Y 2Ԡ_e[@hR3 oAos[M ޠfvݦgݜNI?s.pLJJ JN}LM,--U~6L j/HGPw "#L0[<+ߘ"lc+MX Is$nؗkލ:GP:rz?k5N'n(5_{ D@!ͨ%Ff$ pN CrJ9! B~{<5?ڜ5;蜽U$H.m[Y񡎌"!˨INNfdǗ,z#ƳM:EϺ%?L:(G̦&łSfE ?XMvv6jo3)(IyIIu8?vJ_Nˠ ]ӭfBV\#LjB6@b 3ؙ p\3ݑNI_P"1M-kGh@;--a;S=Vz9FR?S LRaT$@v1&EShB5-Q渞`\΋;X~l;CLRQQ p~@ؓXO:mhh Rbwʺ] fhjj;0F )uSot50Ӟ҅oA`f#P,߷4<<{=wuNh( ! Qv< ;+8UWAnSh }ƀ]H0Eho+t}_bb[j]+ߋhNhkLoϑTח>yV߁|Uy즩8}?5MiQugz<#>yͧwJJ~Tm*)Tgh+DB[6 F|LUD;)8\8\8\8\8\8\8\8\8\8\8\8\iq\tP^]X~˻.JrlllyyyV J4>> 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-2021 Joshua Bronson. .. :license: MPLv2. See LICENSE for details. """ # Use private aliases to not re-export these publicly (for Sphinx automodule with imported-members). from sys import version_info as _version_info if _version_info < (3, 6): # pragma: no cover raise ImportError('Python 3.6+ is required.') from ._abc import BidirectionalMapping, MutableBidirectionalMapping from ._base import BidictBase from ._mut import MutableBidict from ._bidict import bidict from ._frozenbidict import frozenbidict from ._frozenordered import FrozenOrderedBidict from ._named import namedbidict from ._orderedbase import OrderedBidictBase from ._orderedbidict import OrderedBidict from ._dup import ON_DUP_DEFAULT, ON_DUP_RAISE, ON_DUP_DROP_OLD, RAISE, DROP_OLD, DROP_NEW, OnDup, OnDupAction from ._exc import BidictException, DuplicationError, KeyDuplicationError, ValueDuplicationError, KeyAndValueDuplicationError from ._iter import inverted from .metadata import ( __author__, __maintainer__, __copyright__, __email__, __credits__, __url__, __license__, __status__, __description__, __keywords__, __version__, ) # Set __module__ of re-exported classes to the 'bidict' top-level module name # so that private/internal submodules are not exposed to users e.g. in repr strings. _locals = tuple(locals().items()) for _name, _obj in _locals: # pragma: no cover if not getattr(_obj, '__module__', '').startswith('bidict.'): continue try: _obj.__module__ = 'bidict' except AttributeError: # raised when __module__ is read-only (as in OnDup) pass # * Code review nav * #============================================================================== # Current: __init__.py Next: _abc.py → #============================================================================== ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/bidict/_abc.py0000644000175100001710000001102200000000000015063 0ustar00runnerdocker# -*- coding: utf-8 -*- # Copyright 2009-2021 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 → #============================================================================== """Provide the :class:`BidirectionalMapping` abstract base class.""" import typing as _t from abc import abstractmethod from ._typing import KT, VT class BidirectionalMapping(_t.Mapping[KT, VT]): """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__ = () @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 @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) -> _t.Iterator[_t.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()) def values(self) -> _t.AbstractSet[VT]: # type: ignore # https://github.com/python/typeshed/issues/4435 """A set-like object providing a view on the contained values. Override the implementation inherited from :class:`~collections.abc.Mapping`. 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() class MutableBidirectionalMapping(BidirectionalMapping[KT, VT], _t.MutableMapping[KT, VT]): """Abstract base class (ABC) for mutable bidirectional mapping types.""" __slots__ = () # * Code review nav * #============================================================================== # ← Prev: __init__.py Current: _abc.py Next: _base.py → #============================================================================== ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/bidict/_base.py0000644000175100001710000004134000000000000015256 0ustar00runnerdocker# -*- coding: utf-8 -*- # Copyright 2009-2021 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: _frozenbidict.py → #============================================================================== """Provide :class:`BidictBase`.""" import typing as _t from collections import namedtuple from copy import copy from weakref import ref 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_args_kw from ._typing import _NONE, KT, VT, OKT, OVT, IterItems, MapOrIterItems _WriteResult = namedtuple('_WriteResult', 'key val oldkey oldval') _DedupResult = namedtuple('_DedupResult', 'isdupkey isdupval invbyval fwdbykey') _NODUP = _DedupResult(False, False, _NONE, _NONE) BT = _t.TypeVar('BT', bound='BidictBase') # typevar for BidictBase.copy class BidictBase(BidirectionalMapping[KT, VT]): """Base class implementing :class:`BidirectionalMapping`.""" __slots__ = ['_fwdm', '_invm', '_inv', '_invweak', '__weakref__'] #: 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`, :doc:`extending` on_dup = ON_DUP_DEFAULT _fwdm_cls = dict #: class of the backing forward mapping _invm_cls = dict #: class of the backing inverse mapping #: The object used by :meth:`__repr__` for printing the contained items. _repr_delegate = dict def __init_subclass__(cls, **kw): super().__init_subclass__(**kw) # Compute and set _inv_cls, the inverse of this bidict class. if '_inv_cls' in cls.__dict__: return if cls._fwdm_cls is cls._invm_cls: cls._inv_cls = cls return inv_cls = type(cls.__name__ + 'Inv', cls.__bases__, { **cls.__dict__, '_inv_cls': cls, '_fwdm_cls': cls._invm_cls, '_invm_cls': cls._fwdm_cls, }) cls._inv_cls = inv_cls @_t.overload def __init__(self, __arg: _t.Mapping[KT, VT], **kw: VT) -> None: ... @_t.overload def __init__(self, __arg: IterItems[KT, VT], **kw: VT) -> None: ... @_t.overload def __init__(self, **kw: VT) -> None: ... def __init__(self, *args: MapOrIterItems[KT, VT], **kw: VT) -> None: """Make a new bidirectional dictionary. 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 backing :class:`~collections.abc.Mapping` #: storing the forward mapping data (*key* → *value*). self._fwdm: _t.Dict[KT, VT] = self._fwdm_cls() #: The backing :class:`~collections.abc.Mapping` #: storing the inverse mapping data (*value* → *key*). self._invm: _t.Dict[VT, KT] = self._invm_cls() self._init_inv() if args or kw: self._update(True, self.on_dup, *args, **kw) def _init_inv(self) -> None: # 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 = self._inv_cls.__new__(self._inv_cls) # type: ignore inv._fwdm = self._invm inv._invm = self._fwdm # 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 inv._invweak = ref(self) # Since this bidict has a strong reference to its inverse already, set its _invweak to None. self._invweak = None @property def _isinv(self) -> bool: return self._inv is None @property def inverse(self) -> 'BidictBase[VT, KT]': """The inverse of this bidict.""" # 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 # type: ignore # Otherwise a weakref is stored in self._invweak. Try to get a strong ref from it. assert self._invweak is not None 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 #: Alias for :attr:`inverse`. inv = inverse def __getstate__(self) -> dict: """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: dict) -> None: """Implemented because use of :attr:`__slots__` would prevent unpickling otherwise. *See also* :meth:`object.__setstate__` """ for slot, value in state.items(): setattr(self, slot, value) self._init_inv() def __repr__(self) -> str: """See :func:`repr`.""" clsname = self.__class__.__name__ if not self: return f'{clsname}()' return f'{clsname}({self._repr_delegate(self.items())})' # 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: object) -> bool: """*x.__eq__(other) ⟺ x == other* Equivalent to *dict(x.items()) == dict(other.items())* but more efficient. Note that :meth:`bidict's __eq__() ` implementation is inherited by subclasses, in particular by the ordered bidict subclasses, so even with ordered bidicts, :ref:`== comparison is order-insensitive `. *See also* :meth:`bidict.FrozenOrderedBidict.equals_order_sensitive` """ if not isinstance(other, _t.Mapping) or len(self) != len(other): return False selfget = self.get return all(selfget(k, _NONE) == v for (k, v) in other.items()) # type: ignore def equals_order_sensitive(self, other: object) -> bool: """Order-sensitive equality check. *See also* :ref:`eq-order-insensitive` """ # Same short-circuit as in __eq__ above. Factoring out not worth function call overhead. if not isinstance(other, _t.Mapping) or len(self) != len(other): return False return all(i == j for (i, j) in zip(self.items(), other.items())) # 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 # `on_dup`), so it makes sense for them to share implementation too.) def _pop(self, key: KT) -> VT: val = self._fwdm.pop(key) del self._invm[val] return val def _put(self, key: KT, val: VT, on_dup: OnDup) -> None: dedup_result = self._dedup_item(key, val, on_dup) if dedup_result is not None: self._write_item(key, val, dedup_result) def _dedup_item(self, key: KT, val: VT, on_dup: OnDup) -> _t.Optional[_DedupResult]: """Check *key* and *val* for any duplication in self. Handle any duplication as per the passed in *on_dup*. (key, val) already present is construed as a no-op, not a duplication. 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 error. If duplication is found and the corresponding :class:`~bidict.OnDupAction` is :attr:`~bidict.DROP_OLD`, or if no duplication is found, return the :class:`_DedupResult` *(isdupkey, isdupval, oldkey, oldval)*. """ fwdm = self._fwdm invm = self._invm oldval: OVT = fwdm.get(key, _NONE) oldkey: OKT = invm.get(val, _NONE) isdupkey = oldval is not _NONE isdupval = oldkey is not _NONE dedup_result = _DedupResult(isdupkey, isdupval, oldkey, oldval) if isdupkey and isdupval: if self._already_have(key, val, oldkey, 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 dedup_result @staticmethod def _already_have(key: KT, val: VT, oldkey: OKT, oldval: OVT) -> bool: # Overridden by _orderedbase.OrderedBidictBase. isdup = oldkey == key assert isdup == (oldval == val), f'{key} {val} {oldkey} {oldval}' return isdup def _write_item(self, key: KT, val: VT, dedup_result: _DedupResult) -> _WriteResult: # Overridden by _orderedbase.OrderedBidictBase. 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: bool, on_dup: OnDup, *args: MapOrIterItems[KT, VT], **kw: VT) -> None: # 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]) # type: ignore return 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: BidirectionalMapping[KT, VT]) -> None: write_item = self._write_item for (key, val) in other.items(): write_item(key, val, _NODUP) def _update_no_rollback(self, on_dup: OnDup, *args: MapOrIterItems[KT, VT], **kw: VT) -> None: put = self._put for (key, val) in _iteritems_args_kw(*args, **kw): put(key, val, on_dup) def _update_with_rollback(self, on_dup: OnDup, *args: MapOrIterItems[KT, VT], **kw: VT) -> None: """Update, rolling back on failure.""" writes: _t.List[_t.Tuple[_DedupResult, _WriteResult]] = [] append_write = writes.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(writes): undo_write(dedup_result, write_result) raise if dedup_result is not None: write_result = write_item(key, val, dedup_result) append_write((dedup_result, write_result)) def _undo_write(self, dedup_result: _DedupResult, write_result: _WriteResult) -> None: 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: BT) -> BT: """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. cp = self.__class__.__new__(self.__class__) cp._fwdm = copy(self._fwdm) cp._invm = copy(self._invm) cp._init_inv() return cp # type: ignore #: Used for the copy protocol. #: *See also* the :mod:`copy` module __copy__ = copy 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] # On Python 3.8+, dicts are reversible, so even non-Ordered bidicts can provide an efficient # __reversed__ implementation. (On Python < 3.8, they cannot.) Once support is dropped for # Python < 3.8, can remove the following if statement to provide __reversed__ unconditionally. if hasattr(_fwdm_cls, '__reversed__'): # pragma: no cover def __reversed__(self) -> _t.Iterator[KT]: """Iterator over the contained keys in reverse order.""" return reversed(self._fwdm) # Work around weakref slot with Generics bug on Python 3.6 (https://bugs.python.org/issue41451): BidictBase.__slots__.remove('__weakref__') # * Code review nav * #============================================================================== # ← Prev: _abc.py Current: _base.py Next: _frozenbidict.py → #============================================================================== ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/bidict/_bidict.py0000644000175100001710000000372500000000000015607 0ustar00runnerdocker# -*- coding: utf-8 -*- # Copyright 2009-2021 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 → #============================================================================== """Provide :class:`bidict`.""" import typing as _t from ._delegating import _DelegatingBidict from ._mut import MutableBidict from ._typing import KT, VT class bidict(_DelegatingBidict[KT, VT], MutableBidict[KT, VT]): """Base class for mutable bidirectional mappings.""" __slots__ = () if _t.TYPE_CHECKING: @property def inverse(self) -> 'bidict[VT, KT]': ... # * Code review nav * #============================================================================== # ← Prev: _mut.py Current: _bidict.py Next: _orderedbase.py → #============================================================================== ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/bidict/_delegating.py0000644000175100001710000000244100000000000016446 0ustar00runnerdocker# -*- coding: utf-8 -*- # Copyright 2009-2021 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:`_DelegatingBidict`.""" import typing as _t from ._base import BidictBase from ._typing import KT, VT class _DelegatingBidict(BidictBase[KT, VT]): """Provide optimized implementations of several methods by delegating to backing dicts. Used to override less efficient implementations inherited by :class:`~collections.abc.Mapping`. """ __slots__ = () def __iter__(self) -> _t.Iterator[KT]: """Iterator over the contained keys.""" return iter(self._fwdm) def keys(self) -> _t.KeysView[KT]: """A set-like object providing a view on the contained keys.""" return self._fwdm.keys() def values(self) -> _t.KeysView[VT]: # type: ignore # https://github.com/python/typeshed/issues/4435 """A set-like object providing a view on the contained values.""" return self._invm.keys() def items(self) -> _t.ItemsView[KT, VT]: """A set-like object providing a view on the contained items.""" return self._fwdm.items() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/bidict/_dup.py0000644000175100001710000000343300000000000015135 0ustar00runnerdocker# -*- coding: utf-8 -*- # Copyright 2009-2021 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 collections import namedtuple from enum import Enum class OnDupAction(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.name}>' RAISE = OnDupAction.RAISE DROP_OLD = OnDupAction.DROP_OLD DROP_NEW = OnDupAction.DROP_NEW class OnDup(namedtuple('_OnDup', 'key val kv')): r"""A 3-tuple of :class:`OnDupAction`\s specifying how to handle the 3 kinds of duplication. *See also* :ref:`basic-usage:Values Must Be Unique` If *kv* is not specified, *val* will be used for *kv*. """ __slots__ = () def __new__(cls, key: OnDupAction = DROP_OLD, val: OnDupAction = RAISE, kv: OnDupAction = RAISE) -> '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 = OnDup() #: An :class:`OnDup` whose members are all :obj:`RAISE`. ON_DUP_RAISE = OnDup(key=RAISE, val=RAISE, kv=RAISE) #: An :class:`OnDup` whose members are all :obj:`DROP_OLD`. ON_DUP_DROP_OLD = OnDup(key=DROP_OLD, val=DROP_OLD, kv=DROP_OLD) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/bidict/_exc.py0000644000175100001710000000203500000000000015121 0ustar00runnerdocker# -*- coding: utf-8 -*- # Copyright 2009-2021 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.""" 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. """ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/bidict/_frozenbidict.py0000644000175100001710000000470600000000000017033 0ustar00runnerdocker# -*- coding: utf-8 -*- # Copyright 2009-2021 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: _frozenbidict.py Next: _mut.py → #============================================================================== """Provide :class:`frozenbidict`, an immutable, hashable bidirectional mapping type.""" import typing as _t from ._delegating import _DelegatingBidict from ._typing import KT, VT class frozenbidict(_DelegatingBidict[KT, VT]): """Immutable, hashable bidict type.""" __slots__ = ('_hash',) # Work around lack of support for higher-kinded types in mypy. # Ref: https://github.com/python/typing/issues/548#issuecomment-621571821 # Remove this and similar type stubs from other classes if support is ever added. 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: self._hash = _t.ItemsView(self)._hash() # type: ignore return self._hash # type: ignore # * Code review nav * #============================================================================== # ← Prev: _base.py Current: _frozenbidict.py Next: _mut.py → #============================================================================== ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/bidict/_frozenordered.py0000644000175100001710000000624000000000000017214 0ustar00runnerdocker# -*- coding: utf-8 -*- # Copyright 2009-2021 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 → #============================================================================== """Provide :class:`FrozenOrderedBidict`, an immutable, hashable, ordered bidict.""" 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.""" __slots__ = ('_hash',) __hash__ = frozenbidict.__hash__ if _t.TYPE_CHECKING: @property def inverse(self) -> 'FrozenOrderedBidict[VT, KT]': ... # Assume the Python implementation's dict type is ordered (e.g. PyPy or CPython >= 3.6), so we # can delegate to `_fwdm` and `_invm` for faster implementations of several methods. 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. def __iter__(self) -> _t.Iterator[KT]: """Iterator over the contained keys in insertion order.""" return self._iter() def _iter(self, *, reverse: bool = False) -> _t.Iterator[KT]: if reverse: return super()._iter(reverse=True) return iter(self._fwdm._fwdm) def keys(self) -> _t.KeysView[KT]: """A set-like object providing a view on the contained keys.""" return self._fwdm._fwdm.keys() def values(self) -> _t.KeysView[VT]: # type: ignore """A set-like object providing a view on the contained values.""" return self._invm._fwdm.keys() # We can't delegate for items because values in `_fwdm` are nodes. # * Code review nav * #============================================================================== #← Prev: _orderedbase.py Current: _frozenordered.py Next: _orderedbidict.py → #============================================================================== ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/bidict/_iter.py0000644000175100001710000000443500000000000015313 0ustar00runnerdocker# -*- coding: utf-8 -*- # Copyright 2009-2021 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.""" import typing as _t from collections.abc import Mapping from itertools import chain, repeat from ._typing import KT, VT, IterItems, MapOrIterItems _NULL_IT = repeat(None, 0) # repeat 0 times -> raise StopIteration from the start def _iteritems_mapping_or_iterable(arg: MapOrIterItems[KT, VT]) -> IterItems[KT, VT]: """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 iter(arg.items() if isinstance(arg, Mapping) else arg) def _iteritems_args_kw(*args: MapOrIterItems[KT, VT], **kw: VT) -> IterItems[KT, VT]: """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(f'Expected at most 1 positional argument, got {args_len}') itemchain = None if args: arg = args[0] if arg: itemchain = _iteritems_mapping_or_iterable(arg) if kw: iterkw = iter(kw.items()) itemchain = chain(itemchain, iterkw) if itemchain else iterkw # type: ignore return itemchain or _NULL_IT # type: ignore @_t.overload def inverted(arg: _t.Mapping[KT, VT]) -> IterItems[VT, KT]: ... @_t.overload def inverted(arg: IterItems[KT, VT]) -> IterItems[VT, KT]: ... def inverted(arg: MapOrIterItems[KT, VT]) -> IterItems[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__` """ inv = getattr(arg, '__inverted__', None) if callable(inv): return inv() # type: ignore return ((val, key) for (key, val) in _iteritems_mapping_or_iterable(arg)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/bidict/_mut.py0000644000175100001710000001627000000000000015155 0ustar00runnerdocker# -*- coding: utf-8 -*- # Copyright 2009-2021 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 → #============================================================================== """Provide :class:`MutableBidict`.""" import typing as _t from ._abc import MutableBidirectionalMapping from ._base import BidictBase from ._dup import OnDup, ON_DUP_RAISE, ON_DUP_DROP_OLD from ._typing import _NONE, KT, VT, VDT, IterItems, MapOrIterItems class MutableBidict(BidictBase[KT, VT], MutableBidirectionalMapping[KT, VT]): """Base class for mutable bidirectional mappings.""" __slots__ = () if _t.TYPE_CHECKING: @property def inverse(self) -> 'MutableBidict[VT, KT]': ... 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, 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._put(key, val, 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_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: VDT = ...) -> VDT: ... def pop(self, key: KT, default: VDT = _NONE) -> VDT: """*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 _NONE: raise return default def popitem(self) -> _t.Tuple[KT, VT]: """*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 @_t.overload def update(self, __arg: _t.Mapping[KT, VT], **kw: VT) -> None: ... @_t.overload def update(self, __arg: IterItems[KT, VT], **kw: VT) -> None: ... @_t.overload def update(self, **kw: VT) -> None: ... def update(self, *args: MapOrIterItems[KT, VT], **kw: VT) -> None: """Like calling :meth:`putall` with *self.on_dup* passed for *on_dup*.""" if args or kw: self._update(False, self.on_dup, *args, **kw) @_t.overload def forceupdate(self, __arg: _t.Mapping[KT, VT], **kw: VT) -> None: ... @_t.overload def forceupdate(self, __arg: IterItems[KT, VT], **kw: VT) -> None: ... @_t.overload def forceupdate(self, **kw: VT) -> None: ... def forceupdate(self, *args: MapOrIterItems[KT, VT], **kw: VT) -> None: """Like a bulk :meth:`forceput`.""" self._update(False, ON_DUP_DROP_OLD, *args, **kw) @_t.overload def putall(self, items: _t.Mapping[KT, VT], on_dup: OnDup) -> None: ... @_t.overload def putall(self, items: IterItems[KT, VT], on_dup: OnDup = ON_DUP_RAISE) -> None: ... def putall(self, items: MapOrIterItems[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(False, on_dup, items) # * Code review nav * #============================================================================== # ← Prev: _frozenbidict.py Current: _mut.py Next: _bidict.py → #============================================================================== ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/bidict/_named.py0000644000175100001710000000731000000000000015427 0ustar00runnerdocker# -*- coding: utf-8 -*- # Copyright 2009-2021 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`.""" import typing as _t from sys import _getframe from ._abc import BidirectionalMapping, KT, VT from ._bidict import bidict def namedbidict( typename: str, keyname: str, valname: str, *, base_type: _t.Type[BidirectionalMapping[KT, VT]] = bidict, ) -> _t.Type[BidirectionalMapping[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 ` instances via the custom *keyname*\_for property, and access to themselves via the custom *valname*\_for property. *See also* the :ref:`namedbidict usage documentation ` :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:`BidirectionalMapping` subclass that provides ``_isinv`` and :meth:`~object.__getstate__` attributes. (Any :class:`~bidict.BidictBase` subclass can be passed in, including all the concrete bidict types pictured in the :ref:`other-bidict-types:Bidict Types Diagram`. """ if not issubclass(base_type, BidirectionalMapping) or not all(hasattr(base_type, i) for i in ('_isinv', '__getstate__')): raise TypeError(base_type) names = (typename, keyname, valname) if not all(map(str.isidentifier, names)) or keyname == valname: raise ValueError(names) class _Named(base_type): # type: ignore __slots__ = () def _getfwd(self) -> '_Named': return self.inverse if self._isinv else self # type: ignore def _getinv(self) -> '_Named': return self if self._isinv else self.inverse # type: ignore @property def _keyname(self) -> str: return valname if self._isinv else keyname @property def _valname(self) -> str: return keyname if self._isinv else valname def __reduce__(self) -> '_t.Tuple[_t.Callable[[str, str, str, _t.Type[BidirectionalMapping]], BidirectionalMapping], _t.Tuple[str, str, str, _t.Type[BidirectionalMapping]], dict]': return (_make_empty, (typename, keyname, valname, base_type), self.__getstate__()) bname = base_type.__name__ fname = valname + '_for' iname = keyname + '_for' fdoc = f'{typename} forward {bname}: {keyname} → {valname}' idoc = f'{typename} inverse {bname}: {valname} → {keyname}' setattr(_Named, fname, property(_Named._getfwd, doc=fdoc)) setattr(_Named, iname, property(_Named._getinv, doc=idoc)) _Named.__name__ = typename _Named.__qualname__ = typename _Named.__module__ = _getframe(1).f_globals.get('__name__') # type: ignore return _Named def _make_empty( typename: str, keyname: str, valname: str, base_type: _t.Type[BidirectionalMapping] = bidict, ) -> BidirectionalMapping: """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() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/bidict/_orderedbase.py0000644000175100001710000002676000000000000016634 0ustar00runnerdocker# -*- coding: utf-8 -*- # Copyright 2009-2021 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 → #============================================================================== """Provide :class:`OrderedBidictBase`.""" import typing as _t from copy import copy from weakref import ref from ._base import _NONE, _DedupResult, _WriteResult, BidictBase, BT from ._bidict import bidict from ._typing import KT, VT, IterItems, MapOrIterItems class _Node: """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: '_Node' = None, nxt: '_Node' = None) -> None: self._setprv(prv) self._setnxt(nxt) def __repr__(self) -> str: clsname = self.__class__.__name__ prv = id(self.prv) nxt = id(self.nxt) return f'{clsname}(prv={prv}, self={id(self)}, nxt={nxt})' def _getprv(self) -> '_t.Optional[_Node]': return self._prv() if isinstance(self._prv, ref) else self._prv def _setprv(self, prv: '_t.Optional[_Node]') -> None: self._prv = prv and ref(prv) prv = property(_getprv, _setprv) def _getnxt(self) -> '_t.Optional[_Node]': return self._nxt() if isinstance(self._nxt, ref) else self._nxt def _setnxt(self, nxt: '_t.Optional[_Node]') -> None: self._nxt = nxt and ref(nxt) nxt = property(_getnxt, _setnxt) def __getstate__(self) -> dict: """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: dict) -> None: """Set the instance state from *state*.""" self._setprv(state['_prv']) self._setnxt(state['_nxt']) 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. """ __slots__ = () def __init__(self, prv: _Node = None, nxt: _Node = None) -> None: super().__init__(prv or self, nxt or self) def __repr__(self) -> str: return '' def __bool__(self) -> bool: return False def _iter(self, *, reverse: bool = False) -> _t.Iterator[_Node]: """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[KT, VT]): """Base class implementing an ordered :class:`BidirectionalMapping`.""" __slots__ = ('_sntl',) _fwdm_cls = bidict # type: ignore _invm_cls = bidict # type: ignore #: The object used by :meth:`__repr__` for printing the contained items. _repr_delegate = list # type: ignore @_t.overload def __init__(self, __arg: _t.Mapping[KT, VT], **kw: VT) -> None: ... @_t.overload def __init__(self, __arg: IterItems[KT, VT], **kw: VT) -> None: ... @_t.overload def __init__(self, **kw: VT) -> None: ... def __init__(self, *args: MapOrIterItems[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() # 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().__init__(*args, **kw) if _t.TYPE_CHECKING: @property def inverse(self) -> 'OrderedBidictBase[VT, KT]': ... _fwdm: bidict[KT, _Node] # type: ignore _invm: bidict[VT, _Node] # type: ignore def _init_inv(self) -> None: super()._init_inv() self.inverse._sntl = self._sntl # Can't reuse BidictBase.copy since ordered bidicts have different internal structure. def copy(self: BT) -> BT: """A shallow copy of this ordered bidict.""" # Fast copy implementation bypassing __init__. See comments in :meth:`BidictBase.copy`. cp = self.__class__.__new__(self.__class__) sntl = _SentinelNode() fwdm = copy(self._fwdm) invm = copy(self._invm) cur = sntl nxt = sntl.nxt for (key, val) in self.items(): nxt = _Node(cur, sntl) cur.nxt = fwdm[key] = invm[val] = nxt cur = nxt sntl.prv = nxt cp._sntl = sntl cp._fwdm = fwdm cp._invm = invm cp._init_inv() return cp # type: ignore __copy__ = copy def __getitem__(self, key: KT) -> VT: nodefwd = self._fwdm[key] val = self._invm.inverse[nodefwd] return val def _pop(self, key: KT) -> VT: nodefwd = self._fwdm.pop(key) val = self._invm.inverse.pop(nodefwd) nodefwd.prv.nxt = nodefwd.nxt nodefwd.nxt.prv = nodefwd.prv return val @staticmethod def _already_have(key: KT, val: VT, nodeinv: _Node, nodefwd: _Node) -> bool: # type: ignore # Overrides _base.BidictBase. return nodeinv is nodefwd def _write_item(self, key: KT, val: VT, dedup_result: _DedupResult) -> _WriteResult: # Overrides _base.BidictBase. 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 = _NONE elif isdupkey and isdupval: # Key and value duplication across two different nodes. assert nodefwd is not nodeinv oldval = invm.inverse[nodefwd] # type: ignore oldkey = fwdm.inverse[nodeinv] # type: ignore 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) # type: ignore assert tmp is nodeinv tmp = invm.pop(oldval) # type: ignore assert tmp is nodefwd fwdm[key] = invm[val] = nodefwd elif isdupkey: oldval = invm.inverse[nodefwd] # type: ignore oldkey = _NONE oldnodeinv = invm.pop(oldval) # type: ignore assert oldnodeinv is nodefwd invm[val] = nodefwd else: # isdupval oldkey = fwdm.inverse[nodeinv] # type: ignore oldval = _NONE oldnodefwd = fwdm.pop(oldkey) # type: ignore assert oldnodefwd is nodeinv fwdm[key] = nodeinv return _WriteResult(key, val, oldkey, oldval) def _undo_write(self, dedup_result: _DedupResult, write_result: _WriteResult) -> None: 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) -> _t.Iterator[KT]: """Iterator over the contained keys in insertion order.""" return self._iter() def _iter(self, *, reverse: bool = False) -> _t.Iterator[KT]: fwdm_inv = self._fwdm.inverse for node in self._sntl._iter(reverse=reverse): yield fwdm_inv[node] def __reversed__(self) -> _t.Iterator[KT]: """Iterator over the contained keys in reverse insertion order.""" yield from self._iter(reverse=True) # * Code review nav * #============================================================================== # ← Prev: _bidict.py Current: _orderedbase.py Next: _frozenordered.py → #============================================================================== ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/bidict/_orderedbidict.py0000644000175100001710000000652100000000000017151 0ustar00runnerdocker# -*- coding: utf-8 -*- # Copyright 2009-2021 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 #============================================================================== """Provide :class:`OrderedBidict`.""" import typing as _t from ._mut 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.""" __slots__ = () if _t.TYPE_CHECKING: @property def inverse(self) -> 'OrderedBidict[VT, KT]': ... def clear(self) -> None: """Remove all items.""" self._fwdm.clear() self._invm.clear() self._sntl.nxt = self._sntl.prv = self._sntl def popitem(self, last: bool = True) -> _t.Tuple[KT, VT]: """*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)) # type: ignore val = self._pop(key) return key, val def move_to_end(self, key: KT, last: bool = True) -> None: """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: 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 # * Code review nav * #============================================================================== # ← Prev: _frozenordered.py Current: _orderedbidict.py #============================================================================== ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/bidict/_typing.py0000644000175100001710000000153600000000000015661 0ustar00runnerdocker# -*- coding: utf-8 -*- # Copyright 2009-2021 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.""" import typing as _t KT = _t.TypeVar('KT') VT = _t.TypeVar('VT') IterItems = _t.Iterable[_t.Tuple[KT, VT]] MapOrIterItems = _t.Union[_t.Mapping[KT, VT], IterItems[KT, VT]] DT = _t.TypeVar('DT') #: for default arguments VDT = _t.Union[VT, DT] class _BareReprMeta(type): def __repr__(cls) -> str: return f'<{cls.__name__}>' class _NONE(metaclass=_BareReprMeta): """Sentinel type used to represent 'missing'.""" OKT = _t.Union[KT, _NONE] #: optional key type OVT = _t.Union[VT, _NONE] #: optional value type ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/bidict/metadata.py0000644000175100001710000000203700000000000015765 0ustar00runnerdocker# -*- coding: utf-8 -*- # Copyright 2009-2021 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.21.3' __author__ = 'Joshua Bronson' __maintainer__ = 'Joshua Bronson' __copyright__ = 'Copyright 2009-2021 Joshua Bronson' __email__ = 'jabronson@gmail.com' # See: ../docs/thanks.rst __credits__ = [i.strip() for i in """ 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(',')] __description__ = 'The bidirectional mapping library for Python.' __keywords__ = 'dict dictionary mapping datastructure bimap bijection bijective ' \ 'injective inverse reverse bidirectional two-way 2-way' __license__ = 'MPL 2.0' __status__ = 'Beta' __url__ = 'https://bidict.readthedocs.io' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/bidict/py.typed0000644000175100001710000000000000000000000015316 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1630855987.6393647 bidict-0.21.3/bidict.egg-info/0000755000175100001710000000000000000000000015323 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855987.0 bidict-0.21.3/bidict.egg-info/PKG-INFO0000644000175100001710000002624700000000000016433 0ustar00runnerdockerMetadata-Version: 2.1 Name: bidict Version: 0.21.3 Summary: The bidirectional mapping library for Python. Home-page: https://bidict.readthedocs.io Author: Joshua Bronson Author-email: jabronson@gmail.com License: MPL 2.0 Description: .. Forward declarations for all the custom interpreted text roles that Sphinx defines and that are used below. This helps Sphinx-unaware tools (e.g. rst2html, PyPI's and GitHub's renderers, etc.). .. role:: doc .. Use :doc: rather than :ref: references below for better interop as well. bidict ====== The bidirectional mapping library for Python. .. image:: https://raw.githubusercontent.com/jab/bidict/main/assets/logo-sm-white-bg.jpg :target: https://bidict.readthedocs.io/ :alt: bidict logo Status ------ .. image:: https://img.shields.io/pypi/v/bidict.svg :target: https://pypi.org/project/bidict :alt: Latest release .. image:: https://img.shields.io/readthedocs/bidict/main.svg :target: https://bidict.readthedocs.io/en/main/ :alt: Documentation .. image:: https://github.com/jab/bidict/workflows/Tests/badge.svg :target: https://github.com/jab/bidict/actions :alt: GitHub Actions CI status .. image:: https://codecov.io/gh/jab/bidict/branch/main/graph/badge.svg :target: https://codecov.io/gh/jab/bidict :alt: Test coverage .. Hide to reduce clutter .. image:: https://img.shields.io/lgtm/alerts/github/jab/bidict.svg :target: https://lgtm.com/projects/g/jab/bidict/ :alt: LGTM alerts .. image:: https://bestpractices.coreinfrastructure.org/projects/2354/badge :target: https://bestpractices.coreinfrastructure.org/en/projects/2354 :alt: CII best practices badge .. image:: https://img.shields.io/badge/tidelift-pro%20support-orange.svg :target: https://tidelift.com/subscription/pkg/pypi-bidict?utm_source=pypi-bidict&utm_medium=referral&utm_campaign=docs :alt: Paid support available via Tidelift .. image:: https://img.shields.io/pypi/pyversions/bidict.svg :target: https://pypi.org/project/bidict :alt: Supported Python versions .. image:: https://img.shields.io/pypi/implementation/bidict.svg :target: https://pypi.org/project/bidict :alt: Supported Python implementations .. image:: https://img.shields.io/pypi/l/bidict.svg :target: https://raw.githubusercontent.com/jab/bidict/main/LICENSE :alt: License .. image:: https://static.pepy.tech/badge/bidict :target: https://pepy.tech/project/bidict :alt: PyPI Downloads .. image:: https://img.shields.io/badge/GitHub-sponsor-ff69b4 :target: https://github.com/sponsors/jab :alt: Sponsor through GitHub bidict: ^^^^^^^ - has been used for many years by several teams at **Google, Venmo, CERN, Bank of America Merrill Lynch, Bloomberg, Two Sigma,** and many others - has carefully designed APIs for **safety, simplicity, flexibility, and ergonomics** - is **fast, lightweight, and has no runtime dependencies** other than Python's standard library - **integrates natively** with Python’s ``collections.abc`` interfaces - provides **type hints** for all public APIs - is implemented in **concise, well-factored, pure (PyPy-compatible) Python code** that is **optimized for running efficiently** as well as for **reading and learning** [#fn-learning]_ - has **extensive docs and test coverage** (including property-based tests and benchmarks) run continuously on all supported Python versions Installation ------------ ``pip install bidict`` Quick Start ----------- .. code:: python >>> from bidict import bidict >>> element_by_symbol = bidict({'H': 'hydrogen'}) >>> element_by_symbol['H'] 'hydrogen' >>> element_by_symbol.inverse['hydrogen'] 'H' For more usage documentation, head to the :doc:`intro` [#fn-intro]_ and proceed from there. Voluntary Community Support --------------------------- .. image:: https://img.shields.io/badge/gitter-chat-5AB999.svg?logo=gitter-white :target: https://gitter.im/jab/bidict :alt: Chat Please feel free to leave a message in the `bidict chatroom `__ or open a new issue on GitHub for voluntary community support. You can search through `existing issues `__ before creating a new one in case your issue has been addressed already. Enterprise Support ------------------ .. image:: https://img.shields.io/badge/tidelift-enterprise%20support-orange.svg :target: https://tidelift.com/subscription/pkg/pypi-bidict?utm_source=pypi-bidict&utm_medium=referral&utm_campaign=readme :alt: Enterprise support via Tidelift Enterprise-level support for bidict can be obtained via the `Tidelift subscription `__. Notice of Usage --------------- If you use bidict, and especially if your usage or your organization is significant in some way, please let me know in any of the following ways: - `star bidict on GitHub `__ - `create an issue `__ - leave a message in the `chat room `__ - `email me `__ Changelog --------- See the :doc:`changelog` [#fn-changelog]_ for a history of notable changes to bidict. Release Notifications --------------------- .. duplicated in CHANGELOG.rst: (would use `.. include::` but GitHub doesn't understand it) .. image:: https://img.shields.io/badge/libraries.io-subscribe-5BC0DF.svg :target: https://libraries.io/pypi/bidict :alt: Follow on libraries.io Watch releases `on GitHub `__ or `libraries.io `__ to be notified when new versions of bidict are released. Learning from bidict -------------------- One of the best things about bidict is that it touches a surprising number of interesting Python corners, especially given its small size and scope. Check out :doc:`learning-from-bidict` [#fn-learning]_ if you're interested in learning more. Contributing ------------ bidict is currently a one-person operation maintained on a voluntary basis. Your help would be most welcome! See the :doc:`contributors-guide` [#fn-contributing]_ for more information. Sponsoring ^^^^^^^^^^ .. duplicated in CONTRIBUTING.rst (would use `.. include::` but GitHub doesn't understand it) .. image:: https://img.shields.io/badge/GitHub-sponsor-ff69b4 :target: https://github.com/sponsors/jab :alt: Sponsor through GitHub Bidict is the product of thousands of hours of my unpaid work over the 12+ years I've been maintaining it. If bidict has helped you accomplish your work, especially work you've been paid for, it's easy to `sponsor me through GitHub `__. Choose a tier and GitHub handles everything else. The sponsorship just goes on your regular GitHub bill; there's nothing extra to do. You can also sponsor me through `Gumroad `__ or `PayPal `__. Read more about `companies supporting open source developers `__. Finding Documentation --------------------- If you're viewing this on ``__, note that multiple versions of the documentation are available, and you can choose a different version using the popup menu at the bottom-right. Please make sure you're viewing the version of the documentation that corresponds to the version of bidict you'd like to use. If you're viewing this on GitHub, PyPI, or some other place that can't render and link this documentation properly and are seeing broken links, try these alternate links instead: .. [#fn-learning] ``__ | ``__ .. [#fn-changelog] ``__ | ``__ .. [#fn-intro] ``__ | ``__ .. [#fn-contributing] ``__ | ``__ ---- Next: :doc:`intro` [#fn-intro]_ Keywords: dict dictionary mapping datastructure bimap bijection bijective injective inverse reverse bidirectional two-way 2-way Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0) Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Typing :: Typed Requires-Python: >=3.6 Description-Content-Type: text/x-rst ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855987.0 bidict-0.21.3/bidict.egg-info/SOURCES.txt0000644000175100001710000000406200000000000017211 0ustar00runnerdocker.LICENSE_HEADER .coveragerc .editorconfig .lgtm.yml .pre-commit-config.yaml .pylintrc .readthedocs.yml CHANGELOG.rst CODE_OF_CONDUCT.rst CONTRIBUTING.rst LICENSE MANIFEST.in PYPI_DOWNLOAD_STATS.rst README.rst SECURITY.rst build-docs.sh codecov.yml mypy.ini pyproject.toml pytest.ini run_tests.py setup.cfg setup.py tox.ini assets/bidict-types-diagram.dot assets/bidict-types-diagram.png assets/custom.css assets/custom.js assets/favicon.ico assets/logo-sm-white-bg.jpg assets/logo-sm.png assets/logo-square.png assets/logo.png assets/support-on-gumroad.png bidict/__init__.py bidict/_abc.py bidict/_base.py bidict/_bidict.py bidict/_delegating.py bidict/_dup.py bidict/_exc.py bidict/_frozenbidict.py bidict/_frozenordered.py bidict/_iter.py bidict/_mut.py bidict/_named.py bidict/_orderedbase.py bidict/_orderedbidict.py bidict/_typing.py bidict/metadata.py bidict/py.typed bidict.egg-info/PKG-INFO bidict.egg-info/SOURCES.txt bidict.egg-info/dependency_links.txt bidict.egg-info/not-zip-safe bidict.egg-info/top_level.txt docs/Makefile docs/addendum.rst docs/api.rst docs/basic-usage.rst docs/changelog.rst docs/code-of-conduct.rst docs/conf.py docs/contributors-guide.rst docs/extending.rst docs/home.rst docs/index.rst docs/intro.rst docs/learning-from-bidict.rst docs/other-bidict-types.rst docs/other-functionality.rst docs/thanks.rst docs/_static/bidict-types-diagram.dot docs/_static/bidict-types-diagram.png docs/_static/custom.css docs/_static/custom.js docs/_static/favicon.ico docs/_static/logo-sm-white-bg.jpg docs/_static/logo-sm.png docs/_static/logo-square.png docs/_static/logo.png docs/_static/support-on-gumroad.png requirements/dev.in requirements/dev.txt requirements/docs.in requirements/docs.txt requirements/lint.in requirements/lint.txt requirements/tests.in requirements/tests.txt tests/__init__.py tests/conftest.py tests/test_benchmark.py tests/test_bidict.txt tests/test_class_relationships.py tests/test_metadata.py tests/test_orderedbidict.txt tests/properties/__init__.py tests/properties/_strategies.py tests/properties/_types.py tests/properties/test_properties.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855987.0 bidict-0.21.3/bidict.egg-info/dependency_links.txt0000644000175100001710000000000100000000000021371 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855987.0 bidict-0.21.3/bidict.egg-info/not-zip-safe0000644000175100001710000000000100000000000017551 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855987.0 bidict-0.21.3/bidict.egg-info/top_level.txt0000644000175100001710000000000700000000000020052 0ustar00runnerdockerbidict ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/build-docs.sh0000755000175100001710000000274100000000000014763 0ustar00runnerdocker#!/bin/bash # # Copyright 2009-2021 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/. set -euo pipefail log() { echo >&2 " *" "$@" } # Generate a new graph image from its source file if it's been modified. update_graph() { local -r graph_src="bidict-types-diagram.dot" local -r graph_dst="${graph_src%.*}.png" if [[ ! "$(git diff --name-only -- "$graph_src")" ]] && [[ ! "$(git diff --name-only --cached -- "$graph_src")" ]]; then log "$graph_src not modified -> skipping graph update." return 0 fi if ! command -v dot &>/dev/null; then log "'dot' not found -> skipping graph update. Hint: brew install graphviz" return 1 fi if ! dot -v -Tpng -o "$graph_dst" <"$graph_src"; then log "dot exited nonzero." return 1 fi # return 0 if any of the below fail because running dot succeeded, which is the main thing. if ! command -v optipng &>/dev/null; then log "'optipng' not found -> skipping png optimization. Hint: brew install optipng" return 0 fi if ! optipng "$graph_dst"; then log "optipng exited nonzero." return 0 fi } # Use parentheses instead of braces around body so it runs in a subshell -> cd doesn't leak. build_docs() ( make clean html ) main() { cd assets update_graph cd - cd docs build_docs cd - } main ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/codecov.yml0000644000175100001710000000013200000000000014534 0ustar00runnerdockercoverage: status: project: default: target: 99% threshold: 1% ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1630855987.6393647 bidict-0.21.3/docs/0000755000175100001710000000000000000000000013323 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/docs/Makefile0000644000175100001710000001515200000000000014767 0ustar00runnerdocker# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/bidict.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/bidict.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/bidict" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/bidict" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1630855987.6393647 bidict-0.21.3/docs/_static/0000755000175100001710000000000000000000000014751 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/docs/_static/bidict-types-diagram.dot0000644000175100001710000000342000000000000021462 0ustar00runnerdocker// Copyright 2009-2021 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/. // See ../build-docs.sh for how to generate an image from this file. digraph G { rankdir=BT dpi=300 node [fontsize="12", shape="box"] subgraph ABCs { node [fillcolor="#EFEFEF", color="#666666", fontcolor="#333333", style="filled", fontname="Cousine Nerd Font Italic"] Mapping [label="collections.abc.Mapping"] MutableMapping [label="collections.abc.MutableMapping"] Hashable [label="collections.abc.Hashable"] MutableMapping -> Mapping { rank=same Mapping MutableMapping Hashable } BidirectionalMapping [label="bidict._abc.BidirectionalMapping", style="filled, bold", fontcolor="black", fontname="Cousine Nerd Font Bold Italic"] MutableBidirectionalMapping [label="bidict._abc.MutableBidirectionalMapping", style="filled, bold", fontcolor="black", fontname="Cousine Nerd Font Bold Italic"] BidirectionalMapping -> Mapping MutableBidirectionalMapping -> BidirectionalMapping MutableBidirectionalMapping -> MutableMapping } subgraph { node [style="bold", fontname="FiraMono Nerd Font Bold"] bidict [label="bidict.bidict"] frozenbidict [label="bidict.frozenbidict"] OrderedBidict [label="bidict.OrderedBidict"] FrozenOrderedBidict [label="bidict.FrozenOrderedBidict"] bidict -> { MutableBidirectionalMapping } OrderedBidict -> { MutableBidirectionalMapping } FrozenOrderedBidict -> { BidirectionalMapping, Hashable } frozenbidict -> { BidirectionalMapping, Hashable } { rank=same bidict frozenbidict OrderedBidict FrozenOrderedBidict } } } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/docs/_static/bidict-types-diagram.png0000644000175100001710000023513600000000000021473 0ustar00runnerdockerPNG  IHDR ;bKGD̿:IDATxg$eDsNG'^rI(HV  *IQPd$gX`wL[nt0;]=SOj$@PiTa>G@PqTa>G@PqTa>G@PqD\D$[{hTH]t4$ɡx0I%#IzaLB¦s1&Ių|$)$|0_/'I|0_O0 $I!\0/D\]Q'I݄1$|Q"I.]5N_ I0_O I|a0$I I|a>I|I I|a>IR/$Ia0$)$|a>I$|$|$)$|0_/'I|$$I !'IR/'I $Ia0_O$|0_O $IRO I $I $Ia0$) I0_O I|a0$I I|a>I|a>I I|a>IR/$Ia>IR/'IRa>I|a>IR/'I |$)$|0_/'I|0_O0_O$|0_O $IRO0$Ia0_O0_O I|a0$I $Ia0$) I0$)$) I I|a>IR/$Ia0$)$|a>I$|$|$)$|0_/'I|0_O0|$|0_O $IR/'I $Ia0_O$$Ia0$Ia0$I $Ia0$) I0($|0_/'IR/'I $Ia0_O$|0_O $IRO ICO0_O I|a0$I $Ia0$) I0$)$)$Ia0$)$|a>I$|$$)$|0_/'I|a>IR/'I |$)'I $I $IR/'I $Ia0_O$|0_O $I $Ia0$) I0_O I|a0?-c[7+$8cN՘Fcf1 O0+X{Xyߊ0qa~o\?dwuc^J/̯hK:i ߥ*_h2f8aiVfk+[7.p6C 6`ar揲/43NYʃV C!hS1 %Zy98/zmkI`6EGǬ Lm '̯ʣ B1sߊ0_?Cp:}Zyf(a0V^Lpll]wz9앯 ЏdU޽V{}[PkV}cV,SQ+pz<(f.CP?;}`4leءu߹c^J/̯e`u91;z⛙ktf B/|̌ 9G{c6YJV͟u^yqQ\la塋[~NQ]wG_ٺՀ1 %{s]m^XɑyS7)f0qYG\:4h/~̆5TgX08$W]tZ{P/CLw|?G~nǼ30__[jsN<-򞝎k| |N|xWGt?fÚe3jK#!aPNk ~{=߼#G~kǼ30__[YIϷiѕlSJa1+f?`lXLuF Gb$bfY?=<<ڃ|a~ywk*xha k^GJغ /9JKöٍ\1c5h/~̆3TgX0ϊMOaNk ~)?c[ُ}Ƽ30_ߗWWbFlCō=l}_$&NlaqJl+oGqP|*6uc=W81Nclw?1iYw=qr1+85=b*v)^q:{X7ЖWs]5˭xlkFK7 oÁX9LgGg;smȍh3jōpg"F-OJrW/#cx.qx\?pPYq`68.K?9% E~ka<_[d|7}yGq{ۛݞ4~ hԊ2fhj[=-mo+j:4޹vq1c/E.̟+cra~+2e<[{}Qvʣ. ս CP?rF-<''au?/|,3,*\{=-liiv{ZcV,gfZ6۾/ d|3\37IoyȿA9~S0?|X<[{{Qv+cQvۅņÄ~SA6i^\VxoO=qMЂXwpyBa>3Ǟ|rҟܺN\&|eȄ?}^>t>nqr|1P~b)Niy1rG⋱Nx >> >#RNYiZ֞qHĎS|t"Y7]+qh8a<[˷|X&Jߟ?2t;&, n)''=Z1+k?jf0~d{8:s\?pg&E+.qH|-fo\dW y)J̇UZy {Qʣ̵GWewrVew]]l=L~%tnRd>wu|<4ni[%sN.o^(@ҁN6>'o{s1-YO&|ua{O-z-m}tڻؿ3c~@*H<t-lF,t|c6~>vqm?VN3h_魑xtOnu;^n,vN7s{WƺIj Z1,8~h}7w~C1+qbG{Q;fe2G}xoƇ'ySlalo;C=0[NL8M80{~a>zהG7jN.o^(@3gt|z5fZgߡٴWo{,𬎧 `9_ȞC]<6'}n<;.t˔jn}z{F˟|vϯoR]⤻'G3-S\aK߀[w3&TϱneN s|cV,N 7oS{[P/5]gdhC?#X3/C͇Zy sQ0]yZyvv2z0_?CN_}w۫{| 4̿'}RUϞR7i#>gntx5l}O=v6o5-s]1g”i>'=O|=bKǬ1g<.bn;NIM|oʜeZޣ8ⶭ;fy0OZ-VNJ@gLqךmBV7ݫoW=#7y+gٱb7f׵JN6~[IG↖?o5G w#61yNK.Xv+ܭ}S?t{hh7jƬY&=^2m^W#n(x|3\Q3Ԫmkz "Ύ'ڣZ+a=((Bb0a~a{?OH3|c[6 hçt/x~4tGK7xLض9uܸ6HSf~{[e?ݯ%ԟ._6GE1ywX!f4{d}S|ն]LQ3f2G->th\Qyf3= uXz6.̟c$07Vm1Yj+a=l+sQvۅa|P?lٮnkuy]q_Ԙ| 8r붾?0m{̿ԯx)#Z0}l?3dֿtzC8{{=w ڝ$vM:ei+q|\?EO+6K@mʿms⎘_Y[w:궓_8Z1+w?jRWxP۫: wܸ𥎷o1oW*8X4+ǖê<7+To1GWet2Wew]]&CP?_Y7L8 #ߘ=N.o^(ǀ<-dNϤo<bN&Wn{W8%˓%Ŏ'&҉_J| wvh;;ɳ|8'˦^ҿ˝ =ï;[r?jyƬY&;_C¶p˷u. uy'|VbŸHk<7+Tm1̵GWeu2Wew]]&CP?lfqjßa_>N.o^(ǀ;N+SMWۏ.񇹾-{+7w\ync6>Tm8I9n7 ,}ci[,oeUk1{ ɳ|c'|;`{Y;ט:=Ϩrgh?+\pygx\26o oQf<ʣ.Vʣ. յÄ~ꇭ}4]>f,]]1{U|/3\޼P?Aaku# ]vڅ[.`k<5+To1̵G}Wu2Ww]]&CP?li1[Lvwu|\G\2 pO:usݏH[O*+r /..rRԘmaIxc8%vw.Pn%5Z^-)??w_zt|x˚_)HI11h;jYLQxfʷu3\jg[}s83Oi:Fc;}ՀbW;bBo>cXBV\{uQf+sQ~ۅa|P?̿0&9e˗ 10.O9TNf\5׷E_-rpӞI1c6+~6[,MϺAX7\m)>x8C rX4⎘Nz-_/XTs~|gʝeZޣ}5DoeXe: zwg= )=+"7Vo1Yz+=((Bb0a~ak8&/"ﻺ|c>sU|3\޼Potj__wޡgCotM'Rq_?턜cfS+:~}6y?PxMW>;֘23_̶9{ɳ|th+Ͷb9Z1+w7j=pmȷu. 5۾\zWݬZ+nê<7+To1G]Wv2Wew]]&CP?lVo_#~3|c~e#j{| 8̟վ=Cm~vU_sf{tvot_+r^tlZ˷mmqJ?8uY>0N\Nm ,h;jyƬY&ߨ?OX#/x<3\ڝ]:vK&?a_n f{\?䐶4}mem^?泥TntܮO?fm_#WCfK^s:|X;U'h|#vO k_aw\o񳯧&9*s=2g|6}Fܣog;C=>ڽ͛wtq z/-xjg-27Vm1GV\{Tg1GuV\{ nSwv2{0_?CQ|@o;f|y0=N9|?Wӝ:~ufWCo 6n3b8mC[yO{xTckjN.o^(ǀ״&rqEM?Cޔ.g\'g:,n~+e@*gq>C7f;^[ܶ'ŵOVh퐛VB&4Nd6=~v{lm2HOOϱf<+_>q{Okm & =Z1+o7jٱ”{(-˔o|3\j7c⽱C;Xb81:ųڣZ+=ڣJ+a=-os+ CP?[yhzm>kuywޏ꽓m˛ 10bc7f#v]Ԗ+҉3klU {[zZz$te}Ty~"=-'n_;UԘuW|T+BcoO fMbҳ-[}w;|߷#W>|k-~6 Y' b45w|cV,oMa J?Q[Pc1gX/.p|8l+#_4V#Zlo>ckjS ި7kMflH-n8rg3[v}'w[qM̿hbjIWڻ.]md›y7HytGL|l^h\=6mN[oָG{Q7fe2yG-~P;!+&{ZOi̳u. 5cq|ߵMc<#pV+nY͇<(gQڣ:+a=#_wz0_?Cpao!{[}.ﻺ|c+ő| 8'i[=fm^6[n3 X8@'Nmiq+7/N9pw^Uؘszr{]o)G)ћ]f^*ck>˝qllо.㮮+ ۞w&Sq7bmI_]Cc_}Jka~p+=({QpUZy ft2z0_?Cpau)ϻ|c+g̋| 8ˮ`8'츼d'^8=.6;U&~3{nrsqI?eqN֩֘O8&NsҸ((񫸧ŧ7qk7<9~7Ը<Ssi3˾~F1+{~CZ\8rt|[W?DgzqZ$<4{N [Wk5o|hGÅ=$Ckrl$)̇0$YK7':;.đW|O礛"n1=(&Sw擜=]&'IRa>Iout [jogI7hyd!'9zXX=LO0|н9]-o]HI>;1_D|zX]]&'IRa>Ir_n{|b߸*YqM[6f I=]&'IRa>Ir_wv|4E7  8dy=]&'IRa>Ir~<{˿C [Wb|od=]&'IRa>Ir讕?8/e&E [W!na>IÚa|$$~u|4n_ŕq0$aMba|$$9$0$Ia>$I $Ia0$) I0_O I|a0$I I|a>I|a>I I|a>IR/$Ia0$)$|a>I$|0_/'I|a>IR/'I |$)'I $I !'IR/'I $Ia0_O$|0_O $IRO I I0_O I|a0$I I$)̇0$Ia0$)$|a>I I|a>IR/$Ia>IR/'IR/w$)$|0_/'I|a>IR/'I |$)'I $Ia0_O$|0_O $IRO ICO0_O I|a0$I $Ia0$) I0$)$)$Ia0$)$|a>I$)'IRa>I|a>IR/'I |$)$|0_/'I|0_O0_/'IR/'I $Ia0_O$|0_O $IRO I|a0$I $Ia0$) I0$)$)̇0$Ia0$)$|a>I I|a>IR/$Ia>IR/'IR/'I|a>IR/'I |$)'IRO0|$|0_O $IR/'I $Ia0_O$$Ia0$Ia0_O0_O I|a0$I >0$Ia^O(}@ dΊUԀ $vxHHa0,oUU |a>ɩ|YlՁ B]=Fru d-1fă%ÜxUln,Ha08$ԁ|Q"X4Fb ?va:kcxflT~#8݈0a>INb$FbY kfu|1# [8]|n3c$/#1+k|HH0_O! dE6%~؟Hċ )d{_-w)^[Gb dhKn5뗾G}$م+Eⱓz[{Q!3GSo7 \jYq-%xMj7#K/3qϏ):.*2̍3͓U`N{0I˸W]J@9 pIP?'J߳蛆 h|(o.{SVfI}= W-+wMڣg&!"Beo@_ܼ@W]<P* Gٕ2 5 h|(XЭO* }0W"LWbQH|7 P$W\msy2^POXYpoSry5-;JxK%.zk0e<KiO_!ĎWwH|{}Xo P,S-B xCAH4Q=G{~ͰM@)x/\6E7Qyx{OAHľ񌊟9 '|(:,^(Օ*1\`%)q 4a>˺]i20LKP"@\kHlSW{y!0],V СDB16EGⷕx ˝ 3PP,j~ߗʀ#b٧ǔ w#L7v%r#1We'pXzy{wEW&@NbɜAH1y]}]ɠuGr\˾# x:^Exn $F芼 5=.* *\Q3yàuFhK׋#?U.@nn}WU^hЁ"by'S:ڞ{ PP,aw3@}@Wh} 9PGP45f0Eߝ.u){E#kths1h\LC;r焛_sb)Uwmk}[ ;P?P4c!+V:4ϱTa}Ɂ<3fsBO̾; P7P4_wnRݸ滱h̷SB]gJf:|(tlzi+qc b$C }rv,-~t|bRY2fUhk9!e6 @DD K٩Qq_.;z!bX6gţJ@</﯌.^:NS>X$zqBEtd3?ñD*AH2z>L2vx ^ ;+|(,tqvZ~8 0.N88^slcX;֌$^jFܩ'^zȆd[KĹz 7uaƫcX3[ @@nbEG1駗TLx|^cN̉C\WqA(kfwek??\ݒxSkzEl;^t }ǜ=#y{:f P<3c㸠Ͽ.pb0e=5J(b5f7uޖ^? :P^磆h|(Og˪Ɵ|?oQ @nS-H:A1hYN)}pe<7Ef>a4a>Zluʝ37ċRu̝f+ρ4a>Yiiyb[g}rm~kxjڽ-׾Ch|(߲B|0('OFb4٫h|(C3!`ޗƛSBmZ`t!A2'SІu.yRobsi>U`cP6fKW$VϺQJ])4>z,u@s@\+X)3O;q%ΪNP>+ZdВyR`n:y$^7MZ|;2 9|S &֬C~_)]kzovY%0xu^1yR:w_Mju 9 웖kyR:pT7"rC0x:B1H<돿P d=c1g+W4a>TMi YT)Spf<#ıOh0*%P'0ΒYwF)m HU;SM@S@UZ(fxNQĬTY4a>TIT "Cܪ\/J1ܩ@S@U{8[jD_R qbZpk"6$[^1 0#[j5 ] 0Rxo< TV+h |wbbkbϊXGSX- Ҭ:P )J-s0-4T $K,QkVsh |ŊܘnCnDHB?P )J̖̍[+`smP uSڸ[AڰLVo+P-̖[ψ{0͹,S @`_7+H[ޙUh0_yقs\17OQ:Q )j|<[pӜd앢嘒J}^) X$[ri͏DDėR8O9:uVݕh |gK0A߬NbqrtY>@S@QUƜQďb/,+GkK0Slѵb1fpEib/+GWJ4a>TE1K1Ӗ~R4xvS.9,ػh |"ĒٲKQ 6^kl0Vٲb)fpCi-oO+Hל3@@56-WT 4sY/T)`rOkzoA< =pzVR ),-P 4eϬn0My8+E?'*0Iizb%:vJӒ?wE_G.j"0ʐT d0 y,VIQ[b5YT )-R 4d0x<ޓ H|J1f r,-R 4CYG)`TlW wE1f *lxZ1ӎe}iX_7*HTcע vuJӆ(r9Y-/W </ʖ_{*`tOP &fs"m˳j\)f kza<i[xRxF^W=YJ4a>TwSW Y`oW YԉgKp.K_4bl_4NUr~VW+ P'.K`9[1 t[D@sq<#1`.j0blm4oF)1ԋoܬFA)!\IQgc1 0?eblg4dJxQ?CT10Ƨsϊh0'eoT)Lf 9+P7Ų`݊J++W!x-4a>ԏ \4~R@y$ޔBo 2D~RM@ゴP1Y{R@s,?n2*k0[FJh,fnCxg)ȐyUV)PG-;P>uMj^_w)yCV3h|#M'K~F1 eϬmPS)Yܬ %@@=)[$FmPSvIQ7Q +f#0S)& zrK,-NP @#>t;)ԒϤ(D9Jbl @@eF2~iJ5?3U h|+gh >Jt&b(m h|+Oǿe˲@6J5X,㔣T>ħh|/VR @X's*Ԋ btaQ27!RM@eN,;ƲF循P#.g(Q:CJ4a>ԙm٫ 4.7S)6.^O*G8(h|3ץhKg=pk"Cԝ}R1[1 YP)=ޑģ R!.J4a>ԝblyvb kJDR\h|?'V @cX2o(T)Uܮ ,@@$-]Uͺ Jenlލ Sqk*PF:JhOʲgKܣPQJs3|$O&!&xq@M1>R $H3bqrT'(ݬ@@3S7@%4&N $N toE[QaӵJX,[T T/v$كLaGTdW^d5DΎ<}rTf+l昫擬H-H ƥ4K_:O)a>*+$kֱzrjAR_1;)Gl~0PEƊa>Ic 1^-\'Ñ"qb@O$({)0Tr6^( Te|$)GJgOkg#e*lR kI0tX. 5al̾0P%d S?$IpaPy,]e$6l^JblRac$I2 !T}6n;* T%Ua>IWǝ R+vFn-_-նV I0MQKFnSqPT["f)$IR잢Q;zJUX2[}I1 'I|b?+~5lVS @[eƓa>Io%FpYqmZp$Ia>X4{gh|O9j1|R|@Y.[$Ia>xFb8F9j(R|@9)}7ŀ0$I 3{qQ둜7/S @"˳'|$)G\MQ\㳕ق9'ŀ0$I 7ċRuUŒ$ Tclvb@O$)(Rs{iŀ0$I ǿwԞx>0PM.JK*$IR.x4ޜGS4w) TeK|$)GG+m1GAӱH67* Tc%ۢqb@O$])c< Y٨^0PUe=|$)<OQ KxI6T @.fca>I sc4nRFld0P]nEea>I (qr44*lv0$I ђSTJ9~G)a>ʜV I0 qTzϰx dl|0Ped )$IR8!1,WF2~J-|$)Όgdc| }X<;[$Ia>;\x]t{e<$Ia>""xNQl?0P>]Tŀ0$I NjR/m63qJ)a>ٻX@(KmAƵƲ&$q5ovbԸF]KډcDzYQ鰿wv{|?o^3w}{nDEQĖ>ZHՙwHh!POOQEQ2ַ!%pW hoiEQE/k4ĶBI̻'Q4qpYul|(h旭yf[;{)w{{h`ՄEQE/S *4X3,4w|Q4pYeEQE/CKSIR&ms4]! ̧((f~Yc ̻ޏ(, ̧((f~i[Hy˞A |@Lp@3((eֶ{hq_l "7EQE/#WV~=Eef} f> ..4KU50@3((eNU:8,̟N|@\LRG0@3((eQW@n'246" f> >3!j! ̧((f~={-qu0#Ch90@3(( zڿ.!: f> NeDEQEOm8Xh&{ꫯ 4)(XiMR60` Q4qzEEQEOomLK maFƒDf!O 4)(@3múhDg4K4)(8i;I?HیRn;|(h'|n[C5@f<\I|@[@3O^DX7{5]ھZzkn=Zˠ)]}йhh1)uzjin5E|b35 ̛&8d^ڱ_؍(xstfTi38R-Vnm|U|+(L`Vhg%/Hsis;Һ{eE3?ڞP';o`ቀ؎(xZfIwa |iSs;+s9oDydg/}2ڙk񭇜1nksmw%_}9N@3WufICs1mM"bu[CD!_}?)OGLihg%/#%^1_Y=-[p74ulMnYˬ\e{:g8-O>랮f5m仏 "ܺ{NtPַjd'Yf Vi Niia- ~v9964m3>d:"`ֶz(3V-v9)owsլZ#= MUH35ӗY^g>s.֍t6Z:/O?]:G)kT.lĄ?Jge'ŗ4 լm<&ƎymS@nRf c]HyJ)ãOulqk] ћ5KGdlϲذz%R_-|FLZ*p矬~sBq_7<ߵ{e]LeVW~֞+ԬfihW'|3W%/օ :#B4&ҚcGThk|@9mQۦ8Kq=< ƝK^ ѧmځc\ӵfFyuN?:(# G f\} o^Hqls:Bݜ~K3-VHnO!Ժ)6 /sz7/ˬt|F:W[5YǛ \n̊z"[6b }ǫO_t6k3g pC۲5&2XdoͲ~$ Ĭfc9j,2pImWw#W}AլO_Yun(YGvfg|tඟGt.lճz9ΥdE#Xv:ivp@6NSl紾שsL틉j5;F}f5kkz&pӝ[^ g[Ƣ̵rùIFΥnis#x8~ uˇ\ /qu׷Sa|߹֡޺S{~7z.{!p_ϧ2YikD!faZ1jh"Is\Ĵ=wT3;!Wˣ;f6g{ϵG4Gړv(wmaGFLXWνw6ֳ_ȶA}Gb]xc&)~0[zQc5ͣ;:Ǯ1bI=:6ѹg7Y3S?J_[;GRe7ײmeJe`[fMs|F0`]+?^k=iF߯fmkC}%d`?~)!޺V{~g'߽F3 {J2KLdm#@3k-dՄ5pɏ|̓3ܟDSk\shd3|'b n2MMz]6v/_Q:r\lm-öJRϷ\$}EMzMmvMyk#5ײ皻N4F=Ĺl >L 1Yݜ/_b\OP2yiڅݺV{Bf~h, L<1@3w'bi 7928wo\Ͷ }3 +j{[I[¬F[|FzJ8Wέո6=keۮ#ޚ0K9};S5ySӴ{9&M|ճ~\6 i9o4Dp mADNZv;Q4q4{;{[Nחަ86,5t)M(-(}fUehf[5<}/uzm#&d{Bo=Wԥfvf3ײSR6?e:ӦL]۷7S+7EMg{;7i\Fx>͹ ua3>/5ntQK3?oۯv+9nf> Zڽb/Özws\Kws#x5?l\9v̢-{O#&}Q_h_uq.bCxԛejwOa{\>k$uJK#2)ԙi{wyxZǫ}I|s2fvo]L+=?¹Œ;O3?Oy%rv=@31gbً36'cO?{lk\,^MM3nx(,˶RϷ~aS N;zӘTuI5ײ5G{i~ ƛeؾˬ6v[QgZU4ʑ> xfZaέ˸#G4+ƋXX"ǚ{h`{ f;8&c\sL\,^Mngsc9{ѷimXӊ5ײ/eεJhO5Yaοh.{\>_i/u\cp_Bl92ꯙ(L'-gsu_JsBo]L+=ĹlX8f~ξu/fQ<]d@3ۣf"`t3εKpyBdjfD _]4Y;U ۧmͺ +7͢q+ꄬ_YS=geGw-՗?8רMK2n{PgjS,5\m#nB{~8ùq!޺V{d@cgU#Tfis.%%gI f> !,t\n?kW׹ 4o.Yݿ rMmnhNOik}N|^4_SwVm{lo-3.!um<P_{\>~2W:x#/!.ϸPgjn_d5ϙz=R~T37_ 4WOuAa.lbW^ٻ4s25jfAh?hȫAZJt3i/Vs3iIsپiPc&Goeu|syus=|ӟ=fQ9OsBh&:bI=vv)z:94!}=,1sHWƻzC_{\->a˔blmo6 y6gLW~p[}R~T3͇}ѹtPo]؈)=?\o=@{6t(Hxa a-GK{^wss-ptx.95]%fν<[fۘ\_>bI=jcRlLh~TcbC[4@:iM {vs~s骾_B:m8ޏykǬ_@{ߧ{m|W3?w-w_7}MGCua#}3?Ӯ}E n/۵&D@3;E޾DH7<&ߥd9s}=|T=|ϫ%Gz&5ĹmVkۃif4}}|UN%ﱯn$'f7YɸH[u 9iEzØkbӮB9l9iSmo. y_y,_=zwS|mߵzy60z!ߺS{>۹}*t5FQWU}:f~Zt(?xژ(XG6'Z+=8Ovk+|>GGPk.5"uvlZks/уǘo5d+-9G_ݢ:PmUwɹE3gku9b_FLXFb97Yd횥m}~_ݡ5VP̵BsзRO].O~NZkaB^c}Yy!>f}pŖ2ҿuW3[n]؈)=oֱi>zf~lN_ l4؞,@dn~m %h>[kW?z>vinY5o.\>5ikW=WB=~H#&KF˺oF{ԏ7IWe z"ϵ0r*[Y~ Q\gja.߈TgLq..S&}!.t7+ytoG7ig"DZD(c@3 g70ftu ǝfS}6E -QfݜVif k3K߅_Vf|uEf 1aOr{e=Vz>2a3>_<׹Fns|;)7x:5s uʏ*ѿ VWܺS{<}ʯUV6w>(g,"w헂hd=F@D<:.EaOqsn{I/ %b ۼŖz,59)θx]՚ @NvnIQ} uvS۞s8q*2vo篋oٷecسRLH#&sKôsNq\~q^f_~)N6xG}tK[kܞ]L-c;;̛lI3Ԭi:O[6byW>^Gx]vv=|Yx~RDع80$y.! DVMXݬt^$,G ݥB8 ۚu. _/:sѧzYzHIelz[Z]{R*u=5#WQ]L7Q)%i7>ѓ._tƕ s3f˄ m9F]G4!cvh{hgU[@3,[#X4)(=_s)ѮA)ߖaf>w2S3ɏXԦ镧[/u,'?ǡZE1~if> aPػ0@3ʻɏXl1Ӵޤn4rMS#\_NLfLO^{k!F&f=F|@d{hSEQT䴭Kz~6Dhv6=F6fC|@V0@3(RֵN%uLPѓ_=lVC5%#hW3n% f> yZ4,% ̧(uӺWIo5CUikz,ǿD{‚Ek@3Dי^g}KOQEQ)jkORJzhԓ/Ư ]E~7:"p3.# f> ,.! ̧(wl::?-?J}z]Jn淨ƶ](m.$ f> j쏤hSEQw3WY*۝M|@BPY=@OQEQP\n[]4 Eyf̝J|@Ra|hSEQE3?V~G=2%3! f>  4)(_((+̸;(Zfwaf>EQEQ4 2F2׳DD Ō}HK͢fhSEQE3?om_%uy?! f> fY]MOQEQ<>6CfmI|@o}iaf>EQEQ4ִ%}@3d?;hSEQE3?gh]S,Qrϙ&Q4ɶY@OQEQЦ4D;x݌UH;¯RhSEQE3?i[I?0Dxߌl$"7KEQE|f[[jB/8\H|@]h~jaf>EQEQ4XƚƢf HIh~7hSEQE3? Tm5KBXD|@f~Cc4)(QN~!2;'@3tEQE7vKoDfxhoYAOQEQ.z "/@3|7_G|(hgrPge)HX׌'H,." ̧((f~QnY""63ah^ 4)(dV͕@D03.G@3P>SYEOQEQiF+T"@3P3 4)(VsX"R4#h74)(Vs36/! f><,f xaf>EQEQ4/me,1'yQ4*EQE̗$MTh?-bg@3P.檇Y^FOQEQ|ImY!~c@3P6N1u0@3(o]l++eH،jhǻO 4)(*fboǛ%"uԟ@3PNv5=4)(*f2ܶj">D_8݋(rr}|(ʶbE"f@|@9Yf!xaf>EQEQe?϶{|Xݜ(Rg=40@3(lm+,qch](2URz|(ʰmw֓,y/ Rp#-' ̧((̚Ui =w$h8{$!f>EQEQ:pncxtp,f> 34)(*f~[fڎ (7Gv|JOQEQT4_*)yv$ f>,f1x>af>EQEQe@}mKLy0[5A3(2hlCxvGؓ603Qr0@3(7C0Mh!f9Z4)(Jp3VZ/@3PмDOQEQTb4̮|w,C<(rԢM͂0@3(6hO,K1Q4iYvd|(_V Mbi@3PW/$#af>EQEQk/1_Cle_(W2K50@3"sd@QTɛ-:ö{]u_\E|@J(0e3R5RE\FR(Y%jV~cQX6cbh>fQ@|tuF@QTUfEOC̝lFo(_?r" ̧ʤN:JG:@`)A UDc4D{HJQniQ,{g+h2o'hS1l~ݵV 5Hkz:ۯ0[2JPUPNȫYP'KRT,}"Q4r,TD(4muM: B%L% v*P?ѡ&L3Yu3ZsHK̈9Q4lᕄTjz[MjN#U&dsSfAQծKX!!1c@hIեTdj"+TȸWB^ fa:k- U8QT;4jv.3w' f>cy0@3LgO]~uHLK9 O=Yn6.:L2($<,wq=(rYKSuNIZ]t<閴Nq/1F]G N:B# iֳgٛ@3P|H"_Ei寣}4'1ay7E$neQ^;B+cYWQ= oDnS }a$0D"]gSt?}O|э {w-d ā2OG4pY"nC"3ժ_-_h F@{~u -.'-͈(8(ϢvXRe JaD.{DŽ:fc  oiU;kFN3bh`H<0u6=o#80û]i  v֜ Q61/D/,;[Pbդj.+Tu;t=INwĽO'V~w}ɀ2AvqWG4Ѭfx a(ij/4+kmC2'>J~Zhd4BUZD (#ǘf>XjlKl1a(qV@ks^g,k"aF Y5O+Ϲ mI fmcO8]4)fG@{@5A[ښT|GfΚE,Ifc 9@>QV417Vfp/^!z4f)ϕdxSz-c=_߾L([y QqD3P\Ǜz4dU 4&瓞.V^6'au9n0DL}տ|_8lDjDg%vlbf>^8! y:_BZGyo"r94"#rl毪a6n5khp1BH(kwā #b, 9[&Uc`C*5tӯl[`@I'$>V6^݁H;Wl%ՐoXx\W[ ā27̆>D|@ix0`^#kU~*T#nӒή:T૭ Uh6㴥z2PS7; "+|@-c$ YYq<~@rF|\#x3V~*tLbsvGm^{FiUu;igPU 8}jg41D3P|] j6 +{&caŽjWѢc|5S{rF8 +7ir6Ήd701^5x'uN^ Dk[\/6ufU@}?N 4Ί)|@)fC8A[ڱFN;f~Jl&vz",Pg uW1hj;b$I0(y3^5hQF-| Ug9s$a!fis;wяXg% hJcY2N-خV@4%V~*twSf9cQ3̐@[@fn((̒& >V約5XE8)&8T~T=M*GDY<@ +z.graenUJ_۵y5suA ]lhI޷}W;;9m7lHnH f~((͢_Mj2  l mhh$A#4X}k63^f>T>E@Z& AUjdi4G5Μ\Ku b\;{# D|@gPf^mֵTY4RuͣIu.= 1WޞPcٱCHW D|@ $x0Vo SZ]F7M9AbwyLN@rQC3PJs,/# SZT9 Uh5Rt.]TH9KAi1 z@}ng"f>}, +)aլmhR> DDkK(S'BOi1.㯇C@Φ940h YBO@Wz<GA`O>Ѿ'VzPP2ۑEOyv}C@ڟG^i!D3#-QB-OJLbA bG]G=L@^y aqC3^&YD6UUTϰQq:0JlF瓢Fi9cRw8PsYDn@[FU]U&f("Ehm"v0^;8qF د# QqC3~D@;D,+kcճmb`G3bo$vT*P'Ă"xU=88 4s)f>hS& ],VTkwTp<>b$3nEfF8!h4 d摒agPֿ% nh({dJ :'lmAԛ'QY/UD>՚vlȗ@5nf>=}oGJfTmOAZTF-%3E;[zu|tW BquN!̨74L\?z8M+A,}n,hw_@߹7&hB^BqS74kݭ^ja%ăf,B0F:1JnR8̪?74m QE3Wa[5p{[@jeĂ<̷ThK! $'yUK@GioPUc\ߠD8ƌs"Bg6zPŞm64BsY& nh2o'yj,UUw  '3 Hih|Tjf rX!V3@_QqC30ɮA@HƫF=!ŎHF3%ș  Т駏:(h,'$ ` 5Z]U&A2G~#Χ Ko+)mK3' -% V},Ab-'[+ѴT3F!u-q̰(xayYFUj5oS3"ȚΧ=L(hM/~Pw9#QqC3 ;DsjTɄpDic)5"{y:~A3ˆ74pCLM8Ԩeă{3%[:u|ZuW 5P7='J>8"z̴D |@TLQg06MWo Ujlͷ{lO1F)6܇Zݎ9u;Pֺ5#s 8,*7rRK5L O8(sQl;I(e mG|ٗvbf> :^gp| {F 3+.&XYў+A5lWx~s'Enq(,*% Hy<{#nk*xfۇ23[[gH @`Df> JnG}EW ZM8n7c(Q˞fn*g)#?jor%*@D|5 enjZ5pW颥KK4Jx>:k$Gh߾h:%̻h9,+WdL([Ԩ@@\0bkt>6Կ %v@ %f> ZP0P>S^3Hkm3_I6F;5PEwz }L @ 2s/Mr@YYFU9'\q*5jcfDsU.bbI[PRCk$ ^hf?250p<ڪe k63HO}mŚ +fI@DM屄[&US9'F Y VbFh&$ȍ?I@ifD |@\g0`TzɟGKr=w9a$DF8w7!Fm:AA֦W/4Q㽧Y\j9-  /L0|Nn %iVۈhGO4QTc wHf5h@jY'HjP/e'Q7Sj}%l<(xKfbqUYD;ӦF u!ț&io~Q|ܓ(xjWbeUeOZU1j!hZVFWx>a{^ˈ%i}v;͌ܞ(xs=@Gl|Zj9n(͜(ZJD-О}D@ʆ/4o a IkwPxafYDQZ4Zkx>q+5"h~jߣAD @$JB3e;EDHz8M+J #7\T'o_5Y,1O̼G@DٝX xNF0Jq3"2N[:CBaߗz@xD |@-$ D\҆6~oFɧv&&T瓸FrHI# B>Mqv2 VhB\U2^5x',SW3Ceg=gŠфήF=E@Lf> &Lyjm"uv(lΙKѝ4CG51'tf> 3!nj3QDQf^Oej ]uZD,Q{8Fn' rIbf> Ꭓ fFk@D8@ԙy QuY!{nyUku0ss=B3}CBp@XhVf1Q1Z׷dB)Bhz6i)'4qmE()\ Z6ybg >sT|AFUZQf6}8l(8bs;@,Wv*5j q<^Ǡ=;mg݉V7'4n骷zOXCf& 8hV|wH!mC5@nfD |@<,hSCoYI>jY?PJ( FJ @ e,7h:a ߩACm^{vw )ΧpM$-M W/̬=(8a 6^5vDw̌~(թ糿괘X֢m؞_y{QqB3:^ &j5y} QMi?_zP;z8ب53wQqB3?& ejZ9U 癹}4Q FR#4Prvk?{QqB3'%D-Rm.VZH ߒ(AF_>eĒmv903ך{QqB3'?' d3ժ_5p]0;w%K]?QqB3'K45!ը*UTFk\DKFcfvqs'4Rg=|z j5pk q@iM(in6ˉG٣|@LU켞0ІjRxAl̼(ޚ@(mxKڜFKO9<(8c T㯦pq'rDGgj!mN4@Lb0|@̌>>IhQ9- 9RNz5Il:7 1 QqB3?`ѬPosS`F^h=ߓmŚMm.UZD @l}l2_1B3?7J}JxըG$LM`gPzNEf@ @}cg<f> ~؝Z'6O :zp2Dx(OVz]=ie6穄|@,^ǵa7@kߝ- V55f> RDeaUJ_;7 νlLovЎ0%#T/$!8CMcj`{vV:]I+?0ݙΫ~$4DPX|W=.Bk5OPh#s93*IYBkhH׶SUj'~.zP1ojXP>@`MƌehϳUG{ Mʹ/b |#*3~M }/VP>QL4u Wڴ Ξ.ע wu?b@@˙v!EQQ>)FZ=ǙlЙ| ?]qm\\x coS| S=Q1UK{i>ȁ+M7V@Ah盪{ µ\rPS 0ʙ XQab@9L[Г0lyW-)X s oiO^ ( -j**jvIg?ѱZeHL-? +|(o643ʎԨbN1Դ*w`9S盬F?Z~ P\l0ʛYvik0[5P#*`<#D#M;"0V9h㲽~!ϥXZVPhnBR( iQ/﨡S j\jڗR5盭p¸y~-e`n2u}_(gbFBõjx55\1B~ƬP4 PYBa}CMT4#38P6ǝԄBev^DW m4J[h"Esii(hό %T̗ƞ}åp( ̇2 Cמy$nDRk{uOU[A(r_vm?W'7^՗d-:ɞPB!"/-皘n!2L֑dI psabHo | G!B6Sl?D&5?)|j) iZ0B!a~iy[ T8굸hGO\i | G!Be ,'qsWrys [X@!B0Ԭj&n!Qy*jfPm\i n | G!BfwT\˭?Dfթ'_I \v,;b~5+0!B旚ߘ$n!Œ΄;ꝂgXC_b<@9̴W`a>#B!Rg;38@Y۫NB>FxL=ǙX@!B0Ԍ1AEOpgj/{Zv ڴwVa>B!"/5r27P@/MiW}v 7¯0B!a~eg`F'zxC KLp4Va>B!"/=;(ԁ@WfOU{pi | G!BdU?MZ k]{`r#vB/ |B!D_r3QnC@i弗 LaG |B!D_zc2 qoszx=;>:l@B!Kϗ6ʜ?!-jԪ8J{?}'3 >@B!Kru5aC2Wuv ۄ꣑YDyf?LK|#B!(p PyN(Fc dA=Oaa>#B!(p +֧IWV22ȴaa>#B!( +.T$Oot~_5c -ۘ+0!BG6qxPHî+0!BGL\q]8LswP~"hL[q=Va>B!"̏@VIjj06@J0eX@!B0?\h#@E1Ilhٿ7i'Я7I`ڈ0B!a~4+6 k}ymwUc8ƴgaa>#B!hA| fkS)}5o$]@!B0?S ,>| !`msQZWV30 a(Va>B!"̏ k:P,~6Pn۬:].@B!>&N=tg8LF_LVa>B!"̏ X:Pʯ Yk'ί0LӺB!"̏ / 26W 8ɢ1fi | G!BQa /e-wГycwp;ea>B!"̏ b4(S$9@=})!o٧0B!a~dD 17eJaMZ k-&- Sa>B!"̏ Ldq (Cƨݯ i T8b׶ | G!BQfXH7ʎ7<_?z b | G!BQXT (+>T[Ԩ:*NAq՞uj߲m-TZU9uC4i:P6|ulsHj;Eb?@079'~h_ruASbPy\++/JFBEE]S(1A\[@0KۖgOT-U+ګX0_txos5yξ*sы_~$Oj' .+KF*'*UJ='/ :P6ٮHsjn 5r!*M]0s8&t~}k|&bΉADv)Q/G +j-UL ~նJ0~P3_wM~֚FݡeF*Oa89o]N Uj[?\@PVgf/hϸ&?0֯߮jۀZsn4G^ڳ=\%X,PB|[\W1%&Un򨓳Z{BXg4,ڨҴPU8=NWעEAZb?ajt ,u]K5B={ki]+_X@ :~[)]ÞEĤW򢒫9=JLW͍w+?UbٙQZtZ8D_:D:f\$i. @ű |ovFzsngEk}=nSABKLz]uup^G;WL =쯐S!01KEXʾECL]LyKW"LN<7gF<Av_0_.pFFc=I9 !%&j=W뿾:`{Sb¾jn9]46”}]J ema]K| Rq*֕. |o_MۛX9c$DĤ`{XtKֲV1%&F셜VkQia~N]r],v3z_;9[ .@p׿ |o?sWoԬf]_W\ylЦ9:MiSc4Ҏ]s;@GoVjaIi#t'IgQ'Ig@cLT5@[P;3Lq=f=1Yn tI~WKL,̫wazJ0MK_X?W~{Q=gV=Ѷ:@}Ⱦ} m\хhmmCtKi9[unj|jju-h g:5ْj5Dӵ)I'iomt;=0?gd5@4SUӨȜUsW\, ,S |k'լ봲/ [U7LzCVpg 1j߶-ћ)Qgnv)y4Ӆ/1A\1uCIsʟF8o<)1]Z},NJ:fB u)kVskLڨ`l)ggҾ ҷa/h{g~U2DGGN՞FKP\dW |i4Y'(Gog"{Kՠv#l0[uV)FsޔrC#^zªW^ u@{_8Z2]13Q~W,Wa:>i$bMAJL׃i;4WNjqun#N5-:U%&5 3մ j{j_?p %tknl\篲ɯmԐ@?lj !^1sO$MDT)& ZM޻޹7mႝ6CX:S̬x) `5ȩ}Y7/Z=DgG\iC\@2V;!a~ CYWROG):eN!fYkߤxެ|Gf}G:i0~ͳzV/Gr\Y)f}q=f{bI;=Ũ g;ި>ΈCn%nrk;j>W5W 9 H]|<9] u-h gHh?g+-B{-m.PBw@TNkۚ --^z.".&@08jNeIlBm:wfgir9$߲0]6Z%iq>[0oh:MÞqgIm?j v¯OPӚ0rck,99R'mi9 ևYyY6*cP| =jc.΍u/~p_sg{|[SԵ-\s[}~jS}7!˷ ـDgrOeu}@ .(@Y}!a?|L^?p1͖;r6?z~l{tqw Dz?Ŧe1.Ũ4AoE֬6GsAQbq=?e=SMܧfme43 ¯PӶpFlqcy} iYX6*Y͚^TB1{qLJY}[\{W|[SԵ-\sϷfw_Ե`F3o b-} @A[{V)ǥd߷R .+@0 ~~acl7g͒^,>MG[/yzi7jcz˱e=sV\O8ԇؑIs5uqFJtn/1Ykg˝Sns3}mӌA&z8iڋQ{>Me)1{w56],zIu'~=mႝ8CAZkkqiһ-!L TO]=ws:j2..@Y!aN7cO'g@g^,X{Yq^nG-CLG-0y Ąz~wOgJɞxgm8u'OQ_7vj4[yBisnμmj!JFezIxe=zc ?|꼺=Io>pkju-h ܇8[ܞ u-5)M|)y%@Y=KbO-v߾(~Op0389zη[Mqf/*8O2NfћO.[ s߲ jFi3ꪎ+߅\br=Oe⿫k3 gap7ߧ@%&kv=Ai49m=5|JFB35Qkwsg)}nsn)fj|kju-h 窇^)x>JA#2>qpiO@IY<)}Ug34a~2DomfC3<\ެ} @߳G1;MđY|>[ݝmf{}߿C-1ṞIJ+F7-W:ޔF B/1_k39FInF}G'kout^b:(Q;T!f]Z1{s16 }< qxgƄZSԵ-\sy}nԵ`pg>t4[W'򩦫ٶ0m}01K3vs۞=&w:[\ŧΚw(5ǧ eA%&,GWuŏn8{TxƐKLk~=)vYΡO0ɧq[ۨ쏝jS赏;we]mܑۦy"ՒƃNkA[`?ݾRׂ]uw?qFOv;@z>˾U**l]0#)uf˽2NUpY|>S爲v`^1s%mZ:r wRT|vR9ڧg_}|%&5 lF8:͚Q;T!L1{t;6'8\Yg3L{q55H] ;-3˟B~>!Jh1>=?tXqn':NC ̇/ffx|f~~KfܜI/DAS}ں3k\br=?j|KaY1^6٩N30Eř9_Ϸ̺m:}QӜJ;eҤYZQ;d9Pڃ1{ܒR%ezg!R-7N{st55H] ;=3>uk!?H] rݧX:{( Wgs==Ʌ$?0 >>Ozߒs3,1yݐnl3`!%?g9y |w҆mΫ\br=}"=1E@s{a~]C-1AY|S{״r~ƫ| NK\v+mT.N598;7)yb`g!6Jw_rMͿm႞ 뾔f0ޟ] vv^[#3 -ZT_I|.?@hVC ē;Ч9a/;[Ӥ-;ŧ?u f 6m=ϑLf'矋-y⨌<jM Rׂp>lqlRׂ]s5SZ?6gb/ @&\vE^)b={0A_n>Og؟iw}O=RI&b̌hmmO}࿫BiJb{+o^I?Pߣ>f g:TOg7< qUio&ZSԵ`-\sW[JWNcAkA>u'@yA=DШ,>MGoζ'>|l{W^][ /NO̳b9!p\Ϸ׫mǧ99eZmM>%&5 CM;[כ1wr}e-W7gC#FBgA9`G}չm0eiaڛCAZ.Ն}?H r͛ߙ?o^[WX (*ou1-jt.RfR"I ̇5na+:8㱻Ĵۮl{h_j|er[Sy31]7>߰jg o}z |oCo374"'\$,z}5RgۍcZ]Fε ;PS)v>Snr9v,GTӃ1Wpms.= > jԱkZ~owV|#?H r͛c]B.k}zd9](^lЉj٧oVJjD֩0M_G'{u{.ezgg}< >Z-ϒמywm]{^Osn5a޾nW:Hʃp\ǷImLm'l3GJnj z¯]si/xΫ{Yiä_6*c7Y8Ga|Rs= ژ4IO ˦\rMͿm႞{?6==Yl?7k:P5꤄oڧ2ݳ|3@qRKc8O @9ȫ0\ӡ;VMzTkԤ ٴjϤsz{=:wI8>h׭0m_gd c= ,'xK]ty/|LsOĄz>=4<u6_yfl|=j vJ]gi+;FDnx#Z7mTYd#^1s&^=|I BZ6uYS|-\spń2ғSFwK$G+1y ٞj*ٲ} FEaF:=Uyr8!Va;EI{oٚFu1hK#ܲnpЏެ=s]MzL[2^s'>Oso愺iJLx[}#^k?+#Ӝɾ#GZb]Rf>n."F[0Gz{)06wYOvp2Ěmႝþg6&A+&quoFҍt xQ?܍uZ~ja?ca>+?۹%k+?2l'iicˎzL7v!uffpx=*_Q0SUWͺ:%AJLqU[C9Ɵf+Si }{d}ԮgҷQa{Sºy|}/ess|3Rmʉ~af9Ψ`+Ys5|ޒsz}|}Sl^H?89kѬ\4#̰gjLy>N !p]շ\ӫ =;ŢI[5WKLkVzެfګ^:z'#Vؔ of#c8%{fIsz"IIOjM ɃpAݝWkZ?h zWߏ!=tT8so7y|֧%XڻaaF8 :^?a @ aa>)̟,y zg^BtK_܁>74 ;p3I{?+W<_Փ&8Rw0n~Ui 3wo> 7io (ՇY=a~HsW:P .42v qZ-:z%Va>yk#C!T9Ot]+iZY'#5E _<˴ƎQ:]fa @xԮr@"%!*6rWRRЗ;A#oB>r{)pm{t?vd\YV,Z>R-P`65nM |M=筗!PA&i p6,K'狼4MJkc}C_f;r; 057Va>B!P67CIOUkmɡ9a9t TWg+Kՠb2`@!BY'/8zǍsL-ySm;d]բuFL(?30!B(Npb &率4 Ǎs/mn'R x^F_a @lk@!B'ҷEM񫴖3a~-~eLYPk\[Ǧ`a>#Be?7;Jc5VQgJݫ74Njw_o‹@(|um$cH(|873*m@B!xbm Ćh>H&ܙ)!ԭX@!B0?|h'3dQjƐj{iB a>B!"̏21@,i`cHA[ML> | G!BdC_MD56`o0`Qxjj6fS | G!B&O C  S'F` @ 0 |B!DM~o@ޜgc^z;+̙pgw})bH |B!DMM|16PQ4A=q~ձ5@kjX@!B0?cZD6L|Zg|~=)y:ܕX@!B0?h]ӃQװ1@ 2@B!Z& \yTL?d,T:yblȉL9+0!BGLq]ȉ1z(1߭`h]T |B!DU7Ư@n68;"(T%05 |B!DUa@|lh|&vDf٩J [*Sk | G!BQe 0:i)Ȋ/k9Dqpg)i05f |B!DUo"ȂZǖC (,WVy֧v | G!Bѥu'@Ffjc著0$LS<~?5b @5ue0Va>B!"̏.{:yǰ/ig=)㦞@B!˙&8evf!gwpHSKba>#B!alGPx=M2Ku[1)LH5SCzba>#B!0rm9MZ k-8ojG; |B!D]ڈRӢax );^=q~7k1Xcfa>B!"̏.+ilUbGvk`g9@!B0?d"@ !WG~)kZԨi]= |B!D] :`yMlɸ;*u篡FLԄba>#B!܆/Ax19QANClԂG0B!a~ڄAIZ'C*Ξ8[ leX@!B0?lֲaZ!$@Oc Ď]L;Va>B!"̏2Wco:vfvLh'ЯdLX)7`a>#B!(]vvЏk{^j2lp) | G!BQf E5 T m93g|zS &oJp |B!De1^c{N@S1$Qx*j@ 8ݔ0B!a~wE-4Fkk -sTjOG#Ղ-P\`X@!B0?ibE)-:Ն 9c3nS?S@B!ͥ&8.BLm{-UzxcAd`1%+0!BGM.]Xrl%:(L S | G!B}cTk>WװT$w=Va>B!"̏6? 2IV>$^#B! 2+P{6`D1Ea>B!"/1A$ĈƳ`d;;PMpE^ +0!BGLq8z`\쀌:;!WB Va ToR +0!BG;M1NBLH+P$`d{uZC| Gl<@8Y2LR?jȁjPl2K[ba>#P9q= !S 2>PLѺ6ڗo gֶ %Wg2/ӭyfCkBViw\@L# =.n݄ g6N!/{QB PhK7@#PSu"> b8 r | `ț%jPwOAu8erN |P1u/uR'H!r 2P,?$cdt篧'1ʒ. @ Oxh+ Z]=Ӆh)m!r&݂nBŲLG;L&!Ѥu@eH/aa># :JhmU?VgJu(Ս a~Ly|t2: INwLj@h,P:zȻ^ʊL| Ge x CC6Z꫞ۧR'yt:K@@ǔt|{{iv@|}zSؔQX@:Y@5XVUw+Oyjw4E,"̏+&: w}PZԨTN@`F@)'b@!(>7B040pv0ĒB}7qZ!P"K/@7H2Rbglݷ0 lNNJ{c Ҥ;}))3e@2P3a STS \gWfg="իF@)c@!(߫C(86p ĐoynK5cD4ĹBc D;M+ a>@i86`', t;31lYuEZsYZL+ a>@i ȯbQm:Ďj^ƌ2e-@䘫:U{93Yj C m]@IyєU zB唴~Au*:(CZtfs^u@x۔.XeJ"5jcUw1/ToZǻ.@|ep]X3Eνj(I]a@t!(63TU]c_ȼ0 f@1:uܪW寤700t.;čOwQVizlQeBի[̲%p:fD|bHXoQ fYx.6Ċ%`Č2SD܎Pv|; @QY`KφD|³X]RD4߶Dl,d366wbE:oʔ& M3Sh? BPXA}pGcY;, YU?oLx]ee| Ϙ$Po5)n<} Ǘt-WhU 6e7g{X+.bx_+쳱ʞɹ݊hj=х00ZEA <0mfcfA ,XqiNJ2`VصZ!PQxEUK+ a>@,WvN1C6}.Mahx ]1=-ֶֿR as7Wj((VD|0Q#5WFo}Vmi!r.N73"L {' mܑO0 ݁х0 ,^}|pی;e||`jYs{.@XϴaEA[o#@D '::=M9+ a>@a5gy> )<> ̋9D %O,",Ю[z3XWsW_c SƮ BQ3eB mƜk|cF̂sAaX#.4XQ@bT8MZǹ37s?dD|YFHw19i 1O=L 2m&XI)5@ XzuܙvUc ɦt]х0 ?fjT y*"0!(?3 6afѢ*, 1buN1m6%t.zϯN U{µy4b<;̈z; f*j@H 7+ a>@nPՠ|_\$v#O =rQ`Đf9j2l)Sc@t!Ȗ%ݝbojW#Ć}L%VDovz; l1s)Oc@t!ȆyjZ`j!֔%yE %4bBj6aEGvrgx\ZsZa-Ҵ VD|L|4{z&cMrda٘n5-VXZ!m@kz-Ț )@d!XXըnF- a~o-FpiwĊҬ-7C2v\5L,ΖyUQ%͐"!p]1~Oh/ 'YIlhQVܹVV3`-5S1 @A ZvR> eیh%]5T' ןtFQ^ѻ ~83Jbo!Y3Wu]Jjrl ,% *_&N]}qj(# 5Y#uRl^bJX{Z|+LCZ}!9sz:CL 3}*OjQiՆjB9"V nӢ_{U)R5%J?l40eu*7I;%M$mJhhsKw oFL6iQ0`VuP5`Ĕ4|+ERϊ6/c@@^ >H<Q0T{7mUāT ]X>0/X7Œqպ;BcpCiL+ a>T65F5I3oF-H\ebbFx\U!ӤNF1T&3TU|A~w c'I+@{<(oo; cL#VD|<&>ߪeJo$|/Cwng`@AiQ3J N'lCXըo k +.2+@(0+ $nG0@:tB H`K̩34*P),(mjruX>V{kb (c{McJؔc 7PSbVrSBocFM"T EuP~Ėr) BΗS7_k @ݔ]$Io@Ӷ1LQӛZS0%dXUyWv\6P` ݦm I:|3 NJP{ػ 5CJhdW %T1%OI@D!dsG`SbH`ŊD5>nP[uQaK̸>'0ʏ5R}1~j@`Hҿq$3 (91+ϻ@D!bPT $k'\/IP&ϘVD|(0uh AgJVIU0#T.s]5;"S;O4&jbB@ƨFU k5j)@|nI:?o܎z ;"{z Sbxsc@T!H4 {[xVpfNJиh ,WVv`T8^ф0L j5k@4ے=f$vhGU'vDuF`K3^빘M |:u^ [> $n6ÊPNz("k4Vnx +{Cʌ&j T m8 p{Ъ^d"3ДG {"=1LYP,P:zzl]URQmz+ a>qU{_JV ލ`8ێ&|}ޞ CʖϵsW1\G @)Y&߭`B mF-(rw= Д[c*,KbYPШg{5on(> qK<{H\A` \+cV1i =z4 &\M)"V%|pykbһ;␶)Q8'Ye=i H0Ċؤ;&MCg[4-+G߽B cv{6qfi)4I ӚXKA1wh|@ ӱ2~A` ]ck(#EE?US@pSR85 ycq~LSbvx[Z׌2$ﺘcyBӐsPM@MуwV~1hmqfA/z4I ErƢi<=DS֭bևZk s2xƌ}nU2r*z$gMc2n4$\=HSZrѕOŒ֍?9 ~3ess&a>0bu</cuLkwrq)h͋[ cikK!/7DlԶ Gwjo=$vCLΥigiF)YpnO28`~pF-њ$tYLV G+!g4mk4<΋-[#4dj:b]XWj) M|.T]qqa|8{Y'h9]۷V/qE5bQlN$Ԣ) M|rbʽAgg&6Ss}W_>J[7)?(IfSk H0m1{b%,iQ4-懫5,lғ6 Dĝ1[7qfI֙E?m) M| Dl[ 7KMu6dwuQ-l}uߘ(IO( $W26- ȻuF_1hb~xlk}!PsqYV$Vݪ1 E|wkbY,.*=cY} xzk|@\AnؤmiL!Wr.oUR$zۨ|큱G#gO5m&8k`VK_(I7h H01;bby\0f6/35m>[;k孵 cuT4J2jR$X(őqa m_i1?,3:g9.G> (Ŗ1wiDR$6dž 5 cy~m/iw4еcf1Qu#"a>0u:pScŹiM 3j‰z2SmNsI, Xh H0X{b%]3#!OĒ*? qum{{TLkFY4HT]֭-bo'O*IM o-fh8FL`<(~Q_SR$f:/V& 8.4dř.M S o߭9ytiUy\ "a>t|#XS㐕Êm.f [آZ||NtFiӋ8^S@@Dm1poh2re?mZ+Ɨ5Pձˌ=F ?qqRq, 4 z]q\SPrb8%6mܸI薘vDܮY%EۿES@_جoq!co+4%OVOvC *2cOիv !_kؠq!s*gk J^__vcMN`@NcyI $̇ģJ180V)DdqMlu}m㧚 )Gk Jִ_xjk{\d?(w\"a>82,+|:)(UfG~Wu:\C~1厃7d{M@,m[c*,KbYP,5.fdtoo#79Fy۾vijr»h H011bQl+?|Mk JNhNs M2c[|Eۭ xIMJAc"4&aUs䛭oOS;Uo@V3RLS@0~IJ_(ž1wh+J@eY"英aqu>c"6j#&vRuD0FnxH)_O՚zpcqS**fH@B~O;~QjrqUoa>b2[ 7 M=ZlĿqUGv 11glϚj a\+ ^qd\i`A6-i *O1Kbuk]j$XKv[Ť[V{^1 =|ukؠmq-)8%;wke"O5J_[Sƀatխy1q/;ӗ56c{.]$Θ{7qffEK) =|M7d+n\_KP-AU_߶V_2jVb}bJ,v> (.S&bRX4Pb]뻊ã>k0ku702{%?1~Q!a|-XZ w:M5:Ntb-qPkǘ~_qI^yӘ4K|N&̇Q.*w/N5{fq)EbޥwגX40/)<:uJ^nNձӌF)C„?Dl[ 76X0@_\kot,xekWc ذmW1wi.R{4G)UMKAc"40P-Wj :2fl1mkt10΋f35JW-ZhMCIJX\ Uq[hXSq1SpLݭFU 阚9ű<,zY^oa>83cuLkco4=)>6GsrOH6St.0Vmguk:\S@zcU=bX?40D+j :bxYКxȌ=^) =|HoceܣoGƥ'bW|uˠE^ ;c2hi/qf1E+=OS@z<6,dܬih}ItbuďmJrnc}Xl-tMC8O44 ŕbI4GUkZ d˱댝XT|u;" 9|hҍ*XIJ8S@k}cUlZ.u:-1v{EiLme(f?MCS.غo+M[S\[j f[1W|rDcN dW;=vSS@zЄbElR wZMI8Xٳ#{VO[u'ձqF)|h4G5߈e?<=$y3sG_otu&beiw*w7EDwZS@z0f:v_ˮ BLFu:'dž 1G`=SS0s&qWx`N7DlԶ,&Z`ȿdbu֘=K1X?40V\jbR۱Ik.Х阊{s&?4?WDlS 7SsY=-ޓ{QlZe=6V vQ,b2ep܏*NHa1^UyG&NLhlQV{Bta>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC 6a>dH;a0JiC ,8/q~> `J7`pTFe_ })쫏.6X|g&r҄;0!\+|0.mc{xwt6>ڠ `J7`pTF%_?/ xq~d{=ǑqB|!~SngyZNa::8ћ҄>˜ǡOl{2?3>Q%w#7¥-0WD_mUOWOYߕCOӪl`G,ϕ&?̷A BӟXF׊09 3 =!^C{0xK8&&bU|).#orJ[ {㞳'asqg'Tor_THzr5adTbqico㰎8:NS;q\i|k: s]c}lG>7o]uC<~5~W"vuΡׄƓǓ;;>o Б7G)\iùքi>Þa|XN׊09oC㑱},E[nk|IݎwYbNo 02ך҆ };ìw@qjqlk'Xƪq{ `}qFdGLǹ1_4rzc]^_g-d^OO""Ւ-9=l;҆ S }_S8?>&}X09N08#lN_㪭0xq}l_vWū:{q>K{[.sZך0_o?I>jJ0}wg0߉4ta\uO""Tz ⫭Ǿ}>Kȫ 8?FGA5~ ;뽲_dO;i_QYjXf3Ra]D|( C98/?W@|"NvLR{Mo6)3/% f~}vW$N\WK u{ޏ|bң4 4~ [b+9(}s0'IA8䣯k~2'GsQVqa5~}a s'>׽ ow}nYx;9c]IGNmc|WSe/:߿~/.yR >˯r'i''^3~bьP5'xElWZ>6-n'y {|G|mNOņmV;X"wx0}tvrMㆽKcg07wjzx\;u^yO:^="gspzgZa$~y՜3f}w5'wcpӬV_{_kw-Tzt+Y+m5ac0X ;̯g>=4y@|qWR-ǰ9br\6X"̷~a~}=#~qi5Kag0u٭}o]v`N8rW޸-H+z-0xxrs^WsqㆮRoW5'ŽɷtOn_#oң&5 ԳkM0> ءUoCWJ:mH_+|;qccx>.\fvrpco8G?zzt~RTA'7+ۨtûku{?ߑϽ9ؒ83Y05a#>X >?QەlzN?sDogVc#|(eT?}\;&q:_'!xV+KK?E0NuDͯx@,> IsW}( z-0xx=㳞Sz+;6:ǫkO^YGڡ%F^#}8gcKQ86ך0a}ҷĎqTOvq}a~?sDpqL=fjlaŸ͎͌5kn'>7aⱫcʿT0o[cWcl=W^[U.6Eqœ㍥!,z,^;7=ɝ\8#Km^Pyӟ+&y݀OU}Z}CORORzw|KzrO ql/sk79Ѿ=/gvGhlyB)/GZ>3]+ZgW̨>JO^sn?\ztXU"w|^S59W{_+|;q;;aFbvojw7\&wrMpcW僧+O)={}|"~Suaz>ˡr'',[;>뭥g=Ԯq^;$}+{Z{~EDciy }Fa:WsF_g|/*c+.oJc8t63?M1wsYҵ"̷~E1A#0w?75׻N}\;&qFW?j=U jZ}CORO}`DKjBj9Sǥg|EソȻ*_,pT5a~aOaܣt ]yF>U[{ŵ#;O1̷z=xx_>n;&q ?3и|U'D^DM?^r5a${[gw:y}•]zソȻ]޽,k?ך0aOnaܣKcKxn;&q t޻ig2S5)뵺,^OO.;>Mg=˅z}vdJ|k^{~ϋb wx7{&|oW?(?Xy+O˺G74G :̷z+xۈx_>n;&q -=c]]A]}H}}aR)jzvGvmңwz GZ>̷+̟oާ@=[Yz8u`sĠ|1aa͍ㆽkrN}.0W1ו/"PO1 kuY&7;?zAY/r!y^>{n˰^#_;϶xhO,k?ך0aO^a|Jz_/=~sUF8G :̷z0I^Gzq5kv'>UVؕoPO?uZ}C ''\zw|VëBy^>{a˰wy_ٝ\8#\VaK?Ʈܷ쳆z"Pz(\g90xxY. [HgG {em^#xe': &|o#oWU37}/6.=nsĠ|1aaa`͍ㆽkrN}.0޺ҳ4鳥gotGZ}6&7zfv721]UyOsM ~GǍm-{~|m-JxW@a(>)X 9"^ .=q˼ia՘0|pj;f͍ㆽkrN}.0ZcWvC=~Y5v"]/+ xxY\pgk|Igf/#ʮ~0g)X Rz3 .]Jm ia՘0|pz;&zMuㆽkrN}.0?\cW]z 'burYK[%ku0xۈyPz;k[HgŒo7mk{/?Sy&: z, No⊵0wƅT}V>\y㑱ü}k{-oKcMcll\,{?z00a(X #W9'nlkJ叢/o2?;&qM:_[LJw.έ ?=VU>{xtm]ycwqg0yL/KY8"~.oKxwy>G~lOt|~ʽ"S⌸*"nG[c^#_۱w$4 {, Lo3 +F5z`|%k+hi90j,BoG1.;&~G&q5kvg0mT}cY'aDtm-ٹ}nq?}lǟ96^ƣׄƓT̅(qMhڡ+?|]7\|ソ{]&4 {, To0Q97iNsU=$y;̷KwQώ0}\;&q2;IoNC#S;zӧvS~lG &̷KqQώ0}\;&qM6:61^%w幱٬q<`g8Ԋ9[Nq۬?{ep2^ƣׄƓT̅(Wǃ}׏ld} ~Ǔz[Ɨ\kw,ޱFk"̷ka~yqyz7-ylsDaX30ߎbv͌ͅR5kf'>7asؾcS<ѕ_#_qP"*wٚYۣ#"b9~q?}ROBgk|Igf#㈸i@KƓ}*w)t_#ncy/*krJY@?0g4VM򾣝>G[=C(iGd}\;&qM2Lj]<}l֟+{Yq_C\& Y qLlzm'n-ƥׄƓTGp )fK~Ɠncgm5u-Y?z7/7s)Mu]i3 [Ɗ0?b:>sSD;G>G[=C(iGl}TvrMㆿKag0e;/vR#'q]# 8$)xw{;;3Caxe__vƛ88Lg0xꙹqBe_ϊWŇϦxw,~O<97*_4 4FR ga~=aϸXq~?^O☌ MOVci|x(MMV's0Oa>Ý|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% !C| U|H02$ȝ0_P% 6&RIA#oRj aK2ׯJ)Ƨ+F RJ RJuS+R[|| RJ RJ)aRJ/WJ)a0_)|RJ+ RJRJ J J)%+0_RJ R|R|aRJ J)0_)%WJ)%WJ)%WJ)2 OR#]JfiRK>^VJ#7ޟהRJeTI@8a>$N@8a>$N@8a>$η&2XIENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/docs/_static/custom.css0000644000175100001710000000267500000000000017007 0ustar00runnerdocker/* * Copyright 2009-2021 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/. */ pre { padding: 10px; } @media only screen and (max-width: 875px) { p { width: calc(100% - 20px); } pre { max-width: calc(100% - 40px); } /* Make code text in side nav lighter on narrow viewports where it's moved to the footer. */ .sphinxsidebarwrapper code { color: #ccc !important; background: inherit !important; } } h1, h2, h3, h4, h5 { font-weight: bold !important; } ul, ol { margin: 10px 20px; } ul li { margin-bottom: 8px; } /* Float the logo to the right of the status badges on the homepage */ #bidict img[alt="bidict logo"] { float: right; } /* Can remove this when https://github.com/bitprophet/alabaster/pull/125 lands */ a.image-reference:hover { border-bottom: 0 !important; } div.sphinxsidebar img[alt="Donate"] { display: none; } /* override the inherited "max-width: 100%" in case a badge fails to load */ #status img { max-width: inherit; } .sphinxsidebar h3 { margin-top: 20px !important; } #codefund { margin-top: 50px; } #cf, .cf-wrapper { margin: 0 !important; } #cf * { border: none !important; } /* Make cf text lighter on narrow viewports where it's moved to the footer. */ @media only screen and (max-width: 34em) { #cf * { color: #aaa !important; } } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/docs/_static/custom.js0000644000175100001710000000257600000000000016633 0ustar00runnerdocker'use strict'; document.addEventListener('DOMContentLoaded', function () { var sidebar = document.getElementsByClassName('sphinxsidebarwrapper')[0]; (function tweakSidebar() { try { var donateH3 = sidebar.querySelector('.donation'); donateH3.textContent = 'Sponsor'; var tideliftP = donateH3.nextElementSibling.nextElementSibling; var sponsorA = document.createElement('a'); sponsorA.href = 'https://github.com/sponsors/jab'; sponsorA.textContent = 'Sponsor bidict on GitHub.' sidebar.insertBefore(sponsorA, tideliftP); var tideliftH3 = document.createElement('h3'); tideliftH3.textContent = 'Enterprise'; tideliftP.innerHTML = 'Enterprise support for bidict is available via Tidelift.'; sidebar.insertBefore(tideliftH3, tideliftP); } catch (e) {} })(); function addDiv(propName, propVal) { var div = document.createElement('div'); div.style.marginTop = '20px'; div[propName] = propVal; sidebar.append(div); } function addScript(src) { var script = document.createElement('script'); script.src = src; script.async = true; document.body.append(script); } addDiv('className', 'rc-scout'); addScript('https://www.recurse-scout.com/loader.js?t=c17a917136a40c38f5ce6b80adbbfd19'); }); ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/docs/_static/favicon.ico0000644000175100001710000002463600000000000017105 0ustar00runnerdocker )PNG  IHDR{`)OIDATxw|Ew\r$DTTPT lWEPE>*v `ÂX@A(Mz~redww\.߼gg>mfA"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$Mt+& qjw¨qn`v R=!~b6'R)Mhzs WQ /we/ùvnQB'9lV]#_,Fzf3dGg?mr48I2> E;MH7+)R&N}pHZRNK Gr&c^1c/!Is3?Qv34bP !pߘqԪ]W7\CrbBwL #lw.+eSîZ`JJ+]ߚٻ ݞ[NJI)986+38^NJa< #'4q]WCT \+>J,!m F @d4W⿜ҢJ 9-c:'tAv$1ӏi8m8=ZMmkoct+Cvy'_3o!j b>6u2>~ 홗#C"fk>kBc6~cR"WKf$ K_a ߍ|&p>c sZI G2i$D,IP )lTSԋd0[9[uXYLQB;\eӃ[Fp]wl. I!vt+dA:X0aˆQ nܸpJP!r8!ʩ82"1e~9_q5I-v=37+(\h9A3Зo+jA˒Dw.A>7QqY7QMYlXxķvl>u}!_sCyO [X)\b;nq<Z4Vq7SuOXݢC9<@[c(=d+t)dZ<ȧr*bÆ .\#&̘`!DH& hK: ē Ua`bcƳ[gߟa_#CA572FŤtI2gXN[^~&2D20Mt4.e4[PPV&z,;59]h-Gw>T:x.be\1$j `O)f1KFRDۨЃlJ"Dsx=;x^-mٯ&%d3C#hD 2\^3iB[a.q-r f3]bq) FfI/[Nbxb+DЙ1,$) { w^_-y1 !kj$g-FL/95s0U(my7ptmFR" f%{[) h/FSIR Lf.5([4~5+HFFx:M; uи*v(_dfenDLHg̷ё{9+#2 /b.׆#}lg#kY~0k_KH7P#g,b8H@ \ȊF:w"2 ub8A$r K4SR.387D2l:-e~9f& 5 {CI!5}ewDl!HA=y yLoK$U)7F{FBcbWdj|.c С"yΙ(kZe&d9D=8fmi*\([3E- ilb rFD (&糀Ku~T7T?qy0'x~ 2.(@7&Ac>'OM/ [}WDŽXD3T}νa-?経MA0ǯAw٨kKFVf9E~]l|3/렊"R3.R9^a;_| ìݸQpyMN{ 'TKhߊ"=oX43k@- ۜ$W1\|r U;w=^Q=&.Y̗((x=572qM^LO#l]-exb:*Mک#RDGm8tOל8gpjQx~ƅYz]"_bN*UXA \)Oj2YBWANf2-1rYr^rN:nHmb^!8zd~c/E^3N@wvўSSo}F99D &l䳖Oxq<{'7$M!41tF!3y}kzj]tfHjJNo1UMXQX>&,'j?F ϣW Y'Y5Ȕ-75;FܽМ0ۏ66Z, 9f_ۘ@:Ё~7Y/(CYN?|:AB0ج X֬5?jFTZ=>nr6:A]H6~Q9jFH0} ЖPPX^sBs59';y<" ;Xýa: 2oexP^vCt N?{a~/md_謰=Q/4:IRYd2kmoPOcC7z"J[~_d?a_3Xw_Qa|c'^;s[يk?UiDҦb`=)mmRO*QPXgձwPǰ৊qW( x{ySXe!]6& f GiN={7W'¾Ub_O5bnPKAydoiFr>qad& %j]݆2'|ߌ ~O@Oi&`GP/@"sb0s+1/I>K0TJ4:\ @gf1FVKҀAǼ3}%v5t>S]|. jZx1ўK .!7y,{S6>1媟wٙ+rHǚOe8+tWy-৊VswolFғ`gfrJ/@Wm7 <IG>fUoqJli$~~R ϑ8z2%JoÉ|߸3je:?vZpOoco4j(=5^ldjd27'|^g_rCÐL} uR!y)"ϝJWTf1t#>7i7*Z0뜿ii:N>D:w+5VN c><xTd+#H&i4PoJ5N MߢI ډ~k:ikA3F8Qp1^TcW% ˆl(6~JCofUq?(vf8+3FitJJf.$K jQ퉍x#㏀L#oOT\dâyǝ-(Lܡyd 4.F0fb((l+PP!v(ѼNYrҁ׹YGקW y>C6* on͔T͸%rϲݥvxb/™TZu y&ྼu,gNӘ~`(Gsɧ#wD0 Y2`f>-=Yu a U()-4hfײ&xc7o\Y.ӑh -g6h{6{*nfAB7"ŃG&#p\C52gG,I |UpS5nV1gU7M%o]|DK $Ha> \'O\yTm5'?q]/@U',zd r 2C^`1K IsU8GRy j!O<҆tRI"8,1 #W;p`N V(/xF ԅq`3PLp^u Zj Ou0[u zOXuc;Ct5䫀@mk|&,I&t$t,Da"*$K 'N)B)J! K1Gf'1$ŕaVB/S1ГWS|K-?sNS5a!ol@m*|qt\F߳jٴiŅrvLzNmPͨfN/u&v`΅5yΑ61ɕ:Ac~ { zďL`Q@wHnoF33 x8EةjQxY(Ċ?Fc&G'0K,b` BoFs-m'F_*Ь_H;[/2A:y"C8߸4,f=#Cȿu_17ZSf2hMtdЃY:wR+r( *Rn̨2` O"BmȢ-I'xMNS,Q66R~!s09TMdOrͣ+-E7T8 ~Vg0O5,cݐaXl܏tȵZ*-1߬G(x(q8Bb> !n0U=/4g12B, F` Io!Ƅ߉:gϫ+2J)jjXL )1~J7"RN' oFZMLL㐷Rg۹IgYxB*O7r1@gmty-Lܗx'g:rpW̡,g(Ҩ1ēD:ґv-h_pR@y"䲅Ogp&}*"m/9} `TBY@Z}y/`bY5P2O٧V!K F?%okxM^J y4Sϭ<,V07iJfr'8\Q &'[, EP>nS']i^U?:).ְ"[4+9x5F:cYM׊xLn2Tf~G6Q ɤ+'r"=N*q70aF`,qU@ ј9 tL |K sBcIjU͢Ǝ*zbV38Gɑʭ\g&a/;7q.`R)(bIB4QDItQ8|jVoDqIr*rjNXIӝK][= դ/hYtEp qưA^'kx(?) 0xCylMqb(TEvՅn\-:رc"(" )J؁8RɜĉGz wbSL8>}-.Ss\ gޥ[w в|Hyd9bjmf=&o>3r3/p8u7f's!BbDo/Q}̘5ذQC "pK2s:ғL+wa0RqEPKdVYFOr6q '+>A$Vʨ*Tclذ }ߩntQB7K,,ĉxH$^LJ5䰗 eDvJYEjxs*9\i! oPRF뾆.ėz.z11ۓSx4)QGȭX}Av'_8f (+b7hjT"tOL'tq؀h/4'UTNvR#>ۂY3摤݀n=ԓʕOb'S9k7F*1Mtⅲ(37.v+߭QόY[${9\[pɱD"_Pb:GNّ6x"@G5?3e!>Uky' cR3I$")R( l8#"n& c $HI$J2)$}#v(e[nvQF+Puw& p ͠K(KY&]Gd5p*7o)tPرb*J\bŊ] 0c! q$ImiG&dAQ"MlgZ}dfÍ*b[9r ˹~plALgN^5j7f TñZuEOTR-&_I$dҙ.tiE5\q:M" O p @&CSt3> ^ب<2y~3\ލ2Fu7ifhkC1hÆ)"DdQ)A7;]Dof wk`[D~5|-]b yuV3Y<()2)2ʨ*ljO&a[S={`O`LJNjj*EL"a!B&]EOӕtQa^^ENnJt^\HQ9u7aLCPRH!@"1BBM") |(Jh dҝxٺxFshڑff3Q\v*tRH#Fj5tS+}baBiۆa $HEIC.rC.c,m]d{ WN`?z\fsͅcսX`qPCPɼÎrϣPidA&,EG(bsTS];sMiğYX$(WeRvs"<'i{iDNR]1aE[hKP"%^vXIĀ+bMÕlA4<Í`-( cр9\B@D>~A* AP;# Y%`.lDNBBR$TM|~_R%Q2ܘX+#88iu)KTOQpǚcH} IHz 5w 7$xֿO'r|ePx>#cIl6g+ O5Q$^࠿no33/&U~sJ{Hkྟmxߴզf*LQP~I5/v> |J~hzou xk /{Z jkL~ιba7Lx܀[˒\uG8W6_' I h] u#kk\ ̈́~ Ƨɸ4dF?<۳:SPEGs_ \:h%܅U$@4WcοIo8_mw)Ӽ(xXx;gfݯ*u.o%a5Xoh=,"=0~%-Lj{fQL?TO辳2 V:)=@U6h?([৷#?o_yNO6 G')zgptWqwOdH@0#_(g'ӟtHQPgi+O)hUPQYb{{'Jﶱ ۵SZOF9eOjYj-wPѴF_D໶1[9 M1žC,SYW 9/ ]^ Po׈\"CUQi8a?GV?q|b oC,#`k iK}2a'?eijin_Mw0+_~u⛽74yo{)I<*÷ _6YȲ*GBYXd2r (/V_ٓ 79>*|tMK[̐vLTwq_:wP03}/Ÿᾫo,|-q}j3,f#,O ,B$_/'#V3+rcnֵT9Va_㩈e bV#IW3=54<(Nn.6!B!>q`oڧ !iA6׼9Oi#d9QYwvIYIz? _}ZߵomZ|;еXdTl/Yۓ$Wyӿ W}B~Ejn6aZ.?hleL@O֍x_u-| wz5W%4ɮbk)&FMHICa?gO5=v [[Ÿu#0@O&:{ S%Xj3Chݣf/;Tgؠ~9x2޲DYir?q)9\0; Z(\u?4t}S sM.A|4musYX)L'Pe"p ?+Q29^?߰}›k]ېj.nȖю"+)״; ᫯meXhI/br(syttcFe쏧lti4+m)`3֡9W6'<1q5KU_>]:[x>?.o(4xa`K$+H'_c:HvUs;岃#&F$~1~#T7WxEkMOӡKx!PEQ*e߆5oRkg!zѬ*ykhL!Ҭ'+[??/z_ —džnOXm`c 4LLx'!ޞ>fOG5j5ڄ1WQ1xRئVBK>̬-s']/r"8huO?pq*%u1.Ҁ?+Ѿ⟁uMOQ5;0X]7s;eW_/6 1>?d/Qt|Ww-,4X`G?P?446?f|خ2MKŬ?4 ӼdpD1mkKn׭~>߷gawjM|E-O3M0rټΓt'o8]G!uVx6u+xbۇW1S G ]uD\2g_0_Ұ!e9&?jM5cm ,686((K ȐtlRA1`[N5^o-\#sc_9KrᤵmQ 7|N|Aox&K-Er|{"yjx|I;O@1/@~ Z|tSm]}q.r6&_ ) 9O _2?ʹRU,Yٖ/;e>KB jBTܬ[ϭ.7xxQD=s0(%b D;]V)`V_?e _@ C} .&np>Uڠ("' ~>%oiQD7SiqS53BC ḄnS_[6z)°J g,zPg%5e20ȿ-ުtoK7 ȿO2Wys?|EեԵfKh̽n`$0'lH72)' #Xk <;{.y],iZKɲI)FXc2$ IQoF"Ujȍd[I1@l kg3F;H?1 ׶EQ_˯&_ao'߅m>2|p;{-COV#,O][s n4?%N$GVp:^m- !t1 UE|$~Ŀ%GWr_i;q'IBMB( gCÚ}mQ).RA*; 2F$Ĩ`9kn 2;# _#u^nUuY-%U>ĠPoşSjj:i;R{h@R?i࢟= |$#N (F9`|y~ /~!<+XnQkK5eTɒWC0_~?W~K ˩X onn$fX)w틮~?OįڷWy>bWb[-6:OS@ 5Ŗ~nN)`Fex!۞ \9ўƿ o[ a/ kdW>/׃.TDkx_0YQ_!ޕşjU lKK8ܴqv"_To#㵬i]=<lڷ52ɩ\#=m~7gD}Zw{mpH_Pk !{VΣ#Oy4>G_=?1;#LcEyknͼon~Kk}O}Al??烤ΌxN$B37,T0 Gൾ/1<뺴uɬėK_=@~PF_?Bh/ #mz &邲ƽIS~@<]oO,?[7/-jp頼*mр 783dPo'?uM'9+xosHħl/6 _5U6s?R8qq¸r8yOF,|BNz _A9v~.h:RDxAs pv'?bgؖ[ٶx!o"(wA(KwoW?~ռ |ca=v2@}oI걞 >s=~z=|muK_+Uv|_ ޑyrDW袗&!?^.=FܱQC-Dqux^[g<X?m7 uߍMbo oeּ/uk wmGIuk]Z  t6Q l?c(ǟce# kMǵu= GխnhefF'c?4#m F.Gmn,V{e]ġklB!dQg,O|"1L\y9|AGe}Ne8qܝ: ,v/_ )g~1i꺇k;6v0VVV$n`J:Ha8i>>[DvO5~_b?(?[ eeUEܪsn3 1zrQY4 ik>$E4;}M| O -+|Ik kV3wOv>~~?GT%9>7yCQ}c+K?iC>>^|%<9}?xvtn<՜lxBAprh$xE;׬@ VL?\W7?aܩIl_ j`+F^٠_߲NJ"|#j֋i+ Gif#2 JC_,ω_yױ^t"Q11;Mpgq lk/2'~d9uldk???AxM?5_;` qB%VybI$mK{t *J5ߏ9`cꖗWno4x֑5єh(k ~((޶' 3hЁPMSx,f5MB)>h~_1|~HGOEɴ5PEPEPVY^ƳC27Ն <GO?w:١c1|d+َINVǬ?G8=+%K!Ic2!״s7JρJP` R/Mm oO.,HdnR#4Qx g_7/'|9wx%AB9U oe|/ֶ~3B1&2;Fj(N|,c& 4l Ӽ+۷ X5 WL]CTcse}ޣi+h߱wƯ ΋^ZQ2O$hYb'捏N %5QK@'~h,?_?LxhtB~}IAGԢ@E!8&SN: ON_ í/i0|}n#7%;)OȊs(Afk7^o9~.̇YBq?o7Pt |&¶>$+;]V5 V6 xL٪ W.kH< +x.uW헳9׎x' gP[MgPEE@Bj/+:nkܣw %+ xͿ|jG\b A/, eW$%ُDu;@&kH.#M G C5+/_JyZh)?cxg -y?^_,F=mneE/zg9?Pox:Em-vw:IWmj^ObJT1x -cnj7bHH xl5g<3t=]i=M*`{GWh Z? wlQV~wTBs ~ /w|&ގMcH~ 7is_J(i?E'k {DC[h6uݍlC֟ ȃ5o 3Ix4{/7kɯo???QcoOh?.MGCX~1R[IVj((((:~Ͽ?iχ7h? 1֢Omq/U T_w ڶj4eO}~E>~4P|20.~V>XI5o\ZL&R9Zq?{࿃>jkq{cкh^K;fq2NQxd἟ ji/ʷ=vnKKnF랇#/O)GW{ 6|2oֻ9?c Fkt@bC"I"n@?]k Wՠ~z֍e(ncmbJ!]#M3y[\rIg6⑭F}?<K/~W^.Po-؋x^ɇ ~?-Nn/\Z£t/=c 1bk~_W@[|c>#[,abo =bk}%n23>Ǿ3 )h)l",klW릉~v~ͨ_[[񒲯?g;&IJ.~΀?/|k>|eԁ?7:GH mK'eXZ?#1;O_\ojVhDAK;'_bմQ 4_zq[d̒xbT1k!%b+hULj@ x:ãJy됓Mʀ?M#i{KGL* ~ocxx2o}pۏɱ_NL~(FS>m/7sWse+*M玍 + 8~_lI?e/+V,b7>}{GА/ߞnFE4:+C^Ds\z =?ï/Fk~"oD5ퟆХ~U%V8-Ŭogހ2~&|/|+^?'oM*|˧χ)A<ַA2SwmD$C~XMgC-?SZZ1~ +4:~Yo1^kxKMm3wxkv⋹{+Aq Ċg_g{ a}3>泪޷- ɲ$F*A'>73D\ hZT?bR;yu8 I?߿,#Ñ{aqKxÒM,Krl..V88_*G-ׯiV~#ĺh-o+K!u H"_߰e;|!T#7|c[BǻkʭegoYçٮȠEvUEPEPEPEPEP_)~_{|(a/hlXd>"[u$pRA :-bY7xHţlR< o-Gm?dýko2dLtӦvsy5ˢ60\a~WS_U){<_c?Hc;#+zI5:ODEjc"QWkk[$6ym{XL~wm^QFmS?a߉>ry_ֿ (7o$Π麿{7QIk52 xŴ_,MJ?[?dP{㏊}>ѧXk1̤wS\j6j#WEV:KB͠~dVó'RyFE~אgߍ^MoěPegB{şO ?+]NpLM>Uyo1>ޏ&~邿ҊMW 6s'gZ\ZO_h(?R4~Ͼ~<]MgTnlVu#Mu27mQ"8^(././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/docs/_static/logo-sm.png0000644000175100001710000002274300000000000017044 0ustar00runnerdockerPNG  IHDR|~ gAMA a pHYs%%IR$(iTXtXML:com.adobe.xmp 2 0 144 1 144 253 1 124 2018:10:17 10:10:23 Pixelmator 3.7.5 fF!QIDATxw|߱gU3VjVQF{Kj֨MզVƪQZY[Q{XiB$Bܑ{cѾ:|/%.qK\%.q۔ԧ.E'@7NaD8W()]aG&M 4KI\I3#L*P$/+⻌X1,,.\~KAsO67AOƳ`5ǟGɼln3TL4D~POR3)KO}ݟ) M{H_]BF3d )O_+ϙDz0[)zЬSU =eaʺ&9R+NxJ$ <,02,eD jS4!:"x%kQ:"IXb&qZ|R%dюOq)ϥ)o1%|Og99 9LPh~Z Z#Xb L}DE:3=\S(B{[$\`3h0IASv-f17|j]?)Rx:N/1}Q+-4 HNb'>1SUzua>~N:+3 =*r15sJSԢ1P~d)9B4SGیxЕxƯTp3Kp.xdؔQ5Sy\ 7FӖx&!(HU:3Y =YE*^DI'?3=,jԳH9&LL,u?) ǞXA?*ݡ4RMjs0LkCMa?5Z{e|>Gv0$'99/&S_'ӞW11Aڊ3F=Z%C%;D*:+KZms3HvDYvbtE^KP]<ڪ~ :Q1 ͪ oe1*f3&F(PAj(Lq@=,󥽁n"h$V~8*Ρs ˊ*g9~v5Fm8衯 46ݓ2~Lkèl9Mho% %jG޾Hr'Ƈ?GZ9UKKcX3D0\aZU,_|ۅ:yMDrKF 0H\.z=d/F4669Jm9$%`a~jԞqQi$Go)FWq6*wi;*j??]8UTL`Pi]I3<g'gl{N5!uB>fEx30[fv%[T};<./-׊TfhUTbsT(_axk luP#`~ /Jyiϱ;fIngO B)GI7 f+0F2 WqSM SCLu0N[472R# lDۭGF"L%c;NJ@6Y@]Q ,j:+Yy8]J]D~ޥP$}O0<)9@2T(Kv#I V'K7Yk;,ELse Ɨn*{5P \0!tR@#z\s`y2VcB>taX8鄰JbFJrI8e~mFy>աxaM13;sFi[Z|Ϙ}Ҡxн,0#׺+?d#iM_~W- f8Q 9?UT9[{ӓP4r.ۡ/;+0a-X0 bⓑNxիme`NtWMkdiJy\!5uQ {v/aVo.H&9'5:+`pӗLNKxD>s%|*CZ.N[ }wݰ;p2*\XVPB-`ӆ^TƮOUOO7vIM%5w7Iȩ 17G׉~=yi,fMؕN5]j,07Khp]_y|ZV9Ib=wetֶks=fgaU!*mHk vZY޻v&)3:q%dazcxmHe.YoÚVWKvz]s[)e FEk#~»o :RW,<5QRі2#VQ;F< C4Is -%Q®w}g&{re4o~k49] ?,[t"΢n4IMi[7z|YF=PNwu _fKHaBVKIOC'64.Yt+j 4*(؂W툯]>L$Ĵ@_1eбY6:g/+ǀ8xȼH&ZHi89I`ۚW[F-5z4[j@-&tcwPe5{;E)sNqsu˱d H;gfM<&2x/\p~Ҽq߰$Yen^}\!эmݛQAde_1NV'[*,_8h{q6_43U,Q\b)ԧ̔Ye/Ct2ΙE3]$rŌњNRdM|Ӗ3A =~c%gV}k;l;&D٣UY4^IzbHSRt/1tf:?R D21\J/=#؆Tl J1E3x,Gol;>ƫ/1AaDifY+d{AJ`'+یq|Dx:&LLnQ'Wate4xS]EdFCI9~eɰ`9MI]ZA|O#֘agkaG3K.;>r*^fc!R,i@:i&[4?1qHb[8{%,00^'`"L柖PK tL-7Y5,B>w*!J~ F$+':Bge&!;qH^eH\v[U|Ci3l, r#Fx+x9VdsIb2_"0}2 [i"G=|I[n>Ή@%JZ " 3jNL`kKRKԑ#Y(8}eF^,U%?s͆p^;uF O ݚ812봱'$a8,W.;*:G!DUMC<:9uK+!vHӔt}p@f&ޒTѐx]C-Sʲ HI5*)§98N @&\\xai';_8٧{{b=I]by660 c᫡ t^UI'x+*1 v;$ 9\RE&n^; 5uib xSXStf*8] hݻ)Ÿ"iIJڰBm,PYX}^zC%]VL{&lLVY4D&a(WD2rt*rQ81 0B &"P[&B #VQ\gHYӜ\54$..OEɜm |޲$.3/·2zGb=  ߨ.2Fbg$=Y1 _I%lf!?_R2%LzRd$#I;Jn)Nyj?0l7+U;0FR@88RDȢ ^^W B Oc஧9饸Ѐ@*J2߹fъ6' t1(BVIOz2 OIf2;Hn+HAvJPa%G""Y'Hk7A23a:q99M0FG5Ll{3B@Zfк(n5,f&B~a8qq@}GdMv36AN SftcXzs\6)z/#y]ple5@oZQ o>R43P|%@5>Y#= =ZnI6Rf4)d 3i$7iP沎ݜ&mDa?*"FK9r`T:.)&!6W@eHZlԕ^ٰJ=ZW,kB+j~fg sW 0I-'<%犋miϸVmLV!yF7~0gQAt9eEiwHJڬV|:*fuřH786vsU0PVDWАj&?yɃ^!/ET 5GѝoLAYIa5#G~٥:w۫$ =(3#Yp*Պ>_G ./ֳ0|EC*S 3i,-p';(OY(Wo"9FɀٲS\{(IiaPe6X~Lw1'+Hӂ/+ NK^ѐ>f; #1O@O߇J+J:t4:^ÂovqG< O(xHO~eN `;҈"'uDRH;&-x.[D>=O{^%RbwieiI}jRr8E(HWj('9HUjQ5}xf5l '9uS/2GJ26T(ɤޗ`k8& Ux%7U?ToJ>ӟ#pA6)NCa%[ 6/ׄr"-ya4;apL(=/$8-܃.H22"-e-85yj@+R4O QU(_IҺō/5uw2=X'iFm$E&$=yc$O'1,<݋c+vm[1—9~y+}MC{SZO .8ǿnee#O/g&?JFҒܡy֜ >3Y!s ,b;/A-bcԤ Yq'9m)B8Q?Y"fES$Yl2Md~縫 =EÄL8Shnru X>Pr{z3)b ]iK#SL%E<ϒdUi ȴ+4mJ}7ӹnq3 ,fBo:ҒT4)D^rŦ D0IG84 xöo"q $lVTF-15(O)-XE{L3 !\նҞ{DWlP1Md沔le 2 5 72 1 72 507 1 507 2017-11-16T12:11:95 Pixelmator 3.7 P(BwIDATxwWǟ=DIQ"⧥)JPNCT)DQJ٣%JDFVPZG=3>?=s>?=A!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!?rP5 BdQ!TG iXH`jӾf.N. KQ[?a҄0!JqOl8_'Dqo(cg8גY)DrPUaOrB$2h(+Eq FZU|WR3]< Y73Rh!tW}ꖿI4w08ƒ\!"7?qR 5̻0㬦3gi=ﮧj٨Vdk"Va!+I#hJumg|?87\|1M4#^bfpEb"xg al%Wx3r3EJ/Jpz}Q]iڳd+"j򞫘nŬ7a ԫY*^[hrL7яޡCtJ&X}HCRì4-?&!-]i97%H_3caJ%9NW1zj zUg LkyTHWekCգIY ekƻ(OQ syohe}D\Opat=(=\՜BqOpEH1srҝ\jn:FD4Ϲ04_v{G%soȓO._SO(\k殝tKG9Ϻ Dz&My#`/#éZص1ELoc{ ro/|d#53IXZ*+[zDrq.E2bI\!懕F SA r01aǝB +_ϗ9z6ڞ|: hCO1u_??oW~k>g3y<Fя49O1 r /OR-,yτ5/#lkT3#Q47Xu vV0 %rBMB׬r?i\w]ʰ>F  w2y|( I;·,Yъ*ITE!]62, 2,hXϙ)ȏs"䗗d,-RhxY p KjuM[?ϩKF7<JВGXMĶYShG9ɫ4r-ADWWmk>L*Ӌ5-̨";:4O'06rI6!;35sf"vҭHtAy˱5p(v?OB9g}+ְa=hO rWR\Lq R\qPk#}df: {}%+&,J?e7^mvX̩)YB9Va=y͸*J6 p];E8gen8l.K,}@_.BS3fJ͚rw>pfj0}A61aA8V*DZҟ,Y2پmI9keLۆEfRfMvJjY͵ _TX(ZP6!} s8wf/Dm~K2&"1M B]eN͞rُFr9cC=F ᝵q':`:y# ?scVRL.~fPd<{(tᲤ1t]Ƌi30qmҨMx=qO&$gws{GLT8g=+}|j`gq13ur/'U8 ~^{nwpI؂\Mpt q}_1EW5 j۸"N~Llx%yht%K=\ XqG:0b㜦c?br.b{fsB[-"k Q;5\ߥID|2'\^FŠb6/$Մ{]ݏL܈y,lRFnVGw/S6bwI*Uëa^?V6T8Ud4qzИX/41,>#J{rڲ(bȄ8[׸Ndlք˃POlp,;kt?nh{UYVϗ)E>'Oƨ2^nI'oq!P}tD٘+ƣcn Ja\rgb&i̋M^B=l8nvqEk'b<tNKuBx#,p'Bd0OS˜N%\@ϟ/ Dwp`1W1x'WƸO-QG';$B&'#f7\-H"vQWM|m~:+(07ǼW߸VO&&)ͽR)+nHüa' MKVm|ۣ˿K^.1E;w;UPs]t͟ݍ+-CW! U=6"a.F,,wv 4LK'"|fļCoK,η} wr1O|=).J8Z'jU,Ȇ"_r0߶3#&ҏ %{FXR-׮HzQV fFuJu]ӅRPnMQi eZYKs)oޠe6qu&ǂW\|7;ްQ/НdrQ2 :&ëH׵}bS(6&9 o YI>1|E5s5s&[Fn@Iɶ ^SpJRn$M4if6&T tϞykpqՋpWy~jnmAc@weE_`7 +KTq gR0a>\Fʼn3 MƱμ3l4 "0\Eeүd"yQegHGVPs^2xݑҰ70\ROkA'H|뾍<zUX lrw Wz>gzX&\gm1;j|&֘'/)iA5o)"fdΛڍQ=Wm)y|99?x7W374H'Caw/zٰ_BO}t1qi;*3tY, aWnvbcΪ,c/_pـK҈$>w3C!'Z;{CJ6J'wƞWѢ)83TWjڋKۨ51ϻV7iD]f1LV'}zN/$75C.7U2З1CZne 1V|Zw8Xge,fhmvIFUnSg! >~etzӇBE2 FEϫr bmaCQ=j1M;#Rpz Ȉl1W1e t9 >-z#eq|ƈ:<(3)Ms#'J'LV:;=Q+:xO3fb,ĝ賞{1/\d|Vk( z"eV5H{[iNZķ;87{ڻT 3nw~M5GXzЍ.fhZ Uƚp$ĴU5ʐc1.]]ì/sDjp (f~E7|ʋ q |g߯<7%܎#iX#5nXʉL/;(&}ڛVR:1gC4{Yh"_²J@[]gtrmѴg"NpcfULrE4Xv,SٹڵUz?D.~{'ʲ5.zx\s? :Vuf&BϞE!h1'CF)k'k-{Ar:|F.O-t=mDegGKQmOU1> !/ΰ[=hI*r|J7Ϲ }CQ8gnrG 7h)K 6w,mfQb * fBO"a_lXCni9?~{eYp𗌲:?EP2~9e^*B@#&$rRILJwk5, -ΗG ׿!g6Bs_zu\@$_Y4H`4;A9Yx\$uϠ>sK? :(6G.tTRyhA[ۮ)7Lɇi_R%hHO=J +gόb a0ax'm '.t&bvrXY "klRL ?7*tݽ`QN~yh}R}w,@Qsn{uXmpYq)1cY ?XB g} 7Ex2 ĚߏNjkjNuyx?r$'LwFn|BS,;~%.'S'|ЇZ*ɿJIR5\tUpϱ[Vv -XꏇjǿҨ85YFYyKyRvBod)2=m'm -i_ƒ٨nJn+mw ?S-bs2tn FSxv8_j}gNI%g'G&;6?NWu]> ! !໸54G+sؘ )1k>#G;m*fåAYIg'#[B^[Od|h|n_Ge ?CSOeP3Y 5tDU 7CAF۪M* }~p>_}2E7eH-a=,K mwbFq^4jBu-u?(mtSG 5>┟?{{6όEXlPQn%뎓By͋\iɮ}!',{Qg&a^ϵڈ|*G(}kbQuT=a7.kCKc`tHopŜ\`9Ygas48h魄_ֽB8G;fd `Iݢ#Л`~UlsNnlz\z^ی-쇝@ HWBq%1֛fFWg7 og9BomȄٷ"^ nr垌>ҽjye $ËG]Փ)\86{N~c_P>U疸Rҷ2@%^x;)ڦh/)+B8U[fshiD|May}oPҊ|K4M, y(LI* 6vE_a"B9*%dx`D p>jS<ڙaESL!_&i̶n'[OD4L0-^-G^_۟Kԇ/“>SsE\dQ8g`}<'zTO;{2'pPK?۟_CЧ!^KyC _`IrVy9Ş,`)@v(7Qђv+a^\zop>4\N/k O(&~,`-2Q7Ͷ*۪D-\Չ{cbtj&6~-r|+f7f똻!Y#3B =- ha^u)@g˶ܥ ?gTP9}y3jtd?2,fl ?LC>Ssɐa˺) )VT/GkᎽ В~ Lzv_~ !%]{۟Ka)DvŬ;ސo</$g ~F7%\)Mynn9-iF ҐԢ(M1Ύr ^l[blsT}l돟IK0goBX;N{zl]Tĭgٹ4#l`gw;; ѝۨe!y}D6QXkЛa똛M*׽!.d˵ޤ(gx_Rqư/HSg2kkxGTଘ=K;,+u1K ;*8zN40= nnvhY!\>VPY_\TKigoi_lbhG(:Zgh:R-ZZRлIj2Ʒ3ON$^HA.ab<BɯhS#ђ(%8z `ģa 3MmFt,nD<h`e=rTuݤrp?{|c !Mbiců4$7φ܃J1a!\94d8oxx\BI@צeKh. -r3ΰV pgD.Wˣ;"Bez07J9'hG9UR,lHne*){1wbk}#b["2Z<8;dZa2?*lf-+xY+,d>BxldOW+s_IUWYR*/{evTuuMBPښ5.YzG1Ƒ϶F\FǙWv [>C,gDgq#)|r5>GQ1; j~b7{EWA.K̚>y|J9rj8.cuG>͌gъY%B|,Fp77S6I:S(DyӉQb?CshJxtOƃdpGޱC[^\KW ,׋+2~R5ҏFf1\P&l27.L[xڶ X~4 RfxH[Fw x% ͇9n`ߥ, ByWtm Lc.J\#XSAޠ%[?Kcqe9E)1"BƤOg2§0j;f餸3b66<{Vѐ?!J'd/'ᭌJ(.;Sg@{q%r-N8&+xd"E [QZWGk,[[kTrEDԙ-E39kz/be?+O~ +lհdXr ĵ?s 42"Ԣ;O23~4)<Ʃ=B/K. sӵދBLyEQEW1P<7"P`050W5 "׹YH'fKƂ~=s\ΰS L.=s;*v8v#!ᱝPMAP'>s%4ua Z1u>MVџ*SM--Z\CE.b.< S;΢PUAn:ҋ!<Ӽ[+7s52W2mlg"NR+36ǚL{8*::nПj6{漿wlI}l6 ^}J 6<%xJo73iG=*S[Q#?/bópG̣>';E$}/Ac*ZcI tc,/>bJfø_n*G#Ieg؞2Y? VqG^*g9V*Q>\pE}RﱟpQ}T$<_μ1^Sy6 rNі{CaӘoXˇlc>ekZ§|f6bo0xpГN)uN.H49 %>ЁF;G޷ASD{q/KL ǐ yQ#Ԧ?Q^N~r;3}؞+qNӁ~9^e59ʩܞOv)0z҆\KY 183i]Ar[ jEy~nϤjHjkm:J6 ;W{?Sv,"8Q/Z8{,iFҝԠ){(5te:By/eng;s&ș@YxfIqW=AIZ3mA5gIYQ>#<頌 sk8׳Mm΍k1|j_f*ӈlSiB(Ӷk\ W7ڇyL vÙjTWwr%r"%F7b+x6\zF`v8sKa:݄;K+uJi G(!TNRt3,~ZD݄y %WCQЫ+OKAK'nrXi((֥;Ws B gp!Mkg,3y3$ќ2d[qpQvt̕> vU@M5Z]*m~%.5PH6O -J$~*(ԡ=b6KGVI#8:jY; .^ 7J[G3z6̠/ )z&P\JEQzJ+ѓC c$3L౴(xqa4# ~34f (ɹ@jʅTR>Zn. %.G`Occ'da`'[Y[c:LwZs (K!VQFd"5bW(8j 3ŧ![ 0(#_f̄]Sqn Y3됲Gƾ9Ԧ54O1f+ְ錢QKBSk!bл1WVq1|oZ˞MM. 9J _ ,+?<a{GLuZ.nq7xf7|\͹Ϣ*<> a\;#DVr+|~2#95Glm\oXFK0VOvXLrsR3%`T9E 8:j[m5BIwF 6/^'yYQ6avYctf.4ϧ}&w^EcQ!3ZصT1COea[FaV0i\K2J4y(<-jt 1?VI71WV~ͼJXີlxF#JPvf>'L\Q+9Y2d1ͳLc F1< j1ьQ&3\^ug5Ʒ޸9K2fin98 w#ZMV(r6|f?̔SgFAj&F,k`ypБVz*R䏙n p!q5)c&YV~)N7vRsq̕>\,}<ګ۞$e|f2 p+.ˢqI7cx.I7c|lzqTT,3o9dz}i5CK:ђi!=̹T]L竈 #A-se7Sv:~hk,lWY~iOla%xG@gZp05R(LӇ'x/"4 =S. Q.ǔn8s]*%Nˊm~ /v,`#ɝԡFI啉b@ = c[TRi+}N/߮Kl.צ!qnj|SseF@䓻Rp: q^vLei5b(lQpM&}( {LhQ~f#Kyat!9/:҄QbM1ǝYwMRb\CipMhGO2,f3X̓ =)g v Kq'N W\Yfj8Qf)=ih\U62x ~Kvٌ;M¹ǃ\ǃ,q,+}P7l4pa'SKkz2i,d{}a'0GpMn љ96+}B$уA?-A(ȥԠ]g%_'n(:RKKY,1WVSBe_[xΥ<7Ӛ>S~dY$zӄE27\YҶ!XJVz}.-]1x3wҘ)A8'\I]џvD,If 'W!-u5{/J`2 >ӂzԠe(JǞ3)M n7y|xl+mqCV!0C"!Mwc+yŖ=ĭkp&wZxX"}#}PrAŦ( `.b3=9T] ϗ>OXs?>yo1"Y}T+8b`'!ї.Cd-\A=:2G8+eO&>D_N#W6^qsͨC ǠdogC< !'CP4;:~v %'-g.I</2 =\DCd"jҎdߦ&I5XJjd燠J~Fx惀=DO:po&1wG.>t6-!*$z;!]-{3<dOFβEy_mqtU};?=0tzZV KVKֳwx,MVrSˈj/}Rm{eMxY퓙|g޳l.*siA{ #yyWyl[vGtQm|Y퓝x7a#Bo&PkAGz31L9+ȟ H֒dO\aSx>l(euԥwэbx5|?/D9woQWV$&2+,'\B. Q˨J-Њ` 3e)6$͠϶eOod Xj%4]!qB^Υ45]3Y[||slڧ+q 0woivŌ~+&Ꞩp U=Hc:/*ﳞ|ڧs_& B?YV{!RZO*r=uiF[ÙSfﰖO_Li)1y)GUjѐVt'da. |QfzB$YyթMЅ> e+vPZn`"N4VZ~iB!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!vIIENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/docs/_static/logo.png0000644000175100001710000004170600000000000016427 0ustar00runnerdockerPNG  IHDR75sRGB pHYs  $iTXtXML:com.adobe.xmp 2 5 72 1 72 507 1 247 2017-11-16T12:11:52 Pixelmator 3.7 ?;IDATxuEǿ^QCZ^RBiDRAJAIi)D@*}? g=u{<3<{@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!\AA! -SE\c/x$QâU'B zSxjG"b{AЀj'"~b&T B<_Zc0WSnDvT@F( 9^dVëhA+NwI@10kY0j̯BB9oBI f=- KF5 D|zsA OXK1+Tf([#-OCg^R{$a O#}Xcw1kPPR>7By9duDc6g%cɦ^B6I]ҟoy,C#bdg>"HӞܓG8((( ? #/.rD: 2J JAa;x=.1m"ڳW *M(fI,&%=$bJ\6u~Id. Q4X}G]BØ4KP= rՖSBqAkfƚ2BQx)(n >ǦLv!$P/WA %!* lK\ox\2}B L)~!H!8 Bad I»xIjTPBF>@Hn2ATqZe,:F(:r4|AEg>]#&B mO XtOgZj"(DsݢPhk\rYבNMliFČ2Aw(WW]isߡ)(x a4Ik` f!7`3)W"b&g"AL)=mCA)B+xxG迎`왒thTF߾GUpLAT;дj(#1saiɫkZ_u5 i-e!V{ԛ%TRdaa<2- .Qǣ+W.G!UmO$uhMOF0 vp_unpUw~g/Y"f1t1U(DZ)X+{$ R+޼=b͋(JvF'$8 tֲ߄ [8N34ҫeYeU"}]$HLT.ث3S#MĔ%XQn{IeC~c BsJVuc@t ̰PJ0?+5ţIt{ra)c$Vw̧/^;@uv $;o,N)G6# ДI'qtpyuz2 plb1(N/֚6| ZrB84{O<R'eof1n(EWATOcӑ_sÉqd WDм7[ϰcCA{P!?Nfғ$#)IC”UҜe3X|ǯܰՋKKQ"蟲Tak#C\5C?,"6I4I rbYTzш^!%K'F2ExnG'$ `0|Aߤ}yŎr!FEfa3c%)m.ڜS0V7\Pm0s1#>{x%`w̶aM 2F!%\z2:|: ՞Llp1?a`!DXD숐 8I[`c5]n dcA^cHE;zoY6Az!8os(J/h \> fp&--p:`Ubk? c6QKK^hQ&`P4 %AP#425>{h1hIz2^P_`o|Bsz*!v|J IoqR=pC& NJCW kKcxlDC=>\&=ՐPSnz8*1F#%/5r/i6Omeٻ~½vko#1akEj. ÄjCUo Ïd "j,#OQ*gbLr;vD\O9Av5X"0c@/ypLT7d}vQP䓱Exn?񙉾 _D>>S%5#,!m&W&gNҴY\]ΤU'3~c7I  O=$7F:u EOzJ=ʄW#i6[2=4n|l[1#^|$ACm|r1'Dpс s8oFr@XۄO}r+Ud'ܗuN`˷uɹ!'Du/1G+M>3|LJ~lI^ mq'mn?!l̒pN0}G(a Hv6L8%wW IZ1a3x;&ALsFص^I1xJ?ݘKfdGdiEes?^\}N:In9Aâ^F>6E[ߛ<2zP|(7x-[A=|vDl<ؐ}ͦnKD(- ff*S,7EZKS~7qU$>ɢn6T le7 ATpIYY&Aw(@ӒI" n龜4R|>24yj?9('eɮHHͿ4wrY7arE iQE:*oPb'Y|dK5W-?=NkoSgu|~B:f 01 vmYZ;]/,2]|ܧ/?/߻6!ૐXbw@3Boh.7u ?;>[alrԋ-lxy'.,M$CzcvΔ8,Y'Jo8Bu0t95ȣK5eۍr}esPJ=O ^JH"VJQPLOԨsSE٩:ipNAe35;Lk—>nw\a|·,; ģ"Z3 N:ew+ֲ,loA߿:%e$#8kL@!o>V9.YD22 aC>Q/`J)*?: !}79u3jfmu:M&ݾ>;WA}H *lPq ǘA}t5q BK<5 brPORU$Sހgm$ yKW L wCbW&1g:xY2$$v4B"(Jh0FcQR{k|>n 41}Vlg* oфOD_>@9c,nJv, l̓k1-!t0P.I:bÃĦ6 U1=5 ~6PR:\5{.&ϫvLJl > -H=zs?XbާX"1L$̆L~3o:% *.1^v|ĪS'd6yьM 9<|ıuz&n&9r6p z!0 ˏ`/fkػYPIHIOS*s ZxWm[>-X$^dҿg|5}W'`{_,esk?G"6=7ObTf0\Wn\CLbB1\Ղ>b(fH2.;w,Q_FBG[|'Z%8Oڦ* t*ʄHx,ț!~0&X ;>s,ȷ_H*0~_K\aInPz]A; Fm0Ov\Ӄ9ՙR׾UT˭šk7}ײ<)JeR4$RυRR#k% 7Rʠ׾Aa( >8Z IziHH%Rqc%#: Kj:൏Pɘz'c6P,a!UV7w|ZHj%n4RZ&o+M'o1nr8쇧K9¾BT9^u79o⒓iWBb2X-6%S#ilIHm feL]t 5[d kܱh)$j0$eoͣk1!nġ] InXM"refϼa<䑋_ޒ'CFk'^ix7rԾn*MCcegCp^fAe9tP?#ŘvKqtZ-#TpKとk`;|}y[eO+J\?#c->a) -;~ 0?$~ 2JK;bn 1SR/7LO+?u7V.grr_DC[A_H]qhuq}N6\L !}bk2#Π L$;ج䮛ud4 s]pǞ[~Ӂ,-rXkaGԃNڻ;X QmDrzcT#>CmO f'fDX{TH3e=w܄_ x%op#q%~YRkzР9qX'=21R1^R?NjkťjEp u \Sx3{| ~=PjO*s.KNz?:GJ>iF_HN*o ׆ko/ Jw{P -ݜrQIԷ[$a;w9? 4O#% ,Ҥne z{tG/8$s9ZKzhjV$IMqwepnP\t\٫# Y̓nJ}ctȼԐyFrw:]J]'n?Z`daHBAWh+9isY>63IG;m)>C^7VFW62H-u5{ ŤRe2I2 0&71]_ӬRF.›)%И]{R-wϚ%oAo^{3U>)U>@2 HV?],& }6D[)"EN$]8gH_tյ$u7m&h_Q¥/+tasy[CǁF\vb> xWjKA ,zRw8n07#E^~vXo޳M랛ޑHcNy9 z5Y|^ڋBKKQv/4C$LD1ҧt5QVsGW#瞅I ikBu5jk^MoYlzHKy*,L$fDya䖅㺩:7灮GI./ެ){,Ӑ>FM%9&knMIg4ZJLD;'=Qs3 )?ۛ`e0W KscL 3ng {wlAoҳ23Ŧ%ҋ46MI€fRDK'w7I0#Zo$JkQ?) ^Ao.od@ oamOMU±CY~) kg=CRє6xD4хm]>Bs<]`༪|gSSҝ+8lW *썹cAu 9/Ƞw@@zI!4,{U1M <Oa)F 8enAf"-%4}hB[XodЛK)7!^RLY8Uki33[MQaRȇX0$$%(N?A/˾fSi 734dxBFXpz86Flx/H\tV |z @oMTAJͻhhi@}T|hZ })&5{p=z*MQh+?-vE3\'3@=kmٟiäh5ZI,]&jVYM|p 6]>qʭ64n evTuFLzeBCf&L튶|Bއ+T҄D/C\Wc(0o-Z(tRَ1ID¥$ _o4 ztspRbm")l%#Yvɇ/% */tz 5OcP]YPRFehk`x I-ܤ!䲔KУBXF@D]>ݗT^nUZPҵ|B K2I4Ыb6Rx]~5c2V}^ MIN$%=9)Di*Qx49oЂ7hFRT,I^r ^HQɛ@ xX&~ЍǏN,,`y-al˩wߢW,\"h&GG3lf?d3{;L8|ӌ*4d٣ Z9WР7_707"MS& t,xSSqM/I(JC0iA8^V2Na^S]O٩3hРŠsPT7q2Xki!]UV@W}jh環s|XX~.Ro8*Ўr^ \u"4YM5 ^3<>APZێHZS񞅒_FQ^"HK-lZlѮ:j 4 PsC#B@p{B 49NJK|X]5|GzULzb`8qhG~*!Z#ٿ5v4Q% z" ! mRs]tK3"Fr ѪebBvWWe'9>v5_XjVc_ {8 P+XoMm<|*)WAn`nzPBk-knUo1H[@(ۈ509SߡkP~|i3}y<3!tR,=a$!(O=яq,sY|'K ɂZc]~s4~x-6A=*JflʚcԎRSڋWkvU bߏl_9KtKʷ |F&CRPtb K6uHc<0u5 5!j"hJV44=vko \nfXoxEAN[YKΠl8KLC )9Lg g=b?$rA|dbU 4jNA/nb]~.y6I; Vr]O8>̼JXgq5̙2d x9XG%.馡ax:U5If"7r{6r8ɏ`'[Xbf3!C[^2d<>\ri1O꫈F(kI^lh u6ep-N8j }gfZg3SQXd,h汑buq"GLgERJ-!-jpdڧWh. %Рw*W@4C}a.*0j`,Dz:LSTAқq۬-gC5l^ β匧+DNGVX007<7jQAoA`nڛ:"R2NzB#<}LVKDU4X0^g^`65,՗IJiZ32И]a?KAkʒ`/@vy?Q8떏7>Qko~"Q^:__'}+?w '|.H@)Kom"U·8z2:%ӐPuϳmW 07J{͐6=0Ħb'CKwyH9u6 ц)lQ):?t2B)8|]7eՇjld /oz:l'IsX8`50]טCyt#  KmLybq:ka@Рn\A BD(.i֡"bgm H44C6P,4gm&D8n6Ye oӖ&FP|d'#iII2y{&MaJQN :ҋaL#V%8UM{NKra2'84vXb>b& z҉V4(LvRy4f(rԡ@Mtܖh7CF\eh2Ay[TK\<:5XYc:_iRl ciI}:0 |p ^=uqobf02 c glkw1VFk 'o I)Kj{U~1]zП/`RЎu)785F=o'\4{XG;MHw+H@2cF`& ؠ7007{–UێozWtb vs ;}EwV0ek@Z%9&5/#0U嵷omnb2n)mxycҐ^~aѯq0!2Y%P 51 䆁k/X76|d{]Nz?r &6*z=,eLf  7k/|6 P:!Qy16|h`gEҘιqtMF ]]P5+fӇzӥ҄dg G"|'A3C_y-"-l48acBuR;4Nid;~˜0;|L3miNCPTe)E S,$)K9*QԡMi[M1|"V8)_?Z,pm|@jŭJCR^!f@6* 0[ x[W\P zQG`~r:A7Vl?Uu[PR$C9Vm8 IKJQtb35lGZFނwU)!滩kogT'4ۍ=u3+I96{.=ΰOMR > u D1ӊ>g!9E|$)0" h A`KiPTݣD\:WO+a|Ⱊl#ޥ)H$VJJ R a69%/r: Z "G0 U^{ζ36SzEx ㏹EǤ퀴&.VCIHJ2 x-ѓ =F0D&35Ma282 ]iOKSjTF8Pbr4gQ}v6 s=fYD7͋;8Hx6#IB۵egc>=x qHMRftbcBֱ9drKV1 ;x&qI(D=z2 w,7L %lc rC"'HM#OvwC=8b2{IAnXؤ(iE/05(|:gz142y,VIIZ2%y0A5A`;쬲˾Le|*p׸sa,ѹ{Gg"`DM0+߲I1I']0%JъQ⨅EpРV$;mN󴕜$0 tk:@*4/X~~w2|e' N*^ʨJjч3Y* z00Wyk|;&M-vҽvz4kAȰY w= ^LY3sdXuY>)YgS3t9@Qr> ׍C22'ØBֳ\+G7J62e*ב%6⛼'|tw^>#ng9b3Y,3^E3jS"d'̻ςt{3O"t'<$KEyy`oV^{b|DgApp -l8QeKM㻨0P:ta4 38|ޢQ` Uq;ECÉWVRmX&14*E!lCM1$sާ ę]vVh/bfckhzҒ#L^dg"tC" 0Wy}QJd f*iV;lfG[jQXJOԢ7AY~Abڤz|ϴdmb Zf3ԧ Y1,:}XWAHզ~B:۴}[45coyv9aMf)!әĸtց }џ a#8&1c0?ǧvεa7 Q¤"yy ln%Iylwq&YeW87`yVԡB2\ION Sԥ9oыg Y's= !6#D]r]L4` _[8N~G؜TC-+"{YXP⤳CGjrR4=4ȹ{Eg=2T'&x)AKfrPNa` cD!dd$iFW2Elb/' Rӝ xFae6a`bUX E_a˯DRQ'cZv3C-˙;4uvCGg^C 0Wy nXJ1=i!5h^C(KEѕa|jvqk~=|od1cHumlGT6H] xwC/KC!ъLd1_s~Y No/*}@"nJVMJsHpleK$ӗδ>U)MO -Q~"Ԥ*%1L9 k(n͋[`C4u\4fVeО&Ԣ"E:HDN*ҌLd;8mxs^U^+C:k" F 7g9^C(t''iA}wu!H*}$܊M-AR"g8noҔJBIM1ѕ,N ~v AyI5N[.y:yY.U1~tTԗ)H-:2F>&k.l`&yFԠaRf3y"=IB RL``;Kv܍kLhyd;x˘dQ :"YL;oxhI5hJ)}#6QL6bM/HsG|!*QC 9"zk>XW|-2mb[5: 6i@5>!Q50w>W>ذTq~@tKߺ=@3^{A؜q x|g0pcDYU k#OWDp 4g3WQ48PP|ߞpKCbCI>8@jJlb[1͙N~kT~ni{~h3QyYm3i2Cq*Q&;2|ħl`8o\qtQͷm,>QOC$|P UO :қc'*6 ZSW^|A1E>p")EQSFd氄u|^ ^Mq*}#2ևd&BӁ f,lb9y)?)}TC(%& }6RvA ӀVt/Øl/8n30G)d53r+c=ʮ%##y(A%jӔtg <>ep㜍FFK嵏P5^0Zq+(xdLO.R4-H&3kq_+k#+>)L~JQ4=Xf1+gs^Fu((b0NZӅ g"YZd?p6SPPJ-3$jнMIENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/docs/_static/support-on-gumroad.png0000644000175100001710000002212100000000000021237 0ustar00runnerdockerPNG  IHDR7.-viCCPICC ProfileXyXUͷOqVnKnED QI6 ADEDEVnB̜YkwfMul@6%@-`D @ղ4cY# _{z{Y3#ō`8O>I$xk,H?N^# 2^@r[~'mA"/N{ D_[$ڴoQcvcLVr;>?Otq8}_(@c}}}Na #o6lwK!90SQ?:a"FGcX0@D M'ȿ,?oq[N#N/Zs~[9\Cn҆4>iA w%\ f-GG̬{:iy3 +&bcC脄ƒ||#ً(Cj'"hynC,u qYH v3X8Iڒa6,zgТ; I(@c`l#؏κ/BYGpL NbP *p\͠ t.`]3#+'Axb!^H H҃L!+r|`(JAPT C+P+ ݁BOW,L3ܰ0, Z l}08NBn.< Oe  H"*b8!IB2|Cn~L!ÇDק! $a0Ř &-#+7˅a>hl6{{ۇ cѽpgp.C4ndz%jx < Ok ;K#B>A!#EaAIKCQEq O"=QF! u>s"%%JUʽ~( )((_Qbҡrʦ:OEjZZډ::"u/ 44R4F444%4M4#4_h)hhh^}@NNDDWBJn^ނ>>{<0'C*C%C/4#(xq $dϔt>3s s s; "bȒreeUՋ5u'&[[=(*;{qfI 8^h}88wszpfp^|ssYqsUr q-spprqra93ȫ{3_ _!-9~.~CHr?wݑ~ǤQ@E[@ `%gBB*BBBB߄E텏7 a1$\ZTC4LBNLE,@ذ8,(+^"@P8#p'v;HRIjIFI^|%"e*",EZPItoE@* YYc rrr%r[$*+2*)QQDVSUTvS>DIR%Ke@ڦcҮ]Ww}-;`w{Dx3CV6Χ^>AҨx)yN󝖘Vmmuo:tut] z zzz/w_ҟ3P4727|bmathX8 IkSqS 3ss!`f `adqbR2^^˽%{ZZ%X[3ZZXhL؊Fڹ]fkg? pבϱ dtiY󌋢K>}1ݕ֕z fVF UݍOyxx|<9[; Y_ |O~:~~,܂ZoĄ< M v*llB> o`B_u"E#GR*m}->&8f(V<6=]~\u<&#'?`«D$(='Y 95y KIKY:dF*w/ѤӞ}(E3<33e23ײ<+<}?G)l..78w yyqy'N4;qr; ĂȂB–"ܢb\O;yfٺR2r J\eT*j8eu> .*_XUs yi֥v:ɺzە&W{\kj<}zF4<ոoJdjsϢ8~)3_D4~:4073O__Zd_<Գlb%h緌/Pѿjg~دM~?_Z_%IZaoo@#i@ivP:at8A<-GNeCESD^!qY%)u.N^ɿ ($,J'V"!YJ[zD[nQ!UCFUu{) o͏A:szAo ]6[d[.XNin/X4Bا5ϭ4)esȷ[`xЛ0<1+3)!qlK\Q|TEpϤUb:ڑOrx,H􌌤Lr1l]?O䵟(=y_Er!_he37^.--*pԬQ W>uBEKFUd]ڧ';ZZ-o(kh[j1}l[&`Xx݂{n s>=\yqh˜qd|i3 扻c&&_D}fվלǧX{NYC?65|o{r~lxeq%eWJpYMWf`Ybpx75јҘj/Mm7wF_Jwlq콜\ܝ., #* \TUFRXF!YqNU==5t ƍčcLL')-T,[Y?jGm/RimQiW MCKDL C~ _(rVaYkMIHMfL^90q;ԘiGгUzg,c?s&s;W>pab!pht:eز򱊁UUpb|M%Zjub k o<ځWr3[2[ȻYV^Qy+{_Y->bbwY߳`ϰCG4NjoF_ZDsI/_>*uشڙ/o=ߟ}B6O_0\-v.[[ZrܷgWگݹvD葫g,ۊ# E.ѓRJZQɌruP#@ OEw <$yJt̠g_Jʢ*ʪz,vcfn)ůMT^~hҸ$Lgڢ$+7km[^RG'ygjw.] q͑܋<:~~iAbAk!a.rBR|DMGgHtFfJlm)5AD_I7cX=ؖtH+I=| M5m壾BYbYsZX~>~]C'YO8US V𡰦ȯX]әgBZ*1-WT^9w|v5{/_֨SoBrŵ뇛BcZ[n|)Х{VCwlGA?_y5i2ԗo ߞ|9nvd@d 3XR` W>r84h6,\8f 8 y $h(.P8UA8 Kv9p#r9E0̘=hyae& & x| N"T(x)(*(fb`b %҉ rjK&f֙4] =}<Fwi&hƒ*f6͞5ʝcϻ7_#[ RMH]Gk/H酞 \2X9 O([\=j?4vh/Ewҙ̯["{5oڬ?0r{k>'7o1 ,s_L<$3+n4!'rPaGde);ptjoE[I֙2 5qomnںfSrj̠܃ #F^qv|gX=u_%Y6}*0j?Y =` | AhF>o^A24ahc0>kM6`_ 7{K gK A ?Db6qR2=SC440Wttj WԘY"Yljlc9xpqNQCFJ5" 1'q# ՝w|Ro5?8~tY&VKWolYۼ> *m/ϊ_hv6:N/lAUpٴ8XiTXtXML:com.adobe.xmp 232 729 1 &L, IDATxS[Kn~LftNIϤLt6i6ۀxƲyـ!< x@+!Aȋ,Hc {掳w޽|Wr%j-Xs{愯?SЏo/|WG>C<>Lg <>xzƿ22~b!]E)X|-ބ9ǐ:4S`AO9<(v8~Z<6a0ӹ5r h4az`vN8[puB9N' p8NNNN@bwǂ]_`b1mZ͖NX3Z>iLR?sI׹jSEˤ(XۂO\Wl|nh4jZ¾XJ8kx:k?kՓ bL_>/P8_&ۭ=z%J}###1f5""={rcc#{O]GJ@B)R{ي>k PG666I$BhŋSlp7CMZ=z/XR= \[ccc?~ѪjveYY&ueSNq ʹ#" ih\AulqO߅\8;/5\ͅ.>*pUTT,=_gJNV+N ؂r t }ݾ}`eeEV^v+=* 8CQ ݻwhvj"F-pNt<- _`T<` lS<ÇXdd$@DG+o믦n5O$PWX08 9a0XuZz`]b, I$¾XWWzS,}ӉSEKz@Y=::ʺ% ]]]Gb:50-p:%@.X N1ԀВ1H½(ח&j֦>x˧Ѥ$,§a`*#Pؗ8Fj233NWX+`U+J )Y 2Ԡ_e[@hR3 oAos[M ޠfvݦgݜNI?s.pLJJ JN}LM,--U~6L j/HGPw "#L0[<+ߘ"lc+MX Is$nؗkލ:GP:rz?k5N'n(5_{ D@!ͨ%Ff$ pN CrJ9! B~{<5?ڜ5;蜽U$H.m[Y񡎌"!˨INNfdǗ,z#ƳM:EϺ%?L:(G̦&łSfE ?XMvv6jo3)(IyIIu8?vJ_Nˠ ]ӭfBV\#LjB6@b 3ؙ p\3ݑNI_P"1M-kGh@;--a;S=Vz9FR?S LRaT$@v1&EShB5-Q渞`\΋;X~l;CLRQQ p~@ؓXO:mhh Rbwʺ] fhjj;0F )uSot50Ӟ҅oA`f#P,߷4<<{=wuNh( ! Qv< ;+8UWAnSh }ƀ]H0Eho+t}_bb[j]+ߋhNhkLoϑTח>yV߁|Uy즩8}?5MiQugz<#>yͧwJJ~Tm*)Tgh+DB[6 F|LUD;)8\8\8\8\8\8\8\8\8\8\8\8\iq\tP^]X~˻.JrlllyyyV J4`. ``bidict`` Avoids Reference Cycles ---------------------------------- A careful reader might notice the following... .. testsetup:: from bidict import bidict .. doctest:: >>> fwd = bidict(one=1) >>> inv = fwd.inverse >>> inv.inverse is fwd True ...and worry that a :class:`~bidict.bidict` and its inverse create a reference cycle. If this were true, in CPython this would mean that the memory for a :class:`~bidict.bidict` could not be immediately reclaimed when you retained no more references to it, but rather would have to wait for the next garbage collection to kick in before it could be reclaimed. However, :class:`~bidict.bidict`\s use a :class:`weakref.ref` to store the inverse reference in one direction, avoiding the strong reference cycle. As a result, when you no longer retain any references to a :class:`~bidict.bidict` you create, you can be sure that its refcount in CPython drops to zero, and that its memory will therefore be reclaimed immediately. .. note:: In PyPy this is not an issue, as PyPy doesn't use reference counts. The memory for unreferenced objects in PyPy is only reclaimed when GC kicks in, which is unpredictable. Terminology ----------- - It's intentional that the term "inverse" is used rather than "reverse". Consider a collection of *(k, v)* pairs. Taking the reverse of the collection can only be done if it is ordered, and (as you'd expect) reverses the order of the pairs in the collection. But each original *(k, v)* pair remains in the resulting collection. By contrast, taking the inverse of such a collection neither requires the collection to be ordered nor guarantees any ordering in the result, but rather just replaces every *(k, v)* pair with the inverse pair *(v, k)*. - "keys" and "values" could perhaps more properly be called "primary keys" and "secondary keys" (as in a database), or even "forward keys" and "inverse keys", respectively. :mod:`bidict` sticks with the terms "keys" and "values" for the sake of familiarity and to avoid potential confusion, but technically values are also keys themselves. Concretely, this allows :class:`~bidict.bidict`\s to return a set-like (*dict_keys*) object for :meth:`~bidict.bidict.values`, rather than a non-set-like *dict_values* object. Missing ``bidict``\s in the Standard Library -------------------------------------------- The Python standard library actually contains some examples where :class:`~bidict.bidict`\s could be used for fun and profit (depending on your ideas of fun and profit): - The :mod:`logging` module contains a private ``_levelToName`` dict which maps integer levels like *10* to their string names like *DEBUG*. If I had a nickel for every time I wanted that exposed in a bidirectional map (and as a public attribute, no less), I bet I could afford some better turns of phrase. - The :mod:`dis` module maintains a mapping from opnames to opcodes ``dis.opmap`` and a separate list of opnames indexed by opcode ``dis.opnames``. These could be combined into a single bidict. - Python 3's :mod:`html.entities` module maintains separate ``html.entities.name2codepoint`` and ``html.entities.codepoint2name`` dicts. These could be combined into a single bidict. Caveats ------- Non-Atomic Mutation ^^^^^^^^^^^^^^^^^^^ As with built-in dicts, mutating operations on a :class:`~bidict.bidict` are not atomic. If you need to mutate the same :class:`~bidict.bidict` from different threads, use a `synchronization primitive `__ to coordinate access. [#]_ .. [#] *See also:* [`2 `__], [`3 `__] Equivalent but distinct :class:`~collections.abc.Hashable`\s ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Consider the following: .. doctest:: >>> d = {1: int, 1.0: float} How many items do you expect *d* to contain? The actual result might surprise you: .. doctest:: >>> len(d) 1 And similarly, .. doctest:: >>> dict([(1, int), (1.0, float), (1+0j, complex), (True, bool)]) {1: <... 'bool'>} >>> 1.0 in {True} True (Note that ``1 == 1.0 == 1+0j == True``.) This illustrates that a mapping cannot contain two items with equivalent but distinct keys (and likewise a set cannot contain two equivalent but distinct elements). If an object that is being looked up in a set or mapping is equal to a contained object, the contained object will be found, even if it is distinct. With a :class:`~bidict.bidict`, since values function as keys in the inverse mapping, this behavior occurs in the inverse direction too, and means that a :class:`~bidict.bidict` can end up with a different but equivalent key from the corresponding value in its own inverse: .. doctest:: >>> b = bidict({'false': 0}) >>> b.forceput('FALSE', False) >>> b bidict({'FALSE': False}) >>> b.inverse bidict({0: 'FALSE'}) *nan* as a Key ^^^^^^^^^^^^^^ In CPython, *nan* is especially tricky when used as a dictionary key: .. doctest:: >>> d = {float('nan'): 'nan'} >>> d {nan: 'nan'} >>> d[float('nan')] # doctest: +SKIP Traceback (most recent call last): ... KeyError: nan >>> d[float('nan')] = 'not overwritten' >>> d # doctest: +SKIP {nan: 'nan', nan: 'not overwritten'} In other Python implementations such as PyPy, *nan* behaves just like any other dictionary key. But in CPython, beware of this unexpected behavior, which applies to :class:`~bidict.bidict`\s too. :mod:`bidict` contains no special-case logic for dealing with *nan* as a key, so the behavior will match :class:`dict`'s wherever :mod:`bidict` is running. See e.g. `these docs `__ for more info (search the page for "nan"). ---- For more in this vein, check out :doc:`learning-from-bidict`. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/docs/api.rst0000644000175100001710000000125100000000000014625 0ustar00runnerdockerAPI === This page contains auto-generated documentation from the bidict source code. bidict ------ .. automodule:: bidict :members: :imported-members: :member-order: bysource :special-members: :private-members: :show-inheritance: :undoc-members: :inherited-members: .. autodata:: bidict.RAISE .. autodata:: bidict.DROP_OLD .. autodata:: bidict.DROP_NEW .. autodata:: bidict.ON_DUP_DEFAULT .. autodata:: bidict.ON_DUP_RAISE .. autodata:: bidict.ON_DUP_DROP_OLD .. autofunction:: bidict._iter._iteritems_mapping_or_iterable .. autofunction:: bidict._iter._iteritems_args_kw .. attribute:: __version__ The version of bidict represented as a string. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/docs/basic-usage.rst0000644000175100001710000002365100000000000016247 0ustar00runnerdockerBasic Usage ----------- Let's return to the example from the :doc:`intro`: .. testsetup:: from bidict import bidict .. doctest:: >>> element_by_symbol = bidict(H='hydrogen') As we saw, this behaves just like a dict, but maintains a special :attr:`~bidict.BidictBase.inverse` attribute giving access to inverse items: .. doctest:: >>> element_by_symbol.inverse['helium'] = 'He' >>> del element_by_symbol.inverse['hydrogen'] >>> element_by_symbol bidict({'He': 'helium'}) :class:`bidict.bidict` supports the rest of the :class:`collections.abc.MutableMapping` interface as well: .. doctest:: >>> 'C' in element_by_symbol False >>> element_by_symbol.get('C', 'carbon') 'carbon' >>> element_by_symbol.pop('He') 'helium' >>> element_by_symbol bidict() >>> element_by_symbol.update(Hg='mercury') >>> element_by_symbol bidict({'Hg': 'mercury'}) >>> 'mercury' in element_by_symbol.inverse True >>> element_by_symbol.inverse.pop('mercury') 'Hg' Because inverse items are maintained alongside forward items, referencing a :class:`~bidict.bidict`'s inverse is always a constant-time operation. Values Must Be Hashable +++++++++++++++++++++++ Because you must be able to look up keys by value as well as values by key, values must also be hashable. Attempting to insert an unhashable value will result in an error: .. doctest:: >>> anagrams_by_alphagram = dict(opt=['opt', 'pot', 'top']) >>> bidict(anagrams_by_alphagram) Traceback (most recent call last): ... TypeError: ... So in this example, using a tuple or a frozenset instead of a list would do the trick: .. doctest:: >>> bidict(opt=('opt', 'pot', 'top')) bidict({'opt': ('opt', 'pot', 'top')}) Values Must Be Unique +++++++++++++++++++++ As we know, in a bidirectional map, not only must keys be unique, but values must be unique as well. This has immediate implications for :mod:`bidict`'s API. Consider the following: .. doctest:: >>> b = bidict({'one': 1}) >>> b['two'] = 1 # doctest: +SKIP What should happen next? If the bidict allowed this to succeed, because of the uniqueness-of-values constraint, it would silently clobber the existing item, resulting in: .. doctest:: >>> b # doctest: +SKIP bidict({'two': 1}) This could result in surprises or problems down the line. Instead, bidict raises a :class:`~bidict.ValueDuplicationError` so you have an opportunity to catch this early and resolve the conflict before it causes problems later on: .. doctest:: >>> b['two'] = 1 Traceback (most recent call last): ... ValueDuplicationError: 1 The purpose of this is to be more in line with the `Zen of Python `__, which advises, | *Errors should never pass silently.* | *Unless explicitly silenced.* So if you really just want to clobber any existing items, all you have to do is say so explicitly: .. doctest:: >>> b.forceput('two', 1) >>> b bidict({'two': 1}) Similarly, initializations and :meth:`~bidict.bidict.update` calls that would overwrite the key of an existing value raise an exception too: .. doctest:: >>> bidict({'one': 1, 'uno': 1}) Traceback (most recent call last): ... ValueDuplicationError: 1 >>> b = bidict({'one': 1}) >>> b.update([('uno', 1)]) Traceback (most recent call last): ... ValueDuplicationError: 1 >>> b bidict({'one': 1}) Setting an existing key to a new value does *not* cause an error, and is considered an intentional overwrite of the value associated with the existing key, in keeping with dict's behavior: .. doctest:: >>> b = bidict({'one': 1}) >>> b['one'] = 2 # succeeds >>> b bidict({'one': 2}) >>> b.update([('one', 3), ('one', 4), ('one', 5)]) >>> b bidict({'one': 5}) >>> bidict([('one', 1), ('one', 2)]) bidict({'one': 2}) In summary, when attempting to insert an item whose key duplicates an existing item's, :class:`~bidict.bidict`'s default behavior is to allow the insertion, overwriting the existing item with the new one. When attempting to insert an item whose value duplicates an existing item's, :class:`~bidict.bidict`'s default behavior is to raise. This design naturally falls out of the behavior of Python's built-in dict, and protects against unexpected data loss. One set of alternatives to this behavior is provided by :meth:`~bidict.bidict.forceput` (mentioned above) and :meth:`~bidict.bidict.forceupdate`, which allow you to explicitly overwrite existing keys and values: .. doctest:: >>> b = bidict({'one': 1}) >>> b.forceput('two', 1) >>> b bidict({'two': 1}) >>> b.forceupdate([('three', 1), ('four', 1)]) >>> b bidict({'four': 1}) For even more control, you can use :meth:`~bidict.bidict.put` and :meth:`~bidict.bidict.putall`. These variants allow you to pass an :class:`~bidict.OnDup` instance to specify custom :class:`~bidict.OnDupAction`\s for each type of duplication that can occur. .. doctest:: >>> from bidict import OnDup, RAISE >>> b = bidict({1: 'one'}) >>> b.put(1, 'uno', OnDup(key=RAISE)) Traceback (most recent call last): ... KeyDuplicationError: 2 >>> b bidict({1: 'one'}) :mod:`bidict` provides the :attr:`~bidict.ON_DUP_DEFAULT`, :attr:`~bidict.ON_DUP_RAISE`, and :attr:`~bidict.ON_DUP_DROP_OLD` :class:`~bidict.OnDup` instances for convenience. If no *on_dup* argument is passed, :meth:`~bidict.bidict.put` and :meth:`~bidict.bidict.putall` will use :attr:`~bidict.ON_DUP_RAISE`, providing stricter-by-default alternatives to :meth:`~bidict.bidict.__setitem__` and :meth:`~bidict.bidict.update`. (These defaults complement the looser alternatives provided by :meth:`~bidict.bidict.forceput` and :meth:`~bidict.bidict.forceupdate`.) Key and Value Duplication ~~~~~~~~~~~~~~~~~~~~~~~~~ Note that it's possible for a given item to duplicate the key of one existing item, and the value of another existing item. In the following example, the key of the third item duplicates the first item's key, and the value of the third item dulicates the second item's value: .. code-block:: python >>> b.putall([(1, 2), (3, 4), (1, 4)], OnDup(key=...)) What should happen next? Keep in mind, the active :class:`~bidict.OnDup` may specify one :class:`~bidict.OnDupAction` for :attr:`key duplication ` and a different :class:`~bidict.OnDupAction` for :attr:`value duplication `. To account for this, :class:`~bidict.OnDup` allows you to use its :attr:`~bidict.OnDup.kv` field to indicate how you want to handle this case without ambiguity: .. doctest:: >>> from bidict import DROP_OLD >>> on_dup = OnDup(key=DROP_OLD, val=RAISE, kv=RAISE) >>> b.putall([(1, 2), (3, 4), (1, 4)], on_dup) Traceback (most recent call last): ... KeyAndValueDuplicationError: (1, 4) If not specified, *kv* defaults to whatever was provided for *val*. Note that repeated insertions of the same item are construed as a no-op and will not raise, no matter what the active :class:`~bidict.OnDup` is: .. doctest:: >>> b = bidict({1: 'one'}) >>> b.put(1, 'one') # no-op, not a DuplicationError >>> b.putall([(2, 'two'), (2, 'two')]) # The repeat (2, 'two') is also a no-op. >>> sorted(b.items()) [(1, 'one'), (2, 'two')] See the :ref:`extending:\`\`YoloBidict\`\` Recipe` for another way to customize this behavior. Updates Fail Clean ++++++++++++++++++ If an update to a :class:`~bidict.bidict` fails, you can be sure that it fails clean. In other words, a :class:`~bidict.bidict` will never apply only part of an update that ultimately fails, without restoring itself to the state it was in before processing the update: .. doctest:: >>> b = bidict({1: 'one', 2: 'two'}) >>> b.putall([(3, 'three'), (1, 'uno')]) Traceback (most recent call last): ... KeyDuplicationError: 1 >>> # (1, 'uno') was the problem... >>> b # ...but (3, 'three') was not added either: bidict({1: 'one', 2: 'two'}) Order Matters +++++++++++++ Performing a bulk insert operation – i.e. passing multiple items to :meth:`~bidict.BidictBase.__init__`, :meth:`~bidict.bidict.update`, :meth:`~bidict.bidict.forceupdate`, or :meth:`~bidict.bidict.putall` – is like inserting each of those items individually in sequence. [#fn-fail-clean]_ Therefore, the order of the items provided to the bulk insert operation is significant to the result: .. doctest:: >>> b = bidict({0: 0, 1: 2}) >>> b.forceupdate([(2, 0), (0, 1), (0, 0)]) >>> # 1. (2, 0) overwrites (0, 0) -> bidict({2: 0, 1: 2}) >>> # 2. (0, 1) is added -> bidict({2: 0, 1: 2, 0: 1}) >>> # 3. (0, 0) overwrites (0, 1) and (2, 0) -> bidict({0: 0, 1: 2}) >>> sorted(b.items()) [(0, 0), (1, 2)] >>> b = bidict({0: 0, 1: 2}) # as before >>> # Give the same items to forceupdate() but in a different order: >>> b.forceupdate([(0, 1), (0, 0), (2, 0)]) >>> # 1. (0, 1) overwrites (0, 0) -> bidict({0: 1, 1: 2}) >>> # 2. (0, 0) overwrites (0, 1) -> bidict({0: 0, 1: 2}) >>> # 3. (2, 0) overwrites (0, 0) -> bidict({1: 2, 2: 0}) >>> sorted(b.items()) # different items! [(1, 2), (2, 0)] .. [#fn-fail-clean] Albeit with the extremely important advantage of :ref:`failing clean `. Interop +++++++ :class:`~bidict.bidict`\s interoperate well with other types of mappings. For example, they support (efficient) polymorphic equality testing: .. doctest:: >>> bidict(a=1) == dict(a=1) True And converting back and forth works as expected (assuming no :ref:`value duplication `): .. doctest:: >>> dict(bidict(a=1)) {'a': 1} >>> bidict(dict(a=1)) bidict({'a': 1}) See the :ref:`other-bidict-types:Polymorphism` section for more interoperability documentation. ---- Hopefully :mod:`bidict` feels right at home among the Python built-ins you already know. Proceed to :doc:`other-bidict-types` for documentation on the remaining bidict variants. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/docs/changelog.rst0000644000175100001710000010647100000000000016015 0ustar00runnerdocker.. Forward declarations for all the custom interpreted text roles that Sphinx defines and that are used below. This helps Sphinx-unaware tools (e.g. rst2html, PyPI's and GitHub's renderers, etc.). .. role:: doc .. role:: ref Changelog ========= Release Notifications --------------------- .. duplicated in README.rst (would use `.. include::` but GitHub doesn't understand it) .. image:: https://img.shields.io/badge/libraries.io-subscribe-5BC0DF.svg :target: https://libraries.io/pypi/bidict :alt: Follow on libraries.io Tip: Subscribe to releases `on GitHub `__ or `libraries.io `__ to be notified when new versions of ``bidict`` are released. 0.21.3 (2021-09-05) ------------------- - All bidicts now provide the :meth:`~bidict.BidictBase.equals_order_sensitive` method, not just :class:`bidict.OrderedBidict`\s. Since support for Python < 3.6 was dropped in v0.21.0, non-:class:`Ordered ` bidicts preserve a deterministic ordering on all supported Python versions, so all bidicts can now provide :meth:`~bidict.BidictBase.equals_order_sensitive`. - Take better advantage of the fact that dicts are reversible in Python 3.8+. This allows even non-:class:`Ordered ` bidicts to efficiently provide a :meth:`~bidict.BidictBase.__reversed__` implementation, which they now do. As a result, if you are using Python 3.8+, :class:`~bidict.frozenbidict` now gives you everything that :class:`~bidict.FrozenOrderedBidict` gives you with less space overhead. - Drop `setuptools_scm `__ as a ``setup_requires`` dependency. - Remove the ``bidict.__version_info__`` attribute. 0.21.2 (2020-09-07) ------------------- - Include `py.typed `__ file to mark :mod:`bidict` as type hinted. 0.21.1 (2020-09-07) ------------------- This release was yanked and replaced with the 0.21.2 release, which actually provides the intended changes. 0.21.0 (2020-08-22) ------------------- - :mod:`bidict` now provides `type hints `__! ⌨️ ✅ Adding type hints to :mod:`bidict` poses particularly interesting challenges due to the combination of generic types, dynamically-generated types (such as :ref:`inverse bidict classes ` and :func:`namedbidicts `), and complicating optimizations such as the use of slots and weakrefs. It didn't take long to hit bugs and missing features in the state of the art for type hinting in Python today, e.g. missing higher-kinded types support (`python/typing#548 `__), too-narrow type hints for :class:`collections.abc.Mapping` (`python/typeshed#4435 `__), a :class:`typing.Generic` bug in Python 3.6 (`BPO-41451 `__), etc. That said, this release should provide a solid foundation for code using :mod:`bidict` that enables static type checking. As always, if you spot any opportunities to improve :mod:`bidict` (including its new type hints), please don't hesitate to submit a PR! - Add :class:`bidict.MutableBidirectionalMapping` ABC. The :ref:`other-bidict-types:Bidict Types Diagram` has been updated accordingly. - Drop support for Python 3.5, which reaches end of life on 2020-09-13, represents a tiny percentage of bidict downloads on `PyPI Stats `__, and lacks support for `variable type hint syntax `__, `ordered dicts `__, and :attr:`object.__init_subclass__`. - Remove the no-longer-needed ``bidict.compat`` module. - Move :ref:`inverse bidict class access ` from a property to an attribute set in :attr:`~bidict.BidictBase.__init_subclass__`, to save function call overhead on repeated access. - :meth:`bidict.OrderedBidictBase.__iter__` no longer accepts a ``reverse`` keyword argument so that it matches the signature of :meth:`container.__iter__`. - Set the ``__module__`` attribute of various :mod:`bidict` types (using :func:`sys._getframe` when necessary) so that private, internal modules are not exposed e.g. in classes' repr strings. - :func:`~bidict.namedbidict` now immediately raises :class:`TypeError` if the provided ``base_type`` does not provide ``_isinv`` or :meth:`~object.__getstate__`, rather than succeeding with a class whose instances may raise :class:`AttributeError` when these attributes are accessed. 0.20.0 (2020-07-23) ------------------- The following breaking changes are expected to affect few if any users. Remove APIs deprecated in the previous release: - ``bidict.OVERWRITE`` and ``bidict.IGNORE``. - The ``on_dup_key``, ``on_dup_val``, and ``on_dup_kv`` arguments of :meth:`~bidict.bidict.put` and :meth:`~bidict.bidict.putall`. - The ``on_dup_key``, ``on_dup_val``, and ``on_dup_kv`` :class:`~bidict.bidict` class attributes. - Remove :meth:`bidict.BidirectionalMapping.__subclasshook__` due to lack of use and maintenance cost. Fixes a bug introduced in 0.15.0 that caused any class with an ``inverse`` attribute to be incorrectly considered a subclass of :class:`collections.abc.Mapping`. `#111 `__ 0.19.0 (2020-01-09) ------------------- - Drop support for Python 2 :ref:`as promised in v0.18.2 `. The ``bidict.compat`` module has been pruned accordingly. This makes bidict more efficient on Python 3 and enables further improvement to bidict in the future. - Deprecate ``bidict.OVERWRITE`` and ``bidict.IGNORE``. A :class:`UserWarning` will now be emitted if these are used. :attr:`bidict.DROP_OLD` and :attr:`bidict.DROP_NEW` should be used instead. - Rename ``DuplicationPolicy`` to :class:`~bidict.OnDupAction` (and implement it via an :class:`~enum.Enum`). An :class:`~bidict.OnDupAction` may be one of :attr:`~bidict.RAISE`, :attr:`~bidict.DROP_OLD`, or :attr:`~bidict.DROP_NEW`. - Expose the new :class:`~bidict.OnDup` class to contain the three :class:`~bidict.OnDupAction`\s that should be taken upon encountering the three kinds of duplication that can occur (*key*, *val*, *kv*). - Provide the :attr:`~bidict.ON_DUP_DEFAULT`, :attr:`~bidict.ON_DUP_RAISE`, and :attr:`~bidict.ON_DUP_DROP_OLD` :class:`~bidict.OnDup` convenience instances. - Deprecate the ``on_dup_key``, ``on_dup_val``, and ``on_dup_kv`` arguments of :meth:`~bidict.bidict.put` and :meth:`~bidict.bidict.putall`. A :class:`UserWarning` will now be emitted if these are used. These have been subsumed by the new *on_dup* argument, which takes an :class:`~bidict.OnDup` instance. Use it like this: ``bi.put(1, 2, OnDup(key=RAISE, val=...))``. Or pass one of the instances already provided, such as :attr:`~bidict.ON_DUP_DROP_OLD`. Or just don't pass an *on_dup* argument to use the default value of :attr:`~bidict.ON_DUP_RAISE`. The :ref:`basic-usage:Values Must Be Unique` docs have been updated accordingly. - Deprecate the ``on_dup_key``, ``on_dup_val``, and ``on_dup_kv`` :class:`~bidict.bidict` class attributes. A :class:`UserWarning` will now be emitted if these are used. These have been subsumed by the new :attr:`~bidict.bidict.on_dup` class attribute, which takes an :class:`~bidict.OnDup` instance. See the updated :doc:`extending` docs for example usage. - Improve the more efficient implementations of :meth:`~bidict.BidirectionalMapping.keys`, :meth:`~bidict.BidirectionalMapping.values`, and :meth:`~bidict.BidirectionalMapping.items`, and now also provide a more efficient implementation of :meth:`~bidict.BidirectionalMapping.__iter__` by delegating to backing :class:`dict`\s in the bidict types for which this is possible. - Move :meth:`bidict.BidictBase.values` to :meth:`bidict.BidirectionalMapping.values`, since the implementation is generic. - No longer use ``__all__`` in :mod:`bidict`'s ``__init__.py``. 0.18.4 (2020-11-02) ------------------- - Backport fix from v0.20.0 that removes :meth:`bidict.BidirectionalMapping.__subclasshook__` due to lack of use and maintenance cost. 0.18.3 (2019-09-22) ------------------- - Improve validation of names passed to :func:`~bidict.namedbidict`: Use :meth:`str.isidentifier` on Python 3, and a better regex on Python 2. - On Python 3, set :attr:`~definition.__qualname__` on :func:`~bidict.namedbidict` classes based on the provided ``typename`` argument. 0.18.2 (2019-09-08) ------------------- - Warn that Python 2 support will be dropped in a future release when Python 2 is detected. 0.18.1 (2019-09-03) ------------------- - Fix a regression introduced by the memory optimizations added in 0.15.0 which caused :func:`deepcopied ` and :func:`unpickled ` bidicts to have their inverses set incorrectly. `#94 `__ 0.18.0 (2019-02-14) ------------------- - Rename ``bidict.BidirectionalMapping.inv`` to :attr:`~bidict.BidirectionalMapping.inverse` and make :attr:`bidict.BidictBase.inv` an alias for :attr:`~bidict.BidictBase.inverse`. `#86 `__ - :meth:`bidict.BidirectionalMapping.__subclasshook__` now requires an ``inverse`` attribute rather than an ``inv`` attribute for a class to qualify as a virtual subclass. This breaking change is expected to affect few if any users. - Add Python 2/3-compatible ``bidict.compat.collections_abc`` alias. - Stop testing Python 3.4 on CI, and warn when Python 3 < 3.5 is detected rather than Python 3 < 3.3. Python 3.4 reaches `end of life `__ on 2019-03-18. As of January 2019, 3.4 represents only about 3% of bidict downloads on `PyPI Stats `__. 0.17.5 (2018-11-19) ------------------- Improvements to performance and delegation logic, with minor breaking changes to semi-private APIs. - Remove the ``__delegate__`` instance attribute added in the previous release. It was overly general and not worth the cost. Instead of checking ``self.__delegate__`` and delegating accordingly each time a possibly-delegating method is called, revert back to using "delegated-to-fwdm" mixin classes (now found in ``bidict._delegating_mixins``), and resurrect a mutable bidict parent class that omits the mixins as :class:`bidict.MutableBidict`. - Rename ``__repr_delegate__`` to :class:`~bidict.BidictBase._repr_delegate`. 0.17.4 (2018-11-14) ------------------- Minor code, interop, and (semi-)private API improvements. - :class:`~bidict.OrderedBidict` optimizations and code improvements. Use ``bidict``\s for the backing ``_fwdm`` and ``_invm`` mappings, obviating the need to store key and value data in linked list nodes. - Refactor proxied- (i.e. delegated-) to-``_fwdm`` logic for better composability and interoperability. Drop the ``_Proxied*`` mixin classes and instead move their methods into :class:`~bidict.BidictBase`, which now checks for an object defined by the ``BidictBase.__delegate__`` attribute. The ``BidictBase.__delegate__`` object will be delegated to if the method is available on it, otherwise a default implementation (e.g. inherited from :class:`~collections.abc.Mapping`) will be used otherwise. Subclasses may set ``__delegate__ = None`` to opt out. Consolidate ``_MutableBidict`` into :class:`bidict.bidict` now that the dropped mixin classes make it unnecessary. - Change ``__repr_delegate__`` to simply take a type like :class:`dict` or :class:`list`. - Upgrade to latest major `sortedcontainers `__ version (from v1 to v2) for the :ref:`extending:\`\`SortedBidict\`\` Recipes`. - ``bidict.compat.{view,iter}{keys,values,items}`` on Python 2 no longer assumes the target object implements these methods, as they're not actually part of the :class:`~collections.abc.Mapping` interface, and provides fallback implementations when the methods are unavailable. This allows the :ref:`extending:\`\`SortedBidict\`\` Recipes` to continue to work with sortedcontainers v2 on Python 2. 0.17.3 (2018-09-18) ------------------- - Improve packaging by adding a pyproject.toml and by including more supporting files in the distribution. `#81 `__ - Drop pytest-runner and support for running tests via ``python setup.py test`` in preference to ``pytest`` or ``python -m pytest``. 0.17.2 (2018-04-30) ------------------- Memory usage improvements +++++++++++++++++++++++++ - Use less memory in the linked lists that back :class:`~bidict.OrderedBidict`\s by storing node data unpacked rather than in (key, value) tuple objects. 0.17.1 (2018-04-28) ------------------- Bugfix Release ++++++++++++++ Fix a regression in 0.17.0 that could cause erroneous behavior when updating items of an :class:`~bidict.Orderedbidict`'s inverse, e.g. ``some_ordered_bidict.inv[foo] = bar``. 0.17.0 (2018-04-25) ------------------- Speedups and memory usage improvements ++++++++++++++++++++++++++++++++++++++ - Pass :meth:`~bidict.bidict.keys`, :meth:`~bidict.bidict.values`, and :meth:`~bidict.bidict.items` calls (as well as their ``iter*`` and ``view*`` counterparts on Python 2) through to the backing ``_fwdm`` and ``_invm`` dicts so that they run as fast as possible (i.e. at C speed on CPython), rather than using the slower implementations inherited from :class:`collections.abc.Mapping`. - Use weakrefs in the linked lists that back :class:`~bidict.OrderedBidict`\s to avoid creating strong reference cycles. Memory for an ordered bidict that you create can now be reclaimed in CPython as soon as you no longer hold any references to it, rather than having to wait until the next garbage collection. `#71 `__ Misc ++++ - Add ``bidict.__version_info__`` attribute to complement :attr:`bidict.__version__`. 0.16.0 (2018-04-06) ------------------- Minor code and efficiency improvements to :func:`~bidict.inverted` and :func:`~bidict._iter._iteritems_args_kw` (formerly ``bidict.pairs()``). Minor Breaking API Changes ++++++++++++++++++++++++++ The following breaking changes are expected to affect few if any users. - Rename ``bidict.pairs()`` → ``bidict._util._iteritems_args_kw``. 0.15.0 (2018-03-29) ------------------- Speedups and memory usage improvements ++++++++++++++++++++++++++++++++++++++ - Use :ref:`slots` to speed up bidict attribute access and reduce memory usage. On Python 3, instantiating a large number of bidicts now uses ~57% the amount of memory that it used before, and on Python 2 only ~33% the amount of memory that it used before, in a simple but representative `benchmark `__. - Use weakrefs to refer to a bidict's inverse internally, no longer creating a strong reference cycle. Memory for a bidict that you create can now be reclaimed in CPython as soon as you no longer hold any references to it, rather than having to wait for the next garbage collection. See the new :ref:`addendum:\`\`bidict\`\` Avoids Reference Cycles` documentation. `#24 `__ - Make :func:`bidict.BidictBase.__eq__` significantly more speed- and memory-efficient when comparing to a non-:class:`dict` :class:`~collections.abc.Mapping`. (``Mapping.__eq__()``\'s inefficient implementation will now never be used.) The implementation is now more reusable as well. - Make :func:`bidict.OrderedBidictBase.__iter__` as well as equality comparison slightly faster for ordered bidicts. Minor Bugfixes ++++++++++++++ - :func:`~bidict.namedbidict` now verifies that the provided ``keyname`` and ``valname`` are distinct, raising :class:`ValueError` if they are equal. - :func:`~bidict.namedbidict` now raises :class:`TypeError` if the provided ``base_type`` is not a :class:`~bidict.BidirectionalMapping`. - If you create a custom bidict subclass whose ``_fwdm_cls`` differs from its ``_invm_cls`` (as in the ``FwdKeySortedBidict`` example from the :ref:`extending:\`\`SortedBidict\`\` Recipes`), the inverse bidirectional mapping type (with ``_fwdm_cls`` and ``_invm_cls`` swapped) is now correctly computed and used automatically for your custom bidict's :attr:`~bidict.BidictBase.inverse` bidict. Miscellaneous +++++++++++++ - Classes no longer have to provide an ``__inverted__`` attribute to be considered virtual subclasses of :class:`~bidict.BidirectionalMapping`. - If :func:`bidict.inverted` is passed an object with an ``__inverted__`` attribute, it now ensures it is :func:`callable` before returning the result of calling it. - :func:`~bidict.BidictBase.__repr__` no longer checks for a ``__reversed__`` method to determine whether to use an ordered or unordered-style repr. It now calls the new ``__repr_delegate__`` instead (which may be overridden if needed), for better composability. Minor Breaking API Changes ++++++++++++++++++++++++++ The following breaking changes are expected to affect few if any users. - Split back out the :class:`~bidict.BidictBase` class from :class:`~bidict.frozenbidict` and :class:`~bidict.OrderedBidictBase` from :class:`~bidict.FrozenOrderedBidict`, reverting the merging of these in 0.14.0. Having e.g. ``issubclass(bidict, frozenbidict) == True`` was confusing, so this change restores ``issubclass(bidict, frozenbidict) == False``. See the updated :ref:`other-bidict-types:Bidict Types Diagram` and :ref:`other-bidict-types:Polymorphism` documentation. - Rename: - ``bidict.BidictBase.fwdm`` → ``._fwdm`` - ``bidict.BidictBase.invm`` → ``._invm`` - ``bidict.BidictBase.fwd_cls`` → ``._fwdm_cls`` - ``bidict.BidictBase.inv_cls`` → ``._invm_cls`` - ``bidict.BidictBase.isinv`` → ``._isinv`` Though overriding ``_fwdm_cls`` and ``_invm_cls`` remains supported (see :doc:`extending`), this is not a common enough use case to warrant public names. Most users do not need to know or care about any of these. - The :attr:`~bidict.RAISE`, ``OVERWRITE``, and ``IGNORE`` duplication policies are no longer available as attributes of ``DuplicationPolicy``, and can now only be accessed as attributes of the :mod:`bidict` module namespace, which was the canonical way to refer to them anyway. It is now no longer possible to create an infinite chain like ``DuplicationPolicy.RAISE.RAISE.RAISE...`` - Make ``bidict.pairs()`` and :func:`bidict.inverted` no longer importable from ``bidict.util``, and now only importable from the top-level :mod:`bidict` module. (``bidict.util`` was renamed ``bidict._util``.) - Pickling ordered bidicts now requires at least version 2 of the pickle protocol. If you are using Python 3, :attr:`pickle.DEFAULT_PROTOCOL` is 3 anyway, so this will not affect you. However if you are using in Python 2, :attr:`~pickle.DEFAULT_PROTOCOL` is 0, so you must now explicitly specify the version in your :func:`pickle.dumps` calls, e.g. ``pickle.dumps(ob, 2)``. 0.14.2 (2017-12-06) ------------------- - Make initializing (or updating an empty bidict) from only another :class:`~bidict.BidirectionalMapping` more efficient by skipping unnecessary duplication checking. - Fix accidental ignoring of specified ``base_type`` argument when (un)pickling a :func:`~bidict.namedbidict`. - Fix incorrect inversion of ``some_named_bidict.inv._for`` and ``some_named_bidict.inv._for``. - Only warn when an unsupported Python version is detected (e.g. Python < 2.7) rather than raising :class:`AssertionError`. 0.14.1 (2017-11-28) ------------------- - Fix a bug introduced in 0.14.0 where hashing a :class:`~bidict.frozenbidict`\’s inverse (e.g. ``f = frozenbidict(); {f.inv: '...'}``) would cause an ``AttributeError``. - Fix a bug introduced in 0.14.0 for Python 2 users where attempting to call ``viewitems()`` would cause a ``TypeError``. `#48 `__ 0.14.0 (2017-11-20) ------------------- - Fix a bug where :class:`~bidict.bidict`\’s default *on_dup_kv* policy was set to :attr:`~bidict.RAISE`, rather than matching whatever *on_dup_val* policy was in effect as was :ref:`documented `. - Fix a bug that could happen when using Python's optimization (``-O``) flag that could leave an ordered bidict in an inconsistent state when dealing with duplicated, overwritten keys or values. If you do not use optimizations (specifically, skipping ``assert`` statements), this would not have affected you. - Fix a bug introduced by the optimizations in 0.13.0 that could cause a frozen bidict that compared equal to another mapping to have a different hash value from the other mapping, violating Python's object model. This would only have affected you if you were inserting a frozen bidict and some other immutable mapping that it compared equal to into the same set or mapping. - Add :meth:`~bidict.OrderedBidictBase.equals_order_sensitive`. - Reduce the memory usage of ordered bidicts. - Make copying of ordered bidicts faster. - Improvements to tests and CI, including: - Test on Windows - Test with PyPy3 - Test with CPython 3.7-dev - Test with optimization flags - Require pylint to pass Breaking API Changes ++++++++++++++++++++ This release includes multiple API simplifications and improvements. - Rename: - ``orderedbidict`` → :class:`~bidict.OrderedBidict` - ``frozenorderedbidict`` → :class:`~bidict.FrozenOrderedBidict` so that these now match the case of :class:`collections.OrderedDict`. The names of the :class:`~bidict.bidict`, :func:`~bidict.namedbidict`, and :class:`~bidict.frozenbidict` classes have been retained as all-lowercase so that they continue to match the case of :class:`dict`, :func:`~collections.namedtuple`, and :class:`frozenset`, respectively. - The ``ON_DUP_VAL`` duplication policy value for *on_dup_kv* has been removed. Use ``None`` instead. - Merge :class:`~bidict.frozenbidict` and ``BidictBase`` together and remove ``BidictBase``. :class:`~bidict.frozenbidict` is now the concrete base class that all other bidict types derive from. See the updated :ref:`other-bidict-types:Bidict Types Diagram`. - Merge :class:`~bidict.frozenbidict` and ``FrozenBidictBase`` together and remove ``FrozenBidictBase``. See the updated :ref:`other-bidict-types:Bidict Types Diagram`. - Merge ``frozenorderedbidict`` and ``OrderedBidictBase`` together into a single :class:`~bidict.FrozenOrderedBidict` class and remove ``OrderedBidictBase``. :class:`~bidict.OrderedBidict` now extends :class:`~bidict.FrozenOrderedBidict` to add mutable behavior. See the updated :ref:`other-bidict-types:Bidict Types Diagram`. - Make :meth:`~bidict.OrderedBidictBase.__eq__` always perform an order-insensitive equality test, even if the other mapping is ordered. Previously, :meth:`~bidict.OrderedBidictBase.__eq__` was only order-sensitive for other ``OrderedBidictBase`` subclasses, and order-insensitive otherwise. Use the new :meth:`~bidict.OrderedBidictBase.equals_order_sensitive` method for order-sensitive equality comparison. - ``orderedbidict._should_compare_order_sensitive()`` has been removed. - ``frozenorderedbidict._HASH_NITEMS_MAX`` has been removed. Since its hash value must be computed from all contained items (so that hash results are consistent with equality comparisons against unordered mappings), the number of items that influence the hash value should not be limitable. - ``frozenbidict._USE_ITEMSVIEW_HASH`` has been removed, and ``frozenbidict.compute_hash()`` now uses ``collections.ItemsView._hash()`` to compute the hash always, not just when running on PyPy. Override ``frozenbidict.compute_hash()`` to return ``hash(frozenset(iteritems(self)))`` if you prefer the old default behavior on CPython, which takes linear rather than constant space, but which uses the ``frozenset_hash`` routine (implemented in ``setobject.c``) rather than the pure Python ``ItemsView._hash()`` routine. - ``loosebidict`` and ``looseorderedbidict`` have been removed. A simple recipe to implement equivalents yourself is now given in :doc:`extending`. - Rename ``FrozenBidictBase._compute_hash()`` → ``frozenbidict.compute_hash()``. - Rename ``DuplicationBehavior`` → ``DuplicationPolicy``. - Rename: - ``BidictBase._fwd_class`` → ``.fwd_cls`` - ``BidictBase._inv_class`` → ``.inv_cls`` - ``BidictBase._on_dup_key`` → ``on_dup_key`` - ``BidictBase._on_dup_val`` → ``on_dup_val`` - ``BidictBase._on_dup_kv`` → ``on_dup_kv`` 0.13.1 (2017-03-15) ------------------- - Fix regression introduced by the new :meth:`~bidict.BidirectionalMapping.__subclasshook__` functionality in 0.13.0 so that ``issubclass(OldStyleClass, BidirectionalMapping)`` once again works with old-style classes, returning ``False`` rather than raising :class:`AttributeError` `#41 `__ 0.13.0 (2017-01-19) ------------------- - Support Python 3.6. (Earlier versions of bidict should work fine on 3.6, but it is officially supported starting in this version.) - :class:`~bidict.BidirectionalMapping` has been refactored into an abstract base class, following the way :class:`collections.abc.Mapping` works. The concrete method implementations it used to provide have been moved into a new ``BidictBase`` subclass. :class:`~bidict.BidirectionalMapping` now also implements :meth:`~bidict.BidirectionalMapping.__subclasshook__`, so any class that provides a conforming set of attributes (enumerated in :attr:`~bidict.BidirectionalMapping._subclsattrs`) will be considered a :class:`~bidict.BidirectionalMapping` subclass automatically. - ``OrderedBidirectionalMapping`` has been renamed to ``OrderedBidictBase``, to better reflect its function. (It is not an ABC.) - A new ``FrozenBidictBase`` class has been factored out of :class:`~bidict.frozenbidict` and :class:`frozenorderedbidict `. This implements common behavior such as caching the result of ``__hash__`` after the first call. - The hash implementations of :class:`~bidict.frozenbidict` and :class:`frozenorderedbidict `. have been reworked to improve performance and flexibility. :class:`frozenorderedbidict `\’s hash implementation is now order-sensitive. See ``frozenbidict._compute_hash()`` and ``frozenorderedbidict._compute_hash`` for more documentation of the changes, including the new ``frozenbidict._USE_ITEMSVIEW_HASH`` and ``frozenorderedbidict._HASH_NITEMS_MAX`` attributes. If you have an interesting use case that requires overriding these, or suggestions for an alternative implementation, please `share your feedback `__. - Add ``_fwd_class`` and ``_inv_class`` attributes representing the backing :class:`~collections.abc.Mapping` types used internally to store the forward and inverse dictionaries, respectively. This allows creating custom bidict types with extended functionality simply by overriding these attributes in a subclass. See the new :doc:`extending` documentation for examples. - Pass any parameters passed to :meth:`~bidict.bidict.popitem` through to ``_fwd.popitem`` for greater extensibility. - More concise repr strings for empty bidicts. e.g. ``bidict()`` rather than ``bidict({})`` and ``orderedbidict()`` rather than ``orderedbidict([])``. - Add ``bidict.compat.PYPY`` and remove unused ``bidict.compat.izip_longest``. 0.12.0 (2016-07-03) ------------------- - New/renamed exceptions: - :class:`~bidict.KeyDuplicationError` - :class:`~bidict.ValueDuplicationError` - :class:`~bidict.KeyAndValueDuplicationError` - :class:`~bidict.DuplicationError` (base class for the above) - :func:`~bidict.bidict.put` now accepts ``on_dup_key``, ``on_dup_val``, and ``on_dup_kv`` keyword args which allow you to override the default policy when the key or value of a given item duplicates any existing item's. These can take the following values: - :attr:`~bidict.RAISE` - ``OVERWRITE`` - ``IGNORE`` ``on_dup_kv`` can also take ``ON_DUP_VAL``. If not provided, :func:`~bidict.bidict.put` uses the :attr:`~bidict.RAISE` policy by default. - New :func:`~bidict.bidict.putall` method provides a bulk :func:`~bidict.bidict.put` API, allowing you to override the default duplication handling policy that :func:`~bidict.bidict.update` uses. - :func:`~bidict.bidict.update` now fails clean, so if an :func:`~bidict.bidict.update` call raises a :class:`~bidict.DuplicationError`, you can now be sure that none of the given items was inserted. Previously, all of the given items that were processed before the one causing the failure would have been inserted, and no facility was provided to recover which items were inserted and which weren't, nor to revert any changes made by the failed :func:`~bidict.bidict.update` call. The new behavior makes it easier to reason about and control the effects of failed :func:`~bidict.bidict.update` calls. The new :func:`~bidict.bidict.putall` method also fails clean. Internally, this is implemented by storing a log of changes made while an update is being processed, and rolling back the changes when one of them is found to cause an error. This required reimplementing :class:`orderedbidict ` on top of two dicts and a linked list, rather than two OrderedDicts, since :class:`~collections.OrderedDict` does not expose its backing linked list. - :func:`orderedbidict.move_to_end() ` now works on Python < 3.2 as a result of the new :class:`orderedbidict ` implementation. - Add - ``bidict.compat.viewkeys`` - ``bidict.compat.viewvalues`` - ``bidict.compat.iterkeys`` - ``bidict.compat.itervalues`` - ``bidict.compat.izip`` - ``bidict.compat.izip_longest`` to complement the existing ``bidict.compat.iteritems`` and ``bidict.compat.viewitems`` compatibility helpers. - More efficient implementations of ``bidict.pairs()``, :func:`~bidict.inverted`, and :func:`~bidict.BidictBase.copy`. - Implement :func:`~bidict.BidictBase.__copy__` for use with the :mod:`copy` module. - Fix issue preventing a client class from inheriting from ``loosebidict``. `#34 `__ - Add benchmarking to tests. - Drop official support for CPython 3.3. (It may continue to work, but is no longer being tested.) Breaking API Changes ++++++++++++++++++++ - Rename ``KeyExistsException`` → :class:`~bidict.KeyDuplicationError` and ``ValueExistsException`` → :class:`~bidict.ValueDuplicationError`. - When overwriting the key of an existing value in an :class:`orderedbidict `, the position of the existing item is now preserved, overwriting the key of the existing item in place, rather than moving the item to the end. This now matches the behavior of overwriting the value of an existing key, which has always preserved the position of the existing item. (If inserting an item whose key duplicates that of one existing item and whose value duplicates that of another, the existing item whose value is duplicated is still dropped, and the existing item whose key is duplicated still gets its value overwritten in place, as before.) For example: .. code:: python >>> from bidict import orderedbidict # doctest: +SKIP >>> o = orderedbidict([(0, 1), (2, 3)]) # doctest: +SKIP >>> o.forceput(4, 1) # doctest: +SKIP previously would have resulted in: .. code:: python >>> o # doctest: +SKIP orderedbidict([(2, 3), (4, 1)]) but now results in: .. code:: python >>> o # doctest: +SKIP orderedbidict([(4, 1), (2, 3)]) 0.11.0 (2016-02-05) ------------------- - Add :class:`orderedbidict `, ``looseorderedbidict``, and :class:`frozenorderedbidict `. - Add :doc:`code-of-conduct`. - Drop official support for pypy3. (It still may work but is no longer being tested. Support may be added back once pypy3 has made more progress.) 0.10.0.post1 (2015-12-23) ------------------------- - Minor documentation fixes and improvements. 0.10.0 (2015-12-23) ------------------- - Remove several features in favor of keeping the API simpler and the code more maintainable. - In the interest of protecting data safety more proactively, by default bidict now raises an error on attempting to insert a non-unique value, rather than allowing its associated key to be silently overwritten. See discussion in `#21 `__. - New :meth:`~bidict.bidict.forceupdate` method provides a bulk :meth:`~bidict.bidict.forceput` operation. - Fix bugs in :attr:`~bidict.bidict.pop` and :attr:`~bidict.bidict.setdefault` which could leave a bidict in an inconsistent state. Breaking API Changes ++++++++++++++++++++ - Remove ``bidict.__invert__``, and with it, support for the ``~b`` syntax. Use :attr:`~bidict.BidictBase.inv` instead. `#19 `__ - Remove support for the slice syntax. Use ``b.inv[val]`` rather than ``b[:val]``. `#19 `__ - Remove ``bidict.invert``. Use :attr:`~bidict.BidictBase.inv` rather than inverting a bidict in place. `#20 `__ - Raise ``ValueExistsException`` when attempting to insert a mapping with a non-unique key. `#21 `__ - Rename ``collapsingbidict`` → ``loosebidict`` now that it suppresses ``ValueExistsException`` rather than the less general ``CollapseException``. `#21 `__ - ``CollapseException`` has been subsumed by ``ValueExistsException``. `#21 `__ - :meth:`~bidict.bidict.put` now raises ``KeyExistsException`` when attempting to insert an already-existing key, and ``ValueExistsException`` when attempting to insert an already-existing value. 0.9.0.post1 (2015-06-06) ------------------------ - Fix metadata missing in the 0.9.0rc0 release. 0.9.0rc0 (2015-05-30) --------------------- - Add this changelog, `Contributors' Guide `__, `Gitter chat room `__, and other community-oriented improvements. - Adopt Pytest. - Add property-based tests via `hypothesis `__. - Other code, tests, and docs improvements. Breaking API Changes ++++++++++++++++++++ - Move ``bidict.iteritems()`` and ``bidict.viewitems()`` to new ``bidict.compat`` module. - Move :class:`bidict.inverted` to new ``bidict.util`` module (still available from top-level :mod:`bidict` module as well). - Move ``bidict.fancy_iteritems()`` → ``bidict.util.pairs()`` (also available from top level as ``bidict.pairs()``). - Rename :func:`bidict.namedbidict`\'s ``bidict_type`` argument → ``base_type``. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/docs/code-of-conduct.rst0000644000175100001710000000631700000000000017035 0ustar00runnerdockerCode of Conduct =============== Our Pledge ---------- In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. Our Standards ------------- Examples of behavior that contributes to creating a positive environment include: - Using welcoming and inclusive language - Being respectful of differing viewpoints and experiences - Gracefully accepting constructive criticism - Focusing on what is best for the community - Showing empathy towards other community members Examples of unacceptable behavior by participants include: - The use of sexualized language or imagery and unwelcome sexual attention or advances - Trolling, insulting/derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or electronic address, without explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting Our Responsibilities -------------------- Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. Scope ----- This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. Enforcement ----------- Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at . All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. Attribution ----------- This Code of Conduct is adapted from the `Contributor Covenant `__, version 1.4, available at `https://www.contributor-covenant.org/version/1/4 `__. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/docs/conf.py0000644000175100001710000002444600000000000014634 0ustar00runnerdocker# -*- coding: utf-8 -*- # Copyright 2009-2021 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/. # bidict documentation build configuration file, created by # sphinx-quickstart on Fri Aug 29 11:38:22 2014. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. """Sphinx configuration.""" import sys import os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('..')) import bidict # -- General configuration ------------------------------------------------ suppress_warnings = ['image.nonlocal_uri'] # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or custom ones. extensions = [ 'alabaster', 'sphinx.ext.autodoc', 'sphinx.ext.autosectionlabel', 'sphinx.ext.coverage', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode', 'sphinx.ext.todo', ] intersphinx_mapping = {'python': ('https://docs.python.org/3', None)} #todo_include_todos = True # Add any paths that contain templates here, relative to this directory. #templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = 'bidict' author = bidict.__author__ copyright = bidict.__copyright__ # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # The full version, including alpha/beta/rc tags. release = bidict.__version__ # The short X.Y version. version = '.'.join(release.split('.')[:2]) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. modindex_common_prefix = ['bidict.'] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'alabaster' #html_theme = 'sphinx_rtd_theme' #html_theme = 'nature' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. html_theme_options = dict( analytics_id='UA-10116163-3', description=bidict.__description__, link_hover='#247BA1', github_banner=True, github_repo='bidict', github_type='star', github_user='jab', page_width='1000px', show_powered_by=False, show_relbar_bottom=True, donate_url='https://github.com/sponsors/jab', tidelift_url='https://tidelift.com/subscription/pkg/pypi-bidict?utm_source=pypi-bidict&utm_medium=referral&utm_campaign=docs', ) # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [alabaster.get_path()] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = '_static/logo-256.png' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. html_favicon = '_static/favicon.ico' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. #html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. html_sidebars = { '**': [ 'about.html', 'navigation.html', 'relations.html', 'searchbox.html', 'donate.html', ] } # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. html_split_index = True # If true, links to the reST sources are added to the pages. html_show_sourcelink = False # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. html_show_sphinx = False # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'bidictdoc' # -- Options for LaTeX output --------------------------------------------- #latex_elements = { ## The paper size ('letterpaper' or 'a4paper'). ##'papersize': 'letterpaper', # ## The font size ('10pt', '11pt' or '12pt'). ##'pointsize': '10pt', # ## Additional stuff for the LaTeX preamble. ##'preamble': '', #} # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). #latex_documents = [ # ('index', 'bidict.tex', 'bidict Documentation', # author, 'manual'), #] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). #man_pages = [ # ('index', 'bidict', 'bidict Documentation', # [author], 1) #] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) #texinfo_documents = [ # ('index', 'bidict', 'bidict Documentation', # author, 'bidict', 'One line description of project.', # 'Miscellaneous'), #] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False # Ignore urls matching these regex strings when doing "make linkcheck" linkcheck_ignore = [ r'https://codecov\.io/.*', # gives 405 for HEAD requests r'https://pypistats\.org/.*', # unreliable # alternative links for readers on GitHub (which don't work on readthedocs.io): r'CHANGELOG\.rst', r'CODE_OF_CONDUCT\.rst', r'docs/intro\.rst', r'docs/learning-from-bidict\.rst', ] linkcheck_timeout = 10 # 5s default too low # http://www.sphinx-doc.org/en/stable/ext/autosectionlabel.html#configuration autosectionlabel_prefix_document = True # https://www.sphinx-doc.org/en/3.x/usage/extensions/autodoc.html#confval-autodoc_typehints autodoc_typehints = 'description' # http://www.sphinx-doc.org/en/master/usage/extensions/doctest.html doctest_global_setup = """ """ def setup(app): """https://docs.readthedocs.io/en/latest/guides/adding-custom-css.html#adding-custom-css-or-javascript-to-a-sphinx-project""" app.add_js_file('custom.js') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/docs/contributors-guide.rst0000644000175100001710000001427400000000000017715 0ustar00runnerdocker.. Forward declarations for all the custom interpreted text roles that Sphinx defines and that are used below. This helps Sphinx-unaware tools (e.g. rst2html, PyPI's and GitHub's renderers, etc.). .. role:: doc .. role:: ref Contributors' Guide =================== Bug reports, feature requests, patches, and other contributions are warmly welcomed. Contribution should be as easy and friendly as possible. Below are a few guidelines contributors should follow to facilitate the process. Getting Started --------------- - `Create a GitHub account `__ if you don't have one already. - Search through the `issue tracker `__ to see if an issue or pull request has already been created for what you're interested in. If so, feel free to add comments to it or just hit the "subscribe" button to follow progress. If not, you can `join the chat room `__ to discuss there, or go ahead and `create a new issue `__: - Clearly describe the issue giving as much relevant context as possible. - If it is a bug, include reproduction steps, all known environments in which the bug is exhibited, and ideally a failing test case. - If you would like to contribute a patch, make sure you've `created your own fork `__ and have cloned it to your computer. Making Changes -------------- - Before making changes, please (create a `virtualenv `__ and) install the extra packages required for development: ``pip install -r requirements/dev.txt`` We use `EditorConfig `__ and `pre-commit `__ to help achieve uniform style and quality standards across a diversity of development environments. pre-commit gets installed when you run the command above and ensures that various code checks are run before every commit (look in ``.pre-commit-config.yaml`` to see which hooks are run). Ensure the configured hooks are installed by running ``pre-commit install --install-hooks``. EditorConfig allows us to provide a single ``.editorconfig`` file to configure settings like indentation consistently across a variety of supported editors. See https://editorconfig.org/#download to install the plugin for your editor. - Create a topic branch off of main for your changes: ``git checkout -b main`` - Make commits of logical units. - Match the existing code style and quality standards. If you're adding a feature, include accompanying tests and documentation demonstrating its correctness and usage. - Run the tests locally with `tox `__ to make sure they pass for all supported Python versions (see ``envlist`` in ``tox.ini`` for the complete list). If you do not have all the referenced Python versions available locally, you can also push the changes on your branch to GitHub to automatically trigger a new `GitHub Actions `__ build, which should run the tests for all supported Python versions. - Create a concise but comprehensive commit message in the following style:: Include an example commit message in CONTRIBUTING guide #9999 Without this patch the CONTRIBUTING guide would contain no examples of a model commit message. This is a problem because the contributor is left to imagine what the commit message should look like and may not get it right. This patch fixes the problem by providing a concrete example. The first line is an imperative statement summarizing the changes with an issue number from the tracker. The body describes the behavior without the patch, why it's a problem, and how the patch fixes the problem. Submitting Changes ------------------ - Push your changes to a topic branch in your fork of the repository: ``git push --set-upstream origin `` - Submit a pull request providing any additional relevant details necessary. - Acknowledgment should typically be fast but please allow 1-2 weeks for a full response / code review. - The code review process often involves some back-and-forth to get everything right before merging. This is typical of quality software projects that accept patches. - All communication should be supportive and appreciative of good faith efforts to contribute, creating a welcoming and inclusive community. Expect nothing less of any project. Sponsoring ---------- .. image:: https://img.shields.io/badge/GitHub-sponsor-ff69b4 :target: https://github.com/sponsors/jab :alt: Sponsor through GitHub .. image:: https://img.shields.io/badge/Gumroad-sponsor-55a0a4.svg :target: https://gumroad.com/l/bidict :alt: Sponsor through Gumroad .. image:: https://img.shields.io/badge/PayPal-sponsor-blue.svg :target: https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=jabronson%40gmail%2ecom&lc=US&item_name=Sponsor%20bidict%20(name%20a%20fair%20price) :alt: Sponsor through PayPal .. duplicated in README.rst (would use `.. include::` but GitHub doesn't understand it) Bidict is the product of thousands of hours of my unpaid work over the 12+ years I've been maintaining it. If bidict has helped you accomplish your work, especially work you've been paid for, it's easy to `sponsor me through GitHub `__. Choose a tier and GitHub handles everything else. The sponsorship just goes on your regular GitHub bill; there's nothing extra to do. You can also sponsor me through `Gumroad `__ or `PayPal `__. Read more about `companies supporting open source developers `__. Code of Conduct --------------- All participation in this project should respect the :doc:`code-of-conduct`. [#fn-coc]_ By participating, you are expected to honor this code. .. [#fn-let-me-know] ``__ .. [#fn-coc] ``_ | ``__ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/docs/extending.rst0000644000175100001710000001751500000000000016053 0ustar00runnerdockerExtending ``bidict`` -------------------- Although :mod:`bidict` provides the various bidirectional mapping types covered already, it's possible that some use case might require something more than what's provided. For this reason, :mod:`bidict` was written with extensibility in mind. Let's look at some examples. ``YoloBidict`` Recipe ##################### If you'd like :attr:`~bidict.ON_DUP_DROP_OLD` to be the default :class:`~bidict.bidict.on_dup` behavior (for :meth:`~bidict.bidict.__init__`, :meth:`~bidict.bidict.__setitem__`, and :meth:`~bidict.bidict.update`), you can use the following recipe: .. doctest:: >>> from bidict import bidict, ON_DUP_DROP_OLD >>> class YoloBidict(bidict): ... __slots__ = () ... on_dup = ON_DUP_DROP_OLD >>> b = YoloBidict({'one': 1}) >>> b['two'] = 1 # succeeds, no ValueDuplicationError >>> b YoloBidict({'two': 1}) >>> b.update({'three': 1}) # ditto >>> b YoloBidict({'three': 1}) Of course, ``YoloBidict``'s inherited :meth:`~bidict.bidict.put` and :meth:`~bidict.bidict.putall` methods still allow specifying a custom :class:`~bidict.OnDup` per call via the *on_dup* argument, and will both still default to raising for all duplication types. Further demonstrating :mod:`bidict`'s extensibility, to make an ``OrderedYoloBidict``, simply have the subclass above inherit from :class:`bidict.OrderedBidict` rather than :class:`bidict.bidict`. Beware of ``ON_DUP_DROP_OLD`` ::::::::::::::::::::::::::::: There's a good reason that :mod:`bidict` does not provide a ``YoloBidict`` out of the box. Before you decide to use a ``YoloBidict`` in your own code, beware of the following potentially unexpected, dangerous behavior: .. doctest:: >>> b = YoloBidict({'one': 1, 'two': 2}) # contains two items >>> b['one'] = 2 # update one of the items >>> b # now only has one item! YoloBidict({'one': 2}) As covered in :ref:`basic-usage:Key and Value Duplication`, setting an existing key to the value of a different existing item causes both existing items to quietly collapse into a single new item. A safer example of this type of customization would be something like: .. doctest:: >>> from bidict import ON_DUP_RAISE >>> class YodoBidict(bidict): ... __slots__ = () ... on_dup = ON_DUP_RAISE >>> b = YodoBidict({'one': 1}) >>> b['one'] = 2 # Works with a regular bidict, but Yodo plays it safe. Traceback (most recent call last): ... KeyDuplicationError: one >>> b YodoBidict({'one': 1}) >>> b.forceput('one', 2) # Any destructive change requires more force. >>> b YodoBidict({'one': 2}) ``SortedBidict`` Recipes ######################## Suppose you need a bidict that maintains its items in sorted order. The Python standard library does not include any sorted dict types, but the excellent `sortedcontainers `__ and `sortedcollections `__ libraries do. Armed with these, along with :class:`~bidict.BidictBase`'s :attr:`~bidict.BidictBase._fwdm_cls` (forward mapping class) and :attr:`~bidict.BidictBase._invm_cls` (inverse mapping class) attributes, creating a sorted bidict is simple: .. doctest:: >>> from bidict import MutableBidict >>> from sortedcontainers import SortedDict >>> class SortedBidict(MutableBidict): ... """A sorted bidict whose forward items stay sorted by their keys, ... and whose inverse items stay sorted by *their* keys. ... Note: As a result, an instance and its inverse yield their items ... in different orders. ... """ ... __slots__ = () ... _fwdm_cls = SortedDict ... _invm_cls = SortedDict ... _repr_delegate = list # only used for list-style repr >>> b = SortedBidict({'Tokyo': 'Japan', 'Cairo': 'Egypt'}) >>> b SortedBidict([('Cairo', 'Egypt'), ('Tokyo', 'Japan')]) >>> b['Lima'] = 'Peru' >>> list(b.items()) # stays sorted by key [('Cairo', 'Egypt'), ('Lima', 'Peru'), ('Tokyo', 'Japan')] >>> list(b.inverse.items()) # .inverse stays sorted by *its* keys (b's values) [('Egypt', 'Cairo'), ('Japan', 'Tokyo'), ('Peru', 'Lima')] Here's a recipe for a sorted bidict whose forward items stay sorted by their keys, and whose inverse items stay sorted by their values. i.e. An instance and its inverse will yield their items in *the same* order: .. doctest:: >>> from sortedcollections import ValueSortedDict >>> class KeySortedBidict(MutableBidict): ... __slots__ = () ... _fwdm_cls = SortedDict ... _invm_cls = ValueSortedDict ... _repr_delegate = list >>> elem_by_atomicnum = KeySortedBidict({ ... 6: 'carbon', 1: 'hydrogen', 2: 'helium'}) >>> list(elem_by_atomicnum.items()) # stays sorted by key [(1, 'hydrogen'), (2, 'helium'), (6, 'carbon')] >>> list(elem_by_atomicnum.inverse.items()) # .inverse stays sorted by value [('hydrogen', 1), ('helium', 2), ('carbon', 6)] >>> elem_by_atomicnum[4] = 'beryllium' >>> list(elem_by_atomicnum.inverse.items()) [('hydrogen', 1), ('helium', 2), ('beryllium', 4), ('carbon', 6)] Dynamic Inverse Class Generation :::::::::::::::::::::::::::::::: When a bidict class's :attr:`~bidict.BidictBase._fwdm_cls` and :attr:`~bidict.BidictBase._invm_cls` are the same, the bidict class is its own inverse class. (This is the case for all the :ref:`bidict classes ` that come with :mod:`bidict`.) However, when a bidict's :attr:`~bidict.BidictBase._fwdm_cls` and :attr:`~bidict.BidictBase._invm_cls` differ, as in the ``KeySortedBidict`` example above, the inverse class of the bidict needs to have its :attr:`~bidict.BidictBase._fwdm_cls` and :attr:`~bidict.BidictBase._invm_cls` swapped. :class:`~bidict.BidictBase` detects this and dynamically computes the correct inverse class for you automatically. You can see this if you inspect ``KeySortedBidict``'s inverse bidict: >>> elem_by_atomicnum.inverse.__class__.__name__ 'KeySortedBidictInv' Notice that :class:`~bidict.BidictBase` automatically created a ``KeySortedBidictInv`` class and used it for the inverse bidict. As expected, ``KeySortedBidictInv``'s :attr:`~bidict.BidictBase._fwdm_cls` and :attr:`~bidict.BidictBase._invm_cls` are the opposite of ``KeySortedBidict``'s: >>> elem_by_atomicnum.inverse._fwdm_cls.__name__ 'ValueSortedDict' >>> elem_by_atomicnum.inverse._invm_cls.__name__ 'SortedDict' :class:`~bidict.BidictBase` also ensures that round trips work as expected: >>> KeySortedBidictInv = elem_by_atomicnum.inverse.__class__ # i.e. a value-sorted bidict >>> atomicnum_by_elem = KeySortedBidictInv(elem_by_atomicnum.inverse) >>> atomicnum_by_elem KeySortedBidictInv([('hydrogen', 1), ('helium', 2), ('beryllium', 4), ('carbon', 6)]) >>> KeySortedBidict(atomicnum_by_elem.inverse) == elem_by_atomicnum True You can even play tricks with attribute lookup redirection here too. For example, to pass attribute access through to the backing ``_fwdm`` mapping when an attribute is not provided by the bidict class itself, you can override :meth:`~object.__getattribute__` as follows: >>> def __getattribute__(self, name): ... try: ... return object.__getattribute__(self, name) ... except AttributeError as e: ... return getattr(self._fwdm, name) >>> KeySortedBidict.__getattribute__ = __getattribute__ Now, even though this ``KeySortedBidict`` itself provides no ``peekitem`` attribute, the following call still succeeds because it's passed through to the backing ``SortedDict``: >>> elem_by_atomicnum.peekitem() (6, 'carbon') This goes to show how simple it can be to compose your own bidirectional mapping types out of the building blocks that :mod:`bidict` provides. Next proceed to :doc:`other-functionality`. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/docs/home.rst0000644000175100001710000002011200000000000015001 0ustar00runnerdocker.. Forward declarations for all the custom interpreted text roles that Sphinx defines and that are used below. This helps Sphinx-unaware tools (e.g. rst2html, PyPI's and GitHub's renderers, etc.). .. role:: doc .. Use :doc: rather than :ref: references below for better interop as well. bidict ====== The bidirectional mapping library for Python. .. image:: https://raw.githubusercontent.com/jab/bidict/main/assets/logo-sm-white-bg.jpg :target: https://bidict.readthedocs.io/ :alt: bidict logo Status ------ .. image:: https://img.shields.io/pypi/v/bidict.svg :target: https://pypi.org/project/bidict :alt: Latest release .. image:: https://img.shields.io/readthedocs/bidict/main.svg :target: https://bidict.readthedocs.io/en/main/ :alt: Documentation .. image:: https://github.com/jab/bidict/workflows/Tests/badge.svg :target: https://github.com/jab/bidict/actions :alt: GitHub Actions CI status .. image:: https://codecov.io/gh/jab/bidict/branch/main/graph/badge.svg :target: https://codecov.io/gh/jab/bidict :alt: Test coverage .. Hide to reduce clutter .. image:: https://img.shields.io/lgtm/alerts/github/jab/bidict.svg :target: https://lgtm.com/projects/g/jab/bidict/ :alt: LGTM alerts .. image:: https://bestpractices.coreinfrastructure.org/projects/2354/badge :target: https://bestpractices.coreinfrastructure.org/en/projects/2354 :alt: CII best practices badge .. image:: https://img.shields.io/badge/tidelift-pro%20support-orange.svg :target: https://tidelift.com/subscription/pkg/pypi-bidict?utm_source=pypi-bidict&utm_medium=referral&utm_campaign=docs :alt: Paid support available via Tidelift .. image:: https://img.shields.io/pypi/pyversions/bidict.svg :target: https://pypi.org/project/bidict :alt: Supported Python versions .. image:: https://img.shields.io/pypi/implementation/bidict.svg :target: https://pypi.org/project/bidict :alt: Supported Python implementations .. image:: https://img.shields.io/pypi/l/bidict.svg :target: https://raw.githubusercontent.com/jab/bidict/main/LICENSE :alt: License .. image:: https://static.pepy.tech/badge/bidict :target: https://pepy.tech/project/bidict :alt: PyPI Downloads .. image:: https://img.shields.io/badge/GitHub-sponsor-ff69b4 :target: https://github.com/sponsors/jab :alt: Sponsor through GitHub bidict: ^^^^^^^ - has been used for many years by several teams at **Google, Venmo, CERN, Bank of America Merrill Lynch, Bloomberg, Two Sigma,** and many others - has carefully designed APIs for **safety, simplicity, flexibility, and ergonomics** - is **fast, lightweight, and has no runtime dependencies** other than Python's standard library - **integrates natively** with Python’s ``collections.abc`` interfaces - provides **type hints** for all public APIs - is implemented in **concise, well-factored, pure (PyPy-compatible) Python code** that is **optimized for running efficiently** as well as for **reading and learning** [#fn-learning]_ - has **extensive docs and test coverage** (including property-based tests and benchmarks) run continuously on all supported Python versions Installation ------------ ``pip install bidict`` Quick Start ----------- .. code:: python >>> from bidict import bidict >>> element_by_symbol = bidict({'H': 'hydrogen'}) >>> element_by_symbol['H'] 'hydrogen' >>> element_by_symbol.inverse['hydrogen'] 'H' For more usage documentation, head to the :doc:`intro` [#fn-intro]_ and proceed from there. Voluntary Community Support --------------------------- .. image:: https://img.shields.io/badge/gitter-chat-5AB999.svg?logo=gitter-white :target: https://gitter.im/jab/bidict :alt: Chat Please feel free to leave a message in the `bidict chatroom `__ or open a new issue on GitHub for voluntary community support. You can search through `existing issues `__ before creating a new one in case your issue has been addressed already. Enterprise Support ------------------ .. image:: https://img.shields.io/badge/tidelift-enterprise%20support-orange.svg :target: https://tidelift.com/subscription/pkg/pypi-bidict?utm_source=pypi-bidict&utm_medium=referral&utm_campaign=readme :alt: Enterprise support via Tidelift Enterprise-level support for bidict can be obtained via the `Tidelift subscription `__. Notice of Usage --------------- If you use bidict, and especially if your usage or your organization is significant in some way, please let me know in any of the following ways: - `star bidict on GitHub `__ - `create an issue `__ - leave a message in the `chat room `__ - `email me `__ Changelog --------- See the :doc:`changelog` [#fn-changelog]_ for a history of notable changes to bidict. Release Notifications --------------------- .. duplicated in CHANGELOG.rst: (would use `.. include::` but GitHub doesn't understand it) .. image:: https://img.shields.io/badge/libraries.io-subscribe-5BC0DF.svg :target: https://libraries.io/pypi/bidict :alt: Follow on libraries.io Watch releases `on GitHub `__ or `libraries.io `__ to be notified when new versions of bidict are released. Learning from bidict -------------------- One of the best things about bidict is that it touches a surprising number of interesting Python corners, especially given its small size and scope. Check out :doc:`learning-from-bidict` [#fn-learning]_ if you're interested in learning more. Contributing ------------ bidict is currently a one-person operation maintained on a voluntary basis. Your help would be most welcome! See the :doc:`contributors-guide` [#fn-contributing]_ for more information. Sponsoring ^^^^^^^^^^ .. duplicated in CONTRIBUTING.rst (would use `.. include::` but GitHub doesn't understand it) .. image:: https://img.shields.io/badge/GitHub-sponsor-ff69b4 :target: https://github.com/sponsors/jab :alt: Sponsor through GitHub Bidict is the product of thousands of hours of my unpaid work over the 12+ years I've been maintaining it. If bidict has helped you accomplish your work, especially work you've been paid for, it's easy to `sponsor me through GitHub `__. Choose a tier and GitHub handles everything else. The sponsorship just goes on your regular GitHub bill; there's nothing extra to do. You can also sponsor me through `Gumroad `__ or `PayPal `__. Read more about `companies supporting open source developers `__. Finding Documentation --------------------- If you're viewing this on ``__, note that multiple versions of the documentation are available, and you can choose a different version using the popup menu at the bottom-right. Please make sure you're viewing the version of the documentation that corresponds to the version of bidict you'd like to use. If you're viewing this on GitHub, PyPI, or some other place that can't render and link this documentation properly and are seeing broken links, try these alternate links instead: .. [#fn-learning] ``__ | ``__ .. [#fn-changelog] ``__ | ``__ .. [#fn-intro] ``__ | ``__ .. [#fn-contributing] ``__ | ``__ ---- Next: :doc:`intro` [#fn-intro]_ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/docs/index.rst0000644000175100001710000000037500000000000015171 0ustar00runnerdocker.. toctree:: :hidden: Home intro basic-usage other-bidict-types extending other-functionality addendum changelog api learning-from-bidict contributors-guide code-of-conduct thanks .. include:: home.rst ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/docs/intro.rst0000644000175100001710000001251400000000000015213 0ustar00runnerdockerIntroduction ============ The :mod:`bidict` library provides several friendly, efficient data structures for working with `bidirectional mappings `__ in Python. bidict.bidict ------------- :class:`bidict.bidict` is the main bidirectional mapping data structure provided. It allows looking up the value associated with a key, just like a :class:`dict`: .. testsetup:: from bidict import bidict .. doctest:: >>> element_by_symbol = bidict({'H': 'hydrogen'}) >>> element_by_symbol['H'] 'hydrogen' But it also allows looking up the key associated with a value, via the special :attr:`~bidict.BidictBase.inverse` attribute: .. doctest:: >>> element_by_symbol.inverse['hydrogen'] 'H' The :attr:`~bidict.BidictBase.inverse` attribute actually references the entire inverse bidirectional mapping: .. doctest:: >>> element_by_symbol bidict({'H': 'hydrogen'}) >>> element_by_symbol.inverse bidict({'hydrogen': 'H'}) ...which is automatically kept in sync as the original mapping is updated: .. doctest:: >>> element_by_symbol['H'] = 'hydrogène' >>> element_by_symbol.inverse bidict({'hydrogène': 'H'}) If you're used to working with :class:`dict`\s, you'll feel right at home using :mod:`bidict`: >>> dir(element_by_symbol) [..., '__getitem__', ..., '__setitem__', ..., 'items', 'keys', ...] Familiar, concise, Pythonic. Why can't I just use a dict? ---------------------------- A skeptic writes: If I want a mapping associating *a* → *b* and *b* → *a*, I can just create the dict ``{a: b, b: a}``. Why bother using :mod:`bidict`? One answer is better ergonomics for maintaining a correct representation. For example, to get the correct length, you'd have to take the number reported by :func:`len` and cut it in half. But now consider what happens when we need to store a new association, and we try to do so naively: .. code-block:: python >>> el_by_sym = {'H': 'hydrogen', 'hydrogen': 'H'} >>> # Later we need to associate 'H' with a different value >>> el_by_sym.update({'H': 'hydrogène', 'hydrogène': 'H'} # Too naive Here is what we're left with: .. code-block:: python >>> el_by_sym {'H': 'hydrogène', 'hydrogène': 'H', 'hydrogen': 'H'} Oops. We forgot to look up whether the key and value we wanted to set already had any previous associations and remove them as necessary. In general, if we want to store the association *k* ⟷ *v*, but we may have already stored the associations *k* ⟷ *v′* or *k′* ⟷ *v*, a correct implementation using the single-dict approach would require code like this: .. doctest:: >>> d = {'H': 'hydrogen', 'hydrogen': 'H'} >>> def update(d, key, val): ... oldval = d.pop(key, object()) ... d.pop(oldval, None) ... oldkey = d.pop(val, object()) ... d.pop(oldkey, None) ... d.update({key: val, val: key}) >>> update(d, 'H', 'hydrogène') >>> d == {'H': 'hydrogène', 'hydrogène': 'H'} True With :mod:`bidict`, we can instead just write: .. doctest:: >>> b = bidict({'H': 'hydrogen'}) >>> b['H'] = 'hydrogène' And :mod:`bidict` takes care of all the fussy details, leaving us with just what we wanted: .. doctest:: >>> b bidict({'H': 'hydrogène'}) >>> b.inverse bidict({'hydrogène': 'H'}) Even more important... ++++++++++++++++++++++ Beyond this, consider what would happen if we needed to work with just the keys, values, or items that we have associated. Since the single-dict approach inserts values as keys into the same dict that it inserts keys into, we'd never be able to tell our keys and values apart. So iterating over the keys would also yield the values (and vice versa), with no way to tell which was which. Iterating over the items would yield twice as many as we wanted, with a *(v, k)* item that we'd have to ignore for each *(k, v)* item that we expect, and no way to tell which was which. .. doctest:: >>> # Compare the single-dict approach: >>> set(d.keys()) == {'H', 'hydrogène'} # .keys() also gives values True >>> set(d.values()) == {'H', 'hydrogène'} # .values() also gives keys True >>> # ...to using a bidict: >>> b.keys() == {'H'} # just the keys True >>> b.values() == {'hydrogène'} # just the values True In short, to model a bidirectional mapping correctly and unambiguously, we need two separate one-directional mappings, one for the forward associations and one for the inverse, that are kept in sync as the associations change. This is exactly what :mod:`bidict` does under the hood, abstracting it into a clean and ergonomic interface. :mod:`bidict`'s APIs also provide power, flexibility, and safety, making sure the one-to-one invariant is maintained and inverse mappings are kept consistent, while also helping make sure you don't accidentally :ref:`shoot yourself in the foot `. Additional Functionality ------------------------ Besides the standard :class:`bidict.bidict` type, the :mod:`bidict` module provides other bidirectional mapping variants: :class:`~bidict.frozenbidict`, :class:`~bidict.OrderedBidict`, :class:`~bidict.FrozenOrderedBidict`, and :func:`~bidict.namedbidict`. These, and :mod:`bidict`'s other functionality, will be covered in later sections. But first, let's look at a few more details of :doc:`basic-usage`. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/docs/learning-from-bidict.rst0000644000175100001710000004641200000000000020060 0ustar00runnerdockerLearning from ``bidict`` ------------------------ Below is an outline of some of the more fascinating and lesser-known Python corners I got to explore further thanks to working on :mod:`bidict`. If you would like to learn more about any of the topics below, you may find `reading bidict's code `__ particularly interesting. I've sought to optimize the code not just for correctness and performance, but also to make for a clear and enjoyable read, illuminating anything that could otherwise be obscure or subtle. I hope it brings you some of the `joy `__ it's brought me. 😊 Python syntax hacks =================== :mod:`bidict` used to support (ab)using a specialized form of Python's :ref:`slice ` syntax for getting and setting keys by value: .. code-block:: python >>> element_by_symbol = bidict(H='hydrogen') >>> element_by_symbol['H'] # [normal] syntax for the forward mapping 'hydrogen' >>> element_by_symbol[:'hydrogen'] # [:slice] syntax for the inverse (no longer supported) 'H' See `this code `__ for how this was implemented, and `#19 `__ for why this was dropped. Code structure ============== :class:`~bidict.bidict`\s come in every combination of mutable, immutable, ordered, and unordered types, implementing Python's various :class:`relevant ` :class:`collections ` :class:`interfaces ` as appropriate. Factoring the code to maximize reuse, modularity, and adherence to `SOLID `__ design principles (while not missing any chances for special-case optimizations) has been one of the most fun parts of working on bidict. To see how this is done, check out this code: - `_base.py `__ - `_delegating.py `__ - `_frozenbidict.py `__ - `_mut.py `__ - `_bidict.py `__ - `_orderedbase.py `__ - `_frozenordered.py `__ - `_orderedbidict.py `__ Data structures are amazing =========================== Data structures are one of the most fascinating and important building blocks of programming and computer science. It's all too easy to lose sight of the magic when having to implement them for computer science courses or job interview questions. Part of this is because many of the most interesting real-world details get left out, and you miss all the value that comes from ongoing, direct practical application. Bidict shows how fundamental data structures can be implemented in Python for important real-world usage, with practical concerns at top of mind. Come to catch sight of a real, live, industrial-strength linked list in the wild. Stay for the rare, exotic bidirectional mapping breeds you'll rarely see at home. [#fn-data-struct]_ .. [#fn-data-struct] To give you a taste: A regular :class:`~bidict.bidict` encapsulates two regular dicts, keeping them in sync to preserve the bidirectional mapping invariants. Since dicts are unordered, regular bidicts are unordered too. How should we extend this to implement an ordered bidict? We'll still need two backing mappings to store the forward and inverse associations. To store the ordering, we use a (circular, doubly-) linked list. This allows us to e.g. delete an item in any position in O(1) time. Interestingly, the nodes of the linked list encode only the ordering of the items; the nodes themselves contain no key or value data. The two backing mappings associate the key and value data with the nodes, providing the final pieces of the puzzle. Can we use dicts for the backing mappings, as we did for the unordered bidict? It turns out that dicts aren't enough—the backing mappings must actually be (unordered) bidicts themselves! Check out `_orderedbase.py `__ to see this in action. Property-based testing is revolutionary ======================================= When your automated tests run, are they only checking the test cases you happened to hard-code into your test suite? How do you know these test cases aren't missing some important edge cases? With property-based testing, you describe the types of test case inputs your functions accept, along with the properties that should hold for all inputs. Rather than having to think up your test case inputs manually and hard-code them into your test suite, they get generated for you dynamically, in much greater quantity and edge case-exercising diversity than you could come up with by hand. This dramatically increases test coverage and confidence that your code is correct. Bidict never would have survived so many refactorings with so few bugs if it weren't for property-based testing, enabled by the amazing `Hypothesis `__ library. It's game-changing. Check out `bidict's property-based tests `__ to see this in action. Python surprises, gotchas, regrets ================================== - See :ref:`addendum:\*nan\* as a Key`. - See :ref:`addendum:Equivalent but distinct \:class\:\`~collections.abc.Hashable\`\\s`. - What should happen when checking equality of several ordered mappings that contain the same items but in a different order? What about when comparing with an unordered mapping? Check out what Python's :class:`collections.OrderedDict` does, and the surprising results: .. code-block:: python >>> from collections import OrderedDict >>> d = dict([(0, 1), (2, 3)]) >>> od = OrderedDict([(0, 1), (2, 3)]) >>> od2 = OrderedDict([(2, 3), (0, 1)]) >>> d == od True >>> d == od2 True >>> od == od2 False >>> class MyDict(dict): ... __hash__ = lambda self: 0 ... >>> class MyOrderedDict(OrderedDict): ... __hash__ = lambda self: 0 ... >>> d = MyDict([(0, 1), (2, 3)]) >>> od = MyOrderedDict([(0, 1), (2, 3)]) >>> od2 = MyOrderedDict([(2, 3), (0, 1)]) >>> len({d, od, od2}) 1 >>> len({od, od2, d}) 2 According to Raymond Hettinger (Python core developer responsible for much of Python's collections), this design was a mistake (e.g. it violates the `Liskov substitution principle `__ and the `transitive property of equality `__), but it's too late now to fix. Fortunately, it wasn't too late for bidict to learn from this. Hence :ref:`eq-order-insensitive` even for ordered bidicts. For an order-sensitive equality check, bidict provides the separate :meth:`~bidict.BidictBase.equals_order_sensitive` method. - If you define a custom :meth:`~object.__eq__` on a class, it will *not* be used for ``!=`` comparisons on Python 2 automatically; you must explicitly add an :meth:`~object.__ne__` implementation that calls your :meth:`~object.__eq__` implementation. If you don't, :meth:`object.__ne__` will be used instead, which behaves like ``is not``. Python 3 thankfully fixes this. Better memory usage through ``__slots__`` ========================================= Using :ref:`slots` dramatically reduces memory usage in CPython and speeds up attribute access to boot. Must be careful with pickling and weakrefs though! See `BidictBase.__getstate__() `__. Better memory usage through :mod:`weakref` ========================================== A :class:`~bidict.bidict` and its inverse use :mod:`weakref` to avoid creating a strong reference cycle, so that when you release your last reference to a bidict, its memory is reclaimed immediately in CPython rather than having to wait for the next garbage collection. See :ref:`addendum:\`\`bidict\`\` Avoids Reference Cycles`. The (doubly) linked lists that back ordered bidicts also use weakrefs to avoid creating strong reference cycles. Subclassing :func:`~collections.namedtuple` classes =================================================== To get the performance benefits, intrinsic sortability, etc. of :func:`~collections.namedtuple` while customizing behavior, state, API, etc., you can subclass a :func:`~collections.namedtuple` class. Just make sure to include ``__slots__ = ()``, or you'll lose a lot of the performance benefits. Here's an example: .. doctest:: >>> from collections import namedtuple >>> from itertools import count >>> class Node(namedtuple('_Node', 'cost tiebreaker data parent depth')): ... """Represent nodes in a graph traversal. Suitable for use with e.g. heapq.""" ... ... __slots__ = () ... _counter = count() # break ties between equal-cost nodes, avoid comparing data ... ... # Give call sites a cleaner API for creating new Nodes ... def __new__(cls, cost, data, parent=None): ... tiebreaker = next(cls._counter) ... depth = parent.depth + 1 if parent else 0 ... return super().__new__(cls, cost, tiebreaker, data, parent, depth) ... ... def __repr__(self): ... return 'Node(cost={cost}, data={data!r})'.format(**self._asdict()) >>> start = Node(cost=0, data='foo') >>> child = Node(cost=5, data='bar', parent=start) >>> child Node(cost=5, data='bar') >>> child.parent Node(cost=0, data='foo') >>> child.depth 1 :func:`~collections.namedtuple`-style dynamic class generation ============================================================== See the `implementation `__ of :func:`~bidict.namedbidict`. API Design ========== How to deeply integrate with Python's :mod:`collections` and other built-in APIs? - Beyond implementing :class:`collections.abc.Mapping`, bidicts implement additional APIs that :class:`dict` and :class:`~collections.OrderedDict` implement (e.g. :meth:`setdefault`, :meth:`popitem`, etc.). - When creating a new API, making it familiar, memorable, and intuitive is hugely important to a good user experience. - Thanks to :class:`~collections.abc.Hashable`'s implementing :meth:`abc.ABCMeta.__subclasshook__`, any class that implements the required methods of the :class:`~collections.abc.Hashable` interface (namely, :meth:`~collections.abc.Hashable.__hash__`) makes it a virtual subclass already, no need to explicitly extend. I.e. As long as ``Foo`` implements a ``__hash__()`` method, ``issubclass(Foo, Hashable)`` will always be True, no need to explicitly subclass via ``class Foo(Hashable): ...`` - How to make your own open ABC like :class:`~collections.abc.Hashable`? - Override :meth:`~abc.ABCMeta.__subclasshook__` to check for the interface you require. See :class:`~bidict.BidirectionalMapping`'s `old (correct) implementation `__ (this was later removed due to lack of use and maintenance cost when it was discovered that a bug was introduced in v0.15.0). - Interesting consequence of the ``__subclasshook__()`` design: the "subclass" relation becomes intransitive. e.g. :class:`object` is a subclass of :class:`~collections.abc.Hashable`, :class:`list` is a subclass of :class:`object`, but :class:`list` is not a subclass of :class:`~collections.abc.Hashable`. - What if you needed to derive from a second metaclass? Be careful to avoid "TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases". See the great write-up in https://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/. - :class:`collections.abc.Mapping` and :class:`collections.abc.MutableMapping` don't implement :meth:`~abc.ABCMeta.__subclasshook__`, so you must either explicitly subclass them (in which case you inherit their concrete method implementations) or use :meth:`abc.ABCMeta.register` (to register as a virtual subclass without inheriting any of the implementation). - Notice that Python provides :class:`collections.abc.Reversible` but no ``collections.abc.Ordered`` or ``collections.abc.OrderedMapping``. This was proposed in `bpo-28912 `__ but rejected. Would have been useful for bidict's ``__repr__()`` implementation (see ``_base.py``), and potentially for interop with other ordered mapping implementations such as `SortedDict `__. How to make APIs Pythonic? - See the `Zen of Python `__. - "Errors should never pass silently. Unless explicitly silenced. In the face of ambiguity, refuse the temptation to guess." Manifested in bidict's default :attr:`~bidict.bidict.on_dup` class attribute (see :attr:`~bidict.ON_DUP_DEFAULT`). - "Readability counts." "There should be one – and preferably only one – obvious way to do it." An early version of bidict allowed using the ``~`` operator to access ``.inverse`` and a special slice syntax like ``b[:val]`` to look up a key by value, but these were removed in preference to the more obvious and readable ``.inverse``-based spellings. Python's data model =================== - What happens when you implement a custom :meth:`~object.__eq__`? e.g. What's the difference between ``a == b`` and ``b == a`` when only ``a`` is an instance of your class? See the great write-up in https://eev.ee/blog/2012/03/24/python-faq-equality/ for the answer. - If an instance of your special mapping type is being compared against a mapping of some foreign mapping type that contains the same items, should your ``__eq__()`` method return true? Bidict says yes, again based on the `Liskov substitution principle `__. Only returning true when the types matched exactly would violate this. And returning :obj:`NotImplemented` would cause Python to fall back on using identity comparison, which is not what is being asked for. (Just for fun, suppose you did only return true when the types matched exactly, and suppose your special mapping type were also hashable. Would it be worth having your ``__hash__()`` method include your type as well as your items? The only point would be to reduce collisions when multiple instances of different types contained the same items and were going to be inserted into the same :class:`dict` or :class:`set`, since they'd now be unequal but would hash to the same value otherwise.) - Making an immutable type hashable (so it can be inserted into :class:`dict`\s and :class:`set`\s): Must implement :meth:`~object.__hash__` such that ``a == b ⇒ hash(a) == hash(b)``. See the :meth:`object.__hash__` and :meth:`object.__eq__` docs, and the `implementation `__ of :class:`~bidict.frozenbidict`. - Consider :class:`~bidict.FrozenOrderedBidict`: its :meth:`~bidict.FrozenOrderedBidict.__eq__` is :ref:`order-insensitive `. So all contained items must participate in the hash order-insensitively. - Can use `collections.abc.Set._hash `__ which provides a pure Python implementation of the same hash algorithm used to hash :class:`frozenset`\s. (Since :class:`~collections.abc.ItemsView` extends :class:`~collections.abc.Set`, :meth:`bidict.frozenbidict.__hash__` just calls ``ItemsView(self)._hash()``.) - Does this argue for making :meth:`collections.abc.Set._hash` non-private? - Why isn't the C implementation of this algorithm directly exposed in CPython? The only way to use it is to call ``hash(frozenset(self.items()))``, which wastes memory allocating the ephemeral frozenset, and time copying all the items into it before they're hashed. - Unlike other attributes, if a class implements ``__hash__()``, any subclasses of that class will not inherit it. It's like Python implicitly adds ``__hash__ = None`` to the body of every class that doesn't explicitly define ``__hash__``. So if you do want a subclass to inherit a base class's ``__hash__()`` implementation, you have to set that manually, e.g. by adding ``__hash__ = BaseClass.__hash__`` in the class body. See :class:`~bidict.FrozenOrderedBidict`. This is consistent with the fact that :class:`object` implements ``__hash__()``, but subclasses of :class:`object` that override :meth:`~object.__eq__` are not hashable by default. - Using :meth:`~object.__new__` to bypass default object initialization, e.g. for better :meth:`~bidict.bidict.copy` performance. See `_base.py `__. - Overriding :meth:`object.__getattribute__` for custom attribute lookup. See :ref:`extending:\`\`SortedBidict\`\` Recipes`. - Using :meth:`object.__getstate__`, :meth:`object.__setstate__`, and :meth:`object.__reduce__` to make an object pickleable that otherwise wouldn't be, due to e.g. using weakrefs, as bidicts do (covered further below). Portability =========== - Python 2 vs. Python 3 - As affects bidict, mostly :class:`dict` API changes, but also functions like :func:`zip`, :func:`map`, :func:`filter`, etc. - See the :meth:`~object.__ne__` gotcha for Python 2 above. - Borrowing methods from other classes: In Python 2, must grab the ``.im_func`` / ``__func__`` attribute off the borrowed method to avoid getting ``TypeError: unbound method ...() must be called with ... instance as first argument`` See the `old implementation `__ of :class:`~bidict.FrozenOrderedBidict`. - CPython vs. PyPy (and other Python implementations) - See https://doc.pypy.org/en/latest/cpython_differences.html - gc / weakref - Hence ``test_bidicts_freed_on_zero_refcount()`` in `test_properties.py `__ is skipped outside CPython. - primitives' identities, nan, etc. Other interesting stuff in the standard library =============================================== - :mod:`reprlib` and :func:`reprlib.recursive_repr` (but not needed for bidict because there's no way to insert a bidict into itself) - :func:`operator.methodcaller` - See :ref:`addendum:Missing \`\`bidict\`\`\\s in the Standard Library` Tools ===== See the :ref:`Thanks ` page for some of the fantastic tools for software verification, performance, code quality, etc. that bidict has provided an excuse to play with and learn. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/docs/other-bidict-types.rst0000644000175100001710000002625700000000000017610 0ustar00runnerdockerOther ``bidict`` Types ====================== Now that we've covered :doc:`basic-usage` with the :class:`bidict.bidict` type, let's look at some other bidirectional mapping types. .. testsetup:: from bidict import bidict from collections.abc import Mapping, MutableMapping Bidict Types Diagram -------------------- .. image:: _static/bidict-types-diagram.png :target: _static/bidict-types-diagram.png :alt: bidict types diagram All bidirectional mapping types that :mod:`bidict` provides are subclasses of :class:`bidict.BidirectionalMapping`. This abstract base class extends :class:`collections.abc.Mapping` by adding the ":attr:`~bidict.BidirectionalMapping.inverse`" :obj:`~abc.abstractproperty`. As you may have noticed, :class:`bidict.bidict` is also a :class:`collections.abc.MutableMapping`. But :mod:`bidict` provides immutable bidirectional mapping types as well. :class:`~bidict.frozenbidict` ----------------------------- :class:`~bidict.frozenbidict` is an immutable, hashable bidirectional mapping type. As you would expect, attempting to mutate a :class:`~bidict.frozenbidict` causes an error: .. doctest:: >>> from bidict import frozenbidict >>> f = frozenbidict({'H': 'hydrogen'}) >>> f['C'] = 'carbon' Traceback (most recent call last): ... TypeError: ... :class:`~bidict.frozenbidict` also implements :class:`collections.abc.Hashable`, so it's suitable for insertion into sets or other mappings: .. doctest:: >>> my_set = {f} # not an error >>> my_dict = {f: 1} # also not an error See the :class:`~bidict.frozenbidict` API documentation for more information. :class:`~bidict.OrderedBidict` ------------------------------ :class:`bidict.OrderedBidict` is a mutable :class:`~bidict.BidirectionalMapping` that preserves the order in which its items are inserted. It's like a bidirectional version of :class:`collections.OrderedDict`. .. doctest:: >>> from bidict import OrderedBidict >>> element_by_symbol = OrderedBidict([ ... ('H', 'hydrogen'), ('He', 'helium'), ('Li', 'lithium')]) >>> element_by_symbol.inverse OrderedBidict([('hydrogen', 'H'), ('helium', 'He'), ('lithium', 'Li')]) >>> first, second, third = element_by_symbol.values() >>> first, second, third ('hydrogen', 'helium', 'lithium') >>> # Insert an additional item and verify it now comes last: >>> element_by_symbol['Be'] = 'beryllium' >>> last_item = list(element_by_symbol.items())[-1] >>> last_item ('Be', 'beryllium') Additional functionality modeled after :class:`~collections.OrderedDict` is provided as well: .. doctest:: >>> element_by_symbol.popitem(last=True) # Remove the last item ('Be', 'beryllium') >>> element_by_symbol.popitem(last=False) # Remove the first item ('H', 'hydrogen') >>> # Re-adding hydrogen after it's been removed moves it to the end: >>> element_by_symbol['H'] = 'hydrogen' >>> element_by_symbol OrderedBidict([('He', 'helium'), ('Li', 'lithium'), ('H', 'hydrogen')]) >>> # But there's also a `move_to_end` method just for this purpose: >>> element_by_symbol.move_to_end('Li') >>> element_by_symbol OrderedBidict([('He', 'helium'), ('H', 'hydrogen'), ('Li', 'lithium')]) >>> element_by_symbol.move_to_end('H', last=False) # move to front >>> element_by_symbol OrderedBidict([('H', 'hydrogen'), ('He', 'helium'), ('Li', 'lithium')]) As with :class:`~collections.OrderedDict`, updating an existing item preserves its position in the order: .. doctest:: >>> element_by_symbol['He'] = 'updated in place!' >>> element_by_symbol OrderedBidict([('H', 'hydrogen'), ('He', 'updated in place!'), ('Li', 'lithium')]) Collapsing overwrites ##################### When setting an item in an ordered bidict whose key duplicates that of an existing item, and whose value duplicates that of a *different* existing item, the existing item whose *value* is duplicated will be dropped, and the existing item whose *key* is duplicated will have its value overwritten in place: .. doctest:: >>> o = OrderedBidict([(1, 2), (3, 4), (5, 6), (7, 8)]) >>> o.forceput(3, 8) # item with duplicated value (7, 8) is dropped... >>> o # and the item with duplicated key (3, 4) is updated in place: OrderedBidict([(1, 2), (3, 8), (5, 6)]) >>> # (3, 8) took the place of (3, 4), not (7, 8) >>> o = OrderedBidict([(1, 2), (3, 4), (5, 6), (7, 8)]) # as before >>> o.forceput(5, 2) # another example >>> o OrderedBidict([(3, 4), (5, 2), (7, 8)]) >>> # (5, 2) took the place of (5, 6), not (1, 2) .. _eq-order-insensitive: :meth:`~bidict.OrderedBidict.__eq__` is order-insensitive ######################################################### To ensure that equality of bidicts is transitive (and to uphold the `Liskov substitution principle `__), equality tests between an ordered bidict and other mappings are always order-insensitive: .. doctest:: >>> b = bidict([('one', 1), ('two', 2)]) >>> o1 = OrderedBidict([('one', 1), ('two', 2)]) >>> o2 = OrderedBidict([('two', 2), ('one', 1)]) >>> b == o1 True >>> b == o2 True >>> o1 == o2 True For order-sensitive equality tests, use :meth:`~bidict.BidictBase.equals_order_sensitive`: .. doctest:: >>> o1.equals_order_sensitive(o2) False Note that this differs from the behavior of :class:`collections.OrderedDict`\'s ``__eq__()``, by recommendation of Raymond Hettinger (the author of :class:`~collections.OrderedDict`) himself. He later said that making OrderedDict's ``__eq__()`` intransitive was a mistake. What about order-preserving dicts? ################################## In PyPy as well as CPython ≥ 3.6, :class:`dict` preserves insertion order. Given that, can you get away with using a regular :class:`bidict.bidict` in places where you need an insertion order-preserving bidirectional mapping? Consider this example: .. doctest:: >>> b = bidict([(1, -1), (2, -2), (3, -3)]) >>> b[2] = 'UPDATED' >>> b bidict({1: -1, 2: 'UPDATED', 3: -3}) >>> b.inverse bidict({-1: 1, -3: 3, 'UPDATED': 2}) When the value associated with the key ``2`` was changed, the corresponding item stays in place in the forward mapping, but moves to the end of the inverse mapping. Since regular :class:`~bidict.bidict`\s provide weaker ordering guarantees (which allows for a more efficient implementation), it's possible to see behavior like in the example above after certain sequences of mutations. That said, if you depend on preserving insertion order, a non-:class:`~bidict.OrderedBidict` may be sufficient if: * you're never mutating it, or * you're only mutating by removing and/or adding whole new items, never changing just the key or value of an existing item, or * you're only changing existing items in the forward direction (i.e. changing values by key, rather than changing keys by value), and only depend on the order in the forward bidict, not the order of the items in its inverse. On the other hand, if your code is actually depending on the order, using an :meth:`~bidict.OrderedBidict` makes for clearer code. This will also give you additional order-specific APIs, such as :meth:`~bidict.OrderedBidict.move_to_end` and :meth:`popitem(last=False) `. (And also :meth:`~bidict.OrderedBidict.__reversed__` on Python < 3.8. On Python 3.8+, all bidicts are :class:`~collections.abc.Reversible`.) :class:`~bidict.FrozenOrderedBidict` ------------------------------------ :class:`~bidict.FrozenOrderedBidict` is an immutable ordered bidict type. It's like an :class:`~bidict.OrderedBidict` without the mutating APIs, or equivalently like a :class:`reversible ` :class:`~bidict.frozenbidict`. (As of Python 3.6, :class:`~bidict.frozenbidict`\s are order-preserving, because `dicts are order-preserving `__, but :class:`~bidict.frozenbidict`\s are not reversible until Python 3.8+, where dicts became reversible.) If you are using Python 3.8+, :class:`~bidict.frozenbidict` gives you everything that :class:`~bidict.FrozenOrderedBidict` gives you with less space overhead. :func:`~bidict.namedbidict` --------------------------- :func:`bidict.namedbidict`, inspired by :func:`collections.namedtuple`, allows you to easily generate a new bidirectional mapping type with custom attribute-based access to forward and inverse mappings: .. doctest:: >>> from bidict import namedbidict >>> ElementMap = namedbidict('ElementMap', 'symbol', 'name') >>> noble_gases = ElementMap(He='helium') >>> noble_gases.name_for['He'] 'helium' >>> noble_gases.symbol_for['helium'] 'He' >>> noble_gases.name_for['Ne'] = 'neon' >>> del noble_gases.symbol_for['helium'] >>> noble_gases ElementMap({'Ne': 'neon'}) Using the *base_type* keyword arg – whose default value is :class:`bidict.bidict` – you can override the bidict type used as the base class, allowing the creation of e.g. a named frozenbidict type: .. doctest:: >>> ElMap = namedbidict('ElMap', 'symbol', 'name', base_type=frozenbidict) >>> noble = ElMap(He='helium') >>> noble.symbol_for['helium'] 'He' >>> hash(noble) is not TypeError # does not raise TypeError: unhashable type True >>> noble['C'] = 'carbon' # mutation fails Traceback (most recent call last): ... TypeError: ... Polymorphism ------------ (Or: ABCs ftw!) You may be tempted to write something like ``isinstance(obj, dict)`` to check whether ``obj`` is a :class:`~collections.abc.Mapping`. However, this check is too specific, and will fail for many types that implement the :class:`~collections.abc.Mapping` interface: .. doctest:: >>> from collections import ChainMap >>> issubclass(ChainMap, dict) False The same is true for all the bidict types: .. doctest:: >>> issubclass(bidict, dict) False The proper way to check whether an object is a :class:`~collections.abc.Mapping` is to use the abstract base classes (ABCs) from the :mod:`collections` module that are provided for this purpose: .. doctest:: >>> issubclass(ChainMap, Mapping) True >>> isinstance(bidict(), Mapping) True Also note that the proper way to check whether an object is an (im)mutable mapping is to use the :class:`~collections.abc.MutableMapping` ABC: .. doctest:: >>> from bidict import BidirectionalMapping >>> def is_immutable_bimap(obj): ... return (isinstance(obj, BidirectionalMapping) ... and not isinstance(obj, MutableMapping)) >>> is_immutable_bimap(bidict()) False >>> is_immutable_bimap(frozenbidict()) True Checking for ``isinstance(obj, frozenbidict)`` is too specific and could fail in some cases. For example, :class:`~bidict.FrozenOrderedBidict` is an immutable mapping but it does not subclass :class:`~bidict.frozenbidict`: .. doctest:: >>> from bidict import FrozenOrderedBidict >>> obj = FrozenOrderedBidict() >>> is_immutable_bimap(obj) True >>> isinstance(obj, frozenbidict) False Besides the above, there are several other collections ABCs whose interfaces are implemented by various bidict types. Have a look through the :mod:`collections.abc` documentation if you're interested. For more you can do with :mod:`bidict`, check out :doc:`extending` next. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/docs/other-functionality.rst0000644000175100001710000000202400000000000020062 0ustar00runnerdockerOther Functionality =================== :func:`bidict.inverted` ----------------------- bidict provides the :class:`~bidict.inverted` iterator to help you get inverse items from various types of objects. Pass in a mapping to get the inverse mapping: .. doctest:: >>> from bidict import inverted >>> it = inverted({1: 'one'}) >>> {k: v for (k, v) in it} {'one': 1} ...an iterable of pairs to get the pairs' inverses: .. doctest:: >>> list(inverted([(1, 'one'), (2, 'two')])) [('one', 1), ('two', 2)] >>> list(inverted((i*i, i) for i in range(2, 5))) [(2, 4), (3, 9), (4, 16)] ...or any object implementing an ``__inverted__`` method, which objects that already know their own inverses (such as bidicts) can implement themselves: .. testsetup:: from bidict import bidict, OrderedBidict .. doctest:: >>> dict(inverted(bidict({1: 'one'}))) {'one': 1} >>> list(inverted(OrderedBidict([(2, 4), (3, 9)]))) [(4, 2), (9, 3)] Perhaps you'd be interested in taking a look at the :doc:`addendum` next. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/docs/thanks.rst0000644000175100001710000000331200000000000015344 0ustar00runnerdockerThanks ------ Bidict has benefited from the assistance of many people and projects. People ====== .. Remember to update "__credits__" in ../bidict/metadata.py when this is updated - Gregory Ewing for the name. - Terry Reedy for suggesting the slice syntax (it was fun while it lasted). - Raymond Hettinger for suggesting namedbidict and pointing out various caveats. - Francis Carr for the idea of storing the inverse bidict. - Adopt Pytest Month 2015 for choosing bidict, Tom Viner for being bidict's Adopt Pytest helper for the month, and Brianna Laugher for coordinating. - Daniel Pope for suggestions, code reviews, and design discussion. - David Turner for code reviews and design discussion. - Michael Arntzenius for design discussion. - Jozef Knaperek for the bugfix. - Igor Nehoroshev for contributing the py.typed marker. - Bernát Gábor for pyproject.toml support. - Muhammad Faisal for the `CC BY 3.0 `__ `icon `__ that bidict's logo was derived from. - Richard Sanger, Zeyi Wang, and Amol Sahsrabudhe for reporting bugs. Projects ======== - `Tidelift `__ - `Pytest `__ - `Coverage `__ - `hypothesis `__ - `pytest-benchmark `__ - `Sphinx `__ - `Readthedocs `__ - `Codecov `__ - `Pylint `__ - `Flake8 `__ - `setuptools_scm `__ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/mypy.ini0000644000175100001710000000102100000000000014064 0ustar00runnerdocker[mypy] # Be strict about use of Mypy warn_unused_ignores = True warn_unused_configs = True warn_redundant_casts = True warn_return_any = True # Avoid subtle backsliding disallow_incomplete_defs = True disallow_subclassing_any = True # does not work with @staticmethod: # disallow_any_decorated = True # Enable gradually / for new modules # check_untyped_defs = False # disallow_untyped_calls = False # disallow_untyped_defs = False # DO NOT use `ignore_errors`; it doesn't apply # downstream and users have to deal with them. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/pyproject.toml0000644000175100001710000000014500000000000015307 0ustar00runnerdocker[build-system] requires = [ "setuptools", "wheel", ] build-backend = "setuptools.build_meta" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/pytest.ini0000644000175100001710000000141400000000000014424 0ustar00runnerdocker[pytest] testpaths = bidict tests docs filterwarnings = error addopts = --ignore=docs/conf.py --verbose --doctest-modules --doctest-glob=tests/*.txt # pytest's doctest support doesn't support Sphinx extensions # (see https://www.sphinx-doc.org/en/latest/usage/extensions/doctest.html) # so †est the code in the Sphinx docs using Sphinx rather than pytest # (i.e. leave the next line commented out): # --doctest-glob=docs/*.rst # Only add the following from the docs for the sake of pytest's coverage report: --doctest-glob=docs/extending.rst --benchmark-columns=mean,stddev,outliers --benchmark-group-by=func --benchmark-save-data # --benchmark-autosave # --benchmark-compare # --hypothesis-show-statistics doctest_optionflags = IGNORE_EXCEPTION_DETAIL ELLIPSIS ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1630855987.643365 bidict-0.21.3/requirements/0000755000175100001710000000000000000000000015116 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/requirements/dev.in0000644000175100001710000000011200000000000016216 0ustar00runnerdocker-r docs.in -r tests.in -r lint.in check-manifest pip-tools pre-commit tox ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/requirements/dev.txt0000644000175100001710000000630300000000000016437 0ustar00runnerdocker# # This file is autogenerated by pip-compile # To update, run: # # pip-compile dev.in # alabaster==0.7.12 # via sphinx astroid==2.7.3 # via pylint attrs==21.2.0 # via # hypothesis # pytest babel==2.9.1 # via sphinx backports.entry-points-selectable==1.1.0 # via virtualenv build==0.6.0.post1 # via check-manifest certifi==2021.5.30 # via requests cfgv==3.3.1 # via pre-commit charset-normalizer==2.0.4 # via requests check-manifest==0.46 # via -r dev.in click==8.0.1 # via pip-tools coverage==5.5 # via # -r tests.in # pytest-cov distlib==0.3.2 # via virtualenv docutils==0.17.1 # via sphinx filelock==3.0.12 # via # tox # virtualenv hypothesis==6.17.4 # via # -r lint.in # -r tests.in icdiff==2.0.4 # via pytest-icdiff identify==2.2.13 # via pre-commit idna==3.2 # via requests imagesize==1.2.0 # via sphinx iniconfig==1.1.1 # via pytest isort==5.9.3 # via pylint jinja2==3.0.1 # via sphinx lazy-object-proxy==1.6.0 # via astroid markupsafe==2.0.1 # via jinja2 mccabe==0.6.1 # via pylint nodeenv==1.6.0 # via pre-commit packaging==21.0 # via # build # pytest # sphinx # tox pep517==0.11.0 # via # build # pip-tools pip-tools==6.2.0 # via -r dev.in platformdirs==2.3.0 # via # pylint # virtualenv pluggy==1.0.0 # via # pytest # tox pprintpp==0.4.0 # via pytest-icdiff pre-commit==2.14.1 # via # -r dev.in # -r lint.in py-cpuinfo==8.0.0 # via pytest-benchmark py==1.10.0 # via # -r tests.in # pytest # tox pygments==2.10.0 # via sphinx pylint==2.10.2 # via -r lint.in pyparsing==2.4.7 # via packaging pytest-benchmark==3.4.1 # via -r tests.in pytest-cov==2.12.1 # via -r tests.in pytest-icdiff==0.5 # via -r tests.in pytest==6.2.5 # via # -r lint.in # -r tests.in # pytest-benchmark # pytest-cov # pytest-icdiff pytz==2021.1 # via babel pyyaml==5.4.1 # via pre-commit requests==2.26.0 # via sphinx six==1.16.0 # via # tox # virtualenv snowballstemmer==2.1.0 # via sphinx sortedcollections==2.1.0 # via -r tests.in sortedcontainers==2.4.0 # via # -r tests.in # hypothesis # sortedcollections sphinx==4.1.2 # TODO: Enable Sphinx in tox.ini for 3.10 once there's a compatible version of Sphinx # via -r docs.in sphinxcontrib-applehelp==1.0.2 # via sphinx sphinxcontrib-devhelp==1.0.2 # via sphinx sphinxcontrib-htmlhelp==2.0.0 # via sphinx sphinxcontrib-jsmath==1.0.1 # via sphinx sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx toml==0.10.2 # via # check-manifest # pre-commit # pylint # pytest # pytest-cov # tox tomli==1.2.1 # via # build # pep517 tox==3.24.3 # via -r dev.in urllib3==1.26.6 # via requests virtualenv==20.7.2 # via # pre-commit # tox wheel==0.37.0 # via pip-tools wrapt==1.12.1 # via astroid # The following packages are considered to be unsafe in a requirements file: # pip # setuptools ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/requirements/docs.in0000644000175100001710000000000700000000000016373 0ustar00runnerdockersphinx ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/requirements/docs.txt0000644000175100001710000000217000000000000016607 0ustar00runnerdocker# # This file is autogenerated by pip-compile # To update, run: # # pip-compile docs.in # alabaster==0.7.12 # via sphinx babel==2.9.1 # via sphinx certifi==2021.5.30 # via requests charset-normalizer==2.0.4 # via requests docutils==0.17.1 # via sphinx idna==3.2 # via requests imagesize==1.2.0 # via sphinx jinja2==3.0.1 # via sphinx markupsafe==2.0.1 # via jinja2 packaging==21.0 # via sphinx pygments==2.10.0 # via sphinx pyparsing==2.4.7 # via packaging pytz==2021.1 # via babel requests==2.26.0 # via sphinx snowballstemmer==2.1.0 # via sphinx sphinx==4.1.2 # TODO: Enable Sphinx in tox.ini for 3.10 once there's a compatible version of Sphinx # via -r docs.in sphinxcontrib-applehelp==1.0.2 # via sphinx sphinxcontrib-devhelp==1.0.2 # via sphinx sphinxcontrib-htmlhelp==2.0.0 # via sphinx sphinxcontrib-jsmath==1.0.1 # via sphinx sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx urllib3==1.26.6 # via requests # The following packages are considered to be unsafe in a requirements file: # setuptools ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/requirements/lint.in0000644000175100001710000000015000000000000016410 0ustar00runnerdockerpre-commit pylint # pylint runs against tests/*.py which import pytest and hypothesis pytest hypothesis ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/requirements/lint.txt0000644000175100001710000000235000000000000016625 0ustar00runnerdocker# # This file is autogenerated by pip-compile # To update, run: # # pip-compile lint.in # astroid==2.7.3 # via pylint attrs==21.2.0 # via # hypothesis # pytest backports.entry-points-selectable==1.1.0 # via virtualenv cfgv==3.3.1 # via pre-commit distlib==0.3.2 # via virtualenv filelock==3.0.12 # via virtualenv hypothesis==6.17.4 # via -r lint.in identify==2.2.13 # via pre-commit iniconfig==1.1.1 # via pytest isort==5.9.3 # via pylint lazy-object-proxy==1.6.0 # via astroid mccabe==0.6.1 # via pylint nodeenv==1.6.0 # via pre-commit packaging==21.0 # via pytest platformdirs==2.3.0 # via # pylint # virtualenv pluggy==1.0.0 # via pytest pre-commit==2.14.1 # via -r lint.in py==1.10.0 # via pytest pylint==2.10.2 # via -r lint.in pyparsing==2.4.7 # via packaging pytest==6.2.5 # via -r lint.in pyyaml==5.4.1 # via pre-commit six==1.16.0 # via virtualenv sortedcontainers==2.4.0 # via hypothesis toml==0.10.2 # via # pre-commit # pylint # pytest virtualenv==20.7.2 # via pre-commit wrapt==1.12.1 # via astroid # The following packages are considered to be unsafe in a requirements file: # setuptools ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/requirements/tests.in0000644000175100001710000000016600000000000016613 0ustar00runnerdocker-r docs.in hypothesis coverage py pytest pytest-benchmark pytest-cov pytest-icdiff sortedcollections sortedcontainers ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/requirements/tests.txt0000644000175100001710000000373700000000000017033 0ustar00runnerdocker# # This file is autogenerated by pip-compile # To update, run: # # pip-compile tests.in # alabaster==0.7.12 # via sphinx attrs==21.2.0 # via # hypothesis # pytest babel==2.9.1 # via sphinx certifi==2021.5.30 # via requests charset-normalizer==2.0.4 # via requests coverage==5.5 # via # -r tests.in # pytest-cov docutils==0.17.1 # via sphinx hypothesis==6.17.4 # via -r tests.in icdiff==2.0.4 # via pytest-icdiff idna==3.2 # via requests imagesize==1.2.0 # via sphinx iniconfig==1.1.1 # via pytest jinja2==3.0.1 # via sphinx markupsafe==2.0.1 # via jinja2 packaging==21.0 # via # pytest # sphinx pluggy==1.0.0 # via pytest pprintpp==0.4.0 # via pytest-icdiff py-cpuinfo==8.0.0 # via pytest-benchmark py==1.10.0 # via # -r tests.in # pytest pygments==2.10.0 # via sphinx pyparsing==2.4.7 # via packaging pytest-benchmark==3.4.1 # via -r tests.in pytest-cov==2.12.1 # via -r tests.in pytest-icdiff==0.5 # via -r tests.in pytest==6.2.5 # via # -r tests.in # pytest-benchmark # pytest-cov # pytest-icdiff pytz==2021.1 # via babel requests==2.26.0 # via sphinx snowballstemmer==2.1.0 # via sphinx sortedcollections==2.1.0 # via -r tests.in sortedcontainers==2.4.0 # via # -r tests.in # hypothesis # sortedcollections sphinx==4.1.2 # TODO: Enable Sphinx in tox.ini for 3.10 once there's a compatible version of Sphinx # via -r docs.in sphinxcontrib-applehelp==1.0.2 # via sphinx sphinxcontrib-devhelp==1.0.2 # via sphinx sphinxcontrib-htmlhelp==2.0.0 # via sphinx sphinxcontrib-jsmath==1.0.1 # via sphinx sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx toml==0.10.2 # via # pytest # pytest-cov urllib3==1.26.6 # via requests # The following packages are considered to be unsafe in a requirements file: # setuptools ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/run_tests.py0000755000175100001710000000165000000000000015000 0ustar00runnerdocker#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright 2009-2021 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/. # First run all tests that pytest discovers. """Run all tests.""" import sys from functools import reduce from operator import or_ from pytest import main as pytest_main from sphinx.cmd.build import main as sphinx_main TEST_FUNCS = [ pytest_main, # pytest's doctest support doesn't support Sphinx extensions # (see https://www.sphinx-doc.org/en/latest/usage/extensions/doctest.html) # so †est the code in the Sphinx docs using Sphinx's own doctest support. lambda: sphinx_main('-b doctest -d docs/_build/doctrees docs docs/_build/doctest'.split()), ] sys.exit(reduce(or_, (f() for f in TEST_FUNCS))) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1630855987.643365 bidict-0.21.3/setup.cfg0000644000175100001710000000177500000000000014226 0ustar00runnerdocker[metadata] version = attr: bidict.__version__ [bdist_wheel] universal = 0 [flake8] exclude = __pycache__, .git, .tox bidict/_version.py, docs/conf.py, ignore = E265, # block comment should start with '# ' E266, # too many leading '#' for block comment E301, # expected 1 blank line, found 0 (prevents idiomatic type hints) E302, # expected 2 blank lines, found 0 (prevents idiomatic type hints) E402, # import not at top of file E501, # line too long E704, # more than one statement on a single line (prevents idiomatic type hints) E722, # broad except F811, # redefinition of unused ... (prevents idiomatic type hints) per-file-ignores = bidict/__init__.py:F401 [pydocstyle] add_ignore = D102, # missing docstring in public method (prevents idiomatic type hints) D103, # missing docstring in public function (prevents idiomatic type hints) D107, # missing docstring in __init__ (prevents idiomatic type hints) D105, D205, D400, D401, D402 [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/setup.py0000644000175100001710000000524200000000000014110 0ustar00runnerdocker# -*- coding: utf-8 -*- # Copyright 2009-2021 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/. """A setuptools-based setup module. Ref: https://github.com/pypa/sampleproject/blob/main/setup.py """ import sys from os.path import abspath, dirname, join from warnings import warn from setuptools import setup PY2_ERR = """ This version of bidict does not support Python 2. Either use bidict 0.18.4, the last release with Python 2 support, or use Python 3. Also ensure you are using pip >= 9.0.1 to install bidict. See python3statement.org for more info. """ if sys.version_info < (3,): sys.exit(PY2_ERR) elif sys.version_info < (3, 6): warn('This version of bidict is untested on Python < 3.6 and may not work.') from importlib.util import module_from_spec, spec_from_file_location CWD = abspath(dirname(__file__)) # Get bidict's package metadata from ./bidict/metadata.py. METADATA_PATH = join(CWD, 'bidict', 'metadata.py') SPEC = spec_from_file_location('metadata', METADATA_PATH) if not SPEC: raise FileNotFoundError('bidict/metadata.py') METADATA = module_from_spec(SPEC) SPEC.loader.exec_module(METADATA) # type: ignore with open(join(CWD, 'README.rst'), encoding='utf-8') as f: LONG_DESCRIPTION = f.read() setup( name='bidict', author=METADATA.__author__, # type: ignore author_email=METADATA.__email__, # type: ignore description=METADATA.__description__, # type: ignore long_description=LONG_DESCRIPTION, long_description_content_type='text/x-rst', keywords=METADATA.__keywords__, # type: ignore url=METADATA.__url__, # type: ignore license=METADATA.__license__, # type: ignore packages=['bidict'], include_package_data=True, zip_safe=False, # Don't zip. (We're zip-safe but prefer not to.) python_requires='>=3.6', classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)', 'Operating System :: OS Independent', 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development :: Libraries :: Python Modules', 'Typing :: Typed', ], ) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1630855987.643365 bidict-0.21.3/tests/0000755000175100001710000000000000000000000013535 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/tests/__init__.py0000644000175100001710000000052700000000000015652 0ustar00runnerdocker# -*- coding: utf-8 -*- # Copyright 2009-2021 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/. """See http://doc.pytest.org/en/latest/pythonpath.html""" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/tests/conftest.py0000644000175100001710000000132500000000000015735 0ustar00runnerdocker# -*- coding: utf-8 -*- # Copyright 2009-2021 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/. """Set up hypothesis.""" from datetime import timedelta from os import getenv from hypothesis import settings MAX_EXAMPLES_DEFAULT = 200 DEADLINE_DEFAULT = 200 SETTINGS = { 'max_examples': int(getenv('HYPOTHESIS_MAX_EXAMPLES') or MAX_EXAMPLES_DEFAULT), 'deadline': timedelta(milliseconds=int(getenv('HYPOTHESIS_DEADLINE') or DEADLINE_DEFAULT)), } settings.register_profile('custom', **SETTINGS) settings.load_profile('custom') ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1630855987.643365 bidict-0.21.3/tests/properties/0000755000175100001710000000000000000000000015731 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/tests/properties/__init__.py0000644000175100001710000000052700000000000020046 0ustar00runnerdocker# -*- coding: utf-8 -*- # Copyright 2009-2021 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/. """See http://doc.pytest.org/en/latest/pythonpath.html""" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/tests/properties/_strategies.py0000644000175100001710000001374200000000000020623 0ustar00runnerdocker# -*- coding: utf-8 -*- # Copyright 2009-2021 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/. """Strategies for Hypothesis tests.""" from collections import OrderedDict from operator import attrgetter, itemgetter from os import getenv import hypothesis.strategies as st from bidict import DROP_NEW, DROP_OLD, RAISE, OnDup, OrderedBidictBase, namedbidict from . import _types as t MAX = int(getenv('HYPOTHESIS_GEN_MAX_SIZE', '0')) or None BIDICT_TYPES = st.sampled_from(t.BIDICT_TYPES) MUTABLE_BIDICT_TYPES = st.sampled_from(t.MUTABLE_BIDICT_TYPES) FROZEN_BIDICT_TYPES = st.sampled_from(t.FROZEN_BIDICT_TYPES) ORDERED_BIDICT_TYPES = st.sampled_from(t.ORDERED_BIDICT_TYPES) REVERSIBLE_BIDICT_TYPES = st.sampled_from(t.REVERSIBLE_BIDICT_TYPES) MAPPING_TYPES = st.sampled_from(t.MAPPING_TYPES) NON_BIDICT_MAPPING_TYPES = st.sampled_from(t.NON_BIDICT_MAPPING_TYPES) ORDERED_MAPPING_TYPES = st.sampled_from(t.ORDERED_MAPPING_TYPES) HASHABLE_MAPPING_TYPES = st.sampled_from(t.HASHABLE_MAPPING_TYPES) ON_DUP_ACTIONS = st.sampled_from((DROP_NEW, DROP_OLD, RAISE)) ON_DUP = st.tuples(ON_DUP_ACTIONS, ON_DUP_ACTIONS, ON_DUP_ACTIONS).map(OnDup._make) TEXT = st.text() BOOLEANS = st.booleans() # Combine a few different strategies together that generate atomic values # that can be used to initialize test bidicts with. Including only None, bools, and ints # provides enough coverage; including more just slows down example generation. ATOMS = st.none() | BOOLEANS | st.integers() PAIRS = st.tuples(ATOMS, ATOMS) NON_MAPPINGS = ATOMS | st.iterables(ATOMS) ODICTS_KW_PAIRS = st.dictionaries(TEXT, ATOMS, dict_class=OrderedDict, max_size=MAX) L_PAIRS = st.lists(PAIRS, max_size=MAX) I_PAIRS = st.iterables(PAIRS, max_size=MAX) FST_SND = (itemgetter(0), itemgetter(1)) L_PAIRS_NODUP = st.lists(PAIRS, unique_by=FST_SND, max_size=MAX) I_PAIRS_NODUP = st.iterables(PAIRS, unique_by=FST_SND, max_size=MAX) # Reserve a disjoint set of atoms as a source of values guaranteed not to have been # inserted into a test bidict already. DIFF_ATOMS = st.characters() DIFF_PAIRS = st.tuples(DIFF_ATOMS, DIFF_ATOMS) L_DIFF_PAIRS_NODUP = st.lists(DIFF_PAIRS, unique_by=FST_SND, min_size=1, max_size=MAX) DIFF_ITEMS = st.tuples(L_PAIRS_NODUP, L_DIFF_PAIRS_NODUP) RANDOMS = st.randoms(use_true_random=False) SAME_ITEMS_DIFF_ORDER = st.tuples( st.lists(PAIRS, unique_by=FST_SND, min_size=2, max_size=MAX), RANDOMS ).map( lambda i: (i[0], i[1].sample(i[0], len(i[0]))) # (seq, shuffled seq) ).filter(lambda i: i[0] != i[1]) def _bidict_strat(bi_types, init_items=I_PAIRS_NODUP, _inv=attrgetter('inverse')): fwd_bidicts = st.tuples(bi_types, init_items).map(lambda i: i[0](i[1])) inv_bidicts = fwd_bidicts.map(_inv) return fwd_bidicts | inv_bidicts BIDICTS = _bidict_strat(BIDICT_TYPES) FROZEN_BIDICTS = _bidict_strat(FROZEN_BIDICT_TYPES) MUTABLE_BIDICTS = _bidict_strat(MUTABLE_BIDICT_TYPES) ORDERED_BIDICTS = _bidict_strat(ORDERED_BIDICT_TYPES) _ALPHABET = tuple(chr(i) for i in range(0x10ffff) if chr(i).isidentifier()) _NAMEDBI_VALID_NAMES = st.text(_ALPHABET, min_size=1) NAMEDBIDICT_NAMES_ALL_VALID = st.lists(_NAMEDBI_VALID_NAMES, min_size=3, max_size=3, unique=True) NAMEDBIDICT_NAMES_SOME_INVALID = st.lists(st.text(min_size=1), min_size=3, max_size=3).filter( lambda i: not all(str.isidentifier(name) for name in i) ) NAMEDBIDICT_TYPES = st.tuples(NAMEDBIDICT_NAMES_ALL_VALID, BIDICT_TYPES).map( lambda i: namedbidict(*i[0], base_type=i[1]) ) NAMEDBIDICTS = _bidict_strat(NAMEDBIDICT_TYPES) def _bi_and_map(bi_types, builtin_map_types=MAPPING_TYPES, init_items=L_PAIRS_NODUP): """Given bidict types and builtin mapping types, return a pair of each type created from init_items.""" return st.tuples(bi_types, builtin_map_types, init_items).map( lambda i: (i[0](i[2]), i[1](i[2])) ) BI_AND_MAP_FROM_SAME_ND_ITEMS = _bi_and_map(BIDICT_TYPES) # Update the following when we drop support for Python < 3.8. On 3.8+, all mappings are reversible. RBI_AND_RMAP_FROM_SAME_ND_ITEMS = _bi_and_map(REVERSIBLE_BIDICT_TYPES, st.just(OrderedDict)) HBI_AND_HMAP_FROM_SAME_ND_ITEMS = _bi_and_map(FROZEN_BIDICT_TYPES, HASHABLE_MAPPING_TYPES) _unpack = lambda i: (i[0](i[2][0]), i[1](i[2][1])) # noqa: E731 BI_AND_MAP_FROM_DIFF_ITEMS = st.tuples(BIDICT_TYPES, MAPPING_TYPES, DIFF_ITEMS).map(_unpack) OBI_AND_OMAP_FROM_SAME_ITEMS_DIFF_ORDER = st.tuples( ORDERED_BIDICT_TYPES, ORDERED_MAPPING_TYPES, SAME_ITEMS_DIFF_ORDER ).map(_unpack) _cmpdict = lambda i: (OrderedDict if issubclass(i, OrderedBidictBase) else dict) # noqa: E731 BI_AND_CMPDICT_FROM_SAME_ITEMS = st.tuples(BIDICT_TYPES, L_PAIRS_NODUP).map( lambda i: (i[0](i[1]), _cmpdict(i[0])(i[1])) ) NO_ARGS = st.just(()) IM_ARG = st.tuples(ATOMS) IP_ARG = st.tuples(I_PAIRS) TWO_IM_ARGS = st.tuples(ATOMS, ATOMS) ARGS_BY_METHOD = st.fixed_dictionaries({ # mutating # 0-arity (0, 'clear'): NO_ARGS, (0, 'popitem'): NO_ARGS, # 1-arity, an immutable atom (1, '__delitem__'): IM_ARG, (1, 'pop'): IM_ARG, (1, 'setdefault'): IM_ARG, (1, 'move_to_end'): IM_ARG, # 1-arity, a list of pairs (1, 'update'): IP_ARG, (1, 'forceupdate'): IP_ARG, # 2-arity (2, 'pop'): TWO_IM_ARGS, (2, 'setdefault'): TWO_IM_ARGS, (2, '__setitem__'): TWO_IM_ARGS, (2, 'put'): TWO_IM_ARGS, (2, 'forceput'): TWO_IM_ARGS, (2, 'move_to_end'): st.tuples(ATOMS, BOOLEANS), # non-mutating # 0-arity (0, '__copy__'): NO_ARGS, (0, '__iter__'): NO_ARGS, (0, '__len__'): NO_ARGS, (0, 'copy'): NO_ARGS, (0, 'keys'): NO_ARGS, (0, 'items'): NO_ARGS, (0, 'values'): NO_ARGS, (0, 'iterkeys'): NO_ARGS, (0, 'iteritems'): NO_ARGS, (0, 'itervalues'): NO_ARGS, (0, 'viewkeys'): NO_ARGS, (0, 'viewitems'): NO_ARGS, (0, 'viewvalues'): NO_ARGS, # 1-arity (1, '__contains__'): IM_ARG, (1, '__getitem__'): IM_ARG, (1, 'get'): IM_ARG, }) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/tests/properties/_types.py0000644000175100001710000000351000000000000017605 0ustar00runnerdocker# -*- coding: utf-8 -*- # Copyright 2009-2021 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/. """Types for Hypothoses tests.""" from collections import OrderedDict from collections.abc import KeysView, ItemsView, Mapping, Reversible from bidict import FrozenOrderedBidict, OrderedBidict, bidict, frozenbidict, namedbidict MyNamedBidict = namedbidict('MyNamedBidict', 'key', 'val', base_type=bidict) MyNamedFrozenBidict = namedbidict('MyNamedFrozenBidict', 'key', 'val', base_type=frozenbidict) MyNamedOrderedBidict = namedbidict('MyNamedOrderedBidict', 'key', 'val', base_type=OrderedBidict) MUTABLE_BIDICT_TYPES = (bidict, OrderedBidict, MyNamedBidict) FROZEN_BIDICT_TYPES = (frozenbidict, FrozenOrderedBidict, MyNamedFrozenBidict) ORDERED_BIDICT_TYPES = (OrderedBidict, FrozenOrderedBidict, MyNamedOrderedBidict) BIDICT_TYPES = tuple(set(MUTABLE_BIDICT_TYPES + FROZEN_BIDICT_TYPES + ORDERED_BIDICT_TYPES)) # When support is dropped for Python < 3.8, all bidict types will be reversible, # and we can remove the following and just use BIDICT_TYPES instead: REVERSIBLE_BIDICT_TYPES = BIDICT_TYPES if issubclass(bidict, Reversible) else ORDERED_BIDICT_TYPES # Py<3.8 class _FrozenDict(KeysView, Mapping): def __init__(self, *args, **kw): self._mapping = dict(*args, **kw) def __getitem__(self, key): return self._mapping[key] def __hash__(self): return ItemsView(self._mapping)._hash() NON_BIDICT_MAPPING_TYPES = (dict, OrderedDict, _FrozenDict) MAPPING_TYPES = BIDICT_TYPES + NON_BIDICT_MAPPING_TYPES ORDERED_MAPPING_TYPES = ORDERED_BIDICT_TYPES + (OrderedDict,) HASHABLE_MAPPING_TYPES = FROZEN_BIDICT_TYPES + (_FrozenDict,) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/tests/properties/test_properties.py0000644000175100001710000003737300000000000021553 0ustar00runnerdocker# -*- coding: utf-8 -*- # Copyright 2009-2021 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/. """Property-based tests using https://hypothesis.readthedocs.io.""" import gc import pickle from copy import deepcopy from collections import OrderedDict from collections.abc import Iterable from itertools import tee from platform import python_implementation from weakref import ref import pytest from hypothesis import assume, example, given from bidict import ( BidictException, DROP_OLD, RAISE, OnDup, OrderedBidictBase, OrderedBidict, bidict, namedbidict, inverted, KeyDuplicationError, ValueDuplicationError, KeyAndValueDuplicationError, ) from bidict._iter import _iteritems_args_kw from . import _strategies as st require_cpython_gc = pytest.mark.skipif( python_implementation() != 'CPython', reason='Requires CPython GC behavior', ) @given(st.BIDICTS, st.NON_MAPPINGS) def test_unequal_to_non_mapping(bi, not_a_mapping): """Bidicts and their inverses should be unequal to non-mappings.""" assert bi != not_a_mapping assert bi.inv != not_a_mapping assert not bi == not_a_mapping assert not bi.inv == not_a_mapping @given(st.BI_AND_MAP_FROM_DIFF_ITEMS) def test_unequal_to_mapping_with_different_items(bi_and_map_from_diff_items): """Bidicts should be unequal to mappings containing different items.""" bi, mapping = bi_and_map_from_diff_items assert bi != mapping assert not bi == mapping @given(st.BI_AND_MAP_FROM_SAME_ND_ITEMS) def test_equal_to_mapping_with_same_items(bi_and_map_from_same_items): """Bidicts should be equal to mappings created from the same non-duplicating items. The bidict's inverse and the mapping's inverse should also be equal. """ bi, mapping = bi_and_map_from_same_items assert bi == mapping assert not bi != mapping mapping_inv = OrderedDict((v, k) for (k, v) in mapping.items()) assert bi.inv == mapping_inv assert not bi.inv != mapping_inv @given(st.HBI_AND_HMAP_FROM_SAME_ND_ITEMS) def test_equal_hashables_have_same_hash(hashable_bidict_and_mapping): """Hashable bidicts and hashable mappings that are equal should hash to the same value.""" bi, mapping = hashable_bidict_and_mapping assert bi == mapping assert hash(bi) == hash(mapping) @given(st.BI_AND_MAP_FROM_SAME_ND_ITEMS) def test_equals_order_sensitive(bi_and_map_from_same_items): """Ordered bidicts should be order-sensitive-equal to ordered mappings with same nondup items. The bidict's inverse and the ordered mapping's inverse should also be order-sensitive-equal. """ bi, mapping = bi_and_map_from_same_items assert bi.equals_order_sensitive(mapping) mapping_inv = {v: k for (k, v) in mapping.items()} assert bi.inv.equals_order_sensitive(mapping_inv) @given(st.OBI_AND_OMAP_FROM_SAME_ITEMS_DIFF_ORDER) def test_unequal_order_sensitive_same_items_different_order(ob_and_om): """Ordered bidicts should be order-sensitive-unequal to ordered mappings of diff-ordered items. Where both were created from the same items where no key or value was duplicated, but the items were ordered differently. The bidict's inverse and the ordered mapping's inverse should also be order-sensitive-unequal. """ ob, om = ob_and_om assert not ob.equals_order_sensitive(om) om_inv = OrderedDict((v, k) for (k, v) in om.items()) assert not ob.inv.equals_order_sensitive(om_inv) @given(st.ORDERED_BIDICTS, st.NON_MAPPINGS) def test_unequal_order_sensitive_non_mapping(ob, not_a_mapping): """Ordered bidicts should be order-sensitive-unequal to ordered mappings of diff-ordered items. Where both were created from the same items where no key or value was duplicated, but the items were ordered differently. The bidict's inverse and the ordered mapping's inverse should also be order-sensitive-unequal. """ assert not ob.equals_order_sensitive(not_a_mapping) assert not ob.inv.equals_order_sensitive(not_a_mapping) @given(st.MUTABLE_BIDICTS, st.DIFF_ATOMS, st.RANDOMS) def test_setitem_with_dup_val_raises(bi, new_key, rand): """Setting an item whose value duplicates that of an existing item should raise ValueDuplicationError.""" ln = len(bi) assume(ln > 2) for b in (bi, bi.inv): existing_val = rand.choice(list(b.inv)) with pytest.raises(ValueDuplicationError): b[new_key] = existing_val assert len(b) == len(b.inv) == ln @given(st.MUTABLE_BIDICTS, st.RANDOMS) def test_setitem_with_dup_key_val_raises(bi, rand): """Setting an item whose key and val duplicate two different existing items raises KeyAndValueDuplicationError.""" ln = len(bi) assume(ln > 2) for b in (bi, bi.inv): existing_items = rand.sample(list(b.items()), 2) existing_key = existing_items[0][0] existing_val = existing_items[1][1] with pytest.raises(KeyAndValueDuplicationError): b[existing_key] = existing_val assert len(b) == len(b.inv) == ln @given(st.MUTABLE_BIDICTS, st.DIFF_ATOMS, st.RANDOMS) def test_put_with_dup_key_raises(bi, new_val, rand): """Putting an item whose key duplicates that of an existing item should raise KeyDuplicationError.""" ln = len(bi) assume(ln > 2) for b in (bi, bi.inv): existing_key = rand.choice(list(b)) with pytest.raises(KeyDuplicationError): b.put(existing_key, new_val) assert len(b) == len(b.inv) == ln @given(st.BIDICTS) def test_bijectivity(bi): """b[k] == v <==> b.inv[v] == k""" for b in (bi, bi.inv): assert all(b.inv[v] == k for (k, v) in b.items()) @given(st.MUTABLE_BIDICTS) def test_cleared_bidicts_have_no_items(bi): bi.clear() assert not bi assert len(bi) == 0 @given(st.BI_AND_CMPDICT_FROM_SAME_ITEMS, st.ARGS_BY_METHOD) def test_consistency_after_method_call(bi_and_cmp_dict, args_by_method): """A bidict should be left in a consistent state after calling any method, even if it raises.""" bi_orig, cmp_dict_orig = bi_and_cmp_dict for (_, methodname), args in args_by_method.items(): if not hasattr(bi_orig, methodname): continue bi = bi_orig.copy() method = getattr(bi, methodname) try: result = method(*args) except (KeyError, BidictException) as exc: # Call should fail clean, i.e. bi should be in the same state it was before the call. assertmsg = f'{method!r} did not fail clean: {exc!r}' assert bi == bi_orig, assertmsg assert bi.inv == bi_orig.inv, assertmsg else: # Should get the same result as calling the same method on the compare-to dict. cmp_dict = cmp_dict_orig.copy() cmp_dict_meth = getattr(cmp_dict, methodname, None) if cmp_dict_meth: cmp_result = cmp_dict_meth(*args) if isinstance(cmp_result, Iterable): coll = list if isinstance(bi, OrderedBidictBase) else set result = coll(result) cmp_result = coll(cmp_result) assert result == cmp_result, f'methodname={methodname}, args={args!r}' # Whether the call failed or succeeded, bi should pass consistency checks. assert len(bi) == sum(1 for _ in bi.items()) assert len(bi.inv) == sum(1 for _ in bi.inv.items()) assert bi == dict(bi) assert bi.inv == dict(bi.inv) assert bi == OrderedDict((k, v) for (v, k) in bi.inv.items()) assert bi.inv == OrderedDict((v, k) for (k, v) in bi.items()) @given(st.MUTABLE_BIDICTS, st.L_PAIRS, st.ON_DUP) # These test cases ensure coverage of all branches in [Ordered]BidictBase._undo_write # (Hypothesis doesn't always generate examples that cover all the branches otherwise). @example(bidict({1: 1, 2: 2}), [(1, 3), (1, 2)], OnDup(key=DROP_OLD, val=RAISE)) @example(bidict({1: 1, 2: 2}), [(3, 1), (2, 4)], OnDup(key=RAISE, val=DROP_OLD)) @example(bidict({1: 1, 2: 2}), [(1, 2), (1, 1)], OnDup(key=RAISE, val=RAISE, kv=DROP_OLD)) @example(OrderedBidict({1: 1, 2: 2}), [(1, 3), (1, 2)], OnDup(key=DROP_OLD, val=RAISE)) @example(OrderedBidict({1: 1, 2: 2}), [(3, 1), (2, 4)], OnDup(key=RAISE, val=DROP_OLD)) @example(OrderedBidict({1: 1, 2: 2}), [(1, 2), (1, 1)], OnDup(key=RAISE, val=RAISE, kv=DROP_OLD)) def test_putall_same_as_put_for_each_item(bi, items, on_dup): """*bi.putall(items) <==> for i in items: bi.put(i)* for all values of OnDup.""" check = bi.copy() expect = bi.copy() checkexc = None expectexc = None for (key, val) in items: try: expect.put(key, val, on_dup) except BidictException as exc: expectexc = type(exc) expect = bi # Bulk updates fail clean -> roll back to original state. break try: check.putall(items, on_dup) except BidictException as exc: checkexc = type(exc) assert checkexc == expectexc assert check == expect assert check.inv == expect.inv @given(st.BI_AND_MAP_FROM_SAME_ND_ITEMS) def test_bidict_iter(bi_and_mapping): """iter(bi) should yield the keys in a bidict in insertion order.""" bi, mapping = bi_and_mapping assert all(i == j for (i, j) in zip(bi, mapping)) @given(st.RBI_AND_RMAP_FROM_SAME_ND_ITEMS) def test_bidict_reversed(rb_and_rd): """reversed(bi) should yield the keys in a bidict in reverse insertion order.""" rb, rd = rb_and_rd assert all(i == j for (i, j) in zip(reversed(rb), reversed(rd))) @given(st.FROZEN_BIDICTS) def test_frozenbidicts_hashable(bi): """Frozen bidicts can be hashed and inserted into sets and mappings.""" assert hash(bi) assert {bi} assert {bi: bi} @given(st.NAMEDBIDICT_NAMES_SOME_INVALID) def test_namedbidict_raises_on_invalid_name(names): """:func:`bidict.namedbidict` should raise if given invalid names.""" typename, keyname, valname = names with pytest.raises(ValueError): namedbidict(typename, keyname, valname) @given(st.NAMEDBIDICT_NAMES_ALL_VALID) def test_namedbidict_raises_on_same_keyname_as_valname(names): """:func:`bidict.namedbidict` should raise if given same keyname as valname.""" typename, keyname, _ = names with pytest.raises(ValueError): namedbidict(typename, keyname, keyname) @given(st.NAMEDBIDICT_NAMES_ALL_VALID, st.NON_BIDICT_MAPPING_TYPES) def test_namedbidict_raises_on_invalid_base_type(names, invalid_base_type): """:func:`bidict.namedbidict` should raise if given a non-bidict base_type.""" with pytest.raises(TypeError): namedbidict(*names, base_type=invalid_base_type) @given(st.NAMEDBIDICTS) def test_namedbidict(nb): """Test :func:`bidict.namedbidict` custom accessors.""" valfor = getattr(nb, nb._valname + '_for') keyfor = getattr(nb, nb._keyname + '_for') assert all(valfor[key] == val for (key, val) in nb.items()) assert all(keyfor[val] == key for (key, val) in nb.items()) # The same custom accessors should work on the inverse. inv = nb.inv valfor = getattr(inv, nb._valname + '_for') keyfor = getattr(inv, nb._keyname + '_for') assert all(valfor[key] == val for (key, val) in nb.items()) assert all(keyfor[val] == key for (key, val) in nb.items()) @given(st.BIDICTS) def test_bidict_isinv_getstate(bi): """All bidicts should provide ``_isinv`` and ``__getstate__`` (or else they won't fully work as a *base_type* for :func:`namedbidict`). """ bi._isinv # pylint: disable=pointless-statement assert bi.__getstate__() @require_cpython_gc @given(bi_cls=st.BIDICT_TYPES) def test_bidicts_freed_on_zero_refcount(bi_cls): """On CPython, the moment you have no more (strong) references to a bidict, there are no remaining (internal) strong references to it (i.e. no reference cycle was created between it and its inverse), allowing the memory to be reclaimed immediately, even with GC disabled. """ gc.disable() try: bi = bi_cls() weak = ref(bi) assert weak() is not None del bi assert weak() is None finally: gc.enable() @require_cpython_gc @given(ob_cls=st.ORDERED_BIDICT_TYPES, init_items=st.I_PAIRS_NODUP) def test_orderedbidict_nodes_freed_on_zero_refcount(ob_cls, init_items): """On CPython, the moment you have no more references to an ordered bidict, the refcount of each of its internal nodes drops to 0 (i.e. the linked list of nodes does not create a reference cycle), allowing the memory to be reclaimed immediately. """ gc.disable() try: ob = ob_cls(init_items) node_refs = [ref(node) for node in ob._fwdm.values()] assert all(r() is not None for r in node_refs) del ob assert all(r() is None for r in node_refs) finally: gc.enable() @given(bi_cls=st.BIDICT_TYPES) def test_slots(bi_cls): """See https://docs.python.org/3/reference/datamodel.html#notes-on-using-slots.""" stop_at = {object} cls_by_slot = {} for cls in bi_cls.__mro__: if cls in stop_at: break slots = getattr(cls, '__slots__', None) assert slots is not None, f'Expected {cls!r} to define __slots__' for slot in slots: seen_at = cls_by_slot.get(slot) assert not seen_at, f'{seen_at!r} repeats slot {slot!r} declared first by {cls!r}' cls_by_slot[slot] = cls @given(st.BIDICTS) def test_inv_aliases_inverse(bi): """bi.inv should alias bi.inverse.""" assert bi.inverse is bi.inv assert bi.inv.inverse is bi.inverse.inv @given(st.BIDICTS) def test_pickle_roundtrips(bi): """A bidict should equal the result of unpickling its pickle.""" pickled = pickle.dumps(bi) roundtripped = pickle.loads(pickled) assert roundtripped is roundtripped.inv.inv assert roundtripped == bi assert roundtripped.inv == bi.inv assert roundtripped.inv.inv == bi.inv.inv @given(st.BIDICTS) def test_deepcopy(bi): """A bidict should equal its deepcopy.""" cp = deepcopy(bi) assert cp is not bi assert cp.inv.inv is cp assert cp.inv.inv is not bi assert bi == cp assert bi.inv == cp.inv def test_iteritems_args_kw_raises_on_too_many_args(): """:func:`bidict._iteritems_args_kw` should raise if given too many arguments.""" with pytest.raises(TypeError): _iteritems_args_kw('too', 'many', 'args') @given(st.I_PAIRS, st.ODICTS_KW_PAIRS) def test_iteritems_args_kw(arg0, kw): """:func:`bidict._iteritems_args_kw` should work correctly.""" arg0_1, arg0_2 = tee(arg0) it = _iteritems_args_kw(arg0_1, **kw) # Consume the first `len(arg0)` pairs, checking that they match `arg0`. assert all(check == expect for (check, expect) in zip(it, arg0_2)) with pytest.raises(StopIteration): next(arg0_1) # Iterating `it` should have consumed all of `arg0_1`. # Consume the remaining pairs, checking that they match `kw`. # Once min PY version required is higher, can check that the order matches `kw` too. assert all(kw[k] == v for (k, v) in it) with pytest.raises(StopIteration): next(it) @given(st.L_PAIRS) def test_inverted_pairs(pairs): """:func:`bidict.inverted` should yield the inverses of a list of pairs.""" inv = [(v, k) for (k, v) in pairs] assert list(inverted(pairs)) == inv assert list(inverted(inverted(pairs))) == pairs @given(st.BI_AND_MAP_FROM_SAME_ND_ITEMS) def test_inverted_bidict(bi_and_mapping): """:func:`bidict.inverted` should yield the inverse items of an ordered bidict.""" bi, mapping = bi_and_mapping mapping_inv = {v: k for (k, v) in mapping.items()} assert all(i == j for (i, j) in zip(inverted(bi), mapping_inv.items())) assert all(i == j for (i, j) in zip(inverted(inverted(bi)), mapping.items())) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/tests/test_benchmark.py0000644000175100001710000001046000000000000017101 0ustar00runnerdocker# -*- coding: utf-8 -*- # Copyright 2009-2021 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/. """Benchmarks.""" import pytest from bidict import OrderedBidict, ValueDuplicationError, bidict BIDICT_TYPES = (bidict, OrderedBidict) ELEMENTS = OrderedBidict(( ('H', 'hydrogen'), ('He', 'helium'), ('Li', 'lithium'), ('Be', 'beryllium'), ('B', 'boron'), ('C', 'carbon'), ('N', 'nitrogen'), ('O', 'oxygen'), ('F', 'fluorine'), ('Ne', 'neon'), ('Na', 'sodium'), ('Mg', 'magnesium'), ('Al', 'aluminum'), ('Si', 'silicon'), ('P', 'phosphorus'), ('S', 'sulfur'), ('Cl', 'chlorine'), ('Ar', 'argon'), )) UPDATE_NODUP = OrderedBidict(( ('K', 'potassium'), ('Ca', 'calcium'), ('Sc', 'Scandium'), ('Ti', 'titanium'), ('V', 'vanadium'), ('Cr', 'chromium'), ('Mn', 'manganese'), ('Fe', 'iron'), ('Co', 'cobalt'), ('Ni', 'nickel'), ('Cu', 'copper'), ('Zn', 'zinc'), ('Ga', 'gallium'), ('Ge', 'germanium'), ('As', 'arsenic'), ('Se', 'selenium'), ('Br', 'bromine'), ('Kr', 'krypton'), ('Rb', 'rubidium'), ('Sr', 'strontium'), ('Y', 'yttrium'), ('Zr', 'zirconium'), ('Nb', 'niobium'), ('Mo', 'molybdenum'), ('Tc', 'technetium'), ('Ru', 'ruthenium'), ('Rh', 'rhodium'), ('Pd', 'palladium'), ('Ag', 'silver'), ('Cd', 'cadmium'), ('In', 'indium'), ('Sn', 'tin'), ('Sb', 'antimony'), ('Te', 'tellurium'), ('I', 'iodine'), ('Xe', 'xenon'), ('Cs', 'cesium'), ('Ba', 'barium'), ('La', 'lanthanum'), ('Ce', 'cerium'), ('Pr', 'praseodymium'), ('Nd', 'neodymium'), ('Pm', 'promethium'), ('Sm', 'samarium'), ('Eu', 'europium'), ('Gd', 'gadolinium'), ('Tb', 'terbium'), ('Dy', 'dysprosium'), ('Ho', 'holmium'), ('Er', 'erbium'), ('Tm', 'thulium'), ('Yb', 'ytterbium'), ('Lu', 'lutetium'), ('Hf', 'hafnium'), ('Ta', 'tantalum'), ('W', 'tungsten'), ('Re', 'rhenium'), ('Os', 'osmium'), ('Ir', 'iridium'), ('Pt', 'platinum'), ('Au', 'gold'), ('Hg', 'mercury'), ('Tl', 'thallium'), ('Pb', 'lead'), ('Bi', 'bismuth'), ('Po', 'polonium'), ('At', 'astatine'), ('Rn', 'radon'), ('Fr', 'francium'), ('Ra', 'radium'), ('Ac', 'actinium'), ('Th', 'thorium'), ('Pa', 'protactinium'), ('U', 'uranium'), ('Np', 'neptunium'), ('Pu', 'plutonium'), ('Am', 'americium'), ('Cm', 'curium'), ('Bk', 'berkelium'), ('Cf', 'californium'), ('Es', 'einsteinium'), ('Fm', 'fermium'), ('Md', 'mendelevium'), ('No', 'nobelium'), ('Lr', 'lawrencium'), ('Rf', 'rutherfordium'), ('Db', 'dubnium'), ('Sg', 'seaborgium'), ('Bh', 'bohrium'), ('Hs', 'hassium'), ('Mt', 'meitnerium'), ('Ds', 'darmstadtium'), ('Rg', 'roentgenium'), ('Cn', 'copernicium'), )) UPDATE_WITHDUPVAL = OrderedBidict(UPDATE_NODUP, key_with_dup_val='hydrogen') @pytest.mark.parametrize('bi_cls', BIDICT_TYPES) def test_put_nodup(bi_cls, benchmark): """Test inserting a new item with no key or value duplication using put.""" some_bidict = bi_cls(ELEMENTS) benchmark(some_bidict.put, 'K', 'potassium') @pytest.mark.parametrize('bi_cls', BIDICT_TYPES) def test_put_withdup(bi_cls, benchmark): """Test inserting a new item with a duplicate value using put.""" some_bidict = bi_cls(ELEMENTS) def _runner(): with pytest.raises(ValueDuplicationError): some_bidict.put('key_with_dup_val', 'hydrogen') benchmark(_runner) @pytest.mark.parametrize('bi_cls', BIDICT_TYPES) def test_update_nodup(bi_cls, benchmark): """Test inserting new items with no duplication using update.""" some_bidict = bi_cls(ELEMENTS) benchmark(some_bidict.update, UPDATE_NODUP) @pytest.mark.parametrize('bi_cls', BIDICT_TYPES) def test_update_withdup(bi_cls, benchmark): """Test inserting new items with value duplication using update.""" some_bidict = bi_cls(ELEMENTS) def _runner(): with pytest.raises(ValueDuplicationError): some_bidict.update(UPDATE_WITHDUPVAL) benchmark(_runner) @pytest.mark.parametrize('bi_cls', BIDICT_TYPES) def test_forceupdate_withdup(bi_cls, benchmark): """Test inserting new items with value duplication using forceupdate.""" some_bidict = bi_cls(ELEMENTS) benchmark(some_bidict.forceupdate, UPDATE_WITHDUPVAL) assert some_bidict.inv['hydrogen'] == 'key_with_dup_val' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/tests/test_bidict.txt0000644000175100001710000001302300000000000016572 0ustar00runnerdocker# Copyright 2009-2021 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/. Test script for bidict.bidict:: >>> from bidict import bidict >>> keys = (1, 2, 3) >>> vals = ('one', 'two', 'three') >>> bi = bidict(zip(keys, vals)) >>> bi == bidict({1: 'one', 2: 'two', 3: 'three'}) True Works like dict for getting and changing forward mappings:: >>> bi[2] 'two' >>> bi[2] = 'twain' >>> bi[2] 'twain' >>> bi[4] Traceback (most recent call last): ... KeyError: 4 >>> del bi[2] >>> bi.pop(3) 'three' >>> bi bidict({1: 'one'}) ``put`` can also be used to insert a mapping as long as its key and value don't already exist:: >>> bi.put(0, 'zero') >>> bi[0] 'zero' >>> bi.put(1, 'aught') Traceback (most recent call last): ... KeyDuplicationError: 1 >>> del bi[1] >>> bi.put(1, 'aught') >>> bi[1] 'aught' >>> del bi[0] >>> bi bidict({1: 'aught'}) bidicts maintain references to their inverses via the ``inv`` property, which can also be used to access or modify them:: >>> bi.inv bidict({'aught': 1}) >>> bi.inv['aught'] 1 >>> bi.inv['aught'] = 'one' >>> bi bidict({'one': 'aught'}) >>> bi.inv.pop('aught') 'one' >>> bi == bi.inv == bidict() True >>> bi.inv.update(one=1) >>> bi bidict({1: 'one'}) >>> bi is bi.inv.inv True >>> bi.inv is bi.inv.inv.inv True bidicts work with ``inverted`` as expected:: >>> from bidict import inverted >>> biinv = bidict(inverted(bi)) >>> biinv bidict({'one': 1}) This created a new object (equivalent but not identical):: >>> biinv == bi.inv True >>> biinv is bi.inv False Inverting the inverse should round-trip:: >>> bi == bidict(inverted(inverted(bi))) True >>> bi = bi.inv >>> bi == bidict(inverted(inverted(bi))) True The rest of the ``MutableMapping`` interface is supported:: >>> bi.get('one') 1 >>> bi.get('zero') >>> bi.get('zero', 'default') 'default' >>> list(bi.keys()) ['one'] >>> list(bi.values()) [1] >>> list(bi.items()) [('one', 1)] >>> bi.setdefault('one', 2) 1 >>> bi.setdefault('two', 2) 2 >>> bi.pop('one') 1 >>> bi bidict({'two': 2}) >>> bi.inv bidict({2: 'two'}) >>> bi.pop('wrong', 'number', 'of', 'args') Traceback (most recent call last): ... TypeError: pop expected at most 2 arguments (got 4) >>> bi.popitem() ('two', 2) >>> bi.popitem() Traceback (most recent call last): ... KeyError: 'popitem(): bidict is empty' >>> bi.inv.setdefault(3, 'three') 'three' >>> bi bidict({'three': 3}) >>> len(bi) # calls __len__ 1 >>> [key for key in bi] # calls __iter__, returns keys like dict ['three'] >>> 'three' in bi # calls __contains__ True >>> list(bi.keys()) ['three'] >>> list(bi.values()) [3] >>> bi.update([('four', 4)]) >>> bi.update({'five': 5}, six=6, seven=7) >>> sorted(bi.items(), key=lambda x: x[1]) [('three', 3), ('four', 4), ('five', 5), ('six', 6), ('seven', 7)] >>> bi.clear() >>> bi bidict() Empty update is a no-op:: >>> bi.update() >>> bi bidict() Not part of the public API, but test this anyway for the coverage:: >>> bi._update(False, None) >>> bi bidict() Initializing with different keys mapping to the same value fails:: >>> bidict([(1, 1), (2, 1)]) Traceback (most recent call last): ... ValueDuplicationError: 1 Adding a new key associated with an existing value fails:: >>> b = bidict({1: 1}) >>> b[2] = 1 Traceback (most recent call last): ... ValueDuplicationError: 1 >>> b.update({2: 1}) Traceback (most recent call last): ... ValueDuplicationError: 1 ``forceput`` and ``forceupdate`` can be used instead:: >>> b.forceput(2, 1) >>> b bidict({2: 1}) >>> b.forceupdate({1: 1}) >>> b bidict({1: 1}) Trying to insert an existing mapping does not raise, and is a no-op:: >>> b = bidict({1: 'one'}) >>> b[1] = 'one' >>> b[1] 'one' >>> b.inv['one'] = 1 >>> b.inv['one'] 1 The following case does not half-succeed, i.e. the bidict is not in an inconsistent state after:: >>> b = bidict(one=1, two=2) >>> b['one'] = 2 Traceback (most recent call last): ... KeyAndValueDuplicationError: ('one', 2) >>> len(b) == len(b.inv) True ``put`` and ``putall`` allow you to have per-call control over duplication behavior (see doctests in ``../docs/unique-values.rst.inc``). Even with RAISE duplication behavior, inserting existing items is a no-op (i.e. it doesn't raise):: >>> from bidict import RAISE, OnDup >>> b.putall( ... [('three', 3), ('one', 1)], ... OnDup(key=RAISE, val=RAISE) ... ) # does not raise an error because these items were already contained >>> b0 = b.copy() >>> b.putall([]) # no-op >>> b == b0 True Make sure copy.copy and copy.deepcopy create shallow and deep copies, respectively:: >>> from copy import copy, deepcopy >>> from bidict import frozenbidict >>> b = frozenbidict({1: frozenbidict()}) >>> c = copy(b) >>> d = deepcopy(b) >>> b == c == d True >>> b[1] is c[1] True >>> b[1] is d[1] False ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/tests/test_class_relationships.py0000644000175100001710000001300500000000000021216 0ustar00runnerdocker# -*- coding: utf-8 -*- # Copyright 2009-2021 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/. """Test various issubclass checks.""" import re import sys from collections.abc import Hashable, Mapping, MutableMapping, Reversible from collections import OrderedDict import pytest from bidict import ( bidict, frozenbidict, FrozenOrderedBidict, OrderedBidict, BidirectionalMapping, MutableBidirectionalMapping) class AbstractBimap(BidirectionalMapping): # pylint: disable=abstract-method """Dummy type that explicitly extends BidirectionalMapping but fails to override the :attr:`BidirectionalMapping.inverse` :func:`abc.abstractproperty`. As a result, attempting to create an instance of this class should result in ``TypeError: Can't instantiate abstract class AbstractBimap with abstract methods inverse`` """ BIDICT_TYPES = (bidict, frozenbidict, FrozenOrderedBidict, OrderedBidict) BIMAP_TYPES = BIDICT_TYPES + (AbstractBimap,) NOT_BIMAP_TYPES = (dict, OrderedDict, int, object) MUTABLE_BIDICT_TYPES = (bidict, OrderedBidict) HASHABLE_BIDICT_TYPES = (frozenbidict, FrozenOrderedBidict) ORDERED_BIDICT_TYPES = (OrderedBidict, FrozenOrderedBidict) @pytest.mark.parametrize('bi_cls', BIMAP_TYPES) def test_issubclass_bimap(bi_cls): """All bidict types should be considered subclasses of :class:`BidirectionalMapping`.""" assert issubclass(bi_cls, BidirectionalMapping) @pytest.mark.parametrize('not_bi_cls', NOT_BIMAP_TYPES) def test_not_issubclass_not_bimap(not_bi_cls): """Classes that do not conform to :class:`BidirectionalMapping` interface should not be considered subclasses of it. """ assert not issubclass(not_bi_cls, BidirectionalMapping) @pytest.mark.parametrize('bi_cls', BIDICT_TYPES) def test_issubclass_mapping(bi_cls): """All bidict types should be :class:`collections.abc.Mapping`s.""" assert issubclass(bi_cls, Mapping) @pytest.mark.parametrize('bi_cls', MUTABLE_BIDICT_TYPES) def test_issubclass_mutable_and_mutable_bidirectional_mapping(bi_cls): """All mutable bidict types should be mutable (bidirectional) mappings.""" assert issubclass(bi_cls, MutableMapping) assert issubclass(bi_cls, MutableBidirectionalMapping) @pytest.mark.parametrize('bi_cls', HASHABLE_BIDICT_TYPES) def test_hashable_not_mutable(bi_cls): """All hashable bidict types should not be mutable (bidirectional) mappings.""" assert not issubclass(bi_cls, MutableMapping) assert not issubclass(bi_cls, MutableBidirectionalMapping) @pytest.mark.parametrize('bi_cls', HASHABLE_BIDICT_TYPES) def test_issubclass_hashable(bi_cls): """All hashable bidict types should implement :class:`collections.abc.Hashable`.""" assert issubclass(bi_cls, Hashable) @pytest.mark.parametrize('bi_cls', ORDERED_BIDICT_TYPES) def test_ordered_reversible(bi_cls): """All ordered bidict types should be reversible.""" assert callable(bi_cls.__reversed__) def test_issubclass_internal(): """The docs specifically recommend using ABCs over concrete classes when checking whether an interface is provided (see :ref:`polymorphism`). The relationships tested here are not guaranteed to hold in the future, but are still tested so that any unintentional changes won't go unnoticed. """ assert not issubclass(bidict, FrozenOrderedBidict) assert not issubclass(bidict, OrderedBidict) assert not issubclass(bidict, frozenbidict) assert not issubclass(FrozenOrderedBidict, OrderedBidict) assert not issubclass(FrozenOrderedBidict, bidict) assert not issubclass(FrozenOrderedBidict, frozenbidict) assert not issubclass(OrderedBidict, FrozenOrderedBidict) assert not issubclass(OrderedBidict, bidict) assert not issubclass(OrderedBidict, frozenbidict) assert not issubclass(frozenbidict, FrozenOrderedBidict) assert not issubclass(frozenbidict, OrderedBidict) assert not issubclass(frozenbidict, bidict) # Regression test for #111, Bug in BidirectionalMapping.__subclasshook__(): # Any class with an inverse attribute is considered a collections.abc.Mapping OnlyHasInverse = type('OnlyHasInverse', (), {'inverse': ...}) assert not issubclass(OnlyHasInverse, Mapping) def test_abstract_bimap_init_fails(): """See the :class:`AbstractBimap` docstring above.""" with pytest.raises(TypeError) as excinfo: AbstractBimap() # pylint: disable=abstract-class-instantiated assert re.search( "Can't instantiate abstract class AbstractBimap with abstract methods .* inverse", str(excinfo.value)) def test_bimap_inverse_notimplemented(): """Calling .inverse on a BidirectionalMapping should raise :class:`NotImplementedError`.""" with pytest.raises(NotImplementedError): # Can't instantiate a BidirectionalMapping that hasn't overridden the abstract methods of # the interface, so only way to call this implementation is on the class. BidirectionalMapping.inverse.fget(bidict()) def test_bidict_reversible_matches_dict_reversible(): """Reversibility of bidict matches dict's on all supported Python versions.""" assert issubclass(bidict, Reversible) == issubclass(dict, Reversible) @pytest.mark.skipif(sys.version_info < (3, 8), reason='reversible bidicts require Python 3.8+') def test_bidict_reversible(): """All bidicts are Reversible on Python 3.8+.""" assert issubclass(bidict, Reversible) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/tests/test_metadata.py0000644000175100001710000000120100000000000016720 0ustar00runnerdocker# -*- coding: utf-8 -*- # Copyright 2009-2021 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/. """Test bidict metadata.""" import bidict METADATA_ATTRS = """ __author__ __maintainer__ __copyright__ __email__ __credits__ __description__ __keywords__ __license__ __status__ __url__ __version__ """.split() def test_metadata(): """Ensure bidict has expected metadata attributes.""" for i in METADATA_ATTRS: assert getattr(bidict, i) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/tests/test_orderedbidict.txt0000644000175100001710000000531600000000000020145 0ustar00runnerdocker# Copyright 2009-2021 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/. Test for consistency in ordered bidicts after handling duplicate keys/values:: >>> from bidict import OrderedBidict, DuplicationError, RAISE, DROP_OLD, OnDup >>> b = OrderedBidict([(0, 1)]) >>> b.update([(0, 2), (3, 4), (5, 4)]) Traceback (most recent call last): ... ValueDuplicationError: 4 >>> len(b.inv) 1 >>> b.putall([(2, 1), (2, 3)], OnDup(key=RAISE, val=DROP_OLD)) Traceback (most recent call last): ... KeyDuplicationError: 2 >>> len(b) 1 >>> b.forceupdate([(0, 1), (2, 3), (0, 3)]) >>> b OrderedBidict([(0, 3)]) Test for consistency updating an ordered bidict's inverse: >>> b.inv[3] = 'UPDATED KEY' >>> b OrderedBidict([('UPDATED KEY', 3)]) >>> b.inv OrderedBidict([(3, 'UPDATED KEY')]) >>> b.inv.forceput('UPDATED VAL', 'UPDATED KEY') >>> b OrderedBidict([('UPDATED KEY', 'UPDATED VAL')]) >>> b.inv OrderedBidict([('UPDATED VAL', 'UPDATED KEY')]) >>> b.inv['NEW VAL'] = 'NEW KEY' >>> b OrderedBidict([('UPDATED KEY', 'UPDATED VAL'), ('NEW KEY', 'NEW VAL')]) >>> b.inv OrderedBidict([('UPDATED VAL', 'UPDATED KEY'), ('NEW VAL', 'NEW KEY')]) >>> b.inv.forceput('NEW VAL', 'UPDATED KEY') >>> b OrderedBidict([('UPDATED KEY', 'NEW VAL')]) >>> b.inv OrderedBidict([('NEW VAL', 'UPDATED KEY')]) >>> b.inv.update([('NEWER VAL', 'NEWER KEY'), ('NEW VAL', 'NEW KEY'), ('FAIL!', 'NEW KEY')]) Traceback (most recent call last): ... ValueDuplicationError: NEW KEY >>> b OrderedBidict([('UPDATED KEY', 'NEW VAL')]) >>> b.inv OrderedBidict([('NEW VAL', 'UPDATED KEY')]) >>> b.inv.forceupdate([('NEWER VAL', 'NEWER KEY'), ('NEW VAL', 'NEW KEY'), ('SUCCESS!', 'NEW KEY')]) >>> b OrderedBidict([('NEW KEY', 'SUCCESS!'), ('NEWER KEY', 'NEWER VAL')]) >>> b.inv OrderedBidict([('SUCCESS!', 'NEW KEY'), ('NEWER VAL', 'NEWER KEY')]) Test move_to_end here so it shows up in pytest's coverage report (its hypothesis tests may not always hit all code paths, and the doctests in the Sphinx docs don't get counted in the coverage report):: >>> b.move_to_end('NEW KEY') >>> b OrderedBidict([('NEWER KEY', 'NEWER VAL'), ('NEW KEY', 'SUCCESS!')]) >>> b.move_to_end('NEW KEY', last=False) >>> b OrderedBidict([('NEW KEY', 'SUCCESS!'), ('NEWER KEY', 'NEWER VAL')]) >>> b.move_to_end('NOT FOUND') Traceback (most recent call last): ... KeyError: 'NOT FOUND' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630855982.0 bidict-0.21.3/tox.ini0000644000175100001710000000126300000000000013710 0ustar00runnerdocker[tox] envlist = py{310,39,38,37,36,py3} lint docs skip_missing_interpreters = true [testenv] deps = -r requirements/tests.txt commands = ./run_tests.py # Only enable coverage for py39 by default [testenv:py39] setenv = PYTEST_ADDOPTS = --cov=bidict --cov-config=.coveragerc --cov-report=xml # Until there is a compatible version of Sphinx, only run pytest and not Sphinx # for Python 3.10: [testenv:py310] commands = pytest [testenv:lint] deps = -r requirements/lint.txt skip_install = true commands = pre-commit run --all-files --show-diff-on-failure [testenv:docs] deps = -r requirements/docs.txt commands = sphinx-build -b html -d {envtmpdir}/doctrees docs {envtmpdir}/html