From 3d4d5bbb81f55c4e6741ee22e6fd1a903bde020b Mon Sep 17 00:00:00 2001 From: Sakis K Date: Fri, 24 Jul 2026 20:02:06 +0200 Subject: [PATCH] Fix failed tests, linter errors, and restructure (#477) * Switch development dependencies to Pipenv lockfile - add Pipfile and Dependabot-compatible Pipfile.lock - update lint, tox, CI, Travis, and Makefile workflows - support Python 3.10 through 3.14 - remove the pip-tools requirements lockfile - standardize linting, type checking, and tests * Streamline GitHub Actions CI workflow - consolidate duplicate lint and PR workflows - run strict linting, type checking, and tests - test against Python 3.10 through 3.14 - install dependencies from Pipfile.lock - add caching, timeouts, minimal permissions, and concurrency control - remove ignored failures and fragile changed-file logic --- .github/dependabot.yml | 12 + .github/workflows/lint_pr.yml | 288 ----- .github/workflows/lint_python.yml | 82 +- .travis.yml | 14 +- Makefile | 23 +- Pipfile | 21 + Pipfile.lock | 813 +++++++++++++ README.md | 91 +- config_backup/tox.ini | 6 +- lint.sh | 48 +- patterns/behavioral/catalog.py | 4 +- .../behavioral/chain_of_responsibility.py | 14 +- patterns/behavioral/command.py | 8 +- patterns/behavioral/mediator.py | 4 +- patterns/behavioral/memento.py | 13 +- patterns/behavioral/observer.py | 4 +- patterns/behavioral/publish_subscribe.py | 4 +- patterns/behavioral/registry.py | 5 +- patterns/behavioral/state.py | 9 +- patterns/behavioral/strategy.py | 19 +- patterns/behavioral/visitor.py | 11 +- patterns/creational/abstract_factory.py | 5 +- patterns/creational/borg.py | 6 +- patterns/creational/builder.py | 7 +- patterns/creational/factory.py | 4 +- patterns/creational/lazy_evaluation.py | 24 +- patterns/creational/pool.py | 8 +- patterns/dependency_injection.py | 4 +- patterns/fundamental/delegation_pattern.py | 7 +- patterns/other/blackboard.py | 16 +- patterns/other/graph_search.py | 31 +- patterns/structural/3-tier.py | 6 +- patterns/structural/adapter.py | 5 +- patterns/structural/bridge.py | 5 +- patterns/structural/composite.py | 3 +- patterns/structural/flyweight.py | 7 +- patterns/structural/mvc.py | 2 +- patterns/structural/proxy.py | 4 +- poetry.lock | 1029 ----------------- pyproject.toml | 36 +- requirements-dev.txt | 8 - tests/behavioral/test_catalog.py | 29 +- tests/behavioral/test_mediator.py | 9 +- tests/behavioral/test_memento.py | 7 +- tests/behavioral/test_publish_subscribe.py | 21 +- tests/behavioral/test_servant.py | 6 +- tests/behavioral/test_visitor.py | 10 +- tests/creational/test_abstract_factory.py | 5 +- tests/creational/test_borg.py | 18 +- tests/creational/test_builder.py | 18 +- tests/creational/test_factory.py | 28 +- tests/creational/test_lazy.py | 38 +- tests/creational/test_pool.py | 37 +- tests/creational/test_prototype.py | 28 +- tests/fundamental/test_delegation.py | 2 +- tests/structural/test_adapter.py | 28 +- tests/structural/test_bridge.py | 15 +- tests/structural/test_decorator.py | 20 +- tests/structural/test_mvc.py | 2 +- tests/structural/test_proxy.py | 34 +- tests/test_hsm.py | 47 +- 61 files changed, 1329 insertions(+), 1783 deletions(-) create mode 100644 .github/dependabot.yml delete mode 100644 .github/workflows/lint_pr.yml create mode 100644 Pipfile create mode 100644 Pipfile.lock delete mode 100644 poetry.lock delete mode 100644 requirements-dev.txt diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..b565ddcb8 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: + - package-ecosystem: pip + directory: / + schedule: + interval: weekly + versioning-strategy: increase-if-necessary + + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly diff --git a/.github/workflows/lint_pr.yml b/.github/workflows/lint_pr.yml deleted file mode 100644 index 3942ffced..000000000 --- a/.github/workflows/lint_pr.yml +++ /dev/null @@ -1,288 +0,0 @@ -name: lint_pull_request -on: [pull_request, push] -jobs: - check_changes: - runs-on: ubuntu-24.04 - outputs: - has_python_changes: ${{ steps.changed-files.outputs.has_python_changes }} - files: ${{ steps.changed-files.outputs.files }} - steps: - - uses: actions/checkout@v6 - with: - fetch-depth: 0 # To get all history for git diff commands - - - name: Get changed Python files - id: changed-files - run: | - if [ "${{ github.event_name }}" == "pull_request" ]; then - # For PRs, compare against base branch - CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRT origin/${{ github.base_ref }} HEAD | grep '\.py$' | grep -v "^setup\.py$" || echo "") - # Check if setup.py specifically changed - SETUP_PY_CHANGED=$(git diff --name-only --diff-filter=ACMRT origin/${{ github.base_ref }} HEAD | grep "^setup\.py$" || echo "") - if [ ! -z "$SETUP_PY_CHANGED" ]; then - CHANGED_FILES="$CHANGED_FILES $SETUP_PY_CHANGED" - fi - else - # For pushes, use the before/after SHAs - CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRT ${{ github.event.before }} ${{ github.event.after }} | grep '\.py$' | grep -v "^setup\.py$" || echo "") - # Check if setup.py specifically changed - SETUP_PY_CHANGED=$(git diff --name-only --diff-filter=ACMRT ${{ github.event.before }} ${{ github.event.after }} | grep "^setup\.py$" || echo "") - if [ ! -z "$SETUP_PY_CHANGED" ]; then - CHANGED_FILES="$CHANGED_FILES $SETUP_PY_CHANGED" - fi - fi - - # Check if any Python files were changed and set the output accordingly - if [ -z "$CHANGED_FILES" ]; then - echo "No Python files changed" - echo "has_python_changes=false" >> $GITHUB_OUTPUT - echo "files=" >> $GITHUB_OUTPUT - else - echo "Changed Python files: $CHANGED_FILES" - echo "has_python_changes=true" >> $GITHUB_OUTPUT - # Use proper delimiter formatting for GitHub Actions - FILES_SINGLE_LINE=$(echo "$CHANGED_FILES" | tr '\n' ' ' | sed 's/[[:space:]]\+/ /g' | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//') - echo "files=$FILES_SINGLE_LINE" >> $GITHUB_OUTPUT - fi - - - name: PR information - if: ${{ github.event_name == 'pull_request' }} - run: | - if [[ "${{ steps.changed-files.outputs.has_python_changes }}" == "true" ]]; then - echo "This PR contains Python changes that will be linted." - else - echo "This PR contains no Python changes, but still requires manual approval." - fi - - lint: - needs: check_changes - if: ${{ needs.check_changes.outputs.has_python_changes == 'true' }} - runs-on: ubuntu-24.04 - strategy: - fail-fast: false - matrix: - tool: [flake8, format, mypy, pytest, pyupgrade, tox] - steps: - # Additional check to ensure we have Python files before proceeding - - name: Verify Python changes - run: | - if [[ "${{ needs.check_changes.outputs.has_python_changes }}" != "true" ]]; then - echo "No Python files were changed. Skipping linting." - exit 0 - fi - - - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - uses: actions/setup-python@v6 - with: - python-version: 3.12 - - - uses: actions/cache@v5 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('requirements-dev.txt') }} - restore-keys: | - ${{ runner.os }}-pip- - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements-dev.txt - - # Flake8 linting - - name: Lint with flake8 - if: ${{ matrix.tool == 'flake8' }} - id: flake8 - run: | - echo "Linting files: ${{ needs.check_changes.outputs.files }}" - flake8 ${{ needs.check_changes.outputs.files }} --count --show-source --statistics - - # Format checking with isort and black - - name: Format check - if: ${{ matrix.tool == 'format' }} - id: format - run: | - echo "Checking format with isort for: ${{ needs.check_changes.outputs.files }}" - isort --profile black --check ${{ needs.check_changes.outputs.files }} - echo "Checking format with black for: ${{ needs.check_changes.outputs.files }}" - black --check ${{ needs.check_changes.outputs.files }} - - # Type checking with mypy - - name: Type check with mypy - if: ${{ matrix.tool == 'mypy' }} - id: mypy - run: | - echo "Type checking: ${{ needs.check_changes.outputs.files }}" - mypy --ignore-missing-imports ${{ needs.check_changes.outputs.files }} - - # Run tests with pytest - - name: Run tests with pytest - if: ${{ matrix.tool == 'pytest' }} - id: pytest - run: | - echo "Running pytest discovery..." - python -m pytest --collect-only -v - - # First run any test files that correspond to changed files - echo "Running tests for changed files..." - changed_files="${{ needs.check_changes.outputs.files }}" - - # Extract module paths from changed files - modules=() - for file in $changed_files; do - # Convert file path to module path (remove .py and replace / with .) - if [[ $file == patterns/* ]]; then - module_path=${file%.py} - module_path=${module_path//\//.} - modules+=("$module_path") - fi - done - - # Run tests for each module - for module in "${modules[@]}"; do - echo "Testing module: $module" - python -m pytest -xvs tests/ -k "$module" || true - done - - # Then run doctests on the changed files - echo "Running doctests for changed files..." - for file in $changed_files; do - if [[ $file == *.py ]]; then - echo "Running doctest for $file" - python -m pytest --doctest-modules -v $file || true - fi - done - - # Check Python version compatibility - - name: Check Python version compatibility - if: ${{ matrix.tool == 'pyupgrade' }} - id: pyupgrade - run: pyupgrade --py312-plus ${{ needs.check_changes.outputs.files }} - - # Run tox - - name: Run tox - if: ${{ matrix.tool == 'tox' }} - id: tox - run: | - echo "Running tox integration for changed files..." - changed_files="${{ needs.check_changes.outputs.files }}" - - # Create a temporary tox configuration that extends the original one - echo "[tox]" > tox_pr.ini - echo "envlist = py312" >> tox_pr.ini - echo "skip_missing_interpreters = true" >> tox_pr.ini - - echo "[testenv]" >> tox_pr.ini - echo "setenv =" >> tox_pr.ini - echo " COVERAGE_FILE = .coverage.{envname}" >> tox_pr.ini - echo "deps =" >> tox_pr.ini - echo " -r requirements-dev.txt" >> tox_pr.ini - echo "allowlist_externals =" >> tox_pr.ini - echo " pytest" >> tox_pr.ini - echo " coverage" >> tox_pr.ini - echo " python" >> tox_pr.ini - echo "commands =" >> tox_pr.ini - - # Check if we have any implementation files that changed - pattern_files=0 - test_files=0 - - for file in $changed_files; do - if [[ $file == patterns/* ]]; then - pattern_files=1 - elif [[ $file == tests/* ]]; then - test_files=1 - fi - done - - # Only run targeted tests, no baseline - echo " # Run specific tests for changed files" >> tox_pr.ini - - has_tests=false - - # Add coverage-focused test commands - for file in $changed_files; do - if [[ $file == *.py ]]; then - # Run coverage tests for implementation files - if [[ $file == patterns/* ]]; then - module_name=$(basename $file .py) - - # Get the pattern type (behavioral, structural, etc.) - if [[ $file == patterns/behavioral/* ]]; then - pattern_dir="behavioral" - elif [[ $file == patterns/creational/* ]]; then - pattern_dir="creational" - elif [[ $file == patterns/structural/* ]]; then - pattern_dir="structural" - elif [[ $file == patterns/fundamental/* ]]; then - pattern_dir="fundamental" - elif [[ $file == patterns/other/* ]]; then - pattern_dir="other" - else - pattern_dir="" - fi - - echo " # Testing $file" >> tox_pr.ini - - # Check if specific test exists - if [ -n "$pattern_dir" ]; then - test_path="tests/${pattern_dir}/test_${module_name}.py" - echo " if [ -f \"${test_path}\" ]; then echo \"Test file ${test_path} exists: true\" && coverage run -m pytest -xvs --cov=patterns --cov-append ${test_path}; else echo \"Test file ${test_path} exists: false\"; fi" >> tox_pr.ini - - # Also try to find any test that might include this module - echo " coverage run -m pytest -xvs --cov=patterns --cov-append tests/${pattern_dir}/ -k \"${module_name}\" --no-header" >> tox_pr.ini - fi - - # Run doctests for the file - echo " coverage run -m pytest --doctest-modules -v --cov=patterns --cov-append $file" >> tox_pr.ini - - has_tests=true - fi - - # Run test files directly if modified - if [[ $file == tests/* ]]; then - echo " coverage run -m pytest -xvs --cov=patterns --cov-append $file" >> tox_pr.ini - has_tests=true - fi - fi - done - - # If we didn't find any specific tests to run, mention it - if [ "$has_tests" = false ]; then - echo " python -c \"print('No specific tests found for changed files. Consider adding tests.')\"" >> tox_pr.ini - # Add a minimal test to avoid failure, but ensure it generates coverage data - echo " coverage run -m pytest -xvs --cov=patterns --cov-append -k \"not integration\" --no-header" >> tox_pr.ini - fi - - # Add coverage report command - echo " coverage combine" >> tox_pr.ini - echo " coverage report -m" >> tox_pr.ini - - # Run tox with the custom configuration - echo "Running tox with custom PR configuration..." - echo "======================== TOX CONFIG ========================" - cat tox_pr.ini - echo "===========================================================" - tox -c tox_pr.ini - - summary: - needs: [check_changes, lint] - # Run summary in all cases, regardless of whether lint job ran - if: ${{ always() }} - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v6 - - - name: Summarize results - run: | - echo "## Pull Request Lint Results" >> $GITHUB_STEP_SUMMARY - if [[ "${{ needs.check_changes.outputs.has_python_changes }}" == "true" ]]; then - echo "Linting has completed for all Python files changed in this PR." >> $GITHUB_STEP_SUMMARY - echo "See individual job logs for detailed results." >> $GITHUB_STEP_SUMMARY - else - echo "No Python files were changed in this PR. Linting was skipped." >> $GITHUB_STEP_SUMMARY - fi - echo "" >> $GITHUB_STEP_SUMMARY - echo "⚠️ **Note:** This PR still requires manual approval regardless of linting results." >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/lint_python.yml b/.github/workflows/lint_python.yml index 4e2f16be0..e5ef51b51 100644 --- a/.github/workflows/lint_python.yml +++ b/.github/workflows/lint_python.yml @@ -1,36 +1,62 @@ -name: lint_python -on: [pull_request, push] +name: Python CI + +on: + push: + pull_request: + +permissions: + contents: read + +concurrency: + group: python-ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: - lint_python: + quality: + name: Lint, type-check, and test runs-on: ubuntu-24.04 + timeout-minutes: 15 steps: - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 with: - python-version: 3.12 - - name: Install dependencies + python-version: "3.12" + cache: pip + cache-dependency-path: Pipfile.lock + + - name: Run quality checks + run: ./lint.sh + + tests: + name: Test on Python ${{ matrix.python-version }} + runs-on: ubuntu-24.04 + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + python-version: + - "3.10" + - "3.11" + - "3.12" + - "3.13" + - "3.14" + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + cache: pip + cache-dependency-path: Pipfile.lock + + - name: Install locked dependencies run: | python -m pip install --upgrade pip - pip install .[dev] - - name: Lint with flake8 - run: flake8 ./patterns --count --show-source --statistics - continue-on-error: true - - name: Format check with isort and black - run: | - isort --profile black --check ./patterns - black --check ./patterns - continue-on-error: true - - name: Type check with mypy - run: mypy --ignore-missing-imports ./patterns || true - continue-on-error: true - - name: Run tests with pytest - run: | - pytest ./patterns - pytest --doctest-modules ./patterns || true - continue-on-error: true - - name: Check Python version compatibility - run: shopt -s globstar && pyupgrade --py312-plus ./patterns/**/*.py - continue-on-error: true - - name: Run tox - run: tox - continue-on-error: true + python -m pip install pipenv + pipenv requirements --dev > "${RUNNER_TEMP}/requirements-dev.txt" + python -m pip install -r "${RUNNER_TEMP}/requirements-dev.txt" + python -m pip install --no-build-isolation --no-deps -e . + + - name: Run tests + run: python -m pytest tests patterns diff --git a/.travis.yml b/.travis.yml index dfeece703..ecd1fb556 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,16 +2,20 @@ os: linux dist: noble language: python -jobs: - include: - - python: "3.12" - env: TOXENV=py312 +python: + - "3.10" + - "3.11" + - "3.12" + - "3.13" + - "3.14" cache: - pip install: - - pip install codecov tox + - pip install pipenv + - pipenv sync --dev --system + - pip install --no-build-isolation --no-deps -e . script: - tox diff --git a/Makefile b/Makefile index 92ba244aa..5b48aaffb 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ # Usage (line =black line length, path = action path, ignore= exclude folders) # ------ # make pylinter [make pylinter line=88 path=.] -# make pyupgrade +# make lock path := . line := 88 @@ -25,22 +25,11 @@ ifeq ("$(VIRTUAL_ENV)","") exit 1 endif -.PHONY: pyupgrade -pyupgrade: checkvenv -# checks if pip-tools is installed -ifeq ("$(wildcard venv/bin/pip-compile)","") - @echo "Installing Pip-tools..." - @pip install pip-tools -endif - -ifeq ("$(wildcard venv/bin/pip-sync)","") - @echo "Installing Pip-tools..." - @pip install pip-tools -endif - -# pip-tools - # @pip-compile --upgrade requirements-dev.txt - @pip-sync requirements-dev.txt +.PHONY: lock +lock: checkvenv + @command -v pipenv >/dev/null || python -m pip install pipenv + @pipenv lock --dev + @pipenv sync --dev .PHONY: pylinter diff --git a/Pipfile b/Pipfile new file mode 100644 index 000000000..4896442e0 --- /dev/null +++ b/Pipfile @@ -0,0 +1,21 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[packages] + +[dev-packages] +black = ">=25.1.0" +build = ">=1.2.2" +codecov = "*" +codespell = "*" +flake8 = ">=7.1.0" +isort = ">=5.7.0" +mypy = "*" +pipx = ">=1.7.1" +pyupgrade = "*" +pytest = ">=6.2.0" +pytest-cov = ">=2.11.0" +pytest-randomly = ">=3.1.0" +tox = ">=4.25.0" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 000000000..7f02e674c --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,813 @@ +{ + "_meta": { + "hash": { + "sha256": "ad3364694cc5b9ce231a66480b4034c72a47f3761f604c9f16d8e9ae83f9977f" + }, + "pipfile-spec": 6, + "requires": {}, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": {}, + "develop": { + "argcomplete": { + "hashes": [ + "sha256:afde224f753f874807b1dc1414e883ab8fe0cda9c04807b6047dcb8e1ac23913", + "sha256:d8f0f22d2a8a7caa383be1e22b6caf1ecaf0ebd10d8f83cc125e36540c95830c" + ], + "markers": "python_version >= '3.8'", + "version": "==3.7.0" + }, + "ast-serialize": { + "hashes": [ + "sha256:0067b25fce104eaae5b88383de9ab803faeb671831e14ca698b771b356e2600f", + "sha256:085de7f62dc9cc247eb01e965a362707d1d90b1d89a82c5bf78301a60a3c417b", + "sha256:093cb8bb91b720d8523580498d031791bb1bbaa048599c3d21085d380e11a596", + "sha256:113b58346f9ceb664352032770caca817d4a3c86f611c6088e6ef65ddaa70f0e", + "sha256:13b7769970a39983b0adf2f38917b1cd3b8946f76df045756c3d741bc689f089", + "sha256:2d947e45cafc4b09bd7528917fa84c517654a43de173c79785574b7b3068ac24", + "sha256:305802f2ce2a7c4e87835078ea85c58b586ddda8095b92fe2ead9364ae19c80a", + "sha256:32ef62ec34cf6be20ad77d4799556638fbdf187f3ae10698dfb20ef9f2c89516", + "sha256:3ae22a366b752ab4496191525b78b097b5b72d531752e3c1dd7e383a8f2c8a1a", + "sha256:4d6ef91590258ada18909b9caea344dac4de2013906b035473cd674a43f4b790", + "sha256:4ed29121da8b3fdc291002801a1de0f76248fa07dce89157a5f277842cf6126e", + "sha256:5337cb256dcea3df9288205213d1601581536526b8f4da44b6974f1180f3252a", + "sha256:577a6c189068686869f5f1ddc38363f3ae1808a4753b577266f9202071a7bb66", + "sha256:6e15ec740436e1a0d62de848641abe5f3a2f89a7f94907d534795ac91bbacf14", + "sha256:6f7a408601bb3edaefb3bc67a4c01f5235e3253653b6a5729a2ee2382b35341c", + "sha256:82c312a7844d2fdeb4d5c48bd3d215bf940dafd4704e1a9bcf252a99010a99b1", + "sha256:8670bfa51208a2c0c8d138928e40e998fab158f9200d53bb80c088b5b8eda7b8", + "sha256:897ac47b5637be41c0c07061c8a912fafa967ef1dc73fa115e4bfa70882a093b", + "sha256:9f8a8b78b13173de6a9ec22111d9be674874cd5bdccda04f14ae5ebc2bef403a", + "sha256:a14191beec7e0c078d2fc1f6edc0aee88bcd4db9f18e1bc9f8052b559c22dddc", + "sha256:a4826809eb8597a8cd59fd924b6d7c285b8969a1e0007e2cb652cab62376270f", + "sha256:a7520b672827885bafeae7501f684d14d47d17e5f45256f9df547686cca52264", + "sha256:aadd3ffcf4858c9726bf3515f7b199c7eadbe504f96028e4a87172c0da65a8fe", + "sha256:b1dac4e09d341c1300ba69cdcbe62867b32a8c75d90db9bf4d083bec3b039f0b", + "sha256:c4af9a1386166e40ed01464991806f89038a2d89782576c7774876fa77034e32", + "sha256:c617417f9cbb0cb144f6283c3cbe0d2e0f01beaf9f608f662b21191058a626ec", + "sha256:c7b8b8f0c42f752ea00b2b7d7c090b3f80d9c1c5c75cadf16423790a0cc74081", + "sha256:c901adbd750029b9ac4ad3d6aa56853e0ad4875119fbf52b7b8298afc223828b", + "sha256:ccd132fe8db56f61fe743b1f644d01b8d65b83248a8da506f3132bda86d6ed5e", + "sha256:cd5b91b9e6f2356ace3a556963b0cd783b395fbbb0bb17b4defc283415466e77", + "sha256:cdc4e6f930b9090c2f92c9036ad12ffb8e6e44d4a5ba06f1458a05d60f203f7b", + "sha256:dcbed41e9386059fc0261d602445ede0976c2ecec2939688bcbcb9ed0b6f28b7", + "sha256:e61580a69faf47e3689795367ed211f2a10fd741478cc0f36a0f128793360aad", + "sha256:f2ff3baffc3a29c1f15bc9098aa0c09763410262d5e6cef42116f7356c184554" + ], + "markers": "python_version >= '3.7'", + "version": "==0.6.0" + }, + "black": { + "hashes": [ + "sha256:0e48b87e03bf109288e55cfceadcfa15ff5470aca2851a851950ed2926f450d7", + "sha256:1037d5ac7b7b310b2632ad867ec8d0e4c4819dcdb0b820f63135da746a24e418", + "sha256:1ef92b76f7733f282fd096ea406200b5a286c42947412b0eaff3a74e3616cefe", + "sha256:1f7ea64ebfa01b50f693508fc39f875e264446d3b097088f84f203b9d09618a0", + "sha256:22f2cd76d069cc54c71f10360744ba8983fbb616903b4304a85b734915c8e1b4", + "sha256:2b36cf2ddf5566e205f6535f782a62194a184d33e175b64ae8c40b1737522be3", + "sha256:30d3c14661f2792e9142cce3eeeb1cbc175b3eb5f733be0c8eeb99651e52b0c3", + "sha256:32d5ea7f6c8bdfa6e648326ebca1f02b0764e2a029edc6f8dce2627e19d468c3", + "sha256:3915f256e75a2d7cf88d8953d37f780455dc586cc72dee059c528fe77f581217", + "sha256:4ad6fa01f941920f54f2bbb35f3df7673428a0ef98a0b0840c2eaef3b110efa8", + "sha256:4ed7f7da04046d2e488437170797d3b4a4ad83906683bcb7dfc68b673bbce5e2", + "sha256:5119fa92ae61f786e8c3662fd60aece1d0a2dd5cca5d0c79417a95e7a4272a59", + "sha256:577f21094ea469ef92ec1adaf2c9441a226d2144d01a5be2fa823cecf6543e50", + "sha256:58b4bd92cf88aacf83d88479c8f9caee044b1ec55f2451a337354a7ea2590a22", + "sha256:5c34b25da232ead53a6f335b76dbea124f4d152ad568b9080d6f944bc2b34b52", + "sha256:87ed5c6f450580a2f6790bc7cbfb016dfc73bc750249762268a3695361315eef", + "sha256:89c93167a74d3a75dfaa38a5c7cca015537d5820dd7f17d63267d674a61cae90", + "sha256:96ae2c733b2aabdd9986e2c5df628ff3473676cd1c5faded1ff496cf6d74083c", + "sha256:9942db8888e06943c5dde66ca0037dcff82a2a4ec1ad0ada9e0d2ee9d9823893", + "sha256:9d98d4137277c75dfb898ec8d846c4fd68ba1e9cf77f95e2865c203dc18f4c3d", + "sha256:a1dca32d9f1784af512a13410ec204c6f7f0aa9797a111c42e1c03449821c264", + "sha256:dd321f668053961824bcc1be1cc1df748b2d7e4fa28086b08331e577b0100a73", + "sha256:e1a26503279b6b310669fb0b219c39e4820b77e8189fe80f522bb511f247db0a", + "sha256:e88976690a64b0af98312ca958415849cb42423423c5f2ee74af4b49a97a2168", + "sha256:ea8d16dc41655aa113cd64665e7219446cd7e4ff2248d7178eaa905190c86b18", + "sha256:ecb3e624844c798144e9bd986954e0adc81d8911a1f30f375e1252fe26e8c294", + "sha256:ed1a20af114c301a0269bf01163d51dbef72737fd65f850001e7cbe7f3c7abae" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==26.5.1" + }, + "build": { + "hashes": [ + "sha256:13f3eecb844759ab66efec90ca17639bbf14dc06cb2fdf37a9010322d9c50a6f", + "sha256:302c22c3ba2a0fd5f3911918651341ebb3896176cbdec15bd421f80b1afc7647" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==1.5.0" + }, + "cachetools": { + "hashes": [ + "sha256:2c12e255780330af28b91bb7fb96cce4c766f04e38396b9a24510190a5827096", + "sha256:c7a79e7f30ba9943c1cefd08cc36f006aaae086e017af9166f1d59d6170c47e1" + ], + "markers": "python_version >= '3.10'", + "version": "==7.1.6" + }, + "certifi": { + "hashes": [ + "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", + "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55" + ], + "markers": "python_version >= '3.7'", + "version": "==2026.7.22" + }, + "charset-normalizer": { + "hashes": [ + "sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380", + "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62", + "sha256:04ce310cb89c15df659582aee80a0603788732a5e017d5bd5c81158106ce249c", + "sha256:0d861473f743244d349b50f850d10eb87aeb22bbdcc8e64f79273c94af5a8226", + "sha256:0e94703ec9684807f20cfb5eed95c70f67f2a8f21ad620146d7b5a13677b93e5", + "sha256:0fa1aec2d32bcc03c8fa0f6f1712caad1adc38509f31142112e5c9daf5b9c833", + "sha256:16b65ea0f2465b6fb52aa22de5eca612aa964ddfec00a912e26f4656cbef890b", + "sha256:16d10d789dd9bcca1173c95af82c58433122564b7bc39385124be735a35cbe99", + "sha256:19ac87f93086ce37b86e098888555c4b4bc48102279bae3350098c0ed664b501", + "sha256:1d22856ffbe153a602df38e4a5464f0b748a54002e0d69ac6d2ad0a197cc99ec", + "sha256:21e764fd1e70b6a3e205a0e46f3051701f98a8cb3fad66eeb80e48bb502f8698", + "sha256:231ddcbb35e2ff8973e1365db41fe0572662893b99a05deb183b68ad4c0c8bd4", + "sha256:253a4a220747e8b5faf57ec320c4f5efb0cef05f647420bf267143ec15dba10a", + "sha256:280081916dc341820640489a66e4696049401ef1cf6dd672f672e70ad915aca3", + "sha256:2a441ea71902098ffe78c5abe6c494f44160b4af614ed16c3d9a3b1d17fd8ee2", + "sha256:304b13570067b2547562e308af560b3963857b1fa90bd6afd978130130fe2d6a", + "sha256:32286a2c8d167e897177b673176c1e3e00d4057caf5d2b64eef9a3666b03018e", + "sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4", + "sha256:375b83ed0aecfce76c16d198fbc21f3b11b337d68662bea0a995046682a11419", + "sha256:3c09a49d6cde137258beb3d551994a2927fd35ad5cf96aed573f61bbd67c5f84", + "sha256:3d92613ec25e43b05f042302531ec0f00b8445190e43325880cbd6ab7c2581da", + "sha256:40a126142a56b2dfc0aacbad1de8310cbf60da7656db0e6b16eebd48e3e93519", + "sha256:416c229f77e5ea25b3dfd4b582f8d73d7e43c22320302b9ab128a2d3a0b38efe", + "sha256:432786d3561e69aeeae6c7e8648964ce0ad05736120135601f87ac26b9c83381", + "sha256:43b9e366a31fdd1c87d0eb08f579b4a82b723ea54338f040d6b4e518a026ea29", + "sha256:440eede837960000d74978f0eba527be106b5b9aee0daf779d395276ed0b0614", + "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0", + "sha256:476743fe6dfe14a2da12e3ac79125dc84a3b2cf8094369a47a1529b0cd8549fe", + "sha256:4773092f8019072343a7447203308b176e10199920eb02d6195e81bbb3274c29", + "sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0", + "sha256:4d1c96a7a18b9690a4d46df09e3e3382406ae3213727cd1019ebade1c4a81917", + "sha256:51307f5c71007673a2bf8232ad973483d281e74cb99c8c5a990af1eefa6277d9", + "sha256:51447e9aa2684679af07ca5021c3db526e0284347ebf4ffcec1154c3350cfe32", + "sha256:58150c9f9b9a552505912d182ccdf26f6396fb6094816ceebcbb20eecabaed94", + "sha256:5b10cd92fc5c498b35a8635df6d5a100207f88b63a4dc1de7ef9a548e1e2cd63", + "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd", + "sha256:609b3ba8fcc0fb5ab7af00719d0fb6ad0cb518e48e7712d12fd68f1327951198", + "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde", + "sha256:611057cc5d5c0afc743ba8be6bd828c17e0aaa8643f9d0a9b9bb7dea80eb8012", + "sha256:6366a16e1a25018694d6a5d784d09b046edc9eac40ea2b54065c3052672516a1", + "sha256:65a7ff3f705e57d392f7261b6d0550fe137c3019477431f1c355e0db0a7d3e15", + "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b", + "sha256:67830fc78e67501f47bb950471b2dcb9b35b140084429318e862895a8e89c993", + "sha256:68ce9f4d6b26d5ccbf7fd4459bf75f74a0a146677ebba80597df60cbdb20e6f4", + "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5", + "sha256:69b157c5d3292bcd443faca052f3096f637f1e074b98212a933c074ae23dc3b8", + "sha256:75286256590a6320cf106a0d28970d3560aad9ee09aa7b34fb40524792436d35", + "sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642", + "sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2", + "sha256:79580094b00d1789d1f93ea55bc43cb2f611910c72235b7657f3482ddcc1b22d", + "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9", + "sha256:83aed2c10721ddd90f68140685391b50811a880af20654c59af6b6c66c40513c", + "sha256:84fd18bcc17526fc2b3c1af7d2b9217d32c9c04448c16ec693b9b4f1985c3d33", + "sha256:871ff67ea1aad4dfd91736464934d56b32dac49f9fbe16cddba36198a7b3a0db", + "sha256:898f0e9068ca27d37f8e83a5b962821df851532e6c4a7d615c1c033f9da6eedf", + "sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9", + "sha256:8c041122946b7ba21bb32c45b1aa57b1be35527690aeb3c5c234521085632eee", + "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84", + "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44", + "sha256:920079c3f7456fa213e0829ed2073aaa727fd39d889ead5b4f35d0de5460d04f", + "sha256:93d59d504b230e83c7a843251681959a0b6a9cd76f6e146ce1b8a80eb8739af9", + "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9", + "sha256:9b8e0f3107e2200b76f6054de99016eac3ee6762713587b36baaa7e4bd2ae177", + "sha256:9bb41182d93ea91f60b4bc8fbf4c820c69ef8a12ab2d917f3f1834f1acad07e8", + "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b", + "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39", + "sha256:a4cfde78a9f2880208d16a93b795726a3017d5977e08d1e162a7a31322479c41", + "sha256:a4fbdde9dd4a9ce5fd52c2b3a347bb50cc89483ef783f1cb00d408c13f7a96c0", + "sha256:aa99adc8f081b475a12843953db36831eaf83ec33eb46a90629ca6a5de45a616", + "sha256:ac351b3b8014eead140e77e9717e2992c6bbe30b63bc3422422eb84865412e3d", + "sha256:ad41ba96094304aa090f5a30cb6e4fb3b3f1c264c523394b4c39bbacc4dc92ba", + "sha256:b5314963fce9b0b12743891de876e724997864ee22aa496f903f426c7e2fa5b2", + "sha256:bcf74c1df76758a395bf0af608c04c82257523f55c9868b334f06270d0f2112b", + "sha256:bd47ba7fc3ca94896759ea0109775132d3e7ab921fbf54038e1bab2e46c313c9", + "sha256:c0323c9daef75ef2e5083624b4585018a0c9d5e3b40f607eed81a311270b934b", + "sha256:c1225416b463483160e4af85d5fc3a9690ccb53fd4b1865a6437825f5ede3209", + "sha256:c1c948747b03be832dceed96ca815cef7360de9aa19d37c730f8e3f6101aca48", + "sha256:c25fe15c70c59eb7c5ce8c06a1f3fa1da0ecc5ea1e7a5922c40fd2fa9b0d5046", + "sha256:cc1b0fff8ead343dae06305f954eb8468ba0ec1a97881f42489d198e4ce3c632", + "sha256:cd6280cf040f233bd7d3407b743b4b4c74f70e8e1c4199cb112a62c941c0772a", + "sha256:cd6c3d4b783c556fa00bf540854e42f135e2f256abd29669fcd0da0f2dec79c2", + "sha256:d4d6fcde76f94f5cb9e43e9e9a61f16dacefd228cbbf6f1a09bd9b219a92f1a1", + "sha256:ddf4af30b417d9fe16481e9b81c27ab2a7cde1ff7ba3e85653b02db7d145dc7b", + "sha256:df115d4d83168fdf2cae48ef1ff6d1cb4c466364e30861b37121de0f3bf1b990", + "sha256:df7276909358e5635ae203673ab7e509ddd224225a8d6b0790bf13eb2bde1cc5", + "sha256:e4fd89cc178bced6ad29cb3e6dd4aa63fa5017c3524dbd0b25998fb64a87cc8b", + "sha256:e9701d0049d92c16703a42771b98d560b95248949f23f8cf7b4eddd201814fb9", + "sha256:ee2f2a527e3c1a6e6411eb4209642e138b544a2d72fe5d0d76daf77b24063534", + "sha256:f7fb7d750cfa0a070d2c24e831fd3481019a60dd317ea2b39acbcebc08b6ed81", + "sha256:f840ed6d8ecba8255df8c42b87fadeda98ddfc6eeec05e2dc66e26d46dd6f58a", + "sha256:f86c6358749bd4fda175388691e3ba8c46e24c5347d0afd20f9b7edfc9faf07d", + "sha256:fa36ec09ef71d158186bc79e359ff5fdd6e7996fe8ab638f00d6b93139ba4fcf", + "sha256:fe2c7201c642b7c308f1675355ad7ff7b66acfe3541625efe5a3ad38f29d6115" + ], + "markers": "python_version >= '3.7'", + "version": "==3.4.9" + }, + "click": { + "hashes": [ + "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6", + "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76" + ], + "markers": "python_version >= '3.10'", + "version": "==8.4.2" + }, + "codecov": { + "hashes": [ + "sha256:2362b685633caeaf45b9951a9b76ce359cd3581dd515b430c6c3f5dfb4d92a8c", + "sha256:7d2b16c1153d01579a89a94ff14f9dbeb63634ee79e18c11036f34e7de66cbc9", + "sha256:c2ca5e51bba9ebb43644c43d0690148a55086f7f5e6fd36170858fa4206744d5" + ], + "index": "pypi", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.1.13" + }, + "codespell": { + "hashes": [ + "sha256:af2505b335e8573dbd2d384d1c4ef498f4006f4ba2d6fceca01e55b91f52628a", + "sha256:cbe085e331227b37bb86ef8bddd08dc768c704ee9a07ca869852c093fa2793e2" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==2.4.3" + }, + "colorama": { + "hashes": [ + "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", + "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", + "version": "==0.4.6" + }, + "coverage": { + "extras": [ + "toml" + ], + "hashes": [ + "sha256:075560438765b7a2ef43bf7aa7758661b53d889df47f062a31bda6c1ade553a2", + "sha256:0901cfe6c13bcd2302da4f83e884555d2a22bda6e4c476f09ef204ba20ca536e", + "sha256:094dd37f3ef7b2da8b068b583d1f4c40f91c65197e16c52a71962d5d537fc5db", + "sha256:09f5c6ec5901f667bd97dd140b5b9a2586b10efec66f46fb1e6d8135f8b95bdf", + "sha256:0e55510bc98ae943cece9e667a6c0fe94c6a92913720dea34243657a17993d0c", + "sha256:1121caa19159a38b5463eaae4b1e1fde81e525b15ecc5e000cd5b1a108f743a8", + "sha256:1268ac8fb9ddcd783d3948dbabaf80a5d53bfdaa0575e873e2139a692f797443", + "sha256:1473b3ba8e7ee0f076117b1a72c23f579a2b9e2bb742f48a8d86ea27ca93f91a", + "sha256:17c432b5f73ad52ef46fb06019f6fa7c66ce381961cf0f7dfd1d3a4bd3a98145", + "sha256:1adac78e5abc7c5438f7a209c9ca69d06542f0bf481d728b6989ea80b813fdf9", + "sha256:1cd7a5beb7af3e864a13b1f0fb26efd3695da43ef0daf71e586adfffaf34d5b2", + "sha256:1d16e3a7104ea84f03e614611b3edbf6fb6892554b3ab0fe7fbb3f2b2ef04376", + "sha256:25fd15dd40a0a2c51a500d664ca29053c09c3259d998407bf982b6e114696138", + "sha256:2617f8799d268fabdeef42a7e89ac3a23e1deee9025427db2df970f99a89a578", + "sha256:26c3b04a6377fd7c09800921fa934e3a17c0020439cd59df73e73ae1d4b6a78c", + "sha256:29c052f7c83ccfcc5c577eaae025d2e4a9bb80daf03c0ac31c996e83b000ce88", + "sha256:2f1ec6f304b156669cfde653b4e9a953f5de87e247ea02ac599bce0ab2744036", + "sha256:2fbeeeecea279727f8ac16c8e1133ddfeee793e985c86ae343d6a5ce744eef8c", + "sha256:2ff08701be2d1556fc78b326c80a3e8042da09352ecb3819105f8e386c8a3071", + "sha256:38c9518b7103826c403a461544e3c2e77151e8676d06eaed85911a97e962584a", + "sha256:3df60dc267f0a2ca23cb7a9ab1109c62b9335ffbf519fcfe167157c28c09b81d", + "sha256:3ed010aa1b69cda8e827aabfca9866216c980e2dca82ab9a78c5f83689964c8b", + "sha256:40f633c5c5fc783732f6312280122e859538fa24461235597c13d803ea9a108a", + "sha256:42ec3d989421b174a2ab607c1539f24127ad362757b7f1c0c0d7a2993f7eb37b", + "sha256:434e68d531858205895eb0d74b73d20b84260de426387d53c422a5acda2cf050", + "sha256:44826758cfe73fcd0e6af5deb4ba6d5417cc1d13df3acb35c93484a11160f846", + "sha256:4510fb9cdf6bb02dfa6af0be4a534b8102d086e22e4a33f8836df663da3d660d", + "sha256:48ccc6395958eda89093ecdc35644c86f23a8b23a7f4d44958812b721aad67c1", + "sha256:4d3361879d736f469f45723c11ea1a5bbdaf1f6928f0e632c940378b5aa9b660", + "sha256:582edc45c2040543fef83341be23c43024a3ab3ae0c2d8bc498a06282905ad40", + "sha256:63022c4c8dec1d0342f05c3ede99842fe3d007689acc45e86f123a1746e4a026", + "sha256:67d7602480a47bdf5b675635403625553ebaa70d5a62a657c035149fd401cea0", + "sha256:68af907f595ab01a78f794932ff3bdf929c316d3000810d38dbc247129e26f8b", + "sha256:6aa28cfb6488e5453b5b762d65f73aa586380f6693a04d58078ce228a29b06c0", + "sha256:6c0be82b4d4aa5b2704e08518e2252f3e3d110164bcca826816801052e48a7aa", + "sha256:6f6966fc30e6f06ca8f98fb0ce51eda6b111b3ee8d066a8b1ec9e77fa06ab55d", + "sha256:6fc448c377d6eeb00a47c673494bd9bae29280ca53987e1869e67ebedfe20658", + "sha256:728a33676d4c3f0db977990a4bd421dcaa3be3e53b5b6273036fff6666008e89", + "sha256:7466cc7ab6dc0db871d264bf99e8779f0917ee63d40730af0552f71535a6e072", + "sha256:77f091ea3a9cc611cd29f433565476bc1936c084ac8eee00ea0e7e70c27e4199", + "sha256:77f0ef5011df53a4bd1b35211ab122287f8d9b8d7aa1c4553e5c2deb24b1d446", + "sha256:7c63387e21ab21f512c69c9756a8c7dadd322c7275edb064064433c9a09c3743", + "sha256:7d29ca7bd67af6e12e74632d65f026eabc1364da5c254494cd914446a28a3ef7", + "sha256:7dc2950a2992cd676d35c20ae63522836deeb034f08874699d14068710af3dc1", + "sha256:7e8f27131dc7cd53de2c137dd207b3720919320b3c20d499dc30aa9ee6173287", + "sha256:81f382c5a94b434ec1f6da607edb904c76d7212e618cd4d1bc9f97bed4120ef5", + "sha256:835ec4e20b45f0a7f63ed78f94065aca00de033403df8377bfe8b9c6abc0a7be", + "sha256:8bb9f4b4279187560796a4cdaca3b0a93dd97e48ee667df005f4ed9a97403688", + "sha256:8c726b232659cbd2ae57ade46509eb068c9bd7a06df9fcbff6fe484870006934", + "sha256:913b6c56e110da40e035bbd168353bf7aaa2544a5eaccea5d98a4629aac156c7", + "sha256:97a5c5457a9fb1d6c4e06cfb5dc835871fbfb6a6a51addc9e925bdeff5ef7440", + "sha256:9854ca62c152874b2060772503535be2e8f53f70b8aaa7686b094888d872f984", + "sha256:9911f31aad8906abe337c271343485cf20df5e70df5d2f57f9f136e7b55f26bc", + "sha256:9b5bd92ff1ec22e535eab0de75fa6db021992791f461a2aceb7822c625a1187d", + "sha256:9deddf09eecb717b7f980414b43d90a5b22ff3967d2949ab29cb0aa83d9e9098", + "sha256:9e36686f7a442185db2400b3df171aac520869faf9deb59df687d28659eda2a6", + "sha256:9f4432898c4bf2fba0435bbe35dd4437d7264565e5a88a21f5b49d8662a6b629", + "sha256:a0f47002c6eeb7c280228467a4cb0cc15ca2103a8421b986b2d3ec04a0f9bd8b", + "sha256:a164b50081fc7357331c4024ef4d17b78ba325f8380d05f5a69599a7e05257ee", + "sha256:a29ec5305a7335aacee2d799e3422e91e1c8a12474986e2b3b07e315c91be82f", + "sha256:a300c6934e0989c327b9e8a1e110329da4641149f872bbe9f70168be66da76c1", + "sha256:a4c46b247b5d4b78f613bd89fea926d32b25c6cc61a50bd1e99ba310348f3dad", + "sha256:a638db90c61cd219aeee65e83a24fdaa57269a741ae0cf773309208ac862cee3", + "sha256:a63b9e190711134d581c4d703df5df09851b1acf99792c7aacbbe9f41f0283c9", + "sha256:aaccad4129d735a8a4d526f26929894c9a4e8ef7034566f210b176749d6906e3", + "sha256:ae901f7e55ba405c84ee1cab3d3e962e4e871e4a2bcb9c90911adbd69b42ac5a", + "sha256:afa29e2eff3d5729267e2cb2fd4ce9d61c952932fb2694e34ccb5d9540c6a296", + "sha256:affd532502d34c0472d0cdb181325c89f1d2c44992fef0c17e88e7b1576259a1", + "sha256:b171bdd71cb7ff792bf32e376173b0ace7e7963e7e57c58dfc42063a6a7174cd", + "sha256:b868acc62aa5de3be7a9d05c2333bf8359ca987e43f9cb30ff8fbda6a024ab73", + "sha256:b9a6367e4aff723e8ee8190836836124284e8fcd4265e307c844010cfa074f3f", + "sha256:bbc808daf4f5cd567af8075ecc72d21c6dfef9a254709a621a84c217c935ebc0", + "sha256:bbf44513ceb1589e31948e20eafbde9deaface90e1a1afa5f5f77b4423d17ce6", + "sha256:bcc0aae933921d03096f53b0b03eeb702129fd406dee59f08d2efacc68681fa5", + "sha256:bfd341ccf78128e72c094bc70cc25b3ef309c33c7c2c66ba3ed4309549e02de1", + "sha256:c6a98d698f9e2c8008d0370ec7fc452ebfcc530002ae2d0061170d768b992589", + "sha256:cb0fddaa6884be6aae36ced9544b5e90f7d5f03845a2853bf47a14953a4e8688", + "sha256:cee0f89f4767a6057c8fbf168f8135f18be651300496086bd873e3189fed0487", + "sha256:d17d7512151fedfcc64c1821a8977fc9be0dbf495754669afcab7b57abc98ae9", + "sha256:d46e62cb35d91e6e2589fda6d28074426b0e276422b5d2ebef2c6b11dc60dbfd", + "sha256:d50dd325e18ec25bfcc10cd7f99b04df1ab9ec76b0918c260e60817ad0643dee", + "sha256:db9c8438057e5b0f6a22a0af99c0c1d26b57fbbdbd1be5861ddb8f897fcc3a2d", + "sha256:dee88b1ed88587abd8c0269a1fc1f4cc77f7750d1dfde2869e2a123af420e67d", + "sha256:dfd3db045e95960ae3683059571e597fda7cc610106a8916f77c5839048c1deb", + "sha256:e26ff680768b8095e8874aabe0e9d3a47a2a9f176a8340d05f8604c56457c23a", + "sha256:e370c12133095ff18432de8c044962be85a5a96d90c6fcbce8e17e76236d2328", + "sha256:e38def96ad59853824c97953fdcd2c320a84ba3ce99b417db78af8bb6c3db635", + "sha256:e8f91bce78e32343af184c3b7fa28fcf5a9e2641f4b6623d392038f804939188", + "sha256:eb6bcae8d1a9d305351ecb108232441d11c5cfe9de840a04388ba5d2db8d735c", + "sha256:f653e5d7248c1191ec988a85c72edeab46c3ff44f90639a4ed4874ec0be90243", + "sha256:fe41909c9515c3bfdb5f02c4d1f857dba322d9a9a1178069b91eea77889df63a" + ], + "markers": "python_version >= '3.10'", + "version": "==7.15.2" + }, + "distlib": { + "hashes": [ + "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b", + "sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed" + ], + "version": "==0.4.3" + }, + "filelock": { + "hashes": [ + "sha256:7be2ad23a14607ccc71808e68fe30848aeace7058ace17852f68e2a68e310402", + "sha256:d396bea984af47333ef05e50eae7eff88c84256de6112aea0ec48a233c064fe3" + ], + "markers": "python_version >= '3.10'", + "version": "==3.32.0" + }, + "flake8": { + "hashes": [ + "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e", + "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==7.3.0" + }, + "idna": { + "hashes": [ + "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", + "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848" + ], + "markers": "python_version >= '3.9'", + "version": "==3.18" + }, + "iniconfig": { + "hashes": [ + "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", + "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12" + ], + "markers": "python_version >= '3.10'", + "version": "==2.3.0" + }, + "isort": { + "hashes": [ + "sha256:171ac4ff559cdc060bcfff550bc8404a486fee0caab245679c2abe7cb253c78d", + "sha256:28b89bc70f751b559aeca209e6120393d43fbe2490de0559662be7a9787e3d75" + ], + "index": "pypi", + "markers": "python_full_version >= '3.10.0'", + "version": "==8.0.1" + }, + "librt": { + "hashes": [ + "sha256:05d96b80b95d3a2721b619f8982b8558848b04875bb4772fd54842b59f61dd97", + "sha256:0763ca2ab66058174f9dee426dc64f5e0a89c24a7df8d3fe3f1836c04e25de4b", + "sha256:091b60a4d2174fc1ec5c34cdc0b72efb6224753d76b7da61ebeab7a191aec8bd", + "sha256:0b795f5fc70fbbb787ceaf79bb3a0d627bcc33c53de51741755263ec406b775a", + "sha256:0d38604854e8d22faadf683ec6c02bb0f886e2ba56ef981a1c36ee275f21ea22", + "sha256:109b84a9edf69ad89dc1f66358659e14a031baca95e3e5b0060bd903ede8efd6", + "sha256:1304368a3e7ffc3e9db986796cc5326fdb5943a3567ecc137cff318e4240c0e7", + "sha256:17221a7569f8f292aa0014226e48aa25b8c2b08da18088cd230953d0ea0f9cd1", + "sha256:1b5a7bbff495baedbd9b916c367d66854008f8f3b575908ded477c499dc60082", + "sha256:1ce61b3746545029d4f5c17d6bd74b676254ad98433086c846ffb5e8fa73f007", + "sha256:1d2a610c14ac0d0750ee0a3ab8548e83155258387891caaca04def4bf7289781", + "sha256:21b7ac084f701a9cdff6139745a6620579d65a9379ac2d9d50a86368b109e63c", + "sha256:22034924f5b42d5a56371cf271771bfeaabf235a7a8b6264bef2d20013f786c6", + "sha256:25218d94b1d2cbc0ba1d8a3f9dc9af578d9646e5ed16443a70cde1dfdcce6d71", + "sha256:2608d3b39f9e0b4a66a130d9150c615cba40a5090d25eeeaa225e0e46de8c0ac", + "sha256:260c33e92263fa629b4f6d3c51967a1c2158fe6c33237aaa3ebeac586b085259", + "sha256:2e56ea4ee4df77585a6b5c138f6538680886024fa559f5b55bd14b12e98e67b2", + "sha256:2f281549a4c52ac7bb97997f14353f8bd0e53a34ca0dad1c905cfd0b4a58ae99", + "sha256:301067672387902c55f94b51d5022304b36c966ea9fe1f21caab99a9bef487c9", + "sha256:30536798f4504c0fad0885b1d371b0539abb081e4570c9d7c641cb51141b49f0", + "sha256:32c26893cd085c1efe83219e78d866da23fb20a066101b8f68210004361d224c", + "sha256:34bc7938b9fdf14fe32a406c19c71faf894c5cee7e7474bd0be2f17200b82d14", + "sha256:34e47058fcc69a313293d6dee94216a4f30c929ae6f2476e58c5ba635aa639d5", + "sha256:3657346f867469e962549435aa05fd15330b1d6a92829f8e27988e194382d005", + "sha256:36b306a623aaad96fe4b378692b54f9c0789fccd833b9851753d5fbf6138cfde", + "sha256:371f7ce73026815dafd51c50ce38416e91428b28c4b2ec97cd39271164b0045c", + "sha256:375f5af8f99cbaa99dd293af986e3d57caabc9ba81a5d3f021603764854197a1", + "sha256:387e2f1d27e89bffe0d3f520f0da0662c973fd607ca16c1808f8a5085419485e", + "sha256:3aaedf52171bee90860704c560bc798fe83b76247df47568e0197e9b13c735a0", + "sha256:3dbb2a31882456cadc7053378e81ad7ed7693db4ac9f98ab5f81ef034aa8ec9f", + "sha256:4000d961ff9598ac6ea603c6c836a5ed49bc205ade5fc378b998dfe1e2c36628", + "sha256:40ccd13c252d3fe473ffc8a57be7565abc8b64cf1b108344c859d5164f7f3e0c", + "sha256:46c330e82565962c761dbce7941be2cff7db674ee807455a8d0cadc5f9b759b0", + "sha256:4f6db193d2e5e0ed60359b9a5a682cd67205d0d3b1e459a867dd4b5c4e7eaa7a", + "sha256:531b2df3e9fe96b1fcf73a6d165921e4656be5f58d631d384ebce344298368db", + "sha256:54dab44a847d5ad1acd05c8a83fe518ae685516ecf4d3f7cc6e3df2a66767650", + "sha256:5929da1981a46bcf4b28b1b9499905f0ff58e2419da402a048234e9783acbc4b", + "sha256:5f31b0aa13c9b04370d4da6be1ab7779776b3a075cceb6747a39a4be85fe1e40", + "sha256:5fdcf34f86de8fb66d7dc7589f96ba91c4aa46671200d400e6fd6f109a483f18", + "sha256:66c0e7e6b02a155576df2c77ec933a70b72da726e248c494abf690923e624348", + "sha256:66cb1138f384a191a6d75f986064841fcfdc0cea98f7bd9c9ab9b38049917588", + "sha256:68a5faee4bba381cb93b5961f684a514cf0053cb92308ff9c792c2fea0b174c6", + "sha256:6bf6a559ffe4a93bbea6cf31ddf01a7fd9ba342ef51f27beb178e318b74acd61", + "sha256:70d9c62a4cffd9f23396cd5ef93fc5d11b31596b9b7d6306074abe3d5fcf09bd", + "sha256:791aa18a373b90da8ac3c44fc77544f33fdf53ae403acdce9b39f1c26b4a3b94", + "sha256:79e44cff71750d299d61a678e49995b0d5935a9cda238c2574daeca3ba536927", + "sha256:7db9a3ff32ef5f7d1703d93831a3316cdf0b537de6a1cc03cc8fdd09b9194e89", + "sha256:860bd1d8ba48456ce08feaf8d343a8aaeb2fa086f2bcaa2a923fa3f7a3ff9aa3", + "sha256:9320d34c3376ae204b2cd176e8d4883a013934e0aef822f1aed9c536490c275d", + "sha256:93d24ebb82aa4420b1409c389e7857bc35bd0b668007ac8172427d5c73cc8cc5", + "sha256:94b85d664d777bab6c0d709416cb42938251fda9e221b79e3a2215d85df5f4f9", + "sha256:96bad8725a4f196a798366c25ce075d1f7543a4ec045ffc13e6a7ec095cdab04", + "sha256:9af313c66157a69dc69ea0059a66961692250e0dc95af9c385a48ffb770a0d16", + "sha256:9c5d02b89de5acd0379a51ec44a89476fb03df6145442e1c8ecd6bee2f91b176", + "sha256:9e786428f291dd2d2f1cbfc0e0caa45a2e395fab0ad3e2c9314daa8873414390", + "sha256:9f836c37478f167a81200d8c8b2c920a22224564bed2c23d7aeec760965c367a", + "sha256:9fd35e95ab5e45c3901d37110263c7db85a961110f5460588fe37f8c131f88a7", + "sha256:a001519c315d5db40710f2665d32c4791f1d4779fc96a9423fd18d92c8b9ac7b", + "sha256:a3762e75fcac8c9e4dacaaf438bffd9003e2ca2c531b756f3c0035deefa674c8", + "sha256:a38fb81d8376dfa2f8963b265fec07637802b0d01e2a127c19c66cb070fb24f5", + "sha256:a3dfe4edf10e8ed7e55b026a8bfc2c2a8704218b659cd4bffdf604fab966dc39", + "sha256:a4517d47b2b8af26975a406fba7d314de9696d864252e0257c6ea90238cfe27f", + "sha256:a468951af16155824e88bdd8326ebe5bdb371f3ec0ac04642994b98201d914f3", + "sha256:a6e556d6aba31c93dd97ce661d66614d2429c0a3923f9dc8f0af7e8df10223a4", + "sha256:ac04bcd3328eb91d99dfedf6a60d9c1f15d3434e6f6daf922f0420f7d90b85c7", + "sha256:ae01d8512cc17079e53425635327dbf3f7ff57a42c00dec348bf79791c56444c", + "sha256:b15e26cc0fe622d0c67e98bee6ef6bc8f792e20ee3006aa12627a00463d9399f", + "sha256:b222493da6e7b6199db9bd79502436cf5a27da3c1f7fa83c7e285444fc93fd03", + "sha256:c3cd253cf32fe4f4662960d6bf7d55cb8be0c31a5d644a4d48aeafebaff3409a", + "sha256:c6014e3c80f9c1fe268ef8b0e0ef113bac672cc032f2f93866e7ddad4f3e663d", + "sha256:c718e99a0992127af84385378460db624103b559ab260435abcfe77a4e4ed1c1", + "sha256:c7897db4e95e22468bdda33d8e012ceacd0182abf001e6389d763f0def6286b9", + "sha256:cb8a1adce42d8b75485a5d56a9623a50bcab995b6079f1dac59fc44034dd93d9", + "sha256:cc99dfb62b23c9207c33d0be8a2e2af7a42e21e6ea388b380a0c948c7b88953b", + "sha256:d4c8d9bd5abce34b2e75edb3bf37ab0f34e49b1f915a40ae8468eb7c85bc5b46", + "sha256:d4cb6fbfdf874340ab5e51450753c0f817b6958a3621125ee695bbc3de866566", + "sha256:d63bae12a8aeb51380be3438e4dc4bd27354d0f8e19166b2f44e3e94d6f552dc", + "sha256:d6fb0eaa108814581c4d3bfbd068c3fb6757812a81415008d1bae08267cca360", + "sha256:d9188caac26e47671b52836a5e2a49873a7fc11c673b0c122d22515f98bc14e1", + "sha256:db327e7271e653c32040b85ae6188059c924b57d7e1e29f935523fa017cd4e82", + "sha256:dbdd5b6509d0c2a8fe72cf494c299a61dbd58142a90a4190664ae159e4a7b547", + "sha256:e4f9b472e7d308d94b62c801982065661158c6ed02790d6c7ddb4337cea0f9c1", + "sha256:e54a315caf843c8d77e388cadc56ea9ded569935ee2d2347d7ea94992e5aa6fa", + "sha256:f125f5d46b20f89dc5587a55cc416b4ba2a5b2ffda36d048ee120e17598a653a", + "sha256:f19e181de5b3a1148bb3420b8c4b0b0ea0fce6950099724ad151d6cea5acc180", + "sha256:f1f9cc4d09a46d9cb3c2063ae100629d3f52a6517c3c08c2f4c9828261883929", + "sha256:f26629539d4893c2957a16c41bb058e1e135c1f150f6a2e25ed047f64cf3f5c6", + "sha256:f2a7253458e34f33543551394ae4fe104b497ec2a65ac266074de64c1df82e37", + "sha256:f40e56b61b41be5f7dec938cfeffd660668cf4b5e72c78e7bd671d66b7bc2c79", + "sha256:f442e3954b1addc759faae22a7c9a3f1e16d7d1db3f484279dc27d62e06968fa", + "sha256:fadc63331f4388c3dc90090448f682a7e9feafc11481391c1e94f2f907a3976e", + "sha256:fc67741da44c6eaa90e01eafb586bbba9b51eb5b6ed381ee6f5ae72eb3316d21" + ], + "markers": "python_version >= '3.9'", + "version": "==0.13.0" + }, + "mccabe": { + "hashes": [ + "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", + "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e" + ], + "markers": "python_version >= '3.6'", + "version": "==0.7.0" + }, + "mypy": { + "hashes": [ + "sha256:04e617030eca5221909c8b7d8d7fd1c637948199aa2100b2ad9813feb07e1491", + "sha256:094af99f92638aa92852326188b85a89e50f4a472f44827c03362228482f0762", + "sha256:09abd66d8685e73f8f7d17b847c3e104d9a7b164a8706ea87d6c96a3d45816d5", + "sha256:0b025a93cffb9781d231f232be07a17912f35f10a313c24f301c81e842870654", + "sha256:13b1b16e2fa39f3b2e33fb1c468abc7a69369fa2e886b4b87b5afc81472325cd", + "sha256:1c6c6bf687b17f90dbfcad95b960d32eaa0154c00da45f03ab50bf8952e047fe", + "sha256:1fa8d916ac3b705af733c4c1e6c9ebe38fd0d52beb15b105c3e8355b55e6ecdc", + "sha256:28e1e2af8cd8fff551fd30f2fe4b03fb76764ac8b1ba6c6a1bd00ad32b412db3", + "sha256:2d53fc67b9d28a43c6199077f49fea0f05839e36cf6158500331c9549225e5a5", + "sha256:3419d00717afbc5265b50dd14b1278f29ea4884dd398ab67873489ac093fd329", + "sha256:37fa4de896a84e2dc9200d91e614c22563b43d1a266789d4bbac7b22ebe6192b", + "sha256:3961a4a34b05f7c74b0f05aa51fbfe99a2d1e126038df40318d15c8f558b7ef3", + "sha256:3dd0bed92c4bdec57c42505b96416fb9e6a5aa7be84d2809bcd5f2ecec2860d7", + "sha256:3e77244df3843048c3f927182916730e40c124cbaa43905c1fb86cb382aa0805", + "sha256:4359424140d985192c778c1ce2c114a10c1ca58a381ed79cfa70d37df94b299f", + "sha256:465965d41cd9a2726694e983e8ce7113259327bec798115d1e1dfa2a52fb666e", + "sha256:56c184d2c20ca6b6378d58d1960270a767f41f5e44acbbd27f05effef4f4e1d7", + "sha256:5e91adad1ca81742ac7ef9893959911df867752206b37135185e88dfb3c89494", + "sha256:691fdc37132b1ae628d834f672e74de83462d9fb4aff621835767fb43a8dd373", + "sha256:6b1cdb579446b60432432b2b2403a6201b4b475a004d7f488511c9ba177c9e88", + "sha256:6f99ec626e3c3a2f7c0b22c5b90ddb5dabb1c18729c971e9bdaca1f1766d2cee", + "sha256:7247eb2824f996722a949530183394921ca71deb9680052a338cf53cff7925c2", + "sha256:75b0984bb3cbd76bb5c9291a8671f7ae66ca3b51c7584c358fc2e923259f0757", + "sha256:75cbb4b9ef04a0c84a957f07abc4504fbf64b8dcc145675101f2d3a78a4b1d6a", + "sha256:7da939dd335cfd2ad788bdfd081c9f4e47634ab995e5a45eb15fd1e5bc052f8b", + "sha256:85c5385b93012ffa3b31479ab579aef5415f4f3a32c6cf1ae07a984d2a0ff461", + "sha256:91ad22a52ae2c7e621c2f67c94d5a17f66b3209a4cff5cf8a573579835c69e97", + "sha256:944c665d984157cb96a679dfb7a4a81dd1d36b24b9c284b699514e6e626b82d4", + "sha256:9559ab18a9c9957dfa3004ab57cd4bac5f26a724329a9584e583367f0c2e1117", + "sha256:982e3d53dd23d0a4cef67dd66791fdbede0cf38f9eb617bf47663554c51e1e36", + "sha256:99ac767cc5d3b64c8d0ae226ead10c96694f94e4e7da1668642225dcd4e75aac", + "sha256:adebc76aab4f3495a88b41d48aa4aff0c03f2822501da76625afcca5975f19e5", + "sha256:aec15d465d477558fd842757b487849007311cf3897849cdda0e3162ac0ac556", + "sha256:b1942b9314d4c784b8ea1dbab4972603290e5dd5630f06675f13aec97526bc4c", + "sha256:b352b7e49f5e6576009e8df730e1ff4f915cb565b851b396d2ffe2f5a6f5da88", + "sha256:b5cd2f027a972a4a5f2278a11fac9747f5f81a53a30b714d74950b6807e55568", + "sha256:be51653d7669d7d7955d613b8d0bb57d5b652eaf71a873ddf65ac87254dd2595", + "sha256:cfca8ee88544090f86b6dcce05ec55d66eb48a762412ac2507810ba4bd793b6f", + "sha256:d78fcf900b59cb7e82cb7e3a235e31b462d9333d92285bd1e4952d355b8ffba1", + "sha256:de121747278144fc9ae7caa2e978cf5df12aebc82933182f5b3b86081a30baef", + "sha256:de6d2c484742a4d7b0ed6d07b143375624d3b899c5749c7b3c947f56261f48a6", + "sha256:ea317b060ce83e26050f8f9e4d7d6bf44ed7597c8ff9990bccffbb9d1d8522db", + "sha256:f1b3a98dfd21058bc759bb3337d5d1f61d0fdf9f3cf9c00f4291790fb5427bff", + "sha256:f4ed18f111bfe2d599bca7468e7f9251042c1c2118f762c8de2766a56d773c60", + "sha256:fbc00cee7bdbb9291979ddc9d08034a29dfcda4932628c9bbc28c1edd589df0c" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==2.3.0" + }, + "mypy-extensions": { + "hashes": [ + "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", + "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558" + ], + "markers": "python_version >= '3.8'", + "version": "==1.1.0" + }, + "packaging": { + "hashes": [ + "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", + "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661" + ], + "markers": "python_version >= '3.8'", + "version": "==26.2" + }, + "pathspec": { + "hashes": [ + "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", + "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189" + ], + "markers": "python_version >= '3.9'", + "version": "==1.1.1" + }, + "pipx": { + "hashes": [ + "sha256:4cba49e6f5ba7e894058f50fb12f0944868940e712bc68bcd17b405d2ef2beee", + "sha256:a6968fb9cf941535c601c08c331b0cd2a6db3b67bb59fbf5a9552cc1bafdab03" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==1.16.2" + }, + "platformdirs": { + "hashes": [ + "sha256:0555d18370482847566ffabcaa53ad7c6c1c29f195989ae1ed634a05f76ea1e0", + "sha256:360ccded2b7fce0af0ff80cc8f5942a1c5d99b0e856033acb030bfc634709e74" + ], + "markers": "python_version >= '3.10'", + "version": "==4.11.0" + }, + "pluggy": { + "hashes": [ + "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", + "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" + ], + "markers": "python_version >= '3.9'", + "version": "==1.6.0" + }, + "pycodestyle": { + "hashes": [ + "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783", + "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d" + ], + "markers": "python_version >= '3.9'", + "version": "==2.14.0" + }, + "pyflakes": { + "hashes": [ + "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58", + "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f" + ], + "markers": "python_version >= '3.9'", + "version": "==3.4.0" + }, + "pygments": { + "hashes": [ + "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", + "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176" + ], + "markers": "python_version >= '3.9'", + "version": "==2.20.0" + }, + "pyproject-api": { + "hashes": [ + "sha256:860060c8832dce983b5eec6f41c4c43eb3ec06ff7332387a63acdf5ca27b68d8", + "sha256:b8807d85a293e6c9f133e6575946fed45f1d42b22d58c780b33aa2421a799549" + ], + "markers": "python_version >= '3.10'", + "version": "==1.11.0" + }, + "pyproject-hooks": { + "hashes": [ + "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", + "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913" + ], + "markers": "python_version >= '3.7'", + "version": "==1.2.0" + }, + "pytest": { + "hashes": [ + "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", + "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==9.1.1" + }, + "pytest-cov": { + "hashes": [ + "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", + "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==7.1.0" + }, + "pytest-randomly": { + "hashes": [ + "sha256:47f1d9746c3bc3efabd53ae1ebfb8bb385cf3d4df4b505b6d58d9c97a3dfe70f", + "sha256:f55e89e53367b090c0c053697d7f9d77595543d0e0516c93978b50c0f6b252f9" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==4.1.0" + }, + "python-discovery": { + "hashes": [ + "sha256:3e014c6327154d3dda27939a9a0dc9c5c000439f1906d3f303b48f984bd2ecef", + "sha256:70c4fc61b4e7404e44f01d6fc44a715c4d685ca6cea83d295922f05891877c98" + ], + "markers": "python_version >= '3.8'", + "version": "==1.5.0" + }, + "pytokens": { + "hashes": [ + "sha256:0fc71786e629cef478cbf29d7ea1923299181d0699dbe7c3c0f4a583811d9fc1", + "sha256:11edda0942da80ff58c4408407616a310adecae1ddd22eef8c692fe266fa5009", + "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083", + "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1", + "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", + "sha256:27b83ad28825978742beef057bfe406ad6ed524b2d28c252c5de7b4a6dd48fa2", + "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a", + "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1", + "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5", + "sha256:30f51edd9bb7f85c748979384165601d028b84f7bd13fe14d3e065304093916a", + "sha256:34bcc734bd2f2d5fe3b34e7b3c0116bfb2397f2d9666139988e7a3eb5f7400e3", + "sha256:3ad72b851e781478366288743198101e5eb34a414f1d5627cdd585ca3b25f1db", + "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68", + "sha256:42f144f3aafa5d92bad964d471a581651e28b24434d184871bd02e3a0d956037", + "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321", + "sha256:4a58d057208cb9075c144950d789511220b07636dd2e4708d5645d24de666bdc", + "sha256:4e691d7f5186bd2842c14813f79f8884bb03f5995f0575272009982c5ac6c0f7", + "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f", + "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918", + "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9", + "sha256:670d286910b531c7b7e3c0b453fd8156f250adb140146d234a82219459b9640c", + "sha256:682fa37ff4d8e95f7df6fe6fe6a431e8ed8e788023c6bcc0f0880a12eab80ad1", + "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1", + "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3", + "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b", + "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb", + "sha256:941d4343bf27b605e9213b26bfa1c4bf197c9c599a9627eb7305b0defcfe40c1", + "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a", + "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4", + "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa", + "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78", + "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe", + "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9", + "sha256:b49750419d300e2b5a3813cf229d4e5a4c728dae470bcc89867a9ad6f25a722d", + "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975", + "sha256:d70e77c55ae8380c91c0c18dea05951482e263982911fc7410b1ffd1dadd3440", + "sha256:d9907d61f15bf7261d7e775bd5d7ee4d2930e04424bab1972591918497623a16", + "sha256:da5baeaf7116dced9c6bb76dc31ba04a2dc3695f3d9f74741d7910122b456edc", + "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d", + "sha256:dcafc12c30dbaf1e2af0490978352e0c4041a7cde31f4f81435c2a5e8b9cabb6", + "sha256:ee44d0f85b803321710f9239f335aafe16553b39106384cef8e6de40cb4ef2f6", + "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324" + ], + "markers": "python_version >= '3.8'", + "version": "==0.4.1" + }, + "pyupgrade": { + "hashes": [ + "sha256:1a361bea39deda78d1460f65d9dd548d3a36ff8171d2482298539b9dc11c9c06", + "sha256:2ac7b95cbd176475041e4dfe8ef81298bd4654a244f957167bd68af37d52be9f" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==3.21.2" + }, + "requests": { + "hashes": [ + "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", + "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed" + ], + "markers": "python_version >= '3.10'", + "version": "==2.34.2" + }, + "tokenize-rt": { + "hashes": [ + "sha256:8439c042b330c553fdbe1758e4a05c0ed460dbbbb24a606f11f0dee75da4cad6", + "sha256:a152bf4f249c847a66497a4a95f63376ed68ac6abf092a2f7cfb29d044ecff44" + ], + "markers": "python_version >= '3.9'", + "version": "==6.2.0" + }, + "tomli-w": { + "hashes": [ + "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", + "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021" + ], + "markers": "python_version >= '3.9'", + "version": "==1.2.0" + }, + "tox": { + "hashes": [ + "sha256:ab0b126a04dd56bc18e6d216386db09335247f2289b54cf534deb5c4ae3a8d2e", + "sha256:dcae21f5f015f3a67658e35644cce0d1aa0dedcd06f3927f95d84e1717f6cea5" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==4.58.0" + }, + "typing-extensions": { + "hashes": [ + "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", + "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5" + ], + "markers": "python_version >= '3.9'", + "version": "==4.16.0" + }, + "urllib3": { + "hashes": [ + "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", + "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897" + ], + "markers": "python_version >= '3.10'", + "version": "==2.7.0" + }, + "userpath": { + "hashes": [ + "sha256:2cbf01a23d655a1ff8fc166dfb78da1b641d1ceabf0fe5f970767d380b14e89d", + "sha256:6c52288dab069257cc831846d15d48133522455d4677ee69a9781f11dbefd815" + ], + "markers": "python_version >= '3.7'", + "version": "==1.9.2" + }, + "virtualenv": { + "hashes": [ + "sha256:7f9519b9432ff11b6e1a3e94061664efc2ff99ea21780e3cf4f6bd0a5da8b37c", + "sha256:a8370c1c5530fbabf955e40b8fbbc68a431648b10f9433faa587db30a06e51dd" + ], + "markers": "python_version >= '3.9'", + "version": "==21.7.0" + } + } +} diff --git a/README.md b/README.md index c57968958..7624f55f6 100644 --- a/README.md +++ b/README.md @@ -53,18 +53,18 @@ graph TD Composite --> Leaf2 ``` -| Pattern | Description | -|:-------:| ----------- | -| [3-tier](patterns/structural/3-tier.py) | data<->business logic<->presentation separation (strict relationships) | -| [adapter](patterns/structural/adapter.py) | adapt one interface to another using a white-list | -| [bridge](patterns/structural/bridge.py) | a client-provider middleman to soften interface changes | -| [composite](patterns/structural/composite.py) | lets clients treat individual objects and compositions uniformly | -| [decorator](patterns/structural/decorator.py) | wrap functionality with other functionality in order to affect outputs | -| [facade](patterns/structural/facade.py) | use one class as an API to a number of others | -| [flyweight](patterns/structural/flyweight.py) | transparently reuse existing instances of objects with similar/identical state | -| [front_controller](patterns/structural/front_controller.py) | single handler requests coming to the application | -| [mvc](patterns/structural/mvc.py) | model<->view<->controller (non-strict relationships) | -| [proxy](patterns/structural/proxy.py) | an object funnels operations to something else | +| Pattern | Description | +|:-----------------------------------------------------------:|--------------------------------------------------------------------------------| +| [3-tier](patterns/structural/3-tier.py) | data<->business logic<->presentation separation (strict relationships) | +| [adapter](patterns/structural/adapter.py) | adapt one interface to another using a white-list | +| [bridge](patterns/structural/bridge.py) | a client-provider middleman to soften interface changes | +| [composite](patterns/structural/composite.py) | lets clients treat individual objects and compositions uniformly | +| [decorator](patterns/structural/decorator.py) | wrap functionality with other functionality in order to affect outputs | +| [facade](patterns/structural/facade.py) | use one class as an API to a number of others | +| [flyweight](patterns/structural/flyweight.py) | transparently reuse existing instances of objects with similar/identical state | +| [front_controller](patterns/structural/front_controller.py) | single handler requests coming to the application | +| [mvc](patterns/structural/mvc.py) | model<->view<->controller (non-strict relationships) | +| [proxy](patterns/structural/proxy.py) | an object funnels operations to something else | ## Behavioral Patterns @@ -86,46 +86,46 @@ graph LR Caretaker -->|holds| Memento ``` -| Pattern | Description | -|:-------:| ----------- | -| [chain_of_responsibility](patterns/behavioral/chain_of_responsibility.py) | apply a chain of successive handlers to try and process the data | -| [catalog](patterns/behavioral/catalog.py) | general methods will call different specialized methods based on construction parameter | -| [chaining_method](patterns/behavioral/chaining_method.py) | continue callback next object method | -| [command](patterns/behavioral/command.py) | bundle a command and arguments to call later | -| [interpreter](patterns/behavioral/interpreter.py) | define a grammar for a language and use it to interpret statements | -| [iterator](patterns/behavioral/iterator.py) | traverse a container and access the container's elements | -| [iterator](patterns/behavioral/iterator_alt.py) (alt. impl.)| traverse a container and access the container's elements | -| [mediator](patterns/behavioral/mediator.py) | an object that knows how to connect other objects and act as a proxy | -| [memento](patterns/behavioral/memento.py) | generate an opaque token that can be used to go back to a previous state | -| [observer](patterns/behavioral/observer.py) | provide a callback for notification of events/changes to data | -| [publish_subscribe](patterns/behavioral/publish_subscribe.py) | a source syndicates events/data to 0+ registered listeners | -| [registry](patterns/behavioral/registry.py) | keep track of all subclasses of a given class | -| [servant](patterns/behavioral/servant.py) | provide common functionality to a group of classes without using inheritance | -| [specification](patterns/behavioral/specification.py) | business rules can be recombined by chaining the business rules together using boolean logic | -| [state](patterns/behavioral/state.py) | logic is organized into a discrete number of potential states and the next state that can be transitioned to | -| [strategy](patterns/behavioral/strategy.py) | selectable operations over the same data | -| [template](patterns/behavioral/template.py) | an object imposes a structure but takes pluggable components | -| [visitor](patterns/behavioral/visitor.py) | invoke a callback for all items of a collection | +| Pattern | Description | +|:-------------------------------------------------------------------------:|--------------------------------------------------------------------------------------------------------------| +| [chain_of_responsibility](patterns/behavioral/chain_of_responsibility.py) | apply a chain of successive handlers to try and process the data | +| [catalog](patterns/behavioral/catalog.py) | general methods will call different specialized methods based on construction parameter | +| [chaining_method](patterns/behavioral/chaining_method.py) | continue callback next object method | +| [command](patterns/behavioral/command.py) | bundle a command and arguments to call later | +| [interpreter](patterns/behavioral/interpreter.py) | define a grammar for a language and use it to interpret statements | +| [iterator](patterns/behavioral/iterator.py) | traverse a container and access the container's elements | +| [iterator](patterns/behavioral/iterator_alt.py) (alt. impl.) | traverse a container and access the container's elements | +| [mediator](patterns/behavioral/mediator.py) | an object that knows how to connect other objects and act as a proxy | +| [memento](patterns/behavioral/memento.py) | generate an opaque token that can be used to go back to a previous state | +| [observer](patterns/behavioral/observer.py) | provide a callback for notification of events/changes to data | +| [publish_subscribe](patterns/behavioral/publish_subscribe.py) | a source syndicates events/data to 0+ registered listeners | +| [registry](patterns/behavioral/registry.py) | keep track of all subclasses of a given class | +| [servant](patterns/behavioral/servant.py) | provide common functionality to a group of classes without using inheritance | +| [specification](patterns/behavioral/specification.py) | business rules can be recombined by chaining the business rules together using boolean logic | +| [state](patterns/behavioral/state.py) | logic is organized into a discrete number of potential states and the next state that can be transitioned to | +| [strategy](patterns/behavioral/strategy.py) | selectable operations over the same data | +| [template](patterns/behavioral/template.py) | an object imposes a structure but takes pluggable components | +| [visitor](patterns/behavioral/visitor.py) | invoke a callback for all items of a collection | ## Design for Testability Patterns -| Pattern | Description | -|:-------:| ----------- | +| Pattern | Description | +|:--------------------------------------------------------:|------------------------------------| | [dependency_injection](patterns/dependency_injection.py) | 3 variants of dependency injection | ## Fundamental Patterns -| Pattern | Description | -|:-------:| ----------- | +| Pattern | Description | +|:----------------------------------------------------------------:|-----------------------------------------------------------------------------| | [delegation_pattern](patterns/fundamental/delegation_pattern.py) | an object handles a request by delegating to a second object (the delegate) | ## Others -| Pattern | Description | -|:-------:| ----------- | -| [blackboard](patterns/other/blackboard.py) | architectural model, assemble different sub-system knowledge to build a solution, AI approach - non gang of four pattern | -| [graph_search](patterns/other/graph_search.py) | graphing algorithms - non gang of four pattern | -| [hsm](patterns/other/hsm/hsm.py) | hierarchical state machine - non gang of four pattern | +| Pattern | Description | +|:----------------------------------------------:|--------------------------------------------------------------------------------------------------------------------------| +| [blackboard](patterns/other/blackboard.py) | architectural model, assemble different sub-system knowledge to build a solution, AI approach - non gang of four pattern | +| [graph_search](patterns/other/graph_search.py) | graphing algorithms - non gang of four pattern | +| [hsm](patterns/other/hsm/hsm.py) | hierarchical state machine - non gang of four pattern | ## 🚫 Anti-Patterns @@ -173,9 +173,12 @@ When everything else is done - update corresponding part of README. ##### Travis CI Please run the following before submitting a patch: -- `black .` This lints your code. -- Either `tox` or `tox -e ci37` for unit tests. -- If you have a bash compatible shell, use `./lint.sh`. +- If you have a bash-compatible shell, use `./lint.sh` to run formatting, + linting, type checking, and tests. + +Development dependencies are pinned in `Pipfile.lock`. After changing +`Pipfile`, activate a Python 3.10 virtual environment and run `make lock` to +regenerate it. ## Contributing via issue triage [![Open Source Helpers](https://www.codetriage.com/faif/python-patterns/badges/users.svg)](https://www.codetriage.com/faif/python-patterns) You can triage issues and pull requests on [CodeTriage](https://www.codetriage.com/faif/python-patterns). diff --git a/config_backup/tox.ini b/config_backup/tox.ini index 36e2577ec..f0d8b6c1f 100644 --- a/config_backup/tox.ini +++ b/config_backup/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py312,cov-report +envlist = py,cov-report skip_missing_interpreters = true usedevelop = true @@ -7,7 +7,9 @@ usedevelop = true setenv = COVERAGE_FILE = .coverage.{envname} deps = - -r requirements-dev.txt + pipenv +commands_pre = + pipenv sync --dev --system allowlist_externals = pytest flake8 diff --git a/lint.sh b/lint.sh index a7eebda1b..3139587ad 100755 --- a/lint.sh +++ b/lint.sh @@ -1,16 +1,38 @@ -#! /bin/bash +#!/usr/bin/env bash -pip install --upgrade pip -pip install black codespell flake8 isort mypy pytest pyupgrade tox -pip install -e . +set -euo pipefail -source_dir="./patterns" +project_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +venv_dir="${project_dir}/.venv" +python_bin="${PYTHON:-python3}" -codespell --quiet-level=2 ./patterns # --ignore-words-list="" --skip="" -flake8 "${source_dir}" --count --show-source --statistics -isort --profile black "${source_dir}" -tox -mypy --ignore-missing-imports "${source_dir}" || true -pytest "${source_dir}" -pytest --doctest-modules "${source_dir}" || true -shopt -s globstar && pyupgrade --py312-plus ${source_dir}/*.py +if [[ ! -x "${venv_dir}/bin/python" ]]; then + "${python_bin}" -m venv "${venv_dir}" + "${venv_dir}/bin/python" -m pip install --upgrade pip +fi + +venv_python="${venv_dir}/bin/python" +venv_bin="${venv_dir}/bin" + +"${venv_python}" -m pip install pipenv +PIPENV_IGNORE_VIRTUALENVS=1 \ + PIPENV_VENV_IN_PROJECT=1 \ + "${venv_bin}/pipenv" sync --dev +"${venv_python}" -m pip install --no-build-isolation --no-deps -e "${project_dir}" + +source_dir="${project_dir}/patterns" +tests_dir="${project_dir}/tests" + +"${venv_bin}/codespell" --quiet-level=2 "${source_dir}" "${tests_dir}" "${project_dir}/README.md" +"${venv_bin}/flake8" \ + "${source_dir}" \ + "${tests_dir}" \ + --max-line-length=120 \ + --extend-ignore=E266,E731,W503 \ + --count \ + --show-source \ + --statistics +"${venv_bin}/isort" --profile black --check-only "${source_dir}" "${tests_dir}" +"${venv_bin}/black" --check "${source_dir}" "${tests_dir}" +"${venv_bin}/mypy" "${source_dir}" +"${venv_bin}/pytest" "${tests_dir}" "${source_dir}" diff --git a/patterns/behavioral/catalog.py b/patterns/behavioral/catalog.py index 11a730c35..4074c1d21 100644 --- a/patterns/behavioral/catalog.py +++ b/patterns/behavioral/catalog.py @@ -3,13 +3,11 @@ during initialization. Uses a single dictionary instead of multiple conditions. """ - __author__ = "Ibrahim Diop " class Catalog: - """catalog of multiple static methods that are executed depending on an init parameter - """ + """catalog of multiple static methods that are executed depending on an init parameter""" def __init__(self, param: str) -> None: # dictionary that will be used to determine which static method is diff --git a/patterns/behavioral/chain_of_responsibility.py b/patterns/behavioral/chain_of_responsibility.py index 46c3a4195..a44a9210e 100644 --- a/patterns/behavioral/chain_of_responsibility.py +++ b/patterns/behavioral/chain_of_responsibility.py @@ -23,7 +23,7 @@ """ from abc import ABC, abstractmethod -from typing import Optional, Tuple +from typing import Optional class Handler(ABC): @@ -43,7 +43,7 @@ def handle(self, request: int) -> None: self.successor.handle(request) @abstractmethod - def check_range(self, request: int) -> Optional[bool]: + def check_range(self, request: int) -> bool | None: """Compare passed value to predefined interval""" @@ -53,7 +53,7 @@ class ConcreteHandler0(Handler): """ @staticmethod - def check_range(request: int) -> Optional[bool]: + def check_range(request: int) -> bool | None: if 0 <= request < 10: print(f"request {request} handled in handler 0") return True @@ -65,7 +65,7 @@ class ConcreteHandler1(Handler): start, end = 10, 20 - def check_range(self, request: int) -> Optional[bool]: + def check_range(self, request: int) -> bool | None: if self.start <= request < self.end: print(f"request {request} handled in handler 1") return True @@ -75,7 +75,7 @@ def check_range(self, request: int) -> Optional[bool]: class ConcreteHandler2(Handler): """... With helper methods.""" - def check_range(self, request: int) -> Optional[bool]: + def check_range(self, request: int) -> bool | None: start, end = self.get_interval_from_db() if start <= request < end: print(f"request {request} handled in handler 2") @@ -83,13 +83,13 @@ def check_range(self, request: int) -> Optional[bool]: return None @staticmethod - def get_interval_from_db() -> Tuple[int, int]: + def get_interval_from_db() -> tuple[int, int]: return (20, 30) class FallbackHandler(Handler): @staticmethod - def check_range(request: int) -> Optional[bool]: + def check_range(request: int) -> bool | None: print(f"end of chain, no handler for {request}") return False diff --git a/patterns/behavioral/command.py b/patterns/behavioral/command.py index a88ea8be7..d0c3944e4 100644 --- a/patterns/behavioral/command.py +++ b/patterns/behavioral/command.py @@ -20,8 +20,6 @@ https://docs.djangoproject.com/en/2.1/ref/request-response/#httprequest-objects """ -from typing import List, Union - class HideFileCommand: """ @@ -30,7 +28,7 @@ class HideFileCommand: def __init__(self) -> None: # an array of files hidden, to undo them as needed - self._hidden_files: List[str] = [] + self._hidden_files: list[str] = [] def execute(self, filename: str) -> None: print(f"hiding {filename}") @@ -48,7 +46,7 @@ class DeleteFileCommand: def __init__(self) -> None: # an array of deleted files, to undo them as needed - self._deleted_files: List[str] = [] + self._deleted_files: list[str] = [] def execute(self, filename: str) -> None: print(f"deleting {filename}") @@ -64,7 +62,7 @@ class MenuItem: The invoker class. Here it is items in a menu. """ - def __init__(self, command: Union[HideFileCommand, DeleteFileCommand]) -> None: + def __init__(self, command: HideFileCommand | DeleteFileCommand) -> None: self._command = command def on_do_press(self, filename: str) -> None: diff --git a/patterns/behavioral/mediator.py b/patterns/behavioral/mediator.py index 6a59bbb64..122852b77 100644 --- a/patterns/behavioral/mediator.py +++ b/patterns/behavioral/mediator.py @@ -14,7 +14,7 @@ class ChatRoom: """Mediator class""" - def display_message(self, user: User, message: str) -> None: + def display_message(self, user: User, message: str) -> str: return f"[{user} says]: {message}" @@ -25,7 +25,7 @@ def __init__(self, name: str) -> None: self.name = name self.chat_room = ChatRoom() - def say(self, message: str) -> None: + def say(self, message: str) -> str: return self.chat_room.display_message(self, message) def __str__(self) -> str: diff --git a/patterns/behavioral/memento.py b/patterns/behavioral/memento.py index c0d63e9ed..a14ea8a0d 100644 --- a/patterns/behavioral/memento.py +++ b/patterns/behavioral/memento.py @@ -5,8 +5,9 @@ Provides the ability to restore an object to its previous state. """ +from collections.abc import Callable from copy import copy, deepcopy -from typing import Any, Callable, List, Type +from typing import Any def memento(obj: Any, deep: bool = False) -> Callable: @@ -26,7 +27,7 @@ class Transaction: """ deep = False - states: List[Callable[[], None]] = [] + states: list[Callable[[], None]] = [] def __init__(self, deep: bool, *targets: Any) -> None: self.deep = deep @@ -41,7 +42,7 @@ def rollback(self) -> None: a_state() -def Transactional(method): +class Transactional: """Adds transactional semantics to methods. Methods decorated with @Transactional will roll back to entry-state upon exceptions. @@ -51,7 +52,7 @@ def Transactional(method): def __init__(self, method: Callable) -> None: self.method = method - def __get__(self, obj: Any, T: Type) -> Callable: + def __get__(self, obj: Any, T: type) -> Callable: """ A decorator that makes a function transactional. @@ -66,7 +67,7 @@ def transaction(*args, **kwargs): state() raise e - return transaction + return transaction class NumObj: @@ -81,7 +82,7 @@ def increment(self) -> None: @Transactional def do_stuff(self) -> None: - self.value = "1111" # <- invalid value + self.__dict__["value"] = "1111" # <- intentionally invalid value self.increment() # <- will fail and rollback diff --git a/patterns/behavioral/observer.py b/patterns/behavioral/observer.py index c9184be17..9ea97bfc0 100644 --- a/patterns/behavioral/observer.py +++ b/patterns/behavioral/observer.py @@ -12,7 +12,7 @@ # observer.py from __future__ import annotations -from typing import List + class Observer: def update(self, subject: Subject) -> None: @@ -26,7 +26,7 @@ def update(self, subject: Subject) -> None: class Subject: - _observers: List[Observer] + _observers: list[Observer] def __init__(self) -> None: """ diff --git a/patterns/behavioral/publish_subscribe.py b/patterns/behavioral/publish_subscribe.py index 7e76955c6..91e74ab67 100644 --- a/patterns/behavioral/publish_subscribe.py +++ b/patterns/behavioral/publish_subscribe.py @@ -9,8 +9,8 @@ class Provider: def __init__(self) -> None: - self.msg_queue = [] - self.subscribers = {} + self.msg_queue: list[str] = [] + self.subscribers: dict[str, list[Subscriber]] = {} def notify(self, msg: str) -> None: self.msg_queue.append(msg) diff --git a/patterns/behavioral/registry.py b/patterns/behavioral/registry.py index 60cae019a..1288c1f6a 100644 --- a/patterns/behavioral/registry.py +++ b/patterns/behavioral/registry.py @@ -1,8 +1,5 @@ -from typing import Dict - - class RegistryHolder(type): - REGISTRY: Dict[str, "RegistryHolder"] = {} + REGISTRY: dict[str, "RegistryHolder"] = {} def __new__(cls, name, bases, attrs): new_cls = type.__new__(cls, name, bases, attrs) diff --git a/patterns/behavioral/state.py b/patterns/behavioral/state.py index db4d94682..8d792de93 100644 --- a/patterns/behavioral/state.py +++ b/patterns/behavioral/state.py @@ -14,6 +14,13 @@ class State: """Base state. This is to share functionality""" + name: str + pos: int + stations: list[str] + + def toggle_amfm(self) -> None: + raise NotImplementedError + def scan(self) -> None: """Scan the dial to the next station""" self.pos += 1 @@ -53,7 +60,7 @@ def __init__(self) -> None: """We have an AM state and an FM state""" self.amstate = AmState(self) self.fmstate = FmState(self) - self.state = self.amstate + self.state: State = self.amstate def toggle_amfm(self) -> None: self.state.toggle_amfm() diff --git a/patterns/behavioral/strategy.py b/patterns/behavioral/strategy.py index 000ff2ada..13b1c0f5d 100644 --- a/patterns/behavioral/strategy.py +++ b/patterns/behavioral/strategy.py @@ -9,12 +9,15 @@ from __future__ import annotations -from typing import Callable +from collections.abc import Callable +from typing import TypeAlias + +DiscountStrategy: TypeAlias = Callable[["Order"], float] class DiscountStrategyValidator: # Descriptor class for check perform @staticmethod - def validate(obj: Order, value: Callable) -> bool: + def validate(obj: Order, value: DiscountStrategy) -> bool: try: if obj.price - value(obj) < 0: raise ValueError( @@ -29,20 +32,24 @@ def validate(obj: Order, value: Callable) -> bool: def __set_name__(self, owner, name: str) -> None: self.private_name = f"_{name}" - def __set__(self, obj: Order, value: Callable = None) -> None: - if value and self.validate(obj, value): + def __set__(self, obj: Order, value: DiscountStrategy | None = None) -> None: + if value is not None and self.validate(obj, value): setattr(obj, self.private_name, value) else: setattr(obj, self.private_name, None) - def __get__(self, obj: object, objtype: type = None): + def __get__( + self, obj: Order, objtype: type[Order] | None = None + ) -> DiscountStrategy | None: return getattr(obj, self.private_name) class Order: discount_strategy = DiscountStrategyValidator() - def __init__(self, price: float, discount_strategy: Callable = None) -> None: + def __init__( + self, price: float, discount_strategy: DiscountStrategy | None = None + ) -> None: self.price: float = price self.discount_strategy = discount_strategy diff --git a/patterns/behavioral/visitor.py b/patterns/behavioral/visitor.py index aa10b58c7..ab2f5f713 100644 --- a/patterns/behavioral/visitor.py +++ b/patterns/behavioral/visitor.py @@ -14,7 +14,6 @@ which is then being used e.g. in tools like `pyflakes`. - `Black` formatter tool implements it's own: https://github.com/ambv/black/blob/master/black.py#L718 """ -from typing import Union class Node: @@ -34,7 +33,7 @@ class C(A, B): class Visitor: - def visit(self, node: Union[A, C, B], *args, **kwargs) -> None: + def visit(self, node: Node, *args, **kwargs) -> str: meth = None for cls in node.__class__.__mro__: meth_name = "visit_" + cls.__name__ @@ -46,11 +45,11 @@ def visit(self, node: Union[A, C, B], *args, **kwargs) -> None: meth = self.generic_visit return meth(node, *args, **kwargs) - def generic_visit(self, node: A, *args, **kwargs) -> None: - print("generic_visit " + node.__class__.__name__) + def generic_visit(self, node: Node, *args, **kwargs) -> str: + return "generic_visit " + node.__class__.__name__ - def visit_B(self, node: Union[C, B], *args, **kwargs) -> None: - print("visit_B " + node.__class__.__name__) + def visit_B(self, node: Node, *args, **kwargs) -> str: + return "visit_B " + node.__class__.__name__ def main(): diff --git a/patterns/creational/abstract_factory.py b/patterns/creational/abstract_factory.py index 15e5d67f8..2362d8fef 100644 --- a/patterns/creational/abstract_factory.py +++ b/patterns/creational/abstract_factory.py @@ -31,7 +31,6 @@ """ import random -from typing import Type class Pet: @@ -64,7 +63,7 @@ def __str__(self) -> str: class PetShop: """A pet shop""" - def __init__(self, animal_factory: Type[Pet]) -> None: + def __init__(self, animal_factory: type[Pet]) -> None: """pet_factory is our abstract factory. We can set it at will.""" self.pet_factory = animal_factory @@ -91,7 +90,7 @@ def main() -> None: if __name__ == "__main__": animals = [Dog, Cat] - random_animal: Type[Pet] = random.choice(animals) + random_animal: type[Pet] = random.choice(animals) shop = PetShop(random_animal) import doctest diff --git a/patterns/creational/borg.py b/patterns/creational/borg.py index edd0589d9..7a8073e69 100644 --- a/patterns/creational/borg.py +++ b/patterns/creational/borg.py @@ -33,18 +33,16 @@ Provides singleton-like behavior sharing state between instances. """ -from typing import Dict - class Borg: - _shared_state: Dict[str, str] = {} + _shared_state: dict[str, str] = {} def __init__(self) -> None: self.__dict__ = self._shared_state class YourBorg(Borg): - def __init__(self, state: str = None) -> None: + def __init__(self, state: str | None = None) -> None: super().__init__() if state: self.state = state diff --git a/patterns/creational/builder.py b/patterns/creational/builder.py index 16af2295d..c5253ad30 100644 --- a/patterns/creational/builder.py +++ b/patterns/creational/builder.py @@ -29,9 +29,11 @@ class for a building, where the initializer (__init__ method) specifies the """ - # Abstract Building class Building: + floor: str + size: str + def __init__(self) -> None: self.build_floor() self.build_size() @@ -70,6 +72,9 @@ def build_size(self) -> None: class ComplexBuilding: + floor: str + size: str + def __repr__(self) -> str: return "Floor: {0.floor} | Size: {0.size}".format(self) diff --git a/patterns/creational/factory.py b/patterns/creational/factory.py index c8fea1124..801575bdf 100644 --- a/patterns/creational/factory.py +++ b/patterns/creational/factory.py @@ -22,7 +22,7 @@ Creates objects without having to specify the exact class. """ -from typing import Dict, Protocol, Type +from typing import Protocol class Localizer(Protocol): @@ -49,7 +49,7 @@ def localize(self, msg: str) -> str: def get_localizer(language: str = "English") -> Localizer: """Factory""" - localizers: Dict[str, Type[Localizer]] = { + localizers: dict[str, type[Localizer]] = { "English": EnglishLocalizer, "Greek": GreekLocalizer, } diff --git a/patterns/creational/lazy_evaluation.py b/patterns/creational/lazy_evaluation.py index 1f8db6bdd..b9945d998 100644 --- a/patterns/creational/lazy_evaluation.py +++ b/patterns/creational/lazy_evaluation.py @@ -20,15 +20,24 @@ """ import functools -from typing import Callable, Type +from collections.abc import Callable +from typing import Any, overload class lazy_property: - def __init__(self, function: Callable) -> None: + def __init__(self, function: Callable[["Person"], str]) -> None: self.function = function - functools.update_wrapper(self, function) + functools.update_wrapper(self, function) # type: ignore[arg-type] - def __get__(self, obj: "Person", type_: Type["Person"]) -> str: + @overload + def __get__(self, obj: None, type_: type["Person"]) -> "lazy_property": ... + + @overload + def __get__(self, obj: "Person", type_: type["Person"]) -> str: ... + + def __get__( + self, obj: "Person | None", type_: type["Person"] + ) -> "lazy_property | str": if obj is None: return self val = self.function(obj) @@ -36,7 +45,7 @@ def __get__(self, obj: "Person", type_: Type["Person"]) -> str: return val -def lazy_property2(fn: Callable) -> property: +def lazy_property2(fn: Callable[[Any], str]) -> property: """ A lazy property decorator. @@ -45,13 +54,12 @@ def lazy_property2(fn: Callable) -> property: """ attr = "_lazy__" + fn.__name__ - @property - def _lazy_property(self): + def _lazy_property(self: Any) -> str: if not hasattr(self, attr): setattr(self, attr, fn(self)) return getattr(self, attr) - return _lazy_property + return property(_lazy_property) class Person: diff --git a/patterns/creational/pool.py b/patterns/creational/pool.py index 02f617919..684b11857 100644 --- a/patterns/creational/pool.py +++ b/patterns/creational/pool.py @@ -27,9 +27,9 @@ *TL;DR Stores a set of initialized objects kept ready to use. """ + from queue import Queue from types import TracebackType -from typing import Union class ObjectPool: @@ -44,9 +44,9 @@ def __enter__(self) -> str: def __exit__( self, - Type: Union[type[BaseException], None], - value: Union[BaseException, None], - traceback: Union[TracebackType, None], + Type: type[BaseException] | None, + value: BaseException | None, + traceback: TracebackType | None, ) -> None: if self.item is not None: self._queue.put(self.item) diff --git a/patterns/dependency_injection.py b/patterns/dependency_injection.py index 2979f763c..4246d11a2 100644 --- a/patterns/dependency_injection.py +++ b/patterns/dependency_injection.py @@ -24,7 +24,7 @@ def get_current_time_as_html_fragment(self): """ import datetime -from typing import Callable +from collections.abc import Callable class ConstructorInjection: @@ -102,7 +102,7 @@ def main(): >>> time_with_si.get_current_time_as_html_fragment() Traceback (most recent call last): ... - AttributeError: 'SetterInjection' object has no attribute 'time_provider' + AttributeError: ... >>> time_with_si.set_time_provider(midnight_time_provider) >>> time_with_si.get_current_time_as_html_fragment() diff --git a/patterns/fundamental/delegation_pattern.py b/patterns/fundamental/delegation_pattern.py index f7a7c2f5c..c035da202 100644 --- a/patterns/fundamental/delegation_pattern.py +++ b/patterns/fundamental/delegation_pattern.py @@ -8,7 +8,8 @@ from __future__ import annotations -from typing import Any, Callable +from collections.abc import Callable +from typing import Any class Delegator: @@ -19,7 +20,7 @@ class Delegator: >>> delegator.p2 Traceback (most recent call last): ... - AttributeError: 'Delegate' object has no attribute 'p2'. Did you mean: 'p1'? + AttributeError: ... >>> delegator.do_something("nothing") 'Doing nothing' >>> delegator.do_something("something", kw=", faif!") @@ -27,7 +28,7 @@ class Delegator: >>> delegator.do_anything() Traceback (most recent call last): ... - AttributeError: 'Delegate' object has no attribute 'do_anything'. Did you mean: 'do_something'? + AttributeError: ... """ def __init__(self, delegate: Delegate) -> None: diff --git a/patterns/other/blackboard.py b/patterns/other/blackboard.py index 0269a3e7c..e69582ec3 100644 --- a/patterns/other/blackboard.py +++ b/patterns/other/blackboard.py @@ -9,8 +9,8 @@ https://en.wikipedia.org/wiki/Blackboard_system """ -from abc import ABC, abstractmethod import random +from abc import ABC, abstractmethod class AbstractExpert(ABC): @@ -115,6 +115,7 @@ def contribute(self) -> None: def main(): """ + >>> random.seed(1234) >>> blackboard = Blackboard() >>> blackboard.add_expert(Student(blackboard)) >>> blackboard.add_expert(Scientist(blackboard)) @@ -123,15 +124,10 @@ def main(): >>> c = Controller(blackboard) >>> contributions = c.run_loop() - >>> from pprint import pprint - >>> pprint(contributions) - ['Student', - 'Scientist', - 'Student', - 'Scientist', - 'Student', - 'Scientist', - 'Professor'] + >>> contributions[0], contributions[-1] + ('Student', 'Professor') + >>> set(contributions) == {"Student", "Scientist", "Professor"} + True """ diff --git a/patterns/other/graph_search.py b/patterns/other/graph_search.py index 6e3cdffb3..e3c921409 100644 --- a/patterns/other/graph_search.py +++ b/patterns/other/graph_search.py @@ -1,6 +1,3 @@ -from typing import Any, Dict, List, Optional, Union - - class GraphSearch: """Graph search emulation in python, from source http://www.python.org/doc/essays/graphs/ @@ -8,12 +5,12 @@ class GraphSearch: dfs stands for Depth First Search bfs stands for Breadth First Search""" - def __init__(self, graph: Dict[str, List[str]]) -> None: + def __init__(self, graph: dict[str, list[str]]) -> None: self.graph = graph def find_path_dfs( - self, start: str, end: str, path: Optional[List[str]] = None - ) -> Optional[List[str]]: + self, start: str, end: str, path: list[str] | None = None + ) -> list[str] | None: path = path or [] path.append(start) @@ -24,15 +21,16 @@ def find_path_dfs( newpath = self.find_path_dfs(node, end, path[:]) if newpath: return newpath + return None def find_all_paths_dfs( - self, start: str, end: str, path: Optional[List[str]] = None - ) -> List[Union[List[str], Any]]: + self, start: str, end: str, path: list[str] | None = None + ) -> list[list[str]]: path = path or [] path.append(start) if start == end: return [path] - paths = [] + paths: list[list[str]] = [] for node in self.graph.get(start, []): if node not in path: newpaths = self.find_all_paths_dfs(node, end, path[:]) @@ -40,8 +38,8 @@ def find_all_paths_dfs( return paths def find_shortest_path_dfs( - self, start: str, end: str, path: Optional[List[str]] = None - ) -> Optional[List[str]]: + self, start: str, end: str, path: list[str] | None = None + ) -> list[str] | None: path = path or [] path.append(start) @@ -56,7 +54,7 @@ def find_shortest_path_dfs( shortest = newpath return shortest - def find_shortest_path_bfs(self, start: str, end: str) -> Optional[List[str]]: + def find_shortest_path_bfs(self, start: str, end: str) -> list[str] | None: """ Finds the shortest path between two nodes in a graph using breadth-first search. @@ -71,9 +69,9 @@ def find_shortest_path_bfs(self, start: str, end: str) -> Optional[List[str]]: (in terms of hops). If no such path exists, returns an empty list and an empty dictionary instead. """ - queue = [start] - dist_to = {start: 0} - edge_to = {} + queue: list[str] = [start] + dist_to: dict[str, int] = {start: 0} + edge_to: dict[str, str] = {} if start == end: return queue @@ -86,13 +84,14 @@ def find_shortest_path_bfs(self, start: str, end: str) -> Optional[List[str]]: dist_to[node] = dist_to[value] + 1 queue.append(node) if end in edge_to.keys(): - path = [] + path: list[str] = [] node = end while dist_to[node] != 0: path.insert(0, node) node = edge_to[node] path.insert(0, start) return path + return None def main(): diff --git a/patterns/structural/3-tier.py b/patterns/structural/3-tier.py index 287badaf8..3c266f514 100644 --- a/patterns/structural/3-tier.py +++ b/patterns/structural/3-tier.py @@ -3,7 +3,7 @@ Separates presentation, application processing, and data management functions. """ -from typing import Dict, KeysView, Optional, Union +from collections.abc import KeysView class Data: @@ -28,9 +28,7 @@ class BusinessLogic: def product_list(self) -> KeysView[str]: return self.data["products"].keys() - def product_information( - self, product: str - ) -> Optional[Dict[str, Union[int, float]]]: + def product_information(self, product: str) -> dict[str, int | float] | None: return self.data["products"].get(product, None) diff --git a/patterns/structural/adapter.py b/patterns/structural/adapter.py index 22adca88b..5afe42faf 100644 --- a/patterns/structural/adapter.py +++ b/patterns/structural/adapter.py @@ -28,7 +28,8 @@ Allows the interface of an existing class to be used as another interface. """ -from typing import Callable, TypeVar, Any, Dict +from collections.abc import Callable +from typing import Any, TypeVar T = TypeVar("T") @@ -83,7 +84,7 @@ def __getattr__(self, attr: str) -> Any: """All non-adapted calls are passed to the object.""" return getattr(self.obj, attr) - def original_dict(self) -> Dict[str, Any]: + def original_dict(self) -> dict[str, Any]: """Print original object dict.""" return self.obj.__dict__ diff --git a/patterns/structural/bridge.py b/patterns/structural/bridge.py index 1575cb53a..0990efc38 100644 --- a/patterns/structural/bridge.py +++ b/patterns/structural/bridge.py @@ -5,7 +5,6 @@ *TL;DR Decouples an abstraction from its implementation. """ -from typing import Union # ConcreteImplementor 1/2 @@ -23,11 +22,11 @@ def draw_circle(self, x: int, y: int, radius: float) -> None: # Refined Abstraction class CircleShape: def __init__( - self, x: int, y: int, radius: int, drawing_api: Union[DrawingAPI2, DrawingAPI1] + self, x: int, y: int, radius: float, drawing_api: DrawingAPI2 | DrawingAPI1 ) -> None: self._x = x self._y = y - self._radius = radius + self._radius: float = radius self._drawing_api = drawing_api # low-level i.e. Implementation specific diff --git a/patterns/structural/composite.py b/patterns/structural/composite.py index a4bedc1d4..ee0520c6b 100644 --- a/patterns/structural/composite.py +++ b/patterns/structural/composite.py @@ -27,7 +27,6 @@ """ from abc import ABC, abstractmethod -from typing import List class Graphic(ABC): @@ -38,7 +37,7 @@ def render(self) -> None: class CompositeGraphic(Graphic): def __init__(self) -> None: - self.graphics: List[Graphic] = [] + self.graphics: list[Graphic] = [] def render(self) -> None: for graphic in self.graphics: diff --git a/patterns/structural/flyweight.py b/patterns/structural/flyweight.py index 68b6f43cd..148a5bc05 100644 --- a/patterns/structural/flyweight.py +++ b/patterns/structural/flyweight.py @@ -31,12 +31,15 @@ class Card: """The Flyweight""" + value: str + suit: str + # Could be a simple dict. # With WeakValueDictionary garbage collection can reclaim the object # when there are no other references to it. - _pool: weakref.WeakValueDictionary = weakref.WeakValueDictionary() + _pool: weakref.WeakValueDictionary[str, "Card"] = weakref.WeakValueDictionary() - def __new__(cls, value: str, suit: str): + def __new__(cls, value: str, suit: str) -> "Card": # If the object exists in the pool - just return it obj = cls._pool.get(value + suit) # otherwise - create new one (and add it to the pool) diff --git a/patterns/structural/mvc.py b/patterns/structural/mvc.py index 0a7c40343..5726b0897 100644 --- a/patterns/structural/mvc.py +++ b/patterns/structural/mvc.py @@ -4,9 +4,9 @@ """ from abc import ABC, abstractmethod -from typing import Dict, List, Union, Any from inspect import signature from sys import argv +from typing import Any class Model(ABC): diff --git a/patterns/structural/proxy.py b/patterns/structural/proxy.py index 3ef74ec03..c12fb051c 100644 --- a/patterns/structural/proxy.py +++ b/patterns/structural/proxy.py @@ -15,8 +15,6 @@ without changing its interface. """ -from typing import Union - class Subject: """ @@ -59,7 +57,7 @@ def do_the_job(self, user: str) -> None: print("[log] I can do the job just for `admins`.") -def client(job_doer: Union[RealSubject, Proxy], user: str) -> None: +def client(job_doer: RealSubject | Proxy, user: str) -> None: job_doer.do_the_job(user) diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index a29924044..000000000 --- a/poetry.lock +++ /dev/null @@ -1,1029 +0,0 @@ -# This file is automatically @generated by Poetry 2.3.1 and should not be changed by hand. - -[[package]] -name = "argcomplete" -version = "3.6.3" -description = "Bash tab completion for argparse" -optional = true -python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "argcomplete-3.6.3-py3-none-any.whl", hash = "sha256:f5007b3a600ccac5d25bbce33089211dfd49eab4a7718da3f10e3082525a92ce"}, - {file = "argcomplete-3.6.3.tar.gz", hash = "sha256:62e8ed4fd6a45864acc8235409461b72c9a28ee785a2011cc5eb78318786c89c"}, -] - -[package.extras] -test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] - -[[package]] -name = "black" -version = "26.1.0" -description = "The uncompromising code formatter." -optional = true -python-versions = ">=3.10" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "black-26.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ca699710dece84e3ebf6e92ee15f5b8f72870ef984bf944a57a777a48357c168"}, - {file = "black-26.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5e8e75dabb6eb83d064b0db46392b25cabb6e784ea624219736e8985a6b3675d"}, - {file = "black-26.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eb07665d9a907a1a645ee41a0df8a25ffac8ad9c26cdb557b7b88eeeeec934e0"}, - {file = "black-26.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:7ed300200918147c963c87700ccf9966dceaefbbb7277450a8d646fc5646bf24"}, - {file = "black-26.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:c5b7713daea9bf943f79f8c3b46f361cc5229e0e604dcef6a8bb6d1c37d9df89"}, - {file = "black-26.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3cee1487a9e4c640dc7467aaa543d6c0097c391dc8ac74eb313f2fbf9d7a7cb5"}, - {file = "black-26.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d62d14ca31c92adf561ebb2e5f2741bf8dea28aef6deb400d49cca011d186c68"}, - {file = "black-26.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fb1dafbbaa3b1ee8b4550a84425aac8874e5f390200f5502cf3aee4a2acb2f14"}, - {file = "black-26.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:101540cb2a77c680f4f80e628ae98bd2bd8812fb9d72ade4f8995c5ff019e82c"}, - {file = "black-26.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:6f3977a16e347f1b115662be07daa93137259c711e526402aa444d7a88fdc9d4"}, - {file = "black-26.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6eeca41e70b5f5c84f2f913af857cf2ce17410847e1d54642e658e078da6544f"}, - {file = "black-26.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dd39eef053e58e60204f2cdf059e2442e2eb08f15989eefe259870f89614c8b6"}, - {file = "black-26.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9459ad0d6cd483eacad4c6566b0f8e42af5e8b583cee917d90ffaa3778420a0a"}, - {file = "black-26.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a19915ec61f3a8746e8b10adbac4a577c6ba9851fa4a9e9fbfbcf319887a5791"}, - {file = "black-26.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:643d27fb5facc167c0b1b59d0315f2674a6e950341aed0fc05cf307d22bf4954"}, - {file = "black-26.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ba1d768fbfb6930fc93b0ecc32a43d8861ded16f47a40f14afa9bb04ab93d304"}, - {file = "black-26.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2b807c240b64609cb0e80d2200a35b23c7df82259f80bef1b2c96eb422b4aac9"}, - {file = "black-26.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1de0f7d01cc894066a1153b738145b194414cc6eeaad8ef4397ac9abacf40f6b"}, - {file = "black-26.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:91a68ae46bf07868963671e4d05611b179c2313301bd756a89ad4e3b3db2325b"}, - {file = "black-26.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:be5e2fe860b9bd9edbf676d5b60a9282994c03fbbd40fe8f5e75d194f96064ca"}, - {file = "black-26.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9dc8c71656a79ca49b8d3e2ce8103210c9481c57798b48deeb3a8bb02db5f115"}, - {file = "black-26.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b22b3810451abe359a964cc88121d57f7bce482b53a066de0f1584988ca36e79"}, - {file = "black-26.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:53c62883b3f999f14e5d30b5a79bd437236658ad45b2f853906c7cbe79de00af"}, - {file = "black-26.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:f016baaadc423dc960cdddf9acae679e71ee02c4c341f78f3179d7e4819c095f"}, - {file = "black-26.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:66912475200b67ef5a0ab665011964bf924745103f51977a78b4fb92a9fc1bf0"}, - {file = "black-26.1.0-py3-none-any.whl", hash = "sha256:1054e8e47ebd686e078c0bb0eaf31e6ce69c966058d122f2c0c950311f9f3ede"}, - {file = "black-26.1.0.tar.gz", hash = "sha256:d294ac3340eef9c9eb5d29288e96dc719ff269a88e27b396340459dd85da4c58"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=1.0.0" -platformdirs = ">=2" -pytokens = ">=0.3.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.10)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - -[[package]] -name = "build" -version = "1.4.0" -description = "A simple, correct Python build frontend" -optional = true -python-versions = ">=3.9" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "build-1.4.0-py3-none-any.whl", hash = "sha256:6a07c1b8eb6f2b311b96fcbdbce5dab5fe637ffda0fd83c9cac622e927501596"}, - {file = "build-1.4.0.tar.gz", hash = "sha256:f1b91b925aa322be454f8330c6fb48b465da993d1e7e7e6fa35027ec49f3c936"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "os_name == \"nt\""} -importlib-metadata = {version = ">=4.6", markers = "python_full_version < \"3.10.2\""} -packaging = ">=24.0" -pyproject_hooks = "*" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} - -[package.extras] -uv = ["uv (>=0.1.18)"] -virtualenv = ["virtualenv (>=20.11) ; python_version < \"3.10\"", "virtualenv (>=20.17) ; python_version >= \"3.10\" and python_version < \"3.14\"", "virtualenv (>=20.31) ; python_version >= \"3.14\""] - -[[package]] -name = "cachetools" -version = "6.2.4" -description = "Extensible memoizing collections and decorators" -optional = true -python-versions = ">=3.9" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "cachetools-6.2.4-py3-none-any.whl", hash = "sha256:69a7a52634fed8b8bf6e24a050fb60bff1c9bd8f6d24572b99c32d4e71e62a51"}, - {file = "cachetools-6.2.4.tar.gz", hash = "sha256:82c5c05585e70b6ba2d3ae09ea60b79548872185d2f24ae1f2709d37299fd607"}, -] - -[[package]] -name = "chardet" -version = "5.2.0" -description = "Universal encoding detector for Python 3" -optional = true -python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"}, - {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"}, -] - -[[package]] -name = "click" -version = "8.3.1" -description = "Composable command line interface toolkit" -optional = true -python-versions = ">=3.10" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6"}, - {file = "click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = true -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "coverage" -version = "7.13.1" -description = "Code coverage measurement for Python" -optional = true -python-versions = ">=3.10" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "coverage-7.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e1fa280b3ad78eea5be86f94f461c04943d942697e0dac889fa18fff8f5f9147"}, - {file = "coverage-7.13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c3d8c679607220979434f494b139dfb00131ebf70bb406553d69c1ff01a5c33d"}, - {file = "coverage-7.13.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:339dc63b3eba969067b00f41f15ad161bf2946613156fb131266d8debc8e44d0"}, - {file = "coverage-7.13.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:db622b999ffe49cb891f2fff3b340cdc2f9797d01a0a202a0973ba2562501d90"}, - {file = "coverage-7.13.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1443ba9acbb593fa7c1c29e011d7c9761545fe35e7652e85ce7f51a16f7e08d"}, - {file = "coverage-7.13.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c832ec92c4499ac463186af72f9ed4d8daec15499b16f0a879b0d1c8e5cf4a3b"}, - {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:562ec27dfa3f311e0db1ba243ec6e5f6ab96b1edfcfc6cf86f28038bc4961ce6"}, - {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4de84e71173d4dada2897e5a0e1b7877e5eefbfe0d6a44edee6ce31d9b8ec09e"}, - {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:a5a68357f686f8c4d527a2dc04f52e669c2fc1cbde38f6f7eb6a0e58cbd17cae"}, - {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:77cc258aeb29a3417062758975521eae60af6f79e930d6993555eeac6a8eac29"}, - {file = "coverage-7.13.1-cp310-cp310-win32.whl", hash = "sha256:bb4f8c3c9a9f34423dba193f241f617b08ffc63e27f67159f60ae6baf2dcfe0f"}, - {file = "coverage-7.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:c8e2706ceb622bc63bac98ebb10ef5da80ed70fbd8a7999a5076de3afaef0fb1"}, - {file = "coverage-7.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a55d509a1dc5a5b708b5dad3b5334e07a16ad4c2185e27b40e4dba796ab7f88"}, - {file = "coverage-7.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d010d080c4888371033baab27e47c9df7d6fb28d0b7b7adf85a4a49be9298b3"}, - {file = "coverage-7.13.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d938b4a840fb1523b9dfbbb454f652967f18e197569c32266d4d13f37244c3d9"}, - {file = "coverage-7.13.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf100a3288f9bb7f919b87eb84f87101e197535b9bd0e2c2b5b3179633324fee"}, - {file = "coverage-7.13.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef6688db9bf91ba111ae734ba6ef1a063304a881749726e0d3575f5c10a9facf"}, - {file = "coverage-7.13.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b609fc9cdbd1f02e51f67f51e5aee60a841ef58a68d00d5ee2c0faf357481a3"}, - {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c43257717611ff5e9a1d79dce8e47566235ebda63328718d9b65dd640bc832ef"}, - {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e09fbecc007f7b6afdfb3b07ce5bd9f8494b6856dd4f577d26c66c391b829851"}, - {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a03a4f3a19a189919c7055098790285cc5c5b0b3976f8d227aea39dbf9f8bfdb"}, - {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3820778ea1387c2b6a818caec01c63adc5b3750211af6447e8dcfb9b6f08dbba"}, - {file = "coverage-7.13.1-cp311-cp311-win32.whl", hash = "sha256:ff10896fa55167371960c5908150b434b71c876dfab97b69478f22c8b445ea19"}, - {file = "coverage-7.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:a998cc0aeeea4c6d5622a3754da5a493055d2d95186bad877b0a34ea6e6dbe0a"}, - {file = "coverage-7.13.1-cp311-cp311-win_arm64.whl", hash = "sha256:fea07c1a39a22614acb762e3fbbb4011f65eedafcb2948feeef641ac78b4ee5c"}, - {file = "coverage-7.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6f34591000f06e62085b1865c9bc5f7858df748834662a51edadfd2c3bfe0dd3"}, - {file = "coverage-7.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b67e47c5595b9224599016e333f5ec25392597a89d5744658f837d204e16c63e"}, - {file = "coverage-7.13.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e7b8bd70c48ffb28461ebe092c2345536fb18bbbf19d287c8913699735f505c"}, - {file = "coverage-7.13.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c223d078112e90dc0e5c4e35b98b9584164bea9fbbd221c0b21c5241f6d51b62"}, - {file = "coverage-7.13.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:794f7c05af0763b1bbd1b9e6eff0e52ad068be3b12cd96c87de037b01390c968"}, - {file = "coverage-7.13.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0642eae483cc8c2902e4af7298bf886d605e80f26382124cddc3967c2a3df09e"}, - {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5e772ed5fef25b3de9f2008fe67b92d46831bd2bc5bdc5dd6bfd06b83b316f"}, - {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:45980ea19277dc0a579e432aef6a504fe098ef3a9032ead15e446eb0f1191aee"}, - {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f18eca6028ffa62adbd185a8f1e1dd242f2e68164dba5c2b74a5204850b4cf"}, - {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8dca5590fec7a89ed6826fce625595279e586ead52e9e958d3237821fbc750c"}, - {file = "coverage-7.13.1-cp312-cp312-win32.whl", hash = "sha256:ff86d4e85188bba72cfb876df3e11fa243439882c55957184af44a35bd5880b7"}, - {file = "coverage-7.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:16cc1da46c04fb0fb128b4dc430b78fa2aba8a6c0c9f8eb391fd5103409a6ac6"}, - {file = "coverage-7.13.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d9bc218650022a768f3775dd7fdac1886437325d8d295d923ebcfef4892ad5c"}, - {file = "coverage-7.13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cb237bfd0ef4d5eb6a19e29f9e528ac67ac3be932ea6b44fb6cc09b9f3ecff78"}, - {file = "coverage-7.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1dcb645d7e34dcbcc96cd7c132b1fc55c39263ca62eb961c064eb3928997363b"}, - {file = "coverage-7.13.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3d42df8201e00384736f0df9be2ced39324c3907607d17d50d50116c989d84cd"}, - {file = "coverage-7.13.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa3edde1aa8807de1d05934982416cb3ec46d1d4d91e280bcce7cca01c507992"}, - {file = "coverage-7.13.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9edd0e01a343766add6817bc448408858ba6b489039eaaa2018474e4001651a4"}, - {file = "coverage-7.13.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:985b7836931d033570b94c94713c6dba5f9d3ff26045f72c3e5dbc5fe3361e5a"}, - {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ffed1e4980889765c84a5d1a566159e363b71d6b6fbaf0bebc9d3c30bc016766"}, - {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8842af7f175078456b8b17f1b73a0d16a65dcbdc653ecefeb00a56b3c8c298c4"}, - {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:ccd7a6fca48ca9c131d9b0a2972a581e28b13416fc313fb98b6d24a03ce9a398"}, - {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0403f647055de2609be776965108447deb8e384fe4a553c119e3ff6bfbab4784"}, - {file = "coverage-7.13.1-cp313-cp313-win32.whl", hash = "sha256:549d195116a1ba1e1ae2f5ca143f9777800f6636eab917d4f02b5310d6d73461"}, - {file = "coverage-7.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:5899d28b5276f536fcf840b18b61a9fce23cc3aec1d114c44c07fe94ebeaa500"}, - {file = "coverage-7.13.1-cp313-cp313-win_arm64.whl", hash = "sha256:868a2fae76dfb06e87291bcbd4dcbcc778a8500510b618d50496e520bd94d9b9"}, - {file = "coverage-7.13.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:67170979de0dacac3f3097d02b0ad188d8edcea44ccc44aaa0550af49150c7dc"}, - {file = "coverage-7.13.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f80e2bb21bfab56ed7405c2d79d34b5dc0bc96c2c1d2a067b643a09fb756c43a"}, - {file = "coverage-7.13.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f83351e0f7dcdb14d7326c3d8d8c4e915fa685cbfdc6281f9470d97a04e9dfe4"}, - {file = "coverage-7.13.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb3f6562e89bad0110afbe64e485aac2462efdce6232cdec7862a095dc3412f6"}, - {file = "coverage-7.13.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77545b5dcda13b70f872c3b5974ac64c21d05e65b1590b441c8560115dc3a0d1"}, - {file = "coverage-7.13.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4d240d260a1aed814790bbe1f10a5ff31ce6c21bc78f0da4a1e8268d6c80dbd"}, - {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d2287ac9360dec3837bfdad969963a5d073a09a85d898bd86bea82aa8876ef3c"}, - {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0d2c11f3ea4db66b5cbded23b20185c35066892c67d80ec4be4bab257b9ad1e0"}, - {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:3fc6a169517ca0d7ca6846c3c5392ef2b9e38896f61d615cb75b9e7134d4ee1e"}, - {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d10a2ed46386e850bb3de503a54f9fe8192e5917fcbb143bfef653a9355e9a53"}, - {file = "coverage-7.13.1-cp313-cp313t-win32.whl", hash = "sha256:75a6f4aa904301dab8022397a22c0039edc1f51e90b83dbd4464b8a38dc87842"}, - {file = "coverage-7.13.1-cp313-cp313t-win_amd64.whl", hash = "sha256:309ef5706e95e62578cda256b97f5e097916a2c26247c287bbe74794e7150df2"}, - {file = "coverage-7.13.1-cp313-cp313t-win_arm64.whl", hash = "sha256:92f980729e79b5d16d221038dbf2e8f9a9136afa072f9d5d6ed4cb984b126a09"}, - {file = "coverage-7.13.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:97ab3647280d458a1f9adb85244e81587505a43c0c7cff851f5116cd2814b894"}, - {file = "coverage-7.13.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8f572d989142e0908e6acf57ad1b9b86989ff057c006d13b76c146ec6a20216a"}, - {file = "coverage-7.13.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d72140ccf8a147e94274024ff6fd8fb7811354cf7ef88b1f0a988ebaa5bc774f"}, - {file = "coverage-7.13.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3c9f051b028810f5a87c88e5d6e9af3c0ff32ef62763bf15d29f740453ca909"}, - {file = "coverage-7.13.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f398ba4df52d30b1763f62eed9de5620dcde96e6f491f4c62686736b155aa6e4"}, - {file = "coverage-7.13.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:132718176cc723026d201e347f800cd1a9e4b62ccd3f82476950834dad501c75"}, - {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e549d642426e3579b3f4b92d0431543b012dcb6e825c91619d4e93b7363c3f9"}, - {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:90480b2134999301eea795b3a9dbf606c6fbab1b489150c501da84a959442465"}, - {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e825dbb7f84dfa24663dd75835e7257f8882629fc11f03ecf77d84a75134b864"}, - {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:623dcc6d7a7ba450bbdbeedbaa0c42b329bdae16491af2282f12a7e809be7eb9"}, - {file = "coverage-7.13.1-cp314-cp314-win32.whl", hash = "sha256:6e73ebb44dca5f708dc871fe0b90cf4cff1a13f9956f747cc87b535a840386f5"}, - {file = "coverage-7.13.1-cp314-cp314-win_amd64.whl", hash = "sha256:be753b225d159feb397bd0bf91ae86f689bad0da09d3b301478cd39b878ab31a"}, - {file = "coverage-7.13.1-cp314-cp314-win_arm64.whl", hash = "sha256:228b90f613b25ba0019361e4ab81520b343b622fc657daf7e501c4ed6a2366c0"}, - {file = "coverage-7.13.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:60cfb538fe9ef86e5b2ab0ca8fc8d62524777f6c611dcaf76dc16fbe9b8e698a"}, - {file = "coverage-7.13.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:57dfc8048c72ba48a8c45e188d811e5efd7e49b387effc8fb17e97936dde5bf6"}, - {file = "coverage-7.13.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3f2f725aa3e909b3c5fdb8192490bdd8e1495e85906af74fe6e34a2a77ba0673"}, - {file = "coverage-7.13.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ee68b21909686eeb21dfcba2c3b81fee70dcf38b140dcd5aa70680995fa3aa5"}, - {file = "coverage-7.13.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724b1b270cb13ea2e6503476e34541a0b1f62280bc997eab443f87790202033d"}, - {file = "coverage-7.13.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:916abf1ac5cf7eb16bc540a5bf75c71c43a676f5c52fcb9fe75a2bd75fb944e8"}, - {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:776483fd35b58d8afe3acbd9988d5de592ab6da2d2a865edfdbc9fdb43e7c486"}, - {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b6f3b96617e9852703f5b633ea01315ca45c77e879584f283c44127f0f1ec564"}, - {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd63e7b74661fed317212fab774e2a648bc4bb09b35f25474f8e3325d2945cd7"}, - {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:933082f161bbb3e9f90d00990dc956120f608cdbcaeea15c4d897f56ef4fe416"}, - {file = "coverage-7.13.1-cp314-cp314t-win32.whl", hash = "sha256:18be793c4c87de2965e1c0f060f03d9e5aff66cfeae8e1dbe6e5b88056ec153f"}, - {file = "coverage-7.13.1-cp314-cp314t-win_amd64.whl", hash = "sha256:0e42e0ec0cd3e0d851cb3c91f770c9301f48647cb2877cb78f74bdaa07639a79"}, - {file = "coverage-7.13.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eaecf47ef10c72ece9a2a92118257da87e460e113b83cc0d2905cbbe931792b4"}, - {file = "coverage-7.13.1-py3-none-any.whl", hash = "sha256:2016745cb3ba554469d02819d78958b571792bb68e31302610e898f80dd3a573"}, - {file = "coverage-7.13.1.tar.gz", hash = "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd"}, -] - -[package.dependencies] -tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} - -[package.extras] -toml = ["tomli ; python_full_version <= \"3.11.0a6\""] - -[[package]] -name = "distlib" -version = "0.4.0" -description = "Distribution utilities" -optional = true -python-versions = "*" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, - {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, -] - -[[package]] -name = "exceptiongroup" -version = "1.3.1" -description = "Backport of PEP 654 (exception groups)" -optional = true -python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"dev\" and python_version == \"3.10\"" -files = [ - {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"}, - {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"}, -] - -[package.dependencies] -typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "filelock" -version = "3.20.3" -description = "A platform independent file lock." -optional = true -python-versions = ">=3.10" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1"}, - {file = "filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1"}, -] - -[[package]] -name = "flake8" -version = "7.3.0" -description = "the modular source code checker: pep8 pyflakes and co" -optional = true -python-versions = ">=3.9" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e"}, - {file = "flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872"}, -] - -[package.dependencies] -mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.14.0,<2.15.0" -pyflakes = ">=3.4.0,<3.5.0" - -[[package]] -name = "importlib-metadata" -version = "8.7.1" -description = "Read metadata from Python packages" -optional = true -python-versions = ">=3.9" -groups = ["main"] -markers = "extra == \"dev\" and python_full_version < \"3.10.2\"" -files = [ - {file = "importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151"}, - {file = "importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb"}, -] - -[package.dependencies] -zipp = ">=3.20" - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=3.4)"] -perf = ["ipython"] -test = ["flufl.flake8", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] -type = ["mypy (<1.19) ; platform_python_implementation == \"PyPy\"", "pytest-mypy (>=1.0.1)"] - -[[package]] -name = "iniconfig" -version = "2.3.0" -description = "brain-dead simple config-ini parsing" -optional = true -python-versions = ">=3.10" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, - {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, -] - -[[package]] -name = "isort" -version = "7.0.0" -description = "A Python utility / library to sort Python imports." -optional = true -python-versions = ">=3.10.0" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "isort-7.0.0-py3-none-any.whl", hash = "sha256:1bcabac8bc3c36c7fb7b98a76c8abb18e0f841a3ba81decac7691008592499c1"}, - {file = "isort-7.0.0.tar.gz", hash = "sha256:5513527951aadb3ac4292a41a16cbc50dd1642432f5e8c20057d414bdafb4187"}, -] - -[package.extras] -colors = ["colorama"] -plugins = ["setuptools"] - -[[package]] -name = "librt" -version = "0.7.8" -description = "Mypyc runtime library" -optional = true -python-versions = ">=3.9" -groups = ["main"] -markers = "extra == \"dev\" and platform_python_implementation != \"PyPy\"" -files = [ - {file = "librt-0.7.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b45306a1fc5f53c9330fbee134d8b3227fe5da2ab09813b892790400aa49352d"}, - {file = "librt-0.7.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:864c4b7083eeee250ed55135d2127b260d7eb4b5e953a9e5df09c852e327961b"}, - {file = "librt-0.7.8-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6938cc2de153bc927ed8d71c7d2f2ae01b4e96359126c602721340eb7ce1a92d"}, - {file = "librt-0.7.8-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:66daa6ac5de4288a5bbfbe55b4caa7bf0cd26b3269c7a476ffe8ce45f837f87d"}, - {file = "librt-0.7.8-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4864045f49dc9c974dadb942ac56a74cd0479a2aafa51ce272c490a82322ea3c"}, - {file = "librt-0.7.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a36515b1328dc5b3ffce79fe204985ca8572525452eacabee2166f44bb387b2c"}, - {file = "librt-0.7.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b7e7f140c5169798f90b80d6e607ed2ba5059784968a004107c88ad61fb3641d"}, - {file = "librt-0.7.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff71447cb778a4f772ddc4ce360e6ba9c95527ed84a52096bd1bbf9fee2ec7c0"}, - {file = "librt-0.7.8-cp310-cp310-win32.whl", hash = "sha256:047164e5f68b7a8ebdf9fae91a3c2161d3192418aadd61ddd3a86a56cbe3dc85"}, - {file = "librt-0.7.8-cp310-cp310-win_amd64.whl", hash = "sha256:d6f254d096d84156a46a84861183c183d30734e52383602443292644d895047c"}, - {file = "librt-0.7.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ff3e9c11aa260c31493d4b3197d1e28dd07768594a4f92bec4506849d736248f"}, - {file = "librt-0.7.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ddb52499d0b3ed4aa88746aaf6f36a08314677d5c346234c3987ddc506404eac"}, - {file = "librt-0.7.8-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e9c0afebbe6ce177ae8edba0c7c4d626f2a0fc12c33bb993d163817c41a7a05c"}, - {file = "librt-0.7.8-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:631599598e2c76ded400c0a8722dec09217c89ff64dc54b060f598ed68e7d2a8"}, - {file = "librt-0.7.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c1ba843ae20db09b9d5c80475376168feb2640ce91cd9906414f23cc267a1ff"}, - {file = "librt-0.7.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b5b007bb22ea4b255d3ee39dfd06d12534de2fcc3438567d9f48cdaf67ae1ae3"}, - {file = "librt-0.7.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:dbd79caaf77a3f590cbe32dc2447f718772d6eea59656a7dcb9311161b10fa75"}, - {file = "librt-0.7.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:87808a8d1e0bd62a01cafc41f0fd6818b5a5d0ca0d8a55326a81643cdda8f873"}, - {file = "librt-0.7.8-cp311-cp311-win32.whl", hash = "sha256:31724b93baa91512bd0a376e7cf0b59d8b631ee17923b1218a65456fa9bda2e7"}, - {file = "librt-0.7.8-cp311-cp311-win_amd64.whl", hash = "sha256:978e8b5f13e52cf23a9e80f3286d7546baa70bc4ef35b51d97a709d0b28e537c"}, - {file = "librt-0.7.8-cp311-cp311-win_arm64.whl", hash = "sha256:20e3946863d872f7cabf7f77c6c9d370b8b3d74333d3a32471c50d3a86c0a232"}, - {file = "librt-0.7.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9b6943885b2d49c48d0cff23b16be830ba46b0152d98f62de49e735c6e655a63"}, - {file = "librt-0.7.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46ef1f4b9b6cc364b11eea0ecc0897314447a66029ee1e55859acb3dd8757c93"}, - {file = "librt-0.7.8-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:907ad09cfab21e3c86e8f1f87858f7049d1097f77196959c033612f532b4e592"}, - {file = "librt-0.7.8-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2991b6c3775383752b3ca0204842743256f3ad3deeb1d0adc227d56b78a9a850"}, - {file = "librt-0.7.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03679b9856932b8c8f674e87aa3c55ea11c9274301f76ae8dc4d281bda55cf62"}, - {file = "librt-0.7.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3968762fec1b2ad34ce57458b6de25dbb4142713e9ca6279a0d352fa4e9f452b"}, - {file = "librt-0.7.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bb7a7807523a31f03061288cc4ffc065d684c39db7644c676b47d89553c0d714"}, - {file = "librt-0.7.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad64a14b1e56e702e19b24aae108f18ad1bf7777f3af5fcd39f87d0c5a814449"}, - {file = "librt-0.7.8-cp312-cp312-win32.whl", hash = "sha256:0241a6ed65e6666236ea78203a73d800dbed896cf12ae25d026d75dc1fcd1dac"}, - {file = "librt-0.7.8-cp312-cp312-win_amd64.whl", hash = "sha256:6db5faf064b5bab9675c32a873436b31e01d66ca6984c6f7f92621656033a708"}, - {file = "librt-0.7.8-cp312-cp312-win_arm64.whl", hash = "sha256:57175aa93f804d2c08d2edb7213e09276bd49097611aefc37e3fa38d1fb99ad0"}, - {file = "librt-0.7.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4c3995abbbb60b3c129490fa985dfe6cac11d88fc3c36eeb4fb1449efbbb04fc"}, - {file = "librt-0.7.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:44e0c2cbc9bebd074cf2cdbe472ca185e824be4e74b1c63a8e934cea674bebf2"}, - {file = "librt-0.7.8-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4d2f1e492cae964b3463a03dc77a7fe8742f7855d7258c7643f0ee32b6651dd3"}, - {file = "librt-0.7.8-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:451e7ffcef8f785831fdb791bd69211f47e95dc4c6ddff68e589058806f044c6"}, - {file = "librt-0.7.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3469e1af9f1380e093ae06bedcbdd11e407ac0b303a56bbe9afb1d6824d4982d"}, - {file = "librt-0.7.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f11b300027ce19a34f6d24ebb0a25fd0e24a9d53353225a5c1e6cadbf2916b2e"}, - {file = "librt-0.7.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4adc73614f0d3c97874f02f2c7fd2a27854e7e24ad532ea6b965459c5b757eca"}, - {file = "librt-0.7.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:60c299e555f87e4c01b2eca085dfccda1dde87f5a604bb45c2906b8305819a93"}, - {file = "librt-0.7.8-cp313-cp313-win32.whl", hash = "sha256:b09c52ed43a461994716082ee7d87618096851319bf695d57ec123f2ab708951"}, - {file = "librt-0.7.8-cp313-cp313-win_amd64.whl", hash = "sha256:f8f4a901a3fa28969d6e4519deceab56c55a09d691ea7b12ca830e2fa3461e34"}, - {file = "librt-0.7.8-cp313-cp313-win_arm64.whl", hash = "sha256:43d4e71b50763fcdcf64725ac680d8cfa1706c928b844794a7aa0fa9ac8e5f09"}, - {file = "librt-0.7.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:be927c3c94c74b05128089a955fba86501c3b544d1d300282cc1b4bd370cb418"}, - {file = "librt-0.7.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7b0803e9008c62a7ef79058233db7ff6f37a9933b8f2573c05b07ddafa226611"}, - {file = "librt-0.7.8-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:79feb4d00b2a4e0e05c9c56df707934f41fcb5fe53fd9efb7549068d0495b758"}, - {file = "librt-0.7.8-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9122094e3f24aa759c38f46bd8863433820654927370250f460ae75488b66ea"}, - {file = "librt-0.7.8-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e03bea66af33c95ce3addf87a9bf1fcad8d33e757bc479957ddbc0e4f7207ac"}, - {file = "librt-0.7.8-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f1ade7f31675db00b514b98f9ab9a7698c7282dad4be7492589109471852d398"}, - {file = "librt-0.7.8-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a14229ac62adcf1b90a15992f1ab9c69ae8b99ffb23cb64a90878a6e8a2f5b81"}, - {file = "librt-0.7.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5bcaaf624fd24e6a0cb14beac37677f90793a96864c67c064a91458611446e83"}, - {file = "librt-0.7.8-cp314-cp314-win32.whl", hash = "sha256:7aa7d5457b6c542ecaed79cec4ad98534373c9757383973e638ccced0f11f46d"}, - {file = "librt-0.7.8-cp314-cp314-win_amd64.whl", hash = "sha256:3d1322800771bee4a91f3b4bd4e49abc7d35e65166821086e5afd1e6c0d9be44"}, - {file = "librt-0.7.8-cp314-cp314-win_arm64.whl", hash = "sha256:5363427bc6a8c3b1719f8f3845ea53553d301382928a86e8fab7984426949bce"}, - {file = "librt-0.7.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ca916919793a77e4a98d4a1701e345d337ce53be4a16620f063191f7322ac80f"}, - {file = "librt-0.7.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:54feb7b4f2f6706bb82325e836a01be805770443e2400f706e824e91f6441dde"}, - {file = "librt-0.7.8-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:39a4c76fee41007070f872b648cc2f711f9abf9a13d0c7162478043377b52c8e"}, - {file = "librt-0.7.8-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac9c8a458245c7de80bc1b9765b177055efff5803f08e548dd4bb9ab9a8d789b"}, - {file = "librt-0.7.8-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95b67aa7eff150f075fda09d11f6bfb26edffd300f6ab1666759547581e8f666"}, - {file = "librt-0.7.8-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:535929b6eff670c593c34ff435d5440c3096f20fa72d63444608a5aef64dd581"}, - {file = "librt-0.7.8-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:63937bd0f4d1cb56653dc7ae900d6c52c41f0015e25aaf9902481ee79943b33a"}, - {file = "librt-0.7.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cf243da9e42d914036fd362ac3fa77d80a41cadcd11ad789b1b5eec4daaf67ca"}, - {file = "librt-0.7.8-cp314-cp314t-win32.whl", hash = "sha256:171ca3a0a06c643bd0a2f62a8944e1902c94aa8e5da4db1ea9a8daf872685365"}, - {file = "librt-0.7.8-cp314-cp314t-win_amd64.whl", hash = "sha256:445b7304145e24c60288a2f172b5ce2ca35c0f81605f5299f3fa567e189d2e32"}, - {file = "librt-0.7.8-cp314-cp314t-win_arm64.whl", hash = "sha256:8766ece9de08527deabcd7cb1b4f1a967a385d26e33e536d6d8913db6ef74f06"}, - {file = "librt-0.7.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c7e8f88f79308d86d8f39c491773cbb533d6cb7fa6476f35d711076ee04fceb6"}, - {file = "librt-0.7.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:389bd25a0db916e1d6bcb014f11aa9676cedaa485e9ec3752dfe19f196fd377b"}, - {file = "librt-0.7.8-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73fd300f501a052f2ba52ede721232212f3b06503fa12665408ecfc9d8fd149c"}, - {file = "librt-0.7.8-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d772edc6a5f7835635c7562f6688e031f0b97e31d538412a852c49c9a6c92d5"}, - {file = "librt-0.7.8-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde8a130bd0f239e45503ab39fab239ace094d63ee1d6b67c25a63d741c0f71"}, - {file = "librt-0.7.8-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fdec6e2368ae4f796fc72fad7fd4bd1753715187e6d870932b0904609e7c878e"}, - {file = "librt-0.7.8-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:00105e7d541a8f2ee5be52caacea98a005e0478cfe78c8080fbb7b5d2b340c63"}, - {file = "librt-0.7.8-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c6f8947d3dfd7f91066c5b4385812c18be26c9d5a99ca56667547f2c39149d94"}, - {file = "librt-0.7.8-cp39-cp39-win32.whl", hash = "sha256:41d7bb1e07916aeb12ae4a44e3025db3691c4149ab788d0315781b4d29b86afb"}, - {file = "librt-0.7.8-cp39-cp39-win_amd64.whl", hash = "sha256:e90a8e237753c83b8e484d478d9a996dc5e39fd5bd4c6ce32563bc8123f132be"}, - {file = "librt-0.7.8.tar.gz", hash = "sha256:1a4ede613941d9c3470b0368be851df6bb78ab218635512d0370b27a277a0862"}, -] - -[[package]] -name = "mccabe" -version = "0.7.0" -description = "McCabe checker, plugin for flake8" -optional = true -python-versions = ">=3.6" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, - {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, -] - -[[package]] -name = "mypy" -version = "1.19.1" -description = "Optional static typing for Python" -optional = true -python-versions = ">=3.9" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec"}, - {file = "mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b"}, - {file = "mypy-1.19.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6"}, - {file = "mypy-1.19.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74"}, - {file = "mypy-1.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1"}, - {file = "mypy-1.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac"}, - {file = "mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288"}, - {file = "mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab"}, - {file = "mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6"}, - {file = "mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331"}, - {file = "mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925"}, - {file = "mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042"}, - {file = "mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1"}, - {file = "mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e"}, - {file = "mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2"}, - {file = "mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8"}, - {file = "mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a"}, - {file = "mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13"}, - {file = "mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250"}, - {file = "mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b"}, - {file = "mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e"}, - {file = "mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef"}, - {file = "mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75"}, - {file = "mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd"}, - {file = "mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1"}, - {file = "mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718"}, - {file = "mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b"}, - {file = "mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045"}, - {file = "mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957"}, - {file = "mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f"}, - {file = "mypy-1.19.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7bcfc336a03a1aaa26dfce9fff3e287a3ba99872a157561cbfcebe67c13308e3"}, - {file = "mypy-1.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b7951a701c07ea584c4fe327834b92a30825514c868b1f69c30445093fdd9d5a"}, - {file = "mypy-1.19.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b13cfdd6c87fc3efb69ea4ec18ef79c74c3f98b4e5498ca9b85ab3b2c2329a67"}, - {file = "mypy-1.19.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f28f99c824ecebcdaa2e55d82953e38ff60ee5ec938476796636b86afa3956e"}, - {file = "mypy-1.19.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c608937067d2fc5a4dd1a5ce92fd9e1398691b8c5d012d66e1ddd430e9244376"}, - {file = "mypy-1.19.1-cp39-cp39-win_amd64.whl", hash = "sha256:409088884802d511ee52ca067707b90c883426bd95514e8cfda8281dc2effe24"}, - {file = "mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247"}, - {file = "mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba"}, -] - -[package.dependencies] -librt = {version = ">=0.6.2", markers = "platform_python_implementation != \"PyPy\""} -mypy_extensions = ">=1.0.0" -pathspec = ">=0.9.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing_extensions = ">=4.6.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -faster-cache = ["orjson"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.1.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = true -python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, - {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, -] - -[[package]] -name = "packaging" -version = "25.0" -description = "Core utilities for Python packages" -optional = true -python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, - {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, -] - -[[package]] -name = "pathspec" -version = "1.0.3" -description = "Utility library for gitignore style pattern matching of file paths." -optional = true -python-versions = ">=3.9" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "pathspec-1.0.3-py3-none-any.whl", hash = "sha256:e80767021c1cc524aa3fb14bedda9c34406591343cc42797b386ce7b9354fb6c"}, - {file = "pathspec-1.0.3.tar.gz", hash = "sha256:bac5cf97ae2c2876e2d25ebb15078eb04d76e4b98921ee31c6f85ade8b59444d"}, -] - -[package.extras] -hyperscan = ["hyperscan (>=0.7)"] -optional = ["typing-extensions (>=4)"] -re2 = ["google-re2 (>=1.1)"] -tests = ["pytest (>=9)", "typing-extensions (>=4.15)"] - -[[package]] -name = "pipx" -version = "1.8.0" -description = "Install and Run Python Applications in Isolated Environments" -optional = true -python-versions = ">=3.9" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "pipx-1.8.0-py3-none-any.whl", hash = "sha256:b9535d59108d31675e7e14a837273e7816be2b8f08a96b3cc48daf09c066e696"}, - {file = "pipx-1.8.0.tar.gz", hash = "sha256:61a653ef2046de67c3201306b9d07428e93c80e6bebdcbbcb8177ecf3328b403"}, -] - -[package.dependencies] -argcomplete = ">=1.9.4" -colorama = {version = ">=0.4.4", markers = "sys_platform == \"win32\""} -packaging = ">=20" -platformdirs = ">=2.1" -tomli = {version = "*", markers = "python_version < \"3.11\""} -userpath = ">=1.6,<1.9 || >1.9" - -[[package]] -name = "platformdirs" -version = "4.5.1" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -optional = true -python-versions = ">=3.10" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31"}, - {file = "platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda"}, -] - -[package.extras] -docs = ["furo (>=2025.9.25)", "proselint (>=0.14)", "sphinx (>=8.2.3)", "sphinx-autodoc-typehints (>=3.2)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.4.2)", "pytest-cov (>=7)", "pytest-mock (>=3.15.1)"] -type = ["mypy (>=1.18.2)"] - -[[package]] -name = "pluggy" -version = "1.6.0" -description = "plugin and hook calling mechanisms for python" -optional = true -python-versions = ">=3.9" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, - {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["coverage", "pytest", "pytest-benchmark"] - -[[package]] -name = "pycodestyle" -version = "2.14.0" -description = "Python style guide checker" -optional = true -python-versions = ">=3.9" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d"}, - {file = "pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783"}, -] - -[[package]] -name = "pyflakes" -version = "3.4.0" -description = "passive checker of Python programs" -optional = true -python-versions = ">=3.9" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f"}, - {file = "pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58"}, -] - -[[package]] -name = "pygments" -version = "2.19.2" -description = "Pygments is a syntax highlighting package written in Python." -optional = true -python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, - {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pyproject-api" -version = "1.10.0" -description = "API to interact with the python pyproject.toml based projects" -optional = true -python-versions = ">=3.10" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "pyproject_api-1.10.0-py3-none-any.whl", hash = "sha256:8757c41a79c0f4ab71b99abed52b97ecf66bd20b04fa59da43b5840bac105a09"}, - {file = "pyproject_api-1.10.0.tar.gz", hash = "sha256:40c6f2d82eebdc4afee61c773ed208c04c19db4c4a60d97f8d7be3ebc0bbb330"}, -] - -[package.dependencies] -packaging = ">=25" -tomli = {version = ">=2.3", markers = "python_version < \"3.11\""} - -[package.extras] -docs = ["furo (>=2025.9.25)", "sphinx-autodoc-typehints (>=3.5.1)"] -testing = ["covdefaults (>=2.3)", "pytest (>=8.4.2)", "pytest-cov (>=7)", "pytest-mock (>=3.15.1)", "setuptools (>=80.9)"] - -[[package]] -name = "pyproject-hooks" -version = "1.2.0" -description = "Wrappers to call pyproject.toml-based build backend hooks." -optional = true -python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913"}, - {file = "pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8"}, -] - -[[package]] -name = "pytest" -version = "9.0.2" -description = "pytest: simple powerful testing with Python" -optional = true -python-versions = ">=3.10" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b"}, - {file = "pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11"}, -] - -[package.dependencies] -colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} -iniconfig = ">=1.0.1" -packaging = ">=22" -pluggy = ">=1.5,<2" -pygments = ">=2.7.2" -tomli = {version = ">=1", markers = "python_version < \"3.11\""} - -[package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytest-cov" -version = "7.0.0" -description = "Pytest plugin for measuring coverage." -optional = true -python-versions = ">=3.9" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861"}, - {file = "pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1"}, -] - -[package.dependencies] -coverage = {version = ">=7.10.6", extras = ["toml"]} -pluggy = ">=1.2" -pytest = ">=7" - -[package.extras] -testing = ["process-tests", "pytest-xdist", "virtualenv"] - -[[package]] -name = "pytest-randomly" -version = "4.0.1" -description = "Pytest plugin to randomly order tests and control random.seed." -optional = true -python-versions = ">=3.9" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "pytest_randomly-4.0.1-py3-none-any.whl", hash = "sha256:e0dfad2fd4f35e07beff1e47c17fbafcf98f9bf4531fd369d9260e2f858bfcb7"}, - {file = "pytest_randomly-4.0.1.tar.gz", hash = "sha256:174e57bb12ac2c26f3578188490bd333f0e80620c3f47340158a86eca0593cd8"}, -] - -[package.dependencies] -pytest = "*" - -[[package]] -name = "pytokens" -version = "0.4.0" -description = "A Fast, spec compliant Python 3.14+ tokenizer that runs on older Pythons." -optional = true -python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "pytokens-0.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:af0c3166aea367a9e755a283171befb92dd3043858b94ae9b3b7efbe9def26a3"}, - {file = "pytokens-0.4.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daae524ed14ca459932cbf51d74325bea643701ba8a8b0cc2d10f7cd4b3e2b63"}, - {file = "pytokens-0.4.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e95cb158c44d642ed62f555bf8136bbe780dbd64d2fb0b9169e11ffb944664c3"}, - {file = "pytokens-0.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:df58d44630eaf25f587540e94bdf1fc50b4e6d5f212c786de0fb024bfcb8753a"}, - {file = "pytokens-0.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55efcc36f9a2e0e930cfba0ce7f83445306b02f8326745585ed5551864eba73a"}, - {file = "pytokens-0.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:92eb3ef88f27c22dc9dbab966ace4d61f6826e02ba04dac8e2d65ea31df56c8e"}, - {file = "pytokens-0.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f4b77858a680635ee9904306f54b0ee4781effb89e211ba0a773d76539537165"}, - {file = "pytokens-0.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25cacc20c2ad90acb56f3739d87905473c54ca1fa5967ffcd675463fe965865e"}, - {file = "pytokens-0.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:628fab535ebc9079e4db35cd63cb401901c7ce8720a9834f9ad44b9eb4e0f1d4"}, - {file = "pytokens-0.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:4d0f568d7e82b7e96be56d03b5081de40e43c904eb6492bf09aaca47cd55f35b"}, - {file = "pytokens-0.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cd8da894e5a29ba6b6da8be06a4f7589d7220c099b5e363cb0643234b9b38c2a"}, - {file = "pytokens-0.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:237ba7cfb677dbd3b01b09860810aceb448871150566b93cd24501d5734a04b1"}, - {file = "pytokens-0.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01d1a61e36812e4e971cfe2c0e4c1f2d66d8311031dac8bf168af8a249fa04dd"}, - {file = "pytokens-0.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e47e2ef3ec6ee86909e520d79f965f9b23389fda47460303cf715d510a6fe544"}, - {file = "pytokens-0.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:3d36954aba4557fd5a418a03cf595ecbb1cdcce119f91a49b19ef09d691a22ae"}, - {file = "pytokens-0.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73eff3bdd8ad08da679867992782568db0529b887bed4c85694f84cdf35eafc6"}, - {file = "pytokens-0.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d97cc1f91b1a8e8ebccf31c367f28225699bea26592df27141deade771ed0afb"}, - {file = "pytokens-0.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c8952c537cb73a1a74369501a83b7f9d208c3cf92c41dd88a17814e68d48ce"}, - {file = "pytokens-0.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5dbf56f3c748aed9310b310d5b8b14e2c96d3ad682ad5a943f381bdbbdddf753"}, - {file = "pytokens-0.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:e131804513597f2dff2b18f9911d9b6276e21ef3699abeffc1c087c65a3d975e"}, - {file = "pytokens-0.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0d7374c917197106d3c4761374718bc55ea2e9ac0fb94171588ef5840ee1f016"}, - {file = "pytokens-0.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cd3fa1caf9e47a72ee134a29ca6b5bea84712724bba165d6628baa190c6ea5b"}, - {file = "pytokens-0.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c6986576b7b07fe9791854caa5347923005a80b079d45b63b0be70d50cce5f1"}, - {file = "pytokens-0.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9940f7c2e2f54fb1cb5fe17d0803c54da7a2bf62222704eb4217433664a186a7"}, - {file = "pytokens-0.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:54691cf8f299e7efabcc25adb4ce715d3cef1491e1c930eaf555182f898ef66a"}, - {file = "pytokens-0.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:94ff5db97a0d3cd7248a5b07ba2167bd3edc1db92f76c6db00137bbaf068ddf8"}, - {file = "pytokens-0.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d0dd6261cd9cc95fae1227b1b6ebee023a5fd4a4b6330b071c73a516f5f59b63"}, - {file = "pytokens-0.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0cdca8159df407dbd669145af4171a0d967006e0be25f3b520896bc7068f02c4"}, - {file = "pytokens-0.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4b5770abeb2a24347380a1164a558f0ebe06e98aedbd54c45f7929527a5fb26e"}, - {file = "pytokens-0.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:74500d72c561dad14c037a9e86a657afd63e277dd5a3bb7570932ab7a3b12551"}, - {file = "pytokens-0.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e368e0749e4e9d86a6e08763310dc92bc69ad73d9b6db5243b30174c71a8a534"}, - {file = "pytokens-0.4.0-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:865cc65c75c8f2e9e0d8330338f649b12bfd9442561900ebaf58c596a72107d2"}, - {file = "pytokens-0.4.0-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dbb9338663b3538f31c4ca7afe4f38d9b9b3a16a8be18a273a5704a1bc7a2367"}, - {file = "pytokens-0.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:658f870523ac1a5f4733d7db61ce9af61a0c23b2aeea3d03d1800c93f760e15f"}, - {file = "pytokens-0.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:d69a2491190a74e4b6f87f3b9dfce7a6873de3f3bf330d20083d374380becac0"}, - {file = "pytokens-0.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8cd795191c4127fcb3d7b76d84006a07748c390226f47657869235092eedbc05"}, - {file = "pytokens-0.4.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef2bcbddb73ac18599a86c8c549d5145130f2cd9d83dc2b5482fd8322b7806cd"}, - {file = "pytokens-0.4.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:06ac081c1187389762b58823d90d6339e6880ce0df912f71fb9022d81d7fd429"}, - {file = "pytokens-0.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:278129d54573efdc79e75c6082e73ebd19858e22a2e848359f93629323186ca6"}, - {file = "pytokens-0.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:9380fb6d96fa5ab83ed606ebad27b6171930cc14a8a8d215f6adb187ba428690"}, - {file = "pytokens-0.4.0-py3-none-any.whl", hash = "sha256:0508d11b4de157ee12063901603be87fb0253e8f4cb9305eb168b1202ab92068"}, - {file = "pytokens-0.4.0.tar.gz", hash = "sha256:6b0b03e6ea7c9f9d47c5c61164b69ad30f4f0d70a5d9fe7eac4d19f24f77af2d"}, -] - -[package.extras] -dev = ["black", "build", "mypy", "pytest", "pytest-cov", "setuptools", "tox", "twine", "wheel"] - -[[package]] -name = "pyupgrade" -version = "3.21.2" -description = "A tool to automatically upgrade syntax for newer versions." -optional = true -python-versions = ">=3.10" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "pyupgrade-3.21.2-py2.py3-none-any.whl", hash = "sha256:2ac7b95cbd176475041e4dfe8ef81298bd4654a244f957167bd68af37d52be9f"}, - {file = "pyupgrade-3.21.2.tar.gz", hash = "sha256:1a361bea39deda78d1460f65d9dd548d3a36ff8171d2482298539b9dc11c9c06"}, -] - -[package.dependencies] -tokenize-rt = ">=6.1.0" - -[[package]] -name = "tokenize-rt" -version = "6.2.0" -description = "A wrapper around the stdlib `tokenize` which roundtrips." -optional = true -python-versions = ">=3.9" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "tokenize_rt-6.2.0-py2.py3-none-any.whl", hash = "sha256:a152bf4f249c847a66497a4a95f63376ed68ac6abf092a2f7cfb29d044ecff44"}, - {file = "tokenize_rt-6.2.0.tar.gz", hash = "sha256:8439c042b330c553fdbe1758e4a05c0ed460dbbbb24a606f11f0dee75da4cad6"}, -] - -[[package]] -name = "tomli" -version = "2.4.0" -description = "A lil' TOML parser" -optional = true -python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"dev\" and python_full_version <= \"3.11.0a6\"" -files = [ - {file = "tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867"}, - {file = "tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9"}, - {file = "tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95"}, - {file = "tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76"}, - {file = "tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d"}, - {file = "tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576"}, - {file = "tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a"}, - {file = "tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa"}, - {file = "tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614"}, - {file = "tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1"}, - {file = "tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8"}, - {file = "tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a"}, - {file = "tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1"}, - {file = "tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b"}, - {file = "tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51"}, - {file = "tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729"}, - {file = "tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da"}, - {file = "tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3"}, - {file = "tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0"}, - {file = "tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e"}, - {file = "tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4"}, - {file = "tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e"}, - {file = "tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c"}, - {file = "tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f"}, - {file = "tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86"}, - {file = "tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87"}, - {file = "tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132"}, - {file = "tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6"}, - {file = "tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc"}, - {file = "tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66"}, - {file = "tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d"}, - {file = "tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702"}, - {file = "tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8"}, - {file = "tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776"}, - {file = "tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475"}, - {file = "tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2"}, - {file = "tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9"}, - {file = "tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0"}, - {file = "tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df"}, - {file = "tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d"}, - {file = "tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f"}, - {file = "tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b"}, - {file = "tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087"}, - {file = "tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd"}, - {file = "tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4"}, - {file = "tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a"}, - {file = "tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c"}, -] - -[[package]] -name = "tox" -version = "4.34.1" -description = "tox is a generic virtualenv management and test command line tool" -optional = true -python-versions = ">=3.10" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "tox-4.34.1-py3-none-any.whl", hash = "sha256:5610d69708bab578d618959b023f8d7d5d3386ed14a2392aeebf9c583615af60"}, - {file = "tox-4.34.1.tar.gz", hash = "sha256:ef1e82974c2f5ea02954d590ee0b967fad500c3879b264ea19efb9a554f3cc60"}, -] - -[package.dependencies] -cachetools = ">=6.2.4" -chardet = ">=5.2" -colorama = ">=0.4.6" -filelock = ">=3.20.2" -packaging = ">=25" -platformdirs = ">=4.5.1" -pluggy = ">=1.6" -pyproject-api = ">=1.10" -tomli = {version = ">=2.3", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.15", markers = "python_version < \"3.11\""} -virtualenv = ">=20.35.4" - -[[package]] -name = "typing-extensions" -version = "4.15.0" -description = "Backported and Experimental Type Hints for Python 3.9+" -optional = true -python-versions = ">=3.9" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, - {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, -] - -[[package]] -name = "userpath" -version = "1.9.2" -description = "Cross-platform tool for adding locations to the user PATH" -optional = true -python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "userpath-1.9.2-py3-none-any.whl", hash = "sha256:2cbf01a23d655a1ff8fc166dfb78da1b641d1ceabf0fe5f970767d380b14e89d"}, - {file = "userpath-1.9.2.tar.gz", hash = "sha256:6c52288dab069257cc831846d15d48133522455d4677ee69a9781f11dbefd815"}, -] - -[package.dependencies] -click = "*" - -[[package]] -name = "virtualenv" -version = "20.36.1" -description = "Virtual Python Environment builder" -optional = true -python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"dev\"" -files = [ - {file = "virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f"}, - {file = "virtualenv-20.36.1.tar.gz", hash = "sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba"}, -] - -[package.dependencies] -distlib = ">=0.3.7,<1" -filelock = {version = ">=3.20.1,<4", markers = "python_version >= \"3.10\""} -platformdirs = ">=3.9.1,<5" -typing-extensions = {version = ">=4.13.2", markers = "python_version < \"3.11\""} - -[package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] - -[[package]] -name = "zipp" -version = "3.23.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = true -python-versions = ">=3.9" -groups = ["main"] -markers = "extra == \"dev\" and python_full_version < \"3.10.2\"" -files = [ - {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, - {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, -] - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] -type = ["pytest-mypy"] - -[extras] -dev = ["black", "build", "flake8", "isort", "mypy", "pipx", "pytest", "pytest-cov", "pytest-randomly", "pyupgrade", "tox"] - -[metadata] -lock-version = "2.1" -python-versions = ">=3.10" -content-hash = "069cb13b2d57cd6c10ec2639902adf8b15dd8c594a1aaeab1a64b52df188a8af" diff --git a/pyproject.toml b/pyproject.toml index dfac5da9c..1e4740463 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ] dependencies= [ ] @@ -29,8 +30,11 @@ Contributors = "https://github.com/faif/python-patterns/graphs/contributors" [project.optional-dependencies] dev = [ + "codecov", + "codespell", "mypy", "pipx>=1.7.1", + "pipenv", "pyupgrade", "pytest>=6.2.0", "pytest-cov>=2.11.0", @@ -65,7 +69,6 @@ source = ["./"] # Ensure coverage data is collected properly relative_files = true parallel = true -dynamic_context = "test_function" data_file = ".coverage" [tool.coverage.report] @@ -93,28 +96,17 @@ max-line-length = 120 ignore = ["E266", "E731", "W503"] exclude = ["venv*"] +[tool.black] +target-version = ["py310"] + [tool.tox] legacy_tox_ini = """ [tox] -envlist = py312,cov-report -skip_missing_interpreters = true -usedevelop = true - -#[testenv] -#setenv = -# COVERAGE_FILE = .coverage.{envname} -#deps = -# -r requirements-dev.txt -#commands = -# flake8 --exclude="venv/,.tox/" patterns/ -# coverage run -m pytest --randomly-seed=1234 --doctest-modules patterns/ -# coverage run -m pytest -s -vv --cov=patterns/ --log-level=INFO tests/ +envlist = py -#[testenv:cov-report] -#setenv = -# COVERAGE_FILE = .coverage -#deps = coverage -#commands = -# coverage combine -# coverage report -#""" \ No newline at end of file +[testenv] +package = editable +deps = pipenv +commands_pre = pipenv sync --dev --system +commands = pytest {posargs:tests patterns} +""" diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index 1194272ad..000000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,8 +0,0 @@ -flake8 -black -isort -pytest -pytest-randomly -mypy -pyupgrade -tox diff --git a/tests/behavioral/test_catalog.py b/tests/behavioral/test_catalog.py index 609338162..9f86db330 100644 --- a/tests/behavioral/test_catalog.py +++ b/tests/behavioral/test_catalog.py @@ -1,23 +1,30 @@ -import pytest +from patterns.behavioral.catalog import ( + Catalog, + CatalogClass, + CatalogInstance, + CatalogStatic, +) -from patterns.behavioral.catalog import Catalog, CatalogClass, CatalogInstance, CatalogStatic def test_catalog_multiple_methods(): - test = Catalog('param_value_2') + test = Catalog("param_value_2") token = test.main_method() - assert token == 'executed method 2!' + assert token == "executed method 2!" + def test_catalog_multiple_instance_methods(): - test = CatalogInstance('param_value_1') + test = CatalogInstance("param_value_1") token = test.main_method() - assert token == 'Value x1' - + assert token == "Value x1" + + def test_catalog_multiple_class_methods(): - test = CatalogClass('param_value_2') + test = CatalogClass("param_value_2") token = test.main_method() - assert token == 'Value x2' + assert token == "Value x2" + def test_catalog_multiple_static_methods(): - test = CatalogStatic('param_value_1') + test = CatalogStatic("param_value_1") token = test.main_method() - assert token == 'executed method 1!' + assert token == "executed method 1!" diff --git a/tests/behavioral/test_mediator.py b/tests/behavioral/test_mediator.py index 1af60e67d..28e862442 100644 --- a/tests/behavioral/test_mediator.py +++ b/tests/behavioral/test_mediator.py @@ -1,16 +1,15 @@ -import pytest - from patterns.behavioral.mediator import User + def test_mediated_comments(): - molly = User('Molly') + molly = User("Molly") mediated_comment = molly.say("Hi Team! Meeting at 3 PM today.") assert mediated_comment == "[Molly says]: Hi Team! Meeting at 3 PM today." - mark = User('Mark') + mark = User("Mark") mediated_comment = mark.say("Roger that!") assert mediated_comment == "[Mark says]: Roger that!" - ethan = User('Ethan') + ethan = User("Ethan") mediated_comment = ethan.say("Alright.") assert mediated_comment == "[Ethan says]: Alright." diff --git a/tests/behavioral/test_memento.py b/tests/behavioral/test_memento.py index bd307b763..1ae11703b 100644 --- a/tests/behavioral/test_memento.py +++ b/tests/behavioral/test_memento.py @@ -2,9 +2,11 @@ from patterns.behavioral.memento import NumObj, Transaction + def test_object_creation(): num_obj = NumObj(-1) - assert repr(num_obj) == '', "Object representation not as expected" + assert repr(num_obj) == "", "Object representation not as expected" + def test_rollback_on_transaction(): num_obj = NumObj(-1) @@ -17,11 +19,12 @@ def test_rollback_on_transaction(): for _i in range(3): num_obj.increment() try: - num_obj.value += 'x' # will fail + num_obj.value += "x" # will fail except TypeError: a_transaction.rollback() assert num_obj.value == 2, "Transaction did not rollback as expected" + def test_rollback_with_transactional_annotation(): num_obj = NumObj(2) with pytest.raises(TypeError): diff --git a/tests/behavioral/test_publish_subscribe.py b/tests/behavioral/test_publish_subscribe.py index 8bb7130c7..6f142855d 100644 --- a/tests/behavioral/test_publish_subscribe.py +++ b/tests/behavioral/test_publish_subscribe.py @@ -1,10 +1,9 @@ -import unittest from unittest.mock import call, patch from patterns.behavioral.publish_subscribe import Provider, Publisher, Subscriber -class TestProvider(unittest.TestCase): +class TestProvider: """ Integration tests ~ provider class with as little mocking as possible. """ @@ -12,19 +11,19 @@ class TestProvider(unittest.TestCase): def test_subscriber_shall_be_attachable_to_subscriptions(cls): subscription = "sub msg" pro = Provider() - cls.assertEqual(len(pro.subscribers), 0) + assert len(pro.subscribers) == 0 sub = Subscriber("sub name", pro) sub.subscribe(subscription) - cls.assertEqual(len(pro.subscribers[subscription]), 1) + assert len(pro.subscribers[subscription]) == 1 def test_subscriber_shall_be_detachable_from_subscriptions(cls): subscription = "sub msg" pro = Provider() sub = Subscriber("sub name", pro) sub.subscribe(subscription) - cls.assertEqual(len(pro.subscribers[subscription]), 1) + assert len(pro.subscribers[subscription]) == 1 sub.unsubscribe(subscription) - cls.assertEqual(len(pro.subscribers[subscription]), 0) + assert len(pro.subscribers[subscription]) == 0 def test_publisher_shall_append_subscription_message_to_queue(cls): """msg_queue ~ Provider.notify(msg) ~ Publisher.publish(msg)""" @@ -32,10 +31,10 @@ def test_publisher_shall_append_subscription_message_to_queue(cls): pro = Provider() pub = Publisher(pro) Subscriber("sub name", pro) - cls.assertEqual(len(pro.msg_queue), 0) + assert len(pro.msg_queue) == 0 pub.publish(expected_msg) - cls.assertEqual(len(pro.msg_queue), 1) - cls.assertEqual(pro.msg_queue[0], expected_msg) + assert len(pro.msg_queue) == 1 + assert pro.msg_queue[0] == expected_msg def test_provider_shall_update_affected_subscribers_with_published_subscription( cls, @@ -53,8 +52,8 @@ def test_provider_shall_update_affected_subscribers_with_published_subscription( patch.object(sub2, "run") as mock_subscriber2_run, ): pro.update() - cls.assertEqual(mock_subscriber1_run.call_count, 0) - cls.assertEqual(mock_subscriber2_run.call_count, 0) + assert mock_subscriber1_run.call_count == 0 + assert mock_subscriber2_run.call_count == 0 pub.publish("sub 1 msg 1") pub.publish("sub 1 msg 2") pub.publish("sub 2 msg 1") diff --git a/tests/behavioral/test_servant.py b/tests/behavioral/test_servant.py index dd487171e..b3ab45d91 100644 --- a/tests/behavioral/test_servant.py +++ b/tests/behavioral/test_servant.py @@ -1,7 +1,9 @@ -from patterns.behavioral.servant import GeometryTools, Circle, Rectangle, Position -import pytest import math +import pytest + +from patterns.behavioral.servant import Circle, GeometryTools, Position, Rectangle + @pytest.fixture def circle(): diff --git a/tests/behavioral/test_visitor.py b/tests/behavioral/test_visitor.py index 31d230dec..6826679b4 100644 --- a/tests/behavioral/test_visitor.py +++ b/tests/behavioral/test_visitor.py @@ -2,21 +2,25 @@ from patterns.behavioral.visitor import A, B, C, Visitor + @pytest.fixture def visitor(): return Visitor() + def test_visiting_generic_node(visitor): a = A() token = visitor.visit(a) - assert token == 'generic_visit A', "The expected generic object was not called" + assert token == "generic_visit A", "The expected generic object was not called" + def test_visiting_specific_nodes(visitor): b = B() token = visitor.visit(b) - assert token == 'visit_B B', "The expected specific object was not called" + assert token == "visit_B B", "The expected specific object was not called" + def test_visiting_inherited_nodes(visitor): c = C() token = visitor.visit(c) - assert token == 'visit_B C', "The expected inherited object was not called" + assert token == "visit_B C", "The expected inherited object was not called" diff --git a/tests/creational/test_abstract_factory.py b/tests/creational/test_abstract_factory.py index 1676e59d1..e717fafcd 100644 --- a/tests/creational/test_abstract_factory.py +++ b/tests/creational/test_abstract_factory.py @@ -1,13 +1,12 @@ -import unittest from unittest.mock import patch from patterns.creational.abstract_factory import Dog, PetShop -class TestPetShop(unittest.TestCase): +class TestPetShop: def test_dog_pet_shop_shall_show_dog_instance(self): dog_pet_shop = PetShop(Dog) with patch.object(Dog, "speak") as mock_Dog_speak: pet = dog_pet_shop.buy_pet("") pet.speak() - self.assertEqual(mock_Dog_speak.call_count, 1) + assert mock_Dog_speak.call_count == 1 diff --git a/tests/creational/test_borg.py b/tests/creational/test_borg.py index 182611c37..61785020a 100644 --- a/tests/creational/test_borg.py +++ b/tests/creational/test_borg.py @@ -1,28 +1,26 @@ -import unittest - from patterns.creational.borg import Borg, YourBorg -class BorgTest(unittest.TestCase): - def setUp(self): +class TestBorg: + def setup_method(self): self.b1 = Borg() self.b2 = Borg() # creating YourBorg instance implicitly sets the state attribute # for all borg instances. self.ib1 = YourBorg() - def tearDown(self): + def teardown_method(self): self.ib1.state = "Init" def test_initial_borg_state_shall_be_init(self): b = Borg() - self.assertEqual(b.state, "Init") + assert b.state == "Init" def test_changing_instance_attribute_shall_change_borg_state(self): self.b1.state = "Running" - self.assertEqual(self.b1.state, "Running") - self.assertEqual(self.b2.state, "Running") - self.assertEqual(self.ib1.state, "Running") + assert self.b1.state == "Running" + assert self.b2.state == "Running" + assert self.ib1.state == "Running" def test_instances_shall_have_own_ids(self): - self.assertNotEqual(id(self.b1), id(self.b2), id(self.ib1)) + assert id(self.b1) != id(self.b2), id(self.ib1) diff --git a/tests/creational/test_builder.py b/tests/creational/test_builder.py index 923bc4a5c..539d7994b 100644 --- a/tests/creational/test_builder.py +++ b/tests/creational/test_builder.py @@ -1,22 +1,20 @@ -import unittest - from patterns.creational.builder import ComplexHouse, Flat, House, construct_building -class TestSimple(unittest.TestCase): +class TestSimple: def test_house(self): house = House() - self.assertEqual(house.size, "Big") - self.assertEqual(house.floor, "One") + assert house.size == "Big" + assert house.floor == "One" def test_flat(self): flat = Flat() - self.assertEqual(flat.size, "Small") - self.assertEqual(flat.floor, "More than One") + assert flat.size == "Small" + assert flat.floor == "More than One" -class TestComplex(unittest.TestCase): +class TestComplex: def test_house(self): house = construct_building(ComplexHouse) - self.assertEqual(house.size, "Big and fancy") - self.assertEqual(house.floor, "One") + assert house.size == "Big and fancy" + assert house.floor == "One" diff --git a/tests/creational/test_factory.py b/tests/creational/test_factory.py index 4bcfd4c58..7d18211d8 100644 --- a/tests/creational/test_factory.py +++ b/tests/creational/test_factory.py @@ -1,30 +1,30 @@ -import unittest -from patterns.creational.factory import get_localizer, GreekLocalizer, EnglishLocalizer +from patterns.creational.factory import EnglishLocalizer, GreekLocalizer, get_localizer -class TestFactory(unittest.TestCase): + +class TestFactory: def test_get_localizer_greek(self): localizer = get_localizer("Greek") - self.assertIsInstance(localizer, GreekLocalizer) - self.assertEqual(localizer.localize("dog"), "σκύλος") - self.assertEqual(localizer.localize("cat"), "γάτα") + assert isinstance(localizer, GreekLocalizer) + assert localizer.localize("dog") == "σκύλος" + assert localizer.localize("cat") == "γάτα" # Test unknown word returns the word itself - self.assertEqual(localizer.localize("monkey"), "monkey") + assert localizer.localize("monkey") == "monkey" def test_get_localizer_english(self): localizer = get_localizer("English") - self.assertIsInstance(localizer, EnglishLocalizer) - self.assertEqual(localizer.localize("dog"), "dog") - self.assertEqual(localizer.localize("cat"), "cat") + assert isinstance(localizer, EnglishLocalizer) + assert localizer.localize("dog") == "dog" + assert localizer.localize("cat") == "cat" def test_get_localizer_default(self): # Test default argument localizer = get_localizer() - self.assertIsInstance(localizer, EnglishLocalizer) + assert isinstance(localizer, EnglishLocalizer) def test_get_localizer_unknown_language(self): - # Test fallback for unknown language if applicable, - # or just verify what happens. + # Test fallback for unknown language if applicable, + # or just verify what happens. # Based on implementation: localizers.get(language, EnglishLocalizer)() # It defaults to EnglishLocalizer for unknown keys. localizer = get_localizer("Spanish") - self.assertIsInstance(localizer, EnglishLocalizer) + assert isinstance(localizer, EnglishLocalizer) diff --git a/tests/creational/test_lazy.py b/tests/creational/test_lazy.py index 1b815b609..d1d02a1ba 100644 --- a/tests/creational/test_lazy.py +++ b/tests/creational/test_lazy.py @@ -1,38 +1,34 @@ -import unittest - from patterns.creational.lazy_evaluation import Person -class TestDynamicExpanding(unittest.TestCase): - def setUp(self): +class TestDynamicExpanding: + def setup_method(self): self.John = Person("John", "Coder") def test_innate_properties(self): - self.assertDictEqual( - {"name": "John", "occupation": "Coder", "call_count2": 0}, - self.John.__dict__, - ) + assert { + "name": "John", + "occupation": "Coder", + "call_count2": 0, + } == self.John.__dict__ def test_relatives_not_in_properties(self): - self.assertNotIn("relatives", self.John.__dict__) + assert "relatives" not in self.John.__dict__ def test_extended_properties(self): print(f"John's relatives: {self.John.relatives}") - self.assertDictEqual( - { - "name": "John", - "occupation": "Coder", - "relatives": "Many relatives.", - "call_count2": 0, - }, - self.John.__dict__, - ) + assert { + "name": "John", + "occupation": "Coder", + "relatives": "Many relatives.", + "call_count2": 0, + } == self.John.__dict__ def test_relatives_after_access(self): print(f"John's relatives: {self.John.relatives}") - self.assertIn("relatives", self.John.__dict__) + assert "relatives" in self.John.__dict__ def test_parents(self): for _ in range(2): - self.assertEqual(self.John.parents, "Father and mother") - self.assertEqual(self.John.call_count2, 1) + assert self.John.parents == "Father and mother" + assert self.John.call_count2 == 1 diff --git a/tests/creational/test_pool.py b/tests/creational/test_pool.py index cd501db38..97b9d269e 100644 --- a/tests/creational/test_pool.py +++ b/tests/creational/test_pool.py @@ -1,34 +1,33 @@ import queue -import unittest from patterns.creational.pool import ObjectPool -class TestPool(unittest.TestCase): - def setUp(self): +class TestPool: + def setup_method(self): self.sample_queue = queue.Queue() self.sample_queue.put("first") self.sample_queue.put("second") def test_items_recoil(self): with ObjectPool(self.sample_queue, True) as pool: - self.assertEqual(pool, "first") - self.assertTrue(self.sample_queue.get() == "second") - self.assertFalse(self.sample_queue.empty()) - self.assertTrue(self.sample_queue.get() == "first") - self.assertTrue(self.sample_queue.empty()) + assert pool == "first" + assert self.sample_queue.get() == "second" + assert not self.sample_queue.empty() + assert self.sample_queue.get() == "first" + assert self.sample_queue.empty() def test_frozen_pool(self): with ObjectPool(self.sample_queue) as pool: - self.assertEqual(pool, "first") - self.assertEqual(pool, "first") - self.assertTrue(self.sample_queue.get() == "second") - self.assertFalse(self.sample_queue.empty()) - self.assertTrue(self.sample_queue.get() == "first") - self.assertTrue(self.sample_queue.empty()) + assert pool == "first" + assert pool == "first" + assert self.sample_queue.get() == "second" + assert not self.sample_queue.empty() + assert self.sample_queue.get() == "first" + assert self.sample_queue.empty() -class TestNaitivePool(unittest.TestCase): +class TestNaitivePool: """def test_object(queue): queue_object = QueueObject(queue, True) print('Inside func: {}'.format(queue_object.object))""" @@ -38,10 +37,10 @@ def test_pool_behavior_with_single_object_inside(self): sample_queue.put("yam") with ObjectPool(sample_queue) as obj: # print('Inside with: {}'.format(obj)) - self.assertEqual(obj, "yam") - self.assertFalse(sample_queue.empty()) - self.assertTrue(sample_queue.get() == "yam") - self.assertTrue(sample_queue.empty()) + assert obj == "yam" + assert not sample_queue.empty() + assert sample_queue.get() == "yam" + assert sample_queue.empty() # sample_queue.put('sam') # test_object(sample_queue) diff --git a/tests/creational/test_prototype.py b/tests/creational/test_prototype.py index 758ac8722..ca5d77724 100644 --- a/tests/creational/test_prototype.py +++ b/tests/creational/test_prototype.py @@ -1,31 +1,31 @@ -import unittest +import pytest from patterns.creational.prototype import Prototype, PrototypeDispatcher -class TestPrototypeFeatures(unittest.TestCase): - def setUp(self): +class TestPrototypeFeatures: + def setup_method(self): self.prototype = Prototype() def test_cloning_propperty_innate_values(self): sample_object_1 = self.prototype.clone() sample_object_2 = self.prototype.clone() - self.assertEqual(sample_object_1.value, sample_object_2.value) + assert sample_object_1.value == sample_object_2.value def test_extended_property_values_cloning(self): sample_object_1 = self.prototype.clone() sample_object_1.some_value = "test string" sample_object_2 = self.prototype.clone() - self.assertRaises(AttributeError, lambda: sample_object_2.some_value) + pytest.raises(AttributeError, lambda: sample_object_2.some_value) def test_cloning_propperty_assigned_values(self): sample_object_1 = self.prototype.clone() sample_object_2 = self.prototype.clone(value="re-assigned") - self.assertNotEqual(sample_object_1.value, sample_object_2.value) + assert sample_object_1.value != sample_object_2.value -class TestDispatcherFeatures(unittest.TestCase): - def setUp(self): +class TestDispatcherFeatures: + def setup_method(self): self.dispatcher = PrototypeDispatcher() self.prototype = Prototype() c = self.prototype.clone() @@ -36,13 +36,13 @@ def setUp(self): self.dispatcher.register_object("C", c) def test_batch_retrieving(self): - self.assertEqual(len(self.dispatcher.get_objects()), 3) + assert len(self.dispatcher.get_objects()) == 3 def test_particular_properties_retrieving(self): - self.assertEqual(self.dispatcher.get_objects()["A"].value, "a-value") - self.assertEqual(self.dispatcher.get_objects()["B"].value, "b-value") - self.assertEqual(self.dispatcher.get_objects()["C"].value, "default") + assert self.dispatcher.get_objects()["A"].value == "a-value" + assert self.dispatcher.get_objects()["B"].value == "b-value" + assert self.dispatcher.get_objects()["C"].value == "default" def test_extended_properties_retrieving(self): - self.assertEqual(self.dispatcher.get_objects()["A"].ext_value, "E") - self.assertTrue(self.dispatcher.get_objects()["B"].diff) + assert self.dispatcher.get_objects()["A"].ext_value == "E" + assert self.dispatcher.get_objects()["B"].diff diff --git a/tests/fundamental/test_delegation.py b/tests/fundamental/test_delegation.py index 3bfd0496b..f7124b14a 100644 --- a/tests/fundamental/test_delegation.py +++ b/tests/fundamental/test_delegation.py @@ -1,6 +1,6 @@ import pytest -from patterns.fundamental.delegation_pattern import Delegator, Delegate +from patterns.fundamental.delegation_pattern import Delegate, Delegator def test_delegator_delegates_attribute_and_call(): diff --git a/tests/structural/test_adapter.py b/tests/structural/test_adapter.py index 013230752..042a32f26 100644 --- a/tests/structural/test_adapter.py +++ b/tests/structural/test_adapter.py @@ -1,10 +1,8 @@ -import unittest - from patterns.structural.adapter import Adapter, Car, Cat, Dog, Human -class ClassTest(unittest.TestCase): - def setUp(self): +class TestClass: + def setup_method(self): self.dog = Dog() self.cat = Cat() self.human = Human() @@ -13,57 +11,57 @@ def setUp(self): def test_dog_shall_bark(self): noise = self.dog.bark() expected_noise = "woof!" - self.assertEqual(noise, expected_noise) + assert noise == expected_noise def test_cat_shall_meow(self): noise = self.cat.meow() expected_noise = "meow!" - self.assertEqual(noise, expected_noise) + assert noise == expected_noise def test_human_shall_speak(self): noise = self.human.speak() expected_noise = "'hello'" - self.assertEqual(noise, expected_noise) + assert noise == expected_noise def test_car_shall_make_loud_noise(self): noise = self.car.make_noise(1) expected_noise = "vroom!" - self.assertEqual(noise, expected_noise) + assert noise == expected_noise def test_car_shall_make_very_loud_noise(self): noise = self.car.make_noise(10) expected_noise = "vroom!!!!!!!!!!" - self.assertEqual(noise, expected_noise) + assert noise == expected_noise -class AdapterTest(unittest.TestCase): +class TestAdapter: def test_dog_adapter_shall_make_noise(self): dog = Dog() dog_adapter = Adapter(dog, make_noise=dog.bark) noise = dog_adapter.make_noise() expected_noise = "woof!" - self.assertEqual(noise, expected_noise) + assert noise == expected_noise def test_cat_adapter_shall_make_noise(self): cat = Cat() cat_adapter = Adapter(cat, make_noise=cat.meow) noise = cat_adapter.make_noise() expected_noise = "meow!" - self.assertEqual(noise, expected_noise) + assert noise == expected_noise def test_human_adapter_shall_make_noise(self): human = Human() human_adapter = Adapter(human, make_noise=human.speak) noise = human_adapter.make_noise() expected_noise = "'hello'" - self.assertEqual(noise, expected_noise) + assert noise == expected_noise def test_car_adapter_shall_make_loud_noise(self): car = Car() car_adapter = Adapter(car, make_noise=car.make_noise) noise = car_adapter.make_noise(1) expected_noise = "vroom!" - self.assertEqual(noise, expected_noise) + assert noise == expected_noise def test_car_adapter_shall_make_very_loud_noise(self): car = Car() @@ -71,4 +69,4 @@ def test_car_adapter_shall_make_very_loud_noise(self): noise = car_adapter.make_noise(10) expected_noise = "vroom!!!!!!!!!!" - self.assertEqual(noise, expected_noise) + assert noise == expected_noise diff --git a/tests/structural/test_bridge.py b/tests/structural/test_bridge.py index 6665f3273..bcf2a46d6 100644 --- a/tests/structural/test_bridge.py +++ b/tests/structural/test_bridge.py @@ -1,10 +1,9 @@ -import unittest from unittest.mock import patch from patterns.structural.bridge import CircleShape, DrawingAPI1, DrawingAPI2 -class BridgeTest(unittest.TestCase): +class TestBridge: def test_bridge_shall_draw_with_concrete_api_implementation(cls): ci1 = DrawingAPI1() ci2 = DrawingAPI2() @@ -14,10 +13,10 @@ def test_bridge_shall_draw_with_concrete_api_implementation(cls): ): sh1 = CircleShape(1, 2, 3, ci1) sh1.draw() - cls.assertEqual(mock_ci1_draw_circle.call_count, 1) + assert mock_ci1_draw_circle.call_count == 1 sh2 = CircleShape(1, 2, 3, ci2) sh2.draw() - cls.assertEqual(mock_ci2_draw_circle.call_count, 1) + assert mock_ci2_draw_circle.call_count == 1 def test_bridge_shall_scale_both_api_circles_with_own_implementation(cls): SCALE_FACTOR = 2 @@ -32,13 +31,13 @@ def test_bridge_shall_scale_both_api_circles_with_own_implementation(cls): sh2 = CircleShape(1, 2, CIRCLE2_RADIUS, ci2) sh1.scale(SCALE_FACTOR) sh2.scale(SCALE_FACTOR) - cls.assertEqual(sh1._radius, EXPECTED_CIRCLE1_RADIUS) - cls.assertEqual(sh2._radius, EXPECTED_CIRCLE2_RADIUS) + assert sh1._radius == EXPECTED_CIRCLE1_RADIUS + assert sh2._radius == EXPECTED_CIRCLE2_RADIUS with ( patch.object(sh1, "scale") as mock_sh1_scale_circle, patch.object(sh2, "scale") as mock_sh2_scale_circle, ): sh1.scale(2) sh2.scale(2) - cls.assertEqual(mock_sh1_scale_circle.call_count, 1) - cls.assertEqual(mock_sh2_scale_circle.call_count, 1) + assert mock_sh1_scale_circle.call_count == 1 + assert mock_sh2_scale_circle.call_count == 1 diff --git a/tests/structural/test_decorator.py b/tests/structural/test_decorator.py index 8a4154a91..04a7a112e 100644 --- a/tests/structural/test_decorator.py +++ b/tests/structural/test_decorator.py @@ -1,24 +1,18 @@ -import unittest - from patterns.structural.decorator import BoldWrapper, ItalicWrapper, TextTag -class TestTextWrapping(unittest.TestCase): - def setUp(self): +class TestTextWrapping: + def setup_method(self): self.raw_string = TextTag("raw but not cruel") def test_italic(self): - self.assertEqual( - ItalicWrapper(self.raw_string).render(), "raw but not cruel" - ) + assert ItalicWrapper(self.raw_string).render() == "raw but not cruel" def test_bold(self): - self.assertEqual( - BoldWrapper(self.raw_string).render(), "raw but not cruel" - ) + assert BoldWrapper(self.raw_string).render() == "raw but not cruel" def test_mixed_bold_and_italic(self): - self.assertEqual( - BoldWrapper(ItalicWrapper(self.raw_string)).render(), - "raw but not cruel", + assert ( + BoldWrapper(ItalicWrapper(self.raw_string)).render() + == "raw but not cruel" ) diff --git a/tests/structural/test_mvc.py b/tests/structural/test_mvc.py index 5991c5115..3206cc7b6 100644 --- a/tests/structural/test_mvc.py +++ b/tests/structural/test_mvc.py @@ -1,9 +1,9 @@ import pytest from patterns.structural.mvc import ( - ProductModel, ConsoleView, Controller, + ProductModel, Router, ) diff --git a/tests/structural/test_proxy.py b/tests/structural/test_proxy.py index 3409bf0b5..a8965e2cf 100644 --- a/tests/structural/test_proxy.py +++ b/tests/structural/test_proxy.py @@ -1,37 +1,17 @@ -import sys -import unittest -from io import StringIO - from patterns.structural.proxy import Proxy, client -class ProxyTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - """Class scope setup.""" - cls.proxy = Proxy() - - def setUp(cls): - """Function/test case scope setup.""" - cls.output = StringIO() - cls.saved_stdout = sys.stdout - sys.stdout = cls.output - - def tearDown(cls): - """Function/test case scope teardown.""" - cls.output.close() - sys.stdout = cls.saved_stdout - - def test_do_the_job_for_admin_shall_pass(self): - client(self.proxy, "admin") - assert self.output.getvalue() == ( +class TestProxy: + def test_do_the_job_for_admin_shall_pass(self, capsys): + client(Proxy(), "admin") + assert capsys.readouterr().out == ( "[log] Doing the job for admin is requested.\n" "I am doing the job for admin\n" ) - def test_do_the_job_for_anonymous_shall_reject(self): - client(self.proxy, "anonymous") - assert self.output.getvalue() == ( + def test_do_the_job_for_anonymous_shall_reject(self, capsys): + client(Proxy(), "anonymous") + assert capsys.readouterr().out == ( "[log] Doing the job for anonymous is requested.\n" "[log] I can do the job just for `admins`.\n" ) diff --git a/tests/test_hsm.py b/tests/test_hsm.py index 5b49fb970..4ddd4d331 100644 --- a/tests/test_hsm.py +++ b/tests/test_hsm.py @@ -1,6 +1,7 @@ -import unittest from unittest.mock import patch +import pytest + from patterns.other.hsm.hsm import ( Active, HierachicalStateMachine, @@ -12,26 +13,26 @@ ) -class HsmMethodTest(unittest.TestCase): +class TestHsmMethod: @classmethod - def setUpClass(cls): + def setup_class(cls): cls.hsm = HierachicalStateMachine() def test_initial_state_shall_be_standby(cls): - cls.assertEqual(isinstance(cls.hsm._current_state, Standby), True) + assert isinstance(cls.hsm._current_state, Standby) def test_unsupported_state_shall_raise_exception(cls): - with cls.assertRaises(UnsupportedState): + with pytest.raises(UnsupportedState): cls.hsm._next_state("missing") def test_unsupported_message_type_shall_raise_exception(cls): - with cls.assertRaises(UnsupportedMessageType): + with pytest.raises(UnsupportedMessageType): cls.hsm.on_message("trigger") def test_calling_next_state_shall_change_current_state(cls): cls.hsm._current_state = Standby # initial state cls.hsm._next_state("active") - cls.assertEqual(isinstance(cls.hsm._current_state, Active), True) + assert isinstance(cls.hsm._current_state, Active) cls.hsm._current_state = Standby(cls.hsm) # initial state def test_method_perform_switchover_shall_return_specifically(cls): @@ -39,23 +40,23 @@ def test_method_perform_switchover_shall_return_specifically(cls): (here: _perform_switchover()). Add additional test cases...""" return_value = cls.hsm._perform_switchover() expected_return_value = "perform switchover" - cls.assertEqual(return_value, expected_return_value) + assert return_value == expected_return_value -class StandbyStateTest(unittest.TestCase): +class TestStandbyState: """Exemplary 2nd level state test class (here: Standby state). Add missing state test classes...""" @classmethod - def setUpClass(cls): + def setup_class(cls): cls.hsm = HierachicalStateMachine() - def setUp(cls): + def setup_method(cls): cls.hsm._current_state = Standby(cls.hsm) def test_given_standby_on_message_switchover_shall_set_active(cls): cls.hsm.on_message("switchover") - cls.assertEqual(isinstance(cls.hsm._current_state, Active), True) + assert isinstance(cls.hsm._current_state, Active) def test_given_standby_on_message_switchover_shall_call_hsm_methods(cls): with ( @@ -67,32 +68,32 @@ def test_given_standby_on_message_switchover_shall_call_hsm_methods(cls): patch.object(cls.hsm, "_next_state") as mock_next_state, ): cls.hsm.on_message("switchover") - cls.assertEqual(mock_perform_switchover.call_count, 1) - cls.assertEqual(mock_check_mate_status.call_count, 1) - cls.assertEqual(mock_send_switchover_response.call_count, 1) - cls.assertEqual(mock_next_state.call_count, 1) + assert mock_perform_switchover.call_count == 1 + assert mock_check_mate_status.call_count == 1 + assert mock_send_switchover_response.call_count == 1 + assert mock_next_state.call_count == 1 def test_given_standby_on_message_fault_trigger_shall_set_suspect(cls): cls.hsm.on_message("fault trigger") - cls.assertEqual(isinstance(cls.hsm._current_state, Suspect), True) + assert isinstance(cls.hsm._current_state, Suspect) def test_given_standby_on_message_diagnostics_failed_shall_raise_exception_and_keep_in_state( cls, ): - with cls.assertRaises(UnsupportedTransition): + with pytest.raises(UnsupportedTransition): cls.hsm.on_message("diagnostics failed") - cls.assertEqual(isinstance(cls.hsm._current_state, Standby), True) + assert isinstance(cls.hsm._current_state, Standby) def test_given_standby_on_message_diagnostics_passed_shall_raise_exception_and_keep_in_state( cls, ): - with cls.assertRaises(UnsupportedTransition): + with pytest.raises(UnsupportedTransition): cls.hsm.on_message("diagnostics passed") - cls.assertEqual(isinstance(cls.hsm._current_state, Standby), True) + assert isinstance(cls.hsm._current_state, Standby) def test_given_standby_on_message_operator_inservice_shall_raise_exception_and_keep_in_state( cls, ): - with cls.assertRaises(UnsupportedTransition): + with pytest.raises(UnsupportedTransition): cls.hsm.on_message("operator inservice") - cls.assertEqual(isinstance(cls.hsm._current_state, Standby), True) + assert isinstance(cls.hsm._current_state, Standby)