pax_global_header00006660000000000000000000000064145644516430014526gustar00rootroot0000000000000052 comment=636f5a610f0e0b9a1e056fc72b688720c114403b bidict-0.23.1/000077500000000000000000000000001456445164300130475ustar00rootroot00000000000000bidict-0.23.1/.coveragerc000066400000000000000000000002601456445164300151660ustar00rootroot00000000000000[run] branch = True parallel = True source = bidict dynamic_context = test_function [report] precision = 1 exclude_also = @.*overload if .*TYPE_CHECKING def .*: \.\.\.$ bidict-0.23.1/.devcontainer/000077500000000000000000000000001456445164300156065ustar00rootroot00000000000000bidict-0.23.1/.devcontainer/Dockerfile000066400000000000000000000022001456445164300175720ustar00rootroot00000000000000# Choices listed at https://github.com/devcontainers/images/blob/v0.3.10/src/python/.devcontainer/Dockerfile#L1 # Note: If on arm64/Apple Silicon, must use -bullseye variant. ARG VARIANT="3.11-bookworm" FROM mcr.microsoft.com/vscode/devcontainers/python:${VARIANT} # Install dev dependencies globally in the image. # (Still easy to experiment with upgrades/new dependencies by installing them in --user scope, # and if you want to keep them, add them to the requirements file and rebuild the container.) COPY dev-deps/python3.11/*.txt /tmp/bidict-dev-deps/ RUN pip3 --disable-pip-version-check install -U pip && \ pip3 --disable-pip-version-check --no-cache-dir install \ -r /tmp/bidict-dev-deps/test.txt \ -r /tmp/bidict-dev-deps/lint.txt \ -r /tmp/bidict-dev-deps/docs.txt \ -r /tmp/bidict-dev-deps/dev.txt \ && rm -rf /tmp/bidict-dev-deps # [Optional] Install additional OS packages. RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ && apt-get -y install --no-install-recommends \ graphviz optipng `# for docs/_static/build-bidict-types-diagram.sh` \ fish `# make fish shell available for development` bidict-0.23.1/.devcontainer/devcontainer.json000066400000000000000000000052361456445164300211700ustar00rootroot00000000000000/* vim: set ft=json5: */ // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: // https://github.com/microsoft/vscode-dev-containers/tree/v0.223.0/containers/python-3 { "name": "Python 3", "build": { "dockerfile": "Dockerfile", "context": "..", // ARGS set in ./Dockerfile can be overridden here: "args": {} }, // Default, container-specific settings here are set on container create. // Note: The project's .vscode/settings.json is still the right place // for non-container-specific settings. "settings": { "python.defaultInterpreterPath": "/usr/local/bin/python", "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", "python.formatting.blackPath": "/usr/local/py-utils/bin/black", "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", "python.linting.banditPath": "/usr/local/py-utils/bin/bandit", "python.linting.flake8Path": "/usr/local/py-utils/bin/flake8", "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy", "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle", "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle", "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint", "esbonio.sphinx.confDir": "${workspaceFolder}/docs", "restructuredtext.pythonRecommendation.disabled": true, "terminal.integrated.defaultProfile.linux": "fish" }, // Add the IDs of extensions you want installed when the container is created. "extensions": [ "EditorConfig.EditorConfig", "GitHub.vscode-pull-request-github", "charliermarsh.ruff", "eamodio.gitlens", "joaompinto.vscode-graphviz", "lextudio.restructuredtext", "ms-python.python", "ms-python.vscode-pylance", "mutantdino.resource-monitor", "tamasfe.even-better-toml", "trond-snekvik.simple-rst" ], // Use 'forwardPorts' to make a list of ports inside the container available locally by default. // (Even without this, by default VS Code will notice when a process starts listening on an // unforwarded port, and will start forwarding that port automatically. Ref: remote.autoForwardPorts) // "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. // Note: esbonio and rstcheck are just for the restructuredtext vscode extension, // so 'pip install' them with --user in a postCreateCommand: "postCreateCommand": "pip3 install --user esbonio rstcheck", // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. "remoteUser": "vscode", "features": { "git": "os-provided", "github-cli": "latest" } } bidict-0.23.1/.editorconfig000066400000000000000000000002631456445164300155250ustar00rootroot00000000000000# http://EditorConfig.org # top-most EditorConfig file root = true [*] end_of_line = lf insert_final_newline = true indent_style = space indent_size = 2 [*.py] indent_size = 4 bidict-0.23.1/.envrc000066400000000000000000000003341456445164300141650ustar00rootroot00000000000000if ! has nix_direnv_version || ! nix_direnv_version 2.4.0; then source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.4.0/direnvrc" "sha256-XQzUAvL6pysIJnRJyR7uVpmUSZfc7LSgWQwq/4mBr1U=" fi use flake bidict-0.23.1/.github/000077500000000000000000000000001456445164300144075ustar00rootroot00000000000000bidict-0.23.1/.github/FUNDING.yml000066400000000000000000000010531456445164300162230ustar00rootroot00000000000000# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/displaying-a-sponsor-button-in-your-repository github: "jab" custom: - http://paypal.me/js3 - https://thanks.dev - https://gumroad.com/l/bidict tidelift: "pypi/bidict" community_bridge: # PROJECT-NAME issuehunt: # USERNAME ko_fi: # USERNAME liberapay: # Replace with a single Liberapay username open_collective: # Replace with a single Open Collective username patreon: # Replace with a single Patreon username polar: # USERNAME bidict-0.23.1/.github/workflows/000077500000000000000000000000001456445164300164445ustar00rootroot00000000000000bidict-0.23.1/.github/workflows/benchmark.yml000066400000000000000000000050071456445164300211230ustar00rootroot00000000000000name: benchmark "on": push: branches: - main - dev - deps pull_request: branches: - main workflow_dispatch: inputs: ref: description: (optional) ref to benchmark env: FORCE_COLOR: "1" PYTHONHASHSEED: "42" BASELINE_URL: https://github.com/jab/bidict/releases/download/microbenchmarks/GHA-linux-cachegrind-x86_64-CPython-3.12.2-baseline.json jobs: benchmark: runs-on: ubuntu-latest steps: - name: install valgrind uses: awalsh128/cache-apt-pkgs-action@44c33b32f808cdddd5ac0366d70595ed63661ed8 with: packages: valgrind - name: check out source uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 with: fetch-depth: 0 - name: set up Python uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c with: # Pin to micro-release for better reproducibility. # When upgrading to a new Python version, remember to upload new associated baseline # benchmark results here: https://github.com/jab/bidict/releases/edit/microbenchmarks python-version: '3.12.2' cache: pip cache-dependency-path: dev-deps/python3.12/test.txt - name: install PyPI dependencies run: | python -m pip install -U pip setuptools wheel python -m pip install -r dev-deps/python3.12/test.txt - name: install the version of bidict to benchmark run: | git checkout ${{ github.event.inputs.ref || github.sha }} pip install . # restore the current revision so we use its version of tests/microbenchmarks.py git checkout ${{ github.sha }} # move aside the 'bidict' subdirectory to make sure we always import the installed version mv -v bidict src - name: download baseline benchmark results run: | curl -Lso baseline.json "$BASELINE_URL" line1=$(head -n1 baseline.json) [ "$line1" = "{" ] - name: benchmark and compare to baseline run: | ./cachegrind.py pytest -n0 \ --benchmark-enable \ --benchmark-autosave \ --benchmark-compare=baseline.json \ tests/microbenchmarks.py - name: archive benchmark results uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 with: name: microbenchmark results path: .benchmarks if-no-files-found: error permissions: contents: read bidict-0.23.1/.github/workflows/lint.yml000066400000000000000000000015501456445164300201360ustar00rootroot00000000000000name: lint "on": push: branches: - main - dev - deps pull_request: branches: - main workflow_dispatch: jobs: lint: runs-on: ubuntu-latest steps: - name: check out source uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - name: set up Python uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c with: python-version: '3.12' cache: pip cache-dependency-path: dev-deps/python3.12/test.txt - name: install dependencies run: | python -m pip install -U pip setuptools wheel python -m pip install pre-commit - name: run pre-commit uses: pre-commit/action@646c83fcd040023954eafda54b4db0192ce70507 with: extra_args: --all-files --verbose permissions: contents: read bidict-0.23.1/.github/workflows/release-to-pypi.yml000066400000000000000000000013701456445164300222070ustar00rootroot00000000000000name: release to pypi.org "on": push: tags: - "v[0-9]+.[0-9]+.[0-9]+" workflow_dispatch: jobs: main: runs-on: ubuntu-latest steps: - name: check out source uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - name: set up Python uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c with: python-version: '3.12' - name: install dependencies run: python -m pip install -U pip setuptools build - run: python -m build - name: Publish uses: pypa/gh-action-pypi-publish@2f6f737ca5f74c637829c0f5c3acd0e29ea5e8bf with: user: __token__ password: ${{ secrets.PYPI_API_TOKEN }} permissions: contents: read bidict-0.23.1/.github/workflows/release-to-test-pypi.yml000066400000000000000000000024301456445164300231620ustar00rootroot00000000000000name: release to test.pypi.org "on": push: tags: # To publish a test release to test.pypi.org, # create and push a tag as follows: # git tag -a 0.21.3.rc1 -m "Tag 0.21.3.rc1 for release to test.pypi.org" # git push --tags # Go to https://github.com/jab/bidict/actions?query=workflow%3A%22Release+to+Test+PyPI%22 # and watch for a new run of this workflow to publish to test.pypi.org. # IMPORTANT: Run the following to clean up after: # git tag -d 0.21.3.rc1 # git push origin :0.21.3.rc1 - "[0-9]+.[0-9]+.[0-9]+.rc[0-9]+" workflow_dispatch: jobs: main: runs-on: ubuntu-latest steps: - name: check out source uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c with: python-version: '3.12' - run: python -m pip install -U pip setuptools build - run: python -m build - name: Publish uses: pypa/gh-action-pypi-publish@2f6f737ca5f74c637829c0f5c3acd0e29ea5e8bf with: user: __token__ password: ${{ secrets.TEST_PYPI_API_TOKEN }} repository_url: https://test.pypi.org/legacy/ verbose: true permissions: contents: read bidict-0.23.1/.github/workflows/test.yml000066400000000000000000000064541456445164300201570ustar00rootroot00000000000000# This name appears as the text in the build badge: https://github.com/jab/bidict/actions/workflows/test.yml/badge.svg name: tests "on": workflow_dispatch: schedule: - cron: "15 16 * * *" push: branches: - main - dev - deps pull_request: branches: - main env: FORCE_COLOR: "1" PYTEST_ADDOPTS: "--hypothesis-show-statistics" jobs: test-all: name: ${{ matrix.pyversion }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: # https://github.com/actions/python-versions/blob/main/versions-manifest.json - pyversion: "3.12" enable_coverage: true - pyversion: "3.11" - pyversion: "3.10" - pyversion: "3.9" - pyversion: "3.8" - pyversion: "pypy-3.10" deps_subdir: "pypy3.10" - pyversion: "pypy-3.9" deps_subdir: "pypy3.9" steps: - name: check out source uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - name: set up Python ${{ matrix.pyversion }} uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c with: python-version: ${{ matrix.pyversion }} cache: pip cache-dependency-path: dev-deps/${{ matrix.deps_subdir || format('python{0}', matrix.pyversion) }}/test.txt - name: install dependencies run: | python -m pip install -U pip setuptools wheel python -m pip install -r dev-deps/${{ matrix.deps_subdir || format('python{0}', matrix.pyversion) }}/test.txt - name: cache .mypy_cache dir uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 with: path: .mypy_cache key: mypy - name: run mypy run: python -m mypy bidict tests - name: maybe set --hypothesis-profile=more-examples # See tests/conftest.py if: ${{ github.event_name == 'schedule' }} run: | echo PYTEST_ADDOPTS="${PYTEST_ADDOPTS} --hypothesis-profile=more-examples" >> "${GITHUB_ENV}" - name: cache .hypothesis dir uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 with: path: .hypothesis key: hypothesis|${{ runner.os }}|${{ matrix.pyversion }} - name: maybe enable coverage if: matrix.enable_coverage run: | echo COVERAGE_PROCESS_START="$(pwd)/.coveragerc" >> "${GITHUB_ENV}" echo RUN_PYTEST_CMD="coverage run" >> "${GITHUB_ENV}" - name: set COVERAGE_CORE=sysmon if py3.12 if: matrix.pyversion == '3.12' run: | echo COVERAGE_CORE=sysmon >> "${GITHUB_ENV}" - name: run pytest run: ${RUN_PYTEST_CMD:-python} -m pytest - name: combine and show any collected coverage if: matrix.enable_coverage run: | coverage combine coverage debug data coverage report - name: maybe upload to Codecov # https://github.com/codecov/codecov-action if: matrix.enable_coverage uses: codecov/codecov-action@e0b68c6749509c5f83f984dd99a76a1c1a231044 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: verbose: true fail_ci_if_error: false # https://github.com/codecov/codecov-action/issues/557 permissions: contents: read bidict-0.23.1/.github/workflows/update_actions.yml000066400000000000000000000014551456445164300221760ustar00rootroot00000000000000# https://github.com/marketplace/actions/github-actions-version-updater name: update GitHub Actions "on": workflow_dispatch: schedule: # Second day of every third month at noon - cron: "0 12 2 */3 *" jobs: main: runs-on: ubuntu-latest steps: - name: check out code uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 with: token: ${{ secrets.ACTIONS_VERSION_UPDATER_TOKEN }} - name: update GitHub Actions uses: saadmk11/github-actions-version-updater@64be81ba69383f81f2be476703ea6570c4c8686e with: token: ${{ secrets.ACTIONS_VERSION_UPDATER_TOKEN }} update_version_with: release-commit-sha pull_request_labels: "automated, dependencies" permissions: contents: write pull-requests: write bidict-0.23.1/.github/workflows/update_dev_deps.yml000066400000000000000000000017571456445164300223340ustar00rootroot00000000000000name: update dev deps "on": schedule: # First day of every third month at noon - cron: "0 12 1 */3 *" workflow_dispatch: jobs: main: runs-on: ubuntu-latest steps: - name: check out code uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - name: install Nix uses: DeterminateSystems/nix-installer-action@cd46bde16ab981b0a7b2dce0574509104543276e # - name: set up upterm session # uses: lhotari/action-upterm@v1 - name: update development dependencies run: nix develop --command bash -c './init_dev_env && ./dev-deps/update_dev_dependencies' - name: create PR uses: peter-evans/create-pull-request@b1ddad2c994a25fbc81a28b3ec0e368bb2021c50 with: token: ${{ secrets.DEV_DEPS_UPDATE_TOKEN }} title: Update development dependencies labels: automated,dependencies commit-message: Upgrade dev dependencies. permissions: contents: read pull-requests: write bidict-0.23.1/.github/workflows/update_flake_lock.yml000066400000000000000000000014101456445164300226170ustar00rootroot00000000000000name: update flake.lock "on": workflow_dispatch: schedule: # Third day of every third month at noon - cron: "0 12 3 */3 *" jobs: main: runs-on: ubuntu-latest steps: - name: check out code uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - name: install Nix uses: DeterminateSystems/nix-installer-action@cd46bde16ab981b0a7b2dce0574509104543276e - name: update flake.lock uses: DeterminateSystems/update-flake-lock@da2fd6f2563fe3e4f2af8be73b864088564e263d with: token: ${{ secrets.FLAKE_LOCK_UPDATE_ACTION_TOKEN }} pr-title: "Update flake.lock" pr-labels: | automated dependencies permissions: contents: read pull-requests: write bidict-0.23.1/.gitignore000066400000000000000000000003001456445164300150300ustar00rootroot00000000000000*.pyc *.so __pycache__ .DS_Store .benchmarks .cache .coverage* .direnv .eggs .hypothesis .idea .mypy_cache .tox .venv bidict.egg-info build _build coverage.xml dist htmlcov pip-wheel-metadata bidict-0.23.1/.gitpod.yml000066400000000000000000000021651456445164300151420ustar00rootroot00000000000000# https://www.gitpod.io/docs/prebuilds github: prebuilds: # enable for the master/default branch (defaults to true) master: true # enable for all branches in this repo (defaults to false) branches: true # enable for pull requests coming from this repo (defaults to true) pullRequests: true # enable for pull requests coming from forks (defaults to false) pullRequestsFromForks: true # add a "Review in Gitpod" button as a comment to pull requests (defaults to true) addComment: true # add a "Review in Gitpod" badge to pull requests (defaults to false) addBadge: false # add a label once the prebuild is ready to pull requests (defaults to false) addLabel: prebuilt-in-gitpod image: gitpod/workspace-python:latest # https://www.gitpod.io/docs/languages/python tasks: - init: | pyenv install 3.10.2 pyenv global 3.10.2 pip install -U pip pip install -r requirements/dev.txt pre-commit install --install-hooks vscode: extensions: - ms-python.python - EditorConfig.EditorConfig - lextudio.restructuredtext - trond-snekvik.simple-rst bidict-0.23.1/.lgtm.yml000066400000000000000000000002241456445164300146110ustar00rootroot00000000000000queries: - exclude: py/missing-equals - exclude: py/conflicting-attributes - exclude: py/unused-import - exclude: py/import-and-import-from bidict-0.23.1/.pre-commit-config.yaml000066400000000000000000000025631456445164300173360ustar00rootroot00000000000000exclude: ^\.benchmarks repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 hooks: - id: check-added-large-files - id: check-case-conflict - id: check-merge-conflict - id: check-symlinks - id: check-toml - id: check-yaml - id: debug-statements - id: double-quote-string-fixer - id: end-of-file-fixer - id: mixed-line-ending - id: trailing-whitespace - repo: https://gitlab.com/bmares/check-json5 rev: v1.0.0 hooks: - id: check-json5 - repo: https://github.com/Lucas-C/pre-commit-hooks rev: v1.5.5 hooks: - id: forbid-crlf - id: remove-crlf - id: forbid-tabs - id: remove-tabs - repo: https://github.com/codespell-project/codespell rev: v2.2.6 hooks: - id: codespell args: ["--uri-ignore-words-list", "*"] # https://github.com/codespell-project/codespell/issues/2473 - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.8.0 hooks: - id: mypy additional_dependencies: - hypothesis - pytest - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.2.2 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] - id: ruff-format - repo: https://github.com/shellcheck-py/shellcheck-py rev: v0.9.0.6 hooks: - id: shellcheck bidict-0.23.1/.readthedocs.yml000066400000000000000000000004671456445164300161440ustar00rootroot00000000000000--- # https://docs.readthedocs.io/en/latest/config-file/ version: 2 formats: - htmlzip build: os: "ubuntu-22.04" tools: python: "3.12" python: install: - requirements: dev-deps/python3.12/docs.txt - method: pip path: . sphinx: configuration: docs/conf.py fail_on_warning: true bidict-0.23.1/.vscode/000077500000000000000000000000001456445164300144105ustar00rootroot00000000000000bidict-0.23.1/.vscode/extensions.json000066400000000000000000000005551456445164300175070ustar00rootroot00000000000000// vim: set ft=json5 { // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. "recommendations": [ "HarrisonGoldstein.tyche", "charliermarsh.ruff", // "tamasfe.even-better-toml", // "lextudio.restructuredtext", // "trond-snekvik.simple-rst", ], "unwantedRecommendations": [], } bidict-0.23.1/.vscode/settings.json000066400000000000000000000006211456445164300171420ustar00rootroot00000000000000// vim: set ft=json5 { "files.insertFinalNewline": true, "files.trimFinalNewlines": true, "files.trimTrailingWhitespace": true, "python.defaultInterpreterPath": ".venv/dev/bin/python", "python.testing.pytestEnabled": true, "mypy-type-checker.importStrategy": "fromEnvironment", "mypy-type-checker.reportingScope": "workspace", "mypy-type-checker.preferDaemon": true, } bidict-0.23.1/.vscode/tasks.json000066400000000000000000000047151456445164300164370ustar00rootroot00000000000000{ "version": "2.0.0", "tasks": [ { "label": "Run tox (all targets)", "type": "process", "command": "tox", "group": "test", "presentation": { "echo": true, "reveal": "always", "focus": false, "panel": "dedicated", "showReuseMessage": false, "clear": false } }, { "label": "Lint", "type": "process", "command": "pre-commit", "args": [ "run", "--all-files" ], "group": "test", "presentation": { "echo": true, "reveal": "always", "focus": false, "panel": "dedicated", "showReuseMessage": false, "clear": false } }, { "label": "Run tests", "type": "process", "command": "pytest", "isTestCommand": true, "presentation": { "echo": true, "reveal": "always", "focus": false, "panel": "dedicated", "showReuseMessage": false, "clear": false } }, { "label": "Build docs", "type": "process", "command": "tox", "args": [ "-e", "docs" ], "group": "build", "presentation": { "echo": true, "reveal": "silent", "focus": false, "panel": "dedicated", "showReuseMessage": false, "clear": true } }, { "label": "Host docs", "type": "process", "command": "python", "args": [ "-m", "http.server" ], "isBackground": true, "options": { "cwd": "${workspaceFolder}/docs/_build/html" }, "dependsOn": "Build docs", "presentation": { "echo": true, "reveal": "silent", "focus": false, "panel": "dedicated", "showReuseMessage": false, "clear": true }, "problemMatcher": [] } ] } bidict-0.23.1/CHANGELOG.rst000066400000000000000000000711101456445164300150700ustar00rootroot00000000000000.. 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 ========= .. image:: https://img.shields.io/badge/GitHub-sponsor-ff69b4 :target: https://github.com/sponsors/jab :alt: Sponsor :sup:`If you or your organization depends on bidict, please consider sponsoring bidict on GitHub.` .. tip:: Watch `bidict releases on GitHub `__ to be notified when new versions of bidict are published. Click the "Watch" dropdown, choose "Custom", and then choose "Releases". 0.23.1 (2024-02-18) ------------------- Fix a regression in 0.23.0 that could defeat type inference of a bidict's key type and value type when running in Python 3.8 or 3.9. :issue:`310` 0.23.0 (2024-02-14) ------------------- Primarily, this release simplifies bidict by removing minor features that are no longer necessary or that have little to no apparent usage, and it also includes some performance optimizations. Specifically, initializing or updating a bidict is now up to 70% faster in microbenchmarks. The changes in this release will also make it easier to maintain and improve bidict in the future, including further potential performance optimizations. It also contains several other improvements. - Drop support for Python 3.7, which reached end of life on 2023-06-27, and take advantage of features available in Python 3.8+. - Remove ``FrozenOrderedBidict`` now that Python 3.7 is no longer supported. :class:`~bidict.frozenbidict` now provides everything that ``FrozenOrderedBidict`` provided (including :class:`reversibility `) on all supported Python versions, but with less space overhead. - Remove ``namedbidict`` due to low usage. - Remove the ``kv`` field of :class:`~bidict.OnDup` which specified the :class:`~bidict.OnDupAction` to take in the case of :ref:`basic-usage:key and value duplication`. The :attr:`~bidict.OnDup.val` field now specifies the action to take in the case of :ref:`basic-usage:key and value duplication` as well as :ref:`just value duplication `. - Improve type hints for the :attr:`~bidict.BidictBase.inv` shortcut alias for :attr:`~bidict.BidictBase.inverse`. - Fix a bug where calls like ``bidict(None)``, ``bi.update(False)``, etc. would fail to raise a :class:`TypeError`. - All :meth:`~bidict.BidictBase.__init__`, :meth:`~bidict.MutableBidict.update`, and related methods now handle `SupportsKeysAndGetItem `__ objects that are not :class:`~collections.abc.Mapping`\s the same way that `MutableMapping.update() `__ does, before falling back to handling the provided object as an iterable of pairs. - The :func:`repr` of ordered bidicts now matches that of regular bidicts, e.g. ``OrderedBidict({1: 1})`` rather than ``OrderedBidict([(1, 1)])``. (Accordingly, the ``bidict.__repr_delegate__`` field has been removed now that it's no longer needed.) This tracks with the change to :class:`collections.OrderedDict`\'s :func:`repr` `in Python 3.12 `__. - Test with Python 3.12 in CI. Note: Older versions of bidict also support Python 3.12, even though they don't explicitly declare support for it. - Drop use of `Trove classifiers `__ that declare support for specific Python versions in package metadata. 0.22.1 (2022-12-31) ------------------- - Only include the source code in the source distribution. This reduces the size of the source distribution from ~200kB to ~30kB. - Fix the return type hint of :func:`bidict.inverted` to return an :class:`~collections.abc.Iterator`, rather than an :class:`~collections.abc.Iterable`. 0.22.0 (2022-03-23) ------------------- - Drop support for Python 3.6, which reached end of life on 2021-12-23 and is no longer supported by pip as of pip version 22. Take advantage of this to reduce bidict's maintenance costs. - Use mypy-appeasing explicit re-exports in ``__init__.py`` (e.g. ``import x as x``) so that mypy no longer gives you an implicit re-export error if you run it with ``--no-implicit-reexport`` (or ``--strict``) against code that imports from :mod:`bidict`. - Update the implementations and type annotations of :meth:`bidict.BidictBase.keys` and :meth:`bidict.BidictBase.values` to make use of the new :class:`~bidict.BidictKeysView` type, which works a bit better with type checkers. - Inverse bidict instances are now computed lazily the first time the :attr:`~bidict.BidictBase.inverse` attribute is accessed rather than being computed eagerly during initialization. (A bidict's backing, inverse, one-way mapping is still kept in sync eagerly as any mutations are made, to preserve key- and value-uniqueness.) - Optimize initializing a bidict with another bidict. In a microbenchmark on Python 3.10, this now performs over **2x faster**. - Optimize updating an empty bidict with another bidict. In a microbenchmark on Python 3.10, this now performs **60-75% faster**. - Optimize :meth:`~bidict.BidictBase.copy`. In a microbenchmark on Python 3.10, this now performs **10-20x faster**. - Optimize rolling back :ref:`failed updates to a bidict ` in the case that the number of items passed to the update call can be determined to be larger than the bidict being updated. Previously this rollback was O(n) in the number of items passed. Now it is O(1), i.e. **unboundedly faster**. - Optimize :meth:`bidict.BidictBase.__contains__` (the method called when you run ``key in mybidict``). In a microbenchmark on Python 3.10, this now performs over **3-10x faster** in the False case, and at least **50% faster** in the True case. - Optimize :meth:`bidict.BidictBase.__eq__` (the method called when you run ``mybidict == other``). In a microbenchmark on Python 3.10, this now performs **15-25x faster** for ordered bidicts, and **7-12x faster** for unordered bidicts. - Optimize :meth:`~bidict.BidictBase.equals_order_sensitive`. In a microbenchmark on Python 3.10, this now performs **2x faster** for ordered bidicts and **60-90% faster** for unordered bidicts. - Optimize the :class:`~collections.abc.MappingView` objects returned by :meth:`bidict.OrderedBidict.keys`, :meth:`bidict.OrderedBidict.values `, and :meth:`bidict.OrderedBidict.items` to delegate to backing ``dict_keys`` and ``dict_items`` objects if available, which are much faster in CPython. For example, in a microbenchmark on Python 3.10, ``orderedbi.items() == d.items()`` now performs **30-50x faster**. - Fix a bug where :meth:`bidict.BidictBase.__eq__` was always returning False rather than :obj:`NotImplemented` in the case that the argument was not a :class:`~collections.abc.Mapping`, defeating the argument's own ``__eq__()`` if implemented. As a notable example, bidicts now correctly compare equal to :obj:`unittest.mock.ANY`. - :class:`bidict.BidictBase` now adds a ``__reversed__`` implementation to subclasses that don't have an overridden implementation depending on whether both their backing mappings are :class:`~collections.abc.Reversible`. Previously, a ``__reversed__`` implementation was only added to :class:`~bidict.BidictBase` when ``BidictBase._fwdm_cls`` was :class:`~collections.abc.Reversible`. So if a :class:`~bidict.BidictBase` subclass set its ``_fwdm_cls`` to a non-reversible mutable mapping, it would also have to manually set its ``__reversed__`` attribute to None to override the implementation inherited from :class:`~bidict.BidictBase`. This is no longer necessary thanks to bidict's new :meth:`object.__init_subclass__` logic. - The :class:`~collections.abc.MappingView` objects returned by :meth:`bidict.OrderedBidict.keys`, :meth:`bidict.OrderedBidict.values `, and :meth:`bidict.OrderedBidict.items` are now :class:`~collections.abc.Reversible`. (This was already the case for unordered bidicts when running on Python 3.8+.) - Add support for Python 3.9-style dict merge operators (`PEP 584 `__). See `the tests `__ for examples. - Update docstrings for :meth:`bidict.BidictBase.keys`, :meth:`bidict.BidictBase.values`, and :meth:`bidict.BidictBase.items` to include more details. - ``namedbidict`` now exposes the passed-in *keyname* and *valname* in the corresponding properties on the generated class. - ``namedbidict`` now requires *base_type* to be a subclass of :class:`~bidict.BidictBase`, but no longer requires *base_type* to provide an ``_isinv`` attribute, which :class:`~bidict.BidictBase` subclasses no longer provide. - When attempting to pickle a bidict's inverse whose class was :ref:`dynamically generated `, and no reference to the dynamically-generated class has been stored anywhere in :data:`sys.modules` where :mod:`pickle` can find it, the pickle call is now more likely to succeed rather than failing with a :class:`~pickle.PicklingError`. - Remove the use of slots from (non-ABC) bidict types. This better matches the mapping implementations in Python's standard library, and significantly reduces code complexity and maintenance burden. The memory savings conferred by using slots are not noticeable unless you're creating millions of bidict instances anyway, which is an extremely unusual usage pattern. Of course, bidicts can still contain millions (or more) items (which is not an unusual usage pattern) without using any more memory than before these changes. Notably, slots are still used in the internal linked list nodes of ordered bidicts to save memory, since as many node instances are created as there are items inserted. 0.21.4 (2021-10-23) ------------------- Explicitly declare support for Python 3.10 as well as some minor internal improvements. 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, :class:`dict`\s provide a deterministic ordering on all supported Python versions, and as a result, all bidicts do too. So now even non-:class:`Ordered ` bidicts might as well provide :meth:`~bidict.BidictBase.equals_order_sensitive`. See the updated :ref:`other-bidict-types:What about order-preserving dicts?` docs for more info. - Take better advantage of the fact that dicts became :class:`reversible ` in Python 3.8. Specifically, now even non-:class:`Ordered ` bidicts provide a :meth:`~bidict.BidictBase.__reversed__` implementation on Python 3.8+ that calls :func:`reversed` on the backing ``_fwdm`` mapping. As a result, if you are using Python 3.8+, :class:`~bidict.frozenbidict` now gives you everything that ``FrozenOrderedBidict`` gives you, but 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 ``namedbidict``\s), 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. - ``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.MutableBidict.put` and :meth:`~bidict.MutableBidict.putall`. - The ``on_dup_key``, ``on_dup_val``, and ``on_dup_kv`` :class:`~bidict.bidict` class attributes. - Remove ``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`. :issue:`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.MutableBidict.put` and :meth:`~bidict.MutableBidict.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.BidictBase.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 ``bidict.BidirectionalMapping.keys``, ``bidict.BidirectionalMapping.values``, and ``bidict.BidirectionalMapping.items``, and now also provide a more efficient implementation of ``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 ``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 ``bidict.BidirectionalMapping.__subclasshook__`` due to lack of use and maintenance cost. 0.18.3 (2019-09-22) ------------------- - Improve validation of names passed to ``namedbidict``: Use :meth:`str.isidentifier` on Python 3, and a better regex on Python 2. - On Python 3, set :attr:`~definition.__qualname__` on ``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. :issue:`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`. :issue:`86` - ``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 ``_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.BidictBase.keys`, :meth:`~bidict.BidictBase.values`, and :meth:`~bidict.BidictBase.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 ``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._iter._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. :issue:`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** - ``namedbidict`` now verifies that the provided ``keyname`` and ``valname`` are distinct, raising :class:`ValueError` if they are equal. - ``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. **Misc** - 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 ``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, :obj:`pickle.DEFAULT_PROTOCOL` is 3 anyway, so this will not affect you. However if you are using in Python 2, :obj:`~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)``. bidict-0.23.1/CODE_OF_CONDUCT.md000066400000000000000000000002501456445164300156430ustar00rootroot00000000000000Please see [CODE_OF_CONDUCT.rst](CODE_OF_CONDUCT.rst) This file is here to work around the GitHub bug discussed in https://github.com/github/feedback/discussions/9442 bidict-0.23.1/CODE_OF_CONDUCT.rst000066400000000000000000000004301456445164300160530ustar00rootroot00000000000000Code of Conduct =============== All interaction in the bidict community should adhere to the `Python Community Code of Conduct `__. You can report any Code of Conduct issues via email to `jabronson@gmail.com `__. bidict-0.23.1/CONTRIBUTING.rst000066400000000000000000000167741456445164300155270ustar00rootroot00000000000000.. 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 `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 post in the `GitHub Discussions forum `__, 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 -------------- - You can work on bidict in a Visual Studio Code `devcontainer environment `__, where development dependencies and some helpful VS Code extensions are installed inside the dev container environment for you. Try ``Remote-Containers: Clone Repository in Container Volume...`` on this repository. You may need to reload your VS Code window after it finishes cloning and installing extensions, which it should automatically prompt you to do when you open your clone in VS Code. - Note that `pre-commit `__ is used to help achieve uniform style and quality standards. - If not using a VSCode devcontainer, you can try the following to set up a development environment manually: - If you have `Nix `__, you can run ``nix develop`` from within your clone to start a shell where all supported Python versions as well as ``pre-commit`` are installed and added to your PATH. Otherwise, manually ensure you have `pre-commit `__ and at least the latest `stable Python version `__ installed and on your PATH. - Run ``./init_dev_env`` This installs the git hooks for ``pre-commit`` in your clone, creates a virtualenv with all the development dependencies installed, and reminds you to activate the virtualenv env it just created when ready. - 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. Testing your changes with GitHub Actions will require approval from a project admin the first time you submit a PR. - 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. Sponsoring ---------- .. Some of the following badges are duplicated on other pages. Would use `.. include::` but GitHub's renderer doesn't support it. .. 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/PayPal-sponsor-blue.svg :target: https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=jabronson%40gmail%2ecom&lc=US&item_name=Sponsor%20bidict :alt: Sponsor through PayPal Bidict is the product of thousands of hours of my unpaid work over the 15+ years that I've been the sole maintainer. If bidict has helped you or your company accomplish your work, please sponsor my work through one of the following, and/or ask your company to do the same: - `GitHub `__ - `PayPal `__ - `Tidelift `__ - `thanks.dev `__ - `Gumroad `__ - `a support engagement with my LLC `__ If you're not sure which to use, GitHub is an easy option, especially if you already have a GitHub account. Just choose a monthly or one-time amount, and GitHub handles everything else. Your bidict sponsorship on GitHub will automatically go on the same regular bill as any other GitHub charges you pay for. PayPal is another easy option for one-time contributions. See the following for rationale and examples of companies supporting the open source projects they depend on in this manner: - ``__ - ``__ - ``__ .. - ``__ .. - ``__ .. - ``__ 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-coc] ``__ | ``__ bidict-0.23.1/LICENSE000066400000000000000000000406201456445164300140560ustar00rootroot00000000000000Mozilla Public License Version 2.0 ================================== Copyright 2009-2024 Joshua Bronson. All rights reserved. 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. bidict-0.23.1/MANIFEST.in000066400000000000000000000001131456445164300146000ustar00rootroot00000000000000include bidict/py.typed recursive-exclude docs * recursive-exclude tests * bidict-0.23.1/PYPI_DOWNLOAD_STATS.rst000066400000000000000000000003341456445164300166270ustar00rootroot00000000000000Retrieving PyPI Download Stats ------------------------------ * https://pepy.tech/project/bidict * https://pypi.org/project/pypistats/ * https://packaging.python.org/en/latest/guides/analyzing-pypi-package-downloads/ bidict-0.23.1/README.rst000066400000000000000000000172341456445164300145450ustar00rootroot00000000000000.. role:: doc .. (Forward declaration for the "doc" role that Sphinx defines for interop with renderers that are often used to show this doc and that are unaware of Sphinx (GitHub.com, PyPI.org, etc.). Use :doc: rather than :ref: here for better interop as well.) bidict ====== *The bidirectional mapping library for Python.* 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/actions/workflows/test.yml/badge.svg :target: https://github.com/jab/bidict/actions/workflows/test.yml?query=branch%3Amain :alt: GitHub Actions CI status .. 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 Features -------- - Mature: Depended on by Google, Venmo, CERN, Baidu, Tencent, and teams across the world since 2009 - Familiar, Pythonic APIs that are carefully designed for safety, simplicity, flexibility, and ergonomics - Lightweight, with no runtime dependencies outside Python's standard library - Implemented in concise, well-factored, fully type-hinted Python code that is optimized for running efficiently as well as for long-term maintenance and stability (as well as `joy <#learning-from-bidict>`__) - Extensively `documented `__ - 100% test coverage running continuously across all supported Python versions (including property-based tests and benchmarks) 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. Enterprise Support ------------------ Enterprise-level support for bidict can be obtained via the `Tidelift subscription `__ or by `contacting me directly `__. I have a US-based LLC set up for invoicing, and I have 15+ years of professional experience delivering software and support to companies successfully. You can also sponsor my work through several platforms, including GitHub Sponsors. See the `Sponsoring <#sponsoring>`__ section below for details, including rationale and examples of companies supporting the open source projects they depend on. Voluntary Community Support --------------------------- Please search through already-asked questions and answers in `GitHub Discussions `__ and the `issue tracker `__ in case your question has already been addressed. Otherwise, please feel free to `start a new discussion `__ or `create a new issue `__ on GitHub for voluntary community support. 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 `__ - post in `GitHub Discussions `__ - `email me `__ Changelog --------- For bidict release notes, see the :doc:`changelog`. [#fn-changelog]_ Release Notifications --------------------- .. duplicated in CHANGELOG.rst: (Would use `.. include::` but GitHub's renderer doesn't support it.) Watch `bidict releases on GitHub `__ to be notified when new versions of bidict are published. Click the "Watch" dropdown, choose "Custom", and then choose "Releases". 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 ------------ I have been bidict's sole maintainer and `active contributor `__ since I started the project ~15 years ago. 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's renderer doesn't support 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 15+ years that I've been the sole maintainer. If bidict has helped you or your company accomplish your work, please sponsor my work through one of the following, and/or ask your company to do the same: - `GitHub `__ - `PayPal `__ - `Tidelift `__ - `thanks.dev `__ - `Gumroad `__ - `a support engagement with my LLC <#enterprise-support>`__ If you're not sure which to use, GitHub is an easy option, especially if you already have a GitHub account. Just choose a monthly or one-time amount, and GitHub handles everything else. Your bidict sponsorship on GitHub will automatically go on the same regular bill as any other GitHub charges you pay for. PayPal is another easy option for one-time contributions. See the following for rationale and examples of companies supporting the open source projects they depend on in this manner: - ``__ - ``__ - ``__ .. - ``__ .. - ``__ .. - ``__ 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-intro] ``__ | ``__ .. [#fn-changelog] ``__ | ``__ .. [#fn-learning] ``__ | ``__ .. [#fn-contributing] ``__ | ``__ bidict-0.23.1/SECURITY.rst000066400000000000000000000002061456445164300150460ustar00rootroot00000000000000Security ======== Please see `tidelift.com/security `__ for how to report a security issue in bidict. bidict-0.23.1/bidict/000077500000000000000000000000001456445164300143055ustar00rootroot00000000000000bidict-0.23.1/bidict/__init__.py000066400000000000000000000104561456445164300164240ustar00rootroot00000000000000# Copyright 2009-2024 Joshua Bronson. All rights reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # ============================================================================ # * Welcome to the bidict source code * # ============================================================================ # Reading through the code? You'll find a "Code review nav" comment like the one # below at the top and bottom of the key source files. Follow these cues to take # a path through the code that's optimized for familiarizing yourself with it. # # If you're not reading this on https://github.com/jab/bidict already, go there # to ensure you have the latest version of the code. While there, you can also # star the project, watch it for updates, fork the code, and submit an issue or # pull request with any proposed changes. More information can be found linked # from README.rst, which is also shown on https://github.com/jab/bidict. # * Code review nav * # ============================================================================ # Current: __init__.py Next: _abc.py → # ============================================================================ """The bidirectional mapping library for Python. ---- bidict by example: .. code-block:: python >>> from bidict import bidict >>> element_by_symbol = bidict({'H': 'hydrogen'}) >>> element_by_symbol['H'] 'hydrogen' >>> element_by_symbol.inverse['hydrogen'] 'H' Please see https://github.com/jab/bidict for the most up-to-date code and https://bidict.readthedocs.io for the most up-to-date documentation if you are reading this elsewhere. ---- .. :copyright: (c) 2009-2024 Joshua Bronson. .. :license: MPLv2. See LICENSE for details. """ # Use private aliases to not re-export these publicly (for Sphinx automodule with imported-members). from __future__ import annotations as _annotations from contextlib import suppress as _suppress from ._abc import BidirectionalMapping as BidirectionalMapping from ._abc import MutableBidirectionalMapping as MutableBidirectionalMapping from ._base import BidictBase as BidictBase from ._base import BidictKeysView as BidictKeysView from ._base import GeneratedBidictInverse as GeneratedBidictInverse from ._bidict import MutableBidict as MutableBidict from ._bidict import bidict as bidict from ._dup import DROP_NEW as DROP_NEW from ._dup import DROP_OLD as DROP_OLD from ._dup import ON_DUP_DEFAULT as ON_DUP_DEFAULT from ._dup import ON_DUP_DROP_OLD as ON_DUP_DROP_OLD from ._dup import ON_DUP_RAISE as ON_DUP_RAISE from ._dup import RAISE as RAISE from ._dup import OnDup as OnDup from ._dup import OnDupAction as OnDupAction from ._exc import BidictException as BidictException from ._exc import DuplicationError as DuplicationError from ._exc import KeyAndValueDuplicationError as KeyAndValueDuplicationError from ._exc import KeyDuplicationError as KeyDuplicationError from ._exc import ValueDuplicationError as ValueDuplicationError from ._frozen import frozenbidict as frozenbidict from ._iter import inverted as inverted from ._orderedbase import OrderedBidictBase as OrderedBidictBase from ._orderedbidict import OrderedBidict as OrderedBidict from .metadata import __author__ as __author__ from .metadata import __copyright__ as __copyright__ from .metadata import __description__ as __description__ from .metadata import __license__ as __license__ from .metadata import __url__ as __url__ from .metadata import __version__ as __version__ # Set __module__ of re-exported classes to the 'bidict' top-level module, so that e.g. # 'bidict.bidict' shows up as 'bidict.bidict` rather than 'bidict._bidict.bidict'. for _obj in tuple(locals().values()): # pragma: no cover if not getattr(_obj, '__module__', '').startswith('bidict.'): continue with _suppress(AttributeError): _obj.__module__ = 'bidict' # * Code review nav * # ============================================================================ # Current: __init__.py Next: _abc.py → # ============================================================================ bidict-0.23.1/bidict/_abc.py000066400000000000000000000061441456445164300155500ustar00rootroot00000000000000# Copyright 2009-2024 Joshua Bronson. All rights reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # * Code review nav * # (see comments in __init__.py) # ============================================================================ # ← Prev: __init__.py Current: _abc.py Next: _base.py → # ============================================================================ """Provide the :class:`BidirectionalMapping` abstract base class.""" from __future__ import annotations import typing as t from abc import abstractmethod from ._typing import KT from ._typing import VT class BidirectionalMapping(t.Mapping[KT, VT]): """Abstract base class for bidirectional mapping types. Extends :class:`collections.abc.Mapping` primarily by adding the (abstract) :attr:`inverse` property, which implementers of :class:`BidirectionalMapping` should override to return a reference to the inverse :class:`BidirectionalMapping` instance. """ __slots__ = () @property @abstractmethod def inverse(self) -> BidirectionalMapping[VT, KT]: """The inverse of this bidirectional mapping instance. *See also* :attr:`bidict.BidictBase.inverse`, :attr:`bidict.BidictBase.inv` :raises NotImplementedError: Meant to be overridden in subclasses. """ # The @abstractmethod decorator prevents subclasses from being instantiated unless they # override this method. But an overriding implementation may merely return super().inverse, # in which case this implementation is used. Raise NotImplementedError to indicate that # subclasses must actually provide their own implementation. raise NotImplementedError def __inverted__(self) -> t.Iterator[tuple[VT, KT]]: """Get an iterator over the items in :attr:`inverse`. This is functionally equivalent to iterating over the items in the forward mapping and inverting each one on the fly, but this provides a more efficient implementation: Assuming the already-inverted items are stored in :attr:`inverse`, just return an iterator over them directly. Providing this default implementation enables external functions, particularly :func:`~bidict.inverted`, to use this optimized implementation when available, instead of having to invert on the fly. *See also* :func:`bidict.inverted` """ return iter(self.inverse.items()) class MutableBidirectionalMapping(BidirectionalMapping[KT, VT], t.MutableMapping[KT, VT]): """Abstract base class for mutable bidirectional mapping types.""" __slots__ = () # * Code review nav * # ============================================================================ # ← Prev: __init__.py Current: _abc.py Next: _base.py → # ============================================================================ bidict-0.23.1/bidict/_base.py000066400000000000000000000575671456445164300157540ustar00rootroot00000000000000# Copyright 2009-2024 Joshua Bronson. All rights reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # * Code review nav * # (see comments in __init__.py) # ============================================================================ # ← Prev: _abc.py Current: _base.py Next: _frozen.py → # ============================================================================ """Provide :class:`BidictBase`.""" from __future__ import annotations import typing as t import weakref from itertools import starmap from operator import eq from types import MappingProxyType from ._abc import BidirectionalMapping from ._dup import DROP_NEW from ._dup import DROP_OLD from ._dup import ON_DUP_DEFAULT from ._dup import RAISE from ._dup import OnDup from ._exc import DuplicationError from ._exc import KeyAndValueDuplicationError from ._exc import KeyDuplicationError from ._exc import ValueDuplicationError from ._iter import inverted from ._iter import iteritems from ._typing import KT from ._typing import MISSING from ._typing import OKT from ._typing import OVT from ._typing import VT from ._typing import Maplike from ._typing import MapOrItems OldKV = t.Tuple[OKT[KT], OVT[VT]] DedupResult = t.Optional[OldKV[KT, VT]] Unwrites = t.List[t.Tuple[t.Any, ...]] BT = t.TypeVar('BT', bound='BidictBase[t.Any, t.Any]') class BidictKeysView(t.KeysView[KT], t.ValuesView[KT]): """Since the keys of a bidict are the values of its inverse (and vice versa), the :class:`~collections.abc.ValuesView` result of calling *bi.values()* is also a :class:`~collections.abc.KeysView` of *bi.inverse*. """ class BidictBase(BidirectionalMapping[KT, VT]): """Base class implementing :class:`BidirectionalMapping`.""" #: The default :class:`~bidict.OnDup` #: that governs behavior when a provided item #: duplicates the key or value of other item(s). #: #: *See also* #: :ref:`basic-usage:Values Must Be Unique` (https://bidict.rtfd.io/basic-usage.html#values-must-be-unique), #: :doc:`extending` (https://bidict.rtfd.io/extending.html) on_dup = ON_DUP_DEFAULT _fwdm: t.MutableMapping[KT, VT] #: the backing forward mapping (*key* → *val*) _invm: t.MutableMapping[VT, KT] #: the backing inverse mapping (*val* → *key*) # Use Any rather than KT/VT in the following to avoid "ClassVar cannot contain type variables" errors: _fwdm_cls: t.ClassVar[type[t.MutableMapping[t.Any, t.Any]]] = dict #: class of the backing forward mapping _invm_cls: t.ClassVar[type[t.MutableMapping[t.Any, t.Any]]] = dict #: class of the backing inverse mapping #: The class of the inverse bidict instance. _inv_cls: t.ClassVar[type[BidictBase[t.Any, t.Any]]] def __init_subclass__(cls) -> None: super().__init_subclass__() cls._init_class() @classmethod def _init_class(cls) -> None: cls._ensure_inv_cls() cls._set_reversed() __reversed__: t.ClassVar[t.Any] @classmethod def _set_reversed(cls) -> None: """Set __reversed__ for subclasses that do not set it explicitly according to whether backing mappings are reversible. """ if cls is not BidictBase: resolved = cls.__reversed__ overridden = resolved is not BidictBase.__reversed__ if overridden: # E.g. OrderedBidictBase, OrderedBidict return backing_reversible = all(issubclass(i, t.Reversible) for i in (cls._fwdm_cls, cls._invm_cls)) cls.__reversed__ = _fwdm_reversed if backing_reversible else None @classmethod def _ensure_inv_cls(cls) -> None: """Ensure :attr:`_inv_cls` is set, computing it dynamically if necessary. All subclasses provided in :mod:`bidict` are their own inverse classes, i.e., their backing forward and inverse mappings are both the same type, but users may define subclasses where this is not the case. This method ensures that the inverse class is computed correctly regardless. See: :ref:`extending:Dynamic Inverse Class Generation` (https://bidict.rtfd.io/extending.html#dynamic-inverse-class-generation) """ # This _ensure_inv_cls() method is (indirectly) corecursive with _make_inv_cls() below # in the case that we need to dynamically generate the inverse class: # 1. _ensure_inv_cls() calls cls._make_inv_cls() # 2. cls._make_inv_cls() calls type(..., (cls, ...), ...) to dynamically generate inv_cls # 3. Our __init_subclass__ hook (see above) is automatically called on inv_cls # 4. inv_cls.__init_subclass__() calls inv_cls._ensure_inv_cls() # 5. inv_cls._ensure_inv_cls() resolves to this implementation # (inv_cls deliberately does not override this), so we're back where we started. # But since the _make_inv_cls() call will have set inv_cls.__dict__._inv_cls, # just check if it's already set before calling _make_inv_cls() to prevent infinite recursion. if getattr(cls, '__dict__', {}).get('_inv_cls'): # Don't assume cls.__dict__ (e.g. mypyc native class) return cls._inv_cls = cls._make_inv_cls() @classmethod def _make_inv_cls(cls: type[BT]) -> type[BT]: diff = cls._inv_cls_dict_diff() cls_is_own_inv = all(getattr(cls, k, MISSING) == v for (k, v) in diff.items()) if cls_is_own_inv: return cls # Suppress auto-calculation of _inv_cls's _inv_cls since we know it already. # Works with the guard in BidictBase._ensure_inv_cls() to prevent infinite recursion. diff['_inv_cls'] = cls inv_cls = type(f'{cls.__name__}Inv', (cls, GeneratedBidictInverse), diff) inv_cls.__module__ = cls.__module__ return t.cast(t.Type[BT], inv_cls) @classmethod def _inv_cls_dict_diff(cls) -> dict[str, t.Any]: return { '_fwdm_cls': cls._invm_cls, '_invm_cls': cls._fwdm_cls, } def __init__(self, arg: MapOrItems[KT, VT] = (), /, **kw: VT) -> None: """Make a new bidirectional mapping. The signature behaves like that of :class:`dict`. ktems passed via positional arg are processed first, followed by any items passed via keyword argument. Any duplication encountered along the way is handled as per :attr:`on_dup`. """ self._fwdm = self._fwdm_cls() self._invm = self._invm_cls() self._update(arg, kw, rollback=False) # If Python ever adds support for higher-kinded types, `inverse` could use them, e.g. # def inverse(self: BT[KT, VT]) -> BT[VT, KT]: # Ref: https://github.com/python/typing/issues/548#issuecomment-621571821 @property def inverse(self) -> BidictBase[VT, KT]: """The inverse of this bidirectional mapping instance.""" # When `bi.inverse` is called for the first time, this method # computes the inverse instance, stores it for subsequent use, and then # returns it. It also stores a reference on `bi.inverse` back to `bi`, # but uses a weakref to avoid creating a reference cycle. Strong references # to inverse instances are stored in ._inv, and weak references are stored # in ._invweak. # First check if a strong reference is already stored. inv: BidictBase[VT, KT] | None = getattr(self, '_inv', None) if inv is not None: return inv # Next check if a weak reference is already stored. invweak = getattr(self, '_invweak', None) if invweak is not None: inv = invweak() # Try to resolve a strong reference and return it. if inv is not None: return inv # No luck. Compute the inverse reference and store it for subsequent use. inv = self._make_inverse() self._inv: BidictBase[VT, KT] | None = inv self._invweak: weakref.ReferenceType[BidictBase[VT, KT]] | None = None # Also store a weak reference back to `instance` on its inverse instance, so that # the second `.inverse` access in `bi.inverse.inverse` hits the cached weakref. inv._inv = None inv._invweak = weakref.ref(self) # In e.g. `bidict().inverse.inverse`, this design ensures that a strong reference # back to the original instance is retained before its refcount drops to zero, # avoiding an unintended potential deallocation. return inv def _make_inverse(self) -> BidictBase[VT, KT]: inv: BidictBase[VT, KT] = self._inv_cls() inv._fwdm = self._invm inv._invm = self._fwdm return inv @property def inv(self) -> BidictBase[VT, KT]: """Alias for :attr:`inverse`.""" return self.inverse def __repr__(self) -> str: """See :func:`repr`.""" clsname = self.__class__.__name__ items = dict(self.items()) if self else '' return f'{clsname}({items})' def values(self) -> BidictKeysView[VT]: """A set-like object providing a view on the contained values. Since the values of a bidict are equivalent to the keys of its inverse, this method returns a set-like object for this bidict's values rather than just a collections.abc.ValuesView. This object supports set operations like union and difference, and constant- rather than linear-time containment checks, and is no more expensive to provide than the less capable collections.abc.ValuesView would be. See :meth:`keys` for more information. """ return t.cast(BidictKeysView[VT], self.inverse.keys()) def keys(self) -> t.KeysView[KT]: """A set-like object providing a view on the contained keys. When *b._fwdm* is a :class:`dict`, *b.keys()* returns a *dict_keys* object that behaves exactly the same as *collections.abc.KeysView(b)*, except for - offering better performance - being reversible on Python 3.8+ - having a .mapping attribute in Python 3.10+ that exposes a mappingproxy to *b._fwdm*. """ fwdm, fwdm_cls = self._fwdm, self._fwdm_cls return fwdm.keys() if fwdm_cls is dict else BidictKeysView(self) def items(self) -> t.ItemsView[KT, VT]: """A set-like object providing a view on the contained items. When *b._fwdm* is a :class:`dict`, *b.items()* returns a *dict_items* object that behaves exactly the same as *collections.abc.ItemsView(b)*, except for: - offering better performance - being reversible on Python 3.8+ - having a .mapping attribute in Python 3.10+ that exposes a mappingproxy to *b._fwdm*. """ return self._fwdm.items() if self._fwdm_cls is dict else super().items() # The inherited collections.abc.Mapping.__contains__() method is implemented by doing a `try` # `except KeyError` around `self[key]`. The following implementation is much faster, # especially in the missing case. def __contains__(self, key: t.Any) -> bool: """True if the mapping contains the specified key, else False.""" return key in self._fwdm # The inherited collections.abc.Mapping.__eq__() method is implemented in terms of an inefficient # `dict(self.items()) == dict(other.items())` comparison, so override it with a # more efficient implementation. def __eq__(self, other: object) -> bool: """*x.__eq__(other) ⟺ x == other* Equivalent to *dict(x.items()) == dict(other.items())* but more efficient. Note that :meth:`bidict's __eq__() ` implementation is inherited by subclasses, in particular by the ordered bidict subclasses, so even with ordered bidicts, :ref:`== comparison is order-insensitive ` (https://bidict.rtfd.io/other-bidict-types.html#eq-is-order-insensitive). *See also* :meth:`equals_order_sensitive` """ if isinstance(other, t.Mapping): return self._fwdm.items() == other.items() # Ref: https://docs.python.org/3/library/constants.html#NotImplemented return NotImplemented def equals_order_sensitive(self, other: object) -> bool: """Order-sensitive equality check. *See also* :ref:`eq-order-insensitive` (https://bidict.rtfd.io/other-bidict-types.html#eq-is-order-insensitive) """ if not isinstance(other, t.Mapping) or len(self) != len(other): return False return all(starmap(eq, zip(self.items(), other.items()))) def _dedup(self, key: KT, val: VT, on_dup: OnDup) -> DedupResult[KT, VT]: """Check *key* and *val* for any duplication in self. Handle any duplication as per the passed in *on_dup*. If (key, val) is already present, return None since writing (key, val) would be a no-op. If duplication is found and the corresponding :class:`~bidict.OnDupAction` is :attr:`~bidict.DROP_NEW`, return None. If duplication is found and the corresponding :class:`~bidict.OnDupAction` is :attr:`~bidict.RAISE`, raise the appropriate exception. If duplication is found and the corresponding :class:`~bidict.OnDupAction` is :attr:`~bidict.DROP_OLD`, or if no duplication is found, return *(oldkey, oldval)*. """ fwdm, invm = self._fwdm, self._invm oldval: OVT[VT] = fwdm.get(key, MISSING) oldkey: OKT[KT] = invm.get(val, MISSING) isdupkey, isdupval = oldval is not MISSING, oldkey is not MISSING if isdupkey and isdupval: if key == oldkey: assert val == oldval # (key, val) duplicates an existing item -> no-op. return None # key and val each duplicate a different existing item. if on_dup.val is RAISE: raise KeyAndValueDuplicationError(key, 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. elif isdupkey: if on_dup.key is RAISE: raise KeyDuplicationError(key) if on_dup.key is DROP_NEW: return None assert on_dup.key is DROP_OLD # Fall through to the return statement on the last line. elif isdupval: if on_dup.val is RAISE: raise ValueDuplicationError(val) if on_dup.val is DROP_NEW: return None assert on_dup.val is DROP_OLD # Fall through to the return statement on the last line. # else neither isdupkey nor isdupval. return oldkey, oldval def _write(self, newkey: KT, newval: VT, oldkey: OKT[KT], oldval: OVT[VT], unwrites: Unwrites | None) -> None: """Insert (newkey, newval), extending *unwrites* with associated inverse operations if provided. *oldkey* and *oldval* are as returned by :meth:`_dedup`. If *unwrites* is not None, it is extended with the inverse operations necessary to undo the write. This design allows :meth:`_update` to roll back a partially applied update that fails part-way through when necessary. This design also allows subclasses that require additional operations to easily extend this implementation. For example, :class:`bidict.OrderedBidictBase` calls this inherited implementation, and then extends *unwrites* with additional operations needed to keep its internal linked list nodes consistent with its items' order as changes are made. """ fwdm, invm = self._fwdm, self._invm fwdm_set, invm_set = fwdm.__setitem__, invm.__setitem__ fwdm_del, invm_del = fwdm.__delitem__, invm.__delitem__ # Always perform the following writes regardless of duplication. fwdm_set(newkey, newval) invm_set(newval, newkey) if oldval is MISSING and oldkey is MISSING: # no key or value duplication # {0: 1, 2: 3} | {4: 5} => {0: 1, 2: 3, 4: 5} if unwrites is not None: unwrites.extend(( (fwdm_del, newkey), (invm_del, newval), )) elif oldval is not MISSING and oldkey is not MISSING: # key and value duplication across two different items # {0: 1, 2: 3} | {0: 3} => {0: 3} fwdm_del(oldkey) invm_del(oldval) if unwrites is not None: unwrites.extend(( (fwdm_set, newkey, oldval), (invm_set, oldval, newkey), (fwdm_set, oldkey, newval), (invm_set, newval, oldkey), )) elif oldval is not MISSING: # just key duplication # {0: 1, 2: 3} | {2: 4} => {0: 1, 2: 4} invm_del(oldval) if unwrites is not None: unwrites.extend(( (fwdm_set, newkey, oldval), (invm_set, oldval, newkey), (invm_del, newval), )) else: assert oldkey is not MISSING # just value duplication # {0: 1, 2: 3} | {4: 3} => {0: 1, 4: 3} fwdm_del(oldkey) if unwrites is not None: unwrites.extend(( (fwdm_set, oldkey, newval), (invm_set, newval, oldkey), (fwdm_del, newkey), )) def _update( self, arg: MapOrItems[KT, VT], kw: t.Mapping[str, VT] = MappingProxyType({}), *, rollback: bool | None = None, on_dup: OnDup | None = None, ) -> None: """Update with the items from *arg* and *kw*, maybe failing and rolling back as per *on_dup* and *rollback*.""" # Note: We must process input in a single pass, since arg may be a generator. if not isinstance(arg, (t.Iterable, Maplike)): raise TypeError(f"'{arg.__class__.__name__}' object is not iterable") if not arg and not kw: return if on_dup is None: on_dup = self.on_dup if rollback is None: rollback = RAISE in on_dup # Fast path when we're empty and updating only from another bidict (i.e. no dup vals in new items). if not self and not kw and isinstance(arg, BidictBase): self._init_from(arg) return # Fast path when we're adding more items than we contain already and rollback is enabled: # Update a copy of self with rollback disabled. Fail if that fails, otherwise become the copy. if rollback and isinstance(arg, t.Sized) and len(arg) + len(kw) > len(self): tmp = self.copy() tmp._update(arg, kw, rollback=False, on_dup=on_dup) self._init_from(tmp) return # In all other cases, benchmarking has indicated that the update is best implemented as follows: # For each new item, perform a dup check (raising if necessary), and apply the associated writes we need to # perform on our backing _fwdm and _invm mappings. If rollback is enabled, also compute the associated unwrites # as we go. If the update results in a DuplicationError and rollback is enabled, apply the accumulated unwrites # before raising, to ensure that we fail clean. write = self._write unwrites: Unwrites | None = [] if rollback else None for key, val in iteritems(arg, **kw): try: dedup_result = self._dedup(key, val, on_dup) except DuplicationError: if unwrites is not None: for fn, *args in reversed(unwrites): fn(*args) raise if dedup_result is not None: write(key, val, *dedup_result, unwrites=unwrites) def __copy__(self: BT) -> BT: """Used for the copy protocol. See the :mod:`copy` module.""" return self.copy() def copy(self: BT) -> BT: """Make a (shallow) copy of this bidict.""" # Could just `return self.__class__(self)` here, but the below is faster. The former # would copy this bidict's items into a new instance one at a time (checking for duplication # for each item), whereas the below copies from the backing mappings all at once, and foregoes # item-by-item duplication checking since the backing mappings have been checked already. return self._from_other(self.__class__, self) @staticmethod def _from_other(bt: type[BT], other: MapOrItems[KT, VT], inv: bool = False) -> BT: """Fast, private constructor based on :meth:`_init_from`. If *inv* is true, return the inverse of the instance instead of the instance itself. (Useful for pickling with dynamically-generated inverse classes -- see :meth:`__reduce__`.) """ inst = bt() inst._init_from(other) return t.cast(BT, inst.inverse) if inv else inst def _init_from(self, other: MapOrItems[KT, VT]) -> None: """Fast init from *other*, bypassing item-by-item duplication checking.""" self._fwdm.clear() self._invm.clear() self._fwdm.update(other) # If other is a bidict, use its existing backing inverse mapping, otherwise # other could be a generator that's now exhausted, so invert self._fwdm on the fly. inv = other.inverse if isinstance(other, BidictBase) else inverted(self._fwdm) self._invm.update(inv) # other's type is Mapping rather than Maplike since bidict() | SupportsKeysAndGetItem({}) # raises a TypeError, just like dict() | SupportsKeysAndGetItem({}) does. def __or__(self: BT, other: t.Mapping[KT, VT]) -> BT: """Return self|other.""" if not isinstance(other, t.Mapping): return NotImplemented new = self.copy() new._update(other, rollback=False) return new def __ror__(self: BT, other: t.Mapping[KT, VT]) -> BT: """Return other|self.""" if not isinstance(other, t.Mapping): return NotImplemented new = self.__class__(other) new._update(self, rollback=False) return new def __len__(self) -> int: """The number of contained items.""" return len(self._fwdm) def __iter__(self) -> t.Iterator[KT]: """Iterator over the contained keys.""" return iter(self._fwdm) def __getitem__(self, key: KT) -> VT: """*x.__getitem__(key) ⟺ x[key]*""" return self._fwdm[key] def __reduce__(self) -> tuple[t.Any, ...]: """Return state information for pickling.""" cls = self.__class__ inst: t.Mapping[t.Any, t.Any] = self # If this bidict's class is dynamically generated, pickle the inverse instead, whose (presumably not # dynamically generated) class the caller is more likely to have a reference to somewhere in sys.modules # that pickle can discover. if should_invert := isinstance(self, GeneratedBidictInverse): cls = self._inv_cls inst = self.inverse return self._from_other, (cls, dict(inst), should_invert) # See BidictBase._set_reversed() above. def _fwdm_reversed(self: BidictBase[KT, t.Any]) -> t.Iterator[KT]: """Iterator over the contained keys in reverse order.""" assert isinstance(self._fwdm, t.Reversible) return reversed(self._fwdm) BidictBase._init_class() class GeneratedBidictInverse: """Base class for dynamically-generated inverse bidict classes.""" # * Code review nav * # ============================================================================ # ← Prev: _abc.py Current: _base.py Next: _frozen.py → # ============================================================================ bidict-0.23.1/bidict/_bidict.py000066400000000000000000000154131456445164300162600ustar00rootroot00000000000000# Copyright 2009-2024 Joshua Bronson. All rights reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # * Code review nav * # (see comments in __init__.py) # ============================================================================ # ← Prev: _frozen.py Current: _bidict.py Next: _orderedbase.py → # ============================================================================ """Provide :class:`MutableBidict` and :class:`bidict`.""" from __future__ import annotations import typing as t from ._abc import MutableBidirectionalMapping from ._base import BidictBase from ._dup import ON_DUP_DROP_OLD from ._dup import ON_DUP_RAISE from ._dup import OnDup from ._typing import DT from ._typing import KT from ._typing import MISSING from ._typing import ODT from ._typing import VT from ._typing import MapOrItems class MutableBidict(BidictBase[KT, VT], MutableBidirectionalMapping[KT, VT]): """Base class for mutable bidirectional mappings.""" if t.TYPE_CHECKING: @property def inverse(self) -> MutableBidict[VT, KT]: ... @property def inv(self) -> MutableBidict[VT, KT]: ... def _pop(self, key: KT) -> VT: val = self._fwdm.pop(key) del self._invm[val] return val def __delitem__(self, key: KT) -> None: """*x.__delitem__(y) ⟺ del x[y]*""" self._pop(key) def __setitem__(self, key: KT, val: VT) -> None: """Set the value for *key* to *val*. If *key* is already associated with *val*, this is a no-op. If *key* is already associated with a different value, the old value will be replaced with *val*, as with dict's :meth:`__setitem__`. If *val* is already associated with a different key, an exception is raised to protect against accidental removal of the key that's currently associated with *val*. Use :meth:`put` instead if you want to specify different behavior in the case that the provided key or value duplicates an existing one. Or use :meth:`forceput` to unconditionally associate *key* with *val*, replacing any existing items as necessary to preserve uniqueness. :raises bidict.ValueDuplicationError: if *val* duplicates that of an existing item. :raises bidict.KeyAndValueDuplicationError: if *key* duplicates the key of an existing item and *val* duplicates the value of a different existing item. """ self.put(key, val, on_dup=self.on_dup) def put(self, key: KT, val: VT, on_dup: OnDup = ON_DUP_RAISE) -> None: """Associate *key* with *val*, honoring the :class:`OnDup` given in *on_dup*. For example, if *on_dup* is :attr:`~bidict.ON_DUP_RAISE`, then *key* will be associated with *val* if and only if *key* is not already associated with an existing value and *val* is not already associated with an existing key, otherwise an exception will be raised. If *key* is already associated with *val*, this is a no-op. :raises bidict.KeyDuplicationError: if attempting to insert an item whose key only duplicates an existing item's, and *on_dup.key* is :attr:`~bidict.RAISE`. :raises bidict.ValueDuplicationError: if attempting to insert an item whose value only duplicates an existing item's, and *on_dup.val* is :attr:`~bidict.RAISE`. :raises bidict.KeyAndValueDuplicationError: if attempting to insert an item whose key duplicates one existing item's, and whose value duplicates another existing item's, and *on_dup.val* is :attr:`~bidict.RAISE`. """ self._update(((key, val),), on_dup=on_dup) def forceput(self, key: KT, val: VT) -> None: """Associate *key* with *val* unconditionally. Replace any existing mappings containing key *key* or value *val* as necessary to preserve uniqueness. """ self.put(key, val, on_dup=ON_DUP_DROP_OLD) def clear(self) -> None: """Remove all items.""" self._fwdm.clear() self._invm.clear() @t.overload def pop(self, key: KT, /) -> VT: ... @t.overload def pop(self, key: KT, default: DT = ..., /) -> VT | DT: ... def pop(self, key: KT, default: ODT[DT] = MISSING, /) -> VT | DT: """*x.pop(k[, d]) → v* Remove specified key and return the corresponding value. :raises KeyError: if *key* is not found and no *default* is provided. """ try: return self._pop(key) except KeyError: if default is MISSING: raise return default def popitem(self) -> tuple[KT, VT]: """*x.popitem() → (k, v)* Remove and return some item as a (key, value) pair. :raises KeyError: if *x* is empty. """ key, val = self._fwdm.popitem() del self._invm[val] return key, val def update(self, arg: MapOrItems[KT, VT] = (), /, **kw: VT) -> None: """Like calling :meth:`putall` with *self.on_dup* passed for *on_dup*.""" self._update(arg, kw=kw) def forceupdate(self, arg: MapOrItems[KT, VT] = (), /, **kw: VT) -> None: """Like a bulk :meth:`forceput`.""" self._update(arg, kw=kw, on_dup=ON_DUP_DROP_OLD) def putall(self, items: MapOrItems[KT, VT], on_dup: OnDup = ON_DUP_RAISE) -> None: """Like a bulk :meth:`put`. If one of the given items causes an exception to be raised, none of the items is inserted. """ self._update(items, on_dup=on_dup) # other's type is Mapping rather than Maplike since bidict() |= SupportsKeysAndGetItem({}) # raises a TypeError, just like dict() |= SupportsKeysAndGetItem({}) does. def __ior__(self, other: t.Mapping[KT, VT]) -> MutableBidict[KT, VT]: """Return self|=other.""" self.update(other) return self class bidict(MutableBidict[KT, VT]): """The main bidirectional mapping type. See :ref:`intro:Introduction` and :ref:`basic-usage:Basic Usage` to get started (also available at https://bidict.rtfd.io). """ if t.TYPE_CHECKING: @property def inverse(self) -> bidict[VT, KT]: ... @property def inv(self) -> bidict[VT, KT]: ... # * Code review nav * # ============================================================================ # ← Prev: _frozen.py Current: _bidict.py Next: _orderedbase.py → # ============================================================================ bidict-0.23.1/bidict/_dup.py000066400000000000000000000040371456445164300156120ustar00rootroot00000000000000# Copyright 2009-2024 Joshua Bronson. All rights reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. """Provide :class:`OnDup` and related functionality.""" from __future__ import annotations import typing as t 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.__class__.__name__}.{self.name}' RAISE: t.Final[OnDupAction] = OnDupAction.RAISE DROP_OLD: t.Final[OnDupAction] = OnDupAction.DROP_OLD DROP_NEW: t.Final[OnDupAction] = OnDupAction.DROP_NEW class OnDup(t.NamedTuple): r"""A combination of :class:`~bidict.OnDupAction`\s specifying how to handle various types of duplication. The :attr:`~OnDup.key` field specifies what action to take when a duplicate key is encountered. The :attr:`~OnDup.val` field specifies what action to take when a duplicate value is encountered. In the case of both key and value duplication across two different items, only :attr:`~OnDup.val` is used. *See also* :ref:`basic-usage:Values Must Be Unique` (https://bidict.rtfd.io/basic-usage.html#values-must-be-unique) """ key: OnDupAction = DROP_OLD val: OnDupAction = RAISE #: Default :class:`OnDup` used for the #: :meth:`~bidict.bidict.__init__`, #: :meth:`~bidict.bidict.__setitem__`, and #: :meth:`~bidict.bidict.update` methods. ON_DUP_DEFAULT: t.Final[OnDup] = OnDup(key=DROP_OLD, val=RAISE) #: An :class:`OnDup` whose members are all :obj:`RAISE`. ON_DUP_RAISE: t.Final[OnDup] = OnDup(key=RAISE, val=RAISE) #: An :class:`OnDup` whose members are all :obj:`DROP_OLD`. ON_DUP_DROP_OLD: t.Final[OnDup] = OnDup(key=DROP_OLD, val=DROP_OLD) bidict-0.23.1/bidict/_exc.py000066400000000000000000000020521456445164300155740ustar00rootroot00000000000000# Copyright 2009-2024 Joshua Bronson. All rights reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. """Provide all bidict exceptions.""" from __future__ import annotations class BidictException(Exception): """Base class for bidict exceptions.""" class DuplicationError(BidictException): """Base class for exceptions raised when uniqueness is violated as per the :attr:`~bidict.RAISE` :class:`~bidict.OnDupAction`. """ class KeyDuplicationError(DuplicationError): """Raised when a given key is not unique.""" class ValueDuplicationError(DuplicationError): """Raised when a given value is not unique.""" class KeyAndValueDuplicationError(KeyDuplicationError, ValueDuplicationError): """Raised when a given item's key and value are not unique. That is, its key duplicates that of another item, and its value duplicates that of a different other item. """ bidict-0.23.1/bidict/_frozen.py000066400000000000000000000033531456445164300163250ustar00rootroot00000000000000# Copyright 2009-2024 Joshua Bronson. All rights reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # * Code review nav * # (see comments in __init__.py) # ============================================================================ # ← Prev: _base.py Current: _frozen.py Next: _bidict.py → # ============================================================================ """Provide :class:`frozenbidict`, an immutable, hashable bidirectional mapping type.""" from __future__ import annotations import typing as t from ._base import BidictBase from ._typing import KT from ._typing import VT class frozenbidict(BidictBase[KT, VT]): """Immutable, hashable bidict type.""" _hash: int if t.TYPE_CHECKING: @property def inverse(self) -> frozenbidict[VT, KT]: ... @property def inv(self) -> frozenbidict[VT, KT]: ... def __hash__(self) -> int: """The hash of this bidict as determined by its items.""" if getattr(self, '_hash', None) is None: # The following is like hash(frozenset(self.items())) # but more memory efficient. See also: https://bugs.python.org/issue46684 self._hash = t.ItemsView(self)._hash() return self._hash # * Code review nav * # ============================================================================ # ← Prev: _base.py Current: _frozen.py Next: _bidict.py → # ============================================================================ bidict-0.23.1/bidict/_iter.py000066400000000000000000000027721456445164300157710ustar00rootroot00000000000000# Copyright 2009-2024 Joshua Bronson. All rights reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. """Functions for iterating over items in a mapping.""" from __future__ import annotations import typing as t from operator import itemgetter from ._typing import KT from ._typing import VT from ._typing import ItemsIter from ._typing import Maplike from ._typing import MapOrItems def iteritems(arg: MapOrItems[KT, VT] = (), /, **kw: VT) -> ItemsIter[KT, VT]: """Yield the items from *arg* and *kw* in the order given.""" if isinstance(arg, t.Mapping): yield from arg.items() elif isinstance(arg, Maplike): yield from ((k, arg[k]) for k in arg.keys()) else: yield from arg yield from t.cast(ItemsIter[KT, VT], kw.items()) swap: t.Final = itemgetter(1, 0) def inverted(arg: MapOrItems[KT, VT]) -> ItemsIter[VT, KT]: """Yield the inverse items of the provided object. If *arg* has a :func:`callable` ``__inverted__`` attribute, return the result of calling it. Otherwise, return an iterator over the items in `arg`, inverting each item on the fly. *See also* :attr:`bidict.BidirectionalMapping.__inverted__` """ invattr = getattr(arg, '__inverted__', None) if callable(invattr): inv: ItemsIter[VT, KT] = invattr() return inv return map(swap, iteritems(arg)) bidict-0.23.1/bidict/_orderedbase.py000066400000000000000000000213561456445164300173040ustar00rootroot00000000000000# Copyright 2009-2024 Joshua Bronson. All rights reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # * Code review nav * # (see comments in __init__.py) # ============================================================================ # ← Prev: _bidict.py Current: _orderedbase.py Next: _orderedbidict.py → # ============================================================================ """Provide :class:`OrderedBidictBase`.""" from __future__ import annotations import typing as t from weakref import ref as weakref from ._base import BidictBase from ._base import Unwrites from ._bidict import bidict from ._iter import iteritems from ._typing import KT from ._typing import MISSING from ._typing import OKT from ._typing import OVT from ._typing import VT from ._typing import MapOrItems AT = t.TypeVar('AT') # attr type class WeakAttr(t.Generic[AT]): """Descriptor to automatically manage (de)referencing the given slot as a weakref. See https://docs.python.org/3/howto/descriptor.html#managed-attributes for an intro to using descriptors like this for managed attributes. """ def __init__(self, *, slot: str) -> None: self.slot = slot def __set__(self, instance: t.Any, value: AT) -> None: setattr(instance, self.slot, weakref(value)) def __get__(self, instance: t.Any, __owner: t.Any = None) -> AT: return t.cast(AT, getattr(instance, self.slot)()) class Node: """A node in a circular doubly-linked list used to encode the order of items in an ordered bidict. A weak reference to the previous node is stored to avoid creating strong reference cycles. Referencing/dereferencing the weakref is handled automatically by :class:`WeakAttr`. """ prv: WeakAttr[Node] = WeakAttr(slot='_prv_weak') __slots__ = ('__weakref__', '_prv_weak', 'nxt') nxt: Node | WeakAttr[Node] # Allow subclasses to use a WeakAttr for nxt too (see SentinelNode) def __init__(self, prv: Node, nxt: Node) -> None: self.prv = prv self.nxt = nxt def unlink(self) -> None: """Remove self from in between prv and nxt. Self's references to prv and nxt are retained so it can be relinked (see below). """ self.prv.nxt = self.nxt self.nxt.prv = self.prv def relink(self) -> None: """Restore self between prv and nxt after unlinking (see above).""" self.prv.nxt = self.nxt.prv = self class SentinelNode(Node): """Special node in a circular doubly-linked list that links the first node with the last node. When its next and previous references point back to itself it represents an empty list. """ nxt: WeakAttr[Node] = WeakAttr(slot='_nxt_weak') __slots__ = ('_nxt_weak',) def __init__(self) -> None: super().__init__(self, self) def iternodes(self, *, reverse: bool = False) -> t.Iterator[Node]: """Iterator yielding nodes in the requested order.""" attr = 'prv' if reverse else 'nxt' node = getattr(self, attr) while node is not self: yield node node = getattr(node, attr) def new_last_node(self) -> Node: """Create and return a new terminal node.""" old_last = self.prv new_last = Node(old_last, self) old_last.nxt = self.prv = new_last return new_last class OrderedBidictBase(BidictBase[KT, VT]): """Base class implementing an ordered :class:`BidirectionalMapping`.""" _node_by_korv: bidict[t.Any, Node] _bykey: bool def __init__(self, arg: MapOrItems[KT, VT] = (), /, **kw: VT) -> None: """Make a new ordered bidirectional mapping. The signature behaves like that of :class:`dict`. Items passed in are added in the order they are passed, respecting the :attr:`~bidict.BidictBase.on_dup` class attribute in the process. The order in which items are inserted is remembered, similar to :class:`collections.OrderedDict`. """ self._sntl = SentinelNode() self._node_by_korv = bidict() self._bykey = True super().__init__(arg, **kw) if t.TYPE_CHECKING: @property def inverse(self) -> OrderedBidictBase[VT, KT]: ... @property def inv(self) -> OrderedBidictBase[VT, KT]: ... def _make_inverse(self) -> OrderedBidictBase[VT, KT]: inv = t.cast(OrderedBidictBase[VT, KT], super()._make_inverse()) inv._sntl = self._sntl inv._node_by_korv = self._node_by_korv inv._bykey = not self._bykey return inv def _assoc_node(self, node: Node, key: KT, val: VT) -> None: korv = key if self._bykey else val self._node_by_korv.forceput(korv, node) def _dissoc_node(self, node: Node) -> None: del self._node_by_korv.inverse[node] node.unlink() def _init_from(self, other: MapOrItems[KT, VT]) -> None: """See :meth:`BidictBase._init_from`.""" super()._init_from(other) bykey = self._bykey korv_by_node = self._node_by_korv.inverse korv_by_node.clear() korv_by_node_set = korv_by_node.__setitem__ self._sntl.nxt = self._sntl.prv = self._sntl new_node = self._sntl.new_last_node for k, v in iteritems(other): korv_by_node_set(new_node(), k if bykey else v) def _write(self, newkey: KT, newval: VT, oldkey: OKT[KT], oldval: OVT[VT], unwrites: Unwrites | None) -> None: """See :meth:`bidict.BidictBase._spec_write`.""" super()._write(newkey, newval, oldkey, oldval, unwrites) assoc, dissoc = self._assoc_node, self._dissoc_node node_by_korv, bykey = self._node_by_korv, self._bykey if oldval is MISSING and oldkey is MISSING: # no key or value duplication # {0: 1, 2: 3} | {4: 5} => {0: 1, 2: 3, 4: 5} newnode = self._sntl.new_last_node() assoc(newnode, newkey, newval) if unwrites is not None: unwrites.append((dissoc, newnode)) elif oldval is not MISSING and oldkey is not MISSING: # key and value duplication across two different items # {0: 1, 2: 3} | {0: 3} => {0: 3} # n1, n2 => n1 (collapse n1 and n2 into n1) # oldkey: 2, oldval: 1, oldnode: n2, newkey: 0, newval: 3, newnode: n1 if bykey: oldnode = node_by_korv[oldkey] newnode = node_by_korv[newkey] else: oldnode = node_by_korv[newval] newnode = node_by_korv[oldval] dissoc(oldnode) assoc(newnode, newkey, newval) if unwrites is not None: unwrites.extend(( (assoc, newnode, newkey, oldval), (assoc, oldnode, oldkey, newval), (oldnode.relink,), )) elif oldval is not MISSING: # just key duplication # {0: 1, 2: 3} | {2: 4} => {0: 1, 2: 4} # oldkey: MISSING, oldval: 3, newkey: 2, newval: 4 node = node_by_korv[newkey if bykey else oldval] assoc(node, newkey, newval) if unwrites is not None: unwrites.append((assoc, node, newkey, oldval)) else: assert oldkey is not MISSING # just value duplication # {0: 1, 2: 3} | {4: 3} => {0: 1, 4: 3} # oldkey: 2, oldval: MISSING, newkey: 4, newval: 3 node = node_by_korv[oldkey if bykey else newval] assoc(node, newkey, newval) if unwrites is not None: unwrites.append((assoc, node, oldkey, newval)) def __iter__(self) -> t.Iterator[KT]: """Iterator over the contained keys in insertion order.""" return self._iter(reverse=False) def __reversed__(self) -> t.Iterator[KT]: """Iterator over the contained keys in reverse insertion order.""" return self._iter(reverse=True) def _iter(self, *, reverse: bool = False) -> t.Iterator[KT]: nodes = self._sntl.iternodes(reverse=reverse) korv_by_node = self._node_by_korv.inverse if self._bykey: for node in nodes: yield korv_by_node[node] else: key_by_val = self._invm for node in nodes: val = korv_by_node[node] yield key_by_val[val] # * Code review nav * # ============================================================================ # ← Prev: _bidict.py Current: _orderedbase.py Next: _orderedbidict.py → # ============================================================================ bidict-0.23.1/bidict/_orderedbidict.py000066400000000000000000000156501456445164300176300ustar00rootroot00000000000000# Copyright 2009-2024 Joshua Bronson. All rights reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # * Code review nav * # (see comments in __init__.py) # ============================================================================ # ← Prev: _orderedbase.py Current: _orderedbidict.py # ============================================================================ """Provide :class:`OrderedBidict`.""" from __future__ import annotations import typing as t from collections.abc import Set from ._base import BidictKeysView from ._bidict import MutableBidict from ._orderedbase import OrderedBidictBase from ._typing import KT from ._typing import VT class OrderedBidict(OrderedBidictBase[KT, VT], MutableBidict[KT, VT]): """Mutable bidict type that maintains items in insertion order.""" if t.TYPE_CHECKING: @property def inverse(self) -> OrderedBidict[VT, KT]: ... @property def inv(self) -> OrderedBidict[VT, KT]: ... def clear(self) -> None: """Remove all items.""" super().clear() self._node_by_korv.clear() self._sntl.nxt = self._sntl.prv = self._sntl def _pop(self, key: KT) -> VT: val = super()._pop(key) node = self._node_by_korv[key if self._bykey else val] self._dissoc_node(node) return val def popitem(self, last: bool = True) -> tuple[KT, VT]: """*b.popitem() → (k, v)* If *last* is true, remove and return the most recently added item as a (key, value) pair. Otherwise, remove and return the least recently added item. :raises KeyError: if *b* is empty. """ if not self: raise KeyError('OrderedBidict is empty') node = getattr(self._sntl, 'prv' if last else 'nxt') korv = self._node_by_korv.inverse[node] if self._bykey: return korv, self._pop(korv) return self.inverse._pop(korv), korv def move_to_end(self, key: KT, last: bool = True) -> None: """Move the item with the given key to the end if *last* is true, else to the beginning. :raises KeyError: if *key* is missing """ korv = key if self._bykey else self._fwdm[key] node = self._node_by_korv[korv] node.prv.nxt = node.nxt node.nxt.prv = node.prv sntl = self._sntl if last: lastnode = sntl.prv node.prv = lastnode node.nxt = sntl sntl.prv = lastnode.nxt = node else: firstnode = sntl.nxt node.prv = sntl node.nxt = firstnode sntl.nxt = firstnode.prv = node # Override the keys() and items() implementations inherited from BidictBase, # which may delegate to the backing _fwdm dict, since this is a mutable ordered bidict, # and therefore the ordering of items can get out of sync with the backing mappings # after mutation. (Need not override values() because it delegates to .inverse.keys().) def keys(self) -> t.KeysView[KT]: """A set-like object providing a view on the contained keys.""" return _OrderedBidictKeysView(self) def items(self) -> t.ItemsView[KT, VT]: """A set-like object providing a view on the contained items.""" return _OrderedBidictItemsView(self) # The following MappingView implementations use the __iter__ implementations # inherited from their superclass counterparts in collections.abc, so they # continue to yield items in the correct order even after an OrderedBidict # is mutated. They also provide a __reversed__ implementation, which is not # provided by the collections.abc superclasses. class _OrderedBidictKeysView(BidictKeysView[KT]): _mapping: OrderedBidict[KT, t.Any] def __reversed__(self) -> t.Iterator[KT]: return reversed(self._mapping) class _OrderedBidictItemsView(t.ItemsView[KT, VT]): _mapping: OrderedBidict[KT, VT] def __reversed__(self) -> t.Iterator[tuple[KT, VT]]: ob = self._mapping for key in reversed(ob): yield key, ob[key] # For better performance, make _OrderedBidictKeysView and _OrderedBidictItemsView delegate # to backing dicts for the methods they inherit from collections.abc.Set. (Cannot delegate # for __iter__ and __reversed__ since they are order-sensitive.) See also: https://bugs.python.org/issue46713 _OView = t.Union[t.Type[_OrderedBidictKeysView[KT]], t.Type[_OrderedBidictItemsView[KT, t.Any]]] _setmethodnames: t.Iterable[str] = ( '__lt__ __le__ __gt__ __ge__ __eq__ __ne__ __sub__ __rsub__ ' '__or__ __ror__ __xor__ __rxor__ __and__ __rand__ isdisjoint' ).split() def _override_set_methods_to_use_backing_dict(cls: _OView[KT], viewname: str) -> None: def make_proxy_method(methodname: str) -> t.Any: def method(self: _OrderedBidictKeysView[KT] | _OrderedBidictItemsView[KT, t.Any], *args: t.Any) -> t.Any: fwdm = self._mapping._fwdm if not isinstance(fwdm, dict): # dict view speedup not available, fall back to Set's implementation. return getattr(Set, methodname)(self, *args) fwdm_dict_view = getattr(fwdm, viewname)() fwdm_dict_view_method = getattr(fwdm_dict_view, methodname) if ( len(args) != 1 or not isinstance((arg := args[0]), self.__class__) or not isinstance(arg._mapping._fwdm, dict) ): return fwdm_dict_view_method(*args) # self and arg are both _OrderedBidictKeysViews or _OrderedBidictItemsViews whose bidicts are backed by # a dict. Use arg's backing dict's corresponding view instead of arg. Otherwise, e.g. `ob1.keys() # < ob2.keys()` would give "TypeError: '<' not supported between instances of '_OrderedBidictKeysView' and # '_OrderedBidictKeysView'", because both `dict_keys(ob1).__lt__(ob2.keys()) is NotImplemented` and # `dict_keys(ob2).__gt__(ob1.keys()) is NotImplemented`. arg_dict = arg._mapping._fwdm arg_dict_view = getattr(arg_dict, viewname)() return fwdm_dict_view_method(arg_dict_view) method.__name__ = methodname method.__qualname__ = f'{cls.__qualname__}.{methodname}' return method for name in _setmethodnames: setattr(cls, name, make_proxy_method(name)) _override_set_methods_to_use_backing_dict(_OrderedBidictKeysView, 'keys') _override_set_methods_to_use_backing_dict(_OrderedBidictItemsView, 'items') # * Code review nav * # ============================================================================ # ← Prev: _orderedbase.py Current: _orderedbidict.py # ============================================================================ bidict-0.23.1/bidict/_typing.py000066400000000000000000000024111456445164300163260ustar00rootroot00000000000000# Copyright 2009-2024 Joshua Bronson. All rights reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. """Provide typing-related objects.""" from __future__ import annotations import typing as t from enum import Enum KT = t.TypeVar('KT') VT = t.TypeVar('VT') VT_co = t.TypeVar('VT_co', covariant=True) Items = t.Iterable[t.Tuple[KT, VT]] @t.runtime_checkable class Maplike(t.Protocol[KT, VT_co]): """Like typeshed's SupportsKeysAndGetItem, but usable at runtime.""" def keys(self) -> t.Iterable[KT]: ... def __getitem__(self, __key: KT) -> VT_co: ... MapOrItems = t.Union[Maplike[KT, VT], Items[KT, VT]] MappOrItems = t.Union[t.Mapping[KT, VT], Items[KT, VT]] ItemsIter = t.Iterator[t.Tuple[KT, VT]] class MissingT(Enum): """Sentinel used to represent none/missing when None itself can't be used.""" MISSING = 'MISSING' MISSING: t.Final[t.Literal[MissingT.MISSING]] = MissingT.MISSING OKT = t.Union[KT, MissingT] #: optional key type OVT = t.Union[VT, MissingT] #: optional value type DT = t.TypeVar('DT') #: for default arguments ODT = t.Union[DT, MissingT] #: optional default arg type bidict-0.23.1/bidict/metadata.py000066400000000000000000000010751456445164300164420ustar00rootroot00000000000000# Copyright 2009-2024 Joshua Bronson. All rights reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. """Define bidict package metadata.""" __version__ = '0.23.1' __author__ = {'name': 'Joshua Bronson', 'email': 'jabronson@gmail.com'} __copyright__ = '© 2009-2024 Joshua Bronson' __description__ = 'The bidirectional mapping library for Python.' __license__ = 'MPL 2.0' __url__ = 'https://bidict.readthedocs.io' bidict-0.23.1/bidict/py.typed000066400000000000000000000000201456445164300157740ustar00rootroot00000000000000PEP-561 marker. bidict-0.23.1/cachegrind.py000077500000000000000000000106351456445164300155200ustar00rootroot00000000000000#!/usr/bin/env python3 # Based on https://github.com/pythonspeed/cachegrind-benchmarking/blob/main/cachegrind.py """ Run a program under Cachegrind, combining various metrics into one single performance metric. License: https://opensource.org/licenses/MIT ## Features * Disables ASLR. * Sets consistent cache sizes. * Calculates a combined performance metric. For more information see the detailed write up at: https://pythonspeed.com/articles/consistent-benchmarking-in-ci/ ## Usage $ python3 cachegrind.py ./yourprogram --yourparam=yourvalues If you're benchmarking Python, make sure to set PYTHONHASHSEED to a fixed value (e.g. `export PYTHONHASHSEED=1234`). Other languages may have similar requirements to reduce variability. The last line printed will be a combined performance metric, but you can tweak the script to extract more info, or use it as a library. Copyright © 2020, Hyphenated Enterprises LLC. """ from __future__ import annotations import sys import typing as t from subprocess import DEVNULL from subprocess import check_call from subprocess import check_output from subprocess import run from tempfile import NamedTemporaryFile try: check_call(['setarch', '-h'], stdout=DEVNULL, stderr=DEVNULL) check_call(['valgrind', '-h'], stdout=DEVNULL, stderr=DEVNULL) except FileNotFoundError as exc: # e.g. macOS raise SystemExit(f'Command not found: {exc.filename}') from None ARCH = check_output(['uname', '-m'], text=True).strip() DISABLE_ASLR_CMD = ['setarch', ARCH, '-R'] def run_with_cachegrind(args_list: list[str]) -> dict[str, int]: """ Run the the given program and arguments under Cachegrind, parse the Cachegrind specs. For now we just ignore program output, and in general this is not robust. """ temp_file = NamedTemporaryFile('r+') run([ *DISABLE_ASLR_CMD, 'valgrind', '--tool=cachegrind', # Set some reasonable L1 and LL values, based on Haswell. # Feel free to update, important part is that they are consistent across runs, # instead of the default of copying from the current machine. '--I1=32768,8,64', '--D1=32768,8,64', '--LL=8388608,16,64', '--cachegrind-out-file=' + temp_file.name, *args_list, ]) # Don't fail if the program fails (to support e.g. `pytest --benchmark-compare-fail=...`) return parse_cachegrind_output(temp_file) def parse_cachegrind_output(temp_file: t.IO[str]) -> dict[str, int]: header = summary = '' for line in temp_file: if line.startswith('events: '): header = line[len('events: ') :].strip() elif line.startswith('summary: '): summary = line[len('summary:') :].strip() assert header assert summary return dict(zip(header.split(), (int(i) for i in summary.split()))) def get_counts(cg_results: dict[str, int]) -> dict[str, int]: """ Given the result of run_with_cachegrind(), figure out the parameters we will use for final estimate. We pretend there's no L2 since Cachegrind doesn't currently support it. Caveats: we're not including time to process instructions, only time to access instruction cache(s), so we're assuming time to fetch and run_with_cachegrind instruction is the same as time to retrieve data if they're both to L1 cache. """ result = {} d = cg_results ram_hits = d['DLmr'] + d['DLmw'] + d['ILmr'] l3_hits = d['I1mr'] + d['D1mw'] + d['D1mr'] - ram_hits total_memory_rw = d['Ir'] + d['Dr'] + d['Dw'] l1_hits = total_memory_rw - l3_hits - ram_hits assert total_memory_rw == l1_hits + l3_hits + ram_hits result['l1'] = l1_hits result['l3'] = l3_hits result['ram'] = ram_hits return result def combined_instruction_estimate(counts: dict[str, int]) -> int: """ Given the result of run_with_cachegrind(), return estimate of total time to run_with_cachegrind. Multipliers were determined empirically, but some research suggests they're a reasonable approximation for cache time ratios. L3 is probably too low, but then we're not simulating L2... """ return counts['l1'] + (5 * counts['l3']) + (35 * counts['ram']) def main() -> None: results = run_with_cachegrind(sys.argv[1:]) counts = get_counts(results) estimate = combined_instruction_estimate(counts) print(f'{"*" * 80}\nCombined instruction estimate: {estimate:,}') # noqa: T201 if __name__ == '__main__': main() bidict-0.23.1/codecov.yml000066400000000000000000000001321456445164300152100ustar00rootroot00000000000000coverage: status: project: default: target: 99% threshold: 1% bidict-0.23.1/dev-deps/000077500000000000000000000000001456445164300145565ustar00rootroot00000000000000bidict-0.23.1/dev-deps/dev.in000066400000000000000000000000531456445164300156620ustar00rootroot00000000000000pip-tools pytest-clarity pytest-icdiff tox bidict-0.23.1/dev-deps/docs.in000066400000000000000000000000361456445164300160350ustar00rootroot00000000000000sphinx sphinx-copybutton furo bidict-0.23.1/dev-deps/py_ver.env000066400000000000000000000002621456445164300165740ustar00rootroot00000000000000# Keep in sync with _default_py_minor_ver in /tox.ini: export DEFAULT_PY_MINOR_VER="12" export DEFAULT_PY_VER="3.$DEFAULT_PY_MINOR_VER" export DEFAULT_PY="python$DEFAULT_PY_VER" bidict-0.23.1/dev-deps/pypy3.10/000077500000000000000000000000001456445164300160615ustar00rootroot00000000000000bidict-0.23.1/dev-deps/pypy3.10/test.txt000066400000000000000000000247761456445164300176210ustar00rootroot00000000000000# This file was autogenerated by uv v0.1.3 via the following command: # uv pip compile --generate-hashes --upgrade --python-version=3.10 dev-deps/test.in -o dev-deps/pypy3.10/test.txt attrs==23.2.0 \ --hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \ --hash=sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1 # via hypothesis coverage==7.4.1 \ --hash=sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61 \ --hash=sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1 \ --hash=sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7 \ --hash=sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7 \ --hash=sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75 \ --hash=sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd \ --hash=sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35 \ --hash=sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04 \ --hash=sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6 \ --hash=sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042 \ --hash=sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166 \ --hash=sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1 \ --hash=sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d \ --hash=sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c \ --hash=sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66 \ --hash=sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70 \ --hash=sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1 \ --hash=sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676 \ --hash=sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630 \ --hash=sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a \ --hash=sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74 \ --hash=sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad \ --hash=sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19 \ --hash=sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6 \ --hash=sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448 \ --hash=sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018 \ --hash=sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218 \ --hash=sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756 \ --hash=sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54 \ --hash=sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45 \ --hash=sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628 \ --hash=sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968 \ --hash=sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d \ --hash=sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25 \ --hash=sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60 \ --hash=sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950 \ --hash=sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06 \ --hash=sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295 \ --hash=sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b \ --hash=sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c \ --hash=sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc \ --hash=sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74 \ --hash=sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1 \ --hash=sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee \ --hash=sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011 \ --hash=sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156 \ --hash=sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766 \ --hash=sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5 \ --hash=sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581 \ --hash=sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016 \ --hash=sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c \ --hash=sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3 # via coverage-enable-subprocess coverage-enable-subprocess==1.0 \ --hash=sha256:27982522339ec77662965e0d859da5662162962c874d54d2250426506818cbdc \ --hash=sha256:fdbd3dc9532007cd87ef84f38e16024c5b0ccb4ab2d1755225a7edf937acc011 exceptiongroup==1.2.0 \ --hash=sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14 \ --hash=sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68 # via # hypothesis # pytest execnet==2.0.2 \ --hash=sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41 \ --hash=sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af # via pytest-xdist hypothesis==6.98.8 \ --hash=sha256:35bcb6e497967e73d968fc4adedaf49ca5d85a22cc8065798716581f2205563a \ --hash=sha256:82d13cc46311c4a8de49a8a8613f4e6afbeabbc037e86d24ac16e2f15a0d676d iniconfig==2.0.0 \ --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \ --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374 # via pytest mypy==1.8.0 \ --hash=sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6 \ --hash=sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d \ --hash=sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02 \ --hash=sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d \ --hash=sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3 \ --hash=sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3 \ --hash=sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3 \ --hash=sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66 \ --hash=sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259 \ --hash=sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835 \ --hash=sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd \ --hash=sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d \ --hash=sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8 \ --hash=sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07 \ --hash=sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b \ --hash=sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e \ --hash=sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6 \ --hash=sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae \ --hash=sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9 \ --hash=sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d \ --hash=sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a \ --hash=sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592 \ --hash=sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218 \ --hash=sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817 \ --hash=sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4 \ --hash=sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410 \ --hash=sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55 mypy-extensions==1.0.0 \ --hash=sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d \ --hash=sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782 # via mypy packaging==23.2 \ --hash=sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5 \ --hash=sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7 # via pytest pluggy==1.4.0 \ --hash=sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981 \ --hash=sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be # via pytest py-cpuinfo==9.0.0 \ --hash=sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690 \ --hash=sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5 # via pytest-benchmark pytest==8.0.1 \ --hash=sha256:267f6563751877d772019b13aacbe4e860d73fe8f651f28112e9ac37de7513ae \ --hash=sha256:3e4f16fe1c0a9dc9d9389161c127c3edc5d810c38d6793042fb81d9f48a59fca # via # pytest-benchmark # pytest-sphinx # pytest-xdist pytest-benchmark==4.0.0 \ --hash=sha256:fb0785b83efe599a6a956361c0691ae1dbb5318018561af10f3e915caa0048d1 \ --hash=sha256:fdb7db64e31c8b277dff9850d2a2556d8b60bcb0ea6524e36e28ffd7c87f71d6 pytest-sphinx==0.6.0 \ --hash=sha256:542823b7d493b067a0d36bf1359db5f7ae2217e007190edad7201c44145cf451 \ --hash=sha256:9add29fb7de87c241100216d9787d4ad2a93db91eced02e914a696b7a9a7a776 pytest-xdist==3.5.0 \ --hash=sha256:cbb36f3d67e0c478baa57fa4edc8843887e0f6cfc42d677530a36d7472b32d8a \ --hash=sha256:d075629c7e00b611df89f490a5063944bee7a4362a5ff11c7cc7824a03dfce24 sortedcollections==2.1.0 \ --hash=sha256:b07abbc73472cc459da9dd6e2607d73d1f3b9309a32dd9a57fa2c6fa882f4c6c \ --hash=sha256:d8e9609d6c580a16a1224a3dc8965789e03ebc4c3e5ffd05ada54a2fed5dcacd sortedcontainers==2.4.0 \ --hash=sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88 \ --hash=sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0 # via # hypothesis # sortedcollections tomli==2.0.1 \ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f # via # mypy # pytest typing-extensions==4.9.0 \ --hash=sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783 \ --hash=sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd # via mypy bidict-0.23.1/dev-deps/pypy3.9/000077500000000000000000000000001456445164300160115ustar00rootroot00000000000000bidict-0.23.1/dev-deps/pypy3.9/test.txt000066400000000000000000000247741456445164300175470ustar00rootroot00000000000000# This file was autogenerated by uv v0.1.3 via the following command: # uv pip compile --generate-hashes --upgrade --python-version=3.9 dev-deps/test.in -o dev-deps/pypy3.9/test.txt attrs==23.2.0 \ --hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \ --hash=sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1 # via hypothesis coverage==7.4.1 \ --hash=sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61 \ --hash=sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1 \ --hash=sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7 \ --hash=sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7 \ --hash=sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75 \ --hash=sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd \ --hash=sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35 \ --hash=sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04 \ --hash=sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6 \ --hash=sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042 \ --hash=sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166 \ --hash=sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1 \ --hash=sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d \ --hash=sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c \ --hash=sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66 \ --hash=sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70 \ --hash=sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1 \ --hash=sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676 \ --hash=sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630 \ --hash=sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a \ --hash=sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74 \ --hash=sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad \ --hash=sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19 \ --hash=sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6 \ --hash=sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448 \ --hash=sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018 \ --hash=sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218 \ --hash=sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756 \ --hash=sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54 \ --hash=sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45 \ --hash=sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628 \ --hash=sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968 \ --hash=sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d \ --hash=sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25 \ --hash=sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60 \ --hash=sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950 \ --hash=sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06 \ --hash=sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295 \ --hash=sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b \ --hash=sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c \ --hash=sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc \ --hash=sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74 \ --hash=sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1 \ --hash=sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee \ --hash=sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011 \ --hash=sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156 \ --hash=sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766 \ --hash=sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5 \ --hash=sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581 \ --hash=sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016 \ --hash=sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c \ --hash=sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3 # via coverage-enable-subprocess coverage-enable-subprocess==1.0 \ --hash=sha256:27982522339ec77662965e0d859da5662162962c874d54d2250426506818cbdc \ --hash=sha256:fdbd3dc9532007cd87ef84f38e16024c5b0ccb4ab2d1755225a7edf937acc011 exceptiongroup==1.2.0 \ --hash=sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14 \ --hash=sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68 # via # hypothesis # pytest execnet==2.0.2 \ --hash=sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41 \ --hash=sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af # via pytest-xdist hypothesis==6.98.8 \ --hash=sha256:35bcb6e497967e73d968fc4adedaf49ca5d85a22cc8065798716581f2205563a \ --hash=sha256:82d13cc46311c4a8de49a8a8613f4e6afbeabbc037e86d24ac16e2f15a0d676d iniconfig==2.0.0 \ --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \ --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374 # via pytest mypy==1.8.0 \ --hash=sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6 \ --hash=sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d \ --hash=sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02 \ --hash=sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d \ --hash=sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3 \ --hash=sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3 \ --hash=sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3 \ --hash=sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66 \ --hash=sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259 \ --hash=sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835 \ --hash=sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd \ --hash=sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d \ --hash=sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8 \ --hash=sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07 \ --hash=sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b \ --hash=sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e \ --hash=sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6 \ --hash=sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae \ --hash=sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9 \ --hash=sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d \ --hash=sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a \ --hash=sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592 \ --hash=sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218 \ --hash=sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817 \ --hash=sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4 \ --hash=sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410 \ --hash=sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55 mypy-extensions==1.0.0 \ --hash=sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d \ --hash=sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782 # via mypy packaging==23.2 \ --hash=sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5 \ --hash=sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7 # via pytest pluggy==1.4.0 \ --hash=sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981 \ --hash=sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be # via pytest py-cpuinfo==9.0.0 \ --hash=sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690 \ --hash=sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5 # via pytest-benchmark pytest==8.0.1 \ --hash=sha256:267f6563751877d772019b13aacbe4e860d73fe8f651f28112e9ac37de7513ae \ --hash=sha256:3e4f16fe1c0a9dc9d9389161c127c3edc5d810c38d6793042fb81d9f48a59fca # via # pytest-benchmark # pytest-sphinx # pytest-xdist pytest-benchmark==4.0.0 \ --hash=sha256:fb0785b83efe599a6a956361c0691ae1dbb5318018561af10f3e915caa0048d1 \ --hash=sha256:fdb7db64e31c8b277dff9850d2a2556d8b60bcb0ea6524e36e28ffd7c87f71d6 pytest-sphinx==0.6.0 \ --hash=sha256:542823b7d493b067a0d36bf1359db5f7ae2217e007190edad7201c44145cf451 \ --hash=sha256:9add29fb7de87c241100216d9787d4ad2a93db91eced02e914a696b7a9a7a776 pytest-xdist==3.5.0 \ --hash=sha256:cbb36f3d67e0c478baa57fa4edc8843887e0f6cfc42d677530a36d7472b32d8a \ --hash=sha256:d075629c7e00b611df89f490a5063944bee7a4362a5ff11c7cc7824a03dfce24 sortedcollections==2.1.0 \ --hash=sha256:b07abbc73472cc459da9dd6e2607d73d1f3b9309a32dd9a57fa2c6fa882f4c6c \ --hash=sha256:d8e9609d6c580a16a1224a3dc8965789e03ebc4c3e5ffd05ada54a2fed5dcacd sortedcontainers==2.4.0 \ --hash=sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88 \ --hash=sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0 # via # hypothesis # sortedcollections tomli==2.0.1 \ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f # via # mypy # pytest typing-extensions==4.9.0 \ --hash=sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783 \ --hash=sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd # via mypy bidict-0.23.1/dev-deps/python3.10/000077500000000000000000000000001456445164300164015ustar00rootroot00000000000000bidict-0.23.1/dev-deps/python3.10/test.txt000066400000000000000000000250001456445164300201160ustar00rootroot00000000000000# This file was autogenerated by uv v0.1.3 via the following command: # uv pip compile --generate-hashes --upgrade --python-version=3.10 dev-deps/test.in -o dev-deps/python3.10/test.txt attrs==23.2.0 \ --hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \ --hash=sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1 # via hypothesis coverage==7.4.1 \ --hash=sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61 \ --hash=sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1 \ --hash=sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7 \ --hash=sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7 \ --hash=sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75 \ --hash=sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd \ --hash=sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35 \ --hash=sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04 \ --hash=sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6 \ --hash=sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042 \ --hash=sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166 \ --hash=sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1 \ --hash=sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d \ --hash=sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c \ --hash=sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66 \ --hash=sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70 \ --hash=sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1 \ --hash=sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676 \ --hash=sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630 \ --hash=sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a \ --hash=sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74 \ --hash=sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad \ --hash=sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19 \ --hash=sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6 \ --hash=sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448 \ --hash=sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018 \ --hash=sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218 \ --hash=sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756 \ --hash=sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54 \ --hash=sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45 \ --hash=sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628 \ --hash=sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968 \ --hash=sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d \ --hash=sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25 \ --hash=sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60 \ --hash=sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950 \ --hash=sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06 \ --hash=sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295 \ --hash=sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b \ --hash=sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c \ --hash=sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc \ --hash=sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74 \ --hash=sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1 \ --hash=sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee \ --hash=sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011 \ --hash=sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156 \ --hash=sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766 \ --hash=sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5 \ --hash=sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581 \ --hash=sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016 \ --hash=sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c \ --hash=sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3 # via coverage-enable-subprocess coverage-enable-subprocess==1.0 \ --hash=sha256:27982522339ec77662965e0d859da5662162962c874d54d2250426506818cbdc \ --hash=sha256:fdbd3dc9532007cd87ef84f38e16024c5b0ccb4ab2d1755225a7edf937acc011 exceptiongroup==1.2.0 \ --hash=sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14 \ --hash=sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68 # via # hypothesis # pytest execnet==2.0.2 \ --hash=sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41 \ --hash=sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af # via pytest-xdist hypothesis==6.98.8 \ --hash=sha256:35bcb6e497967e73d968fc4adedaf49ca5d85a22cc8065798716581f2205563a \ --hash=sha256:82d13cc46311c4a8de49a8a8613f4e6afbeabbc037e86d24ac16e2f15a0d676d iniconfig==2.0.0 \ --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \ --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374 # via pytest mypy==1.8.0 \ --hash=sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6 \ --hash=sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d \ --hash=sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02 \ --hash=sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d \ --hash=sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3 \ --hash=sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3 \ --hash=sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3 \ --hash=sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66 \ --hash=sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259 \ --hash=sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835 \ --hash=sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd \ --hash=sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d \ --hash=sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8 \ --hash=sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07 \ --hash=sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b \ --hash=sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e \ --hash=sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6 \ --hash=sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae \ --hash=sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9 \ --hash=sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d \ --hash=sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a \ --hash=sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592 \ --hash=sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218 \ --hash=sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817 \ --hash=sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4 \ --hash=sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410 \ --hash=sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55 mypy-extensions==1.0.0 \ --hash=sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d \ --hash=sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782 # via mypy packaging==23.2 \ --hash=sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5 \ --hash=sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7 # via pytest pluggy==1.4.0 \ --hash=sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981 \ --hash=sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be # via pytest py-cpuinfo==9.0.0 \ --hash=sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690 \ --hash=sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5 # via pytest-benchmark pytest==8.0.1 \ --hash=sha256:267f6563751877d772019b13aacbe4e860d73fe8f651f28112e9ac37de7513ae \ --hash=sha256:3e4f16fe1c0a9dc9d9389161c127c3edc5d810c38d6793042fb81d9f48a59fca # via # pytest-benchmark # pytest-sphinx # pytest-xdist pytest-benchmark==4.0.0 \ --hash=sha256:fb0785b83efe599a6a956361c0691ae1dbb5318018561af10f3e915caa0048d1 \ --hash=sha256:fdb7db64e31c8b277dff9850d2a2556d8b60bcb0ea6524e36e28ffd7c87f71d6 pytest-sphinx==0.6.0 \ --hash=sha256:542823b7d493b067a0d36bf1359db5f7ae2217e007190edad7201c44145cf451 \ --hash=sha256:9add29fb7de87c241100216d9787d4ad2a93db91eced02e914a696b7a9a7a776 pytest-xdist==3.5.0 \ --hash=sha256:cbb36f3d67e0c478baa57fa4edc8843887e0f6cfc42d677530a36d7472b32d8a \ --hash=sha256:d075629c7e00b611df89f490a5063944bee7a4362a5ff11c7cc7824a03dfce24 sortedcollections==2.1.0 \ --hash=sha256:b07abbc73472cc459da9dd6e2607d73d1f3b9309a32dd9a57fa2c6fa882f4c6c \ --hash=sha256:d8e9609d6c580a16a1224a3dc8965789e03ebc4c3e5ffd05ada54a2fed5dcacd sortedcontainers==2.4.0 \ --hash=sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88 \ --hash=sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0 # via # hypothesis # sortedcollections tomli==2.0.1 \ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f # via # mypy # pytest typing-extensions==4.9.0 \ --hash=sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783 \ --hash=sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd # via mypy bidict-0.23.1/dev-deps/python3.11/000077500000000000000000000000001456445164300164025ustar00rootroot00000000000000bidict-0.23.1/dev-deps/python3.11/test.txt000066400000000000000000000240671456445164300201330ustar00rootroot00000000000000# This file was autogenerated by uv v0.1.3 via the following command: # uv pip compile --generate-hashes --upgrade --python-version=3.11 dev-deps/test.in -o dev-deps/python3.11/test.txt attrs==23.2.0 \ --hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \ --hash=sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1 # via hypothesis coverage==7.4.1 \ --hash=sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61 \ --hash=sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1 \ --hash=sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7 \ --hash=sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7 \ --hash=sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75 \ --hash=sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd \ --hash=sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35 \ --hash=sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04 \ --hash=sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6 \ --hash=sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042 \ --hash=sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166 \ --hash=sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1 \ --hash=sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d \ --hash=sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c \ --hash=sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66 \ --hash=sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70 \ --hash=sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1 \ --hash=sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676 \ --hash=sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630 \ --hash=sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a \ --hash=sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74 \ --hash=sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad \ --hash=sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19 \ --hash=sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6 \ --hash=sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448 \ --hash=sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018 \ --hash=sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218 \ --hash=sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756 \ --hash=sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54 \ --hash=sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45 \ --hash=sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628 \ --hash=sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968 \ --hash=sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d \ --hash=sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25 \ --hash=sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60 \ --hash=sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950 \ --hash=sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06 \ --hash=sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295 \ --hash=sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b \ --hash=sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c \ --hash=sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc \ --hash=sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74 \ --hash=sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1 \ --hash=sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee \ --hash=sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011 \ --hash=sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156 \ --hash=sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766 \ --hash=sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5 \ --hash=sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581 \ --hash=sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016 \ --hash=sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c \ --hash=sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3 # via coverage-enable-subprocess coverage-enable-subprocess==1.0 \ --hash=sha256:27982522339ec77662965e0d859da5662162962c874d54d2250426506818cbdc \ --hash=sha256:fdbd3dc9532007cd87ef84f38e16024c5b0ccb4ab2d1755225a7edf937acc011 execnet==2.0.2 \ --hash=sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41 \ --hash=sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af # via pytest-xdist hypothesis==6.98.8 \ --hash=sha256:35bcb6e497967e73d968fc4adedaf49ca5d85a22cc8065798716581f2205563a \ --hash=sha256:82d13cc46311c4a8de49a8a8613f4e6afbeabbc037e86d24ac16e2f15a0d676d iniconfig==2.0.0 \ --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \ --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374 # via pytest mypy==1.8.0 \ --hash=sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6 \ --hash=sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d \ --hash=sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02 \ --hash=sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d \ --hash=sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3 \ --hash=sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3 \ --hash=sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3 \ --hash=sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66 \ --hash=sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259 \ --hash=sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835 \ --hash=sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd \ --hash=sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d \ --hash=sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8 \ --hash=sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07 \ --hash=sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b \ --hash=sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e \ --hash=sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6 \ --hash=sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae \ --hash=sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9 \ --hash=sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d \ --hash=sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a \ --hash=sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592 \ --hash=sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218 \ --hash=sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817 \ --hash=sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4 \ --hash=sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410 \ --hash=sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55 mypy-extensions==1.0.0 \ --hash=sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d \ --hash=sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782 # via mypy packaging==23.2 \ --hash=sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5 \ --hash=sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7 # via pytest pluggy==1.4.0 \ --hash=sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981 \ --hash=sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be # via pytest py-cpuinfo==9.0.0 \ --hash=sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690 \ --hash=sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5 # via pytest-benchmark pytest==8.0.1 \ --hash=sha256:267f6563751877d772019b13aacbe4e860d73fe8f651f28112e9ac37de7513ae \ --hash=sha256:3e4f16fe1c0a9dc9d9389161c127c3edc5d810c38d6793042fb81d9f48a59fca # via # pytest-benchmark # pytest-sphinx # pytest-xdist pytest-benchmark==4.0.0 \ --hash=sha256:fb0785b83efe599a6a956361c0691ae1dbb5318018561af10f3e915caa0048d1 \ --hash=sha256:fdb7db64e31c8b277dff9850d2a2556d8b60bcb0ea6524e36e28ffd7c87f71d6 pytest-sphinx==0.6.0 \ --hash=sha256:542823b7d493b067a0d36bf1359db5f7ae2217e007190edad7201c44145cf451 \ --hash=sha256:9add29fb7de87c241100216d9787d4ad2a93db91eced02e914a696b7a9a7a776 pytest-xdist==3.5.0 \ --hash=sha256:cbb36f3d67e0c478baa57fa4edc8843887e0f6cfc42d677530a36d7472b32d8a \ --hash=sha256:d075629c7e00b611df89f490a5063944bee7a4362a5ff11c7cc7824a03dfce24 sortedcollections==2.1.0 \ --hash=sha256:b07abbc73472cc459da9dd6e2607d73d1f3b9309a32dd9a57fa2c6fa882f4c6c \ --hash=sha256:d8e9609d6c580a16a1224a3dc8965789e03ebc4c3e5ffd05ada54a2fed5dcacd sortedcontainers==2.4.0 \ --hash=sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88 \ --hash=sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0 # via # hypothesis # sortedcollections typing-extensions==4.9.0 \ --hash=sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783 \ --hash=sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd # via mypy bidict-0.23.1/dev-deps/python3.12/000077500000000000000000000000001456445164300164035ustar00rootroot00000000000000bidict-0.23.1/dev-deps/python3.12/dev.txt000066400000000000000000000135321456445164300177260ustar00rootroot00000000000000# This file was autogenerated by uv v0.1.3 via the following command: # uv pip compile --generate-hashes --upgrade --python-version=3.12 dev-deps/dev.in -o dev-deps/python3.12/dev.txt build==1.0.3 \ --hash=sha256:538aab1b64f9828977f84bc63ae570b060a8ed1be419e7870b8b4fc5e6ea553b \ --hash=sha256:589bf99a67df7c9cf07ec0ac0e5e2ea5d4b37ac63301c4986d1acb126aa83f8f # via pip-tools cachetools==5.3.2 \ --hash=sha256:086ee420196f7b2ab9ca2db2520aca326318b68fe5ba8bc4d49cca91add450f2 \ --hash=sha256:861f35a13a451f94e301ce2bec7cac63e881232ccce7ed67fab9b5df4d3beaa1 # via tox chardet==5.2.0 \ --hash=sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7 \ --hash=sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970 # via tox click==8.1.7 \ --hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \ --hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de # via pip-tools colorama==0.4.6 \ --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 # via tox distlib==0.3.8 \ --hash=sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784 \ --hash=sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64 # via virtualenv filelock==3.13.1 \ --hash=sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e \ --hash=sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c # via # tox # virtualenv icdiff==2.0.7 \ --hash=sha256:f05d1b3623223dd1c70f7848da7d699de3d9a2550b902a8234d9026292fb5762 \ --hash=sha256:f79a318891adbf59a45e3a7694f5e1f18c5407065264637072ac8363b759866f # via pytest-icdiff iniconfig==2.0.0 \ --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \ --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374 # via pytest markdown-it-py==3.0.0 \ --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \ --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb # via rich mdurl==0.1.2 \ --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba # via markdown-it-py packaging==23.2 \ --hash=sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5 \ --hash=sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7 # via # build # pyproject-api # pytest # tox pip==24.0 \ --hash=sha256:ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc \ --hash=sha256:ea9bd1a847e8c5774a5777bb398c19e80bcd4e2aa16a4b301b718fe6f593aba2 # via pip-tools pip-tools==7.4.0 \ --hash=sha256:a92a6ddfa86ff389fe6ace381d463bc436e2c705bd71d52117c25af5ce867bb7 \ --hash=sha256:b67432fd0759ed834c5367f9e0ce8c95441acecfec9c8e24b41aca166757adf0 platformdirs==4.2.0 \ --hash=sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068 \ --hash=sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768 # via # tox # virtualenv pluggy==1.4.0 \ --hash=sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981 \ --hash=sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be # via # pytest # tox pprintpp==0.4.0 \ --hash=sha256:b6b4dcdd0c0c0d75e4d7b2f21a9e933e5b2ce62b26e1a54537f9651ae5a5c01d \ --hash=sha256:ea826108e2c7f49dc6d66c752973c3fc9749142a798d6b254e1e301cfdbc6403 # via # pytest-clarity # pytest-icdiff pygments==2.17.2 \ --hash=sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c \ --hash=sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367 # via rich pyproject-api==1.6.1 \ --hash=sha256:1817dc018adc0d1ff9ca1ed8c60e1623d5aaca40814b953af14a9cf9a5cae538 \ --hash=sha256:4c0116d60476b0786c88692cf4e325a9814965e2469c5998b830bba16b183675 # via tox pyproject-hooks==1.0.0 \ --hash=sha256:283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8 \ --hash=sha256:f271b298b97f5955d53fb12b72c1fb1948c22c1a6b70b315c54cedaca0264ef5 # via # build # pip-tools pytest==8.0.1 \ --hash=sha256:267f6563751877d772019b13aacbe4e860d73fe8f651f28112e9ac37de7513ae \ --hash=sha256:3e4f16fe1c0a9dc9d9389161c127c3edc5d810c38d6793042fb81d9f48a59fca # via # pytest-clarity # pytest-icdiff pytest-clarity==1.0.1 \ --hash=sha256:505fe345fad4fe11c6a4187fe683f2c7c52c077caa1e135f3e483fe112db7772 pytest-icdiff==0.9 \ --hash=sha256:13aede616202e57fcc882568b64589002ef85438046f012ac30a8d959dac8b75 \ --hash=sha256:efee0da3bd1b24ef2d923751c5c547fbb8df0a46795553fba08ef57c3ca03d82 rich==13.7.0 \ --hash=sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa \ --hash=sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235 # via pytest-clarity setuptools==69.1.0 \ --hash=sha256:850894c4195f09c4ed30dba56213bf7c3f21d86ed6bdaafb5df5972593bfc401 \ --hash=sha256:c054629b81b946d63a9c6e732bc8b2513a7c3ea645f11d0139a2191d735c60c6 # via pip-tools tox==4.13.0 \ --hash=sha256:1143c7e2489c68026a55d3d4ae84c02c449f073b28e62f80e3e440a3b72a4afa \ --hash=sha256:dd789a554c16c4b532924ba393c92fc8991323c4b3d466712bfecc8c9b9f24f7 virtualenv==20.25.0 \ --hash=sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3 \ --hash=sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b # via tox wheel==0.42.0 \ --hash=sha256:177f9c9b0d45c47873b619f5b650346d632cdc35fb5e4d25058e09c9e581433d \ --hash=sha256:c45be39f7882c9d34243236f2d63cbd58039e360f85d0913425fbd7ceea617a8 # via pip-tools bidict-0.23.1/dev-deps/python3.12/docs.txt000066400000000000000000000432121456445164300200760ustar00rootroot00000000000000# This file was autogenerated by uv v0.1.3 via the following command: # uv pip compile --generate-hashes --upgrade --python-version=3.12 dev-deps/docs.in -o dev-deps/python3.12/docs.txt alabaster==0.7.16 \ --hash=sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65 \ --hash=sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92 # via sphinx babel==2.14.0 \ --hash=sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363 \ --hash=sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287 # via sphinx beautifulsoup4==4.12.3 \ --hash=sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051 \ --hash=sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed # via furo certifi==2024.2.2 \ --hash=sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f \ --hash=sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1 # via requests charset-normalizer==3.3.2 \ --hash=sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027 \ --hash=sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087 \ --hash=sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786 \ --hash=sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8 \ --hash=sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09 \ --hash=sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185 \ --hash=sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574 \ --hash=sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e \ --hash=sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519 \ --hash=sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898 \ --hash=sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269 \ --hash=sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3 \ --hash=sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f \ --hash=sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6 \ --hash=sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8 \ --hash=sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a \ --hash=sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73 \ --hash=sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc \ --hash=sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714 \ --hash=sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2 \ --hash=sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc \ --hash=sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce \ --hash=sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d \ --hash=sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e \ --hash=sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6 \ --hash=sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269 \ --hash=sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96 \ --hash=sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d \ --hash=sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a \ --hash=sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4 \ --hash=sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77 \ --hash=sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d \ --hash=sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0 \ --hash=sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed \ --hash=sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068 \ --hash=sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac \ --hash=sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25 \ --hash=sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8 \ --hash=sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab \ --hash=sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26 \ --hash=sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2 \ --hash=sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db \ --hash=sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f \ --hash=sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5 \ --hash=sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99 \ --hash=sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c \ --hash=sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d \ --hash=sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811 \ --hash=sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa \ --hash=sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a \ --hash=sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03 \ --hash=sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b \ --hash=sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04 \ --hash=sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c \ --hash=sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001 \ --hash=sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458 \ --hash=sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389 \ --hash=sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99 \ --hash=sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985 \ --hash=sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537 \ --hash=sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238 \ --hash=sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f \ --hash=sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d \ --hash=sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796 \ --hash=sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a \ --hash=sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143 \ --hash=sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8 \ --hash=sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c \ --hash=sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5 \ --hash=sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5 \ --hash=sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711 \ --hash=sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4 \ --hash=sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6 \ --hash=sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c \ --hash=sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7 \ --hash=sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4 \ --hash=sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b \ --hash=sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae \ --hash=sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12 \ --hash=sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c \ --hash=sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae \ --hash=sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8 \ --hash=sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887 \ --hash=sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b \ --hash=sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4 \ --hash=sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f \ --hash=sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5 \ --hash=sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33 \ --hash=sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519 \ --hash=sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561 # via requests docutils==0.20.1 \ --hash=sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6 \ --hash=sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b # via sphinx furo==2024.1.29 \ --hash=sha256:3548be2cef45a32f8cdc0272d415fcb3e5fa6a0eb4ddfe21df3ecf1fe45a13cf \ --hash=sha256:4d6b2fe3f10a6e36eb9cc24c1e7beb38d7a23fc7b3c382867503b7fcac8a1e02 idna==3.6 \ --hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \ --hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f # via requests imagesize==1.4.1 \ --hash=sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b \ --hash=sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a # via sphinx jinja2==3.1.3 \ --hash=sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa \ --hash=sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90 # via sphinx markupsafe==2.1.5 \ --hash=sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf \ --hash=sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff \ --hash=sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f \ --hash=sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3 \ --hash=sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532 \ --hash=sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f \ --hash=sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617 \ --hash=sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df \ --hash=sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4 \ --hash=sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906 \ --hash=sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f \ --hash=sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4 \ --hash=sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8 \ --hash=sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371 \ --hash=sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2 \ --hash=sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465 \ --hash=sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52 \ --hash=sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6 \ --hash=sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169 \ --hash=sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad \ --hash=sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2 \ --hash=sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0 \ --hash=sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029 \ --hash=sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f \ --hash=sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a \ --hash=sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced \ --hash=sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5 \ --hash=sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c \ --hash=sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf \ --hash=sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9 \ --hash=sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb \ --hash=sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad \ --hash=sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3 \ --hash=sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1 \ --hash=sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46 \ --hash=sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc \ --hash=sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a \ --hash=sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee \ --hash=sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900 \ --hash=sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5 \ --hash=sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea \ --hash=sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f \ --hash=sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5 \ --hash=sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e \ --hash=sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a \ --hash=sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f \ --hash=sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50 \ --hash=sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a \ --hash=sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b \ --hash=sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4 \ --hash=sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff \ --hash=sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2 \ --hash=sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46 \ --hash=sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b \ --hash=sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf \ --hash=sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5 \ --hash=sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5 \ --hash=sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab \ --hash=sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd \ --hash=sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68 # via jinja2 packaging==23.2 \ --hash=sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5 \ --hash=sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7 # via sphinx pygments==2.17.2 \ --hash=sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c \ --hash=sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367 # via # furo # sphinx requests==2.31.0 \ --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 # via sphinx snowballstemmer==2.2.0 \ --hash=sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1 \ --hash=sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a # via sphinx soupsieve==2.5 \ --hash=sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690 \ --hash=sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7 # via beautifulsoup4 sphinx==7.2.6 \ --hash=sha256:1e09160a40b956dc623c910118fa636da93bd3ca0b9876a7b3df90f07d691560 \ --hash=sha256:9a5160e1ea90688d5963ba09a2dcd8bdd526620edbb65c328728f1b2228d5ab5 # via # furo # sphinx-basic-ng # sphinx-copybutton sphinx-basic-ng==1.0.0b2 \ --hash=sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9 \ --hash=sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b # via furo sphinx-copybutton==0.5.2 \ --hash=sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd \ --hash=sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e sphinxcontrib-applehelp==1.0.8 \ --hash=sha256:c40a4f96f3776c4393d933412053962fac2b84f4c99a7982ba42e09576a70619 \ --hash=sha256:cb61eb0ec1b61f349e5cc36b2028e9e7ca765be05e49641c97241274753067b4 # via sphinx sphinxcontrib-devhelp==1.0.6 \ --hash=sha256:6485d09629944511c893fa11355bda18b742b83a2b181f9a009f7e500595c90f \ --hash=sha256:9893fd3f90506bc4b97bdb977ceb8fbd823989f4316b28c3841ec128544372d3 # via sphinx sphinxcontrib-htmlhelp==2.0.5 \ --hash=sha256:0dc87637d5de53dd5eec3a6a01753b1ccf99494bd756aafecd74b4fa9e729015 \ --hash=sha256:393f04f112b4d2f53d93448d4bce35842f62b307ccdc549ec1585e950bc35e04 # via sphinx sphinxcontrib-jsmath==1.0.1 \ --hash=sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178 \ --hash=sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8 # via sphinx sphinxcontrib-qthelp==1.0.7 \ --hash=sha256:053dedc38823a80a7209a80860b16b722e9e0209e32fea98c90e4e6624588ed6 \ --hash=sha256:e2ae3b5c492d58fcbd73281fbd27e34b8393ec34a073c792642cd8e529288182 # via sphinx sphinxcontrib-serializinghtml==1.1.10 \ --hash=sha256:326369b8df80a7d2d8d7f99aa5ac577f51ea51556ed974e7716cfd4fca3f6cb7 \ --hash=sha256:93f3f5dc458b91b192fe10c397e324f262cf163d79f3282c158e8436a2c4511f # via sphinx urllib3==2.2.1 \ --hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \ --hash=sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19 # via requests bidict-0.23.1/dev-deps/python3.12/test.txt000066400000000000000000000240671456445164300201340ustar00rootroot00000000000000# This file was autogenerated by uv v0.1.3 via the following command: # uv pip compile --generate-hashes --upgrade --python-version=3.12 dev-deps/test.in -o dev-deps/python3.12/test.txt attrs==23.2.0 \ --hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \ --hash=sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1 # via hypothesis coverage==7.4.1 \ --hash=sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61 \ --hash=sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1 \ --hash=sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7 \ --hash=sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7 \ --hash=sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75 \ --hash=sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd \ --hash=sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35 \ --hash=sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04 \ --hash=sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6 \ --hash=sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042 \ --hash=sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166 \ --hash=sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1 \ --hash=sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d \ --hash=sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c \ --hash=sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66 \ --hash=sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70 \ --hash=sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1 \ --hash=sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676 \ --hash=sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630 \ --hash=sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a \ --hash=sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74 \ --hash=sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad \ --hash=sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19 \ --hash=sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6 \ --hash=sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448 \ --hash=sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018 \ --hash=sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218 \ --hash=sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756 \ --hash=sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54 \ --hash=sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45 \ --hash=sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628 \ --hash=sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968 \ --hash=sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d \ --hash=sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25 \ --hash=sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60 \ --hash=sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950 \ --hash=sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06 \ --hash=sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295 \ --hash=sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b \ --hash=sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c \ --hash=sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc \ --hash=sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74 \ --hash=sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1 \ --hash=sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee \ --hash=sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011 \ --hash=sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156 \ --hash=sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766 \ --hash=sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5 \ --hash=sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581 \ --hash=sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016 \ --hash=sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c \ --hash=sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3 # via coverage-enable-subprocess coverage-enable-subprocess==1.0 \ --hash=sha256:27982522339ec77662965e0d859da5662162962c874d54d2250426506818cbdc \ --hash=sha256:fdbd3dc9532007cd87ef84f38e16024c5b0ccb4ab2d1755225a7edf937acc011 execnet==2.0.2 \ --hash=sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41 \ --hash=sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af # via pytest-xdist hypothesis==6.98.8 \ --hash=sha256:35bcb6e497967e73d968fc4adedaf49ca5d85a22cc8065798716581f2205563a \ --hash=sha256:82d13cc46311c4a8de49a8a8613f4e6afbeabbc037e86d24ac16e2f15a0d676d iniconfig==2.0.0 \ --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \ --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374 # via pytest mypy==1.8.0 \ --hash=sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6 \ --hash=sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d \ --hash=sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02 \ --hash=sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d \ --hash=sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3 \ --hash=sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3 \ --hash=sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3 \ --hash=sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66 \ --hash=sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259 \ --hash=sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835 \ --hash=sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd \ --hash=sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d \ --hash=sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8 \ --hash=sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07 \ --hash=sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b \ --hash=sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e \ --hash=sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6 \ --hash=sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae \ --hash=sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9 \ --hash=sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d \ --hash=sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a \ --hash=sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592 \ --hash=sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218 \ --hash=sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817 \ --hash=sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4 \ --hash=sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410 \ --hash=sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55 mypy-extensions==1.0.0 \ --hash=sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d \ --hash=sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782 # via mypy packaging==23.2 \ --hash=sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5 \ --hash=sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7 # via pytest pluggy==1.4.0 \ --hash=sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981 \ --hash=sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be # via pytest py-cpuinfo==9.0.0 \ --hash=sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690 \ --hash=sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5 # via pytest-benchmark pytest==8.0.1 \ --hash=sha256:267f6563751877d772019b13aacbe4e860d73fe8f651f28112e9ac37de7513ae \ --hash=sha256:3e4f16fe1c0a9dc9d9389161c127c3edc5d810c38d6793042fb81d9f48a59fca # via # pytest-benchmark # pytest-sphinx # pytest-xdist pytest-benchmark==4.0.0 \ --hash=sha256:fb0785b83efe599a6a956361c0691ae1dbb5318018561af10f3e915caa0048d1 \ --hash=sha256:fdb7db64e31c8b277dff9850d2a2556d8b60bcb0ea6524e36e28ffd7c87f71d6 pytest-sphinx==0.6.0 \ --hash=sha256:542823b7d493b067a0d36bf1359db5f7ae2217e007190edad7201c44145cf451 \ --hash=sha256:9add29fb7de87c241100216d9787d4ad2a93db91eced02e914a696b7a9a7a776 pytest-xdist==3.5.0 \ --hash=sha256:cbb36f3d67e0c478baa57fa4edc8843887e0f6cfc42d677530a36d7472b32d8a \ --hash=sha256:d075629c7e00b611df89f490a5063944bee7a4362a5ff11c7cc7824a03dfce24 sortedcollections==2.1.0 \ --hash=sha256:b07abbc73472cc459da9dd6e2607d73d1f3b9309a32dd9a57fa2c6fa882f4c6c \ --hash=sha256:d8e9609d6c580a16a1224a3dc8965789e03ebc4c3e5ffd05ada54a2fed5dcacd sortedcontainers==2.4.0 \ --hash=sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88 \ --hash=sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0 # via # hypothesis # sortedcollections typing-extensions==4.9.0 \ --hash=sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783 \ --hash=sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd # via mypy bidict-0.23.1/dev-deps/python3.8/000077500000000000000000000000001456445164300163305ustar00rootroot00000000000000bidict-0.23.1/dev-deps/python3.8/test.txt000066400000000000000000000247761456445164300200700ustar00rootroot00000000000000# This file was autogenerated by uv v0.1.3 via the following command: # uv pip compile --generate-hashes --upgrade --python-version=3.8 dev-deps/test.in -o dev-deps/python3.8/test.txt attrs==23.2.0 \ --hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \ --hash=sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1 # via hypothesis coverage==7.4.1 \ --hash=sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61 \ --hash=sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1 \ --hash=sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7 \ --hash=sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7 \ --hash=sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75 \ --hash=sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd \ --hash=sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35 \ --hash=sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04 \ --hash=sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6 \ --hash=sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042 \ --hash=sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166 \ --hash=sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1 \ --hash=sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d \ --hash=sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c \ --hash=sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66 \ --hash=sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70 \ --hash=sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1 \ --hash=sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676 \ --hash=sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630 \ --hash=sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a \ --hash=sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74 \ --hash=sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad \ --hash=sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19 \ --hash=sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6 \ --hash=sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448 \ --hash=sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018 \ --hash=sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218 \ --hash=sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756 \ --hash=sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54 \ --hash=sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45 \ --hash=sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628 \ --hash=sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968 \ --hash=sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d \ --hash=sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25 \ --hash=sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60 \ --hash=sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950 \ --hash=sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06 \ --hash=sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295 \ --hash=sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b \ --hash=sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c \ --hash=sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc \ --hash=sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74 \ --hash=sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1 \ --hash=sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee \ --hash=sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011 \ --hash=sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156 \ --hash=sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766 \ --hash=sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5 \ --hash=sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581 \ --hash=sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016 \ --hash=sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c \ --hash=sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3 # via coverage-enable-subprocess coverage-enable-subprocess==1.0 \ --hash=sha256:27982522339ec77662965e0d859da5662162962c874d54d2250426506818cbdc \ --hash=sha256:fdbd3dc9532007cd87ef84f38e16024c5b0ccb4ab2d1755225a7edf937acc011 exceptiongroup==1.2.0 \ --hash=sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14 \ --hash=sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68 # via # hypothesis # pytest execnet==2.0.2 \ --hash=sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41 \ --hash=sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af # via pytest-xdist hypothesis==6.98.8 \ --hash=sha256:35bcb6e497967e73d968fc4adedaf49ca5d85a22cc8065798716581f2205563a \ --hash=sha256:82d13cc46311c4a8de49a8a8613f4e6afbeabbc037e86d24ac16e2f15a0d676d iniconfig==2.0.0 \ --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \ --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374 # via pytest mypy==1.8.0 \ --hash=sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6 \ --hash=sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d \ --hash=sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02 \ --hash=sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d \ --hash=sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3 \ --hash=sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3 \ --hash=sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3 \ --hash=sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66 \ --hash=sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259 \ --hash=sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835 \ --hash=sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd \ --hash=sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d \ --hash=sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8 \ --hash=sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07 \ --hash=sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b \ --hash=sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e \ --hash=sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6 \ --hash=sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae \ --hash=sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9 \ --hash=sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d \ --hash=sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a \ --hash=sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592 \ --hash=sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218 \ --hash=sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817 \ --hash=sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4 \ --hash=sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410 \ --hash=sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55 mypy-extensions==1.0.0 \ --hash=sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d \ --hash=sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782 # via mypy packaging==23.2 \ --hash=sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5 \ --hash=sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7 # via pytest pluggy==1.4.0 \ --hash=sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981 \ --hash=sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be # via pytest py-cpuinfo==9.0.0 \ --hash=sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690 \ --hash=sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5 # via pytest-benchmark pytest==8.0.1 \ --hash=sha256:267f6563751877d772019b13aacbe4e860d73fe8f651f28112e9ac37de7513ae \ --hash=sha256:3e4f16fe1c0a9dc9d9389161c127c3edc5d810c38d6793042fb81d9f48a59fca # via # pytest-benchmark # pytest-sphinx # pytest-xdist pytest-benchmark==4.0.0 \ --hash=sha256:fb0785b83efe599a6a956361c0691ae1dbb5318018561af10f3e915caa0048d1 \ --hash=sha256:fdb7db64e31c8b277dff9850d2a2556d8b60bcb0ea6524e36e28ffd7c87f71d6 pytest-sphinx==0.6.0 \ --hash=sha256:542823b7d493b067a0d36bf1359db5f7ae2217e007190edad7201c44145cf451 \ --hash=sha256:9add29fb7de87c241100216d9787d4ad2a93db91eced02e914a696b7a9a7a776 pytest-xdist==3.5.0 \ --hash=sha256:cbb36f3d67e0c478baa57fa4edc8843887e0f6cfc42d677530a36d7472b32d8a \ --hash=sha256:d075629c7e00b611df89f490a5063944bee7a4362a5ff11c7cc7824a03dfce24 sortedcollections==2.1.0 \ --hash=sha256:b07abbc73472cc459da9dd6e2607d73d1f3b9309a32dd9a57fa2c6fa882f4c6c \ --hash=sha256:d8e9609d6c580a16a1224a3dc8965789e03ebc4c3e5ffd05ada54a2fed5dcacd sortedcontainers==2.4.0 \ --hash=sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88 \ --hash=sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0 # via # hypothesis # sortedcollections tomli==2.0.1 \ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f # via # mypy # pytest typing-extensions==4.9.0 \ --hash=sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783 \ --hash=sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd # via mypy bidict-0.23.1/dev-deps/python3.9/000077500000000000000000000000001456445164300163315ustar00rootroot00000000000000bidict-0.23.1/dev-deps/python3.9/test.txt000066400000000000000000000247761456445164300200710ustar00rootroot00000000000000# This file was autogenerated by uv v0.1.3 via the following command: # uv pip compile --generate-hashes --upgrade --python-version=3.9 dev-deps/test.in -o dev-deps/python3.9/test.txt attrs==23.2.0 \ --hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \ --hash=sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1 # via hypothesis coverage==7.4.1 \ --hash=sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61 \ --hash=sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1 \ --hash=sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7 \ --hash=sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7 \ --hash=sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75 \ --hash=sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd \ --hash=sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35 \ --hash=sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04 \ --hash=sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6 \ --hash=sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042 \ --hash=sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166 \ --hash=sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1 \ --hash=sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d \ --hash=sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c \ --hash=sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66 \ --hash=sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70 \ --hash=sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1 \ --hash=sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676 \ --hash=sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630 \ --hash=sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a \ --hash=sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74 \ --hash=sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad \ --hash=sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19 \ --hash=sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6 \ --hash=sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448 \ --hash=sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018 \ --hash=sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218 \ --hash=sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756 \ --hash=sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54 \ --hash=sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45 \ --hash=sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628 \ --hash=sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968 \ --hash=sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d \ --hash=sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25 \ --hash=sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60 \ --hash=sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950 \ --hash=sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06 \ --hash=sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295 \ --hash=sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b \ --hash=sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c \ --hash=sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc \ --hash=sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74 \ --hash=sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1 \ --hash=sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee \ --hash=sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011 \ --hash=sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156 \ --hash=sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766 \ --hash=sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5 \ --hash=sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581 \ --hash=sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016 \ --hash=sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c \ --hash=sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3 # via coverage-enable-subprocess coverage-enable-subprocess==1.0 \ --hash=sha256:27982522339ec77662965e0d859da5662162962c874d54d2250426506818cbdc \ --hash=sha256:fdbd3dc9532007cd87ef84f38e16024c5b0ccb4ab2d1755225a7edf937acc011 exceptiongroup==1.2.0 \ --hash=sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14 \ --hash=sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68 # via # hypothesis # pytest execnet==2.0.2 \ --hash=sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41 \ --hash=sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af # via pytest-xdist hypothesis==6.98.8 \ --hash=sha256:35bcb6e497967e73d968fc4adedaf49ca5d85a22cc8065798716581f2205563a \ --hash=sha256:82d13cc46311c4a8de49a8a8613f4e6afbeabbc037e86d24ac16e2f15a0d676d iniconfig==2.0.0 \ --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \ --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374 # via pytest mypy==1.8.0 \ --hash=sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6 \ --hash=sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d \ --hash=sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02 \ --hash=sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d \ --hash=sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3 \ --hash=sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3 \ --hash=sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3 \ --hash=sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66 \ --hash=sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259 \ --hash=sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835 \ --hash=sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd \ --hash=sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d \ --hash=sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8 \ --hash=sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07 \ --hash=sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b \ --hash=sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e \ --hash=sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6 \ --hash=sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae \ --hash=sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9 \ --hash=sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d \ --hash=sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a \ --hash=sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592 \ --hash=sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218 \ --hash=sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817 \ --hash=sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4 \ --hash=sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410 \ --hash=sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55 mypy-extensions==1.0.0 \ --hash=sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d \ --hash=sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782 # via mypy packaging==23.2 \ --hash=sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5 \ --hash=sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7 # via pytest pluggy==1.4.0 \ --hash=sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981 \ --hash=sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be # via pytest py-cpuinfo==9.0.0 \ --hash=sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690 \ --hash=sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5 # via pytest-benchmark pytest==8.0.1 \ --hash=sha256:267f6563751877d772019b13aacbe4e860d73fe8f651f28112e9ac37de7513ae \ --hash=sha256:3e4f16fe1c0a9dc9d9389161c127c3edc5d810c38d6793042fb81d9f48a59fca # via # pytest-benchmark # pytest-sphinx # pytest-xdist pytest-benchmark==4.0.0 \ --hash=sha256:fb0785b83efe599a6a956361c0691ae1dbb5318018561af10f3e915caa0048d1 \ --hash=sha256:fdb7db64e31c8b277dff9850d2a2556d8b60bcb0ea6524e36e28ffd7c87f71d6 pytest-sphinx==0.6.0 \ --hash=sha256:542823b7d493b067a0d36bf1359db5f7ae2217e007190edad7201c44145cf451 \ --hash=sha256:9add29fb7de87c241100216d9787d4ad2a93db91eced02e914a696b7a9a7a776 pytest-xdist==3.5.0 \ --hash=sha256:cbb36f3d67e0c478baa57fa4edc8843887e0f6cfc42d677530a36d7472b32d8a \ --hash=sha256:d075629c7e00b611df89f490a5063944bee7a4362a5ff11c7cc7824a03dfce24 sortedcollections==2.1.0 \ --hash=sha256:b07abbc73472cc459da9dd6e2607d73d1f3b9309a32dd9a57fa2c6fa882f4c6c \ --hash=sha256:d8e9609d6c580a16a1224a3dc8965789e03ebc4c3e5ffd05ada54a2fed5dcacd sortedcontainers==2.4.0 \ --hash=sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88 \ --hash=sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0 # via # hypothesis # sortedcollections tomli==2.0.1 \ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f # via # mypy # pytest typing-extensions==4.9.0 \ --hash=sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783 \ --hash=sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd # via mypy bidict-0.23.1/dev-deps/test.in000066400000000000000000000002341456445164300160640ustar00rootroot00000000000000coverage coverage-enable-subprocess hypothesis mypy pytest pytest-benchmark pytest-sphinx pytest-xdist sortedcollections sortedcontainers typing_extensions bidict-0.23.1/dev-deps/update_dev_dependencies000077500000000000000000000041411456445164300213320ustar00rootroot00000000000000#!/usr/bin/env bash # # Copyright 2009-2024 Joshua Bronson. All rights reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # shellcheck disable=SC2086,SC1091 set -euo pipefail log() { >&2 printf "> %s\n" "$@" } main() { local -r dotslash="$(dirname "$(readlink -f "$0")")" source "$dotslash/py_ver.env" if ! type "$DEFAULT_PY"; then log "Error: No $DEFAULT_PY on PATH. Hint: Check /init_dev_env and try again." exit 1 fi local -r venv_dir="$dotslash/../.venv" if [ ! -e "$venv_dir/bin/$DEFAULT_PY" ]; then log "Error: No such file "$venv_dir/bin/$DEFAULT_PY". Hint: Check /init_dev_env and try again." exit 2 fi if ! type "pre-commit"; then log "Error: pre-commit not found in PATH." exit 3 fi local -r pip_compile_pfx="uv pip compile --generate-hashes --upgrade" for py in python3.12 python3.11 python3.10 python3.9 python3.8 pypy3.10 pypy3.9; do if ! $py -m sysconfig >/dev/null; then log "Detected broken $py installation -> skipping" continue fi local py_ver pip_compile py_ver="$(echo "$py" | grep -o '3\.[0-9]\+')" pip_compile="$pip_compile_pfx --python-version=$py_ver" $pip_compile dev-deps/test.in -o "dev-deps/$py/test.txt" >/dev/null # Compile remaining depsets just for our dev interpreter: if [ "$py" = "$DEFAULT_PY" ]; then for depset in docs dev; do $pip_compile "dev-deps/$depset.in" -o "dev-deps/$py/$depset.txt" done fi done log "Upgrading PyPI dependencies: Done" log "Upgrading pre-commit hooks..." pre-commit autoupdate log "Done." log "Reminders:" log " - Re-run /init_dev_env when ready to sync the new requirements to the dev env." log " - Check release notes of upgraded packages for anything that affects bidict." log " - Ensure tests pass for all supported Python versions." log " - Check output for any new warnings, not just test failures." log " - Ensure 'pre-commit run --all-files' still succeeds." } main bidict-0.23.1/docs/000077500000000000000000000000001456445164300137775ustar00rootroot00000000000000bidict-0.23.1/docs/Makefile000066400000000000000000000151521456445164300154430ustar00rootroot00000000000000# 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." bidict-0.23.1/docs/README.rst000077700000000000000000000000001456445164300173632../README.rstustar00rootroot00000000000000bidict-0.23.1/docs/__init__.py000066400000000000000000000000001456445164300160760ustar00rootroot00000000000000bidict-0.23.1/docs/_static/000077500000000000000000000000001456445164300154255ustar00rootroot00000000000000bidict-0.23.1/docs/_static/android-chrome-192x192.png000066400000000000000000001115061456445164300217670ustar00rootroot00000000000000PNG  IHDRݾP cHRMz&u0`:pQ<bKGD pHYsodtIME ;wb.nzTXtRaw profile type app11xGb\9EXE-,vRdI̮JkΙ.XLɞe/Ur/)?q1͎[{7|Q oyߞL {~gWBŗHKOX=Ҽ)WzI?Kúz?ȯfZdGg/dy{Z%g=aX5uu:&ߝ5ȤТGRC49ڲ݇g|cƭ'Ozi~0Vo9F1Ťыca~}%lo_Q+{ݵQM\gOێu32>>ߌ++MvϚNraxu;1-co9mW <^ixg18XJm[9>&3_q28ia ?tofv{ՍZBX>b1h֕>DYaXo3ɳwt<giˏF`;n3̴SeO+otqX(kسgrerΞbu+ѝU]Z W0]ye }@A \}m.7O@^lg=NfZ1T%`7 s|G+ԖV߳X]L.^Nҫ ]`ܞFJ #9wˎGKǜM̱&{q?|*鍅Vz:ױ{]6COWgur0?v1" H]Nѵ{<1{-yԙ3Nbz}}Ҽ_荏h:a\<#g>qdC1?mx fȿ"@H.tUb~;7oG \ oDCsC tB$gjE2 6{h6MzKuinw:>;3%,mWLaVx&j8ʙnv;Yu 5kP^VK駌P&g1#cG86R@uX9 n8̰W s\ՈwbOB0vW;[ usʭp386@&uaPǩkal9kB1n9E`Vɹѐ[`Vp ?P1Ɏǭoi1-q?CڦzIFn @tsHR@!N웾BoL!,}Wq>(bZ8ZdF<ȽA+ْ8^x<24_&Խ`(8)j. !VLB%hqL@, phQ(,B~P|A|N΢reBmd]ZT.sFu%~_NZ97c;Gg_/dt~qn>7o&L]tjͿ~o>'&N3yw;^3-0#ZP TmtAH."~tĬG@P;H>aa<Ӫ274N=֊XTTu`f.#qqu2X#K ;jVD3Zun`fo *esv4ZN*jyQζ%By@RE F4/W p 2肅sm00 &qYJsOurᡴ2y+*iA*;FQT. J9om\'h-:I[kd%,KXla8ڇ8/a6a9 !qp$&0r"hTfÆ3"d]eg\k=6,a k9-  SB 3t8jr1qd+WqwQ+5Wiabqv#aAd“m-UW10i{㇧ET#ɣB j^V O` LA#{71PM8aCWx㵷9|BؗfiW 2BZV{E"Jd8>_Ue F^2P^0qpè2KVTt[^3ahX̵x0+Y6 j]ZS)8[Qo9,0Z%:D@I='Pq9p. mݻp%жԫCں' = ^3BPюL DFwf y@ ɕsE^iMRR} g>D׸_5J{֚Fշd1 {dmoǃJhkZ4A4k>ǘ)e:ʘ E[;q&H%,oD+G}w~>SЏM8a >Zqirr0H+F[@d^Xæҭ&ý0g`ˁdot]2UZvrdXtFZ(Q)"oGHvd<+͌ t!*Z / ZSR>i5B%#Qt0- ЗwX.8Ƣ=~!TU>o7I '@@/v`[EWsM͟'U7>J ^ P5\I5@l.&)"&Yay;eb 3F.G  yjz#Xeg,D MBp B@_11!p0;^'t[Va:4'*ȵ-cŲ>pd nJ. !9" *F器 Dn!JLVRjKC}r_9LCB<@+ZrZ!紑f4Q!bP:6DG XXqU8U#3l`-3SՓ u)wW0gΞK Ϗl6p|_̶8hU[lzt y݆ɥDŽH BqxP0La2*UZ-8ܵ ,OK|Ҟ"2w-W/5yS Qb֦/N)#5plϤ`^Xa<W?֥>mTqVZݠyPa/_ ͿӭMNV_MJP/eyi#n_Iz x}^\S৏pHc0ߏ׃axrϣXձ_6a{f#n N)t FEJӖS+d^YޑBI:_*VbT_ NU8N;c]gxOa%X@@ՐfJA,;~9l:Wma3Q D=*h|Ø];>H@)6dVi(K!MȦ' whpH x:Y.>EZ]K:6Ÿ}/^'?$~+2O~/'vI Q5X+GTYnRTb W^!AL)j''}tpawv]>wO c(R'x+OՊAh1F,0vQ>u7m&W9S`]'˯HƼ/R${;X*5>~ -u,wbl-1Ư=ߌyϨf̫P5go <730Y,~@qS R8o8$17Nc] X0 V}hspK{FxD Kņ{Zfj,:X;R' `jBK |0ATh[:*U!Yt0ׂIyMh)'U8UjIfD{D'`B6HOEm *JU?0&-_by@:qlȵ|p{>hDWhx:"ƒ! bKYCsLoq2rP  10x^p". Nyg&^6.VIM9D>=mX, }0 ?qœ XŢJE&S- ޖJLj Pwt>ՅkAN}UhG=|uNgAK&oNh-mqyDYRr@.OuV,gNm]N %Z sVu*#xe#0c`'u@D *Z,Bm*hd@j3Z i 'qug9 ,[hje1ˬD?J HXj=}w?z7⇧2F;?{Y}}|T쯪'5wbpv/MS^߇ >;[̿7>5Ǿjk ";hNBD6ς2 3/B/X^M{9f'ړ49"vsx:MoRNRzE殳]0 ( m7Yrp0F#Q?ȪuLn`/>T0]$AZSG&>y?zA.@Q_o̮5f90a4jk$6ۍ(pc{#1!!suip6d+@3fG){ 4 ``V@" 6{%VG{#{0ﭘc@eajT$Ə7 Z5<I*TuE'M4"nm&TmBFKm0܉5bIWE4C+`Um1t+ 8·Ʈoʑ\M;>`&35Z-HpsiJDdu/l6p#$[Lo:+M [0h` ?E^1CPօU5eeUYMWaԘbe2UE Xu2\a>K^v!aw؅e "vE<[S"3?Om}}6}xgB1_גKF 3*`ZgSͿo|OX2;O{VU SreO;hd:YQu0pI˞,!ZHv`iSB;YW5vh?dgp7 lԢꢳ ׇ4IwMs=bRPK Zr5[YuМf:tTUMCxN J|ڱھ˗0&/@%- 2strKCFĪI˲jvcM:$K?*`xu[0QbXTYeMB9-wPSQBk4n[K IE./uTڂ$H1ZܨRTؾ&eYk1<ݰ*a] 4"eA)sTyyQ mq,Bh :bdEp2da ($;53{R:'mяaDNG0N'ܖN+mQ/݈HXvühy@ ̈ę쨽q$]I mYV]rHEty7p co9rCE؆BLRLPwÑ'I8>H|X2\sM(6tio ݧE>#Pw'ɒdOLw+0o\tǚgcf^6)rfGvÞW&< G&|RL#|8׽8z( '&F[ 3*$uȚ@ZpKݨPy&0҇?'J|y mLމ¶K}" S2O$vz]n5v%ƣ{bKwtf%I: 4$umP}[F:&ã}x6aIXCP:Dڛ1L4J:]*:Ӆeh^Wtv98oY!x7?ƛ|WYm}x"ڭ\KsQ_L*|wܧ2B#~Io'S!tS|L|P{Uu^MUbe 64U~7 g{YO?:+TWYı|zSX$D-b`x , p52[2mF>~gW[xX^ ­55LemEIU.t_*B-x4bN-Q1n axD j7,Hn4 sEO"3u nLQɉĦpU|7ٯfGAhht,:"IqPBO^^EJiFIch\dxCKD" Um^OpUYlfW\v':*^0Ng@'Z- Y!! ¬{pP*e'Rlµ$ϙA|:xX$3T7 ]41NF˶ҽlNW#wƵ( `qS]T /«DGfQ$l)յC=a Hڑ;3C/c>:M'TW& D} G۾Vp</|%,ԙ{nawMc: Y8ȣ}//TU:2xRt "cb[*%#%4cp2!sēlT`l]άUGzF0e] Z?c#ŏ7kM_֨S$}M(Ne Zv=ގYA!'h6u(8s*NrZMᗱnUA ӱ7$ؿ@\QcȤNM]ZudZwv3UQ)Z#2'-հ`뻶]u 5#0nNۀ8ġ#vq $.VcsSk~5'g1{k{-[%MAMBAR⚮#3 ­W&Df`$: Tnk_zTXtRaw profile type exifJLRS3.Ss 3K33 0I1I100J4 6654 b0P2sA #d 4CbIDATxw]u']k[^OOU 1blpqIff~efIs^DZۉMܰ1`@]zOr^skN?t߽瞳k^^864$L^(` T5"PDpފtO?l@NR5j.4_.`/:}(L/ hA@nw!"$ swC'够[ϊm/k }cL5/6%4ť B):w j}- #h' `+g:iu gGRojF9,%m<ÁIwppQ׳P[R;X#`O@E47Yԍ95kG/V7 2<.A$m֔߬3l#s!!uJJ>XU|҇S?XÚa>YȔŸsz|}tMz0#?N Q .nJgVrÅD[@SyS֣~=5v$M( t;2yf?'.stx]%vE:S hp}f#|DGKl"m3GW:oÖp؉8AjMk="~j^0<Mϝu t߿F#5B͸Ys\,-7py/:uzHLGu89Zhs'sY?bY#Cfڱ-k?b &H ]F+~jp3w sCl;VQ[8(f6v~1^f5;lnCN~BE?qt^]-Ah3+|!Pې渇_5gK`,̖N_ʹ˼򚳰lQ1͋/ܩCsE9hN-gcC?^h1GjD| Tc+Q Ę0;?8rֈa"+2 ʡǙoݎ9g~.K3fH ?{ڱ11?Ј 33Yb_ȇc8BG Nlf,9d86H 8p G!e!eØajZFk>8H#[/l^sLl?|.5B7:|nl#8E 4Լkw9TF8*G %?Fhm֑596^z؁|4l>Lev)޴|KDεռhrԑ.LD)(W{P5B].]u9e_r|'[XUTV'&LCxLDb(B" *pOOP*&([f1UEQ%ӕZ-V\'03 &NSdhlCb5Jho +N>_[>:GۭRdh{i+>M]n٢E]aW"cA$LHUJe/rhoꮑX- گw53P Y =3C=+Ty٪eg^K:V-CoO `KRC{҈У(Ŷ~xŧuC{v2EiphUPAHE8S6κ7rPؕR :MZi x 0Cm?wOt@U!6-JH)+q'@ @ V,D]/;,O)`ZMxHF {L E0:Xm~71Yg8 H -DR% ]s}⟼u+ &VDJ4XM(;Mx}BH8S"36[߾屧7U4zG@M7Pw xٗxM* |IVi+(*Ѥeq}-|>{(6ZG@aqW6XX lWs QT@,|)@((d*,;"CU+eIR vatIl#\}yޞ۸u65ʶ(BbmDcY@tA͟FX71' ގb޲?~Ӻ9QDN,BTE,J!QfxF[j* Ac->u-A449b꿬&E`u;%CUU rMj&#U4w<Sr}yrz?m%`Z2C=՟WF`Z4Bܺ=aN_钋ٟ5 )  WW_C?u5]@Bjn&E>3L bB]W}󏏌(V%`R@ Һ]?ᆋ2p=]5:(&j@T#A1N@Ë 5$s?ݼ=ʃ { /[\!O5˜ YS3 3T{n",O{òjUDURUǟ9P.r˖}P$@B@S @'KM'l8uŶ{$s6KCW8S- c}q\uPFbuBLAVQjSͿw O}.e _~AL\WX Q&&-\6rc{|O[޹CPKBW*G%$$bzꆞK.YgGL10'Qҵ_U"V*&T 5Ak@J*j jPu/hZ%`rd]=X[>ʽ]{irZ5:a V.9[L`d ݋ DN\~M:qr}k@8 K~éK 5$~QP(D J,W^P!M P@?a%=+6]pj=ܲI 5hCchnE]2CjPt >?#X0 &"rv6'78JFsQ\ʹ=1&ç pUi(TB)Z}MArTP.zoo޹|Wmrz7 D`~`#S>D$ W*yٜȮW/~s7':Duq/>q,ߺ}bԲ|8 b[=]8q%T8F(H $vr<߻}wz?1 `Pi'z ʽiPJHU._|vwOs8A0YmqH5S 7^mXu&3GX윙v(2" Y&_ocaxO~dgI(J*O%&8@eZ zO\e) ZF* /UDX^U(NsN`(vwN^[ D}QuJEpY|7o}O~ O>weueljJ1rfm2P?<|iŶ>RP_q/omXRe::Hp t^rB%)>F(Zª\X&|Gmry뻮j%!#%IZH4|܍o'UI6}Y@bKO+Gdb"8Q<ص;yuob6*@LJ)kD%mbOdQ@sE|Q!~fy#HmbOJflL[mz@F3I1'8xv7vBI ):?m*(+?tq aRecX9LQIu$ JkKEz+!#xP)`p194vIAbWU%\ %2*wo KFGoWM/\yX{&Ro Ov A"5KNfpdm+ت]<,bXjs4S"beEL<=)2Ƌsq@my*`.tzj+i [( fp-sxl"UR%+dIHKRSv%"babrD=cPU.zF$ U*q-Łǀ,gO%C1(;!0sjݰZ:h<h|ԢTU4R4 hФ36ր98D4f=oI:RrScEy?՞ިPRqijTYa hƶ@AfQf0w+@0<_ї(/?|:אWy;$!` +F+ll(1FR\$`"3oժӸ|#Ei'zm-jcuWIDL>R3okefЄoŚ 6 ɕƛd5zO:a{}TWk0Y.1j Ĕ5o 6(9}8@!A=h4{'ьRX je?_:r.[㵫ED P"e 0I vnxZ_hԽj這:ּ4 .&YU!x_wR~}vԴMwT@yDyTW{J T1Jx{U(uroRZs˶h0%PSU'*G!ܵ?Uxi[#QvTIn- sA@G)TQ8 Wd;&΂hyI6Z_bfQ1Չ®7}A^~B\,@5 M2]/>LNJ.bIAȯrP[_w}A Z}UU42P&c] WYYޡ=ZmMZO+]trъHjpRک0*Nءq1;(i0km:BD`'Wr\-nTi;*]Ȧ3yB:J07?_/v=IkˆRQdQ*ӮD@<0˗$CX)&u!uKlXmv !P|ʡǞ>H(ęZSRlakJsҺت2DJ'4c/ TAvETDY)YcD4>H8Njd"dOnjَ;# iBy;k?[}j{6;Y7KD=7 _9R՘m k fҌ{"妧yga1K:'"LOE+NR0µq""~cvj⨵w<> >|YBDqWuB)GDa}l.-uvA:SR\6jIBjI,%RoD_]0Yq}vcc# )Uby4[8\، @%I)u3zOTE3>%ccDbȹ~GAⶍyjScckIXUDs= (R !.2" q{ 4b@I]pH#&jj<6t8D l|>Z(iމy2YRNм%EZb۠uFA(4R("#XvYKG"rdx3gr&6Y6c>F5F" 9fPP:94P^^4;S:ۜ>F-1[;h6s|1:]sP:_[awX6* ՁH)&?݆YתR 3 oy(V;tr ,Io')KUr>UUIWt)D OM!`yyh~;_FP$W`"ejc` 'D'!DIfj%dT&(H\huXBV*B)gԯE29m7&SN;DȰ-"p1 npqYU man qp(j`&?}:vr:.g+oLQ,YRNU:Pv0G swP`IUH0j͗4GK0%0)BKN*ġZj?K) 0X. Ϋ*"@#(|RY͸M`7#a "Co?aYA%!# }10O 3!o;e#kVB4W}Vuny?i>= ΀]W|[=IXQT.ᏈN׫o}[?FL 1ڴvpW@PC$\.Y@n)h㿹4`U0Uq1T׾g3¬Zcd\̝M_V(2 H\zzk564'Oh5C;;8#쏹BŻ57XI\d@.3K\Q$'Fp{r#E["[n[HPxƕdxڤ* Ɨ34jQL+r7\pƟ_LOKT֟|*!D^L]#4I jK ޫB'7}3s̍euQ6 -,!ETk[7o|GG⵰ٰG(_(UkeH=S+PE)rɽzRw[Pq_z#ܵ -)[L_'vAc0#ZqE?qIo4^|4rx@VBhyZX!(HȨfU?OqeCPhE'[qh߁.CKB?JYLui=R%K;k_Lg?}ِDJv,'VJ6};m5P,Ye+_ꡝU,=K.աwZ0D5:5b<[nw0ѡ6k69;U!@!ު] ID]tFQj̧_oQ2\pNG'2񲢚}}~QIik.\룓NIzM wUg`R}HRP*{lޛ};B=i7V{&'|%cUqV1bD0p\&S$SDdLr?|q`*t\v#=Oܥ1C,NW^F6RCm\Rjfx)#4U5%ĵ-OzJM(8CJ]͙hRcni4!OM _%HZ=Ti .NIJJ$B×; g~E0 A) {Q1J Eg..Mc߶Wn>1Du_{yn4Yܳlic&xBRhZe˺~Ւ+߷$rUr+c$|^wU'w ^[f[ʕl /2_#fFI㘬x#YPͷ1fזbnN4x5j+^4L>;v5 @jz3?ӷKQT |7J& gpB)DpɠA|ӎy;ؓ`ˡFڔVW'v v 4D&b2lpť?iŘ;ʢ7|RRDDi |hw@O}[÷>R.ET4lhow,>a5ϧ(%?Œt(`5Mޣ Dle i$luN@)uq B @"j5i!*% E0W'ޯ)ARR>o.鋕+U.z`"Pj;I@ uz+?ӕ]P=)l3g/2_.*$+p!DIQ..VE3p.pݓNlV7|oyx"&j#-ᲁިIZ؂S/r;VԛYT}|k޲W]|zƹ:̩_Xe;UK 6D`HXINvVdC)6`bbs`E"7T K0ܵv, D~F =r,-3Nl saLy 7[!YA)8ރ^w鮾Q=~m}k(i"r/K:hJ>Bd9<~w|O?dC99^2DY+OYtĉ˼ JN+r<]ɶ|yTi1X-J'ouKhbXbgHi1!99KӖ[W//jm?&T 53y "aP_d#[QS48_QẆrB\c*]mf ?ד%QN@key3 V8(Rt ]^]R]4v.ARDLCrOfI n~BN~XAJ}a0uɚԧӴ6Uti{M/ \*]#(HW }4$I_Vɪ;]L( ʛ-4*EjXu%U)2dG%HNፀ^?zӜ0mLYLb>_?Ai (L\s`=yy|;^&AQXD5/BVS+-wzc\`E)ZKz?P2oTMwb6Sl1o>Pشnm ,ZG/^Y1tsۣ"aꢋ..,漨d! ԥs/EW{?[Ndf-!U+8t }Ḙ0t3\;8 kzWVD&M*Y]6DHhd5:w$[~V K(W\]}\L\B.LIheLTfT7jhZ@ʇ)fJ(W4zթ[=4_B>BSυIHtqjIs?wƚ)1Sdɺ\)Ç*>ukO'k%f"(E8O[.ZW AԁF$5A So'ionejT5YXd\Z7tsi}eWnA0 k>WdM} @xRp&?DI4Dڨ:|ɝEqk ڱ]땣-C & S'4K^ۆ@qh(b#*Jv-%~{+$Ą̆dW a9\Fu<'w듋b-+' a9,"v4cVqȈBϽ0Zl8^(Y 6-Q fa(fA|P1 У<@H ~$/:*S}hH@)<\iO^4gի $ULN`tW |ޥpGq۾>a -_V{~'" @f 9IEae% F˯v]G=%B옰6Y@$>\ LC)%v#ң%%":7pZRb$W3dΜz"8Wp{!Y]$8/QݪFbQƢ6f;kY&gLp6{j4P>:󭇆?u;cdhQrU{lb(q 3 iR8izi HRLZc~S;b1 b߄:3l@F~扆)>0bURaYk,ζ.!c/ܔ%nh9Gy>XFDby[m[ut7*L(,۠KO=\,`5*vZ=<{p| Ĥ;o{lc׌Z3̨T?ϕ&qdckzzj_s_~9)NEb$纂R3NRN4K8e9 F &Xpv_.pGAaP.n8pu8ZHiԨK3L3Op֮r1fn'Gh*DJܿ\Zu~lEw>72Ŋz8su+w@WG(*Y%KTFkZkAFN[[ؕo}KtM d`1xqmN}ʱ3VQe8DZ*71%5@T@dN'J^ y"PPU YֆH t<0,IR]1դ͒?id )] ŤE@']!W2#5`-(+)-"2ULBdS|+^O&;:WGDB}_xڥVQ)049 (A C6~ҷ;4e ]N8cN]|ҁA]w/A2RIܘ]#CompR)6$.ćcN>3/%C vFdעɓ࠴kNA T!j k|ēreVUbW猻W+ s%((a0@!"[|>WkE Dno灟T.Z@iy'M)n4Jm}w5@FQ~IՏ^;vcåIQR$LTXE:ev'h#?/إƇ$#C ۉ0ë#99qJj"ro? LCMuF ԧ vS+@Z^k.D()i$K\K^L +JFU$}IJ (p.\ܛlX__.@4QLJ,|pҔ TzU{?ݮgu)Mtªڇ{I|PYvuE hTPd>Tͤ#C vb놟]ycx$B=Μ3 rGKPCVo%O=1%5@|^wm2|P7*QL=$Yz䔒HI4U-H/>ɫ[# -HΝP=FߕgP JZh[Hw>=ꩍ#Tc5s=42yI×n\6ZTO֞2\dGh*_mM#c)R g󮄍@5H45 OIӢw d{-}ߑѽ1TtGNoKΨ^%M\))qWMV`H^HEvS4k>~}?j1 %˦ᅱ8)ȤmYds HTcNvwxx M+W;;d/UE,2N ~DY9Gdrwy>t[3n$ 0Q L,,/gK 9%7]ů { ;R( {MW uMAyoy4|mn0[_ 2cs%@cOjhCȵu»N}^=*"4onv>c'vFYӮ_ -*'NɋySE9aԑra"j<*2 -ɋJBitΩ7o"ipo"$C]毡Dϫ7>b{Yg\QS .q@de%= "]shKxꑽ11T/n pniN@%}`e"}y +"h# NvЮ=q#"1zD=NR$86H^CXdEYu 0ӥgD58w$ªoo! Vժ&}m _vw2}>_H6OwŪqi\KiyF%z\ :'XyЗf+l ηp̞]1%>j4b^̜,.-?h& 1`3/Yz, "^KZ/uM +HL>^GIA,nNJɤ̜257fFʏ7q{(rw3*4]g(ԕV(Ŏ#4UFOxJ9^'9n~$8r>~YSs.9)o',Cھ&,Vm C/ "84XI3p$! |QM<Ϛ/O\nb.k*1n'*EjI'^MN$Ԓtq)_uƫQS 6g٩("HScODԧy߫Xyf; b!tkПt`S& q0!gh(D@|ɡ6̾ž|e4s|iTg@`\MͶ␯B_(D[SIl$Qk l;ՋĢ2Fi\q(-m@M`,,ȿ葈$ GXmNS@vk gʫɌk`(.`\C̡`CВfʘ`-c-2s;v, pWTȫP=DgD]PnZHa㥿PuY^r 84/ykM.t KqnrYPڛ=3ڟNQvb8J&̪"pXLZ(!HS),]C.f@[ 䢥ZXOU +iVjn&xEv-5D.xRm=rEP2m Fk;K|W\s3ƯJ-U')8ަ]e~ )h]Z e26,VQWs*M\jL궐MY6bXʧ\:C3`@ `F28 "Mi"_4ٸ>*[@  L`X+UvŒ 8ffAO[A)KRc2Imu*.SΚ.5 ;ajy a!nq~f%M(Fxj`maWH߭b)onHQ= 9}.6=',l| 歅cnI}5ؼрzX:ʛ/o\r(”RNq˹rȪP|bHcK 3#PfD-`ZL%Rg#Y`D1ի*.)LBGĤQQ *4m27MM}BReg&zE^:p<4DեfX:9oS[:|剉J1HyfC %*QUuy5=ΔLIJ }mxF}');rg }:Lὰy#msF 5]@ f) YL . 2,:< yG驭ఊdY\<VM?oAX-2;5O(@w "s4RH͈ Wd=t`5mvBt@iM&ŻxY4H(]ͨMmC.SM<ژ)lAAzW#@q{/97|>er3RPZ*?F(6QF[An)GWn(٥#`ȉy2ڳBzsy܌^N-# Ӛ,!;z4d sTᗰ| eC^YJ)nRfFMG^-jcj.ܤņ˳`P@(PЖCL`iA3cR !N,墯 r (;Rdѧ9dC_JY2H};A#m}ҟj u j*@SvFΪFTxW'lqdj e 1Rp$VDܡ)˚Ցr4g$_}sUaONms xPnƷet-V.)* v'hHD{ʢ,`MxjA^UFo;QadnHde =L[ҪR.4,:3pjP3i J$vKi{'?k}v X١иkKVtA R!J`{ Zy uY`,BoCDSB!c9ە 9sc \~6|njp}jLƢG.!2qŲ'۞;(~֦6hɯ}Aā*Yme!D(SJ;za*HԓPыY]}m vxyXci\ՉW;"*4Y%V/:F̺ JxX/3N-^pF̓'mwtΉ*dR3w_*!+Bep}/?>DeHd|Clҭ2#D .e4鮓 0@!)DW7r4x%;ZxBmb,A:Zؘ }-TBUp9n-Ҿ\gyLdSɅZ5!R1?&Qֹ E #F*=T}x[g|dO^NdlMmo>@P#qrJ̯=nJB}?;4K|EPOwΜ֧eFJ]-DLui9n#81gZ,m\iٹMM[0%tPVaCD9Yy޸`DN7㴙&,L3#jҿmNL' =1:6: aNFBF4pkXq#9>r!chǰ4RuRm`\:6V8F|\bec;[#EF1W\ƿ1SOq ,ăbF`Cq yv!톯1euL0w>f@3K>G#}l؏cámپOC 8ƏHKA:_LW4_u.( qBp_G&L7hYZk./|\lpJf,Dmc.^3 SY_ 5 0 H{1?Axk0a0\xm"-'Ӏ/:S#;\dR|bQ;)Mj{cdp*M57]W;D9,l>sB;;-ŹSw&Hnֆͥ#r׼nh?+[2ۺ)  2׼ƕK7%tEXtdate:create2024-01-25T21:10:59+00:00 $%tEXtdate:modify2024-01-25T21:10:59+00:00x tEXtexif:PixelPerUnitX08*tEXtexif:PixelPerUnitY09I@tEXtexif:PixelUnit1eWzTXtRaw profile type iptcx qV((OIR# .c #K D4d#T ˀHJ.tB5IENDB`bidict-0.23.1/docs/_static/android-chrome-512x512.png000066400000000000000000003633431456445164300217670ustar00rootroot00000000000000PNG  IHDR{C cHRMz&u0`:pQ<bKGD pHYsodtIME ;wb.nzTXtRaw profile type app11xGb\9EXE-,vRdI̮JkΙ.XLɞe/Ur/)?q1͎[{7|Q oyߞL {~gWBŗHKOX=Ҽ)WzI?Kúz?ȯfZdGg/dy{Z%g=aX5uu:&ߝ5ȤТGRC49ڲ݇g|cƭ'Ozi~0Vo9F1Ťыca~}%lo_Q+{ݵQM\gOێu32>>ߌ++MvϚNraxu;1-co9mW <^ixg18XJm[9>&3_q28ia ?tofv{ՍZBX>b1h֕>DYaXo3ɳwt<giˏF`;n3̴SeO+otqX(kسgrerΞbu+ѝU]Z W0]ye }@A \}m.7O@^lg=NfZ1T%`7 s|G+ԖV߳X]L.^Nҫ ]`ܞFJ #9wˎGKǜM̱&{q?|*鍅Vz:ױ{]6COWgur0?v1" H]Nѵ{<1{-yԙ3Nbz}}Ҽ_荏h:a\<#g>qdC1?mx fȿ"@H.tUb~;7oG \ oDCsC tB$gjE2 6{h6MzKuinw:>;3%,mWLaVx&j8ʙnv;Yu 5kP^VK駌P&g1#cG86R@uX9 n8̰W s\ՈwbOB0vW;[ usʭp386@&uaPǩkal9kB1n9E`Vɹѐ[`Vp ?P1Ɏǭoi1-q?CڦzIFn @tsHR@!N웾BoL!,}Wq>(bZ8ZdF<ȽA+ْ8^x<24_&Խ`(8)j. !VLB%hqL@, phQ(,B~P|A|N΢reBmd]ZT.sFu%~_NZ97c;Gg_/dt~qn>7o&L]tjͿ~o>'&N3yw;^3-0#ZP TmtAH."~tĬG@P;H>aa<Ӫ274N=֊XTTu`f.#qqu2X#K ;jVD3Zun`fo *esv4ZN*jyQζ%By@RE F4/W p 2肅sm00 &qYJsOurᡴ2y+*iA*;FQT. J9om\'h-:I[kd%,KXla8ڇ8/a6a9 !qp$&0r"hTfÆ3"d]eg\k=6,a k9-  SB 3t8jr1qd+WqwQ+5Wiabqv#aAd“m-UW10i{㇧ET#ɣB j^V O` LA#{71PM8aCWx㵷9|BؗfiW 2BZV{E"Jd8>_Ue F^2P^0qpè2KVTt[^3ahX̵x0+Y6 j]ZS)8[Qo9,0Z%:D@I='Pq9p. mݻp%жԫCں' = ^3BPюL DFwf y@ ɕsE^iMRR} g>D׸_5J{֚Fշd1 {dmoǃJhkZ4A4k>ǘ)e:ʘ E[;q&H%,oD+G}w~>SЏM8a >Zqirr0H+F[@d^Xæҭ&ý0g`ˁdot]2UZvrdXtFZ(Q)"oGHvd<+͌ t!*Z / ZSR>i5B%#Qt0- ЗwX.8Ƣ=~!TU>o7I '@@/v`[EWsM͟'U7>J ^ P5\I5@l.&)"&Yay;eb 3F.G  yjz#Xeg,D MBp B@_11!p0;^'t[Va:4'*ȵ-cŲ>pd nJ. !9" *F器 Dn!JLVRjKC}r_9LCB<@+ZrZ!紑f4Q!bP:6DG XXqU8U#3l`-3SՓ u)wW0gΞK Ϗl6p|_̶8hU[lzt y݆ɥDŽH BqxP0La2*UZ-8ܵ ,OK|Ҟ"2w-W/5yS Qb֦/N)#5plϤ`^Xa<W?֥>mTqVZݠyPa/_ ͿӭMNV_MJP/eyi#n_Iz x}^\S৏pHc0ߏ׃axrϣXձ_6a{f#n N)t FEJӖS+d^YޑBI:_*VbT_ NU8N;c]gxOa%X@@ՐfJA,;~9l:Wma3Q D=*h|Ø];>H@)6dVi(K!MȦ' whpH x:Y.>EZ]K:6Ÿ}/^'?$~+2O~/'vI Q5X+GTYnRTb W^!AL)j''}tpawv]>wO c(R'x+OՊAh1F,0vQ>u7m&W9S`]'˯HƼ/R${;X*5>~ -u,wbl-1Ư=ߌyϨf̫P5go <730Y,~@qS R8o8$17Nc] X0 V}hspK{FxD Kņ{Zfj,:X;R' `jBK |0ATh[:*U!Yt0ׂIyMh)'U8UjIfD{D'`B6HOEm *JU?0&-_by@:qlȵ|p{>hDWhx:"ƒ! bKYCsLoq2rP  10x^p". Nyg&^6.VIM9D>=mX, }0 ?qœ XŢJE&S- ޖJLj Pwt>ՅkAN}UhG=|uNgAK&oNh-mqyDYRr@.OuV,gNm]N %Z sVu*#xe#0c`'u@D *Z,Bm*hd@j3Z i 'qug9 ,[hje1ˬD?J HXj=}w?z7⇧2F;?{Y}}|T쯪'5wbpv/MS^߇ >;[̿7>5Ǿjk ";hNBD6ς2 3/B/X^M{9f'ړ49"vsx:MoRNRzE殳]0 ( m7Yrp0F#Q?ȪuLn`/>T0]$AZSG&>y?zA.@Q_o̮5f90a4jk$6ۍ(pc{#1!!suip6d+@3fG){ 4 ``V@" 6{%VG{#{0ﭘc@eajT$Ə7 Z5<I*TuE'M4"nm&TmBFKm0܉5bIWE4C+`Um1t+ 8·Ʈoʑ\M;>`&35Z-HpsiJDdu/l6p#$[Lo:+M [0h` ?E^1CPօU5eeUYMWaԘbe2UE Xu2\a>K^v!aw؅e "vE<[S"3?Om}}6}xgB1_גKF 3*`ZgSͿo|OX2;O{VU SreO;hd:YQu0pI˞,!ZHv`iSB;YW5vh?dgp7 lԢꢳ ׇ4IwMs=bRPK Zr5[YuМf:tTUMCxN J|ڱھ˗0&/@%- 2strKCFĪI˲jvcM:$K?*`xu[0QbXTYeMB9-wPSQBk4n[K IE./uTڂ$H1ZܨRTؾ&eYk1<ݰ*a] 4"eA)sTyyQ mq,Bh :bdEp2da ($;53{R:'mяaDNG0N'ܖN+mQ/݈HXvühy@ ̈ę쨽q$]I mYV]rHEty7p co9rCE؆BLRLPwÑ'I8>H|X2\sM(6tio ݧE>#Pw'ɒdOLw+0o\tǚgcf^6)rfGvÞW&< G&|RL#|8׽8z( '&F[ 3*$uȚ@ZpKݨPy&0҇?'J|y mLމ¶K}" S2O$vz]n5v%ƣ{bKwtf%I: 4$umP}[F:&ã}x6aIXCP:Dڛ1L4J:]*:Ӆeh^Wtv98oY!x7?ƛ|WYm}x"ڭ\KsQ_L*|wܧ2B#~Io'S!tS|L|P{Uu^MUbe 64U~7 g{YO?:+TWYı|zSX$D-b`x , p52[2mF>~gW[xX^ ­55LemEIU.t_*B-x4bN-Q1n axD j7,Hn4 sEO"3u nLQɉĦpU|7ٯfGAhht,:"IqPBO^^EJiFIch\dxCKD" Um^OpUYlfW\v':*^0Ng@'Z- Y!! ¬{pP*e'Rlµ$ϙA|:xX$3T7 ]41NF˶ҽlNW#wƵ( `qS]T /«DGfQ$l)յC=a Hڑ;3C/c>:M'TW& D} G۾Vp</|%,ԙ{nawMc: Y8ȣ}//TU:2xRt "cb[*%#%4cp2!sēlT`l]άUGzF0e] Z?c#ŏ7kM_֨S$}M(Ne Zv=ގYA!'h6u(8s*NrZMᗱnUA ӱ7$ؿ@\QcȤNM]ZudZwv3UQ)Z#2'-հ`뻶]u 5#0nNۀ8ġ#vq $.VcsSk~5'g1{k{-[%MAMBAR⚮#3 ­W&Df`$: Tnk_zTXtRaw profile type exifJLRS3.Ss 3K33 0I1I100J4 6654 b0P2sA #d 4CIDATxw%u~߉{o3=`=@CA(R4dH+Z(V{z?RJɕ- H3 0wϴ2e9H{fuUwUwt~yfFDFsXb@ ТEIhy k[OA-Zh@$}_J-ZhDȉ ԟܠE-Z@@2S >]WkZ\ o|p-P7nL`;o[hⅉr -ZhF[hBi6pfyT{l Mom-5?eRQryk;lJ13miamni?7bViQpUάȼQf{^ԟ\h[=Y5ްv$Mmdf.v2ʅ6 {nC'6aٖtF;%x!Qm$cؒEY[\(:C5J]IlqcP]ӯkqmvnDol3khЋo9Aoԇ/XۚY؊n=|Y]*rz^k"a=qbֵH68>՗-э~}:t\|p>j]^4xSγsp+q_^r l蹛 jswntݜ6xYhiJ|ve_vnʻ>ۤ>!Ec5ydЦȳ ,jsgts wsژ딪˚yx[>Ib f+ئ['3/y/u=M8wwDmsQ=VF76V՗Mc~Sstnj0~Mz ʼn9|>|_:=aޮWhqijmh͑q[hѢŅv!ZТZGZPz1\BnvK1f66+Fghj^S1^$R-ZB3jZТEv@xRZh⢃eYmտ“6nZ#em̍<6د6U):ڿ.:HFm C{6/K^!἞&ZܕE'K:hѢ aaojb3^|9,=Z7-.iXS/tlJRhq)c{ؔ=%5-/dӽs^ņrſmެǶIDm;d~톳]cFZyMr/[l]xf2-ZZoXZ\.x!iZ\ZذbBᒴTє~= 7Y/|YE7m[=W7+`97lqqᅴi;V6lUl n@-dMGZ40Zl:.yڂŵTUߺp- msKD{Q8hѢehѢťMz G0i29댪?yշ6y S7v ʹbmQ]bۭ&f_H"E]|ƊuwV sle7yQ 癏ClZd- _-.u0ږ8?[d2 /WjRVWo[qbmq>1yrm\G6֔nBmIA+ehq6lQtQ;K 6[hqJx>Uhqм/`;"ElVƭւhsQ-+ZUv-)R9mS1=vF}rQA`f͏i!g߆FP5.[@ٴ-u\X}lfc1E $kkتv9cXeYZ~sΈʉs9BUfjkcUh"bf-,hf) ruX-ES-\=1V]gtzdt5n,H-;I3a~5 l:$K3HTXA)ooBu* `w/l:.N=Ϛm ~@‘-BB@)i6/s-\βU_FVi5BWRlF)Qxf3XWh8m4ix/V6֩dW}A˨g&N͓F 9aR(WwϞL< -T4YB5ыґl1+f̾2dDFe| WѶ>}U(g(2 ΰ$I3SCW s̈paa3e2 HgMC*\.{Q\$"y}X*4!LC薆 D,A& >$$@jTWx3oJf6'Z Jg B"P΁FPU2uB"f@3,xĩSN8~G>'G+Dt5FAith5 wOw,n]=bPLDfԐvfk]}n[w3f hL¦,w V^Eh!"L= !#t|zGv|NX,tdtai}VVZm|h ZYF"XD WIo]//nk횝튃Z:Aaʩ:ʠ(PZѺ`$t#HgOc2\쏏9O<_G8|34u`1Miah|6{w*<v-u hJE/Ew|uw.D #TSXBmez >5K@KҜHЙ:Q,Nc_W̧{g7CCڰcnń4$!:Jۡk/7կvzJ Χǫ`PxC4NtRAf-2#[_Hg rqrG{xȑ3cSP h9RaRLYW]w/y+^|MW޵F6 &Hy!*2SBTeweDmZ.m@ *XF3Ut:D׹%g{p䊠ڸZp1$S߹Wo}7!u, R@KMFhC5g'&]P@Lg YgyOc?{3'O.zGtbXd 6d'm-8Gl7z 8d0 .s&SyFo楯Ɨ/溫Fx^SZJIPe4{L(sӪT=j60NT/h{ji\ g`}{8@ңOo{>{ial i3S!}_uwo"= L 73&29%5Dns%!`0kpd/UZ|ӟԽO~+89Hǎ2hQ9UZ ekc 1\Xy‡_%LD o<t~/5wox 襰tClٶW$7pUJ7mT'.;O<'&an]Yrk|G(ɘjQPM\qۮAaU8S3DW~wyKﺦկ!*$s̓=qWelЄd{& y͔4z_4,@epNᢪEcFb]:_c~=}Yf^`eRe)߰c؜:"[˘@ ϳx 'jb҃_o|ݛMw#աOTmlSTRN%B,4:'WE.(?xg_%+cYD#YN`sde¯_G"f 4j ']G'2^a1[&cfnox/ïzmhh~Yt4,v8 .A>l(|8m}aOU OLu3c=ٿ>?Ǿύ8G΀e"ʪ|<2{1U@ 3*S׋o7w_}Zt@6B#='5N CMe-.SӴDO3fS! ۃռXI87-la,x~s{]4a=hA" 2,lSο=e)ȫPZhLh +~ni~ۿ]޹}LĜB,o06$٨M3iXURStS'ß{dd,d@Kt:iY{Q@аKN 2-q0fƗ~ 7  &" (*9Rrh|#Jl녙RvfѐMl `} a6EB95]yq{xGʇ{ cYZ([) S@gA-A=|~XSHR( >0Ԡ >yv#T/4xp}C [ 7SFfg/αÏ˿[O~̢78s ]60`5VXq@P@ip jJus_~yC-IMSыtJ*Ofzw+>dM[ת`גyO0Sq9fůvhUB/͌f-E ~?oόg04O@'Դ  "A6K#(j]̧'O=Œb<g j58\:M y41ؚm"QcJl1-2Raްi|S~sRX,Ueib>lP`mm6@a&T™8|[ݷsIu`>NTB|۬xepJ9u73~Hȩ;f@?9T^3T/e Y`[ݾ;"1 093h|ۿ]߲p%bHNc9!]9+հ\K::Z6.Yuf^3HV? pNk+405-c{<jnA?r飑DJ-zdK_{?)Woyۋt]$2l#uT0$G3+L0Ʀ@!5b7غhmv(-.eh cQ `Y13OKO=͸hY ʪ[>v:qwN`y"1 E]MxϷe؊iH\Щr;A13C\Mor2k0Uʏ*r1~CSHĆ1KTaP0 X8hFo),О=ۥ>?܉XjMjU @ #iFQ;_{-ιL!x PZD `i (,)LM=S4˳VS-ߜc?U_M+Ӿ;Unt}=eY8Qsi()j81{/'LZY0K2s^A[뛌bS!i^<,zӻo{k^2S^yd$ho[Y7=uMԢrTBw`w VǞ|ƚtkٹ620OiFH퇪)f"c_{C|rr%3'&nt8HFj}N6?T IA'BlO.Xg~4ĈNL<({(p_+ctDHdv0Ӈ>2Lܝw\wW]yN$2t0A $h]XLWiEFa~ z [ X(庂ItwGWznHBH}L̬zʽՌ/$l`EPbSn=?=/盱wQ' \*3d3gMǧTNs f~xhh_~ϝ^=Kn}o|xGRQH}fJd]~<0L?We 0JR1)p 5bQI☑s_oէ=כdy h3$ ,  cpŬo6}̣[}s)ţ9=wwkp1C D\kEXCbW7+mY,M0v<<+"4^~H {6  f '; 1DA)3|_ͯ9=㎦ȹԉ˕s撾Ioٷ x˨rJՇ]i_}6_YaҪ/Y>+??!`%MOy>ҡw b0 Rd ͽ&=WFo& O+[g)<*^Շ,26vӗ׼}v4g)82$3w֞[;*fq܋Fx-}o{ٿ7xGanfCzcSWFcS/zez̧OsLc`,ƻ߰{>|Ԣ0fE{Y<7Cv2M0q50 8 > FD Л_"1. z6T.S̎Jz BsrK^4] Dlh&]Q1 FBI0a8ė7a%gYŁzm]smJ%Es}BIDȝ]׈?Չ`H(az AHDW'O>:[\<ұΠ;*uQ20'~Ofz CN5W?sX0eowaz"cO˜GHasG#>:p箟?Mo{p&s8}hox+\4 qY\jpneex@PWpiPB ˑ,^7U?#RCTo*ga\U²s%-WK`GXߙYF[-ƺN5>=ݲ)uEfu"hYtgO?IGE7tG]xK|c>y8r?]WcgѼGE=Ze],%VHS4Zj6uO?wW;C5͟e~tQei6dķe7A7zg~'v֍#o$ PB bw/?OY<>xo{ŽrhK}9:=z-ڽ\fn!>C =PBꟾK1I04c"yyĘ8zlcG7t~G_y@EBm0K#.?39*z/Έ$%HH4}.g냥&Rs7x+?L J?k.=GJ )6LX/c%2Gz|wcOS.dEWG0 $)!2RVГC:H:Q gFHةP'GA6@F:tS/NF 3DAyObj¥v\]~hP _v qFCATRPaZlCCptO,/c .?sEA%}{t׿s=3'z]κf4a^obWmAz5e7L+-F^Tѩ|uvOw,x:C""PؘRbC. BCi))0 YnoxOw};έFXKx^-H򽥭ѫy]zYT5J5&ZXș34>BQrıC47jY5$LbVPI%xϽn왵j{oF' 0ɫd ; #a-7^"O}j!;K1 txiuvF:yv!=UR5_MVH>)-PQ[-ư0g<*ZӐk H.+M!d$f:x,=B%:)EpǏ'i)Q߀}֙ɗ?NYHf!=rYW6FPl97^'s,'ŶfWNH"ʮ-~'YvDf~J9V!¬dVQTf GW__FΏ2=gNɫRBM XZZ|q #X?4r c5S3؈L Պ%{DO2z9ήP^#S@dSj3IHC5 1nv/㯼C{Lǟ;?a G383kOؑ:TA')wyihS rv'fЌ ha|^i-!S>2Y-033H ۚiglu%۽E?+; Fz%JA+2ۤ } h7읮hBX\uz&4ucglt >k_kw"'=|??4Kz1! dJ8D(l,^*p(PkHHbjF%;'sž(?vW?)YQ*(+^|&݁WWF˿QsN2(RKXip6E aԫ̠L#=wá 脾B/.jHL9ۉr_s$|%o|ɍ!ES>ȃE!t"YUg6u4 PoXCj*Y]JUbP=`" _}u::m1Z ;w? )jo6w-o^Y%;pP ;.)?/3?a|Yux dafQ  L E?O9N LV5NY ،iC[BBGj~>aK\Cs7w|qf&Zs0 /oQ_!矉?+>EFPWC[ʞMś&~Q2gfD( Ek+^w~9Tb-EdD,wq(5NAu'[~0"3HXAG~m-oY>mD[mn0ieH@{}q3En'"B-Zx=YG* ˉ@L\w-/yѾ{<?FOv@0{>[TB,Y:9>}b 8Q^{[^qҲ"Il\00ZZJ7u:Jb6sLf4)t| iIFH3i.__0}K_z}?Ȭ'JA<8 H"1!@lq'zWig=dǙx2ulVfAn4O!09f_}IE~I\7vێ:N!8zH)=rOg(3 gdȘ T<3,6%iMmm=ya7)3 lgM<ة&@ojGb4&w$hfFoR4\rY;р54DEiRrͤ)|25-j"{i&&I #ʒWg/e*(Ůe=1r`.*94M>Tt.I]Xe+&~K_;Hx(f]v]_7vR˜OR!.+C !It*.c8@f c\[+וV % d?ŊJ˩[J䴗^N}gO'%5R?}~,d0k+y` Ͽvx܁ﬤ՟&|b~>ҁ%C}KW,CӾ}$+H˓IyHHDQƔY%5Kl1l!| i݌|׊">[,${‹_|c?SSΘDH8D^z %3yϫ~|Uź"(ۙ 6L7?j]CU̅sAF0ccʌь02,hq"6\7XNЌFL"QvE?>L#QW)wyDKμݝe}hgWV:|\Co4TTk'1嶊hXtw/~@ ̘:U w}gn;;*E<2]8Xߐxjnυ ^b|*i"$رܷۧe"mP1`UlEUXV-Әy -A +N' DZF]{em,=RW|pfEw?i]s=缺q}{! Xϻ,귓Ԫg%aZzs=pc+'踸vy\DU2kju{qϸSO8%T_+~ޙ8}1ͤ*"z9@vg'[\YZ Bx]Hz/~2oZ^[QV\ Pf;1:f$T9EnE̾w:yQCŜ[| ]u0E a\V\]x?]BL޼QS(lajg|z0Mo)}_CgNz{nn5/w7]4I\/5.0^ 21ڔFKbFƣXYƱꌦjnL͉0JrҥQ#yu x餌cX\:{?+*T|hze[8f^tOEB'z1S@"Gԅd|ɕY lol@l)i_/";n5A<B1X߰ZfTD]]2П|_zc{Co|ݝoxWM\zXғt LHyˌ65Ve#k'ŀīaLjݡ_N/fKrK}Z##3lQ/w,v;֍`+TLUy;j>OluT.9ԝ`,vp}j{`/ 3LߏԜv;ӣiX""Q())>:re[`B=m\(/.?~oWc/Bj%rtQvV)3maJMEUHH:Sgځ{~>/%cjS1/lQ)^x3οǻi_812*$pif.%]0P 6dݹK8"ȌQ ҄`0I4[*tUvx9{eÑbQGg-ll_B6l\6`JL.~9H&t1``R]_OuG(`{0*-S23>b ֽ~Jwq'2F q!(wZК>&bXeN9P*l|/}`qD iG䔸"Ϙ>2?<>mqv$$5:^c%b$$_QռYh4>g,=Bg ܧscF^ Uө8gΙ(iP`Yf, -*NCܜz^Ogrw7Rxҥ Ć'xvϮ}WqJȢĩ("J.LT:GLTLB/E "f>"fN_bGCS˲Ufz48}bΙNd GƔb%eiG-pJ.r!Oe׫}ZjJS'ce7SjJ*M ngnc?xUOtElLiP=_`OM8E_[N\FQ(^VB6ZsKe`oN/\eհYI7!0' |e(% LOQI2coy'Y"wLT =RTX4?rçӧ?h\H74V))`TJM~JiLM+GQ]Μ>O?z?<(|uljI ?}SOw;{'đ $:bC$c! r(pHDD}x{RUڊ$I6K_R6l&<0VےUB홈1Gcvneë^2FHl^N{k(?OT5 pm\M7몞0WJ5{)TD^,+)cY0.t J҄03RPq{Q:jRDt凒__/;ÌF"t~t:L\-13",c!*^LesYjXֆH4E2+ѩSgM~?$R57M 5J+3Qg֟ڿp;wnmpAZ'D*})yV9dYK*]DGQw:<6_Jdݝ׹7C)@o5Oʼ*kY!l$["d6%&)!s@X敱p `=h}5(sybAq+vxW*M`ύdL J:1ݽ]D Yf|Swzz_l# ihىT\&&PWt%[!W9dYڵlooYzNmۛdL𪻤X31ʒ?ЙG:wJd<\/ŋϙYP(WA{PeXh%8S5S't0ԕ VZJ7%YhF#$.Mك-8]3鎞Z8ed $̪; nmYVvbBQuWQ.PZƐÐ:%*fSKX^9k}2ZceJ•w)'PIEihi} zE_}?ϧoi9㽀 P G%O&/ X{R~o99ˇ]&YO _3dpU=&Y_siՖ0U7W>1y*|KkH %3,ZΈB 3E"F=ӣ$R:O|j/>s|5Li[R]Yoo d+ױQKS(f@S` Q=ՖK0Q*\̓ rKH_w:K?znIlΥ09{uPw% V!5E|RZ\HW/hRZx Chi#<3kΣXak1h haŽO~Q4Ṕa#h٤:S//^$VxA%iw7}߷;Tҿ_T\!oFW3t.h,kY.YX|Td(,t.4|`XNNO-8YTMTz~$O>2z^D9gХzyQ'6mZu3snUl`\aUi#3W^ڙ)s#SjFQOsxi:"F42":jX%(DA#\]5TJ/4sn%s1d* N[X}Vy|dLyݵ?{S##Ԩd^/j.lbg=ozՎve`N ZQQyLٿ,s'^M\иJŠ[YfRxnɟbF *ۂ}%%Y@Oɉcxt8^y'hpMA$G~q33@zSSa/kohM|#M F4J#{tO>w(,zD/j!q^IӋ;LU4`j~C* S=f&hgU]XOSk5){>\ 9ҭLv&9H ? c4ܕnvő c`m'P8V YфTm5XÑyM&Ԑv=;eRQT+GL̜fozj^ F"1GQ8gwKёGO?4:s~Хvb ٗ`TLJ?L(Y%պ5ru)´Ypvgύ"%|mHKJсB)Bv~/ΦjYz`q+Den+ 1?DdtB\cIm׋A(@^:1{“}vf]C$X'l4Wq:Xk[h\ ^Sd/}O؛n(kin**LOj5 ZDJXl!}AUi6)EPW7F1Hje/DCPd<åN2PUFP!Arip*#~f}FAbgV s.:Ϋ035b/CO-S_}<Կv<a1%`TRw H |rQ"e'v͹hn2GAbPjuCc}iV雦zg>dxRXC*rvXE("j< 0,"188 UiMsSr_xg'6NX41ZǢ&iIVxiMYeAebLi)y{n~X7B &`%8Rej1?wTg l s#elҥI7QgD]0`D Q*$ o=!џˈTʻ;q~?IT^{85@TN:Qډ9z[3%2sQ(BrT] &&jT^a3lB8$NƶtJϜHMFgb? [F[;^|4hUMfAVISJ!6 ģ3R?;n5vc UQC C}>Ql:] ]šQMWqN B=BxjLgyz_s6/b Q#T@,߫.Y4MzZϰU{ٔ|=-Yyb)1ժ wb 2q0E}'?fI(UQ9cOF].5:^?UINi˓f~yeeĢ`Z}j%~U ;K7ۏlTaR6wIJKu}Ro{b3TJ(#$1Qi\hژ`@ #Z)OSGDBhajYRNV`ip?{xx Fnl7x02!|7u:tX`d֣A*%'$8G΂0Vh,(C{_⩅=f~FΆOû4М4?|P8gk2l?ġ~e4^5ׁAQhLsմ0`, 1[ٿ=У'5 q!ōnP|Iǎd_u~⢂W(BJ"f[]#N(M${dEIgÃGgQE M OY)TQwx/+ ZN??]P}wBzYfQ!PR,-b-?W>GC4:✰Y `=1[X( aĻD;əO|%pucgIO#QE+f0=\\`rڕkBWR)r:\FD.N*zj牼ՒX#"V]ߺZ!]m^27 >vϗz Ma"0zqu&3E> UeMȆ\7Y6PTVp3DfL3G#Gt&T `6` ibH3}__sJ-"4uPG9jQ|S+QctXJ6b1mφ4y*JMRK1/yu#ѼM84 系hT"B 1t<.5?l:o Y|zTa scꑣ^97,^ T:kv$UTX-3amW yVzAGHcO=2K#2-E8#-lP|lFNf]R@ucy3sǞH8=xXxx8 o^ ,hdŷ.hTpL&i:|*?w/Z7P;t*$@ERUdz,`Nt9-CS#H/+.8t&4)/U[Ȃ*HK լ8s?}0osNRymB^Zd^Ԃy3I-,E֫[ >ˇ_CΏܱ'[c" ]f-ep$ȅfbИΜ饐p٨H+i8fNyxǗ?͟Q7ӎgLFb qN@XPQ>#QϋL͆dey =賟>{O?57͹n⮑4Ϸ^XJ7nwmdI%.>[/P(Λ405 uDU8|ҧY@dt5hPtUYG b*ag{5Q18ŝ;&YXјah3jd\W%!2az׋7ۋ2j?>+|Q| dԓ)Ҽ O<5X<`ܕTPb/4h] ҁrPzԄ"E wMfWÕJc*:ȏd(!NKO1qgtQY* kTհj<Տuy~1Xi~5Jh(&p3 38%Uŏi<6^dD;E[D wBV^< Lъ"tUGB¯TT |U0nYʒV)H"s3yu-[4ML$C1ZӚs>8 , 9/N\sNmJ3mӟjQ2+F2}²l (&رK=z ҵ0yY.܅28cÄ82ML͢)j^:վqI) 2P[ heWdyQNԣ'OOeh_Pǀ) b42\y2yZ(iS6yBFbp&S%}]ҝ ko?t-btAbg2c'vL2G !).hQ ԫ a[ !҃*B$:]FH($"%UC,$Z"Z%X&bLKg,\w,N$WC,BSULDZ{jT`o~˾Gtas Aԥs_={b^ W K7*YnF)C)sO "'B !ܺʫ`QBA(DNੇ$1wgڍݮu:(ELTER5k{FOƲ+2kՐ?̌;o|v-S!!Fc/B@D)9j.!Ntǻ|$Wv)8^PNXW3 N>j^l-jYEe >fU/yZ-ᵌhoSONf6kyNˤbu`Yj)EӥM{)ܡҌ꜒b$҈Ig0>wTzbGң_+'08&R7!i V>Rt޶bˊP̨ P1UZ*G3Z 098G<8E#̉аS"[Se@pƈf@!OF=3=K|޵=ITīҔcT`(M2bB/GمDĦz*ݵJɾ)|\sd߃̛ L[شLx Pf\CNxyPdq{s+˼flvmʓe13MO3ϧH&&$B构[n5x#|W:{E}<8 R$w)@5Sغ˝V h*CpS8%i-AWݸYפ#nq.*%SQJh,کymY]$% &#g GϤ_|8sǡ;oF3lI0r$4T8OGeGs9M" [SVfdZ!R5Wf}Ugd,h( tk\_*{`ܭ7 Q*^"Puic&Ζf<4 ^s47ɣCc(Trfy/ ;HQE <c{?3;O9TQB`1L<22 bi_en홼He^ М'UdJapjIŠEȖo epJLcOEf3G1UVD +FX59_w I̜xiGF=~Է~n:;Lw2r)OQ+zu|T :8g⸋.NeJyʢTTztԿMjpЁ^}_<lű{n!ݽ`FsןvK>kmV0`Wjj'!; 5T*#μ^$5j ?k)N[ܟy=]M<蘊H9 B5l&4ngid2 ѩ)C q F'̡[T:6\ K7j,Lc>o31;LS'r@Xa~6Fb)c Iu2ѥO~jw_zۋxv.0G5bHǓto]ˍlNAU ,8" NjUJ+9|8r]T{2(x@6:7ҙ5:5Jݴq]}⚦ jߠ\(/`>UB6ZSrM ɤ!TVwp(g??> ^#XCEM<*:UKC2ZՁUжfUT*FyD2mO`YKfvE0`dd L"MUNʵ6on# cTu9&MBt9]_xjÿwڡ;uwTBd1^ƈցE(H%3(ĺ~ 8ׁyvWrF3 布q8vscg7BV4!b5C'<*'9<}& b&`ji%W\Xyk_ u6V^Txp ZTctFtT8v{'州'z35$85Ҳd1Aj?h:PkB0 c*vv{ x!s;sHJOq ``j$bG}rxH) 3f(-ң=b$%]\<oo[\ʐՇ)`%"ief{~wds!"X @#KVJaվ&@ TP >? /0@tOO7sbL ȓa4E+JY0/E^H ª[0tde,"OAs IJCdݕy6F:(S/,?Q3\ %6#:G_^C*@09# , 6["%n[UW}n+fڟƠ ΋hIcP F3ͳ3w͈6eeU.Y98%3IUK4J3͍EEFh%U >?.x5%87M5_U%,+ $д3-~ -o¨&crD(+N ؘ< '\7Xv8kX^a!?LcG>i*CVm嬴uϥ-3N/;.:c̺Ȭz²Yf3$n¤b BVWq@TQmՌ z̐ݵ3kV~y @ST E,TD"G(K`>珯rьUn 'C*},[aXH]\L0TDmlI_6 Qѹ lEI3땕%_O1+8Tek cj* BDdy _{(9q[բE"$P)GNv>/}op,ue&GS_@y+~ۋLd# P={Ȃj@lA:Jh9%匓YԳ^.ʔCmz2-`6 J,TV4O5ZZS*I:h1Z|m2-5.TSH 1ᕽ3{-NIyI@bPX LxS,X YL^VD4*:1sw15q@3[n /ͼ[nU?(sˣNE%2%!T ΌT,<$uM+ޢEc9(~S'DXdH34yI3l.@p\ Q,Xv4d^7E$XX-sEՀQ1 dsM+)ش3,sy\a2gհ0OgNAӢEfL88=O)'O'LJ]X\իf|PugPZ5t SB)~^;(q[2PEt,WMz]̯Z݁,fn˪xfگ9(T](T6B{Ռ L`_͹D*PF2R}1ˋgc +2C9u W-Z\X"fF*-MZo$ L+֑NrGG @IH*[5O0@6s|>Ӌe>*[s]W}5q\_&'Oߓ):!Ιz zh}1nv! ?-6 K 7m} {b߰$XuMRg$-KѢEs Pj s T0(dsݒLotqMIuj`um8w\/+ԳHGvUBPaW^uXD[0l2,4fPhQA0hX ܈t 9[MP%9S9_-U ‰c&M:6PꬢpzS1ӌJ J^~X,TcL9?-IӪ|p "I  K>\ "#\W))qEy :77>ahqM~5fm~{Ϧ6@lcS)B/o_'qe8I+Fe(2Sgm+0AQkg.X/!ZsபP_4 `hA6SM#/a ݄a\eiEO^@k)UlwvZ0Ñv+gfGCi%u s/Wkp 1;o T(A.9V8Opc^zL aG ƀxRCր2s݁fj Oe/et.XgXd)5jr Weiu J$ r+hbݠ0V8ʁ͔ L%? ]'JnJ?oF4A&"D{R9dqI:_L}2Lꬄ)}&Fh*&,dy#cC]σӄWIv/vxdf:-Zl&0H]/sž5yjECP`f:Pf|I j@vՄ>>%Gr Pl];si0OQK Mg,Fif몄toɳ~fY&R(Qx{ B;1UkZWϚ~Mݡ~q7a _]o ۭD-6L`Q)u7uS:̛p8*&캆u$UmtNO ĤDh6!La[cNmѹ@xU9 ]Pf )-.-R3;4OIKBPƩ[~S|_gf;:n8hiڤ4]/ k5x^IcVUϊ[jL:G,R(A(e,w3F'N0wfeB φʲ: yHf:Ln"Ev|E'=ASQƱ_oQ&wwirIPPzOk4W׷{~ys ewh5ג,I&z9<VSCBj<ڑ#@{\Og 4 u/  Btv{9񔺶p^lӊ9OX3,jN'jKLdz3/}1C:`S dbF1Y{ :j uatWgn4p+ZTRDtxՎfɎ\6M\ kIDAT87t>#rg`QM Fr3 ?l8^u$":I R%KŝxB/&0(ͭDh73kA,'O%(oN-@1T kp ⍠&ˋx8CRz,+m+x e)jπ¢ȲsJֈ-3f vq0>5q.+QH%* HZLsY241Ah=$bB.qFSۿaow|Z 'G&+Syʉ&MtQZ i0"3@ /||trQRj,P21Z>p.vA"Qq X+dL٫6eV몣hWMIF2_gbMwW}heQjN1"$ ()a4a_ OJ>T'`D"ʄF jDt Q.d9V `.*|Ij /΂+9a}8_V6eh֘lC sʂ+ȉGMU^.$Ǖ*<+Ou(*.&MZ`ERbky-Q)~$YɕRtJv"22glPY^1 "yo9tU-lp@ZiUtig&-ĆG=sMLM 'z>P!h<{΄)6j֊B"{d!z>DKhcQBF*īb`#y~UױZpQENJ,#[<<^U)ſe5;JxckFJ4)NK0Mʛ1$+=h2FGu>0O{GC`^[A JyےQEbHlxʟy;xYU)eI \G8kB];^u啻wv]qND2S$WFW\UBcf,PO$Oћq%cz*_=CTUUFI?q'NXcseHٺD*ɬ"*bC:;wE]$ڄP^y] 3PQnqϻ˷j8_0WVeULW0z՚%3X ;^}jF&է0&Cqn2A42 Q$rNTxq݋nv3…ͬYjYy뇕 $l 9(PnM0,I#$3\r<֘iu' 6|'VlY/0v~7}(d"y.>tC_3wh0*gf])XDD'G|ڎ?@ F04|MwMoxnsȏgL-w*;Q.Fˍ7F_Ub>`*!`aTZՌ}"\wޫw雿/~Dǒ%K$Kt o敷EQNIm*XDNh'rLZqʨ۴i6Xmh]_M 5YFsU{hsqdIlt Cr:zzix7pXLD }7z<8~ӓ s8S?6P^ĨB"tTLjc S>uSky;"wݩmy jL9~u0Ѻ)u*QyӜ 9tXrֱ_s[UbJh& {͛o\)\&S10:(O|OtIrPj|Nf89y/?H'k! Q)n}v mKܷ|~ ֆ7j, %C4lfOk{=Jj"Hs,3ͪp7 ؁dT%N,8_k "DfVrSWg['<ۤ‰؈i3cCPjҭz{}7|⃱aA: x?+102L06(4Mh!@hz Z]נ2NG`w_=g9KlB>"E+WzOy?f)v|zh :nd \Y8n\r䂟' w̉eDyʾʷD Ciq~,`>x;c'J ` 3,] qJeL ^r]3Wա`xB7ABA[]IapM5*m0o;ԱNyyfEuFƱT9O/FzWᩍҎ[WkOÂ]F'RQюe;V7vsk?}O+%Ě=3YD\vئ=yƇǫqA 5 TX랟$x®%#(ԮhjmǪuc]_r_ bE3̓%A2B'>wpzœD*@+ª!):3\{[{KlL4Z08cf]ʦŽ~\;DT Q ,&w 'HC@3ů nMA˓uyaS&f 'rBv֥m?mOԩև$.!Wwv7HSf Um;_sWJK "&Y4|5WNl?tlr6Ejf&zj'JG?W e5+ŧr+Mc ߨ.Ш!#aM|KZgJq/QSC)b`<{1uVuysYѱt~q_`Pg7}n,~kN { C]D #UTo͑lms:m 5ʱ0@<n]/ cc[ u)\m m1UOdX&jcnUq=]p@ H]'N~Wek4jOJ CcWGRHk>JVC}>?\Y],d4yVר/%B7O=)\)kH؝Y16LIrY6^V;5]:Y|ۙn{Tf* 6ԣYoa7=)hˌδ+!xzcqd|OςTqM>!2Ho~[;rbvAgȝbuNkw@.|xUw|`kԥ QZ\`5^~ lBT3xjkUյ".0Fb9\Om펪X5sEy[]K2CP>iAO̧kcʠ04Mݮ!3ċ3<I`LM%و`cz\UD JmZ+N`d^C䩣?ߊuQAbڵ2G˱zu{Ҫ0Hbc d$|lW'wrx>tNI/QI6٬S)' ӽ;~2)Ul/Zxw P`u4_>?;x.ɠxi#-MJπ@33VϲEG}G3M-D1э*[82/#t. ˣy#sV:yA$b4zMNϙ9U+}ӻn~_:GQܖn*eSSjgbm# ɝ$63nOMQ0j: 04XbMnE4_BT ģk*^4`CL7 F8Tc4Ʃy}f!s &-D 㦯Ӏ1e*|4Ei"۵r{tv}1{It:m'a%QG,ڳvJìt&~它R  JW) ,z:g}K⟾[eճ޺B@JF'[lȬ۸_u eb1\xGؓQd(x;_\RA@h:=¾%rJCl;aGb kـ: =>M^rjGL,+UuW_~?];575+)bSoBW5bgz:C3D{TB%%^ĐF !tοcx^Qufd[tw+huY4h3ڸjfQ5ld&—[8_=h N?uo!Bp gq~bC{ܣ0F`|ŽӃdW[Fpm–tH"{5J+wɅ䳤~ Vl”>'6KH|^ vYi=6<*"qtzJ6h YC./O_Y|^ռ:2  = r~Mū=-fNt2u=z?'@;кٳQ;n;n "_Ɯ`{pC{nTn ASy<_>11v&w|1afbp9MUv>?)(%qm"f!r9n~3+_[GOSG 8QM,ҥJ3D,uxn_7`]8Y@sM !b HQvtQ{~&H1Ve#SA3F"g#4 V E,j4 HGb [R瞖TsS B9 ,cC "I0㋧=暽 bkU&JWmm*s֞)Ty "G ĩ(^) tBґ<i 0&QO[ m3v{,r&;Q"#`5bmK]p/#[rD{%uQL-6ԯe'y6rM/ߩvڛ5o~(89i%K6b>t᩵@R1Vj98 /POb 0Q"1(sʼbW2ubsx\y(*Re9#~7{ /Rȝ:3 v^_u`wɍd$ZCf+R"AFښ9 :NW4yĞۀv;׺H>pT+CnUv, CV#2X .'vMY"K(Z/NJh4zӬ,?|ꩇz:4L))\?7֯+h(ESpؑUV5I BJ^LOzx8t.C)0d%h$^5J%{nt8ըԬ,ԍUW$ӛ>pUeDiQ4u= O}#w B5pVs2ql z69\AɤlP #u 8\S![ԵR7GYլąXGw}6Nb@3D{'#Qß<|e׽(5oql YpP/T * jbĠt Gedt*Q8O(CA|RC&${4Jx?hWPe)L1LTDe踉bV7tmF.q{}޿կ7uu:fFWg &t%z/_?쮰,=IN!M76v voRSI]s*ӻaJYBGsEc淭X[Z.ȴLEX/բ)YT$T!&lȔcdIeW.yݿvW釩E]hf;[D_=oFuOyTCI`fH<sNsη/Ȝ +($u `0+(PJ[꺭[;<Ї[V?̩B3jl" ,"g~c[ͯrTHtk-xTr5lSKL̜Ua?-2vy ߆Kvrf`zglżD M>.,UC&vLr-Fq3VC(xCFҬ*=yxy3B'EHq c@mӹ(4T%bo~uosYQJwMB]pCQ{u8+noC@B=Rںllq4t:WgX.f(|J5O& KM̩5Wm5?\/}؟a?7yiZOykoj(ę/i,sv/UQpWqR=fHYl63u~9ko*mFw D"Ql\ ]G#C1ksq~^s߶o7:90uy*>-NS]ՄTcc)omS9u1@ydsh8nqJY͟ B+p0q߶wq 'Dˣ9'DJ#k-dfu$Zgt\MH2LJ+='k0J b#Xb;n +  !;Ga?;;/{ p+B. JH|Z[/v28$Pj Fغ^<+Ax|8IΔZ|*٨[?#;$;j?9)x 2Q" ߻7gG/ l@IY P-t^ϱ=voZ'=Vi+ީm 8T/ <{>)N K ߝE;u4 M(]}ѻ߽曆zpu(!%[ǒ *Ibcdo&Pڤ΀giOlJQE %|vW}`)+]t?piD*fIawҢ"t)aJ4Oh:w @Ue}?[ p^i%Uj: 05ѢfThz=ԏ-ό}<32lzT Q Tøjkk6zO?_WOG#U˃di@#r5]+,|/uVѮ0/A$b)/:}`g >6i~  b7GqtW60CBI=ZڹZɁv;pkoW'7͡F1ZVEe.X|">yxnw4ۖGBiNՁJNž5]SG.8$Iv#"HLoIF0W'^My߀qr9j:2Hut{B4£qU?p;zooУZ1uOS}kQ}*s IvQb-r{A",`1jdwS'ҦP8!W/=??w]ϹNDCPõ bi6ģ8B$[[?=S<?M`a-ʚloBr>׃O}-/% J\ *7/c̍]X&Aoٿ mPhwhmyz }w*YM8+$m|ڦ"[>Kbۚ$ڭ1%j*uX Fʙ'﫞_V049@B TsYTE$ypu{n_z…N梖DhqڂLaIL'6i&r#(0y[d5!)҂hWy7`9T䨼7ߔۿGU1wkBkq$sAwsRpz3g]Ѯ?SY?yߛaI6P%!jVjdmǚݤ)کظdrNU!%eȸm6,EBtd~%_'}t^22qdp@\5' ۋ yӵLOKU |ldƠjubvp^?S#Р@,ш(Y|g Zz!TwK'wkM:tkpeNG|0(gp<ӏi_0k2HI֮rJ٧[8X^?]T+Ŭk?]<;ڱ P8WCRXjR|tY~÷"^͆NDW< 1@M]h\nNvpK`=fVwk}6@=RD۶!5ѵL 1X{+o\=:nWԨ5Rvß{ElAb.~P9Ø| 2 EcBi "Pz\vNU7 ő|kG;u}\Rz~|2JTX7\w,Νr,wuXNd@cV˨- J"XG%EW2!תO1: Ɋ8%/Y0zYz 8w9,N͈*ջͦ_f盠v&ɆO0Yr~˟/ -QLsfb&S "qf}| r 8L@MU(65[;흢M'f 3eLxcZdƫ6~qOKzi4QA-RQ֗C8NF]8 !f+j@ Q QAKI-˴곷%\]4^v=M3Zf͚q[L@ DU Z us2QA_e ɿ?{fp.2OjU,qȁчU˯.^uAt-+rq  SbcMYۆyL@  mR;>ub|\~gO3%מ_8zh}n47Sitv`hj^<i49oiVl(+X֧ķxF(J2Ul( 1A*b>3x"ޛ_s]oުSGG xĉN1fsLqS-=9ۤ l*T4FV&Bvȑɀ `aj.6}_;0g3x4mߐDBp&f[}3R A pVN{i?+8̲30nnJid3+ `5²HJP3P(R*1&E1ByZe?g=2_M?gEUfVM6KW. a[`#. s^l`#*i"vW?~yۙ:yki3*F?RWA,#@x [d]/ nY-IմIh%.Xmzău/{j%*ֆFTQL#,WYWE 9Dc!4Avj2̤rnkCNKfQ r1ebyj^t;F~0;w.*N14޼ߝGSP*lȭ,._rՃ&K8*QF&E ALgE.~ Y@74 K1c;|h9Zzo1׺#n.oN9ʇ*G.Gõl^fe5_ DFc>$ijjݡ:/x .C̜Uʍօ>݂>CXrU,-}œy$?F:E^RN2)X?6|T Z-W:#64AMz٥W햅n\x|'<2ԨCLVo,55ȩ Df*҉8 h TĜ*L[bp3~p  hP꼳\T{fy޻LZ4FSF55-`+dY09'Q5:A_&-.ZȠvy{w9{˃9ǃB U]VMat! +?KxqW EԪh[-vIǤwYޝ;R &DM4pP*lY/ ?[,Os0^y*j>ڨ:} a۹DUԝ1[ RIP֍ QNȨ7CW|Oe5YDf2XXf,P,+rwo}ҭGЩ $+!Q 6be8)91EB8|f^m2S˄M,Bn4fڍht)mbg{~eE|%&-aޥ`_]p7?|bI 's T&( $ Y` !2A56Et PuSqu"Cvds\ҷOq%hKHkT"uMx1zwƽ٩,helw|ۤ؏c Ύf_^,Ӓ"VRH _~xa99 l.{4}B?!+)}=}`𑾋XW+tf0%)2xU ޶mpzdE7_)}pķm:ΰA赼7 .g=e︹[*z0D!f2vN !VzBќ%ZWOys5OϏHgihjJ^.lɎ 办$Afy (i9k3V/|'*T/hN]3?^ d-13WgH *keÜ%i/1ßRQA| 4X<EM;+7i;w˞COfe!zt{c,:v̲k 1YI ڨuX! "8ԸZIT}޹;>u̅eGl>K5wE1t)z~`W8#LGͣPz 2|z\"#ME]l:HN_AU `E\~X2g/3FMD 2Q44Pu{3p*O95]cM.g(hFDe0)q+7o{P8屝#6* L))9[vI`Hmvg$ D3. `i{닟^:1~-vlV`ni.Lajs[NdXyST<7w{g.E,7ʠȡ9֏/L,5`Y,%ȳ-|/MGlh`~{CD$TQ p05SJtЍXva'ũ'ⱯUgJ2* 1u>A/v\7w`RIId'(D;j#DoS>W@C.5s9c* +ecBZ$u(iV\aID.P&4+*Gu]OS'D "H3BvW|ug XK FY- f6m:sOǮg-[oy((3JlTpΚүU1ӓV[q]ll`+2Jo=\pC?֗g}3gBQHLhit~h7>| mg`jdl(i+>Q=ǻ1znYjl.42MД Ju F]瓫l\"bRO*4yPQs"ǢOX|S *u$L)@!j&_P+},0[}bųXPaQR7RR"bi^0]\oˮU&j ^lz)6``~du|Ss:ڐg߆^'G&*5U' %R M!3/~?7.J]]^fn5f.{s@$m fy;¡'.:(t0ifۈ8&jEd(AXFq0%!/hH }0.:c 2*ij*U &=< 7u*RPe 5jfܻܦnIAm*ژO멩D" f#3A.íjykBεL{:<9;[/{>I /T0*3ZovRLty$ohVji $DTb# /C>,.nAa1榹H$Acr(3Bgmo:~zpaYSJNjP3s9?/['@L߸ZҌh`KN5-t~=}?9]:^͒dS^4ϧ 7̍|, j({o}Q\Ub/:-5  x懣'mF J#J6@ZZ_r{ . ;E6v1[]WD ih?۬FR_0}.`@J8? Zds(*bƉem!hև SVwa6Wʫow7k,9JPʯk.Gp ʜLfu f`i2+z6m8;&fTd/=uz9:u\b3Ā0c$yhМF͸o;[sAjL D ΂n5>oڷwL)5[ &H3_ xfS6+s\0rs߾y_j`VYF:N`cdWgjȨ.hV߱hv//-|ܬں+xʓjH-+V&5\UŪ",Ȫl]vmoxŧA$IsQNT373w999Dj|}m:lKYO)BfI/zŨ$I09E ;uc03,XaNZׇYy)7Ծܵ7 |]?ZG!zUōWg;,Uh'.F31sfB큮m?B11z/=6?[=vUȍnVh4ԫi<܂_}膋z2+y'hfFn΂bfd=LL6u|PKud"T 0Fբm"2Br֑?~~*2D3C#)rS}e@@F ,e،ߵ=X;x׬ng6sœwF躰$`I$0A^}Vuz҃ouM7_rB7 S, JgF8Lz½#^}>?;2 G SA2!jYL: 0n4ۤp t5,׭WyF\stvF> ۛt[%bmsgJkcF5Jw#qsA <˅/}u g"-'ALl #s%ǘ{Dѕg_7]|$0m@gG3u|γj3ha0qkPfªێ|Kob}eh~Y%+$}Z fࢳGU>V级zç釲m=R y'΂-$H A/d-ox]xJo>Zm#.i.r0 ogkQ5sgA#ԜlbbyҖ=uK^9(u^HYE(뉟׃w%g̢BMͥ-fkܶXuT.3[UTy]ǶU!s@rB\dh0tDPƖ ST}P~>XRϺAeP:f=&f$2a Eq~^?s9+6d03P`>FMϛ7VX_첄vL^c%bj@oUq~H5#=ϯ~Ϝ,4JT` φ^&eB6$b^#b(̈td G,FUQz"͌b"QOjV<{nmo)/t7WQrnU$8*FWt(t=tYlv&ns1*oƍcÿDwVVo(C;M {P"l@*z.[n AۃXh'|'1d'_ӏ;[Y'`He:K! DaPbPaϢty!1`xb茶onbMJBl  }(`Xnĺ+ᫌȋOTtWfhe^FJA9p)koϨC,0{_ ~MS{s4DQ|\YWm U4=( 1F5ZH$]pnYv+IS$A@/ATJ$~2M>tNuaΎYcg `<6?mϫciL HĨLRd5,Gl{( Zbp#kP6ќ{-(I*>-ӡKS*48*$)HĿ_֘u<`&.Ƨ\5\\4'D ][Gl{F`aþmY>('֏~+ŧGO.siSyQAiZ$3/Kl/-{n{-,v0>C%󩳘H%C$[IrpK41a+-.*Q9k*HFpnEgaknFEU3_|ѧ^qTou ].L &=Ӵ˚&Iv ĐZ(Fy}-^~|e*XYa{3hyJ`~l|^yӻb4FRH"4SZjg GE,np2e+ I2AcD#,57Qxc,tE:KoPZ^d*#MLM} aUt֥ Z"I*_n|\2+BԌBz(<}W|Ά]|ҮH(I ,*̙fq[(V_{oxܣ.%Bgt$<fr;L}gCW_*JKF)ggK,+62Trӹ?14sJ5'uC!F1Ҭ?4q؋˥X0бҙ{[Qb.DgBc+aM}~?iTXӐB$WtlaW-r{Cdu";O@qhϥR8 !;tKۿ=͙dgsSo;d.6( iY8N,TJ-LakV֟M Iq=nQ-P\ey4&&p 5B)>JK'G'~8j.哎z2(zgc︠c'0t+"bBB)o4+ߺ/\cIn 23DKwX |ï\yEef%)CǰǞ|d[s̒rqC? M?h.A(]A.G6l[ s!TJR df~xTJ $ťW][ƛ{f$3+)]YVdaEumO4ډrS8hp&Mab4BˍYLG ;ǟ;N>[(- hHU0H ts88ʹWaP:3 F5jD2hEeӿz, >]{5ֻ D4` sAެS#-}[Qg(NˌIɑfPzceAWP.=g_\ve1NuʘY f: e+cIFarL5g?݌t Ħ"Ajwkt "̀l6ln3J՜G6?_鏏.(9%FZM*heD觽+.Ԅ>::7߽ 6ő*JqBeo#Y )5Ӹh3wrRIh9NV~oxh>FR$F@Y䙋?S_:𲌲 l5nu.ff[BW8/yU׼=;zǂ7!5P^sن'7c5ՁkCԂH1 o4jj@@.S~3.u [V2)#-E8/^vc,!/[|d3IPڛ_ַ̽y]4Fo,a9m5PvVM4眣ɹS}kp ܝ+:5 mȪMWŞǖ{uca :JAs(!wy-`FQffs_pѫ.Hj1J=-;3?Xk"ۄt%Ryx2Fh&"r)7#=6p8b.ΏSRYO`4[Y_yێ|c{8-$*r(R&6`ZZg}[w3=.YRܘ=o3rc6Ny6ڪV6=h( 5ڰЍA,1Oϗ~ }JO*$2K",`氌3|#]ݶn7WGBQomd`(, {ne£BD)3KNQ%Wey$%5M=k)?Vr~0nn/[;8v}Gv؄IXnA-mo?zh;F= 2CPUkPJ \ND%L ˴$dG,\$p;ÉL+3 wmcsAL\rs߬>·ndJ_E/ѹYFŦb HH'F3J g 3m}j8)Sos4P8j^Y{ַK.)::tֈ'U==:ȍϘY'mm}M&RkRŗ#*4:ssm}ocƠ4cIQlgT0mQD ۼ)sssٛ//9' 656AAM@kȟ}zyMZ*(izΉElС+=1:"ALL,8uF q1踭_yFm:z@R`HШ`NM1xϹ?㛵fXk[!B*V$;yf?+-lz>]GO}i{O[ғ͉}C$t蝙!Df,;}OۮN7'ٞ2.T.WD*!ՙGf3)MJH-o&p34NbBep­tzKuEDaf&d85Bg;ϻ_󘒙5;tpZ 30e!4%"޴5ߓapHODiQ?j5L|1&waRV+l*C y=":^ԭ<.y^Je'*W\/\z%8MHj/cVyDܞ񓶋~[z56 DHwaɒԭ۞aeTdKU߼3~smF~)d@,HLenjwxI65<<{|c03fbtͤz"Km9 @et)I!˸tp5{mڕ^o輋&&dz53 y;ڹNxEh3Au $ER1rJq׼ ()0,홓wY-[_r-X!uH@F } UAc( 5HiT~5h r @L2AQA"3\#0ZT 6F1l;7̹ ¥?s_{븵V<>U*`i:Z|Z73qd0E3m]NM{|-0$ڙ,6ب6Gؠ.lcDzʗoD۫0Ȗp jbJUAt>\_}w_ɲ AitYwoT:! D T`f M6!0bP%4[winG| ޠy^+FY]$KJb=Zv{`b<+*~v?ѩ$tFsb(>]s]Y؀&ZӊS|2=9W]qM#TK)6@uq:;]p4lݏJs9݅@SihA tMS{ObE]Zt"<@55n93}!#HZDʍM~Ĵ6p ¶ET C~ Ew|9:3$4W5yff:֮w{s~amfy\5^g߶3?#3r:&5=bk55EbcPOw޹|3MFg@HMJ`"*4$%  \Œb)MS!i %q~R9oK)|n  5sFH &%N͑B!b[K{Gy7/֤r"0<)iRT֧z:ʙ=/Ƌ9l†1hoH+1y`X(ۥ @-G}̞ cTubE3Qz3?BaCLJR(D0,Sq{npmQ組`tb `hŽOo_;_˛=`1:\ewÙrށֽ {þ1kp ~f?#IYH;&!i)RTYMv, u}$2 6 U#$֖-/Wm6P f]d-:,:+b}_X5_CKKYRV 13S" t&Mn&~~w߈QrESeBgHGCEhIDpnc}+ޜ?g)ݴZB*iQ:c6nrCIje0< Lc2TAhz /T,WE4TЗۺyZ}*='m!jKSH47lRII3U y'Dh&CqfVt:[_?.;w-dV$OVhF* J`,"[sR?65ƢcKÔ:L bfEPH "5 l 9Rmoxo_.R!uL40MJBE;2ԡwk^K?}ߨG Tv,JDIUOqW?Vx$.JzPQF1L^Kb;қ,揾*?r-n{*J*{ &!b&`7v;OlwG`,I(Ts]j* EF x3$: 1h*AE`Γq/^~vnfP%5A] 64 `T! q<]"ׇTc0#2'._O\}zmqċ󀂕 w,@"R0vHݼpignu}k3Q`DLݒ?i\;CYGeKgo̩(%d١ ~VS3^NhѼyn߹ {{ݛi zxs?rPϘ|9}ޱRjc@h+v&~w~':} *8>LEMo d.|Y弊ӺHh$S'I&(+C^ԓ}2}w9zQ 6%Y,ZSRM `-&U9nRsspg?t?tɫ#q%  Wn{9Όv~l&'LICp:7DF*&QF;?Z?/~ noCUg#2ɌP?[ r׮Ե^PJՑ4MUhiHNd ӆh@tѹw`_//uNW^#$^o&/v}.eEMu{|מ9ڳoI̻(yxGD5m7ͺljfƟ3}riɬIlФiޭ]D0 kj^g7VNuZ9\^"7̩d`'!R[LZ(DTĄ J1X'}ֹe} Dr 3g߻g펿y_7V7S(\gx~+0mg: Y&Cj%n[}W:]әbjotuG5mGeaɄ׿<6m~К\_f c ZѫwzOhCUb(z껉ću, չ0 -Mo M{ތP% nl1f:_ޣO1`ToQXDt~<"_eߥ!3p7qϴ(ѫdj4Uu&s:^qi7);!cjN& (rӹGaIDATa?Ze4hM~11" Ѣ"ʼ]9mWK:Ќ`3gXęKUULт#EL4Eb~>ޢSVw](OG$.Fh:rhvXAxcȲY[.-o;})oC&iy'^msZmπbfgN@:~YbL}`T}lEQ$7?ջÁ$@5hR܂zsKlҥ'˵N棯b6pysj.rrL$8g s~^A:fsc|/{z)b\ uua~A ԯ$pxu,HdG@ *ٝc 6ۄ ϳ_O蔘s]T-+sTŹ=w5|<ÀZT3Dx%S'| ˹ V'Ȏ *OR`DlJu%|̺9ՂY$!Fs 2UܹԙnUDe)#1p u [7nxu7סNvq$P#ؠf1:V3@w }jк4v&MXNj2npf9pp~?\zi$u~߹c{k{P})H)SOVk1Mh{Lf4=jYGER"]   P*^_̏/̗UYc×w=w㌑%s5N-SMN-m{}Km$鐋7*[6H]Y2I0X떖dq2#ڂ`?~ŗce beMM]$I6eվރ?oV-HPsJFhRJ|/_Ǯ$q~U2{R+U)Wͫe6[_*W.}Po" (5 S 4>gȽ޾閕maKA)HPZBb%ĕ`N[2'9 uي@PXp,mqۗO~ro@ sQG7K.(Iۆz/?:|iSZ5qyyl0Œ挝:ԪN}Mh*LQP .,'xho V@Wλ#W]{82gz%(B4-yJX/pTV%,.%vڥUZ hppq>^/^!- =oJI#v~V>:E;SY,0Jx RxTI٤z mCh2@A&$;ㅋ_r?_nz"EkV̍Vx3Ue1 4DTҝÝ|t==p=LUd5&~m&FD`ɩ'@4F`-,U(pAjD4Lj!fN.Agn峟s;"P4WV No mk 4Fe,\ʇ_kC+D"D b_.L19]֞ E|(s$Gu 4YFi2vI0Dve[@9W7?Xe|Q뙵m/ !̫TH2 E_xS?t=\/=h3 a`4<ɥk(e$(;]{=/h!"ˁ!k6- Һ=r?}BʘM>FeKW-e0 h4+|۲B}nG]wH3Ki" Wұ%>$߼lF }!'ښ& PVV(6ȠB- ]8'68"9PALE)% Ad:psc|g- &;j4ѡ̩GYbٖoE*XcIndLfe2dcӫ{׿\ɥ2A,rlVJ* \փl:2t}aKQkʃ?wDr/3Vt6Rghn P ,vR %j[Ό梁}dExeOc7oy~}wX}x"呌m1 G!.osm7~cGvw61f( # %EQ!^9敠T..gՒrR&fu ?yn3tA(5i@: \юF YC~][K!K&ֿ2:66/U^gfpV@5O?u<ˆ"ZbEz Vl+''OWe,hJ9` 5bY"B􎶏=:~,2CfbeԘ;sGŌQ]!hrkrDA1#Pтҙ ^8XCȽ󷿃GC b!D%jYo,UTEG-;mn)|^GyVhD$R+텥#7u voΒ¹*|fr 8luzoGsHnh0_ 6 15a{*t_=2MBEU P\-E~pv5Cv[hwcm0lYS44ZEJ׽O۝) $ ݶ?2fd $S/~<=Ͽ_4S VqZ1f/74G[!;\BKF@磻L푌#Zklՠv^L:{BNX~q op˟}1>_q_Qhw)l)P)TBT?o{?z_h h93K[HT2u*6a珴1O4rDSs8?vDSN镀Dap9ZgЧEpeĽ4՝p!2u o}p[ϷzKh̢Op-{z:|sVr:?M@pIO$0 A *v;-g.xؤmnf9XKE`TQݗ}8t67E"5%$`JZkSu >[v.iw( vQ()rYF~1IB]?A֙M dna+LF&L\;/O\=t=QŒO*{Ԏ M8*7% kW)eGSV[އqsPiHla.:EY>E'Ӕ̕:pitSE3op@*X K>fg4'b!5\BvPpob>h_>%()g$jA D_mL[i8іV}ҷZ6|\m(DʧRJ@=Z, M)L>T\J"+"4 ~\`lw~FvmAL4 HE${x^=sxmn E"^0Q"- @)H2:li:$%&9Rw6]F_ܸO\q٤xS7PgvD߳rt`E &ń XbE=ӵlc{s f 5cZlO=O͊-[17%A*FxHs *;=HC;ND1NS@D,KT@h+rldK.v4f!RIȒJbH쒍uGWp@S KW@QSUnOefP$nx_J~Ihw;/{>*U \bB($2 S\jD@bS=f5*Np8`faz?\`̽=7Ϋ^PQAAD R͙yͲ(v~G8y:yюs`&pd20´L;IHW\ġ͖]G"ْF< 5m.A[4XzogN}gSV<|BMP1i6__ⷾڸ}4!U:. Q4J-T(=δwꝺpB3 B .aiaa4gm 1p&n',v`gf&QePTL() r4 @')1SR 1_sO8hUWB5c(`K[Z5j.6ct\ރ~]/dzӛ-=>5mjڥ P/0LpMZv5ʐ*F@U  -QCmek} ~?VoB;'. ^{@Xː%.ԽwG3T! E$qgP0TJpP4q-G% ¢vkiIzt*I Ntd0UcR'l 6V 1SWz#4UΙíSfe.Gfpe70ML ة@"]miYlD/:-*o5>:_-yjiw?!,?#e~˥qmj/4pBhȒTg&fPUL-ߢ?so{s(Ts(0al0E'U@4iE 3{=fM*tܰbkGƘV %c~Vi|.|>]|Vs@;;k/E*.+\D(ܧ?}/eakrJFCVHMX G3?-,6qLBVmD+GV#8M %aő'. d!dX#3tJ kb@`*HZę#zΒ2-(Z M(crȦ{KCČgShiSqCLvʖe߰4D ų[_~b+_W]Xf"-1E+sh]kkḳu b,*&DFRummle3@bZo˥JL+F2q䮲D#y }6"x9F *EE ( \>W>X>oLZIH$Z}vQjV[G ĥ2jXeS lzc@}խs/;[mXS#,i ;B{v6@Wyv7m|AE|3 .$1q(iݞ:@2Xw}k|N;:,;`ح I_r()8ef`~JA,/gGL5F6-5մ q-E (\S:ru9kW\ASӴJ&72AK&G=Ǻ z[Z4%"7Ҝ9 ):rAb]W>k(I5)&'O^,W*EUؑz <m]]liNaPQh@ bbPUfY+x\#^ ϐF)w^ף56TΟV>WtLRM V.qALaeS/ђBmħϿpC-5$q$;aBzqQ;r~36Zy]q@sP˜8\G"ׁ/ 6qlbC4gv;?_ǗWbMs bAp@P3 l}DxJ{!JMTLi&$p p툷؃w Sc<̍]̰yfs+>cpd<f0u{ (^c4qdt=_8R:IN}DPb @C4dܜ+Q̜E4 ݠTnT9&~NW5O".@ egG]yAva.!8jP@h^z}?{ċYlaKHRʲVJ7x³镩F5 &À5(EV:uC n?y+#4870`,FIBըHUƮ`@e¥|W/}Olt/1FfiS6. ɄDme~a[+SXhTix]'#fBEm?#o6͵w?.kHf[|⑾k`R&o[=8Q# T MTwsH1kW{ p0âC![W??\.;E 4$% O$\UΉu<ސyʪ(22"jɪԠV4q19)xweх(j%uf t8&-GӢAiѕ$WEP?ȁiqA[AFa¯' V~qB}Z T[t;N7ݾ-W}qdhS5؝j.T 9R@ bNՖcofhf,~2DW6H3lZnPzZh5(yWq}}Q,&bwկ~TXQC2F\2JlKsx{%\9kzҠk5n&^DgYh0G}Ke2(=58ȭX,680Wt}#_ϐ6`;:P,:8 dfh=t{+߻o͎V%jY jBW|e5F^wklw‚t;^1 š.NҼOk4-cLlڇ?^_޶2x4k R:G>};K i-@Dq>p%y:+{$;J@\" 3SH*F|kowUC;"*̂ceLm谳;N 4B]I*h%aFHb-hCv8!S)ʏ6 ꬄ{jkhZ/Z@S(MI.n]]^^n_~ΓZWʀ &y˝O?m/ 2Z&>OL܋f+5?UT5D"vt5+lf;xg~K?}mǢ[:52ڡvi%(s%ϜݽҁbS8`>D+q ac fupn2*dq~Id(-al}RVftq_o>1S2Ku-! P*ȝsќ*M#%ܥZ5dW5g)feb1 ݺ?{;a6F`I4ffjeO?*ěS`*hy!d']RIL3 ف;p;$QEQmdއߡ& toy2E$\iEDm>q>|n C`B*Y% %i:E} 0s' ԧShiӚQ5o [0oOT謿_!ݾ;~7/W [ B©0z5S-7)=?CNђu_ЗRRH^!IBa9|6|'ba 9,aє~.[dj.щIBP6(W.GY 0fw_G^%Q_ ؏R#_z.4q6 'V3O)`kE0 ?N@(Ado|w+jmp4T QH:u]lzxvLE54*I4 XxS9A츃]`LPzɂdHPiiݴ6j+tXV+F{ WF;?[dPcbD:d،9M:HLR[fmxuo;} s:BdO6 c<F0UiT!$50T͠Wl#{i/޹.#p#2SlUA mKGN-e PRQ=2"HZiϳ݅8$7kf- QnUО'vʾDgMnOV$z uc١EQ%0yZ%oz Hxeg@ϨfYyvV1W&,w,AcЫ9GasWpS_wK/ ǃ?(%c1#:II<'~??S}GWaŅhΑ1<#RSP5ĢHkeu2>Uxo.?#X[#~l?ȁl|Nċ6-c/ޙ9{\lv^qOrhLCK-0HPAt5^D `b70w{{;i7S/}#\dZau\G4Png0ԫI1p5)k+A.x֜5/)辴fԘ;Q1?_~kg UZ$M; dڇw%HѶ+y]9qxۧVxSkCLtj b9A2W<ʑo_2ډ&ɐvf3RDCG'0J޹<ߊgY;ebX֘rhs@p$n6?̛匝G׭ރ[ap}d ,l@8v<"D ν;fiJX4vn\^Ol|nDL׬$'QfmwٔcT!8!AF@ԜE(=.dy5[;Q M_<[o=`p:Qg/|x1(xsJzk;~?yܫά*2né.;`@GYlxȬDE6H:*s6U{>o[ Vt)+TF%[.ƶ?r?ɻgzEy+lSS&eg76HT`ڭەn 3h_c3<`s${ܛ}-^mJ?"lC4-}Fg>30Njf2,;q\)iuSR)pR\,Hg>/1AۂW(]D'ʢ?l 3h @]A* JU5~.0d(SQ& W.9;}zHYYw_ܓ72/)d*\8{9˓H19GtDQN![;7}|XpQ\Youˆdm;6亍+~18aU7`T"@dJ? nv4zC?xbR\n+PΔF3iWr>#yrσg' gbGip\,yE-:"^Gzzq%K(tȉM;'؀$O_ȇn#Ӓzq'ZntN߽)'Jf ;J1 &.b6GpWTT"WKauK[k$cKen\d;OGWc(^A7U#F (]vuR8UI^0xZ*iѻv '̏tzeWDim0Dj `|{}ߕ]|[bJ$ ֝b 'jvX ϊ̓]ub9i5i VlniEӑ*M0:kZBBW ˛b_y/jbi+3  EL+2䞱+߿Sn^Aމې "^s*ZFVQщ^GJXWb=2fRFPS:z95r\_\U{Ƿ7g*"g.w߷[9`M%͇ d5j`aMNR [;>:v|K_k :+`PPLe^'|v{ְh@Mכdh40F8b쭍-PHhEU a#N$9\;j?6óG ;'51HJ@h>eÏkUr@"irCv6%MrRyu/[-'E!}`ͼh}RwB~J)phZ}=3W^7W=dN.WZS:rr-+}tO-U)i"HIԦ. UrL] |O Wߡ|u_qc0fRB{޶m3Jj{4&&ܺ ׆S5j/UҴ :̜2`XRA"xh'ܷWSzbU H*Z΢k)9p܉p`e7 7d1YU3,UXo#0Ϣ2Bg¹@h^!#qcҢ9Xίz„YTu6w6uߧ) @EБ#^d֎mJÕ!$mokЅc cf^h KS+/SP^i JTGb#w~gf1,fElC2 I08wc}pe-,oD0gT\@WHVPĢ\)dli^zmxgpA4ԒaJ~;`ҁ7̬\2.tV=G~SW#+@N|Mho@)2LwLq6T0!cR_Y}٪ӪƕCL_d]1jJylą/|Ͻ|S؏L$ (aLa9E=H[:w} |h- i`4 : LT -AP:cM1@2ڈpUFU l=֠.|2H (UD8 jծ?7 h8L3^M{2ŏȏ*[oy–V:>"y1IXIh6u(N~^T&=&R DY䪋+ۃ~矸ڑւː[ <)I$[ }?#>/S7ZޑPDj2j~0t򘌁i2%ד^= 3((1])Vۛ ,#l花^TSu$P<yt#>g<ýT+&;o@:2?"GmcưӦHF$RF 76gB)Y_r9$\Qf޾tv-,u485gpK3>Fcn3|(9K@}"ie\utc 1XFmݘnEC|2lZ޴8L1iiIU3_ɂw %]97J/q->\ 5& $kEi0G+Zq`FFSB?k_:۟ڈMz>PKyHuز{cq}Ӌ]\tPDцk4x+WzpQ^L&Iы˜HN@BbH5k;_XMUQHƐ8on$G7R I* ȸp<;q׆P`Š&M4 dۡC I͈0_o7D0 H%#)j0zYǑ6Rhf,@UELJ@׹KؑH,IQO$CpӔ8eKYIdĒiqO7͘Apx_>Lr޲V 4"AHnZ!2O7߽?\PD2BR:ܛ2QMFfnTsFO)!= :867no _xУ]{?zҜӶy{~Redd~ |@T1%LHF+a92kG/ G{YnxjR,hSVwQPm#>Ao0$&:rW\:!4c<0MØ&+դQD,^gy`f%foK#ْ(-R@S)\Y[3Q K%]T)fZ2FPC2 ,[ZFxܞǵ:QJ #b#}{}y 3 X&Ï=cmżXl G L@SN+i{^4H&*C]U !}xcOuM{CӟexJlӭ32t8|e._=qS C.bTKCD첽4:>TL38%@ CU7#]ˤY7f()F5 T}ʾ(FD͓3Ŧ<~NniUBEdAoXGNʄO}ԱŕVTa27 &7> &k~tm+PXεB+¿gխc`Y{ ԒTufG~=Ͼb#`>7[I ',M4ϭ!2{ݖﵰԕˉPX׹  !Nb&1Vdф[c]quqK4rK`SnW6L ,n\ec{nHiBwSwQ}sb4)Uk2 _\XUA\,-2#^HӍK/ճ  0W Ab-dV1JG kw9d9aGpnOWX(uUSj|zT6Y]rW94.b Soq')V^؂ QTUF@f=Љ32dBxB]nM4I ` fz :2ʕ6yq@~UczG33KWg10(^fH!]ʀ$'v$l>пsOv[jwM6 x%o.^2f 5TSQwN9|y~; # "؜kR qց#[` fPBe:vJo42hnzB2bk_ /ynfYh gZqDHY Ӕ`NDM5GMc 3+`r^ g z"FEr6ݑz>wҦem)nZRLHICg4IC{oX=!1Ar- O\l8pUc0-8lqfD}{@U14'DC\]8wW~7xLC Ma)kSY/oi2J}bC S>Xe=,%pRSl/og۟X]G>v.W5JbDOyfQ3fHC,:ahI; jDE cE#L1sbB;mbݩ6[E'ԭ% b9 r淞dLYR}%gG)A1 9C` '|0"JC$ѤvzU%ьh$s/7Ntz{OeM}20PǴj#Qwn1Z&uq@iZp}۝rh|}"}ޭ&LDu[{%Wq2 X!vLihZ}ĪL@ipԛ"V^ok[zUO+38%mHވ62`6,ȁ! p_ Л fAuqe#׷_7kG9ay1 R$:YfۯeкYJaAc?t_:!H#8z'6 R8} /z$gF8KKN~_X=9gb ։ oU_`"SzsaXw_S_n+ I^eP UtWE=8 h Xf85k^{ GDˈ(n ( YǠV/ƪ9_J2Iq%ifuH8.ւruP(C">hbAa-'6_;q*$ 5e6Hm<55,yhj 4J疬%)v bŋ&>ЛmM wz:H@42%kEU#@U#80 bpek ۢ,y*ɗo;0/>i(E3:QV2eÎ/ RvIr9~# F(+nj}sg d a7͇}1DyyXd8qW0)s`@T 0O+ S'*Tbō/~GC|sBE@r[חa|yxEeYnB42d6_;o[\D4heEÉip ԤRK.7TwVDQӹry7RE0E& "e-|WG?6׎+N;L)1@jLL',#$Bti :)4._9tu8]b jUю#?CmJe~UᚌXn"*Τ}{:5IB͠m@MxLQݺDS_LkҦӏ7>Y0LOw|g'q@mؿ@H\ئCjʐ懶,m'Je$hXk&0~G V@Tj¶m'=#_aG7UOXwT UJ&oUdXZ =yrkP.RDafy﯊.zW7dyєtyDA KVqw~_m_ٺo"7*yZ:63kyK2FFRm`sՋ#7J2L5 TgN;ja F 0@08=Wh)v?LR/wb 9Uv΁=4i#mxq 2@hiXua0¼BvDOm8""H\sgv-8|xac6 S9cH0I\}me埿WV'5HI-U-Q Wfof[.kZDTZWPqLڥɤv#NN;X0A99!KsfA'a+xn~VJ2bP+ ,m:ף3} M|*hJmrŬAoZ1UY$so8ڝߖL!M?hN $ja5%2%΂!yE歸OʓW?D.\}CK" QYeyI]SOhJ>e#7s$() p>?). f)f72/ML]>/Kw9$9Ĩβ5I:7"ME8~byh^(pD%z?N[JaJf2"w:&Eۺ -v?@ֲŒL<DZt7/c)Әs*tb@Eo:Ͽx~;z{B+W*-kzU Fj%LZԼ]хų=maaѬgܡUBoͬ-%6r%b?ҳ 82Tܨ7ZWMB0VwRJ2=PR!K\jP$B[ Z!R^eTf9,wW(8ƹ+O>WD҆q5fc;?6`gx.dH R:R1PJU ^1qHS,[+OK$2D;?MѧvH_11(؍ű_է_Z҉PED4Feֻx1{=aN3>v~iƉf!Zhl ߍ(LEd];4Z5S8D=&fDD'%V w8C)_<؊Q\+ZDs.:S9lO9M}K)>jW 2Vƕomv ڋRt:XQsC^~n'}j`#xoڥ.N}FiJ4T~XD}afRw$TD*D IH#CK[V}31tUx:/BZ¸b0 MK 9>jk uP~+yq Ef"`0!|ZlP-igwx<%gfPC &8& SlW=Rn3&@N4TfÙC0)qV~3)zkwJskPifjPڇmǪ4rOZ^QKag7bU`@hjNXm>qXJ fu͍uo[vqU^QdCs0a2NtF|uIvlWpA$|zh-\Ht43W6|UZ*wiM3r&L m)9in VRO$LkˤO& VLmħRͩbc0 [:Jr$1-//=ηYs#=|S$R@EK;̐gghtÖbZY8~$XUUlkn*3d 8z94g;y%w*K@͆[44'"_$&ͨ5=N.)4;?)EO͗S dv(D.kB2HQRdPd6osuȌi% b@Z7%%0ۯ\w1%l^roKJlկaԌɣjF-RpmBaB p McaZ@F1Uu~kζNV}(nMTf%Fɟn^\Y YX[p x,4V a`_ӫ+G!R86kE隑g$ Uc ݮ2~}54AV3 h5>7@XV"4#h1:ᛱ1\x&HÂz.Sl" fFR܂D&vk`4,yŀ0ަ(;~$8C,帨Xꆦ: qs-B)m89%[ޓ\+x:b5d$. Rx Zf}B3; kQ@r.!)@Ԅ+diȻD / iwN<BR4?DwzNZ]w()M|;"9EۛTdSƂsڑ2L"5Qd[Hߚ[di+@J_oicy!-(YAkjmCS:oo&]|esl4uIZxG@%z(&ツkg9JZVVzxXY3vok{Me5Aeuhg_7,vZQM+ tf'K7.f<Nr33- :y(pb\*oܘ!0$#咆08!.) Up\6qPtڑVTLSjega.z75OFEڭ7)WIo²'8S F_^y桛VQÖv;c<;{i(k>>BSp|fO7 ).x  0g$o*8 Z1~IE6UٲIOP5P ̆)JS2PW-1/uZrLo7t(J$c0X<"#29@lo"ϤӰDa&jX}`R 2xq8e .\oΌԨj~߆J^R;Ֆ #`ނE^"Q;P 1r}_o7Dk%FY=kkF9銢f @ zٱVW4mSޓ0KUoeUZn;v\b`$e[l 0#i "+i:p礕]1dh+ɵQ'(l Z&fǴbm;/4*OISnXwfrm< l{+}[eZ !8hjpyeؤFԎ_jjlYQIђE'Ɣ/hh:}T:,]Uɴ n4hGb f& %nu^KM-0梁oq9vjUJ# RpLn{` fWhkn jrRzS1aƷ*4zy&g9cx34T*AfKٰ5nkڋsݹ_ūda*QbyizIHpNUҘ]*1VMs`6W}s(0X⑵)bu| A' GBwķ )J@4jVPenKxg#TTySGpS7?v\O.@(iΖ`P6p1xJj>Zu&1VkCuhm8ϋM1>x1tP\߁7O-ĸxHKJuSz6x&畬' MBڎ*CM }hi<XKbؒD%4 A-:Jp_[tyASRr 3t R_aˢ"cExZ1*<Ѩ;LP&[>s_93?ާKz:vĉ|LvDEZ*@Cu16[9\G0j ݇)c0c3 0L4SMҤe;Ѱkbg듎`txj-6K=ƶItQ(︝N䀎VϚ-ŋU0Pnm"Ө[7B,F7)jAag𴝚/L [f"9heO9Lh(I-PQ!V໱M17C5ؓe~㔺[mT6 `0R0} SN8dz㚽*tYhj+OT %ido;v`z7#mWťZV&g\jD fhs lZf*z¿1EQF&KI[8y:\tioN]`So gGn˥ea'ڴ}~.7 ?zV+]كJ-jp^G a?~BZ Hә %':UTf0bpc&HaK0҃&}pzp_\ZDڀ.5䱄9/m;T@f{CRFO r˭" !5v-!lT~r A+N-[˞3YJ^ U &c|*%z%`;s@x#'ܤoTF ϴyfv җM g@/i*SNQ`;2/9I6!5hn}#lz%%Xl}н}okR۟X+ot}' 9^=JرC,Do}fz h.W_+3Cf&o&*^JVlQN˞PMfymT3*4+O{4CĦ/*ymf:?59 zlQcɭanoG'Pz@1.4X)M*fO^YZ譐V_C8G)qzN73\Eh{g5{Fw/pee*m5FC1b%S~UkF**%tU`.F볻pCًoY~UoL7Q* L4 ͹wv|tsF*-ҚywxÔ;F -XAO:U7aONE eWW Bu˭FX/s>{ۊ"-%S\k?*4s P q@'~ޥ޷|ܤ1~ȭgZeTLu!.-'^u} `nPmpbA,lb,0`f`fth)e.)Lhgk8;NJa@ȂYC߻zWt( &mҾG:%M%v#cM2YӘy0P(Tc߻qZ"x'߈aM;y+J̜Zh9"8+Nm\P3gfM@5bAb}zBY,$#sH#k\;FfVG+V*Oup?RB!$d>UD=8m`K)*p\ɘx/}މF@F^g v#eOrXFLvϝu[bY!c\)El/м_ʛ\bteU3jg/H/F @*ab .yh  DID}y;IQiEX`}{;ZhFXZ. 2n>Cw8GA`&1WMIY1)W4H`ЩzKkư[/m/j aJ5v5}9{XJ9SEk;urRzp.p>#G܋-l0knolC&z!yT>  (ui!y qӻJ|S0Ѻma즣 V҆XP/Ck𚏃B|!HNTgThYn`1bE$ݺ=s/@ 5J:L+\}ا63Ԓ 3Dy?noJvly²hم+E@b8_b$%l4CK!tx'?0z?K!e] VsYl9M"%ﺸ, w˿~׉I@}300g .`^F6G 2weq˷.M,i*M֮%Ǚ Wx^dJ\s}ݹ{ EY4EaFFlFFfFP/ w(;C6@tfa᜙=ް/na"b6,GJ?+3?<-tw]X_0H獹`J45 SW{%B9uN_^~WhB@MTXN`tzMm^yuŹg@ϩOhġ}5_`&PFQ `jl/ϥmz <w>1;3h% v8垻0R1|c4鶀:Gg<"3 ~>fQjs3 \E3;޽>1G_|.6[SOjѡaFs@>kWs *hՊW׼}C>Ex5䦗qpҌ|.|#g3 PZ2'ZHѳ/0wmSx`u.Bp01BD%z ;l=Ds9D;[(lT5z+bi%mSٰ>v4Ӷ:6h"[Nށ[$j;[72"(Z{|w7;'s.GJEK`PQAR.mt"b|@߶j=tךpp"q)OJVQxϝMt|mǚaZIhRV̉Vۭ?tρ{oKV^6s 6}6DZ4@!BǘW/?ѷ-η`pfbJK{0r%} |Z}a_.9c'Sp S$KQٔRb=-<-ģp#A0XOhx&myVjd[<:DfE `E&V\Fnʇ$p0T9ޡo{#z-8H!0СBL1#*%X|X/T RMIǢvv]JVdzhH7]gMv>#!i^TSw6|{O̩90q;0ʎ혶QߟD Y)lÓA103q0z+=<_mWa5#̲I$؋f/kuF R9@.'?ՕeiQ7OV -=6arZj;R:#G鱏Կ>ĹW. J "QDM` P<uȍ\{wV1$U)F d+ skycs=ƆJaCr!1`9M_L CSv>܎0k> /r}ԬG*/}uVZj~e9 f<-7;C)4m@_9&o ߾JrYkҿL+lHu]mB6j29gZMd'X,b^f5gZluqO}Y@2^D!Ts1k؂(Ԣ싫;p-\Un'ƥ$awJyn|G>g髗{>l2Te R6Vmr^\_dk<"T jbDky$<ӌ߬V$cwTry敗 %pexIy=]NBhEcg 7{iI{ϽsxƩ:?unۃ{B:JFt7N4N::qBﹳ}n&v>j!2=r0jJŴ&ׂL\qG]w.z[R8yM\Z;MK%] I-MjoCxۭb1J$Рt[Soi))dJ@etnkOg^eڒ0JrC.Yu{"sJͰS7^pP3x_"XՐ@.$>1VJYD/!|P5htJVUgH̙!3vZi +{@)MMᵟV.j|cd?o ^8xϜmL 5'teXzd"b5@,=W( {@>6!p3R3 (%i+]#W ʵэ\; wS ^, 8ϯn-m%LVi؍ 7WPHtNo.N[Pxb6zW/H^8(M=#LhU ^ !C6tNT)I$Fʅt~L\8\CλFBk=hT&!U\qiMn󻇚Hi+W־d|Γ^LַLwN`@xcD +gUCN2pj+ (6^|>bvmdyǧ5VS6i3ccUЀj)b PX %;SKV6io7(k/ZPA,_.La3yX H R ţ`4<-&CҩR{31R EOm^+r~|Q7D%&ik>`||.vۄZ4:lp101rgUO{So(ШAݑOAsc ZA2X6¾ 13 6XxRe.r/\Rz}N #H9\ XP2*u&MӪ:10+u*LB)(D A6ܓ[zC=벧U*9*RO_5[oZ@Hm}k;>2tÐV5ե k&4L,;$楴ބWfr* @?IbJ>)äP>ԻVL,E V׻ɳ}E207O>&ӼZ^ ;etZ080| k",SkcUK9Dp\Xt2d"qLdJ9:_)icRP1~R07}W ]SF=Q vE&>%}e@Ѣz>EFqknagk(ȥS?ڔiu}XQ,aΌgKƷ[6gD<qhB o)!0:&P+W [+CsJ=|MR\I!W{NMVg,JտSI闯j)[W5"Q/"q0}#h8'^9{iA㜃@j8]OZ)K-RlRz,oj j8ߥz#-.3M+i,1?)NDHqXDf Gܷ.?^]6;n:hVo^ 6H=3jhWԓTͻ8h .mu+V\;ZPDhF?/|u?aN t)3|X^L+Ia"~)Jˌ,\ 0A`Z/gZ߬}nĔd42Mt`ם+ romۮvX5bޑP+$9?Ӿ8ƀHW0U ss~?V$qI| ]ifz#ʛef'uihԿW.l-}qLTeo(.SJ(iu|ߵMs. Ģf m ka6Ўvl7юfzܯo-~!$)zX5m#WfJˍ F?\P (6ȥ)bp >봇͠ 3kxtIQ3j ˋ4M#43aN-4vOꧾ|>vmSkՄ멙pM@x7=`xr4s( ĬN>i\7d:=[kjMX=b乇#;ƈyM"l_FN2R; lgQ-&7.3aت G1wAkJT ;]4axk׿簹;?5eڧ!]w43d7΢&@$:^\y{7q`V kt#`}cM^_d9dsfT"E Z$y6IDAT~s+9O1f"萒w7wp Vz{H6w& `bYMF#cVڽܳI R\5CDFA"Ej)hIy iJbh^{m}•̮2D#~8>k~hJ-!ff.o ` P! DfYGܓo sC1f͎} Yq]/4hE.aPei 6fKf@4dMn}7!f慬]b "Zf<4> STV:;vV\xdHm*JzQT/y¤\˲`:ThA<b@lK.閻bq[5'41/ܫ-Ӏ( &}̶~=_|mX:k7w\MU9zx|ps"Q뚈I!Ql?Y\=sU h<2 -m~y.@@` gٚg4F!Ml66ظ Sb}&8Zkl9uIn[Mh&Mt*F3%#Ro+ŵg7Y!lCt+٧=UYcl *1Q;S;ҟ<֟fJ Pv3sYü~zҀfSh6/^rNՏ|ZR'AwO}~_V13; ,A\$AH!#gt#va٢NK2H &q,wfg=UU=U];=`b1S]]/fX|jW-M LpFe=Nڇ?7_i{OHd0Чj0XԹ^s7@ l]rFԩd==G&+҈Z@b׉l;n4϶mt´v,Fʏ_D>ϤfF"LB/g&O,󤑀whlq`{K? !4ݷzg~'O ,v's&bT'%mZ;;jq"MGM[s0^Ҽ [{l//Ͼ9s2ăPnmܞGk已@@ iv~n}j2>T{WJ?A .?mX唿mݳ\UD$|{' מ-.'c=i"AޱKh76n(H3z7P%޹4ޝO?OUlLXRWn0VŹͭ޺Muh6wmr[4| wFUf뉇u~wggO1TG"'!x3 @'r7Kɉ4R03s}U}JQ%ˎ7lv{lc  "%0Km͗b%Ջ{PH*PQ$+y>~OM"%Q.RE6 _nayES B kވxxo|O'Ss$0<>Y`ll|cm^z>{& 22 z'`F'yRn;X a۬[g`L]+}`'Pq5Y?oÏc/ͷ&RJlFZ+uPxt $ɩȳɍ,럺OIC"%@*F!ş*,7s4}S7]tk71TQ}FKo_āR] 1i4G߸r?:y:sItTxNOۦN?m"e8Lյȭzםg2Ya7\Doh|Y/'rhLc`NcX sa?-Bnkm4t~"^Z ih)*[Z.x*/<_A51BtJ/f^B GȪ-M꣗%ڌXda (5SW]zzc/ىP B3w0Wln6O*jѠ]Cզ!=unO닱ֶ%ٲTb6a2ZYΊ'OXéS~^}W~wn4(T:6NY8d-o$Py ˋxoe;;>S9{F¼3uJX2H]жT;B#sr.Ku|PŴ--CC;o0ff*omsY ~B$By^~}'?w|29A b'̗5f>AEw|) 缰RZÔf&k<{}k^(TdAwuzY|oFO%΁L = TSYNտzlEA^-n~e-&E,Oǽ/z᫃zuc`6 jC<Ko_#xd}cjFĄ&մRS:Z?B7 D,9¼)'yڧ__7xrT=g$*$߹{גzlyExW rztHߎ~rH/R T&F1Fk/ҳ=)ެp֍fwʖ4'4ŗ/m]ǞԠc"|EL?O#Qr&Fa]Q,}Ąa05]dp&cGhšPuPoNgt"Xl5mzJ$ <n{39zxkY_0{FAJFUQO斾Ww?ni/ɰ~ };V{~볨.)'?1@tc3 ܋<˟d郙*cSCcpjz:6$"ILwmu5em])Hh)F'unP{iIwrXQW6b Mav^\~.MT h 7t\2P$.<@d`T! /^yO|'Ny/--hS`9FZv:UZEԀr-"Af~p97;/'/׿4OOxAMVmz#x@b4ͷ8,iqV2?-~(kXgԱ\OEYU25 VUV?-^Hmq07/F0%&= ysremSAc l>n x# μ+/=tC IÏL,l#)OK4zkځvzʞڐYY{,O??;ϼqL.'AC,ؗv7`"˫^('7VJ )m`{~s{ ;Az?VTcWZ5Ws$oUQռjBHhŶneX8Cl<7_;y/qM.r(oPDI+ٍ̳A:H\ RIOcፃ16 4%I4>>;nZNmYtJVMw [ڍb4%{kW.)sgJOX5Z"8ݱ#011IUN\>{y'?uIoz+rJ2ѐUT~xn 9UPw uHlœNٓ)Y}ɫ?>gr*M}M_{JV=izup7$ 3GTSITFmb@|:i3eN:Ѵ=FqT4݌l1kO5]x*G8_T)UiBl4~K׿̛G%3 _DQnXv0ULgXF+.gkޕo?ɳII7&fL4ncAۭ bҵ&s[5 ZR*]HN?7Jo長{@R` 7h΍ ζhjJ\E0g 3OdOzoYnl_7ۀW&TQIJ?w d4k1SЃj0K2||KG>fmDg'\)hA um ZXs2bN;ŵr=.|wL5)?:CkCtA<"Z= x›hNB_W/.~Kw߳kbҷg H2(*ߦ;.U.5Q}eœz2Y.v~oFBܦ|w -'HH2B=j{ˌ \ܗye;{䡧76~*$'r Ԃ3Az2Y{=CO^ȟ~Ͼ}ikܦQVx@ԟX'vGGP߹LTxʦ 0b9`b"TD槟v?yZև!3§A)Mok.O6Dku>ލt! ϟzyjcaf޷ΪGWv I7W'S+"&R)MЃS#ei&Umeo9A]}[T?@s$w3Ϧ? W'{P'HrnSQs[c?+C !Bb]jo[MO2ˁ)x[Y?  =} i GEBM`-4 *lLuh%500QbWȆ2:b[@dE|ws1Qzb4WtߕT;3зҜϯm&k E䠘́;QAzu/>αo77+[GPYf!DEs=#Vsg%[?vŃw Mq4b (0gT2+$sl3 Sə\|=;\vc3#D!p5зq@"It~o՝쇓A/!A0IlD$\eHPC?Wr/G):h^ R<< @5VUPrd"50G; n;嗳K/o߂Ljzw.@[?yim??>c\d,hK%$7x d^ɶ ,$18䞭MpY3V*mlWg]l.n7DvAE SqCO"0OLܛ?{yk'_7J- zGVAyWQĤ~YT]d\PƮ4?(2-YIkT,fU,I+Mb-UɈBZy9sﰗp;\޻q!]ySG^I`bk%Q4TUFfdh hv'?vɣ6{N$2f˶oϯ4s6qڴXj3X ,x)+%9ރ O7 ,Zݺn>kܥgߴߴoL._Z6Z TQs̼$>=rqw#\{sw֛Lid`8s65Sb4y5:VMm1+k5+i0R D"IӍ^r|o]5]Dkj}LDҽMK~LGa`fࣛx/}?g=Ol>t'Uw5۲lG?0E,N`UE{Nn֜Vq0<(:Eq&"II_dMz떬y37_?_w\`hfa0BZxrVQP)A0Շ̠KN|o>n>c]e@$YGF E}wt=-Yfop-L ` ESAG L,ٵ7Kw-ёC:48{TA/޻ iD[j3@C $,x:0䣔Oԏ}g>?n2o:9E=,7Mf 4K4lCIFh3w޹W.[ne92x˚r*f)B$9ǐzd=$\OgBu=MzW6/[>k=EvZ6`+eb`,ɴ/M@qzŅB*@:SD]OktGdp&11*.y~SFPn3dEf*)3!!,/%SB A0UhF9Hlsgӧ{ y3'ᚦ }LCܧBg&h١Zݪ;HV9ӱRUh\-ܷu 0y")=^掿\+W^yc/^{5MD )c: C(aMc4q1e;`Aé–TYQ8OL!w*[KNgUt,~EO(f66E "z;TUW<\lG'ȶltMG[G9N8~P7  qʊmȗǍ*ba8SvNk,'r̓V뾜fzG7a~l`:2FD %U,Nb!\J_"-=.Pn)lkLu巙ynѼ5kfxCX ?83f4u<ܦA1SO: 5 v$l =ҩt-ʫ0 heV[܊X*͙wVYumK"h53N’E r#ʝnH'U(( ԩPӤ7 (ʤiڊ:h(R畫QQ[ôH>4v{YS[H)ހ]Jp(оaa']͚l%H{4F4˪T˾ow[m<MWn cY#֭߫n9[>ۈ5 7;;< ?Ș;ۘVe^"+W.nC۶+vMi~^r wK!nAr_PȌw2cir7aɹ3#hyHb޵QD"rXȡHɴbH$9tg?JǬUD";B#{5 1 "HdE DV("HdEnhGnhY5zYG4|D7sZNqCD=]m?eI|nwJuyY[(DQ (DV("HdEI:}@TE"q!"HdE DV("HdEİTИկv{p/뾫^n?QV#D"w7$H$j$ >fv{hD"ID"Ȋ@$(QD"ȊҞqz`D"ǑD"% H$YQD"% H$YQp4"H$rl+H$YQD"}@|կִI1'p$G DV("HdE DVXm "HWH$DD"+?D""7q4D"CTE"Ȋ@$(QD"Ȋ@$(QD"Ȋ@$(QD"Ȋ2,?D"WH$DD"+JH$I>~9׌H$9H$rW]ā>D nC4Zr"H䮣 ?#Ru»֦iX/Bc;tV$ܞўLӎvtaCk7'1YJzL"˛~.Wm{-+Q> Z}K֙;n-}@&R|F4nGGyob XѼk7."u9D+'\߅[?ǹ/,omBklBJ0.-t{wXҢ/Bvn96_ff˂4 *Η~iiiK-|r8pmv?hE/pjVά$ǭ*:^4P@o2/=Z/iM qY5kSo5}w9+?=U.Fѹuf}mz_ηI[]h|&-y_ZBjIoq[6H pGw}Ƚ'˹*tgW/qg!Pq r4u v.uٷy{ ѽ>$7r7ߞL {~gWBŗHKOX=Ҽ)WzI?Kúz?ȯfZdGg/dy{Z%g=aX5uu:&ߝ5ȤТGRC49ڲ݇g|cƭ'Ozi~0Vo9F1Ťыca~}%lo_Q+{ݵQM\gOێu32>>ߌ++MvϚNraxu;1-co9mW <^ixg18XJm[9>&3_q28ia ?tofv{ՍZBX>b1h֕>DYaXo3ɳwt<giˏF`;n3̴SeO+otqX(kسgrerΞbu+ѝU]Z W0]ye }@A \}m.7O@^lg=NfZ1T%`7 s|G+ԖV߳X]L.^Nҫ ]`ܞFJ #9wˎGKǜM̱&{q?|*鍅Vz:ױ{]6COWgur0?v1" H]Nѵ{<1{-yԙ3Nbz}}Ҽ_荏h:a\<#g>qdC1?mx fȿ"@H.tUb~;7oG \ oDCsC tB$gjE2 6{h6MzKuinw:>;3%,mWLaVx&j8ʙnv;Yu 5kP^VK駌P&g1#cG86R@uX9 n8̰W s\ՈwbOB0vW;[ usʭp386@&uaPǩkal9kB1n9E`Vɹѐ[`Vp ?P1Ɏǭoi1-q?CڦzIFn @tsHR@!N웾BoL!,}Wq>(bZ8ZdF<ȽA+ْ8^x<24_&Խ`(8)j. !VLB%hqL@, phQ(,B~P|A|N΢reBmd]ZT.sFu%~_NZ97c;Gg_/dt~qn>7o&L]tjͿ~o>'&N3yw;^3-0#ZP TmtAH."~tĬG@P;H>aa<Ӫ274N=֊XTTu`f.#qqu2X#K ;jVD3Zun`fo *esv4ZN*jyQζ%By@RE F4/W p 2肅sm00 &qYJsOurᡴ2y+*iA*;FQT. J9om\'h-:I[kd%,KXla8ڇ8/a6a9 !qp$&0r"hTfÆ3"d]eg\k=6,a k9-  SB 3t8jr1qd+WqwQ+5Wiabqv#aAd“m-UW10i{㇧ET#ɣB j^V O` LA#{71PM8aCWx㵷9|BؗfiW 2BZV{E"Jd8>_Ue F^2P^0qpè2KVTt[^3ahX̵x0+Y6 j]ZS)8[Qo9,0Z%:D@I='Pq9p. mݻp%жԫCں' = ^3BPюL DFwf y@ ɕsE^iMRR} g>D׸_5J{֚Fշd1 {dmoǃJhkZ4A4k>ǘ)e:ʘ E[;q&H%,oD+G}w~>SЏM8a >Zqirr0H+F[@d^Xæҭ&ý0g`ˁdot]2UZvrdXtFZ(Q)"oGHvd<+͌ t!*Z / ZSR>i5B%#Qt0- ЗwX.8Ƣ=~!TU>o7I '@@/v`[EWsM͟'U7>J ^ P5\I5@l.&)"&Yay;eb 3F.G  yjz#Xeg,D MBp B@_11!p0;^'t[Va:4'*ȵ-cŲ>pd nJ. !9" *F器 Dn!JLVRjKC}r_9LCB<@+ZrZ!紑f4Q!bP:6DG XXqU8U#3l`-3SՓ u)wW0gΞK Ϗl6p|_̶8hU[lzt y݆ɥDŽH BqxP0La2*UZ-8ܵ ,OK|Ҟ"2w-W/5yS Qb֦/N)#5plϤ`^Xa<W?֥>mTqVZݠyPa/_ ͿӭMNV_MJP/eyi#n_Iz x}^\S৏pHc0ߏ׃axrϣXձ_6a{f#n N)t FEJӖS+d^YޑBI:_*VbT_ NU8N;c]gxOa%X@@ՐfJA,;~9l:Wma3Q D=*h|Ø];>H@)6dVi(K!MȦ' whpH x:Y.>EZ]K:6Ÿ}/^'?$~+2O~/'vI Q5X+GTYnRTb W^!AL)j''}tpawv]>wO c(R'x+OՊAh1F,0vQ>u7m&W9S`]'˯HƼ/R${;X*5>~ -u,wbl-1Ư=ߌyϨf̫P5go <730Y,~@qS R8o8$17Nc] X0 V}hspK{FxD Kņ{Zfj,:X;R' `jBK |0ATh[:*U!Yt0ׂIyMh)'U8UjIfD{D'`B6HOEm *JU?0&-_by@:qlȵ|p{>hDWhx:"ƒ! bKYCsLoq2rP  10x^p". Nyg&^6.VIM9D>=mX, }0 ?qœ XŢJE&S- ޖJLj Pwt>ՅkAN}UhG=|uNgAK&oNh-mqyDYRr@.OuV,gNm]N %Z sVu*#xe#0c`'u@D *Z,Bm*hd@j3Z i 'qug9 ,[hje1ˬD?J HXj=}w?z7⇧2F;?{Y}}|T쯪'5wbpv/MS^߇ >;[̿7>5Ǿjk ";hNBD6ς2 3/B/X^M{9f'ړ49"vsx:MoRNRzE殳]0 ( m7Yrp0F#Q?ȪuLn`/>T0]$AZSG&>y?zA.@Q_o̮5f90a4jk$6ۍ(pc{#1!!suip6d+@3fG){ 4 ``V@" 6{%VG{#{0ﭘc@eajT$Ə7 Z5<I*TuE'M4"nm&TmBFKm0܉5bIWE4C+`Um1t+ 8·Ʈoʑ\M;>`&35Z-HpsiJDdu/l6p#$[Lo:+M [0h` ?E^1CPօU5eeUYMWaԘbe2UE Xu2\a>K^v!aw؅e "vE<[S"3?Om}}6}xgB1_גKF 3*`ZgSͿo|OX2;O{VU SreO;hd:YQu0pI˞,!ZHv`iSB;YW5vh?dgp7 lԢꢳ ׇ4IwMs=bRPK Zr5[YuМf:tTUMCxN J|ڱھ˗0&/@%- 2strKCFĪI˲jvcM:$K?*`xu[0QbXTYeMB9-wPSQBk4n[K IE./uTڂ$H1ZܨRTؾ&eYk1<ݰ*a] 4"eA)sTyyQ mq,Bh :bdEp2da ($;53{R:'mяaDNG0N'ܖN+mQ/݈HXvühy@ ̈ę쨽q$]I mYV]rHEty7p co9rCE؆BLRLPwÑ'I8>H|X2\sM(6tio ݧE>#Pw'ɒdOLw+0o\tǚgcf^6)rfGvÞW&< G&|RL#|8׽8z( '&F[ 3*$uȚ@ZpKݨPy&0҇?'J|y mLމ¶K}" S2O$vz]n5v%ƣ{bKwtf%I: 4$umP}[F:&ã}x6aIXCP:Dڛ1L4J:]*:Ӆeh^Wtv98oY!x7?ƛ|WYm}x"ڭ\KsQ_L*|wܧ2B#~Io'S!tS|L|P{Uu^MUbe 64U~7 g{YO?:+TWYı|zSX$D-b`x , p52[2mF>~gW[xX^ ­55LemEIU.t_*B-x4bN-Q1n axD j7,Hn4 sEO"3u nLQɉĦpU|7ٯfGAhht,:"IqPBO^^EJiFIch\dxCKD" Um^OpUYlfW\v':*^0Ng@'Z- Y!! ¬{pP*e'Rlµ$ϙA|:xX$3T7 ]41NF˶ҽlNW#wƵ( `qS]T /«DGfQ$l)յC=a Hڑ;3C/c>:M'TW& D} G۾Vp</|%,ԙ{nawMc: Y8ȣ}//TU:2xRt "cb[*%#%4cp2!sēlT`l]άUGzF0e] Z?c#ŏ7kM_֨S$}M(Ne Zv=ގYA!'h6u(8s*NrZMᗱnUA ӱ7$ؿ@\QcȤNM]ZudZwv3UQ)Z#2'-հ`뻶]u 5#0nNۀ8ġ#vq $.VcsSk~5'g1{k{-[%MAMBAR⚮#3 ­W&Df`$: Tnk_zTXtRaw profile type exifJLRS3.Ss 3K33 0I1I100J4 6654 b0P2sA #d 4CYIDATxw\q'337fREJs(ZTWlْl9^{w{ViK@R")sA 2.n3sNW? 7 М{>Օ"CUq56X(ԏK+L- T5"DPJH>rMN!G S#"Yɏ픏Lk nJF6̅漍ɏZHAPjH@'R  D-Pff1:,vdGbojk7D%ScohF(gj5 +͘9S;@<V!5aᇞ~ =L4 `8o_H @i)˭] f,d=BJ917̨y o lqZED6ѡ7|o¨4-MU˷/lgzzqRśߚyp/4 Y[=Bը;CLw"s&G#C;~:^9 *6I (/CnG? X0h~6cn~R7 -6Z}s^i`n4ͬfPh'Ifm O-dE#A9<#§ GԱvh:Lh,pp8(ZkÛw{T7BcZ84"(x )R-{/!uD <z*%\mją 507I[Y({[F9r/i+J,hVCs٘ڴ9қ7Z`Txݑ*99%E7TxZƇTKRTJ߀(>drchE9Ua95Aa!Vr; +q:rlt851+?lHP*|4 s^h[lkM  $ %TE@h¯d#i=lSBJ)?vDy]I6HZ~*Gۢ"6jnV 򪬶PUVX9j2oQbp!9|>tNtQ< ҄af+ЖVGۏD;,[%H_ȑpRcSʊKژh?64߳-V]D#?9J#( Qt'ſ)MV@#Q7%_@as_̱pņhGmO~?|d}94xS'.yCUw.X U (cJJpQ$YUlTŪX "&HWx*UtBBD*!_ҀEUU DFr#*@E 8A QՔ ÜNB{Y@ދ tvw,_9zk-[lhВ% +]* #"8Sxuۮ/ګ&fb60Eb2|Yƍ+Ob 왪V:H;"RVU_nXo,@ 5mKld6a=X߽nyb#6{=6brHE#QK Xj˝g^~o3˖Nj KY1.@Ɖ I))Hհ җ_-߹GbkWKlDgm"rxjyhw*Fr{>yK-KX!L0}f)`0Tsɰ1u, POx]\֤ȱ a q@qPVY,JbC*WkT*ZWX@ CUvCPRRaʎ]__۶2GPRFK©`})PV`|"[ﴟyotY@T@\ԫ- ھkWo֞KOe=Cւ%`V73{_I6׎mVutXPX rE}vFE5A@ `A{W}utWr j_9'(5a:(Q{v۶jڦ= A帖 y;#8brI_U*PuPT'5ԁP#15(Ŋcrwm}馻NPZzh)bԿ}w=6{`?.H}h|Jfy ̀OoSZs|={9ST' $M[,H!֏YQKv=Ԟɉ:I9|ERsGlʴ#8Kd8K(& u!M OC&: Q v~ۿ/g?_?έeqD9QJ@u.kЅ̎~{BB$JąB~f hj]ud'Ғ'4C< w~?_S'y]?8%1 Z@ȡ$ A ,4"I*g-9U>w*q;V2Z׊ B U%IKOoӯ~?(D% ifE*K0d&K;UFz;7tN75[R)!/WHK :b)c T=-@L R뗿^{eݟ$*d0Oe׾Ԅ*¢ dlY qA-jD$сZT^1?4ܰL%__Y0(`]4_߾ 7hZ%6{iT.OخYj8C侃kuј"쒤vh V_EFSZ*uo|bFww< +&%oO[ !ˉ8$#!)z{FeԂ_n^GrB(;y _yX-;]n)$6V&dܭ:rL=O4Գ}<>60 qjhĆT]wt)*S G|)켞y3k [^+=\55.YUU k#/ŽJA>׿ *Ӗ#׾6ԉ(9wO~}KNA8^"C08+'Ȅ,=v *, c,TLXR/U FR "(R>>~;Rϱ,u5M͋ـhT"H =q[/keɛYh⌃s4@YKwX1)W Ȥx 6d˗JK$*-xvcdt̎&'~CFX 8@@ @,ݣ9-ASʖ#14\^G]б.(O1ZA9^D  XմQgP,?+[Ĕ۷ [5m6ҩNs+'qKKT,v 3LsRU;z"P\T6Iʤ%Eډ_(>ι6/o7M72HyX;?tT&"MmE#A#8Pt]CnNBdPӋ|ǰUS7*ʈd929=߂GGG: kjCqd/h.pypV L] @Dd 5}BJtTȘjNrBx1%<(T4FeXވ35k̦*l*j >x VRV;+#[%"akTH&u<=|4,4`ɴBDbNkՉx-1̳ߐɮԵ`O˜"bS-"&t˦5+>7T%$NTl0CY&ȸXU/Ll>d-r 3ۚ'@}}:{`hax UKN”D"5\>~㷒ԩa%e&HvNy6;EET T?/}aMi #5PR=I<"(hd;$w<77~zQ .)b:7::Dz9s Ո9Ďubo1(o$JF00)E_6[H&P+\= NڔV¨2abc*Ծ׵/)X'uvɢ @vq ۼ휌-Gd\J [OO9_n#%yvR$JF#"Q.KS# r&ߖGNE=2sS0DabaJXXq!Vm I (1v&wln͜K[j=]%$}$v9U]M3wWoep/VIgQ MHUZ }mj[$7,TgގvHazzd)%Ԓ %+MԠ52 *UPPUF4;[:fz(5.Pkr_YO<,;#S\DvdN:O?-K&*p4_16J"Ǭ0eLWOvg&P(#*{~!$BzV%Zw`2dKGQZ&:eq Da(C !3DW?γ xSUBi:;)i(ĪB5Qob7{xS"ՏZS;c{D.wNFvD"V`Sjw7:Nl #5 ^iJb!ý=JB g@f4&1XWL2!zOxjFgT!㕑&Q9Tr3m4G` bjKA_ATL"!h\4)2)E ([@HB=?Uqk/ Zl5~$P9lW `4D`eK~*EjXMbJuA^FCGˤ^(lv/nC!?|)ܿ]J:)CR*r ̑ `+~p5Ag0ʆBVeUQ-Z X'm2w6w5ݙ @@mLs-K)0H Ԕ1TUB.uIӸ?Y,Zb瞗gAj9b:(d.tBKZGcY/[Fu|!)ܼ Q0\TΪxVrl4M8tGhFoEP  +=v5]>QܨCY5/eWZDUh(gkDR];q)Ty X>VQ:-Z}Po5gY fr&H}=vz'y,]>pN:mAEP% UUهnSn1|6\&{ sC;N%̷=',JX0$Aa(X3QZ]@&yPUUTTT!3c#͗Vذ~='+C-q=ufT23Vy慺1!o-_IĪJ_Tr`@ IIwgm۱J&Ghfd#f&Wo{c*d 9qWmku _uC06#NF+?~egiR⋎?cK"_+Im͹:M[+fe2p22WNLEDVMYEVIH3Sܐi)eےϿl*2@j4FX_TX%Oο,\W]䨋R(HN:OJ/UbB2ï>&O}/ bV_뫏yP U5C# IAB$"qQRPUrlݧg}=hP*G.ngB4+y[y%F#+WB.I G ِ+DY S1K_~VXA_^sNWiUQUvZ"UYt{)c,qݲ~udIm:_ a䊎gnTǞgS:tp=y>G>Pc\}ԚcńĂ\J;3(#"c QX~UvlT'PԮҾW4ǒ+9 ( o'U)e!(.4<3{́m;sKTRVr_̊\eFƶE9JiɆ$Ĥ!ԆnHcِ@Q<1J?CnHizsd(UK|{6u$BJn3Z&y)A)J1K]}q<9nfeezdt)( ;3 +8ڌ֦PBjYAJK}qPr?wvԙ>JT'-pAp/!ۗl8)&yZԢ܍q5]?zq_W2t"%6hdis ax1$[vq?YK̈́LmFqٷݮr9񺬫eH! W}꧗t[:| ]AZC"RB'il~m;}ƾkoyuo/ŊnX e]F -V2r KJp _ ,ѢEk5} TgjR:ZWxMl`0C ը}vβ}^Vrvu']a&))Rc-R+]o^@T4Ai\dG%5"3gV֡u`jCujU%t*,)C#{>zTl$Wʷ%/SJ@VChga1ٻ?PbDIѸj4QG;~Z:'V+9oa bn$~eʝ:} ^]]9[&DuJPVo @@ di JDlkc]ٽ/+CX4\H|u˜#@bE?%V,pF>tj@3p ~?~/_ U[fCN,?}eײn@mhV+) ٟҀDVa3&06ռM_@|$R4ceLԱ R"*[#?0z_^]OtMb(jf %?MkXD`[}+'=#Cgkӽb\pZHѨ݋oq'ʷN Y'w^`h˻SwحR%r ݗ^Vyy2*`]d|)F|jV8!'d`BTBmLg"NsBvGPЄ;(3bs+̥P(v f:Fct}cst0 ZZ:$3 )(X: vgJ$M )#W+*&4oE3_Vj K/${sZl96I Խ:R7`)[qEߝ S'U"kDq4=o)X@Χ*P%_D[gWQ`T ny-1$3fz;g%u Di9werOmM+ޮ5Dvw}K;ՁY+!H80'"2vX/$4<=ϗ=((JWX }߂c6e5Jf5o+örUbT ;7DJuљm\{k?prF9ΟK-/鬉65uJUN/\(jֲdond5`$FW\'N?)T9pe[V;]{[$SJPbV| .еkc\Nj\ [j(iPԙd5O!(AIJ""j 2gjcV5X] "s!p KyHyleƍ%SR5ETd ޽;˾rs*Lq|ƙ_xU-YT,uHӢ@AW.🫂*MоOޗ޽]{A@7;76D4ogj[ӷG%2BPҥwyɥvq֔! UٜRd(P$p8/t/)y_R[2{#h"O~so?M]q, "בT1>Ic,[K\+'l(5sPB⬶Lq1xD9Q6I'v}W8$B9Z$Ub_+>|9p@vݏ@FUNifen3zY%Q[z9EcIIbD(`WՃR ".S. .Te0)ZU$Jب#!cQp5ZjRC@<~1;fkjkSvw:nyv^^j $˗W|Co٘8 (N=EfڢTBsND2\ꅞks;: ɣbvyֆ"Q5tW)g&R)|ڼBPOΌXJm%g{؊郪ʕ~^z\阷`&L[l=yAM,4&>\]9rja^z7 |ݓ ė;TfTX)&4'D'Tm U ':YLN~Wo=;K v.yW:Wjr'¾\Z,xr&$L,YCiHvECF\QZ/<_Scb+JwѲf;̲M/]RSeln͈7ZD~q_DD/~·:} URz Z(0~UAKuDgw\;=%KeRڡXg by$ݺUwK\l8!JrvOZ-CQ0o|QbuL$旷ŷܘ<~_25{Mv%UpTc[' ".bg:YETؒCuH=%~UҤ ;g#%Iucdoy+";kSıu>ۑhE܏,rC E6b9= h:Wɹ 78JBd"|qS[ذ+)r{@xFS!cM&rH&e(Lb9ikz?rI%uv: 9&Uɑyryl$p=#᱉=|_]/V[WS:y3^pXw}{# 'a!U#` (uvNJ?4:7\C%2zDAy.ڽ;,+yf^srOcbx^ -##!ȕ IĤo?+{61bR(uXP!C0tWUU,*4=97y/ܸ'+ed%ܥcWety4)2VyCCV<2pjhB9R:MI]5 ]__? W?v=~pH°̂D@dX;=zY"C*^ԎHE'eә,b/B)rCkIf`U%Ԕ>h| v3ATKZ8K*Jʾ@.$41 H$WmOjN:4 "O"V3[g?6qfUb޴`𫁴6*É:bzF'7ۇUaD~`cV4q%<…Ihr[B·ߝ}Y"#驶Yƅ"GP+ b֟T&Nt~OTJꃟ,ũ.NP&F=6)T6}xevC2XXWyJ#d`PSGU&PamnPʆο9-Zgv#Wiރ)ZRJa,x k{_yW<̮LdE/}&7VTX RG)_^g37mȑ*U#ՕOjriF- P \xcAqd^ݪw<}LBu`{HlI@D{kTz`;dDK&8߹vd9oT5WMkE+Y)~Ut @XԸ>ىz}K{]5 cAə>se/L(!&{)i._FۇiT~?GJ6l9(9~sjODKf_/8育KP0fN{2~4U]zEIz(Q]@ hx9Z} %lL]C|Tzo!؄ȇA:f槺zbKwj㍳;Ij4>]ۭ>o|w"My/cuO -I0oݼt)؝:;HfRiDIkA*^'eHgQ@eRKWtuDYFkl␃ȨxUu%#aL)FMߖWQ3(*-;.;Śn2Q,@ &) Q'řbIr|{;FfﺎE|gJ*Jk?iuJH2GijGPzF3K(bWiuڷ@Tؤ{Mҥg;36ǧZ6:Ї@50FY׬O>LE )Tνl 2!s:5ptPo=LEa$SPEl^'> !Cϲt)lD'283Є=s=\hkgk֍$$Wg>_J-ny4 (r_3U=wij|pvu{}eVhRfO_:uI=n݄U<Գx6g|oݜ1s:Dr]dyw $e;!< F!ĥzõGnR)3+й^B/M:Wsrb%kw~ bU/&a%e]٩Du(_W_(D2:y+8J l$Tᩁ#MNPwmc˻'#G6x DV(c{6XVk#;- %߫2QBq-rT(:Z[mH6l,K_p|3Ǯ+gO"6 rWIbUվ3N*2C̑H 2]3$VVx'<{6Hũ#3N*cٻUZڱO^`)457𲊂#4<{;n1?cl}Z|Bsfe$q?>8Up(y` AW&kOv?͙6M 'f2t^ !K0D:H[NwDI+,e{q{M`>}8|} +D{_ ~!ED(ܟw]GH Y"%0RXHUtPY ]KbAVlXl>nZ1+9m_hxBd(=b=7d͍vΟM%I$ĒAχ6vuZmK;+BU5QMTD5qJuK_>m))hΏsM Ldn}wDUIAPezp%E\Kl_]*gDDJ`JՆcx|@T¾r(I6(<`rKKl3o E- %*j,=&$:=~ j G՟uFEܔV/!U[љ o9jdlwo'zW! dYH{'9b3DuUrqZQ(sD{朱fH#ҭ{mѱ+ŋ4a[V AkTʶ#T#mxk}pICzaP9WS+<Iw b!!r ݩv:i0p8#2,/Vɧ!c[I c4J@4^Ug;+F )(lU^ ")Zp0oE]u2ovE-6yH z,a&dkpP.,Juh84RBPK7Qb A tUV5Ŏ[!vVxc5|  ĮJK_<h#OB+* ̙ U.׾_[n]$q~{a$gܜU\9#L$+UUAD ܝ|Α/ڡ]#aK/\Ui9 fv9[z/̮bY~medhEEAAmT,x""'gL6);yBOOaE* E_5_EVT!,Zg%yB)<2j;JqUUp{\UCH[7Y>]aEW_,(:;?qcRZ̵n$ZcO2*|2KZ &DˏUڎ5 /(soL b&i[M ҙqϨ،eBmQ7oSq_]IJE 8ۈ2CJSXk:Hu1w_Z鑀 woL;&$z-R~/}x>q`ݒ$HtWS6IwDD%;WMB4qS,h;{/>J rXFRYjXiٺEݣq-;It'f֡09:F4JOcjZ榍7S2-hGkݺmGЀg֒n7nF +Sǻ^Va 5L8}SfOX9ى 8tRcrk &bz^-G֦&%: oU[H:]qd:Ќ0TKӵty)M=XWNwsu#hGw"XYL$(G scg \vV0qֺ֗DDJELo$E8<j4ق vY[S`WuBKs6ECX*f*q^eP*S͊T jV[Դ,++g']W[O_588BKSÎJ"jP:$eLmo>!D$?h)QwQu7ngȖwE9-SVepv“@!H'W_S\,A= >gUW,z|DCަOZDy:6TI$cfxrh䪨8(JYt+ Nmd& k( R"ti{^|)BX+I `>cPgx#j0c O=5z&k7HDqztUi;d*SJS,}i.vQ$)iehd;"9}n[3jL&QlꫵsY5x2RtT,kx9)?q.!l&H}PW1^KD&YKW҂UmtUI`j{i۱D$r7d$.܀z@nyTg(+#O'_ܔGd*]dx>9wUogsm͚_+ C"?6L+w_OS+5!h#MV~8QKWP"n^mT2 7 ( E!,),zpfZ Bqr;_ǿ$/h!-OQTfL߶@Z4h{Fh4œh[ *юcnx1lDNkjF1fv0/( @E >o43h{C'E&3u!6ކ'l<Z.ɜ[#GᘠY[޲, PNq:rãRB4!Rp#LjHI;W̉31+4G WZ#G:lI\fAhefOeaOSmǞѧx"gW n|\ 6hd>ILs÷; #V cWLy]+|p*Bij( *X2;k Uyln}+2UH8Z>-/.G5V<̾]ftj¡PdMsOZkmn]Q( Cfw$Ҹ@ XP*BfspmĎ>w&J AkI4~Mӷ,15)2GԚu^)r 0*[S/՟zNV;nNI՘|,AԖ,#c9Fl6dJڵgU1\ѡTph=kRIϾL2ctGڱD$U< ?t}1(".s J6Jia}w" 糄硨5}%5Bxmi|æz[%)53Q@U+VEj]mZB4 ϏY3\ E4tDNYY`ZP OZ0}@WSODIg{Gqg5XԼJ,n:*Da gPԇ{n{pTL4 <a &kodRjf\<6sʂ;ß־qZ< J R/7ӊ/ Hr!nYY>׹ד26K{{O'4pa3T-_\TȞrե]rŠ]1g&!<-=5-̽%~P+EtC>VƍOW 8/H5 RH7mk(b*u %'Sq(wGIő5/7 l<%:Ftj<'#.>/9pgP՘IW/wy+gcLkgASd`)J2YK y+EDV{kd| yq'Xx]%o"9-bR#UTa"9(XH"*Sys9u2uJ`O9}AV+YRp8AmXf^ݯ1HUAE4X=8EC5hNܦ cw RCaMtq]י@/ˆ9eلSZ1"X=?wpWNNaug«tρZSa\0(,a! oxF=i@6\MGb*zYK. !ERWA5"Tnm J ]+'U+P5@ki%:И\y*B~yyD@(wm9;T%y#2~ -.IX|Ɨ #|G ev1v 0SU.NTP+F(`eiun yyؚro-a0v Ak0py m>EzE5y\D]:zy\c9 (f6Wۗ YG܉QGvKيf >vm@ԪOtVD@V;/y+_kb4iU9#󆽹1[B<Iר/5.ܝvVn9x..M|s 7BiUbI&Bi5vPuwS0 _־$JʲO:GQe-kq=⛐xD+Moo~XP˯!!ekdpw|%tEXtdate:create2024-01-25T21:10:58+00:00ú%tEXtdate:modify2024-01-25T21:10:58+00:00ޞ,tEXtexif:PixelPerUnitX08*tEXtexif:PixelPerUnitY09I@tEXtexif:PixelUnit1eWzTXtRaw profile type iptcx qV((OIR# .c #K D4d#T ˀHJ.tB5IENDB`bidict-0.23.1/docs/_static/bidict-types-diagram.dot000066400000000000000000000030521456445164300221370ustar00rootroot00000000000000// Copyright 2009-2024 Joshua Bronson. All rights reserved. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. // See build-bidict-types-diagram for how to generate a PNG from this file. digraph G { dpi=300 node [fontsize="12", shape="box"] subgraph bidicts { node [fontname="Operator Mono SSm Lig Book"] bidict [label="bidict.bidict"] frozenbidict [label="bidict.frozenbidict"] OrderedBidict [label="bidict.OrderedBidict"] { rank=same bidict frozenbidict OrderedBidict } } subgraph ABCs { node [fillcolor="#EFEFEF", color="#666666", fontcolor="#333333", style="filled,rounded", fontname="Operator Mono SSm Lig Book Italic", fontsize="10"] 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.BidirectionalMapping"] MutableBidirectionalMapping [label="bidict.MutableBidirectionalMapping"] { rank=same BidirectionalMapping MutableBidirectionalMapping } BidirectionalMapping -> Mapping MutableBidirectionalMapping -> BidirectionalMapping MutableBidirectionalMapping -> MutableMapping } bidict -> { MutableBidirectionalMapping } OrderedBidict -> { MutableBidirectionalMapping } frozenbidict -> { BidirectionalMapping, Hashable } } bidict-0.23.1/docs/_static/bidict-types-diagram.png000066400000000000000000001154541456445164300221470ustar00rootroot00000000000000PNG  IHDRbKGD̿IDATxgڀ{.ZUkZQJ/" "RDtHHgY) 9''df23L{Λ>D7‘/1@V"+v{:2H/Tf8=ɐ}[}֘0 }pGJJGҭNWF͋O{aJRd%nOy>#*2SVdJ>\>0)d8-XzGiFg6G/Ğ@V"+v{3x祾|e7Vk$CVႨpA\$+No#e%AsSYv٠2[V$-˼@V"+v{J3PT޵qhdYއ;K~/F8RVR87BlxYq}YSmȯ|Ĺ ;d])'IVg޺ʠŷldNؖi=$EVO~)ٰCVd%seeQ[Ǿ9)~#2RVΛ4cOd<#O'w M߻_-qᾭ+OV2i-MU]6Xođ9}i1b=%+Wzf zYI@Vғ6efMH_Bӎsc~d3>h,!+Z6jl 7`}}AwX`{]s"W{/-.H8!hv|\ f/.([ )r^>뵽W/S{,+{U/zY[VLgO5Y;[aePl!;O}xww_ܝU;dEҮy?r/|k'޶=a>z}vpEd4"M⌠ Ԇ?_M?xF(j#}N qYf4ȊvFݵ%zYSVM+eO^kɲA?'K r߷OŹٴCVTuVQ.>lG!e%&YqFCp|K\B`Y*羨Fe~Beꊢ d%NY7S+/K2夐/]h3(fYGEMKd6m.8}Yh6־Wfިdv#$+Z?_} -֌;XQ!x1J7,{*+oɿn,ΰ@V{0'ˏ^8Ƴ!a"+Y#k~ɢsw/OO|eք?9F/>:a}TCo>[{5JJf6"uV8:Zbn >rxeQ'b5 g,{f$+[ɓ+qPO +qJ0U\ɲ^kϩ33"6 3KY[+KdY b|>lm!Jf6"E8:^;g ]>oV{Ŭ峆N/tz4iДQ +W['؜貢,j*)ڴ_[;,z:zLQl>?Vx$}!.Le&.+وtJGǫej7(}:5W7G]LVnn,؛貢,л?W:P,{hasV!!^þ8=}`}1"5,Ndd#^n?A͏e^hVs+3a3m򏦁Ϫs-{A 6U=XG؛貢,Zxa*RzZOU^3Yڇj,/~V_kpJ&6"Z토k5}ϴjdWC?Sr{2;g޿Q=$,+&`=&VJeNjmص"н,fY,6iGsd%7wJh6T#}T6ЇC˽&GV2i-?9|m#6a3kDa)1{G[̃dۼuYU-|J²bkߣˊn|k 'CifYЊW;d%[OBϿBOᐐILlDe%A/P N j}Ϭ[іi=ߺmmkEqfؚ貢,^.لPԊ_S`9˼5"[L!+}<@a_*EdЇ$WV2i-Z-sR2\ۂ>R]pWɻ,}ܩg_tŵ~CO<782;gТpԛ7gVO + ˊ ~.+8|U?򥺛ֳxzk%Yڇ=;/G1[6ЇB'l'GV2i-/0yNGTþgm]C&?L-TV[N.ά@V[0U]Vtm*6O|*,+%tƚdY CUKcԾ7ZCmRhLlDe%Apc[Vt+¾g2;g0Yy*Pؚ貢,]ժ;ʸ/T5'eb>d%7wJHUuڇ-m5^ĖH?hqtV_X2Sw8R9{P]S _̃&+u#/;'DeLUGdBC>?e)3_COJFn>Xu]ح=[6NݖQBQ' u JF6"ݲC++SEEl*}ϴZq˪GQe}68R*tדV懍H+BZH͹efd#(+Z_|zgDN{Xt}\3jk CL-TV9'Ȋ *+ɢ=ϻCdwEU"kY6qƊ7~LndAA]|-]Zj~%{Ϭ;/o8RL]z_i=SrޔWm_1:tswnO +ʊ f⡥x tچD{~ɍp8)hwcP*[^V lhM!CVi=ߋ,_ns{YUVMgBJ[/Ͻmo!VίבL!+V2 r} 韗vO+:;=fS? gJ{Gζ2PڤUnbe&3Yn{YIؑ)@=Yn)Y^ }Mg!4Yؒ8{sDϘRG҅ڻ{=8Ϳ'qt [,.F8QV;^&_ζ2A4iedJRdŞLyʏ֒M5=Q?B 9YqYcuC }#oj,y?jXdz;kw.CF8CV;^1_mIʖS ᳋/_ԿIem3 Y)uuȊMLy&2&7N5ldY1`>?ЭvzI{y 71ݣg# 軫d4"ؠ9V~8lw# Na3n`~O]s8C{YYVҘg`ym|ߎ5;3~s#H   +    + +    +    + +    +      + + +  + + +  + + + +  + + +  + +   + + +  + +   + + +  + + + + + + + + + + + + + + + +  + + + + + + + + + + + + + + + + + + +d\2vR=J!v4e2 d +YH +   + 7!οI+tк$uaSE)Yԧ.uM)*W]"R*QV *?ڤHd,eH*pp+WlYGL}Hd(y<'"`?3Q쾔+dPt!H]+Dv1]>Fl\vޅ~DV(=-@' +ceB>vHdd]H΢K,HO]j݀ ɔ?0V/;MDvbtM;|f(򞿉Df)DnUxAmA:IeDDnVKJ-i\@0?,3=D"sSuYU7ҢT/! PE$2i]un [b'Lz<*VY\w[u }L3o*XGse^%L UDfW6?1AV Fn!G{HStn䍬Z6 ӫ4tH-ZNo*WTA\"h>TlIspÒIt lBfŔow2%tf:ud"vz0j"X(Wy@؋ֆI>S DwL׳ gR*V1' 'WYH *8k)p"_T G@;q`p&+BVVT2DY0y"`#N,Z!Y}!EdG$ dO\H؈3ֆX{,dLlJD",?WիB$éEk)VBVt!dn'آuXtf|nG eTE$Eké)qz YS:nI eTC$lEk'AӐ0NS{&좵b-9Yh-D:{*Q}8hmʾH!+JĮWD$R0uCD6_6buO/22k!)gJF g<=CV #4SKu`P6X&QYVJ*nz@XUU'.|wꦔǘ<:r ^2hm8Z/5'~nKIE ]qk~Xt q"?R*! ۶aS6b¬dWH$ϫ{+y5mT6rWЉ XG[(t>\e0*ZaZu^DVy^9_  C׿Xt#X|wdN'6qEkÙ"m#ʔ>H$? 'L%Ek)=Jd,|u", n>u.,ZswЗ X%L6䐧X@$!3ֆ3F4(i#+`?.I.Hۮ=W"` Z6OD"+`Wܿx-H ^\M$l!sֆhCt(_vV]Z#Ek,q^Y]'eD^~?O` ]6Ѣ)TdbH/;dvdgUdb|uiܳb# !c-dzp>5 ү Ÿ7j ]Tωe7/ZζҢ=XdbbrKpt"aԖVi `YP6JAZdbC{ S;VO$l +ֆQ`:B"+P㴤 (+cCDhm8kdxrau[X"(}hӅD qhm׊VF"++?A>UR6=Eki#u9@o?H$BGimXD$'ֆH6b a+S2HOimI 'F yߖNFV MC#e(ݏHU%eEki*v@<;[>R݉D|kׅH$h,mLٺ\Yxh6nh?'uEk9"㰭EV >rL;32&hm8/VƇV诈@depgb G'l%EQ=΢8Y/@|*F 1= @dmpFV ^>SEXQ2U)s@dopgcw 2rcQɻ^봟@dqpg.GV nFM>uR" ]6DSӑeIl.:w9H,/ZN_ wCD"K  $e+cQXqU@d}D#+O{&g(]H$FI(ZN3ڎ@B:In:E$t#w>HQ6?dEt> ,e09*TJHQ6D $Buޠ."4u:6%hm8mE/& q'Cc^f$F)Z2Edse.]Ϝ ThsH$D *Zm $Jug?%SHeKPp^MA@h5ϮL$B]'$N +Z< dT\"L'53 dgrcr16- @B@@V]#'&>*Sֆ3Y@V[ EQSR@"Ȣ:MSvPPAnSZJ$_RҝH$B -Zγ"w2Mrr"Q|9u}H[6" e  Ÿ7 ˩J| -ˎbH$@-ZξD3"MˉKxꢃߺC0ՅFߐOK5j⎬MN'r}r1}lqn֚^Aj2U\dV6vqBZ3p Yi+STW oYOՊ76X=qBZsʉ' ͧj\ sY+/Кqkc)#W:@P6 MDt܄YSZn{wݬnf\ҰOr%vHhL03̮S|;yWLޫ}7x>SVl!= {"^hmt)"|dui_z+@&ZԎ|ڞzW=,"‹Q /U' $jv+g򬕽! yᄃl+aKķ:‬@N%BRCRc= y"AP"Nm'fcCw{jeYa-Q.,Չs_hed]:d{z)Ť7k4&Mu|R*e< O"+fʥy_g괥 'fS_hWN)Z {d='!+_w6"3^Sn8xm|b="XOdkv2ʟz mԵ>r"2H] ƼmЖ :KNhmɫh(oS+S= BEkc N] ȸִ6nG5@:dEkcB>0q@V^U3޽gީ'7TzUE&Ekcc l‬}ˌ֜f)=0-isΟ%6-֌0 +`'3uL;ik|I]/m}%Y;]~W2J(9I/<"6ʠ-! ơL:h9n`YHCΕJ@VNAԠW7Ĺ;.ln"fdgE߻_9V69_<[O3Lꅄ'Ș" ƁAWeѩQr|t:.dNKGevոec̖[p}Fz!Ẍ́Y{]>~pm L>ׄOd LW94BVn:*Ol8l[Dkcr^_u?!фMf.0Ứ^"HyZn€@ؑ󏌾!hቕcjNO,3Jīlq@V  >҄Qmqe>yg!+$ &SOџuS^ SKY<2x=JHEmj0ZVQ{Wq/ e@YXLZYybTqt&X0s(;i,q@V irlZy2ʵ~#0LtSYMb6e[ΩaV]ҡ{rMt"%\T."w:H6a9},d]/`VftP6.69ӈ? yȡO, >A<,)kh*Un&>RAD1a@V ?;2(-kDWnn1.s{Uwqn$[@VfVLx܉+t1r>o&Uz)mT^2o7h*GT 8b#[.RwyIwpЖMZyh7=&4s'>Cķ;MnAvfG]_YC:%\:[魨qSkV d6YBF+-s>y4훚yw:a)>>3׼WG9&w#+GZ[v;BU4=GͥCfo o/'VH%YzJ :W`RS,w !c6b$/|wc6,w~δU»oJ@J/Wrc`JB+]sNtܛڀo<%}*:{O =vnl]eNDy't\ALviz)d=V`'ycW84BV6;,!ϡĐ?v|J:nZu.t6_=Mo!+Dx٠o>Ŏ+jR'zAvh'=(l{ia+d%Ni'zl&DfKNRT_]Ibvm^lnR @Vr>H+ȓ+#Me졒RcvyM=]JVM6L~V3WJܭ eR}Uvc7Isd%vtQg$G{ߧ*vSv4l5W 3:!+-d%ffIj(t|Wٛ."|6*o)Xٯ+Ii(Tm*Σ\97,CJHtJ}:RDYC0nfJ<ߪi)(w[HRtް:}_*dDY#ꖄ7֑?{Ibdҽ|vHygv Y7"@u2 H5% d::LYr7YN*HFFB imӿt Yi +QȑeZ4zV!z0tVMN dŔUY ` MOrj 馠8`BtY131X* kX*wCNXqUBVZȊ-ߒƼcN)?+u3Sl[ Y@VX"Ś&L%+%qތG4yꌁ:J|Y1a0'|:ےNϓhx0 | +&cuDC2]Iw!W)l/Fi6R@V !cag9 9_6 Ҿ#"dFY1zޚ|0EN{#֑3F vi!lB"Q҇|0%_+ϵ)>AL,XN9!+w Ȋ!X6rMIR,k!gMߒ=!+GcȊb. _̑7x'ɝ yA leF/n"]̑L>'VTYU 1>,d!Y1`V(=II$S%@VY&iK ?LiaOzS-d =G [ +琭QM^'LD)c ,J#K-@DaUhKɺ=(8ib@wS!+ ]&aڅlf)+{lI/#(8b@}V&Ȋ.iV(l%醋%  ;JBV 8UV: Y9R + Cee<YAVJ5BV^G dYpr9R + See +@VdR\NJ[YAV엕Ïޏ8M܀ +'k[P[-Mk=7)X>v؈% Ya"+.)Jؓt͹8S7Ly(-@Vnw[Fowx: {&="x؈% Ya.+.]VI\)Iid9"J"Hﵖ 4N\Dku& R߭Q?ޅ;6bCFDAֿ++6%]js4ARȣjh̗IP[5Y~mφ1[\ٷ\Weڐ.wW"fCc1+":gbSINNJ#$+߉$* /@V2I򍏗xφeP3F;"herنi&ԇ*hTD,~c!b}$e*UG]bONNN#$+N9/@V$O5]J/k[f =p{J|sJ͖_ &Lb[a Y߇sWP箤G, YIieYg-bOY`lgh'N'D݇ + ʳ"6ku/snKG5l<:|\[cvFA# FQHʏ,^4"Y[sоeŞNNN#%+_9f7b-+;+IRƑ?ko_|ߔPYw+ٖ5׊nw_ψ0^ 뭜ѶN` :!@2ٲ867کY1%++r Y[Vˬ'&':+/ȝ㆐W/eWmykԕJ;>nWf'y(y~:p3G.K򼴻]ےsCΫ>c>d_2^X Z~nXPr7kmۙ-ˊc.Yc;桝vv' Y[VFt;<;>$lPzoֽ/8?>whs\/}/|f ͧTt2Y rf|>|mّ*jT[m#<"dޕnXgG #ra?z<+!M3ϗ͖UsQvzw73 x>|E}J|E71ڴ}6?޼ʞ /2Y5u]l+Wv6;oy.=6~t{M?sAG溯4`uW lfɔۙJIgф.Ic;턱m}h;{l;LVt95@Vⓕڠd͹YؽsZYa2Cjԝ*3dN>DN2QOnzZX1y渽ebbɤG:4&%_PѸZHuFxKQElc"[9d^lGF˥6rՉ^O?4jӊe8LȈ;_5hny]NI5V?6t;UYI4 >x%il21-mmDD d5sfIfSo4?rSWk0%+oL0f[]ہ=9];3XnoYju7k*E7{~ CWծPˏ̮,xldzXN!tϧVC <9Movݷa`Gݐk/g~W<}l*mUVK=z,s~8lYVI&tIև#ƶ0Y)AdJSYOV> :$Y2syrAV>!o& \lU*Xmo=x!@qDn!+7i]ЎV)P5 i]#M4'0AFVTrO$bqV[]ΕL!޺-V-͙?c7nt{`m]Xn~tWw]Mwߺ\eɱ(+$]D<NmCcvvirqn%+r{SO&(jvmfam>[ŠF~fv ָݭ :+6Uzc_eF|Y`Uvת箺Q/w,>/xde:Gb{;܀&723ߓS،{=1u?(w:O{pUwfq n,`Ym(U鮮DеKfrL8gŞGIm}h;cl[ڎێD\ +ʄ;!N rг&}va&7[nwy jHrRA]ô*GݵS 0Ʒl=v.k,32y<+k_bG7]j,(R"M2^3X: oU?bw2Dlǂ$z7ؚ#ZȞ Im}h;dl[ڎێ2[V"J=xLĬ?A'-3&D~5WPB)7VZ -e -@n\>v`-J8/Gz.]Y3; ڠ3'1ϲ?nEXhG7n ۃ4MN= !5t #M2~UOv9$SVGEY'x%il01mmJ"a:"J8h#+�w#TY? .C' ot/jSҞlX]5crUv6k Z'_6h=跠kmr#b[vW8Nlbnm;JTeGݐM2UF诮@ϝՂvɒU?oQVlIx$t۱ mgmCcqR\_L%YCV 9!r}4w_)J糝 Z!Oiv" x7hRoMVIx$tI1 mGmCcyr49bJ̲2%0|#AӷwNXߒWg?;r8|G/<^7WV*MU6ԃƪ7+US{݃o.3ն;2Cэ1=XZĭ}[5~z`TG䘼ԕ|vUB$r^ّŭɊ=Iē.c;vض>?'+ϊ;@Vb1C^!HF+@T|rtOTc\Z{_ZӠbxִ.xO6^FifGM}z\Og=M|%f;}&͖Yakt#Llow |YqquCk ?_G>H{ɫ]]OMyCKŤʩ>͚ؒtH8>m'mCcہ2ZOYUV O0t\/@ ,QK{vnUߐ+Y{gzgߩ;oj?o>Q!P+_YaGf_O.Cvgk4Uormbe͛ߥ }_ZU]jt;}GnVnzm= w1?C[umBou1?*1j*et+Gj7nt{`BM{~7s/oW{7畆o]V)/+$J4>- mmCcہxCCd%VYy0bAYu滯QRyH5쯷>W i{TU(A_Mr[Wm ^E;|h#^ 6E,tR:]#'+hGݐ ^[_mz&U4K^Y ܦؒt:h%}l[VǶe!FJ5x{$r1!SbV>TBq!W[6Ӛy]o|wA)V쭝(;w\>6ߤTn{ Wl1}ϋSX0uf}:]V`M^4gRL8Lt,}t_oz<+:;k3l{4M:KbS錠.cvض8?(+D&?@VbM?T~ur{Vo8߉ceY&ڒ+; s9QlPi,5Dco1o&qoϭ$|8Oeꀷb!A'W~QhUmZF-5b>&2ZXc߈-Y-o|8̠0~0Z YQ;.-ɊMIש%]Ƕ팱mqh;}l;QV? +cۡZ +N:rYAVJq9!+QdY@V*+MQd% +]V+-I7Y'1O 7o.ȊbQX/e-IXRo" Namo<|n8Nݖl"SSj"3ILw/ YG@VK @ D4'l" Na=sVJH"Y-d+@LimO-!N98IJ?$Yl'wOoO&ՔS-;>.d&H q+ `N?>wʹ)81g:r8\1{dE"+9D'犚e' v6q#{2K@V$}8HRX$];e! nu;sxdE"½OƾpZ ۞քr4YN0cȓJ ]AOYСeL@Fb aH SW,m1A'KNѩǪBVj Ȋ' .-ډ '~SV! [CNC/DYln=e"A:؛t+L"^r=#|BxM$.X,9:e'VC9WJ'LYl: #qt|~VIZ8 d]d]!+7c Ȋb,yd OCw6^H#V&{$dVTY.;29ٟtȩb/5s["&l7(FA= FtɤO!]ln&F|Zݨ8Z@2A*d$_jWAHʱQBV Ȋb"1JN Y;N_b a}2`xa+CqdEC D;a+t]eM&ʐ4SwgN!+ ȊR @/CtMRO^9fNr! ȊZ$1:m&-;Zn!Аrd):2c'(a+cdrl%4~R#I'UC BJv2d/ydK\}y (IwL<RzDR=V Y9[@V[.l8+|&IyM}xJf2uf^rs)a+Ӱdeu셭x)u%96)%\B~UȠdy!+dWIi.U8['o *f+WI_. +~֫ gjB gQ RtK4:vtH =hugnrtY Ys# %9Ԕv/'! :24 Yi. +A+A-PrcԱGzRuz%drvI+d%-jm15P|=SWb+ \VU'iOL* uL~kmSYi(R%#)Lj:oAL\d2:yɢ / +!6gP֛Tҽd5^-wT0.鱉bbp.Bp~фP0U>?#wSڈ0 +3aJ@ b}M;NCUl>r`?Kswufhg[0 +.yc*ŪqaN GҒt[6?+`/+i◌S d6YdޫږL@Fڈ=]9wdJcmcY/MUܛ{ʱBVc J${j:M!!)ץ6ڛLOg :-FA;=O2c@VXw0n5IYJCZzGzÑ{Ҝt{WGrH>{;O)]BV}~{7Xɹi2-Ic7 9W?>s5Iq+drc Ȋswb;6z$gA}>nu~猤;ӇAհ]~ÿHe1ׇ Vfϣ|2 +ݼ@,'yn*]P#YK3(bBҡ-Iyޭy񛝖t aٲ%d唃8b=Z0סrv{~s!ft$K [3 +{͌oGK=yALφ`O]9mܗ>-u%Vc&/ɾ$7-c]Y0Y:ġI dUdYJr2S‬v(_ <'Yd P #QYd P16d4CZ0a18" +i``18K@V kF88 +ׂ w,a@VYH+>ԃYd PLVZYd PL$G}€F(_ t @|-FzG@Vk! ׈:a@VYH(Cr€6(_ QX$-# +irb% +ib@@Vk!*3d@Zʑs(B@Vk/QR0 +ׂä6‬(_ 8p'} @|-X 1N# +i`/@M@VRk+FYd P,r)OdCZH_1RNO@VR k"eXO@VR k*2a@VYH1|,Yd P,G)1ZfdBZ b4% +)mh8 +B ,3dTBZ+xiC@VRk!&^Z€B(_ 11WUYd uPb1b:dAZbT" +)&‬*(_ 1r41f>% ++ϊ1s'a@VYH-L‬(_ 1x1jd@ZŨy0 +B Yd PSV 1Yd P1n# +=ĸ9q@VYH>\J )Yd P⣢9  @|-rYD@V k!NV ?ddCZ95 €l(_ Z+ @|-Byh9q@VYH.)'O{€\(_ D7a@VYH..ddBZHgӍ8 +$B"O[  @2|-$81~  +ɃNq@VYHx\Yd yPc8`'kk!A#Ȑ 6jH`@V;gaB#S?sqAVY.E&j/PC' j=G\HB_z|-Q,b5l-|-$2bMThK\h{(A6qT.SI*Q5\:hp5&, ơ3+ S",FQa5‚}ԏ&+?#*΋GiWt6odcVWAVq@wMRdsYiC p_Rg1o\SGYy\V~#B]b6ޏXeO0K=6U'@e#΄eR_b\codEn,(i1BVNښ[w15RHI&)w lD񚹬#B JEt@lS)b*+0ێ#:m]tI|2L $@ VI::_*">`(@긝Prc{9q5X0 +`/} aƻDblJƒxO' d]Gp FLP\YyP?پ!2}L1ڹ\@O[Np/tsTY<}KY&DBN[dlfz,dcBq`8me?AVn˵){(qa4mAVnڹ d!;t,gm#2Sw1Y)<+2ךF-Omti+'d>PW%qE\Yv!;љN^ aћraAV GZɹeWHi+Wd1 v9m:@XiR߭@<`V5b,Cɿ`hNhRfXŻr}Hs~Z(7saW8`PR|q iJ\;[n/?)~ti$b9|ީi r3Фuݐf1 9WlZ[c/~W\"TLڌ@Jyb{6+-rC)8JBɾS#,s6 u:GDAΕtM-=ƕfiZ[4p 7\e|vhXB:~(SS\D~8f[ a1\Y^lBK8D{1mr+E! t)UK\y@Fߔ*h"P:G)iYulg" _Z) 7'i\{1+Q-lGb#idD\8*Adi0ߢx5-$rng!6֖hџ0 T.iE-; }qYYIn!]p贻^`Fκ6tYH(pک i)z A]4ZJ*&I7oVhF8TGKcub%H59-0A#f=Np|4b`خvꮙѵ ipulic# n_$JU4 l`ҀEЦªc!nNHI/H;jtYp 'Aq&v!TI6i:܂h/kV>աF ZC\"db$St ) ^\.5Yna$bAt9),#Ad6~ဤi5+27F#3 D2$TrZ\M)p6T@oH0O:*D&n9լ|-5~ \E7$f (y!$ V1LTTB-Ek`}'8R uɝ6 @[AwY!53$a7Y;R 1ҙ @ YSJ !p !qf.#r ,G@h̍N5+iڙ劰eV 5Ztk&u@@h'5N5+˥F{H|9>݂:s 1ܩfD{!I8C~!p^dVP =>Yf Yf`V`VY0+0+ff`VYf Ƭ_X`V J$K[5I1)}+ W80K2E1+CzOŬW(( \>^|NHsMfEA!UFe3tbtY$0k2E5+70^<aspbVFq^R M]G&B,zKh *NZWfEs͊Ձ980+#xq 95M(\(\+nݏDa3aKfH_bhV,G| ftL% O̊5/S<cQ-(zy<5rHgL7R%68AG?$1;QhL6qiIҌ:T̊EՁ:k+N`I Ӥ&mf:\ڼɣ>C[_~xĪKz Q@czQ00l$ͰY*baV,G0͊JдN0Vu2O )(.6t~`V4Ȕ:![Þ[75bԪifN9y =T^7h&8Ƈ5I1+:fŢ@qċ߬Mknd/𞡹Wfk:?0+y~zGjEԣ<|􇃬te 5LWy;-" 3 ftf̊Eaeqt:. \Zdi=Cg(3\I(>=W<4BeW%/u}є򩆻$;7Lfوwkx^i 7:O:A .?pH:!j >MQ~;J 6p&7,jaN<7ۧH鹹fEw:3Y1Xb0UVyDhF13+JЬ풠y N v%ة[?~L?ӓft@tsұgǨdtVP.zTlvSԋ<=ޞXNWS>Ua9M|wY7Z kS H(l~:܄qLޔ5bZ&c?N!_tVΆ΄vtFY|pOvtS=2▫-1+әẙP갨8x4!BAYUF54Fj2hn:k_LCN RukIpRD8jLQSY OQs_bG"K ]߉V.z+Z((2]-|3T|[ELMΔ\OGrOEa30n ?ݧwP/iz9> ⎟[eVh:sڱ!:l.saQqphG4 b j0>$hZ'4ꦪf}Qyᄁ산S!tvͣ[/F2Zx΃g+svVzu[mЈWuXtB+yޗ]i ?Mtf3p, |Hi(5# ӹZIrf6Y݃:INOsҏiHZnw[lVә 0]v갨8x4#BA߬*A [YReD_LA רoc[;= nVckpM}^$(9MQ,TW{\*P~U؈S:ǡέG}Ut6A~1ؠ<>@eC%.v"Zy;Mh>Wū~[v:J2]KKt)/ITh́֯ W| \>9"|WsQo3iYgUb}cΩqdBa:WKnTuNNvznH_+T::oyn0]갨8x4#BAݬ*An V5Ⱦ* Hb%kC[3NO+UooIݦwiS!~z*g)Aƴ!h}W1cV[S|i[gaX+|:AH? [Yʺ7Rdjl`eEknՍOx20K1yP3i:y_JfE7ta:.aQqhC4hYUMk\Þ|n~lk',q^TJ{(φ^Q:Qx*24^,zcrˆ]RRN~| r2{)jne3loEEt7љzX:f# BBdBa:Wc{]t[߳EvҴS!VRe!>FzVlu\Ú Einb5c ֎f(l.{s4<,ݚǶvzݬ0btпh`gH}U5Q^-zCVble4_#Nk +(QG7zHeJEw(/uoB138ӯ-_HK3v4KnƓ ͆\zrbzS5>Yyr&%3k}Ǭp갷8LWEAҠfV rk049t!3b3/ܽʸpQʭ~l'y!ԣBJSi Ey<#18JC_,0o3l( ]||z: wTIs%` 0OlOTj2$ =OPoe׶3eFa|5Dh36+~盨M? ?yx=/E 7a*N oыWx20 0,wAͤj-OENƬc+r갷8W5AҠfV rk04AUq_Lυ(fD7+h^f Zp =sB};U[QUoQg~WV .6W~_| 6Q> ~HK6Mno/t3:Ea/=%sOy0֛8,ϢeXڔϙ>x@▞^-Y7yFw?l׫ޞpmxTi@h5m}qv?G&UyQjG&s5㱏2ɥʽug}6Iwěyr Gq{luXT<0 ڽΊ`%ȭd/?)&ԷfD7+\O^b)[}hPHE?6==#O^ CePyWawzT|W% kA xX-y/OEy?UoW>$>ʟ n陦τNô6HφA;N^zk|C4ef8Va:* rP6+JS1 ;v'4J>Q.\vk{;ݚǶvzݬ?IByYߖ#naߌG hScUY:<`)݆ǧ(?؂1Me0V_KGy<Z/i]DL뼣կtO(ب,ypNf8: ӼZ=!<4+| RI4+}ate0[갬8kG4hFJS1 i MҿvsnMc[;=nVo}_vM5S~Rˇ==8NWGO g_oyz0G̎4)JֆXW~~ix $̧=0 vu@:e?sU2 ƢQ^Ȭ׍_ÌfBaZW;K<4flASwQF _q;duXW5#Bqݬ+A> TtZJy`ˏֿvsnMc[;=nV(l+8y|ofΊN|Xwn[jb~kWu$Ò' ZB 8#|Wi(o}rYGeU,YJAA$R y&Dҩ"@qϩf%0Vj1d% n\R^I7sHG7QZ!XBKnA[+lM8y &\p [t}!l&NE* 8d*G+]E),nggkZOd; ^L2 EL`kT~Y|A38F/ m`x  pY ԰no&8(&u?Z及bxy/0V e9FtTM40f%͐a TͽŶWlj06Ue'FL +,>ph/pZ]wBu $v۰m";< Ma&<_ `VJ~s17=eu!&C|хMHFrb,XHHKvU sseaWJNsŻ4΃8axY/Ǭ:ȝ+qEwt,m1]yxwXzW@bYFtY j2N)49'_y6?aq5凭$5;vٻ1?$+}0+[O]%zA̿6Tdҧu1UI~ íKS_ dBiLHpjKV* e3+WoRƶPWJ֍U]Rqsi=cו\כ媣y3B9D߆d9W;w ʍ 廓wG<rY  67s]^blh5ӓ*Lɺ/WǬHU)1e_wœ#-&OYygW\eVJ {WYqW#1 uęk%!1 o?;ޝ\fVhJ%3f4iu4d"ɮoR>; JXd{E7KϬ0WJvm^VtOq^? #da532c bidict-0.23.1/docs/_static/build-bidict-types-diagram000077500000000000000000000024101456445164300224470ustar00rootroot00000000000000#!/bin/bash # # Copyright 2009-2024 Joshua Bronson. All rights reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. set -euo pipefail log() { echo >&2 " *" "$@" } # Generate a new graph image from its source file if it's been modified. main() { 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 } main bidict-0.23.1/docs/_static/custom.js000066400000000000000000000023721456445164300173010ustar00rootroot00000000000000'use strict'; document.addEventListener('DOMContentLoaded', function() { function addScript(src) { var el = document.createElement('script'); el.setAttribute('async', ''); el.setAttribute('src', src); document.head.appendChild(el); } // Google Analytics addScript('https://www.googletagmanager.com/gtag/js?id=UA-10116163-3'); window.dataLayer = window.dataLayer || []; function gtag() { dataLayer.push(arguments); } gtag('js', new Date()); gtag('config', 'UA-10116163-3'); var sidebarUl = document.querySelector('.sidebar-tree > ul'); sidebarUl.insertAdjacentHTML('beforeend', ''); }); document.head.insertAdjacentHTML('beforeend', ` `); bidict-0.23.1/docs/_static/favicon-16x16.png000066400000000000000000000316561456445164300203560ustar00rootroot00000000000000PNG  IHDR(-S cHRMz&u0`:pQ< PLTEA A A@B @ > > A ? ? @1]4hGo8B?8Z ._'/BXYJoz݄HOR ?B uA~3oHB@ =<=5SO26_Z@D M *` [&a;j5C '[!(?BKJH=^[9 ?;voTD@sk9;9˲.!XR5 @R}V=5yAA6*(ka/w ֽ!!'9 ^qz0}QWCmb&9OJ. 463HHf]Wca%u<80ۯ <?U5CD?'~C@QG/t $&9EF ? A@@ A@9bKGD b pHYsodtIME ;wb.nzTXtRaw profile type app11xGb\9EXE-,vRdI̮JkΙ.XLɞe/Ur/)?q1͎[{7|Q oyߞL {~gWBŗHKOX=Ҽ)WzI?Kúz?ȯfZdGg/dy{Z%g=aX5uu:&ߝ5ȤТGRC49ڲ݇g|cƭ'Ozi~0Vo9F1Ťыca~}%lo_Q+{ݵQM\gOێu32>>ߌ++MvϚNraxu;1-co9mW <^ixg18XJm[9>&3_q28ia ?tofv{ՍZBX>b1h֕>DYaXo3ɳwt<giˏF`;n3̴SeO+otqX(kسgrerΞbu+ѝU]Z W0]ye }@A \}m.7O@^lg=NfZ1T%`7 s|G+ԖV߳X]L.^Nҫ ]`ܞFJ #9wˎGKǜM̱&{q?|*鍅Vz:ױ{]6COWgur0?v1" H]Nѵ{<1{-yԙ3Nbz}}Ҽ_荏h:a\<#g>qdC1?mx fȿ"@H.tUb~;7oG \ oDCsC tB$gjE2 6{h6MzKuinw:>;3%,mWLaVx&j8ʙnv;Yu 5kP^VK駌P&g1#cG86R@uX9 n8̰W s\ՈwbOB0vW;[ usʭp386@&uaPǩkal9kB1n9E`Vɹѐ[`Vp ?P1Ɏǭoi1-q?CڦzIFn @tsHR@!N웾BoL!,}Wq>(bZ8ZdF<ȽA+ْ8^x<24_&Խ`(8)j. !VLB%hqL@, phQ(,B~P|A|N΢reBmd]ZT.sFu%~_NZ97c;Gg_/dt~qn>7o&L]tjͿ~o>'&N3yw;^3-0#ZP TmtAH."~tĬG@P;H>aa<Ӫ274N=֊XTTu`f.#qqu2X#K ;jVD3Zun`fo *esv4ZN*jyQζ%By@RE F4/W p 2肅sm00 &qYJsOurᡴ2y+*iA*;FQT. J9om\'h-:I[kd%,KXla8ڇ8/a6a9 !qp$&0r"hTfÆ3"d]eg\k=6,a k9-  SB 3t8jr1qd+WqwQ+5Wiabqv#aAd“m-UW10i{㇧ET#ɣB j^V O` LA#{71PM8aCWx㵷9|BؗfiW 2BZV{E"Jd8>_Ue F^2P^0qpè2KVTt[^3ahX̵x0+Y6 j]ZS)8[Qo9,0Z%:D@I='Pq9p. mݻp%жԫCں' = ^3BPюL DFwf y@ ɕsE^iMRR} g>D׸_5J{֚Fշd1 {dmoǃJhkZ4A4k>ǘ)e:ʘ E[;q&H%,oD+G}w~>SЏM8a >Zqirr0H+F[@d^Xæҭ&ý0g`ˁdot]2UZvrdXtFZ(Q)"oGHvd<+͌ t!*Z / ZSR>i5B%#Qt0- ЗwX.8Ƣ=~!TU>o7I '@@/v`[EWsM͟'U7>J ^ P5\I5@l.&)"&Yay;eb 3F.G  yjz#Xeg,D MBp B@_11!p0;^'t[Va:4'*ȵ-cŲ>pd nJ. !9" *F器 Dn!JLVRjKC}r_9LCB<@+ZrZ!紑f4Q!bP:6DG XXqU8U#3l`-3SՓ u)wW0gΞK Ϗl6p|_̶8hU[lzt y݆ɥDŽH BqxP0La2*UZ-8ܵ ,OK|Ҟ"2w-W/5yS Qb֦/N)#5plϤ`^Xa<W?֥>mTqVZݠyPa/_ ͿӭMNV_MJP/eyi#n_Iz x}^\S৏pHc0ߏ׃axrϣXձ_6a{f#n N)t FEJӖS+d^YޑBI:_*VbT_ NU8N;c]gxOa%X@@ՐfJA,;~9l:Wma3Q D=*h|Ø];>H@)6dVi(K!MȦ' whpH x:Y.>EZ]K:6Ÿ}/^'?$~+2O~/'vI Q5X+GTYnRTb W^!AL)j''}tpawv]>wO c(R'x+OՊAh1F,0vQ>u7m&W9S`]'˯HƼ/R${;X*5>~ -u,wbl-1Ư=ߌyϨf̫P5go <730Y,~@qS R8o8$17Nc] X0 V}hspK{FxD Kņ{Zfj,:X;R' `jBK |0ATh[:*U!Yt0ׂIyMh)'U8UjIfD{D'`B6HOEm *JU?0&-_by@:qlȵ|p{>hDWhx:"ƒ! bKYCsLoq2rP  10x^p". Nyg&^6.VIM9D>=mX, }0 ?qœ XŢJE&S- ޖJLj Pwt>ՅkAN}UhG=|uNgAK&oNh-mqyDYRr@.OuV,gNm]N %Z sVu*#xe#0c`'u@D *Z,Bm*hd@j3Z i 'qug9 ,[hje1ˬD?J HXj=}w?z7⇧2F;?{Y}}|T쯪'5wbpv/MS^߇ >;[̿7>5Ǿjk ";hNBD6ς2 3/B/X^M{9f'ړ49"vsx:MoRNRzE殳]0 ( m7Yrp0F#Q?ȪuLn`/>T0]$AZSG&>y?zA.@Q_o̮5f90a4jk$6ۍ(pc{#1!!suip6d+@3fG){ 4 ``V@" 6{%VG{#{0ﭘc@eajT$Ə7 Z5<I*TuE'M4"nm&TmBFKm0܉5bIWE4C+`Um1t+ 8·Ʈoʑ\M;>`&35Z-HpsiJDdu/l6p#$[Lo:+M [0h` ?E^1CPօU5eeUYMWaԘbe2UE Xu2\a>K^v!aw؅e "vE<[S"3?Om}}6}xgB1_גKF 3*`ZgSͿo|OX2;O{VU SreO;hd:YQu0pI˞,!ZHv`iSB;YW5vh?dgp7 lԢꢳ ׇ4IwMs=bRPK Zr5[YuМf:tTUMCxN J|ڱھ˗0&/@%- 2strKCFĪI˲jvcM:$K?*`xu[0QbXTYeMB9-wPSQBk4n[K IE./uTڂ$H1ZܨRTؾ&eYk1<ݰ*a] 4"eA)sTyyQ mq,Bh :bdEp2da ($;53{R:'mяaDNG0N'ܖN+mQ/݈HXvühy@ ̈ę쨽q$]I mYV]rHEty7p co9rCE؆BLRLPwÑ'I8>H|X2\sM(6tio ݧE>#Pw'ɒdOLw+0o\tǚgcf^6)rfGvÞW&< G&|RL#|8׽8z( '&F[ 3*$uȚ@ZpKݨPy&0҇?'J|y mLމ¶K}" S2O$vz]n5v%ƣ{bKwtf%I: 4$umP}[F:&ã}x6aIXCP:Dڛ1L4J:]*:Ӆeh^Wtv98oY!x7?ƛ|WYm}x"ڭ\KsQ_L*|wܧ2B#~Io'S!tS|L|P{Uu^MUbe 64U~7 g{YO?:+TWYı|zSX$D-b`x , p52[2mF>~gW[xX^ ­55LemEIU.t_*B-x4bN-Q1n axD j7,Hn4 sEO"3u nLQɉĦpU|7ٯfGAhht,:"IqPBO^^EJiFIch\dxCKD" Um^OpUYlfW\v':*^0Ng@'Z- Y!! ¬{pP*e'Rlµ$ϙA|:xX$3T7 ]41NF˶ҽlNW#wƵ( `qS]T /«DGfQ$l)յC=a Hڑ;3C/c>:M'TW& D} G۾Vp</|%,ԙ{nawMc: Y8ȣ}//TU:2xRt "cb[*%#%4cp2!sēlT`l]άUGzF0e] Z?c#ŏ7kM_֨S$}M(Ne Zv=ގYA!'h6u(8s*NrZMᗱnUA ӱ7$ؿ@\QcȤNM]ZudZwv3UQ)Z#2'-հ`뻶]u 5#0nNۀ8ġ#vq $.VcsSk~5'g1{k{-[%MAMBAR⚮#3 ­W&Df`$: Tnk_zTXtRaw profile type exifJLRS3.Ss 3K33 0I1I100J4 6654 b0P2sA #d 4CIDATc````dd`dbbbb&fL&V66v (/"($,"* .!)%-#+'Ơo`hdlbj`naiemckgʐ_PXT\RPV^QYU]S[W?aS2L>csΛ`K8RS.iUVs΄ 01"S_=C%tEXtdate:create2024-01-25T21:10:59+00:00 $%tEXtdate:modify2024-01-25T21:10:59+00:00x tEXtexif:PixelPerUnitX08*tEXtexif:PixelPerUnitY09I@tEXtexif:PixelUnit1eWzTXtRaw profile type iptcx qV((OIR# .c #K D4d#T ˀHJ.tB5IENDB`bidict-0.23.1/docs/_static/favicon-32x32.png000066400000000000000000000335571456445164300203540ustar00rootroot00000000000000PNG  IHDR  cHRMz&u0`:pQ<bKGD pHYsodtIME ;wb.nzTXtRaw profile type app11xGb\9EXE-,vRdI̮JkΙ.XLɞe/Ur/)?q1͎[{7|Q oyߞL {~gWBŗHKOX=Ҽ)WzI?Kúz?ȯfZdGg/dy{Z%g=aX5uu:&ߝ5ȤТGRC49ڲ݇g|cƭ'Ozi~0Vo9F1Ťыca~}%lo_Q+{ݵQM\gOێu32>>ߌ++MvϚNraxu;1-co9mW <^ixg18XJm[9>&3_q28ia ?tofv{ՍZBX>b1h֕>DYaXo3ɳwt<giˏF`;n3̴SeO+otqX(kسgrerΞbu+ѝU]Z W0]ye }@A \}m.7O@^lg=NfZ1T%`7 s|G+ԖV߳X]L.^Nҫ ]`ܞFJ #9wˎGKǜM̱&{q?|*鍅Vz:ױ{]6COWgur0?v1" H]Nѵ{<1{-yԙ3Nbz}}Ҽ_荏h:a\<#g>qdC1?mx fȿ"@H.tUb~;7oG \ oDCsC tB$gjE2 6{h6MzKuinw:>;3%,mWLaVx&j8ʙnv;Yu 5kP^VK駌P&g1#cG86R@uX9 n8̰W s\ՈwbOB0vW;[ usʭp386@&uaPǩkal9kB1n9E`Vɹѐ[`Vp ?P1Ɏǭoi1-q?CڦzIFn @tsHR@!N웾BoL!,}Wq>(bZ8ZdF<ȽA+ْ8^x<24_&Խ`(8)j. !VLB%hqL@, phQ(,B~P|A|N΢reBmd]ZT.sFu%~_NZ97c;Gg_/dt~qn>7o&L]tjͿ~o>'&N3yw;^3-0#ZP TmtAH."~tĬG@P;H>aa<Ӫ274N=֊XTTu`f.#qqu2X#K ;jVD3Zun`fo *esv4ZN*jyQζ%By@RE F4/W p 2肅sm00 &qYJsOurᡴ2y+*iA*;FQT. J9om\'h-:I[kd%,KXla8ڇ8/a6a9 !qp$&0r"hTfÆ3"d]eg\k=6,a k9-  SB 3t8jr1qd+WqwQ+5Wiabqv#aAd“m-UW10i{㇧ET#ɣB j^V O` LA#{71PM8aCWx㵷9|BؗfiW 2BZV{E"Jd8>_Ue F^2P^0qpè2KVTt[^3ahX̵x0+Y6 j]ZS)8[Qo9,0Z%:D@I='Pq9p. mݻp%жԫCں' = ^3BPюL DFwf y@ ɕsE^iMRR} g>D׸_5J{֚Fշd1 {dmoǃJhkZ4A4k>ǘ)e:ʘ E[;q&H%,oD+G}w~>SЏM8a >Zqirr0H+F[@d^Xæҭ&ý0g`ˁdot]2UZvrdXtFZ(Q)"oGHvd<+͌ t!*Z / ZSR>i5B%#Qt0- ЗwX.8Ƣ=~!TU>o7I '@@/v`[EWsM͟'U7>J ^ P5\I5@l.&)"&Yay;eb 3F.G  yjz#Xeg,D MBp B@_11!p0;^'t[Va:4'*ȵ-cŲ>pd nJ. !9" *F器 Dn!JLVRjKC}r_9LCB<@+ZrZ!紑f4Q!bP:6DG XXqU8U#3l`-3SՓ u)wW0gΞK Ϗl6p|_̶8hU[lzt y݆ɥDŽH BqxP0La2*UZ-8ܵ ,OK|Ҟ"2w-W/5yS Qb֦/N)#5plϤ`^Xa<W?֥>mTqVZݠyPa/_ ͿӭMNV_MJP/eyi#n_Iz x}^\S৏pHc0ߏ׃axrϣXձ_6a{f#n N)t FEJӖS+d^YޑBI:_*VbT_ NU8N;c]gxOa%X@@ՐfJA,;~9l:Wma3Q D=*h|Ø];>H@)6dVi(K!MȦ' whpH x:Y.>EZ]K:6Ÿ}/^'?$~+2O~/'vI Q5X+GTYnRTb W^!AL)j''}tpawv]>wO c(R'x+OՊAh1F,0vQ>u7m&W9S`]'˯HƼ/R${;X*5>~ -u,wbl-1Ư=ߌyϨf̫P5go <730Y,~@qS R8o8$17Nc] X0 V}hspK{FxD Kņ{Zfj,:X;R' `jBK |0ATh[:*U!Yt0ׂIyMh)'U8UjIfD{D'`B6HOEm *JU?0&-_by@:qlȵ|p{>hDWhx:"ƒ! bKYCsLoq2rP  10x^p". Nyg&^6.VIM9D>=mX, }0 ?qœ XŢJE&S- ޖJLj Pwt>ՅkAN}UhG=|uNgAK&oNh-mqyDYRr@.OuV,gNm]N %Z sVu*#xe#0c`'u@D *Z,Bm*hd@j3Z i 'qug9 ,[hje1ˬD?J HXj=}w?z7⇧2F;?{Y}}|T쯪'5wbpv/MS^߇ >;[̿7>5Ǿjk ";hNBD6ς2 3/B/X^M{9f'ړ49"vsx:MoRNRzE殳]0 ( m7Yrp0F#Q?ȪuLn`/>T0]$AZSG&>y?zA.@Q_o̮5f90a4jk$6ۍ(pc{#1!!suip6d+@3fG){ 4 ``V@" 6{%VG{#{0ﭘc@eajT$Ə7 Z5<I*TuE'M4"nm&TmBFKm0܉5bIWE4C+`Um1t+ 8·Ʈoʑ\M;>`&35Z-HpsiJDdu/l6p#$[Lo:+M [0h` ?E^1CPօU5eeUYMWaԘbe2UE Xu2\a>K^v!aw؅e "vE<[S"3?Om}}6}xgB1_גKF 3*`ZgSͿo|OX2;O{VU SreO;hd:YQu0pI˞,!ZHv`iSB;YW5vh?dgp7 lԢꢳ ׇ4IwMs=bRPK Zr5[YuМf:tTUMCxN J|ڱھ˗0&/@%- 2strKCFĪI˲jvcM:$K?*`xu[0QbXTYeMB9-wPSQBk4n[K IE./uTڂ$H1ZܨRTؾ&eYk1<ݰ*a] 4"eA)sTyyQ mq,Bh :bdEp2da ($;53{R:'mяaDNG0N'ܖN+mQ/݈HXvühy@ ̈ę쨽q$]I mYV]rHEty7p co9rCE؆BLRLPwÑ'I8>H|X2\sM(6tio ݧE>#Pw'ɒdOLw+0o\tǚgcf^6)rfGvÞW&< G&|RL#|8׽8z( '&F[ 3*$uȚ@ZpKݨPy&0҇?'J|y mLމ¶K}" S2O$vz]n5v%ƣ{bKwtf%I: 4$umP}[F:&ã}x6aIXCP:Dڛ1L4J:]*:Ӆeh^Wtv98oY!x7?ƛ|WYm}x"ڭ\KsQ_L*|wܧ2B#~Io'S!tS|L|P{Uu^MUbe 64U~7 g{YO?:+TWYı|zSX$D-b`x , p52[2mF>~gW[xX^ ­55LemEIU.t_*B-x4bN-Q1n axD j7,Hn4 sEO"3u nLQɉĦpU|7ٯfGAhht,:"IqPBO^^EJiFIch\dxCKD" Um^OpUYlfW\v':*^0Ng@'Z- Y!! ¬{pP*e'Rlµ$ϙA|:xX$3T7 ]41NF˶ҽlNW#wƵ( `qS]T /«DGfQ$l)յC=a Hڑ;3C/c>:M'TW& D} G۾Vp</|%,ԙ{nawMc: Y8ȣ}//TU:2xRt "cb[*%#%4cp2!sēlT`l]άUGzF0e] Z?c#ŏ7kM_֨S$}M(Ne Zv=ގYA!'h6u(8s*NrZMᗱnUA ӱ7$ؿ@\QcȤNM]ZudZwv3UQ)Z#2'-հ`뻶]u 5#0nNۀ8ġ#vq $.VcsSk~5'g1{k{-[%MAMBAR⚮#3 ­W&Df`$: Tnk_zTXtRaw profile type exifJLRS3.Ss 3K33 0I1I100J4 6654 b0P2sA #d 4CIDATHǵVklT}ך58$S84M򀤐UڪUҦ$?*IRURT%QD -J Uy`ڰݻ]lU%z{{c朏^0 |}}KKUa ՟+̘E:s"".ӜLD _=l8ՐTsSѻk+K `{}L39ʮTAM-r"@\*3jP݈86\.*tyj 0!lC߭k/-|rʅ@ p\G6ěѨJOsz"HtXׂ?{onvf[&n0RݢuG#kOLJs:͊w5'&JВ.fXhO@2SL2yxiN^Oϑeq}c15Uo ZTGKoD:&ơ=%}W4J ]wyS1wY!15*d=ؒ_J!1h{wj3l})H@ "B,3xuV b0kbcoljA4?|{͞"h9"dNBid;Jm{z^z&e\Gu|!`L1#VJ aۧpGJt/ <_>a5IH2o)ep\ C>0p81ft4R{]S.&Ry/L^}7;OjG_Gc["";SI tj~k~KY=.\0I /?vJNMZ ޷79vGFM].=Y+'CNTLcϏ:&=3$2A} џ Q)Whs.7RNN7"ȶzyo@9$OC=f%rdOR kwlz8UqA>|[<׶g_VwwXB@R%ަVk!.fwaK6u+MWL7%Kb"ɳ`vRﲢ"kA&SmMVn $Inhl5OC@~ `(94trc = < = = < < < < > ? @ A A A A A A A A @ ? > > ? ? @ @ @ @ ? ? ? ? @@A A A ? @@@A@? < D\u"%&%&~&|%lR? > @ A A A A A @ ? > = ; 8>B6Q[7T^6T^6T^6S^5S^6NW;77=? ? ? @ A A ? @@AA> Ar"<LQRRRRRRPH3Y= ? A A A A @ > ;".l 3Ub>> ? @ A ? @@A ? G5TYXXXXXYXXXYYMr#= ? A A @ > 933&= ? ? A ? @@@ B8]^^__________^_Vi!< @ @ > ;%"#=? @ ? @@> w'`dddeefggggfeeeefNI> ? >&t(_p> ? ? @?FOmkllllbSQQUgmmmmmks)< ? 9%" 9? ? @?\ittttudk%EEEH1mvuuuwA= @ 1EI%k{:;;=8)$/AE@ ? ?= l'w}}}|}E< ? A A @ EZ~~~M= ? -U[:> ? ? ? @ .MS(Zc@ ? @= m*A< ? @ ? > CaM= ? 1OR9> ? ? ? ?/QX )\c@ ? @>`#^AMTSTB~9< ? 93.!+jv= ;6 5279!0GJ? ? @?Jut5s"IazՀQ> ? >4*((&#'p5''%^q!:% ? ? @@> GCy'Cc@> @ @ @ >50:630.+$#/NW,FR ))(4}> ? ? @@?Jlvy9,Lo KA@ A A A @ ?>:C?;973.''0BH(Qb+7>%> @ ? @@A? Nap t4/CJb+> ? @ A A A @ > >"ClmGE@>;91()|0>E"^y >0,@ @ A ? @@A A ? @h2t  l :CR X= @ A A @? <"t%i9??Gz|KHDB?;2(*l}09A;? ?A A ? AAA A > y8f:r=  g'= @A A @ ? :+/*-`m=B?KOMHDA;/%0DO? ? A A ? @@A > e+o z8wE L : = ? @ ? ?5DJ6II=12W]AIEPRKGE@7(3?D? @ A ? @@@Cq  b T|OĤ -'! "#l~=x8L< /BG-OU2lwLWZ\ZK64NQC\[ROJHD;/<> @ ? A@> [&  V :Ml/ ]#ʷDX^a^URHp9<'"Yosz~gKosFNKUOMJE;;B?> @ ? @@= t?   Ɛ Ja l'[? J c;%hB+hA+hA*hB)iB)e<%HAT5*[A6\A6\A6[A7ZA6Z@6Z@6Z@6Z@6YA8P80?KOPOLE?ln? @ ? @@= O  o < > ? ? A @ ? > > ? > > ? A @ ? > > > > > > > > > = > > AZ[ESVUOC> @? @@= }M B= ? ? ? ? @ @ A A A A A A A A A A A A @ ? ? ? ? ? ? = 8v7[`_ZIyz> @? @@> f7  SYRRRRH> ? A A A A A A A @ @ @? > : 76665#*W`?kjhbIWT? @ ? @@@I˰ '##$ ߂r@D< = = > ?@@ @ ? ? = *IO5lvusgC'? A ? @@@> |X1/,,..++,//(Z p;p<p;`+A>7'#3??3@?3?=/\c3Q`a_`al}~Zvt? @ A ? @@@@C$DA<::;:955=:3555._- =EQTT_ÃxvpC? A A ? @@@@?Fz/[`ZVTSRAAUVs'? @AN GAH!BBA _RJwF? @ A A ? A@@A A@Af?Sx}II ? A A @ @ A @ A A A CtibͺۑbQHB? @ A A A ? @@@AA A A ? @R%gA,tP9uR;tR;uS @ A A A A A ? @@AA A A A A A @ > = = = = > ? A A A A A A A A A A A A A A @ > > > > > > @ @ A A A A A A A ? @@A A A BA A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A BBA A A ? @ABA A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A BBAAA ? @AAA A BA A A A BA A A A A A A A A A A A A A A A A A A A A A A A BA A A A A BBBAA A ? AAABBBBBA BBA A A A A A A A A A A A A A A A A A A A A A A A A A A A A A AABB A A ? AABBBBBBBA BBA A BBAAA A BA A A A A A A A A A A A A A A A BBBA ABBBBA ? @ABBBBAAAA BBBA BBBBBA A A A A A A A A A A A A A A A A A BA A A A BA BBA ? @AABBBABBBBBBA BBBBBBBBAA A A A BA A A ABBA A A A A A A A BA AAA @ AAAABBA BBBBBBA AABBBBBAAA A A A AA AA@ABA A BBBA A A BA A@@A A A A A A A A A A A A A A A A A A A A A A A A A A A @ @ A A A @ A A A A A A A A A A A A A @ @ A ( @ ? @ A A A A A A A A A A A A A A A A A A A A A A A A A A A A A @ @A A A AA A A A A A A A A A A A A A A A A A A A A A A A A A @ @@A A AA A A A A A A A A A A A A A A A A A A A A A A A A A @ @@A A A A A A A A A A A A A A A A A A A A A A A A A A A A A @ AAA A A A A A A A A A A A A A A A A A A A A A A A A A A A A @ @AA A ? = > > > > = = ? A A A A A A @ ? ? @ A A @ @ @ @ A A @ @@@> P~%13331'S> @ A A @ ? ; 0O[(n(t(t's(p/Ve;&"? @ A @ @@? t#HUWXXXWVKz&? @ A ? 5@F0Q]?@ @ @> m"W``abbba`aZq%= @ 701 0KU? @ ?GNkjjhYUWfkklPE? {:? >]lwvvJMIK;uxxlW; w:; <%ix1:;@ = d%|{2; = : g&~xX: /MT?? @3?B /GI@ >XQS}*9irH>, $8#3+,(J[553? ?CgL<c?> @ =?=51-(&*dsr #&-= ? @? ]&ͅzFHsՈ KB@ A @ @IFA>:4.+(bt+=?<@ @ @A ? Scߡ y FFd#= A A @ 7&%.fv?yGE@:1,%^u5/3@ A @ @A @ ?lR t  R ; ? @ ?4S]236qzEMJC:.2S\? A @ @? [%u F ## #r8U;6AB r WA H@ AEFFFCBEEEDDDDABMLKTQF?@ @> sjD@ @ @ = @ A A A A A @ @ ? > = > <1muDc`N? @ @= xN$!xghc=D> > ? @ ? > <,IN +csnOec? @ @@ I%500.-..$tWV[(<"\^?v}PacgjzpD @ @ @@> W, G> ? @ A @ @ Cqd\Ť”pldI!? A A @ @@A A A > BH I I F? @ A A A A A A A @ EH H G B? @ A A A @ @AA A A A A A A A A A A A A A A A A A A A A A A A A A BAA @ @AA A A A A A A A A A A A A A A A A A A A A A A A A A BBA @ AABBBABA A A A A A A A A A A A A A A A A A A A A BBA @ ABBBAABBA BBBA A A A A A A A A A A A A BA A BBB@ @ABBBBBBBBBBBBAAA A A A AAA A A A A A B AA@ AA A A A A A A A A A A A A A A A A A A AAA A A A A A A A @@ ( HQu%Uؑ 364wog_5v;A DROH!I]1G``9B> ? j;> ? A CB['EFE T wJ<58Vq2OSH؎08<> o<zZKH`* ~A} F?C@@oGD !.JO_. a&߳T5+R(MovS*? @ A @9[^C= MJYXCa9ks 9&$?(!=~'?}0@A B5=<\id@</GQȓh4?Zy5[ AB@φ9JeOEco35RXH&bmfWd`> ڻ8? W@ ^ A @WBD 6AA.u ] tTUO=9'!qkc;B/'? @ */akA 6@oz? Qcd!"TP>xx a <q`Tm:S]3Z-v\il)0bW8k{DAeI6o(_~zsJ%B ,L$[.YyGn ^2|+RO@7K4tF&hV9wC 5=Ug*rj?';1EX Ncp/Mfu#}HdOOfdfdfdfdddddcbidict-0.23.1/docs/_static/mstile-150x150.png000066400000000000000000000406071456445164300203600ustar00rootroot00000000000000PNG  IHDRxgAMA a cHRMz&u0`:pQ<bKGDtIME ;wb@(IDATxyeUy{(F@!JcPTc4&w%{Gxt&:bLA$HSPP@}k1WާFꜽW3:k~70 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 xvgud2-?%U}^"<nQ{'}}DU-^44{o &8MU~ruiGv4qiT!tN "z>yC[Ծ=!YKhWu P&4 1cLXCY%~~(w#99q8gy7 +,q鹎* ɂUÿQ|1y׾6Aka؃Ǐ7-:5;J˨gsY/pwmO*X,Y>*q/qYT9m7L8X=>e9Lp$Rg)u3KTΖW<#1=[U&\7&8|XJiw DG.G5;,poO*3=J{Ω9\?&8$0永6 GEBQC\~ w. q;AՃDNq!_$CT7UӶIDZ+ؓ Y;C˜Xx٩;I劔4 9a|0ǞN?u'͊+XbF=\kVHs S&ql|r;XܑrNL 㼗^Vרg7XbUhڵ3yͬlZ1Z? # ÍTA zK00u=ӳ(ul' >~-\STcp^HI܎/mXa/q|_Gw·b]<y,hdj6tszS;T^+?񯹈dtWE ywGP˧x]w?غU8tZDjZ7k4Ucs׳R,=~."sYyKւ ix?`;Ga` J+)[3ՉTH ?}ggϞ:#T8Ǣh K{TaSFxϽ|֮"Q%!Th-GPq _1Rg>N[8W1+§fWǯ%؁v j}Hc:[5'7 4woC}'o5wnu4"NJ{z1p0s ^AjWnz?w]Hn i&8El79O K}Z=9˜wњcFYfD70Kycww>4'y+bUl4h)Th2_>]|;6 _uƣ֌366ԁ|αL0==KGRXz\~Z~CgWD~` dhOǃGO.\a@@疙21J mkqik,RU"$$)i(Z;6n_'yiԙV+xUNU;%崙hz;yOc|3n3 i x}an'ȆpԲA~7^q I4[,]z&7{($p1<sv"Nr6 1zVfYQN{_RQݟg\ B貀{)k9 KU=ܻM[ZWЖ"<;=[$C0-Bҙd InxDVm~띻檝Xሢ:"ŕC{N-?\4صo Mx[ӦThQL} \J|ED$NBfe(33 6);$ķYuP69m )t3ɽ[LԫSY1Rl? ёcW|hkOY4V+l! pdtwhjZdOxjlʲjg?LΆ,>䠘3Ux n8~VBysqR .[˒(e7of=~1uuv| Oږ84GHZdd/:jmd~@C_׾)z&8@1#srbW &ЀRDgsV rބ]{&7>L .~;~d#Lٷ\b9-{"oW5~m8)4m/}VnrH߇n}0rϢr"g,[C9?} _0a0&8Utz=c%qAPd,TUqD.ajg =p dn #bڡNKb^8ɻ7?fOd7#}hVյ"mkW\p4'et(AsBj"Lmm<A݂O!/GĥGo}(n/3Mg隔88Lp,J5.}/ wq$pp/MP̟HI;?MMpƋjdtê/hV/G68lƂ!j'd:Fѧ:wo!캄-[lh%HR]kYyJ)\@| #ApC৑d6I'&滴9ḈZLPq,|!D1xQ'l4aA=>V! SމW)+',Ylɢ(౧{WOBHbm"4 ݡrj8e o(~V?٘8D9qH/O B+❠3IvmN(4zQ+7ZŸ"tm kV1W BçieCpOS^$az4I\ 4^:<8ȩr% Cġx4"XDQ|p &8@t5gi82TôyG*`2C}]rBaؾ:ׂw%X|wFA*9NծeQyB^ߦ m"J)kn@,×Vɂ!*3I0Sǡ`c ]93 :΃Hҗ!y{84${P;T']3PNL|C4 ?زsL._~N.f1T͉y; Eߗϕq)<6w`^0c'ei8صKh6-&pX9/fǷ*B{.t^/L$R[FӜE.ڭ·Ѩ4F,%Np>qUCCCDQT$oR APMYe-ShǞSQ/B_@=j1-7 ۬2RS^;FU>МMmǨW^vVsNK hZX2y& Y 6zZm)Ж1pDHF <2,AM'GgS?~Oww6T!Tjs NJuy/gkjy{!t*ef@ܑj5 O| xU>{Ss5Be&R,H%4Thiw-O=كH9HD $F}wΙj&i ؍=NH~r#z)*iU\h_ Z[G{&LD={#ٿ AYq*^K9孯v)!=Gݠ%^6't|wsDh=B4|P(à6ǑAA -ƶG/ xGy%PЪ *hUEDEIٹC!;+9 K[`E. ځfAʲ 1's~I>~q_ ;7Dq&,P(5Qs>v Z̈́v|CIeǃJ2S2<ȮD? Å$qu5xj%fR(Ȭ~Gţ$;#;Ghre|Ch2gF^9Ric^x|cJt- `, !Z4Qve 1k֑~YJ)*k&E~9}W$a797Sc970p<] cB^oT Bәv.Q<3O|d[waoZM[7=&ʡ$>LG .dj:vRcld ZVy^~t/i6?^W_YcE/o??6S kNTH;*>ڸE3i_6||ZzCxx+hdH zbYɠ4wO魍U87 C$y8͏Ѹ嫸yOxADkP|JԚ< 5yCoc4Hii6),3RS۩tٿQj Cˡy8T&Svs )і'+AibE#'EvO`,;ZE3"&4*3>Gi3:kNg_;JMk'ƒG߽qdxQ8ZnVk{h=w+n/> Ϻcj\QaN;Vi>DOD_L *WaĄ 0o?ZFui %PtT1POu ׽'SOm4wӺFrO!QS jz%bEHej+ZZIEΊ:ħbYIuha`jotv_-#=~&rr%1oz}1!h>PݾGPQ>1ՇG9jW)5B}L B ds)ed~ 4`ٿH\=OueBP0{wBU˽8ǩCpܩy 3@{?DB.]X-^zUO.BKAHHgO"UH<6ʞnʣ>];PbE.5R߅25=sV(" )t.Y 7O~/8^wxScL6iV>h͢&B5ZH}o;[Y8tLp,dnd{'/jg4?}hqиr^]GKM4HP~tY.UΊ' p4AaA]'{2ؾ~Nq!MUAWZG[{T|{O޸{6ъ_7lPIR1lvYBBteӣ1raOu0FJaTNH?N|gx2ec H2 yR 03?򘫤+i@SXѵ@Hcdt28Ltm伃ˮ}Q=bf;GGFN#%GKW3-*TVhiމF<0 ,_w(wm:pgi4&8D> n|+.½ SHZZăΪW")wipDqlx-_y'H}xܺg#NI`Ӓ)"[Gy˹Խ^aJBZ:ŷǿ<7fLA%/c: :K4 ܫ-:(L8dtz/]3ϥ@Gc^\LڌTFj(Ѷ`I?̕e!C3Hv?߷12X~r` VqyɞI}1s3=KƘVIHO+.}kWplyx.:{7'?#![\*8`$&UqϙxHZ?HWD2>UdzwY!݃q -7oz"s/C^;reB4KDcꏐ|Is*F ?,Yi٠$q5] ^~Wk :RZPWit4nkg!NZ佗]S856qc̗^,i[L8d}s_oѭ}xZKwr}he&FG+Vvd?P=Ƌ 9~60&UqAD/' }4^o$B lQ&tD0t|uU&H<|EGL7WGfAQ8\@n>Go'Y$ߪ/?ǯgTg(R;|"MZFytj(ꚸIllFWG!q6=|n. c'PY'$Ɔ1\>lMǎvkr|oqǏj!ʕYr_``6θvJf!r4g;<8!(RV_ h1!,yVdC ZQE]Mi}r.)[LW4C|K^z)zC>̱91Y"sϒ2KZlZNM#BHe/O :C\fd`d+YA$[|d%>PO+mmB%O Cm.8{ӛo3r,3\p4d!h C@&AH|GdS?Ohrډ{ 8k.<3"3ket8al1Aޡ &hº kHm;4NٽWdg{li]=71Į(򝭵^3lXJ5L۠#+eg=e]@hm ڠ1m1>&ac r`)$T{n%BS~dk1L$ [LLj-Kv* #1# oU!4DW*ΝK?~:L (/#m M? S8s)%en Sʣj +fBI@j5i(_R(Vsl]-ڝx lfAv\$JH=Z@_X0*U)M+q/`y-:4- Jgv ])Rg| [q#%"JJv}m?H|YvL7芴( Yo()Se⒉&,FYBJ .K,@DZ'QkTD hzN9h(*}a ";a!" >Ds!N$5 RjKg:0>SB Qg6>Ҧt9= ś)I IcLp,\XT<2/LW<gU~Wtܲ[E]3DէFB%%(NqIKz(8K-P<;MU$u*k9mTq>70I5 9׎$Q$D+d?{FN .csꑱ5S^zɖ/]=`洦*w*e  g٨i!aÖ]WNQs"$V TRU^rJ7@pzEŎwڜ)w6uul4ͪġ{6!9&i~;%CD/I_$3S!K(LW"/x$]ޣϛ nv?,2Hɭ ⶣF-kHsInTF(>dԅ53,SYvht"ZD0V A4BbR^vZ 5˩GO1֘Fb#:gv Q'E9HLp,)fw/p֓JeTBᵆk) tH,(2QJ)Mw&Cwco`瞶;$v,uzLEhT~ԘU-NZ'gU? B/V?x\.2r9Ttd烸g~>Ԏ]%_PSMBU42 Z>O)p#:3Hc/:¶cAKIa]hIױŲ1mc4ے'A}'"۽$)ڔ =hrSc8x DIy^sI-I'/lTH$IM-H= mO28PE+-" ^K[~, [>!NktnfT&%RێSH+kc{, % WJӰG;略P 3ɴTkc/OΕ-3U:bv>i*"[/uݙm!.툰896RIYd?DcÖ&mv-g, PI.VǷIh&f,H<*\&nӹ_DZ=LMN}(w/4Uu=[0*T=c>kM˹f."ϵ]즙d 3AT17$Юta*Q5eQXU?\)g'V%˗˓ IJ6GvhT%L.D:7slq} ˍdC)z ՗-n&MmōGlŽ,#We+9Ut: W4B+;:kF; .#qZ*L ySVsI$I;2y{n)_[{R#$'oz0٤tFz?d?yXE "Mȁc,i5!!"9¢%j  ~!P2AQ>oKkq~7Ii},C{ ӉIO\G"TPvpf%H SLty:7]jLZ>O {&I Wkj&8 2燊ތ1B!E*ѱIkeB9wQ4!s?'H|OeY})߫ص?{[}}\dhV:6Ɨ| hƜt \seodSVlԇc+H=JIxQ!&u{(AnrYs?ǂY^g#U6iv1#q0p\Xq:r"R@]޹#s͑^qFa}n|Xc虧 % ٚŵ'HXq}\ya9'Njh?,"3EBe=J5'~dpۃ\f^FLp,=%8:u,1U%1EM D %2x4n`ڿ !,ERr3U][܁NmOmAgwE sPJM;ښ}'0]~֭N?ƋvUȠ[O'>X+Ni'ʞ'6 = >mg8̒N"ϫVBY@A;}3œQ WRD'˻FY u*mJ{,/8-DKP- UeK4O=wAUևp&8͎=m7 :7|e\GsԊçe  6s{/Z]PJ|7=v@>z̢I# ߨfdcQt\cl鵸gqJv{Hcc!-*߁,BkudϟUգ,I@  (7E]yqrO̽0XhKJ2j^4Y4sVa0cHY`eK0^`ƱhzIAlnㅅi W鍚a0cAL(F'&8Bݼ 3_3&8 Created by potrace 1.14, written by Peter Selinger 2001-2017 bidict-0.23.1/docs/_static/site.webmanifest000066400000000000000000000006521456445164300206220ustar00rootroot00000000000000{ "name": "", "short_name": "", "icons": [ { "src": "/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" }, { "src": "/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" } ], "theme_color": "#ffffff", "background_color": "#ffffff", "display": "standalone" } bidict-0.23.1/docs/addendum.rst000066400000000000000000000172761456445164300163270ustar00rootroot00000000000000Addendum ======== Performance ----------- Bidict is written to be as performant as possible without sacrificing other important goals, such as safety, portability, and maintainability. In general, using a bidict to maintain a bidirectional mapping should exhibit about the same performance as keeping two mutually-inverse one-directional mappings in sync manually. The test suite includes benchmarks so that bidict's performance can be continuously measured and improved. If you spot an opportunity to improve bidict's performance further, please don't hesitate to :doc:`file an issue or submit a pull request `. ``bidict`` Avoids Reference Cycles ---------------------------------- A careful reader might notice the following... .. 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 does not occur, 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.BidictBase.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:: >>> {1: int, 1.0: float, 1+0j: complex, True: bool} {1: } >>> 1+0j 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 bidict's behavior will match :class:`dict`'s on whatever runtime you're using. See e.g. `these docs `__ for more info (search the page for "nan"). Simultaneous Assignment ^^^^^^^^^^^^^^^^^^^^^^^ :class:`~bidict.bidict`\s may behave differently from dicts with respect to so-called "simultaneous assignment". Consider the following: .. doctest:: >>> m = {'a': 'a', 'b': 'b'} >>> m['a'], m['b'] = m['b'], m['a'] # swap two values >>> m {'a': 'b', 'b': 'a'} With a :class:`~bidict.bidict`, simultaneous assignment cannot be used to swap two values in this way: .. doctest:: >>> m = bidict({'a': 'a', 'b': 'b'}) >>> m['a'], m['b'] = m['b'], m['a'] Traceback (most recent call last): ... bidict.KeyAndValueDuplicationError: ('a', 'b') This is because "simultaneous" assignments like the above are `by definition `__ just syntax sugar for: .. code-block:: python # desugaring: m['a'], m['b'] = m['b'], m['a'] tmp = (m['b'], m['a']) m['a'] = tmp[0] m['b'] = tmp[1] and so the intermediate ``m['a'] = tmp[0]`` assignment raises :class:`~bidict.KeyAndValueDuplicationError` before the second half of the swap assignment has a chance to run. For a working alternative, you can write: .. doctest:: >>> m.forceupdate({m['a']: m['b'], m['b']: m['a']}) >>> m bidict({'a': 'b', 'b': 'a'}) ---- For more in this vein, check out :doc:`learning-from-bidict`. bidict-0.23.1/docs/api.rst000066400000000000000000000011521456445164300153010ustar00rootroot00000000000000API === This page contains auto-generated documentation from the bidict source code. bidict ------ .. automodule:: bidict :members: :imported-members: :member-order: bysource :special-members: :show-inheritance: :undoc-members: .. autoattribute:: bidict.BidictBase._fwdm_cls .. autoattribute:: bidict.BidictBase._invm_cls .. 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 .. attribute:: __version__ The version of bidict represented as a string. bidict-0.23.1/docs/basic-usage.rst000066400000000000000000000267321456445164300167260ustar00rootroot00000000000000Basic Usage ----------- Let's return to the example from the :doc:`intro`: .. 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 bidict({'hydrogen': 'H'}) >>> element_by_symbol.inverse['helium'] = 'He' >>> element_by_symbol bidict({'H': 'hydrogen', 'He': 'helium'}) >>> del element_by_symbol.inverse['hydrogen'] >>> element_by_symbol bidict({'He': 'helium'}) Note you can also use :attr:`~bidict.BidictBase.inv` as a shortcut for :attr:`~bidict.BidictBase.inverse`: .. doctest:: >>> element_by_symbol.inv bidict({'helium': 'He'}) Both a :class:`bidict.bidict` and its inverse support the entire :class:`collections.abc.MutableMapping` interface: .. doctest:: >>> 'C' in element_by_symbol False >>> element_by_symbol.get('C', 'missing') 'missing' >>> 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' The inverse is automatically kept up-to-date. Referencing a :class:`~bidict.bidict`'s inverse is always a constant-time operation; the inverse is not computed on demand. 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): ... bidict.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.MutableBidict.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): ... bidict.ValueDuplicationError: 1 >>> b = bidict({'one': 1}) >>> b.update({'uno': 1}) Traceback (most recent call last): ... bidict.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.MutableBidict.forceput` (mentioned above) and :meth:`~bidict.MutableBidict.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.MutableBidict.put` and :meth:`~bidict.MutableBidict.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:: >>> b = bidict({1: 'one'}) >>> b.put(1, 'uno', OnDup(key=RAISE)) Traceback (most recent call last): ... bidict.KeyDuplicationError: 1 >>> 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.MutableBidict.put` and :meth:`~bidict.MutableBidict.putall` will use :attr:`~bidict.ON_DUP_RAISE`, providing stricter-by-default alternatives to :meth:`~bidict.MutableBidict.__setitem__` and :meth:`~bidict.MutableBidict.update`. (These defaults complement the looser alternatives provided by :meth:`~bidict.MutableBidict.forceput` and :meth:`~bidict.MutableBidict.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. For example: .. code-block:: python b.putall([(1, -1), (2, -2), (1, -2)], on_dup=OnDup(...)) Here, the third item we're trying to insert, (1, -2), duplicates the key of the first item we're passing, (1, -1), and the value of the second item we're passing, (2, -2). Keep in mind, the :class:`~bidict.OnDup` may specify one :class:`~bidict.OnDupAction` for :attr:`key duplication ` and a different :class:`~bidict.OnDupAction` for :attr:`value duplication `. In the case of a key and value duplication, the :class:`~bidict.OnDupAction` for :attr:`value duplication ` takes precedence: .. doctest:: >>> on_dup = OnDup(key=DROP_OLD, val=RAISE) >>> b.putall([(1, -1), (2, -2), (1, -2)], on_dup=on_dup) Traceback (most recent call last): ... bidict.KeyAndValueDuplicationError: (1, -2) Note that repeated insertions of the same item are construed as a no-op and will not raise, no matter what :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. >>> b bidict({1: 'one', 2: 'two'}) See the :ref:`extending:\`\`YoloBidict\`\` Recipe` for another way to customize this behavior. Collapsing Overwrites +++++++++++++++++++++ When setting an item 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:: >>> b = bidict({1: -1, 2: -2, 3: -3, 4: -4}) >>> b.forceput(2, -4) # item with duplicated value, namely (4, -4), is dropped >>> b # and the item with duplicated key, (2, -2), is updated in place: bidict({1: -1, 2: -4, 3: -3}) >>> # (2, -4) took the place of (2, -2), not (4, -4) >>> # Another example: >>> b = bidict({1: -1, 2: -2, 3: -3, 4: -4}) # as before >>> b.forceput(3, -1) >>> b bidict({2: -2, 3: -1, 4: -4}) >>> # (3, -1) took the place of (3, -3), not (1, -1) 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): ... bidict.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.MutableBidict.update`, :meth:`~bidict.MutableBidict.forceupdate`, or :meth:`~bidict.MutableBidict.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. For example, let's try calling `~bidict.MutableBidict.forceupdate` with a list of three items that duplicate some keys and values already in an initial bidict: .. doctest:: >>> b = bidict({0: 0, 1: 2}) >>> b.forceupdate({ ... 2: 0, # (2, 0) overwrites (0, 0) -> bidict({2: 0, 1: 2}) ... 0: 1, # (0, 1) is added -> bidict({2: 0, 1: 2, 0: 1}) ... 0: 0, # (0, 0) overwrites (0, 1) and (2, 0) -> bidict({1: 2, 0: 0}) ... }) >>> b bidict({1: 2, 0: 0}) Now let's do the exact same thing, but with a different order of the items that we pass to :meth:`~bidict.MutableBidict.forceupdate`: .. doctest:: >>> b = bidict({0: 0, 1: 2}) # as above >>> b.forceupdate({ ... # same items as above, different order: ... 0: 1, # (0, 1) overwrites (0, 0) -> bidict({0: 1, 1: 2}) ... 0: 0, # (0, 0) overwrites (0, 1) -> bidict({0: 0, 1: 2}) ... 2: 0, # (2, 0) overwrites (0, 0) -> bidict({1: 2, 2: 0}) ... }) >>> b # different items! bidict({1: 2, 2: 0}) Of course, if you try to initialize or update a bidict with an iterable that yields items in a nondeterministic order, the results will vary accordingly. .. [#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: .. doctest:: >>> dict(bidict(a=1)) {'a': 1} >>> bidict(dict(a=1)) bidict({'a': 1}) (Just remember that if there were any :ref:`duplicate values ` in the dict passed to :class:`~bidict.bidict`, it would trigger a :class:`~bidict.ValueDuplicationError`.) See the :ref:`other-bidict-types:Polymorphism` section for more interoperability documentation. ---- Proceed to :doc:`other-bidict-types` for documentation on the remaining bidict variants. bidict-0.23.1/docs/changelog.rst000077700000000000000000000000001456445164300207072../CHANGELOG.rstustar00rootroot00000000000000bidict-0.23.1/docs/code-of-conduct.rst000077700000000000000000000000001456445164300227172../CODE_OF_CONDUCT.rstustar00rootroot00000000000000bidict-0.23.1/docs/conf.py000066400000000000000000000212761456445164300153060ustar00rootroot00000000000000# Copyright 2009-2024 Joshua Bronson. All rights reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # 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.""" # Prefer having to run `pip install .` to get bidict on PYTHONPATH over the # typical `sys.path.insert(0, os.path.abspath('..'))` hack so that we avoid the # `bidict` module incorrectly importing as a _frozen_importlib_external.NamespaceLoader # object, which doesn't allow accessing e.g. the .metadata submodule. import bidict # -- General configuration ------------------------------------------------ # suppress_warnings = [ # ] # 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 = [ 'sphinx.ext.autodoc', 'sphinx.ext.autosectionlabel', 'sphinx.ext.coverage', 'sphinx.ext.doctest', 'sphinx.ext.extlinks', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode', ] try: import sphinx_copybutton # noqa: F401 except ImportError: pass else: extensions.append('sphinx_copybutton') intersphinx_mapping = {'python': ('https://docs.python.org/3', None)} # 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.metadata.__author__['name'] copyright = bidict.metadata.__copyright__.lstrip('© ') # noqa: A001 # 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 = 'colorful' # 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 = 'furo' # 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. # https://pradyunsg.me/furo/customisation/#theme-options # html_theme_options = dict( # ) # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". html_title = 'bidict' # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = 'bidict' # 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 = [] # https://docs.readthedocs.io/en/stable/guides/adding-custom-css.html#adding-custom-css-or-javascript-to-sphinx-documentation # html_css_files = ['custom.css'] html_js_files = ['custom.js'] # 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. # https://pradyunsg.me/furo/customisation/sidebar/#using-html-sidebars html_sidebars = { '**': [ 'sidebar/brand.html', 'sidebar/search.html', 'sidebar/scroll-start.html', 'sidebar/navigation.html', 'sidebar/scroll-end.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' # https://www.sphinx-doc.org/en/master/usage/extensions/extlinks.html extlinks = { 'issue': ('https://github.com/jab/bidict/issues/%s', '#%s'), } # 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 r'https://gitpod\.io/#.*', # linkcheck complains about anchor links on this site # alternative links for readers on GitHub (which don't work on readthedocs.io): r'docs/learning-from-bidict\.rst', r'CHANGELOG\.rst', r'docs/intro\.rst', r'CONTRIBUTING\.rst', r'CODE_OF_CONDUCT\.rst', ] linkcheck_timeout = 10 # 5s default too low nitpick_ignore_regex = [ # work around https://github.com/sphinx-doc/sphinx/issues/10974 ('py:(class|obj)', r'(bidict\._typing\.)?(KT|VT|DT|Maplike|MapOrItems|MissingT)'), ('py:class', r'(bidict\._base\.)?BT'), ('py:class', r't\.(Any|ClassVar|MutableMapping)'), ] # 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' # pytest-sphinx does not support doctest_global_setup. We use conftest.py instead. # Ref: https://github.com/thisch/pytest-sphinx/issues/5#issuecomment-618072237 # doctest_global_setup = """""" # https://sphinx-copybutton.readthedocs.io/en/latest/#strip-and-configure-input-prompts-for-code-cells copybutton_prompt_text = '>>> ' bidict-0.23.1/docs/conftest.py000066400000000000000000000016241456445164300162010ustar00rootroot00000000000000# Copyright 2009-2024 Joshua Bronson. All rights reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. from __future__ import annotations import sys import typing as t from collections.abc import Mapping from collections.abc import MutableMapping import pytest import bidict # https://github.com/thisch/pytest-sphinx/issues/5#issuecomment-618072237 @pytest.fixture(autouse=True) def _add_doctest_globals(doctest_namespace: t.MutableMapping[str, t.Any]) -> None: doctest_namespace['Mapping'] = Mapping doctest_namespace['MutableMapping'] = MutableMapping doctest_namespace['pypy'] = sys.implementation.name == 'pypy' doctest_namespace['sys'] = sys doctest_namespace.update(i for i in vars(bidict).items() if not i[0].startswith('_')) bidict-0.23.1/docs/contributors-guide.rst000077700000000000000000000000001456445164300232302../CONTRIBUTING.rstustar00rootroot00000000000000bidict-0.23.1/docs/extending.rst000066400000000000000000000213741456445164300165250ustar00rootroot00000000000000Extending ``bidict`` -------------------- 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.BidictBase.on_dup` behavior (for :meth:`~bidict.BidictBase.__init__`, :meth:`~bidict.MutableBidict.__setitem__`, and :meth:`~bidict.MutableBidict.update`), you can use the following recipe: .. doctest:: >>> from bidict import bidict, ON_DUP_DROP_OLD >>> class YoloBidict(bidict): ... 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.MutableBidict.put` and :meth:`~bidict.MutableBidict.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``, the class above can simply 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}) # b contains two items >>> b['one'] = 2 # update one of the items >>> b # now b only has one item! YoloBidict({'one': 2}) As covered in :ref:`basic-usage:collapsing overwrites`, setting an existing key to the value of a different existing item causes both existing items to quietly collapse into a single new item. The opposite customization would look something like: .. doctest:: >>> from bidict import ON_DUP_RAISE >>> class YodoBidict(bidict): # yodo: you only die once! ... on_dup = ON_DUP_RAISE >>> b = YodoBidict({'one': 1}) >>> b['one'] = 2 # Unlike a regular bidict, YodoBidict won't allow this. Traceback (most recent call last): ... bidict.KeyDuplicationError: one >>> b YodoBidict({'one': 1}) >>> b.forceput('one', 2) # Any type of overwrite requires more force. >>> b YodoBidict({'one': 2}) ``WeakrefBidict`` Recipe ######################## Suppose you need to store some objects in a bidict without incrementing their refcounts. With :class:`~bidict.BidictBase`\'s :attr:`~bidict.BidictBase._fwdm_cls` (forward mapping class) and :attr:`~bidict.BidictBase._invm_cls` (inverse mapping class) attributes, accomplishing this is as simple as: .. doctest:: >>> from bidict import MutableBidict >>> from weakref import WeakKeyDictionary, WeakValueDictionary >>> class WeakrefBidict(MutableBidict): ... _fwdm_cls = WeakKeyDictionary ... _invm_cls = WeakValueDictionary Now you can insert items into *WeakrefBidict* without incrementing their refcounts: .. doctest:: >>> b = WeakrefBidict() >>> o1, o2 = frozenset({1}), frozenset({2}) >>> b[o1] = o2 Since o1 and o2 are the only strong references to these objects, if you delete these references, the refcounts will go to zero and the objects will immediately be deallocated on CPython, since the *WeakrefBidict* isn't holding on to them: .. use `:skipif: pypy` for the test below once https://github.com/thisch/pytest-sphinx/issues/9 is fixed .. doctest:: >>> del o1, o2 # after this, b immediately becomes empty on CPython: >>> if sys.implementation.name == 'cpython': ... assert not b ``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. Using 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 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. ... """ ... _fwdm_cls = SortedDict ... _invm_cls = SortedDict >>> 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): ... _fwdm_cls = SortedDict ... _invm_cls = ValueSortedDict >>> 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)] Automatic "Get Attribute" Pass-Through ###################################### Python makes it easy to customize a class's "get attribute" behavior. You can take advantage of this to pass attribute access through to the backing ``_fwdm`` mapping when an attribute is not provided by the bidict class itself: >>> def __getattribute__(self, name): ... try: ... return object.__getattribute__(self, name) ... except AttributeError: ... return getattr(self._fwdm, name) >>> KeySortedBidict.__getattribute__ = __getattribute__ Now, even though this ``KeySortedBidict`` itself provides no ``peekitem`` attribute, you can still call ``peekitem`` on it and it will return the result of calling ``peekitem`` on the backing ``SortedDict``: >>> elem_by_atomicnum.peekitem() (6, 'carbon') 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`` and ``WeakrefBidict`` recipes 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 ----- This all goes to show how simple it can be to compose your own bidirectional mapping types out of the building blocks that :mod:`bidict` provides. bidict-0.23.1/docs/index.rst000066400000000000000000000003601456445164300156370ustar00rootroot00000000000000.. toctree:: :hidden: intro basic-usage other-bidict-types extending other-functionality addendum changelog api learning-from-bidict contributors-guide thanks code-of-conduct .. include:: README.rst bidict-0.23.1/docs/intro.rst000066400000000000000000000123131456445164300156640ustar00rootroot00000000000000Introduction ============ 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`: .. 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` and :class:`~bidict.OrderedBidict`. 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`. bidict-0.23.1/docs/learning-from-bidict.rst000066400000000000000000000447031456445164300205350ustar00rootroot00000000000000Learning from ``bidict`` ------------------------ Working on bidict has taken me to some of the most interesting and unexpected places I've gotten to visit in many years of programming. (When I started this project 15+ years ago, I'd never heard of things like higher-kinded types. Thanks to bidict, I not only learned about them, I got to `share a practical example with Guido `__ where they would be beneficial for Python.) The problem space that bidict inhabits is abundant with beautiful symmetries, delightful surprises, and rich opportunities to come up with elegant solutions. You can check out bidict's source to see for yourself. I've sought to optimize the code not just for correctness and performance, but also for clarity, maintainability, and to make for an enjoyable read. See below for more, and feel free to let me know what you think. I hope reading bidict's code brings you some of the `joy `__ that bidict has brought me. Code structure ============== :class:`~bidict.bidict`\s come in mutable, immutable, and ordered variants, 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 specialized optimizations) has been one of the most fun parts of working on bidict. To see how this is done, check out the code starting with `__init__.py `__, and then follow the path suggested in the "code review nav" comments at the top of the file: - `_base.py `__ - `_frozen.py `__ - `_bidict.py `__ - `_orderedbase.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 real-world usage, with practical concerns at top of mind. ``OrderedBidict``\'s design =========================== A regular :class:`~bidict.bidict` encapsulates two regular dicts, keeping them in sync to preserve the bidirectional mapping invariants. How should we extend this to implement :class:`~bidict.OrderedBidict`? From :class:`~bidict.BidictBase`, :class:`~bidict.OrderedBidictBase` inherits the use of two regular dicts to store the contents of the forward and inverse items. To store the _ordering_ of the items, we use a doubly-linked list (much like :class:`~collections.OrderedDict`), allowing us to e.g. move any item to the front of the bidict in constant time. Interestingly, the nodes of the linked list encode only the ordering of the items; the nodes themselves contain no key or value data. An additional backing mapping associates the key/value data with the nodes, providing the final piece of the puzzle. And since the implementation needs to not only look up nodes by key/value, but also key/value by node, we use a :class:`~bidict.bidict` for this internally. Bidicts all the way down! Python syntax hacks =================== bidict :issue:`used to <19>` support 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') # [normal] syntax for the forward mapping lookup: element_by_symbol['H'] # ==> 'hydrogen' # [:slice] syntax for the inverse lookup (no longer supported): element_by_symbol[:'hydrogen'] # ==> 'H' See `this code `__ for how this was implemented. It's super cool when you find a way to bend Python's syntax to support new use cases like this that stll feel like they fit well into the language, especially given that Python (wisely) limits how much you can customize its syntax. Property-based testing is incredible ==================================== When your automated tests run, are they only checking the test cases that you happened to think of when writing your tests? How do you know you aren't missing some important edge cases? With property-based testing, you describe the _types_ of the test case inputs that your APIs accept, along with the properties that should hold for all valid inputs. Rather than having to think of your test case inputs manually and hard-code them into your test suite, they get generated for you dynamically, in much greater quantity and diversity than you would typically come up with by hand. This dramatically increases test coverage and confidence that your code is correct with much less actual test code. 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. Check out `bidict's property-based tests `__ to see this in action. Python surprises ================ - What should happen when checking equality of several ordered mappings that contain the same items but in a different order? First let's see how :class:`collections.OrderedDict` works. The results may surprise you: .. doctest:: >>> from collections import OrderedDict >>> x = OrderedDict({1: 1, 2: 2}) >>> y = {1: 1, 2: 2} >>> z = OrderedDict({2: 2, 1: 1}) >>> x == y True >>> y == z True >>> x == z # !!! False So :class:`collections.OrderedDict` violates the `transitive property of equality `__. This can lead to some even more unusual behavior than the above. As an example, let's see what would happen if ``bidict.frozenbidict.__eq__()`` behaved this way: .. doctest:: >>> class BadFrozenBidict(BidictBase): ... __hash__ = frozenbidict.__hash__ ... ... def __eq__(self, other): # (deliberately simplified) ... # Override to be order-sensitive, like collections.OrderedDict: ... return all(i == j for (i, j) in zip(self.items(), other.items())) >>> x = BadFrozenBidict({1: 1, 2: 2}) >>> y = frozenbidict({1: 1, 2: 2}) >>> z = BadFrozenBidict({2: 2, 1: 1}) >>> x == y True >>> y == z True >>> x == z # !!! False >>> set1 = {x, y, z} >>> len(set1) 2 >>> set2 = {y, x, z} >>> len(set2) # !!! 1 According to Raymond Hettinger, the Python core developer who built Python's collections foundation, :class:`collections.OrderedDict`\'s ``__eq__()`` implementation should have been order-insensitive. Making it order-sensitive violates the transitive property of equality as well as the `Liskov substitution principle `__. It's too late now to change this for :class:`collections.OrderedDict`. But at least it's not too late 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, thanks to Raymond's advice. - See :ref:`addendum:\*nan\* as a Key`. - See :ref:`addendum:Equivalent but distinct \:class\:\`~collections.abc.Hashable\`\\s`. Better memory usage through ``__slots__`` ========================================= Using :ref:`slots` speeds up attribute access, and can dramatically reduce memory usage in CPython when creating many instances of the same class. As an example, the ``Node`` class used internally (in the linked list that backs :class:`~bidict.OrderedBidictBase`) uses slots for better performance at scale, since there are as many node instances kept in memory as there are items in every ordered bidict in memory. *See:* `_orderedbase.py `__ Note that extra care must be taken when using slots with pickling and weakrefs; see the code for more. Better memory usage through ``weakref`` ======================================= A :class:`~bidict.bidict` and its inverse use :mod:`weakref` to :ref:`avoid creating a reference cycle `. As a result, when you drop your last reference to a bidict, its memory is reclaimed immediately in CPython rather than having to wait for the next garbage collection. *See:* `_base.py `__ As another example, the ``Node`` class used internally by :class:`~bidict.OrderedBidictBase` uses weakrefs to avoid creating reference cycles in the doubly-linked lists used to encode the ordering of inserted items. *See:* `_orderedbase.py `__ Using descriptors for managed attributes ======================================== To abstract the details of creating and dereferencing the weakrefs that :class:`~bidict.OrderedBidictBase`\'s aforementioned doubly-linked list nodes use to refer to their neighbor nodes, a ``WeakAttr`` descriptor is used to `manage access to these attributes automatically `__. *See:* `_orderedbase.py `__ The implicit ``__class__`` reference ==================================== Anytime you have to reference the exact class of an instance (and not a potential subclass) from within a method body, you can use the implicit, lexically-scoped ``__class__`` reference rather than hard-coding the current class's name. *See:* https://docs.python.org/3/reference/datamodel.html#executing-the-class-body Subclassing ``namedtuple`` classes ================================== To get the performance benefits, intrinsic sortability, etc. of :class:`~typing.NamedTuple` (or :func:`~collections.namedtuple`) while customizing behavior, API, etc., you can subclass. See the *OnDup* class in `_dup.py `__ for an example. Here's another 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 ``namedtuple``-style dynamic class generation ============================================= See the `implementation `__ of ``namedbidict`` (it was since removed due to low usage). 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. ``setdefault()``, ``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, ``__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. - 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``. *See:* ``__ - See the `Zen of Python `__ for how to make APIs Pythonic. The following Zen of Python guidelines have been particularly influential for bidict: - "Errors should never pass silently. Unless explicitly silenced. - "In the face of ambiguity, refuse the temptation to guess." - "Readability counts." - "There should be one – and preferably only one – obvious way to do it." 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. - 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.frozenbidict`: its ``__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()``.) - See also ``__ - 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. 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. - 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 =========== - 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. - Python 2 vs. Python 3 - As affects bidict, mostly :class:`dict` API changes, but also functions like :func:`zip`, :func:`map`, :func:`filter`, etc. - :meth:`~object.__ne__` fixed in Python 3 - 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`` 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 a great opportunity to learn and use. bidict-0.23.1/docs/other-bidict-types.rst000066400000000000000000000243521456445164300202560ustar00rootroot00000000000000Other ``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. 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 can see above, :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: 'frozenbidict' object does not support item assignment :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 :class:`~bidict.OrderedBidict` ------------------------------ :class:`bidict.OrderedBidict` is a :class:`~bidict.MutableBidirectionalMapping` that preserves the insertion order of its items, and offers some additional ordering-related APIs not offered by the plain bidict type. 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 = element_by_symbol.items() >>> last_item ('Be', 'beryllium') .. _extra-order-sensitive-apis: Extra order-sensitive APIs ++++++++++++++++++++++++++ Additional, efficiently-implemented, order-sensitive APIs are provided as well, following the example of :class:`~collections.OrderedDict`. Namely, :class:`~bidict.OrderedBidict` provides constant-time implementations of :meth:`popitem(last: bool) ` and :meth:`move_to_end(last: bool) `, which make ordered bidicts suitable to use for things like FIFO queues and LRU caches. .. 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'}) .. _eq-order-insensitive: :meth:`~bidict.OrderedBidict`\'s ``__eq__()`` is order-insensitive ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ To ensure that ``==`` comparison for any bidict always upholds the `transitive property of equality `__ and the `Liskov substitution principle `__, equality tests between a bidict and another mapping are always order-insensitive, even for ordered bidicts: .. doctest:: >>> o1 = OrderedBidict({1: 1, 2: 2}) >>> o2 = OrderedBidict({2: 2, 1: 1}) >>> 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 improves on the behavior of ``collections.OrderedDict.__eq__()``. For more about this, see :ref:`learning-from-bidict:Python surprises`.) What about order-preserving dicts? ++++++++++++++++++++++++++++++++++ In CPython 3.6+ and all versions of PyPy, :class:`dict` preserves insertion order. Since bidicts are built on top of dicts, can we get away with using a plain bidict in places where you need an order-preserving bidirectional mapping? (Assuming we don't need the :ref:`extra-order-sensitive-apis`.) Let's look at some examples. Order consistency between bidicts and their inverses ++++++++++++++++++++++++++++++++++++++++++++++++++++ Consider the following: .. doctest:: >>> b = bidict({1: -1, 2: -2, 3: -3}) >>> b[2] = 'UPDATED' >>> b bidict({1: -1, 2: 'UPDATED', 3: -3}) So far so good, but look what happens to the inverse: .. doctest:: >>> b.inverse bidict({-1: 1, -3: 3, 'UPDATED': 2}) After the mutation, the ordering of the items in the plain bidict is no longer consistent with its inverse. To ensure that ordering is kept consistent between a bidict and its inverse, no matter how it's mutated, you have to use an ordered bidict: .. doctest:: >>> ob = OrderedBidict({1: -1, 2: -2, 3: -3}) >>> ob[2] = 'UPDATED' >>> ob OrderedBidict({1: -1, 2: 'UPDATED', 3: -3}) >>> ob.inverse # better: OrderedBidict({-1: 1, 'UPDATED': 2, -3: 3}) Preserving insertion order of items even after key changes ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Another way that ordered bidicts differ from plain bidicts is that you can change the *key* of an existing item, and its order will still be preserved. Let's look at an example: .. doctest:: >>> bi = bidict({1: -1}) >>> ob = OrderedBidict({1: -1}) >>> bi.forceupdate({2: -2, 3: -1}) >>> ob.forceupdate({2: -2, 3: -1}) This update changes the key of the existing item with value -1. In the ordered bidict, this change is performed in-place, preserving the insertion order. The item with value -1 was the first item inserted, and it remains the first item even after the update: >>> ob OrderedBidict({3: -1, 2: -2}) In the plain bidict, however, the changed item has now been moved to the end: >>> bi bidict({2: -2, 3: -1}) Note that if you insert an item that changes the key of one existing item and the value of another existing item, the behavior described in :ref:`basic-usage:collapsing overwrites` still applies. Trade-offs ++++++++++ Like plain bidicts (and plain dicts too, for that matter), ordered bidicts take *O(n)* space. But to preserve insertion order, as well as implement the :ref:`extra-order-sensitive-apis` in constant time, it costs :class:`~bidict.OrderedBidict` a higher constant factor in its *O(n)* space complexity. If you depend on preserving insertion order, an unordered bidict may be sufficient if: * you'll never mutate it (in which case, use a :class:`~bidict.frozenbidict`), or: * you only mutate by removing and/or adding whole new items, never changing just the key or value of an existing item, or: * you are okay with inconsistent ordering between a bidict and its inverse after changing the key or value of an existing item, as well as with items moving to the end when you change their key rather than being changed in place. That said, if your code depends on the ordering, using an :class:`~bidict.OrderedBidict` makes for clearer code, and ensures that insertion order will be preserved no matter what mutations you perform. The :ref:`extra-order-sensitive-apis` that :class:`~bidict.OrderedBidict` gives you also expand the range of use cases where your bidict would be suitable, as mentioned above. Reversing a bidict ------------------ All provided bidict types are reversible (since they are backed by dicts, which are themselves reversible on all supported Python versions as of CPython 3.8+). .. doctest:: >>> b = bidict({1: 'one', 2: 'two', 3: 'three'}) >>> list(reversed(b)) [3, 2, 1] >>> list(reversed(b.items())) # keys/values/items views are reversible too [(3, 'three'), (2, 'two'), (1, 'one')] Polymorphism ------------ Code that needs to check only whether an object is *dict-like* should not use ``isinstance(obj, dict)``. This check is too specific, because dict-like objects need not actually be instances of dict or a dict subclass. You can see this for many dict-like objects in the standard library: .. doctest:: >>> from collections import ChainMap >>> chainmap = ChainMap() >>> isinstance(chainmap, dict) False The same is true for all the bidict types: .. doctest:: >>> bi = bidict() >>> isinstance(bi, dict) False A better way to check whether an object is dict-like is to use the :class:`~collections.abc.Mapping` abstract base class (ABC) from the :mod:`collections.abc` module, which provides a number of ABCs intended for this purpose: .. doctest:: >>> isinstance(chainmap, Mapping) True >>> isinstance(bi, Mapping) True Also note that the proper way to check whether an object is a mutable mapping is to use the :class:`~collections.abc.MutableMapping` ABC: .. doctest:: >>> isinstance(chainmap, MutableMapping) True >>> isinstance(bi, MutableMapping) True To illustrate this, here's an example of how you can combine the above with bidict's own :class:`~bidict.BidirectionalMapping` ABC to implement your own check for whether an object is an immutable bidirectional mapping: .. doctest:: >>> def is_immutable_bimap(obj): ... return ( ... isinstance(obj, BidirectionalMapping) ... and not isinstance(obj, MutableMapping)) >>> is_immutable_bimap(bidict()) False >>> is_immutable_bimap(frozenbidict()) True For more you can do with :mod:`bidict`, check out :doc:`extending` next. bidict-0.23.1/docs/other-functionality.rst000066400000000000000000000016071456445164300205440ustar00rootroot00000000000000Other 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: .. doctest:: >>> dict(inverted(bidict({1: 'one'}))) {'one': 1} >>> list(inverted(OrderedBidict({2: 4, 3: 9}))) [(4, 2), (9, 3)] bidict-0.23.1/docs/thanks.rst000066400000000000000000000041611456445164300160230ustar00rootroot00000000000000Thanks ------ Bidict has benefited from the assistance of many people and projects. People ====== - Gregory Ewing for the name. - Terry Reedy for suggesting the slice syntax (it was fun while it lasted). - Raymond Hettinger for providing feedback on the design and implementation, and (most of all) for the amazing work on Python's built-in collections that made bidict possible. - 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. - Zac Hatfield-Dodds for the amazing work (as well as soliciting feedback) on `Hypothesis `__, `Hypofuzz `__, and `Pytest `__.` - Harrison Goldstein for building `Tyche `__ and showing me how to use it. - Daniel Pope, Leif Walsh, David Turner, Itamar Turner-Trauring, and Michael Arntzenius for suggestions, code reviews, and design discussion. - Leif Walsh for contributing the initial `devcontainer `__ setup. - Jozef Knaperek for the bugfix. - Igor Nehoroshev for contributing the py.typed marker. - Bernát Gábor for pyproject.toml support. - Richard Sanger, Zeyi Wang, Brian Maissy, and Amol Sahsrabudhe for reporting bugs. Projects ======== - `Python `__ - `GitHub `__ - `Tidelift `__ - `Pytest `__ - `Hypothesis `__ - `Tyche `__ - `Pytest-Benchmark `__ - `Coverage `__ - `Codecov `__ - `Sphinx `__ - `Readthedocs `__ - `mypy `__ - `ruff `__ - `pre-commit `__ bidict-0.23.1/flake.lock000066400000000000000000000010261456445164300150020ustar00rootroot00000000000000{ "nodes": { "nixpkgs": { "locked": { "lastModified": 1708200228, "narHash": "sha256-saorWFWEVrcmkbNJQXBZNvGUqGGr4Urp6sbMxhYnyho=", "owner": "NixOS", "repo": "nixpkgs", "rev": "1816e34c003c524b8e83f2f7abefa41b44100976", "type": "github" }, "original": { "owner": "NixOS", "repo": "nixpkgs", "type": "github" } }, "root": { "inputs": { "nixpkgs": "nixpkgs" } } }, "root": "root", "version": 7 } bidict-0.23.1/flake.nix000066400000000000000000000020051456445164300146460ustar00rootroot00000000000000{ description = "bidict"; # Flake inputs inputs = { nixpkgs.url = "github:NixOS/nixpkgs"; }; # Flake outputs outputs = { self, nixpkgs }: let # Systems supported allSystems = [ "aarch64-darwin" "aarch64-linux" "x86_64-darwin" "x86_64-linux" ]; # Helper to provide system-specific attributes nameValuePair = name: value: { inherit name value; }; genAttrs = names: f: builtins.listToAttrs (map (n: nameValuePair n (f n)) names); forAllSystems = f: genAttrs allSystems (system: f { pkgs = import nixpkgs { inherit system; }; }); in { # Development environment output devShells = forAllSystems ({ pkgs }: { default = pkgs.mkShell { packages = with pkgs; [ pre-commit python312 python311 python310 python39 python38 pypy310 pypy39 uv ]; }; }); }; } bidict-0.23.1/init_dev_env000077500000000000000000000033301456445164300154450ustar00rootroot00000000000000#!/usr/bin/env bash # # Copyright 2009-2024 Joshua Bronson. All rights reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # shellcheck disable=SC1091 set -euo pipefail declare -r hint="Hint: Use 'nix develop' to bootstrap a development environment" source ./dev-deps/py_ver.env for cmd in "$DEFAULT_PY" uv pre-commit; do if ! type "$cmd"; then >&2 echo "Error: No '$cmd' on PATH. $hint" exit 1 fi done pre-commit install -f export VENV_DIR=".venv" if ! test -d "$VENV_DIR"; then uv venv --python="$DEFAULT_PY" "$VENV_DIR" fi declare -r req_sets="dev docs test" declare -r out_dir="dev-deps/$DEFAULT_PY" mkdir -p "$out_dir" for i in $req_sets; do reqs_in="dev-deps/$i.in" reqs_out="$out_dir/$i.txt" # The following options should match those passed in dev-deps/update_dev_dependencies so that the # resulting "autogenerated...via the following command:" comments match. uv pip compile --generate-hashes --upgrade --python-version="$DEFAULT_PY_VER" "$reqs_in" -o "$reqs_out" done # TODO The following triggers https://github.com/astral-sh/uv/issues/1552 # uv pip sync $out_dir/*.txt # so work around this as follows: declare -r reqs=("$out_dir"/*.txt) uv pip install "${reqs[@]/#/-r}" # TODO Until https://github.com/astral-sh/uv/issues/1594 is fixed, we can't use the following: # if ! uv pip show -qq bidict; then # so work around this as follows: if ! uv pip freeze | grep -q ^bidict; then uv pip install -e . fi echo "Development virtualenv initialized: ./$VENV_DIR" echo "To activate, run: source ./$VENV_DIR/bin/activate (or equivalent for your shell)" bidict-0.23.1/pyproject.toml000066400000000000000000000033311456445164300157630ustar00rootroot00000000000000[project] name = "bidict" dynamic = ["version"] description = "The bidirectional mapping library for Python." authors = [{ name = "Joshua Bronson", email = "jabronson@gmail.com" }] license = { text = "MPL 2.0" } dependencies = [] requires-python = ">=3.8" readme = "README.rst" keywords = [ "bidict", "bimap", "bidirectional", "dict", "dictionary", "mapping", "collections", ] classifiers = [ "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)", "Operating System :: OS Independent", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Typing :: Typed", ] [project.urls] Changelog = "https://bidict.readthedocs.io/changelog.html" Documentation = "https://bidict.readthedocs.io" Funding = "https://bidict.readthedocs.io/#sponsoring" Repository = "https://github.com/jab/bidict" [build-system] requires = ["setuptools >= 40.9.0"] build-backend = "setuptools.build_meta" [tool.setuptools] packages = ["bidict"] [tool.setuptools.dynamic] version = {attr = "bidict.metadata.__version__"} [tool.mypy] strict = true show_error_codes = true show_error_context = true show_column_numbers = true pretty = true [tool.ruff] preview = true line-length = 121 [tool.ruff.lint] # https://beta.ruff.rs/docs/rules/ extend-select = [ "A", "ARG", "B", "BLE", "E", "F", "FA", "FLY", "FURB", "G", "I", "ICN", "PERF", "PGH", "PIE", "PT", "PTH", "RET", "RSE", "RUF", "SLOT", "T20", "TID", "UP", "W", "YTT", ] [tool.ruff.lint.isort] force-single-line = true lines-after-imports = 2 [tool.ruff.format] docstring-code-format = true docstring-code-line-length = "dynamic" preview = true quote-style = "single" bidict-0.23.1/pytest.ini000066400000000000000000000007411456445164300151020ustar00rootroot00000000000000[pytest] testpaths = bidict tests docs doctest_optionflags = ELLIPSIS addopts = -vv # Disable pytest-benchmark by default and require explicitly passing --benchmark-enable to override (prevents warning about incompatibility with pytest-xdist) --benchmark-disable --benchmark-columns=min,rounds,iterations --benchmark-disable-gc --benchmark-group-by=name --doctest-modules --doctest-glob=tests/*.txt --doctest-glob=docs/*.rst --durations=5 --numprocesses=auto bidict-0.23.1/tests/000077500000000000000000000000001456445164300142115ustar00rootroot00000000000000bidict-0.23.1/tests/bidict_test_fixtures.py000066400000000000000000000162001456445164300210100ustar00rootroot00000000000000# Copyright 2009-2024 Joshua Bronson. All rights reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. from __future__ import annotations import operator import typing as t from collections import UserDict from dataclasses import dataclass from itertools import starmap from bidict import DROP_NEW from bidict import DROP_OLD from bidict import RAISE from bidict import BidictBase from bidict import DuplicationError from bidict import KeyAndValueDuplicationError from bidict import KeyDuplicationError from bidict import MutableBidirectionalMapping from bidict import OnDup from bidict import OrderedBidict from bidict import ValueDuplicationError from bidict import bidict from bidict import frozenbidict from bidict._typing import MapOrItems KT = t.TypeVar('KT') VT = t.TypeVar('VT') class SupportsKeysAndGetItem(t.Generic[KT, VT]): def __init__(self, *args: t.Any, **kw: t.Any) -> None: self._mapping: t.Mapping[KT, VT] = dict(*args, **kw) def keys(self) -> t.KeysView[KT]: return self._mapping.keys() def __getitem__(self, key: KT) -> VT: return self._mapping[key] BB = BidictBase[KT, VT] BT = t.Type[BB[KT, VT]] user_bidict_types: list[BT[t.Any, t.Any]] = [] def user_bidict(cls: BT[KT, VT]) -> BT[KT, VT]: user_bidict_types.append(cls) return cls @user_bidict class UserBi(bidict[KT, VT]): _fwdm_cls = UserDict _invm_cls = UserDict @user_bidict class UserOrderedBi(OrderedBidict[KT, VT]): _fwdm_cls = UserDict _invm_cls = UserDict @user_bidict class UserBiNotOwnInv(bidict[KT, VT]): """A custom bidict whose inverse class is not itself.""" _fwdm_cls = dict _invm_cls = UserDict UserBiNotOwnInvInv = UserBiNotOwnInv._inv_cls assert UserBiNotOwnInvInv is not UserBiNotOwnInv BTs = t.Tuple[BT[t.Any, t.Any], ...] builtin_bidict_types: BTs = (bidict, frozenbidict, OrderedBidict) bidict_types: BTs = (*builtin_bidict_types, *user_bidict_types) update_arg_types = (*bidict_types, list, dict, iter, SupportsKeysAndGetItem) mutable_bidict_types: BTs = tuple(t for t in bidict_types if issubclass(t, MutableBidirectionalMapping)) assert frozenbidict not in mutable_bidict_types MBT = t.Union[t.Type[bidict[KT, VT]], t.Type[OrderedBidict[KT, VT]]] def should_be_reversible(bi_t: BT[KT, VT]) -> bool: return bi_t in builtin_bidict_types or issubclass(bi_t, OrderedBidict) assert all(not should_be_reversible(bi_t) or issubclass(bi_t, t.Reversible) for bi_t in bidict_types) SET_OPS: t.Any = ( operator.le, operator.lt, operator.gt, operator.ge, operator.eq, operator.ne, operator.and_, operator.or_, operator.sub, operator.xor, (lambda x, y: x.isdisjoint(y)), ) DEFAULT_ON_DUP = OnDup(DROP_OLD, RAISE) @dataclass class Oracle(t.Generic[KT, VT]): data: dict[KT, VT] ordered: bool @property def data_inv(self) -> dict[VT, KT]: return {v: k for (k, v) in self.data.items()} def assert_match(self, bi: BidictBase[KT, VT]) -> None: assert dict(bi) == self.data assert dict(bi.inv) == self.data_inv self.assert_items_match(bi) def assert_items_match(self, bi: BidictBase[KT, VT]) -> None: if self.ordered: assert zip_equal(bi.items(), self.data.items()) else: assert bi.items() == self.data.items() def clear(self) -> None: self.data.clear() def pop(self, key: KT) -> VT: return self.data.pop(key) def popitem(self, last: bool = True) -> tuple[KT, VT]: if last: return self.data.popitem() key = next(iter(self.data)) return key, self.data.pop(key) def put(self, key: KT, val: VT, on_dup: OnDup = DEFAULT_ON_DUP) -> None: oldval = self.data.get(key) oldkey = self.data_inv.get(val) isdupkey = oldval is not None isdupval = oldkey is not None if isdupkey and isdupval: if key == oldkey: # (key, val) duplicates an existing item -> no-op assert val == oldval return # key and val each duplicate a different existing item. if on_dup.val is RAISE: raise KeyAndValueDuplicationError(key, val) if on_dup.val is DROP_NEW: return assert on_dup.val is DROP_OLD elif isdupkey: if on_dup.key is RAISE: raise KeyDuplicationError(key) if on_dup.key is DROP_NEW: return assert on_dup.key is DROP_OLD elif isdupval: if on_dup.val is RAISE: raise ValueDuplicationError(val) if on_dup.val is DROP_NEW: return assert on_dup.val is DROP_OLD if not self.ordered: self.data[key] = val self.data.pop(oldkey, None) # type: ignore[arg-type] return # Ensure insertion order is preserved in the case of a sequence of overwriting updates. updated = {} for k, v in self.data.items(): if k == oldkey or v == oldval: if k == oldkey and isdupkey and isdupval: continue updated[key] = val else: updated[k] = v updated[key] = val self.data = updated def putall(self, updates: MapOrItems[KT, VT], on_dup: OnDup = DEFAULT_ON_DUP) -> None: # https://bidict.readthedocs.io/en/main/basic-usage.html#order-matters tmp = self.data.copy() if isinstance(updates, t.Mapping): updates = updates.items() elif hasattr(updates, 'keys') and hasattr(updates, '__getitem__'): updates = [(k, updates[k]) for k in updates.keys()] try: for key, val in updates: self.put(key, val, on_dup) except DuplicationError: self.data = tmp # fail clean (no partially-applied updates) raise def __ior__(self, other: t.Mapping[KT, VT]) -> dict[KT, VT]: self.putall(other) return self.data def __or__(self, other: t.Mapping[KT, VT]) -> dict[KT, VT]: before = self.data.copy() self.putall(other) after = self.data self.data = before return after def __ror__(self, other: t.Mapping[KT, VT]) -> dict[KT, VT]: before = self.data.copy() self.data = {} try: self.putall(other) self.putall(before) except DuplicationError: self.data = before raise after = self.data self.data = before return after def move_to_end(self, key: KT, last: bool = True) -> None: val = self.pop(key) if last: self.put(key, val) else: self.data = {key: val, **self.data} def zip_equal(i1: t.Iterable[t.Any], i2: t.Iterable[t.Any]) -> bool: return all(starmap(operator.eq, zip(i1, i2))) def invdict(d: dict[KT, VT]) -> dict[VT, KT]: return {v: k for (k, v) in d.items()} def dedup(x: MapOrItems[KT, VT]) -> dict[KT, VT]: return invdict(invdict(dict(x))) bidict-0.23.1/tests/conftest.py000066400000000000000000000006141456445164300164110ustar00rootroot00000000000000# Copyright 2009-2024 Joshua Bronson. All rights reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. from hypothesis import settings settings.register_profile('more-examples', deadline=None, max_examples=999, stateful_step_count=200) bidict-0.23.1/tests/microbenchmarks.py000066400000000000000000000214521456445164300177360ustar00rootroot00000000000000# Copyright 2009-2024 Joshua Bronson. All rights reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. """Microbenchmarks. Uses https://pytest-benchmark.readthedocs.io/en/v4.0.0/pedantic.html which pairs well with ../cachegrind.py (as used by ../.github/workflows/benchmark.yml). """ from __future__ import annotations import pickle import typing as t from collections import deque from functools import partial import pytest import bidict consume: t.Any = partial(deque, maxlen=0) LENS = (99, 999, 9_999) DICTS_BY_LEN = {n: {i: i for i in range(n)} for n in LENS} BIDICTS_BY_LEN = {n: bidict.bidict(DICTS_BY_LEN[n]) for n in LENS} ORDERED_BIDICTS_BY_LEN = {n: bidict.OrderedBidict(DICTS_BY_LEN[n]) for n in LENS} DICTS_BY_LEN_LAST_ITEM_DUPVAL = {n: {**DICTS_BY_LEN[n], n - 1: 0} for n in LENS} BIDICT_AND_DICT_ONLY_LAST_ITEM_DIFFERENT = {n: (BIDICTS_BY_LEN[n], DICTS_BY_LEN_LAST_ITEM_DUPVAL[n]) for n in LENS} ORDERED_BIDICT_AND_DICT_ONLY_LAST_ITEM_DIFFERENT = { n: (bidict.OrderedBidict(bi_and_d[0]), bi_and_d[1]) for (n, bi_and_d) in BIDICT_AND_DICT_ONLY_LAST_ITEM_DIFFERENT.items() } BIDICT_AND_DICT_LAST_TWO_ITEMS_DIFFERENT_ORDER = {} ORDERED_BIDICT_AND_DICT_LAST_TWO_ITEMS_DIFFERENT_ORDER = {} for _i in LENS: _bi = BIDICTS_BY_LEN[_i] _d = dict(_bi) _last, _secondlast = _d.popitem(), _d.popitem() _d[_last[0]] = _last[1] # new second-last _d[_secondlast[0]] = _secondlast[1] # new last BIDICT_AND_DICT_LAST_TWO_ITEMS_DIFFERENT_ORDER[_i] = (_bi, _d) ORDERED_BIDICT_AND_DICT_LAST_TWO_ITEMS_DIFFERENT_ORDER[_i] = (bidict.OrderedBidict(_bi), _d) @pytest.mark.parametrize('n', LENS) def test_bi_init_from_dict(n: int, benchmark: t.Any) -> None: """Benchmark initializing a new bidict from a dict.""" other = DICTS_BY_LEN[n] benchmark.pedantic(bidict.bidict, args=(other,)) @pytest.mark.parametrize('n', LENS) def test_bi_init_from_bi(n: int, benchmark: t.Any) -> None: """Benchmark initializing a bidict from another bidict.""" other = BIDICTS_BY_LEN[n] benchmark.pedantic(bidict.bidict, args=(other,)) @pytest.mark.parametrize('n', LENS) def test_bi_init_fail_worst_case(n: int, benchmark: t.Any) -> None: """Benchmark initializing a bidict from a dict with a final duplicate value.""" other = DICTS_BY_LEN_LAST_ITEM_DUPVAL[n] def failing_init() -> None: with pytest.raises(bidict.DuplicationError): bidict.bidict(other) benchmark.pedantic(failing_init) @pytest.mark.parametrize('n', LENS) def test_empty_bi_update_from_bi(n: int, benchmark: t.Any) -> None: """Benchmark updating an empty bidict from another bidict.""" bi: bidict.bidict[int, int] = bidict.bidict() other = BIDICTS_BY_LEN[n] benchmark.pedantic(bi.update, args=(other,)) assert bi == other @pytest.mark.parametrize('n', LENS) def test_small_bi_update_from_bi(n: int, benchmark: t.Any) -> None: """Benchmark updating a small bidict from another bidict that has no duplication.""" bi = bidict.bidict({i: i for i in range(-9, 0)}) other = BIDICTS_BY_LEN[n] benchmark.pedantic(bi.update, args=(other,)) assert bi.keys() == set(range(-9, 0)) | other.keys() @pytest.mark.parametrize('n', LENS) def test_small_bi_large_update_fails_worst_case(n: int, benchmark: t.Any) -> None: """Benchmark updating a small bidict with a large update that fails on the final item and then rolls back.""" bi = bidict.bidict({i: i for i in range(-9, 0)}) other = DICTS_BY_LEN_LAST_ITEM_DUPVAL[n] def failing_update() -> None: with pytest.raises(bidict.DuplicationError): bi.update(other) benchmark.pedantic(failing_update) assert list(bi.items()) == [(i, i) for i in range(-9, 0)] @pytest.mark.parametrize('n', LENS) def test_bi_iter(n: int, benchmark: t.Any) -> None: """Benchmark iterating over a bidict.""" bi = BIDICTS_BY_LEN[n] benchmark.pedantic(consume, args=(iter(bi),)) @pytest.mark.parametrize('n', LENS) def test_orderedbi_iter(n: int, benchmark: t.Any) -> None: """Benchmark iterating over an OrderedBidict.""" ob = ORDERED_BIDICTS_BY_LEN[n] benchmark.pedantic(consume, args=(iter(ob),)) @pytest.mark.parametrize('n', LENS) def test_bi_contains_key_present(n: int, benchmark: t.Any) -> None: """Benchmark bidict.__contains__ with a contained key.""" bi = BIDICTS_BY_LEN[n] key = next(iter(bi)) result = benchmark.pedantic(bi.__contains__, args=(key,)) assert result @pytest.mark.parametrize('n', LENS) def test_bi_contains_key_missing(n: int, benchmark: t.Any) -> None: """Benchmark bidict.__contains__ with a missing key.""" bi = BIDICTS_BY_LEN[n] result = benchmark.pedantic(bi.__contains__, args=(object(),)) assert not result @pytest.mark.parametrize('n', LENS) def test_bi_equals_with_equal_dict(n: int, benchmark: t.Any) -> None: """Benchmark bidict.__eq__ with an equivalent dict.""" bi, d = BIDICT_AND_DICT_LAST_TWO_ITEMS_DIFFERENT_ORDER[n] result = benchmark.pedantic(bi.__eq__, args=(d,)) assert result @pytest.mark.parametrize('n', LENS) def test_orderedbi_equals_with_equal_dict(n: int, benchmark: t.Any) -> None: """Benchmark OrderedBidict.__eq__ with an equivalent dict.""" ob, d = ORDERED_BIDICT_AND_DICT_LAST_TWO_ITEMS_DIFFERENT_ORDER[n] result = benchmark.pedantic(ob.__eq__, args=(d,)) assert result @pytest.mark.parametrize('n', LENS) def test_orderedbi_items_equals_with_equal_dict_items(n: int, benchmark: t.Any) -> None: """Benchmark OrderedBidict.items().__eq__ with an equivalent dict_items.""" ob, d = ORDERED_BIDICT_AND_DICT_LAST_TWO_ITEMS_DIFFERENT_ORDER[n] obi, di = ob.items(), d.items() result = benchmark.pedantic(obi.__eq__, args=(di,)) assert result @pytest.mark.parametrize('n', LENS) def test_orderedbi_items_equals_with_unequal_dict_items(n: int, benchmark: t.Any) -> None: """Benchmark OrderedBidict.items().__eq__ with an unequal dict_items.""" ob, d = ORDERED_BIDICT_AND_DICT_ONLY_LAST_ITEM_DIFFERENT[n] obi, di = ob.items(), d.items() result = benchmark.pedantic(obi.__eq__, args=(di,)) assert not result @pytest.mark.parametrize('n', LENS) def test_bi_equals_with_unequal_dict(n: int, benchmark: t.Any) -> None: """Benchmark bidict.__eq__ with an unequal dict.""" bi, d = BIDICT_AND_DICT_ONLY_LAST_ITEM_DIFFERENT[n] result = benchmark.pedantic(bi.__eq__, args=(d,)) assert not result @pytest.mark.parametrize('n', LENS) def test_orderedbi_equals_with_unequal_dict(n: int, benchmark: t.Any) -> None: """Benchmark OrderedBidict.__eq__ with an unequal dict.""" ob, d = ORDERED_BIDICT_AND_DICT_ONLY_LAST_ITEM_DIFFERENT[n] result = benchmark.pedantic(ob.__eq__, args=(d,)) assert not result @pytest.mark.parametrize('n', LENS) def test_bi_order_sensitive_equals_dict(n: int, benchmark: t.Any) -> None: """Benchmark bidict.equals_order_sensitive with an order-sensitive-equal dict.""" bi, d = BIDICTS_BY_LEN[n], DICTS_BY_LEN[n] result = benchmark.pedantic(bi.equals_order_sensitive, args=(d,)) assert result @pytest.mark.parametrize('n', LENS) def test_orderedbi_order_sensitive_equals_dict(n: int, benchmark: t.Any) -> None: """Benchmark OrderedBidict.equals_order_sensitive with an order-sensitive-equal dict.""" ob, d = ORDERED_BIDICTS_BY_LEN[n], DICTS_BY_LEN[n] result = benchmark.pedantic(ob.equals_order_sensitive, args=(d,)) assert result @pytest.mark.parametrize('n', LENS) def test_bi_equals_order_sensitive_with_unequal_dict(n: int, benchmark: t.Any) -> None: """Benchmark bidict.equals_order_sensitive with an order-sensitive-unequal dict.""" bi, d = BIDICT_AND_DICT_LAST_TWO_ITEMS_DIFFERENT_ORDER[n] result = benchmark.pedantic(bi.equals_order_sensitive, args=(d,)) assert not result @pytest.mark.parametrize('n', LENS) def test_orderedbi_equals_order_sensitive_with_unequal_dict(n: int, benchmark: t.Any) -> None: """Benchmark OrderedBidict.equals_order_sensitive with an order-sensitive-unequal dict.""" ob, d = ORDERED_BIDICT_AND_DICT_LAST_TWO_ITEMS_DIFFERENT_ORDER[n] result = benchmark.pedantic(ob.equals_order_sensitive, args=(d,)) assert not result @pytest.mark.parametrize('n', LENS) def test_copy(n: int, benchmark: t.Any) -> None: """Benchmark creating a copy of a bidict.""" bi = BIDICTS_BY_LEN[n] benchmark.pedantic(bi.copy) @pytest.mark.parametrize('n', LENS) def test_pickle(n: int, benchmark: t.Any) -> None: """Benchmark pickling a bidict.""" bi = BIDICTS_BY_LEN[n] benchmark.pedantic(pickle.dumps, args=(bi,)) @pytest.mark.parametrize('n', LENS) def test_unpickle(n: int, benchmark: t.Any) -> None: """Benchmark unpickling a bidict.""" bp = pickle.dumps(BIDICTS_BY_LEN[n]) benchmark.pedantic(pickle.loads, args=(bp,)) bidict-0.23.1/tests/test_bidict.py000066400000000000000000000527561456445164300170770ustar00rootroot00000000000000# Copyright 2009-2024 Joshua Bronson. All rights reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. """Tests for :mod:`bidict`. Mainly these are property-based tests implemented via https://hypothesis.works. """ from __future__ import annotations import gc import pickle import sys import typing as t import weakref from copy import copy from copy import deepcopy from functools import partial from itertools import product from itertools import starmap from random import Random from unittest.mock import ANY import pytest from bidict_test_fixtures import BB from bidict_test_fixtures import BT from bidict_test_fixtures import KT from bidict_test_fixtures import MBT from bidict_test_fixtures import SET_OPS from bidict_test_fixtures import VT from bidict_test_fixtures import Oracle from bidict_test_fixtures import SupportsKeysAndGetItem from bidict_test_fixtures import UserBiNotOwnInv from bidict_test_fixtures import UserOrderedBi from bidict_test_fixtures import bidict_types from bidict_test_fixtures import dedup from bidict_test_fixtures import mutable_bidict_types from bidict_test_fixtures import should_be_reversible from bidict_test_fixtures import update_arg_types from bidict_test_fixtures import zip_equal from hypothesis import assume from hypothesis import given from hypothesis import note from hypothesis.stateful import RuleBasedStateMachine from hypothesis.stateful import initialize from hypothesis.stateful import invariant from hypothesis.stateful import precondition from hypothesis.stateful import rule from hypothesis.strategies import booleans from hypothesis.strategies import dictionaries from hypothesis.strategies import frozensets from hypothesis.strategies import integers from hypothesis.strategies import lists from hypothesis.strategies import randoms from hypothesis.strategies import sampled_from from hypothesis.strategies import tuples from typing_extensions import assert_type from bidict import BidirectionalMapping from bidict import DuplicationError from bidict import KeyAndValueDuplicationError from bidict import MutableBidict from bidict import MutableBidirectionalMapping from bidict import OnDup from bidict import OnDupAction from bidict import OrderedBidict from bidict import ValueDuplicationError from bidict import frozenbidict from bidict import inverted from bidict._typing import MapOrItems MAX_SIZE = 5 # Used for init_items and updates. 5 is enough to cover all possible duplication scenarios. keys = integers(min_value=1, max_value=10) vals = integers(min_value=-10, max_value=-1) # faster than keys.map(operator.neg) InitItems = t.Dict[t.Any, t.Any] init_items = dictionaries(vals, keys, max_size=MAX_SIZE).map(lambda d: {v: k for (k, v) in d.items()}) # no dup vals bidict_t = sampled_from(bidict_types) mut_bidict_t = sampled_from(mutable_bidict_types) items = tuples(keys, vals) ItemLists = t.List[t.Tuple[int, int]] itemlists = lists(items, max_size=MAX_SIZE) # "lists" to allow testing updates with dup k and/or v updates_t = sampled_from(update_arg_types) itemsets = frozensets(items, max_size=MAX_SIZE) on_dups = tuple(starmap(OnDup, product(OnDupAction, repeat=2))) on_dup = sampled_from(on_dups) class BidictStateMachine(RuleBasedStateMachine): bi: MutableBidict[int, int] oracle: Oracle[int, int] @initialize(mut_bidict_t=mut_bidict_t, init_items=init_items) def init(self, mut_bidict_t: type[MutableBidict[int, int]], init_items: InitItems) -> None: self.bi = mut_bidict_t(init_items) self.oracle = Oracle(init_items, ordered=self.is_ordered()) def is_ordered(self) -> bool: return isinstance(self.bi, OrderedBidict) @invariant() def assert_match_oracle(self) -> None: note(f'> {self.bi=}\n> {self.oracle.data=}') self.oracle.assert_match(self.bi) viewnames = sampled_from(('keys', 'values', 'items')) # Would make this an invariant rather than a rule, but it slows down the tests too much. @rule(rand=randoms(), viewname=viewnames, set_op=sampled_from(SET_OPS), other_set=itemsets) def assert_views_match_oracle(self, rand: Random, viewname: str, set_op: t.Any, other_set: t.Any) -> None: check = getattr(self.bi, viewname)() expect = getattr(self.oracle.data, viewname)() if viewname != 'values' else self.oracle.data_inv.keys() assert len(check) == len(expect) if self.is_ordered(): assert zip_equal(check, expect) else: assert check == frozenset(expect) missing = ('foo', 'bar') if viewname == 'items' else 'foo' assert missing not in check if self.oracle.data: contained = rand.choice(tuple(expect)) assert contained in check if viewname != 'items': other_set = {k for (k, _) in other_set} assert_calls_match( partial(set_op, check, other_set), partial(set_op, expect, other_set), ) if viewname == 'items': other_set = self.bi.__class__(dedup(other_set)).items() assert_calls_match( partial(set_op, check, other_set), partial(set_op, expect, other_set), ) @invariant() def assert_bi_and_inv_are_inverse(self) -> None: assert_bi_and_inv_are_inverse(self.bi) @precondition(lambda self: should_be_reversible(self.bi.__class__)) @invariant() def assert_reversed_works(self) -> None: assert list(reversed(self.bi)) == list(self.bi)[::-1] assert list(reversed(self.bi.items())) == list(self.bi.items())[::-1] if self.is_ordered(): assert zip_equal(reversed(self.bi), reversed(self.oracle.data)) assert zip_equal(reversed(self.bi.items()), reversed(self.oracle.data.items())) assert zip_equal(reversed(self.bi.values()), reversed(self.oracle.data.values())) @rule() def copy(self) -> None: for cp in (copy(self.bi), deepcopy(self.bi)): assert_bi_and_inv_are_inverse(cp) assert_bidicts_equal(cp, self.bi) @rule() def pickle(self) -> None: for b in (self.bi, self.bi.inv): roundtripped = pickle.loads(pickle.dumps(b)) assert_bi_and_inv_are_inverse(roundtripped) assert_bidicts_equal(roundtripped, b) @rule() def clear(self) -> None: self.bi.clear() self.oracle.clear() @rule(key=keys, val=vals, on_dup=on_dup) def put(self, key: int, val: complex, on_dup: OnDup) -> None: assert_calls_match( partial(self.bi.put, key, val, on_dup), partial(self.oracle.put, key, val, on_dup), ) @rule(updates=itemlists, updates_t=updates_t, on_dup=on_dup) def putall(self, updates: MapOrItems[int, int], updates_t: t.Any, on_dup: OnDup) -> None: # Don't let the updates_t(updates) calls below raise a DuplicationError. if isinstance(updates_t, type) and issubclass(updates_t, BidirectionalMapping): updates = dedup(updates) # Since updates_t can be iter, can't extract the two updates_t(updates) calls below into a single value. assert_calls_match( partial(self.bi.putall, updates_t(updates), on_dup), partial(self.oracle.putall, updates_t(updates), on_dup), ) @rule(other=init_items) def __ior__(self, other: t.Mapping[KT, VT]) -> None: assert_calls_match( partial(self.bi.__ior__, other), partial(self.oracle.__ior__, other), ) @rule(other=init_items) def __or__(self, other: t.Mapping[KT, VT]) -> None: assert_calls_match( partial(self.bi.__or__, other), partial(self.oracle.__or__, other), ) # https://bidict.rtfd.io/basic-usage.html#order-matters @precondition(lambda self: zip_equal(self.bi, self.oracle.data)) @rule(other=init_items) def __ror__(self, other: t.Mapping[KT, VT]) -> None: assert_calls_match( partial(self.bi.__ror__, other), partial(self.oracle.__ror__, other), ) @precondition(lambda self: len(self.bi) >= 2) @rule(random=randoms()) def update_with_dup(self, random: Random) -> None: # Covered nondeterministically by the more general "putall" rule above, but this ensures that basic duplication # scenarios are deterministically covered. len_before = len(self.bi) # Choose two existing items at random. (k1, v1), (k2, v2) = random.sample(tuple(self.oracle.data.items()), 2) # Inserting (new_key, dup_val) should raise ValueDuplicationError. with pytest.raises(ValueDuplicationError): self.bi.update([('foo', 'foo'), ('bar', v1)]) # type: ignore[list-item] # Any partial update applied before the failure should have been rolled back (fails clean). assert len(self.bi) == len_before assert 'foo' not in self.bi # type: ignore[comparison-overlap] assert self.bi.inv[v1] != 'bar' # type: ignore[comparison-overlap] # key and value duplication across two different items should raise KeyAndValueDuplicationError. for key, val in ((k1, v2), (k2, v1)): with pytest.raises(KeyAndValueDuplicationError): self.bi.update([('foo', 'foo'), (key, val)]) # type: ignore[list-item] assert len(self.bi) == len_before assert 'foo' not in self.bi # type: ignore[comparison-overlap] assert self.bi[key] != val # Inserting already-present items should be a no-op. self.bi.update([(k1, v1), (k2, v2)]) assert len(self.bi) == len_before def is_empty(self) -> bool: return not self.bi @precondition(is_empty) @rule() def popitem_empty(self) -> None: with pytest.raises(KeyError): self.bi.popitem() def is_nonempty(self) -> bool: return bool(self.bi) @precondition(is_nonempty) @rule(last=booleans()) def popitem(self, last: bool) -> None: kw = {'last': last} if self.is_ordered() else {} popped_item = self.bi.popitem(**kw) self.oracle.pop(popped_item[0]) assert popped_item not in self.bi.items() if self.is_nonempty(): inv_popped_item = self.bi.inv.popitem(**kw) self.oracle.pop(inv_popped_item[1]) assert inv_popped_item not in self.bi.inv.items() @precondition(is_nonempty) @rule(random=randoms()) def pop_randkey(self, random: Random) -> None: key = random.choice(tuple(self.oracle.data)) expect = self.oracle.pop(key) check = self.bi.pop(key) assert check == expect @precondition(is_ordered) @precondition(is_nonempty) @rule(random=randoms(), last=booleans()) def move_to_end_randkey(self, random: Random, last: bool) -> None: key, val = random.choice(tuple(self.oracle.data.items())) self.bi.move_to_end(key, last=last) # type: ignore[attr-defined] self.oracle.move_to_end(key, last=last) it: t.Any = reversed if last else iter assert (key, val) == next(it(self.bi.items())) assert (val, key) == next(it(self.bi.inv.items())) assert (key, val) == next(it(self.oracle.data.items())) assert (val, key) == next(it(self.oracle.data_inv.items())) BidictStateMachineTest = BidictStateMachine.TestCase @pytest.mark.parametrize('bi_t', bidict_types) def test_init_and_update_with_bad_args(bi_t: BT[KT, VT]) -> None: for bad_args in ((None,), (0,), (False,), (True,), ({}, {})): # type: ignore[var-annotated] with pytest.raises(TypeError): bi_t(*bad_args) # type: ignore[arg-type] if not issubclass(bi_t, MutableBidict): continue bi = bi_t() with pytest.raises(TypeError): bi.update(*bad_args) # type: ignore[arg-type] @pytest.mark.parametrize('bi_t', bidict_types) def test_inv_attrs_readonly(bi_t: BT[KT, VT]) -> None: """Attempting to set .inverse or .inv should raise AttributeError.""" bi: t.Any = bi_t() with pytest.raises(AttributeError): bi.inverse = 'foo' with pytest.raises(AttributeError): bi.inv = 'foo' @pytest.mark.parametrize('bi_t', mutable_bidict_types) def test_pop_missing_key(bi_t: MBT[t.Any, t.Any]) -> None: bi = bi_t() with pytest.raises(KeyError): bi.pop('foo') assert bi.pop('foo', 'bar') == 'bar' @pytest.mark.parametrize('bi_t', [OrderedBidict, UserOrderedBi]) def test_move_to_end_missing_key(bi_t: type[OrderedBidict[KT, VT]]) -> None: bi = bi_t() with pytest.raises(KeyError): bi.move_to_end('foo') # type: ignore[arg-type] @pytest.mark.parametrize('bi_t', bidict_types) def test_eq_defers_to_other_eq(bi_t: BT[KT, VT]) -> None: """bidict.__eq__(other) should defer to other.__eq__ when other is not a mapping.""" # ANY.__eq__ always returns true, so this test will fail if bi_t.__eq__ fails to defer. assert bi_t() == ANY @pytest.mark.parametrize(('bi_t', 'non_mapping'), product(bidict_types, (None, 1, [], SupportsKeysAndGetItem({})))) def test_eq_and_or_with_non_mapping(bi_t: BT[KT, VT], non_mapping: t.Any) -> None: bi = bi_t() assert bi != non_mapping assert not bi.equals_order_sensitive(non_mapping) with pytest.raises(TypeError): bi | non_mapping with pytest.raises(TypeError): non_mapping | bi @given(init_items=init_items, bidict_t=bidict_t, rand=randoms()) def test_ne_ordsens_to_equal_map_with_diff_order(init_items: InitItems, bidict_t: BT[KT, VT], rand: Random) -> None: bi = bidict_t(init_items) items_shuf = list(init_items.items()) rand.shuffle(items_shuf) assume(not zip_equal(items_shuf, init_items.items())) map_shuf = dict(items_shuf) assert bi == map_shuf assert not bi.equals_order_sensitive(map_shuf) @given(items=itemlists, bidict_t=bidict_t) def test_inverted(items: ItemLists, bidict_t: BT[int, int]) -> None: check_list = list(inverted(inverted(items))) expect_list = items assert check_list == expect_list items_nodup = dedup(items) check_bi = bidict_t(inverted(bidict_t(items_nodup))) expect_bi = bidict_t({v: k for (k, v) in items_nodup.items()}) assert_bidicts_equal(check_bi, expect_bi) @given(init_items=init_items) def test_frozenbidicts_hashable(init_items: InitItems) -> None: """Frozen bidicts can be hashed (and therefore inserted into sets and mappings).""" bi = frozenbidict(init_items) h1 = hash(bi) h2 = hash(bi) assert h1 == h2 assert {bi} assert {bi: bi} bi2 = frozenbidict(init_items) assert bi2 == bi assert hash(bi2) == h1 # 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.) @pytest.mark.parametrize(('bi_t', 'on_dup'), product(mutable_bidict_types, on_dups)) def test_putall_matches_bulk_put(bi_t: type[MutableBidict[int, int]], on_dup: OnDup) -> None: init_items = {0: 0, 1: 1} bi = bi_t(init_items) for k1, v1, k2, v2 in product(range(4), repeat=4): for b in bi, bi.inv: assert_putall_matches_bulk_put(b, [(k1, v1), (k2, v2)], on_dup) def assert_putall_matches_bulk_put(bi: MutableBidict[int, int], new_items: ItemLists, on_dup: OnDup) -> None: tmp = bi.copy() checkexc = None expectexc = None try: for key, val in new_items: tmp.put(key, val, on_dup) except DuplicationError as exc: expectexc = type(exc) tmp = bi # Since bulk updates fail clean, expect no changes (i.e. revert to bi). try: bi.putall(new_items, on_dup) except DuplicationError as exc: checkexc = type(exc) assert checkexc == expectexc assert bi == tmp assert bi.inv == tmp.inv def test_pickle_orderedbi_whose_order_disagrees_with_fwdm() -> None: """An OrderedBidict whose order does not match its _fwdm's should pickle with the correct order.""" ob = OrderedBidict({0: 1, 2: 3}) # First get ob._fwdm's order to disagree with ob's: ob.inv[1] = 4 assert list(ob.items()) == [(4, 1), (2, 3)] assert list(ob._fwdm.items()) == [(2, 3), (4, 1)] # Now check that its order is preserved after pickling and unpickling: roundtripped = pickle.loads(pickle.dumps(ob)) assert list(roundtripped.items()) == [(4, 1), (2, 3)] assert roundtripped.equals_order_sensitive(ob) def test_pickle_dynamically_generated_inverse_bidict() -> None: """Instances of dynamically-generated inverse bidict classes should be pickleable.""" ub: MutableBidict[str, int] = UserBiNotOwnInv(one=1, two=2) roundtripped = pickle.loads(pickle.dumps(ub)) assert roundtripped == ub == UserBiNotOwnInv({'one': 1, 'two': 2}) assert dict(roundtripped) == dict(ub) # Now for the inverse: assert repr(ub.inverse) == "UserBiNotOwnInvInv({1: 'one', 2: 'two'})" # We can still pickle the inverse, even though its class, _UserBidictInv, was # dynamically generated, and we didn't save a reference to it named "_UserBidictInv" # anywhere that pickle could find it in sys.modules: ubinv = pickle.loads(pickle.dumps(ub.inverse)) assert repr(ubinv) == "UserBiNotOwnInvInv({1: 'one', 2: 'two'})" assert ub._inv_cls.__name__ not in (name for m in sys.modules for name in dir(m)) def test_abstract_bimap_init_fails() -> None: class AbstractBimap(BidirectionalMapping[t.Any, t.Any]): """Does not override `inverse` and therefore should not be instantiable.""" for bi_t in (BidirectionalMapping, MutableBidirectionalMapping, AbstractBimap): with pytest.raises(TypeError, match="Can't instantiate abstract class"): bi_t() def test_bimap_bad_inverse() -> None: # Overrides `inverse`, but merely calls the abstract superclass implementation. class BimapBadInverse(BidirectionalMapping[t.Any, t.Any]): __getitem__ = __iter__ = __len__ = ... # type: ignore [assignment] @property def inverse(self) -> t.Any: return super().inverse # type: ignore [safe-super] bi = BimapBadInverse() with pytest.raises(NotImplementedError): bi.inverse # noqa: B018 skip_if_pypy = pytest.mark.skipif( sys.implementation.name == 'pypy', reason='Requires CPython refcounting behavior', ) @skip_if_pypy @given(bidict_t=bidict_t) def test_bidicts_freed_on_zero_refcount(bidict_t: BT[KT, VT]) -> None: """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 = bidict_t() weak = weakref.ref(bi) assert weak() is not None del bi assert weak() is None finally: gc.enable() @skip_if_pypy @given(init_items=init_items) def test_orderedbidict_nodes_freed_on_zero_refcount(init_items: InitItems) -> None: """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 = OrderedBidict(init_items) nodes = weakref.WeakSet(ob._sntl.iternodes()) assert len(nodes) == len(ob) del ob assert len(nodes) == 0 finally: gc.enable() @given(init_items=init_items) def test_orderedbidict_nodes_consistent(init_items: InitItems) -> None: """The nodes in an ordered bidict's backing linked list should be the same as those in its backing mapping.""" ob = OrderedBidict(init_items) mapnodes = set(ob._node_by_korv.inverse) linkedlistnodes = set(ob._sntl.iternodes()) assert mapnodes == linkedlistnodes def test_abc_slots() -> None: """Bidict ABCs should define __slots__. Ref: https://docs.python.org/3/reference/datamodel.html#notes-on-using-slots Note: non-abstract bidict types do not define __slots__ as of v0.22.0. """ assert BidirectionalMapping.__dict__['__slots__'] == () assert MutableBidirectionalMapping.__dict__['__slots__'] == () @pytest.mark.parametrize('bi_t', bidict_types) def test_inv_aliases_inverse(bi_t: BT[KT, VT]) -> None: """bi.inv should alias bi.inverse.""" bi = bi_t() assert bi.inverse is bi.inv assert bi.inv.inverse is bi.inverse.inv def test_static_types() -> None: d = {'1': 1} fb = frozenbidict(d) assert_type(fb, frozenbidict[str, int]) assert_type(fb.inv, frozenbidict[int, str]) def assert_calls_match(call1: t.Callable[..., t.Any], call2: t.Callable[..., t.Any]) -> None: results: dict[t.Any, t.Any] = {call1: None, call2: None} for call in results: try: results[call] = call() except Exception as exc: # noqa: BLE001, PERF203 results[call] = exc.__class__ assert results[call1] == results[call2] def assert_mappings_are_inverse(m1: t.Mapping[KT, VT], m2: t.Mapping[VT, KT]) -> None: assert len(m1) == len(m2) assert all(k == m2[v] for (k, v) in m1.items()) assert m1.keys() == frozenset(m2.values()) assert frozenset(m1.values()) == m2.keys() def assert_bi_and_inv_are_inverse(bi: BB[KT, VT]) -> None: assert_mappings_are_inverse(bi, bi.inv) assert bi is bi.inv.inv assert bi.inv is bi.inv.inv.inv def assert_bidicts_equal(b1: BB[KT, VT], b2: BB[KT, VT]) -> None: assert b1 == b2 assert b1.inv == b2.inv assert_mappings_are_inverse(b1, b2.inv) assert_mappings_are_inverse(b1.inv, b2) bidict-0.23.1/tox.ini000066400000000000000000000041421456445164300143630ustar00rootroot00000000000000[tox] envlist = # Use "python3.X" rather than "py3X" so env names match python executable names python3.{12,11,10,9,8} pypy3.{10,9} benchmark lint docs # do not include 'coverage' so it only runs when explicitly requested skip_missing_interpreters = true # Keep in sync with ./dev-deps/py_ver.env: _default_py_minor_ver = 12 _default_req_dir = dev-deps/python3.{[tox]_default_py_minor_ver} [testenv] deps = -r dev-deps/{envname}/test.txt # https://hynek.me/articles/turbo-charge-tox/ package = wheel wheel_build_env = .pkg passenv = # https://docs.pytest.org/en/stable/example/simple.html PYTEST_ADDOPTS # https://docs.pytest.org/en/7.1.x/reference/reference.html#envvar-FORCE_COLOR FORCE_COLOR # https://hypothesis.readthedocs.io/en/latest/settings.html#hypothesis.settings.print_blob # https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables CI commands = mypy bidict tests pytest [testenv:python3.12] base_python = py312 [testenv:python3.11] base_python = py311 [testenv:python3.10] base_python = py310 [testenv:python3.9] base_python = py39 [testenv:python3.8] base_python = py38 [testenv:benchmark] base_python = py3{[tox]_default_py_minor_ver} deps = -r {[tox]_default_req_dir}/test.txt commands = pytest -n0 --benchmark-autosave tests/microbenchmarks.py [testenv:lint] base_python = py3{[tox]_default_py_minor_ver} deps = pre-commit skip_install = true commands = pre-commit run --all-files --verbose --show-diff-on-failure [testenv:docs] base_python = py3{[tox]_default_py_minor_ver} deps = -r {[tox]_default_req_dir}/docs.txt commands = sphinx-build -Wn --keep-going -b html -d {envtmpdir}/doctrees docs {envtmpdir}/html [testenv:coverage] base_python = py3{[tox]_default_py_minor_ver} deps = -r {[tox]_default_req_dir}/test.txt setenv = # https://coverage.readthedocs.io/en/7.4.0/changes.html#version-7-4-0-2023-12-27 COVERAGE_CORE=sysmon # https://hynek.me/articles/turbo-charge-tox/#coverage-enable-subprocess COVERAGE_PROCESS_START={toxinidir}/.coveragerc commands = coverage run -m pytest coverage combine coverage report
  • GitHub Repository