diff --git a/.editorconfig b/.editorconfig index 573ac0f4..c98add8c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,6 +4,7 @@ root = true [*] end_of_line = lf charset = utf-8 +max_line_length = 99 [*.py] indent_style = space diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 461254e9..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -name: Bug report -about: Create a bug report to help us improve semver -title: '' -labels: bug -assignees: '' - ---- - - - -# Situation - - -# To Reproduce - - -# Expected Behavior - - -# Environment -- OS: [e.g. Linux, MacOS, Windows, ...] -- Python version [e.g. 3.6, 3.7, ...] -- Version of semver library [e.g. 3.0.0] - -# Additional context - diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml new file mode 100644 index 00000000..6e75e2c8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -0,0 +1,88 @@ +name: 🐞Bug report +description: Create a bug report to help us improve +# title: '' +labels: [bug] +assignees: [tomschr] + +body: + - type: markdown + attributes: + value: | + Thanks a lot for taking the time to fill out this bug report! 🐛 + It will help us to improve the project for everyone. 🌟 + + Find help from the community on our [GitHub Discussions](https://github.com/python-semver/python-semver/discussions) page or + on our [Documentation](https://python-semver.readthedocs.io/en/latest/). + + - type: dropdown + id: python_version + attributes: + label: Which version of Python is the problem with? + multiple: true + options: + - "3.7" + - "3.8" + - "3.9" + - "3.10" + - "3.11" + - "3.12" + - "3.13" + - "3.14" + validations: + required: true + + - type: input + id: semver_version + attributes: + label: What semver version are you using? + description: You can find this with `pip show semver` + placeholder: 3.0.2 + + - type: dropdown + id: os + attributes: + label: What OS are you using? (Add more in the Environment section) + multiple: true + options: + - Linux + - Windows + - macOS + - Other + + - type: textarea + id: situation + attributes: + label: Situation + description: A clear and concise description of what the bug is. + placeholder: Describe the problem you see... + validations: + required: true + + - type: textarea + id: reproduction_steps + attributes: + label: How to reproduce + description: | + Steps to reproduce the behavior: + 1. Run '...' + 2. Scroll down to '....' + 3. See error + placeholder: Describe the steps to reproduce the issue... + validations: + required: true + + - type: textarea + id: expected_behavior + attributes: + label: Expected behavior + description: A clear and concise description of what you expected to happen. + placeholder: Describe the expected behavior... + validations: + required: true + + - type: textarea + id: environment + attributes: + label: Environment + description: Optionally provide some more details about your environment. + placeholder: Describe your environment... diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 5a24681d..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: enhancement -assignees: '' - ---- - - - -# Situation - - -# Possible Solution/Idea - - - -# Additional context - diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml new file mode 100644 index 00000000..2ec54c11 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -0,0 +1,36 @@ +name: Feature request +description: Suggest an idea for this project +# title: '' +labels: [enhancement] +assignees: [tomschr] + +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this feature request! + + - type: textarea + id: situation + attributes: + label: Situation + description: A clear and concise description of what the feature is. Ex. I'm always frustrated when [...] + placeholder: Describe the situation... + validations: + required: true + + - type: textarea + id: expected_solution + attributes: + label: Expected solution + description: A clear and concise description of what you want to happen. + placeholder: Describe the expected solution... + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives + description: A clear and concise description of any alternative solutions or features you've considered. + placeholder: Describe any alternatives... \ No newline at end of file diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index f310a7e3..61d1a1f6 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -13,7 +13,7 @@ name: "CodeQL" on: push: - branches: [ master, maint/v2 ] + branches: [ maint/v2, release/* ] pull_request: # The branches below must be a subset of the branches above branches: [ master ] @@ -35,11 +35,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -50,7 +50,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -64,4 +64,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/python-testing.yml b/.github/workflows/python-testing.yml index fb23fcf8..7c5759ce 100644 --- a/.github/workflows/python-testing.yml +++ b/.github/workflows/python-testing.yml @@ -1,79 +1,134 @@ --- name: Python +# HINT: Sync this paths with the egrep in step check_files on: push: - branches: [ master ] + branches: [ "master", "main" ] + paths: + - 'pyproject.toml' + - 'setup.cfg' + - '**.py' + - '.github/workflows/*.yml' + pull_request: - branches: [ master ] + branches: [ "master", "main" ] + paths: + - 'pyproject.toml' + - 'setup.cfg' + - '**.py' + - '.github/workflows/*.yml' + +permissions: + contents: read concurrency: # only cancel in-progress runs of the same workflow group: ${{ github.workflow }}-${{ github.ref }} - # ${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: - check: + check-files: runs-on: ubuntu-latest + outputs: + can_run: ${{ steps.check_files.outputs.can_run }} + steps: - - uses: actions/checkout@v3 - - name: Output env variables - run: | - echo "Default branch=${default-branch}" - echo "GITHUB_WORKFLOW=${GITHUB_WORKFLOW}" - echo "GITHUB_ACTION=$GITHUB_ACTION" - echo "GITHUB_ACTIONS=$GITHUB_ACTIONS" - echo "GITHUB_ACTOR=$GITHUB_ACTOR" - echo "GITHUB_REPOSITORY=$GITHUB_REPOSITORY" - echo "GITHUB_EVENT_NAME=$GITHUB_EVENT_NAME" - echo "GITHUB_EVENT_PATH=$GITHUB_EVENT_PATH" - echo "GITHUB_WORKSPACE=$GITHUB_WORKSPACE" - echo "GITHUB_SHA=$GITHUB_SHA" - echo "GITHUB_REF=$GITHUB_REF" - echo "GITHUB_HEAD_REF=$GITHUB_HEAD_REF" - echo "GITHUB_BASE_REF=$GITHUB_BASE_REF" - echo "::debug::---Start content of file $GITHUB_EVENT_PATH" - cat $GITHUB_EVENT_PATH - echo "\n" - echo "::debug::---end" - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + - uses: actions/checkout@v4 with: - python-version: 3.7 - - name: Install dependencies + fetch-depth: 0 + + - name: GitHub variables + id: gh-vars + run: | + for var in GITHUB_WORKFLOW GITHUB_ACTION GITHUB_ACTIONS GITHUB_REPOSITORY GITHUB_EVEN_NAME GITHUB_EVENT_PATH GITHUB_WORKSPACE GITHUB_SHA GITHUB_REF GITHUB_HEAD_REF GITHUB_BASE_REF; do + echo "$var = ${!var}" + done + + - name: Check for file changes + id: check_files run: | - python3 -m pip install --upgrade pip setuptools setuptools-scm - pip install tox tox-gh-actions - - name: Check + # ${{ github.event.after }} ${{ github.event.before }} + can_run=$(git diff --name-only HEAD~1 HEAD | \ + egrep -q '.github/workflows/|pyproject.toml|setup.cfg|\.py$' && echo 1 || echo 0) + echo "can_run=$can_run" + echo "can_run=$can_run" >> $GITHUB_OUTPUT + + skip_test: + runs-on: ubuntu-latest + needs: check-files + timeout-minutes: 2 + if: ${{ needs.check-files.outputs.can_run == '0' }} + + steps: + - name: Skip test run: | - tox -e checks + echo "Nothing to do as no TOML, Python, or YAML file has been changed. + " + echo "Skipping." + + check: + runs-on: ubuntu-latest + needs: check-files + timeout-minutes: 15 + # needs.check-files.outputs.can_run + if: ${{ needs.check-files.outputs.can_run == '1' }} + + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v3 + with: + enable-cache: true + + - name: Set up Python 3.7 + # check tox.ini for the lowest version + run: uv python install 3.7 + + - name: Install the project + run: | + uv sync --all-extras --group gh-action + + - name: Checks + continue-on-error: true + run: uv run tox run -e checks tests: needs: check - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} + # continue-on-error: true strategy: max-parallel: 5 + fail-fast: true matrix: python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", - # "3.12-dev" + "3.12", + "3.13", ] + os: ["ubuntu-latest", "macos-latest"] + exclude: + - os: "macos-latest" + python-version: "3.7" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v3 + - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python3 -m pip install --upgrade pip - pip install tox tox-gh-actions - - name: Test with tox + run: uv python install ${{ matrix.python-version }} + + - name: Install the project run: | - tox + uv sync --all-extras --dev + uv pip install tox tox-gh-actions + + - name: Checks + run: uv run tox run -e ${{ matrix.python-version }} diff --git a/.gitignore b/.gitignore index c0859cd3..256de715 100644 --- a/.gitignore +++ b/.gitignore @@ -108,7 +108,7 @@ celerybeat.pid # Environments .env -.venv +.venv* env/ venv/ ENV/ @@ -268,3 +268,6 @@ fabric.properties docs/_api !docs/_api/semver.__about__.rst + +# For node +node_modules/ diff --git a/.pytest.ini b/.pytest.ini new file mode 100644 index 00000000..2058b4fd --- /dev/null +++ b/.pytest.ini @@ -0,0 +1,15 @@ +[pytest] +norecursedirs = .git build .env/ env/ .pyenv/ .tmp/ .eggs/ venv/ +testpaths = tests docs +pythonpath = src tests +filterwarnings = + ignore:Function 'semver.*:DeprecationWarning + # ' <- This apostroph is just to fix syntax highlighting +addopts = + --import-mode=importlib + --no-cov-on-fail + --cov=semver + --cov-report=term-missing + --doctest-glob='*.rst' + --doctest-modules + --doctest-report ndiff \ No newline at end of file diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..626c2a5e --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,26 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the version of Python and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.11" + jobs: + pre_install: + # - curl -LsSf https://astral.sh/uv/install.sh | sh + - pip install uv + - uv export --only-group docs --no-hashes --no-color > requirements-docs.txt + post_install: + - pip install -r requirements-docs.txt + pre_build: + - make -C docs html + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/conf.py + diff --git a/.ruff.toml b/.ruff.toml new file mode 100644 index 00000000..ddcdbb07 --- /dev/null +++ b/.ruff.toml @@ -0,0 +1,53 @@ +# +# Configuration file for ruff +# See https://docs.astral.sh/ruff/configuration/ + +line-length = 88 +indent-width = 4 +include = [ + "pyproject.toml", + "src/**/*.py", + "tests/**/*.py", + "docs/**/*.py", +] + +[lint] +extend-ignore = [ + "RUF005", + "RUF012", + # Comment contains ambiguous `’` (RIGHT SINGLE QUOTATION MARK): + "RUF003", + "ISC001", +] +select = [ + "F", # pyflakes + "E", # pycodestyle + "I", # isort + "N", # pep8-naming + "UP", # pyupgrade + "RUF", # ruff + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "ISC", # flake8-implicit-str-concat + "PTH", # flake8-use-pathlib + "SIM", # flake8-simplify + "TID", # flake8-tidy-imports +] +# TODO: Remove ISC001 ignore when formatter updated: https://github.com/astral-sh/ruff/issues/8272 + + +[format] +# Exclude type hint stub files from formatting. +exclude = ["*.pyi"] + +# Like Black, use double quotes for strings. +quote-style = "double" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +docstring-code-format = true +docstring-code-line-length = "dynamic" \ No newline at end of file diff --git a/BUILDING.rst b/BUILDING.rst deleted file mode 100644 index 61f3d4cb..00000000 --- a/BUILDING.rst +++ /dev/null @@ -1,60 +0,0 @@ -.. _build-semver: - -Building semver -=============== - -.. _PEP 517: https://www.python.org/dev/peps/pep-0517/ -.. _PEP 621: https://www.python.org/dev/peps/pep-0621/ -.. _A Practical Guide to Setuptools and Pyproject.toml: https://godatadriven.com/blog/a-practical-guide-to-setuptools-and-pyproject-toml/ -.. _Declarative config: https://setuptools.rtfd.io/en/latest/userguide/declarative_config.html - - -This project changed slightly its way how it is built. The reason for this -was to still support the "traditional" way with :command:`setup.py`, -but at the same time try out the newer way with :file:`pyproject.toml`. -As Python 3.6 got deprecated, this project does support from now on only -:file:`pyproject.toml`. - - -Background information ----------------------- - -Skip this section and head over to :ref:`build-pyproject-build` if you just -want to know how to build semver. -This section gives some background information how this project is set up. - -The traditional way with :command:`setup.py` in this project uses a -`Declarative config`_. With this approach, the :command:`setup.py` is -stripped down to its bare minimum and all the metadata is stored in -:file:`setup.cfg`. - -The new :file:`pyproject.toml` contains only information about the build backend, currently setuptools.build_meta. The idea is taken from -`A Practical Guide to Setuptools and Pyproject.toml`_. -Setuptools-specific configuration keys as defined in `PEP 621`_ are currently -not used. - - -.. _build-pyproject-build: - -Building with pyproject-build ------------------------------ - -To build semver you need: - -* The :mod:`build` module which implements the `PEP 517`_ build - frontend. - Install it with:: - - pip install build - - Some Linux distributions has already packaged it. If you prefer - to use the module with your package manager, search for - :file:`python-build` or :file:`python3-build` and install it. - -* The command :command:`pyproject-build` from the :mod:`build` module. - -To build semver, run:: - - pyproject-build - -After the command is finished, you can find two files in the :file:`dist` folder: a ``.tar.gz`` and a ``.whl`` file. \ No newline at end of file diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7f515aa1..9efa4477 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,8 @@ Changes for the upcoming release can be found in the `"changelog.d" directory `_ in our repository. +This section covers the changes between major version 2 and version 3. + .. Do *NOT* add changelog entries here! @@ -16,198 +18,269 @@ in our repository. .. towncrier release notes start -Version 3.0.0-dev.4 -=================== +Version 3.0.4 +============= -:Released: 2022-12-18 -:Maintainer: +:Released: 2025-01-24 +:Maintainer: Tom Schraitle Bug Fixes --------- -* :gh:`374`: Correct Towncrier's config entries in the :file:`pyproject.toml` file. - The old entries ``[[tool.towncrier.type]]`` are deprecated and need - to be replaced by ``[tool.towncrier.fragment.]``. +* :gh:`459`: Fix 3.0.3: + * :pr:`457`: Re-enable Trove license identifier + * :pr:`456`: Fix source dist file -Deprecations ------------- +---- -* :gh:`372`: Deprecate support for Python 3.6. - Python 3.6 reached its end of life and isn't supported anymore. - At the time of writing (Dec 2022), the lowest version is 3.7. +Version 3.0.3 +============= - Although the `poll `_ - didn't cast many votes, the majority agree to remove support for - Python 3.6. +:Released: 2025-01-18 +:Maintainer: Tom Schraitle +Bug Fixes +--------- -Improved Documentation ----------------------- +* :pr:`453`: The check in ``_comparator`` does not match the check in :meth:`Version.compare`. + This breaks comparision with subclasses. -* :gh:`335`: Add new section "Converting versions between PyPI and semver" the limitations - and possible use cases to convert from one into the other versioning scheme. -* :gh:`340`: Describe how to get version from a file -* :gh:`343`: Describe combining Pydantic with semver in the "Advanced topic" - section. +Improved Documentation +---------------------- -* :gh:`350`: Restructure usage section. Create subdirectory "usage/" and splitted - all section into different files. +* :pr:`435`: Several small improvements for documentation: -* :gh:`351`: Introduce new topics for: + * Add meta description to improve SEO + * Use canonicals on ReadTheDocs (commit 87f639f) + * Pin versions for reproducable doc builds (commit 03fb990) + * Add missing :file:`.readthedocs.yaml` file (commit ec9348a) + * Correct some smaller issues when building (commit f65feab) - * "Migration to semver3" - * "Advanced topics" +* :pr:`436`: Move search box more at the top. This makes it easier for + users as if the TOC is long, the search box isn't visible + anymore. Features -------- -* :pr:`359`: Add optional parameter ``optional_minor_and_patch`` in :meth:`.Version.parse` to allow optional - minor and patch parts. +* :pr:`439`: Improve type hints to fix TODOs -* :pr:`362`: Make :meth:`.Version.match` accept a bare version string as match expression, defaulting to - equality testing. -* :gh:`364`: Enhance :file:`pyproject.toml` to make it possible to use the - :command:`pyproject-build` command from the build module. - For more information, see :ref:`build-semver`. -* :gh:`365`: Improve :file:`pyproject.toml`. +Internal Changes +---------------- - * Use setuptools, add metadata. Taken approach from - `A Practical Guide to Setuptools and Pyproject.toml - `_. - * Doc: Describe building of semver - * Remove :file:`.travis.yml` in :file:`MANIFEST.in` - (not needed anymore) - * Distinguish between Python 3.6 and others in :file:`tox.ini` - * Add skip_missing_interpreters option for :file:`tox.ini` - * GH Action: Upgrade setuptools and setuptools-scm and test - against 3.11.0-rc.2 +* :pr:`440`: Update workflow file +* :pr:`446`: Add Python 3.13 to GitHub Actions +* :pr:`447`: Modernize project configs with :file:`pyproject.toml` and + use Astral's uv command. -Trivial/Internal Changes ------------------------- + * In :file:`pyproject.toml`: -* :gh:`378`: Fix some typos in Towncrier configuration + * Move all project related data from :file:`setup.cfg` to :file:`pyproject.toml` + * Use new dependency group from :pep:`735` + * Consolidate flake8, isort, pycodestyle with ruff + * Split towncrier config type "trivial" into "trivial" and "internal" + + * Create config file for ruff (:file:`.ruff.toml`) + * Create config file for pytest (:file:`.pytest.ini`) + * Simplify :file:`tox.ini` and remove old stuff + * Document installation with new :command:`uv` command + * Simplify Sphinx config with :func:`find_version()` + * Update the authors + * Use :command:`uv` in GitHub Action :file:`python-testing.yml` workflow + +* Update :file:`release-procedure.md`. + +* :pr:`451`: Turn our Markdown issue templates into YAML + + +Trivial Changes +--------------- + +* :pr:`438`: Replace organization placeholder in license + +* :pr:`445`: Improve private :func:`_nat_cmp` method: + + * Remove obsolete else. + * Find a better way to identify digits without the :mod:`re` module. + * Fix docstring in :meth:`Version.compare` ---- -Version 3.0.0-dev.3 -=================== +Version 3.0.2 +============= -:Released: 2022-01-19 +:Released: 2023-10-09 :Maintainer: Tom Schraitle Bug Fixes --------- -* :gh:`310`: Rework API documentation. - Follow a more "semi-manual" attempt and add auto directives - into :file:`docs/api.rst`. +* :pr:`418`: Replace :class:`~collection.OrderedDict` with :class:`dict`. + + The dict datatype is ordered since Python 3.7. As we do not support + Python 3.6 anymore, it can be considered safe to avoid :class:`~collection.OrderedDict`. + Related to :gh:`419`. + +* :pr:`426`: Fix :meth:`~semver.version.Version.replace` method to use the derived class + of an instance instead of :class:`~semver.version.Version` class. Improved Documentation ---------------------- -* :gh:`312`: Rework "Usage" section. +* :pr:`431`: Clarify version policy for the different semver versions (v2, v3, >v3) + and the supported Python versions. + +* :gh:`432`: Improve external doc links to Python and Pydantic. - * Mention the rename of :class:`~semver.version.VersionInfo` to - :class:`~semver.version.Version` class - * Remove semver. prefix in doctests to make examples shorter - * Correct some references to dunder methods like - :func:`~.semver.version.Version.__getitem__`, - :func:`~.semver.version.Version.__gt__` etc. - * Remove inconsistencies and mention module level function as - deprecated and discouraged from using - * Make empty :py:func:`super` call in :file:`semverwithvprefix.py` example -* :gh:`315`: Improve release procedure text + +Features +-------- + +* :pr:`417`: Amend GitHub Actions to check against MacOS. Trivial/Internal Changes ------------------------ -* :gh:`309`: Some (private) functions from the :mod:`semver.version` - module has been changed. - - The following functions got renamed: +* :pr:`420`: Introduce :py:class:`~typing.ClassVar` for some :class:`~semver.version.Version` + class variables, mainly :data:`~semver.version.Version.NAMES` and some private. - * function ``semver.version.comparator`` got renamed to - :func:`semver.version._comparator` as it is only useful - inside the :class:`~semver.version.Version` class. - * function ``semver.version.cmp`` got renamed to - :func:`semver.version._cmp` as it is only useful - inside the :class:`~semver.version.Version` class. +* :pr:`421`: Insert mypy configuration into :file:`pyproject.toml` and remove + config options from :file:`tox.ini`. - The following functions got integrated into the - :class:`~semver.version.Version` class: - * function ``semver.version._nat_cmd`` as a classmethod - * function ``semver.version.ensure_str`` -* :gh:`313`: Correct :file:`tox.ini` for ``changelog`` entry to skip - installation for semver. This should speed up the execution - of towncrier. +---- -* :gh:`316`: Comparisons of :class:`~semver.version.Version` class and other - types return now a :py:const:`NotImplemented` constant instead - of a :py:exc:`TypeError` exception. - The `NotImplemented`_ section of the Python documentation recommends - returning this constant when comparing with ``__gt__``, ``__lt__``, - and other comparison operators to "to indicate that the operation is - not implemented with respect to the other type". +Version 3.0.1 +============= - .. _NotImplemented: https://docs.python.org/3/library/constants.html#NotImplemented +:Released: 2023-06-14 +:Maintainer: Tom Schraitle -* :gh:`319`: Introduce stages in :file:`.travis.yml` - The config file contains now two stages: check and test. If - check fails, the test stage won't be executed. This could - speed up things when some checks fails. -* :gh:`322`: Switch from Travis CI to GitHub Actions. +Bug Fixes +--------- -* :gh:`347`: Support Python 3.10 in GitHub Action and other config files. +* :gh:`410`: Export functions properly using ``__all__`` in ``__init__.py``. ---- -Version 3.0.0-dev.2 -=================== +Version 3.0.0 +============= -:Released: 2020-11-01 +:Released: 2023-04-02 :Maintainer: Tom Schraitle +Bug Fixes +--------- + +* :gh:`291`: Disallow negative numbers in VersionInfo arguments + for ``major``, ``minor``, and ``patch``. + +* :gh:`310`: Rework API documentation. + Follow a more "semi-manual" attempt and add auto directives + into :file:`docs/api.rst`. + +* :gh:`344`: Allow empty string, a string with a prefix, or ``None`` + as token in + :meth:`~semver.version.Version.bump_build` and + :meth:`~semver.version.Version.bump_prerelease`. + +* :gh:`374`: Correct Towncrier's config entries in the :file:`pyproject.toml` file. + The old entries ``[[tool.towncrier.type]]`` are deprecated and need + to be replaced by ``[tool.towncrier.fragment.]``. + +* :pr:`384`: General cleanup, reformat files: + + * Reformat source code with black again as some config options + did accidentely exclude the semver source code. + Mostly remove some includes/excludes in the black config. + * Integrate concurrency in GH Action + * Ignore Python files on project dirs in .gitignore + * Remove unused patterns in MANIFEST.in + * Use ``extend-exclude`` for flake in :file:`setup.cfg`` and adapt list. + * Use ``skip_install=True`` in :file:`tox.ini` for black + +* :pr:`393`: Fix command :command:`python -m semver` to avoid the error "invalid choice" + +* :pr:`396`: Calling :meth:`~semver.version.Version.parse` on a derived class will show correct type of derived class. + + Deprecations ------------ * :gh:`169`: Deprecate CLI functions not imported from ``semver.cli``. +* :gh:`234`: In :file:`setup.py` simplified file and remove + ``Tox`` and ``Clean`` classes + +* :gh:`284`: Deprecate the use of :meth:`~Version.isvalid`. + + Rename :meth:`~semver.version.Version.isvalid` + to :meth:`~semver.version.Version.is_valid` + for consistency reasons with :meth:`~semver.version.Version.is_compatible`. + + +* :pr:`290`: For semver 3.0.0-alpha0 deprecated: + + * Remove anything related to Python2 + * In :file:`tox.ini` and :file:`.travis.yml` + Remove targets py27, py34, py35, and pypy. + Add py38, py39, and nightly (allow to fail) + * In :file:`setup.py` simplified file and remove + ``Tox`` and ``Clean`` classes + * Remove old Python versions (2.7, 3.4, 3.5, and pypy) + from Travis + +* :gh:`372`: Deprecate support for Python 3.6. + + Python 3.6 reached its end of life and isn't supported anymore. + At the time of writing (Dec 2022), the lowest version is 3.7. + + Although the `poll `_ + didn't cast many votes, the majority agreed to remove support for + Python 3.6. + +* :pr:`402`: Keep :func:`semver.compare `. + Although it breaks consistency with module level functions, it seems it's + a much needed/used function. It's still unclear if we should deprecate + this function or not (that's why we use :py:exc:`PendingDeprecationWarning`). + + As we don't have a uniform initializer yet, this function stays in the + :file:`_deprecated.py` file for the time being until we find a better solution. See :gh:`258` for details. Features -------- -* :gh:`169`: Create semver package and split code among different modules in the packages. +* :gh:`169`: Create semver package and split code among different modules in the packages: * Remove :file:`semver.py` * Create :file:`src/semver/__init__.py` @@ -218,21 +291,90 @@ Features * Create :file:`src/semver/version.py` to hold the :class:`Version` class (old name :class:`VersionInfo`) and its utility functions * Create :file:`src/semver/__about__.py` for all the metadata variables -* :gh:`305`: Rename :class:`VersionInfo` to :class:`Version` but keep an alias for compatibility +* :gh:`213`: Add typing information + +* :gh:`284`: Implement :meth:`~semver.version.Version.is_compatible` to make "is self compatible with X". + +* :gh:`305`: Rename :class:`~semver.version.VersionInfo` to :class:`~semver.version.Version` but keep an alias for compatibility + +* :pr:`359`: Add optional parameter ``optional_minor_and_patch`` in :meth:`~semver.version.Version.parse` to allow optional + minor and patch parts. + +* :pr:`362`: Make :meth:`~semver.version.Version.match` accept a bare version string as match expression, defaulting to equality testing. + +* :gh:`364`: Enhance :file:`pyproject.toml` to make it possible to use the + :command:`pyproject-build` command from the build module. + For more information, see :ref:`build-semver`. + +* :gh:`365`: Improve :file:`pyproject.toml`. + + * Use setuptools, add metadata. Taken approach from + `A Practical Guide to Setuptools and Pyproject.toml + `_. + * Doc: Describe building of semver + * Remove :file:`.travis.yml` in :file:`MANIFEST.in` + (not needed anymore) + * Distinguish between Python 3.6 and others in :file:`tox.ini` + * Add skip_missing_interpreters option for :file:`tox.ini` + * GH Action: Upgrade setuptools and setuptools-scm and test + against 3.11.0-rc.2 Improved Documentation ---------------------- +* :gh:`276`: Document how to create a sublass from :class:`~semver.version.VersionInfo` class + +* :gh:`284`: Document deprecation of :meth:`~semver.version.Version.isvalid`. + +* :pr:`290`: Several improvements in the documentation: + + * New layout to distinguish from the semver2 development line. + * Create new logo. + * Remove any occurances of Python2. + * Describe changelog process with Towncrier. + * Update the release process. + * :gh:`304`: Several improvements in documentation: * Reorganize API documentation. * Add migration chapter from semver2 to semver3. * Distinguish between changlog for version 2 and 3 -* :gh:`305`: Add note about :class:`Version` rename. +* :gh:`305`: Add note about :class:`~semver.version.Version` rename. + +* :gh:`312`: Rework "Usage" section. + * Mention the rename of :class:`~semver.version.VersionInfo` to + :class:`~semver.version.Version` class + * Remove semver. prefix in doctests to make examples shorter + * Correct some references to dunder methods like + :func:`~semver.version.Version.__getitem__`, + :func:`~semver.version.Version.__gt__` etc. + * Remove inconsistencies and mention module level function as + deprecated and discouraged from using + * Make empty :py:func:`super` call in :file:`semverwithvprefix.py` example + +* :gh:`315`: Improve release procedure text + +* :gh:`335`: Add new section "Converting versions between PyPI and semver" the limitations + and possible use cases to convert from one into the other versioning scheme. + +* :gh:`340`: Describe how to get version from a file + +* :gh:`343`: Describe combining Pydantic with semver in the "Advanced topic" + section. + +* :gh:`350`: Restructure usage section. Create subdirectory "usage/" and splitted + all section into different files. + +* :gh:`351`: Introduce new topics for: + + * "Migration to semver3" + * "Advanced topics" + +* :pr:`392`: Fix the example in the documentation for combining semver and pydantic. Trivial/Internal Changes @@ -247,6 +389,8 @@ Trivial/Internal Changes Increase coverage to 100% for all non-deprecated APIs +* :pr:`290`: Add supported Python versions to :command:`black`. + * :gh:`304`: Support PEP-561 :file:`py.typed`. According to the mentioned PEP: @@ -258,96 +402,61 @@ Trivial/Internal Changes Add package_data to :file:`setup.cfg` to include this marker in dist and whl file. +* :gh:`309`: Some (private) functions from the :mod:`semver.version` + module has been changed. + The following functions got renamed: ----- - - -Version 3.0.0-dev.1 -=================== - -:Released: 2020-10-26 -:Maintainer: Tom Schraitle - - -Deprecations ------------- - -* :pr:`290`: For semver 3.0.0-alpha0: - - * Remove anything related to Python2 - * In :file:`tox.ini` and :file:`.travis.yml` - Remove targets py27, py34, py35, and pypy. - Add py38, py39, and nightly (allow to fail) - * In :file:`setup.py` simplified file and remove - ``Tox`` and ``Clean`` classes - * Remove old Python versions (2.7, 3.4, 3.5, and pypy) - from Travis - -* :gh:`234`: In :file:`setup.py` simplified file and remove - ``Tox`` and ``Clean`` classes - - + * function :func:`semver.version.comparator` got renamed to + :func:`semver.version._comparator` as it is only useful + inside the :class:`~semver.version.Version` class. + * function :func:`semver.version.cmp` got renamed to + :func:`semver.version._cmp` as it is only useful + inside the :class:`~semver.version.Version` class. -Features --------- + The following functions got integrated into the + :class:`~semver.version.Version` class: -* :pr:`290`: Create semver 3.0.0-alpha0 - - * Update :file:`README.rst`, mention maintenance - branch ``maint/v2``. - * Remove old code mainly used for Python2 compatibility, - adjusted code to support Python3 features. - * Split test suite into separate files under :file:`tests/` - directory - * Adjust and update :file:`setup.py`. Requires Python >=3.6.* - Extract metadata directly from source (affects all the ``__version__``, - ``__author__`` etc. variables) - -* :gh:`270`: Configure Towncrier (:pr:`273`:) - - * Add :file:`changelog.d/.gitignore` to keep this directory - * Create :file:`changelog.d/README.rst` with some descriptions - * Add :file:`changelog.d/_template.rst` as Towncrier template - * Add ``[tool.towncrier]`` section in :file:`pyproject.toml` - * Add "changelog" target into :file:`tox.ini`. Use it like - :command:`tox -e changelog -- CMD` whereas ``CMD`` is a - Towncrier command. The default :command:`tox -e changelog` - calls Towncrier to create a draft of the changelog file - and output it to stdout. - * Update documentation and add include a new section - "Changelog" included from :file:`changelog.d/README.rst`. - -* :gh:`276`: Document how to create a sublass from :class:`VersionInfo` class + * function :func:`semver.version._nat_cmd` as a classmethod + * function :func:`semver.version.ensure_str` -* :gh:`213`: Add typing information +* :gh:`313`: Correct :file:`tox.ini` for ``changelog`` entry to skip + installation for semver. This should speed up the execution + of towncrier. +* :gh:`316`: Comparisons of :class:`~semver.version.Version` class and other + types return now a :py:const:`NotImplemented` constant instead + of a :py:exc:`TypeError` exception. -Bug Fixes ---------- + The `NotImplemented`_ section of the Python documentation recommends + returning this constant when comparing with ``__gt__``, ``__lt__``, + and other comparison operators to "to indicate that the operation is + not implemented with respect to the other type". -* :gh:`291`: Disallow negative numbers in VersionInfo arguments - for ``major``, ``minor``, and ``patch``. + .. _NotImplemented: https://docs.python.org/3/library/constants.html#NotImplemented +* :gh:`319`: Introduce stages in :file:`.travis.yml` + The config file contains now two stages: check and test. If + check fails, the test stage won't be executed. This could + speed up things when some checks fails. +* :gh:`322`: Switch from Travis CI to GitHub Actions. -Improved Documentation ----------------------- +* :gh:`347`: Support Python 3.10 in GitHub Action and other config files. -* :pr:`290`: Several improvements in the documentation: +* :gh:`378`: Fix some typos in Towncrier configuration - * New layout to distinguish from the semver2 development line. - * Create new logo. - * Remove any occurances of Python2. - * Describe changelog process with Towncrier. - * Update the release process. +* :gh:`388`: For pytest, switch to the more modern :mod:`importlib` approach + as it doesn't require to modify :data:`sys.path`: + https://docs.pytest.org/en/7.2.x/explanation/pythonpath.html +* :pr:`389`: Add public class variable :data:`Version.NAMES `. + This class variable contains a tuple of strings that contains the names of + all attributes of a Version (like ``"major"``, ``"minor"`` etc). -Trivial/Internal Changes ------------------------- + In cases we need to have dynamical values, this makes it easier to iterate. -* :pr:`290`: Add supported Python versions to :command:`black`. .. diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 00000000..708f4d0a --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,104 @@ +# This CITATION.cff file was generated with cffinit: +# https://bit.ly/cffinit + +cff-version: 1.2.0 +title: python-semver +message: >- + If you use this software, please cite it using the + metadata from this file. +type: software + +authors: + - given-names: Kostiantyn + family-names: Rybnikov + email: k-bx@k-bx.com + - given-names: Tom + family-names: Schraitle + email: tom_schr@web.de + - given-names: Sebastian + family-names: Celles + email: s.celles@gmail.com + - name: "The python-semver software team" + +identifiers: + - type: url + value: 'https://github.com/python-semver/python-semver' + description: GitHub python-semver/python-semver +url: 'https://python-semver.readthedocs.io' +repository-code: 'https://github.com/python-semver/python-semver' +repository-artifact: 'https://pypi.org/project/semver/' + +abstract: >- + A Python module for semantic versioning. Simplifies + comparing versions. This modules follows the + MAJOR.MINOR.PATCH style. + +keywords: + - Python + - Python module + - semver + - versioning + - semantic versioning + - semver-format + - semver-tag + - versions + +references: + - authors: + - family-names: Preston-Werner + given-names: Tom + - name: "The semver team" + title: 'Semantic Versioning 2.0.0' + url: 'https://semver.org' + repository-code: 'https://github.com/semver/semver' + type: standard + version: 2.0.0 + languages: + - ar + - bg + - ca + - cs + - da + - de + - el + - en + - es + - fa + - fr + - he + - hin + - hr + - hu + - hy + - id + - it + - ja + - ka + - kab + - ko + - nl + - pl + - pt + - ro + - ru + - sk + - sl + - sr + - sv + - tr + - uk + - vi + - zh + abstract: >- + Given a version number MAJOR.MINOR.PATCH, increment the: + + 1. MAJOR version when you make incompatible API changes + 2. MINOR version when you add functionality in a backwards compatible manner + 3. PATCH version when you make backwards compatible bug fixes + + Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format. + +license: BSD-3-Clause +commit: 3a7680dc436211227c0aeae84c9b45e0b3345b8f +version: 3.0.0 +date-released: '2023-04-02' diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index fe531990..ce598e01 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -8,6 +8,7 @@ The semver source code is managed using Git and is hosted on GitHub:: git clone git://github.com/python-semver/python-semver + Reporting Bugs and Asking Questions ----------------------------------- @@ -34,193 +35,14 @@ consider the following general requirements: If not, ask on its GitHub project https://github.com/semver/semver. +More topics +----------- -Modifying the Code ------------------- - -We recommend the following workflow: - -#. Fork our project on GitHub using this link: - https://github.com/python-semver/python-semver/fork - -#. Clone your forked Git repository (replace ``GITHUB_USER`` with your - account name on GitHub):: - - $ git clone git@github.com:GITHUB_USER/python-semver.git - -#. Create a new branch. You can name your branch whatever you like, but we - recommend to use some meaningful name. If your fix is based on a - existing GitHub issue, add also the number. Good examples would be: - - * ``feature/123-improve-foo`` when implementing a new feature in issue 123 - * ``bugfix/234-fix-security-bar`` a bugfixes for issue 234 - - Use this :command:`git` command:: - - $ git checkout -b feature/NAME_OF_YOUR_FEATURE - -#. Work on your branch and create a pull request: - - a. Write test cases and run the complete test suite, see :ref:`testsuite` - for details. - - b. Write a changelog entry, see section :ref:`add-changelog`. - - c. If you have implemented a new feature, document it into our - documentation to help our reader. See section :ref:`doc` for - further details. - - d. Create a `pull request`_. Describe in the pull request what you did - and why. If you have open questions, ask. - -#. Wait for feedback. If you receive any comments, address these. - -#. After your pull request got accepted, delete your branch. - - -.. _testsuite: - -Running the Test Suite ----------------------- - -We use `pytest`_ and `tox`_ to run tests against all supported Python -versions. All test dependencies are resolved automatically. - -You can decide to run the complete test suite or only part of it: - -* To run all tests, use:: - - $ tox - - If you have not all Python interpreters installed on your system - it will probably give you some errors (``InterpreterNotFound``). - To avoid such errors, use:: - - $ tox --skip-missing-interpreters - - It is possible to use one or more specific Python versions. Use the ``-e`` - option and one or more abbreviations (``py37`` for Python 3.7, - ``py38`` for Python 3.8 etc.):: - - $ tox -e py37 - $ tox -e py37,py38 - - To get a complete list and a short description, run:: - - $ tox -av - -* To run only a specific test, pytest requires the syntax - ``TEST_FILE::TEST_FUNCTION``. - - For example, the following line tests only the function - :func:`test_immutable_major` in the file :file:`test_bump.py` for all - Python versions:: - - $ tox -e py37 -- tests/test_bump.py::test_should_bump_major - - By default, pytest prints only a dot for each test function. To - reveal the executed test function, use the following syntax:: - - $ tox -- -v - - You can combine the specific test function with the ``-e`` option, for - example, to limit the tests for Python 3.7 and 3.8 only:: - - $ tox -e py37,py38 -- tests/test_bump.py::test_should_bump_major - -Our code is checked against formatting, style, type, and docstring issues -(`black`_, `flake8`_, `mypy`_, and `docformatter`_). -It is recommended to run your tests in combination with :command:`checks`, -for example:: - - $ tox -e checks,py37,py38 - - -.. _doc: - -Documenting semver ------------------- - -Documenting the features of semver is very important. It gives our developers -an overview what is possible with semver, how it "feels", and how it is -used efficiently. - -.. note:: - - To build the documentation locally use the following command:: - - $ tox -e docs - - The built documentation is available in :file:`docs/_build/html`. - - -A new feature is *not* complete if it isn't proberly documented. A good -documentation includes: - - * **A docstring** - - Each docstring contains a summary line, a linebreak, an optional - directive (see next item), the description of its arguments in - `Sphinx style`_, and an optional doctest. - The docstring is extracted and reused in the :ref:`api` section. - An appropriate docstring should look like this:: - - def to_tuple(self) -> VersionTuple: - """ - Convert the Version object to a tuple. - - .. versionadded:: 2.10.0 - Renamed ``VersionInfo._astuple`` to ``VersionInfo.to_tuple`` to - make this function available in the public API. - - :return: a tuple with all the parts - - >>> semver.Version(5, 3, 1).to_tuple() - (5, 3, 1, None, None) - """ - - * **An optional directive** - - If you introduce a new feature, change a function/method, or remove something, - it is a good practice to introduce Sphinx directives into the docstring. - This gives the reader an idea what version is affected by this change. - - The first required argument, ``VERSION``, defines the version when this change - was introduced. You can choose from: - - * ``.. versionadded:: VERSION`` - - Use this directive to describe a new feature. - - * ``.. versionchanged:: VERSION`` - - Use this directive to describe when something has changed, for example, - new parameters were added, changed side effects, different return values, etc. - - * ``.. deprecated:: VERSION`` - - Use this directive when a feature is deprecated. Describe what should - be used instead, if appropriate. - - - Add such a directive *after* the summary line, as shown above. - - * **The documentation** - - A docstring is good, but in most cases it's too dense. API documentation - cannot replace a good user documentation. Describe how - to use your new feature in our documentation. Here you can give your - readers more examples, describe it in a broader context or show - edge cases. - - -.. _add-changelog: - -Adding a Changelog Entry ------------------------- - -.. include:: ../changelog.d/README.rst - :start-after: -text-begin- +* `Running the Test Suite `_ +* `Documenting semver `_ +* `Adding a Changelog Entry `_ +* `Preparing the Release `_ +* `Finish the Release `_ .. _black: https://black.rtfd.io diff --git a/CONTRIBUTORS b/CONTRIBUTORS index e5fef99b..0d90ab3a 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -22,30 +22,37 @@ Old maintainer: * Kostiantyn Rybnikov -Significant contributors -======================== +List of Contributors +==================== -* Alexander Puzynia -* Alexander Shorin -* Anton Talevnin -* Ben Finney +(in alphabetical order) + +* Jelo Agnasin * Carles Barrobés -* Craig Blaszczyk -* Damien Nadé * Eli Bishop -* George Sakkis -* Jan Pieter Waagmeester -* Jelo Agnasin -* Karol Werner * Peter Bittner -* robi-wan -* sbrudenell +* Craig Blaszczyk +* Tyler Cross +* Dennis Felsing +* Ben Finney +* Zane Geiger * T. Jameson Little +* Raphael Krupinski * Thomas Laferriere -* Tuure Laurinolli -* Tyler Cross * Zack Lalanne - +* Tuure Laurinolli +* Damien Nadé +* Jan Pieter Waagmeester +* Alexander Puzynia +* Lexi Robinson +* robi-wan +* George Sakkis +* Mike Salvatore +* sbrudenell +* Alexander Shorin +* Anton Talevnin +* Karol Werner + .. Local variables: coding: utf-8 diff --git a/LICENSE.txt b/LICENSE.txt index f98e22bc..bbb09286 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -11,7 +11,7 @@ are permitted provided that the following conditions are met: list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of the {organization} nor the names of its + Neither the name of the python-semver org nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. diff --git a/MANIFEST.in b/MANIFEST.in index e37851c9..fa2dcb20 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,8 +1,26 @@ +include *.md include *.rst include *.txt -include tests/test_*.py +include CONTRIBUTORS +include CITATION.cff +include Makefile +include changelog.d/* +graft docs/** +include tests/*.py +include tox.ini +include .pytest.ini +include .ruff.toml +include uv.lock + +# The dot files: +include .coveragerc +include .editorconfig +include .gitignore +include .readthedocs.yaml + prune docs/_build recursive-exclude .github * +prune docs/**/__pycache__ global-exclude __pycache__ diff --git a/README.rst b/README.rst index cad99a04..66552796 100644 --- a/README.rst +++ b/README.rst @@ -1,37 +1,15 @@ -.. warning:: - - This is a development version. Do **NOT** use it - in production before the final 3.0.0 is released. - Quickstart ========== .. teaser-begin -A Python module for `semantic versioning`_. Simplifies comparing versions. +A Python module to simplify `semantic versioning`_. |GHAction| |python-support| |downloads| |license| |docs| |black| |openissues| |GHDiscussion| .. teaser-end -.. note:: - - This project works for Python 3.7 and greater only. If you are - looking for a compatible version for Python 2, use the - maintenance branch |MAINT|_. - - The last version of semver which supports Python 2.7 to 3.5 will be - 2.x.y However, keep in mind, the major 2 release is frozen: no new - features nor backports will be integrated. - - We recommend to upgrade your workflow to Python 3 to gain support, - bugfixes, and new features. - -.. |MAINT| replace:: ``maint/v2`` -.. _MAINT: https://github.com/python-semver/python-semver/tree/maint/v2 - - The module follows the ``MAJOR.MINOR.PATCH`` style: * ``MAJOR`` version when you make incompatible API changes, diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 00000000..4702eb08 --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,14 @@ +# Getting support + +If you need help, try these ways: + +* Ask your questions on the [discussion](https://github.com/python-semver/python-semver/discussions) page. + + Our forum is a good way to post your questions. +* Read the [python-semver documentation](https://python-semver.readthedocs.io/). + + The documentation contains all the information to install and use the library. +* Suggest a new feature or a new [issue](https://github.com/python-semver/python-semver/issues/new) + + If you found a problem or would like to suggest a new feature that could improve python-semver, + this is the place to go. diff --git a/changelog.d/460.bugfix.rst b/changelog.d/460.bugfix.rst new file mode 100644 index 00000000..dc21db80 --- /dev/null +++ b/changelog.d/460.bugfix.rst @@ -0,0 +1,9 @@ +:meth:`~semver.version.Version.bump_prerelease` will now add `.0` to an +existing prerelease when the last segment of the current prerelease, split by +dots (`.`), is not numeric. This is to ensure the new prerelease is considered +higher than the previous one. + +:meth:`~semver.version.Version.bump_prerelease` now also support an argument +`bump_when_empty` which will bump the patch version if there is no existing +prerelease, to ensure the resulting version is considered a higher version than +the previous one. \ No newline at end of file diff --git a/changelog.d/463.trivial.rst b/changelog.d/463.trivial.rst new file mode 100644 index 00000000..3dcad2ae --- /dev/null +++ b/changelog.d/463.trivial.rst @@ -0,0 +1 @@ +Remove double code in :meth:`Version.bump_build` \ No newline at end of file diff --git a/changelog.d/pr384.bugfix.rst b/changelog.d/pr384.bugfix.rst deleted file mode 100644 index ca0b08d0..00000000 --- a/changelog.d/pr384.bugfix.rst +++ /dev/null @@ -1,11 +0,0 @@ -General cleanup, reformat files: - -* Reformat source code with black again as some config options - did accidentely exclude the semver source code. - Mostly remove some includes/excludes in the black config. -* Integrate concurrency in GH Action -* Ignore Python files on project dirs in .gitignore -* Remove unused patterns in MANIFEST.in -* Use ``extend-exclude`` for flake in :file:`setup.cfg`` and adapt list. -* Use ``skip_install=True`` in :file:`tox.ini` for black - diff --git a/docs/Makefile b/docs/Makefile index ea7c53f0..a38fc5bd 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -8,6 +8,12 @@ SPHINXPROJ = semver SOURCEDIR = . BUILDDIR = _build +# Set the source directory for your project +SRC_DIR = ../src + +# Set the PYTHONPATH environment variable +export PYTHONPATH := $(SRC_DIR):$(PYTHONPATH) + # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css index 81a5906f..33510db3 100644 --- a/docs/_static/css/custom.css +++ b/docs/_static/css/custom.css @@ -31,6 +31,10 @@ div.related.top nav { margin-bottom: -0.75em; } +#searchbox { + margin-bottom: 1.25em; +} + .section h1 { font-weight: 700; } diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html index 7a114d41..1f72ac26 100644 --- a/docs/_templates/layout.html +++ b/docs/_templates/layout.html @@ -3,47 +3,8 @@ #} {% extends "!layout.html" %} -{%- block footer %} - -{% if theme_github_banner|lower != 'false' %} - - Fork me on GitHub - -{% endif %} -{% if theme_analytics_id %} - -{% endif %} -{%- endblock %} \ No newline at end of file +{%- block extrahead %} + {{- super() }} + +{% endblock %} \ No newline at end of file diff --git a/docs/advanced/combine-pydantic-and-semver.rst b/docs/advanced/combine-pydantic-and-semver.rst index a9249daf..c7566e12 100644 --- a/docs/advanced/combine-pydantic-and-semver.rst +++ b/docs/advanced/combine-pydantic-and-semver.rst @@ -1,45 +1,75 @@ Combining Pydantic and semver ============================= +.. meta:: + :description lang=en: + Combining Pydantic and semver + According to its homepage, `Pydantic `_ "enforces type hints at runtime, and provides user friendly errors when data is invalid." -To work with Pydantic, use the following steps: +To work with Pydantic>2.0, use the following steps: 1. Derive a new class from :class:`~semver.version.Version` - first and add the magic methods :py:meth:`__get_validators__` - and :py:meth:`__modify_schema__` like this: + first and add the magic methods :py:meth:`__get_pydantic_core_schema__` + and :py:meth:`__get_pydantic_json_schema__` like this: .. code-block:: python + from typing import Annotated, Any, Callable + from pydantic import GetJsonSchemaHandler + from pydantic_core import core_schema + from pydantic.json_schema import JsonSchemaValue from semver import Version - class PydanticVersion(Version): + + class _VersionPydanticAnnotation: @classmethod - def __get_validators__(cls): - """Return a list of validator methods for pydantic models.""" - yield cls.parse + def __get_pydantic_core_schema__( + cls, + _source_type: Any, + _handler: Callable[[Any], core_schema.CoreSchema], + ) -> core_schema.CoreSchema: + def validate_from_str(value: str) -> Version: + return Version.parse(value) + + from_str_schema = core_schema.chain_schema( + [ + core_schema.str_schema(), + core_schema.no_info_plain_validator_function(validate_from_str), + ] + ) + + return core_schema.json_or_python_schema( + json_schema=from_str_schema, + python_schema=core_schema.union_schema( + [ + core_schema.is_instance_schema(Version), + from_str_schema, + ] + ), + serialization = core_schema.to_string_ser_schema(), + ) @classmethod - def __modify_schema__(cls, field_schema): - """Inject/mutate the pydantic field schema in-place.""" - field_schema.update(examples=["1.0.2", - "2.15.3-alpha", - "21.3.15-beta+12345", - ] - ) + def __get_pydantic_json_schema__( + cls, _core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler + ) -> JsonSchemaValue: + return handler(core_schema.str_schema()) + + ManifestVersion = Annotated[Version, _VersionPydanticAnnotation] 2. Create a new model (in this example :class:`MyModel`) and derive - it from :class:`pydantic.BaseModel`: + it from :py:class:`pydantic:pydantic.BaseModel`: .. code-block:: python import pydantic class MyModel(pydantic.BaseModel): - version: PydanticVersion + version: _VersionPydanticAnnotation 3. Use your model like this: @@ -50,4 +80,4 @@ To work with Pydantic, use the following steps: The attribute :py:attr:`model.version` will be an instance of :class:`~semver.version.Version`. If the version is invalid, the construction will raise a - :py:exc:`pydantic.ValidationError`. + :py:class:`pydantic:pydantic_core.ValidationError`. diff --git a/docs/advanced/convert-pypi-to-semver.rst b/docs/advanced/convert-pypi-to-semver.rst index 76653ceb..82b4f5a3 100644 --- a/docs/advanced/convert-pypi-to-semver.rst +++ b/docs/advanced/convert-pypi-to-semver.rst @@ -1,6 +1,10 @@ -Converting versions between PyPI and semver +Converting Versions between PyPI and semver =========================================== +.. meta:: + :description lang=en: + Converting versions between PyPI and semver + .. Link https://packaging.pypa.io/en/latest/_modules/packaging/version.html#InvalidVersion @@ -135,7 +139,7 @@ semver: def convert2semver(ver: packaging.version.Version) -> semver.Version: """Converts a PyPI version into a semver version - :param packaging.version.Version ver: the PyPI version + :param ver: the PyPI version :return: a semver version :raises ValueError: if epoch or post parts are used """ @@ -145,7 +149,7 @@ semver: raise ValueError("Can't convert a post part to semver") pre = None if not ver.pre else "".join([str(i) for i in ver.pre]) - semver.Version(*ver.release, prerelease=pre, build=ver.dev) + return semver.Version(*ver.release, prerelease=pre, build=ver.dev) .. _convert_semver_to_pypi: diff --git a/docs/advanced/create-subclasses-from-version.rst b/docs/advanced/create-subclasses-from-version.rst index 7c97ee6f..3a5194f4 100644 --- a/docs/advanced/create-subclasses-from-version.rst +++ b/docs/advanced/create-subclasses-from-version.rst @@ -3,6 +3,10 @@ Creating Subclasses from Version ================================ +.. meta:: + :description lang=en: + Creating subclasses from Version class + If you do not like creating functions to modify the behavior of semver (as shown in section :ref:`sec_dealing_with_invalid_versions`), you can also create a subclass of the :class:`Version ` class. @@ -16,7 +20,8 @@ but the other behavior is the same, use the following code: The derived class :class:`SemVerWithVPrefix` can be used like -the original class: +the original class. Additionally, you can pass "incomplete" +version strings like ``v2.3``: .. code-block:: python @@ -24,7 +29,7 @@ the original class: >>> assert str(v1) == "v1.2.3" >>> print(v1) v1.2.3 - >>> v2 = SemVerWithVPrefix.parse("v2.3.4") + >>> v2 = SemVerWithVPrefix.parse("v2.3") >>> v2 > v1 True >>> bad = SemVerWithVPrefix.parse("1.2.4") diff --git a/docs/advanced/deal-with-invalid-versions.rst b/docs/advanced/deal-with-invalid-versions.rst index ee5e5704..120d82a0 100644 --- a/docs/advanced/deal-with-invalid-versions.rst +++ b/docs/advanced/deal-with-invalid-versions.rst @@ -3,6 +3,10 @@ Dealing with Invalid Versions ============================= +.. meta:: + :description lang=en: + Dealing with invalid versions + As semver follows the semver specification, it cannot parse version strings which are considered "invalid" by that specification. The semver library cannot know all the possible variations so you need to help the diff --git a/docs/advanced/display-deprecation-warnings.rst b/docs/advanced/display-deprecation-warnings.rst index 825bbe76..a2fb8342 100644 --- a/docs/advanced/display-deprecation-warnings.rst +++ b/docs/advanced/display-deprecation-warnings.rst @@ -3,6 +3,10 @@ Displaying Deprecation Warnings =============================== +.. meta:: + :description lang=en: + Displaying and filtering deprecation warnings + By default, deprecation warnings are `ignored in Python `_. This also affects semver's own warnings. diff --git a/docs/advanced/index.rst b/docs/advanced/index.rst index 8a45d361..26cd5729 100644 --- a/docs/advanced/index.rst +++ b/docs/advanced/index.rst @@ -1,8 +1,12 @@ Advanced topics =============== +.. meta:: + :description lang=en: + Advanced topics for Python semver .. toctree:: + :maxdepth: 1 deal-with-invalid-versions create-subclasses-from-version diff --git a/docs/advanced/semverwithvprefix.py b/docs/advanced/semverwithvprefix.py index f2a7fecd..7e411d35 100644 --- a/docs/advanced/semverwithvprefix.py +++ b/docs/advanced/semverwithvprefix.py @@ -20,7 +20,7 @@ def parse(cls, version: str) -> "SemVerWithVPrefix": f"{version!r}: not a valid semantic version tag. " "Must start with 'v' or 'V'" ) - return super().parse(version[1:]) + return super().parse(version[1:], optional_minor_and_patch=True) def __str__(self) -> str: # Reconstruct the tag diff --git a/docs/advanced/version-from-file.rst b/docs/advanced/version-from-file.rst index 6dc9bb48..5d484438 100644 --- a/docs/advanced/version-from-file.rst +++ b/docs/advanced/version-from-file.rst @@ -1,23 +1,28 @@ .. _sec_reading_versions_from_file: -Reading versions from file +Reading Versions from File ========================== +.. meta:: + :description lang=en: + Reading versions from file + In cases where a version is stored inside a file, one possible solution is to use the following function: .. code-block:: python + import os + from typing import Union from semver.version import Version - def get_version(path: str = "version") -> Version: + def get_version(path: Union[str, os.PathLike]) -> semver.Version: """ - Construct a Version from a file + Construct a Version object from a file :param path: A text file only containing the semantic version :return: A :class:`Version` object containing the semantic version from the file. """ - with open(path,"r") as fh: - version = fh.read().strip() + version = open(path,"r").read().strip() return Version.parse(version) diff --git a/docs/api.rst b/docs/api.rst index 196e30a9..0ce4012c 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3,6 +3,10 @@ API Reference ============= +.. meta:: + :description lang=en: + API reference about Python semver + .. currentmodule:: semver @@ -17,8 +21,37 @@ Deprecated Functions in :mod:`semver._deprecated` .. automodule:: semver._deprecated +.. autofunction:: semver._deprecated.compare + +.. autofunction:: semver._deprecated.bump_build + +.. autofunction:: semver._deprecated.bump_major + +.. autofunction:: semver._deprecated.bump_minor + +.. autofunction:: semver._deprecated.bump_patch + +.. autofunction:: semver._deprecated.bump_prerelease + .. autofunction:: semver._deprecated.deprecated +.. autofunction:: semver._deprecated.finalize_version + +.. autofunction:: semver._deprecated.format_version + +.. autofunction:: semver._deprecated.match + +.. autofunction:: semver._deprecated.max_ver + +.. autofunction:: semver._deprecated.min_ver + +.. autofunction:: semver._deprecated.parse + +.. autofunction:: semver._deprecated.parse_version_info + +.. autofunction:: semver._deprecated.replace + + CLI Parsing :mod:`semver.cli` ----------------------------- diff --git a/docs/build-semver.rst b/docs/build-semver.rst new file mode 100644 index 00000000..9c0441d8 --- /dev/null +++ b/docs/build-semver.rst @@ -0,0 +1,33 @@ +.. _build-semver: + +Building semver +=============== + +.. meta:: + :description lang=en: + Building semver + +.. _Installing uv: https://docs.astral.sh/uv/getting-started/installation/ + + +This project changed its way how it is built over time. We used to have +a :file:`setup.py` file, but switched to a :file:`pyproject.toml` setup. + +The build process is managed by :command:`uv` command. + +You need: + +* Python 3.7 or newer. + +* The :mod:`setuptools` module version 61 or newer which is used as + a build backend. + +* The command :command:`uv` from Astral. Refer to the section + `Installing uv`_ for more information. + + +To build semver, run:: + + uv build + +After the command is finished, you can find two files in the :file:`dist` folder: a ``.tar.gz`` and a ``.whl`` file. \ No newline at end of file diff --git a/docs/build.rst b/docs/build.rst deleted file mode 100644 index ba0c84a4..00000000 --- a/docs/build.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../BUILDING.rst diff --git a/docs/changelog-semver2.rst b/docs/changelog-semver2.rst deleted file mode 100644 index dca94413..00000000 --- a/docs/changelog-semver2.rst +++ /dev/null @@ -1,670 +0,0 @@ -################## -Change Log semver2 -################## - -This changelog contains older entries for semver2. - ----- - - -Version 2.13.0 -============== - -:Released: 2020-10-20 -:Maintainer: Tom Schraitle - - -Features --------- - -* :pr:`287`: Document how to create subclass from ``VersionInfo`` - - -Bug Fixes ---------- - -* :pr:`283`: Ensure equal versions have equal hashes. - Version equality means for semver, that ``major``, - ``minor``, ``patch``, and ``prerelease`` parts are - equal in both versions you compare. The ``build`` part - is ignored. - - -Additions ---------- - -n/a - - -Deprecations ------------- - -n/a - - ----- - - -Version 2.12.0 -============== - -:Released: 2020-10-19 -:Maintainer: Tom Schraitle - - -Bug Fixes ---------- - -* :gh:`291` (:pr:`292`): Disallow negative numbers of - ``major``, ``minor``, and ``patch`` for :class:`semver.VersionInfo` - - ----- - - -Version 2.11.0 -============== - -:Released: 2020-10-17 -:Maintainer: Tom Schraitle - - -Bug Fixes ---------- - -* :gh:`276` (:pr:`277`): ``VersionInfo.parse`` should be a class method - Also add authors and update changelog in :gh:`286` -* :gh:`274` (:pr:`275`): Py2 vs. Py3 incompatibility TypeError - - ----- - - -Version 2.10.2 -============== - -:Released: 2020-06-15 -:Maintainer: Tom Schraitle - -Features --------- - -:gh:`268`: Increase coverage - - -Bug Fixes ---------- - -* :gh:`260` (:pr:`261`): Fixed ``__getitem__`` returning None on wrong parts -* :pr:`263`: Doc: Add missing "install" subcommand for openSUSE - - -Deprecations ------------- - -* :gh:`160` (:pr:`264`): - * :func:`semver.max_ver` - * :func:`semver.min_ver` - - ----- - - -Version 2.10.1 -============== - -:Released: 2020-05-13 -:Maintainer: Tom Schraitle - - -Features --------- - -* :pr:`249`: Added release policy and version restriction in documentation to - help our users which would like to stay on the major 2 release. -* :pr:`250`: Simplified installation semver on openSUSE with ``obs://``. -* :pr:`256`: Made docstrings consistent - - - -Bug Fixes ---------- - -* :gh:`251` (:pr:`254`): Fixed return type of ``semver.VersionInfo.next_version`` - to always return a ``VersionInfo`` instance. - - ----- - - - -Version 2.10.0 -============== - -:Released: 2020-05-05 -:Maintainer: Tom Schraitle - -Features --------- - -* :pr:`138`: Added ``__getitem__`` magic method to ``semver.VersionInfo`` class. - Allows to access a version like ``version[1]``. -* :pr:`235`: Improved documentation and shift focus on ``semver.VersionInfo`` instead of advertising - the old and deprecated module-level functions. -* :pr:`230`: Add version information in some functions: - - * Use ``.. versionadded::`` RST directive in docstrings to - make it more visible when something was added - * Minor wording fix in docstrings (versions -> version strings) - - -Bug Fixes ---------- - -* :gh:`224` (:pr:`226`): In ``setup.py``, replaced in class ``clean``, - ``super(CleanCommand, self).run()`` with ``CleanCommand.run(self)`` -* :gh:`244` (:pr:`245`): Allow comparison with ``VersionInfo``, tuple/list, dict, and string. - - -Additions ---------- - -* :pr:`228`: Added better doctest integration - - -Deprecations ------------- -* :gh:`225` (:pr:`229`): Output a DeprecationWarning for the following functions: - - - ``semver.parse`` - - ``semver.parse_version_info`` - - ``semver.format_version`` - - ``semver.bump_{major,minor,patch,prerelease,build}`` - - ``semver.finalize_version`` - - ``semver.replace`` - - ``semver.VersionInfo._asdict`` (use the new, public available - function ``semver.VersionInfo.to_dict()``) - - ``semver.VersionInfo._astuple`` (use the new, public available - function ``semver.VersionInfo.to_tuple()``) - - These deprecated functions will be removed in semver 3. - - ----- - - -Version 2.9.1 -============= -:Released: 2020-02-16 -:Maintainer: Tom Schraitle - -Features --------- - -* :gh:`177` (:pr:`178`): Fixed repository and CI links (moved https://github.com/k-bx/python-semver/ repository to https://github.com/python-semver/python-semver/) -* :pr:`179`: Added note about moving this project to the new python-semver organization on GitHub -* :gh:`187` (:pr:`188`): Added logo for python-semver organization and documentation -* :gh:`191` (:pr:`194`): Created manpage for pysemver -* :gh:`196` (:pr:`197`): Added distribution specific installation instructions -* :gh:`201` (:pr:`202`): Reformatted source code with black -* :gh:`208` (:pr:`209`): Introduce new function :func:`semver.VersionInfo.isvalid` - and extend :command:`pysemver` with :command:`check` subcommand -* :gh:`210` (:pr:`215`): Document how to deal with invalid versions -* :pr:`212`: Improve docstrings according to PEP257 - -Bug Fixes ---------- - -* :gh:`192` (:pr:`193`): Fixed "pysemver" and "pysemver bump" when called without arguments - - ----- - -Version 2.9.0 -============= -:Released: 2019-10-30 -:Maintainer: Sébastien Celles - -Features --------- - -* :gh:`59` (:pr:`164`): Implemented a command line interface -* :gh:`85` (:pr:`147`, :pr:`154`): Improved contribution section -* :gh:`104` (:pr:`125`): Added iterator to :func:`semver.VersionInfo` -* :gh:`112`, :gh:`113`: Added Python 3.7 support -* :pr:`120`: Improved test_immutable function with properties -* :pr:`125`: Created :file:`setup.cfg` for pytest and tox -* :gh:`126` (:pr:`127`): Added target for documentation in :file:`tox.ini` -* :gh:`142` (:pr:`143`): Improved usage section -* :gh:`144` (:pr:`156`): Added :func:`semver.replace` and :func:`semver.VersionInfo.replace` - functions -* :gh:`145` (:pr:`146`): Added posargs in :file:`tox.ini` -* :pr:`157`: Introduce :file:`conftest.py` to improve doctests -* :pr:`165`: Improved code coverage -* :pr:`166`: Reworked :file:`.gitignore` file -* :gh:`167` (:pr:`168`): Introduced global constant :data:`SEMVER_SPEC_VERSION` - -Bug Fixes ---------- - -* :gh:`102`: Fixed comparison between VersionInfo and tuple -* :gh:`103`: Disallow comparison between VersionInfo and string (and int) -* :gh:`121` (:pr:`122`): Use python3 instead of python3.4 in :file:`tox.ini` -* :pr:`123`: Improved :func:`__repr__` and derive class name from :func:`type` -* :gh:`128` (:pr:`129`): Fixed wrong datatypes in docstring for :func:`semver.format_version` -* :gh:`135` (:pr:`140`): Converted prerelease and build to string -* :gh:`136` (:pr:`151`): Added testsuite to tarball -* :gh:`154` (:pr:`155`): Improved README description - -Removals --------- - -* :gh:`111` (:pr:`110`): Dropped Python 3.3 -* :gh:`148` (:pr:`149`): Removed and replaced ``python setup.py test`` - - ----- - -Version 2.8.2 -============= -:Released: 2019-05-19 -:Maintainer: Sébastien Celles - -Skipped, not released. - ----- - -Version 2.8.1 -============= -:Released: 2018-07-09 -:Maintainer: Sébastien Celles - -Features --------- - -* :gh:`40` (:pr:`88`): Added a static parse method to VersionInfo -* :gh:`77` (:pr:`47`): Converted multiple tests into pytest.mark.parametrize -* :gh:`87`, :gh:`94` (:pr:`93`): Removed named tuple inheritance. -* :gh:`89` (:pr:`90`): Added doctests. - -Bug Fixes ---------- - -* :gh:`98` (:pr:`99`): Set prerelease and build to None by default -* :gh:`96` (:pr:`97`): Made VersionInfo immutable - - ----- - -Version 2.8.0 -============= -:Released: 2018-05-16 -:Maintainer: Sébastien Celles - - -Changes -------- - -* :gh:`82` (:pr:`83`): Renamed :file:`test.py` to :file:`test_semver.py` so - py.test can autodiscover test file - -Additions ---------- - -* :gh:`79` (:pr:`81`, :pr:`84`): Defined and improve a release procedure file -* :gh:`72`, :gh:`73` (:pr:`75`): Implemented :func:`__str__` and :func:`__hash__` - -Removals --------- - -* :gh:`76` (:pr:`80`): Removed Python 2.6 compatibility - ----- - - -Version 2.7.9 -============= - -:Released: 2017-09-23 -:Maintainer: Kostiantyn Rybnikov - - -Additions ---------- - -* :gh:`65` (:pr:`66`): Added :func:`semver.finalize_version` function. - - ----- - -Version 2.7.8 -============= - -:Released: 2017-08-25 -:Maintainer: Kostiantyn Rybnikov - -* :gh:`62`: Support custom default names for pre and build - - ----- - -Version 2.7.7 -============= - -:Released: 2017-05-25 -:Maintainer: Kostiantyn Rybnikov - -* :gh:`54` (:pr:`55`): Added comparision between VersionInfo objects -* :pr:`56`: Added support for Python 3.6 - - ----- - -Version 2.7.2 -============= - -:Released: 2016-11-08 -:Maintainer: Kostiantyn Rybnikov - -Additions ---------- - -* Added :func:`semver.parse_version_info` to parse a version string to a - version info tuple. - -Bug Fixes ---------- - -* :gh:`37`: Removed trailing zeros from prelease doesn't allow to - parse 0 pre-release version - -* Refine parsing to conform more strictly to SemVer 2.0.0. - - SemVer 2.0.0 specification §9 forbids leading zero on identifiers in - the prerelease version. - - ----- - -Version 2.6.0 -============= - -:Released: 2016-06-08 -:Maintainer: Kostiantyn Rybnikov - -Removals --------- - -* Remove comparison of build component. - - SemVer 2.0.0 specification recommends that build component is - ignored in comparisons. - - ----- - -Version 2.5.0 -============= - -:Released: 2016-05-25 -:Maintainer: Kostiantyn Rybnikov - -Additions ---------- - -* Support matching 'not equal' with “!=”. - -Changes -------- - -* Made separate builds for tests on Travis CI. - - ----- - -Version 2.4.2 -============= - -:Released: 2016-05-16 -:Maintainer: Kostiantyn Rybnikov - -Changes -------- - -* Migrated README document to reStructuredText format. - -* Used Setuptools for distribution management. - -* Migrated test cases to Py.test. - -* Added configuration for Tox test runner. - - ----- - -Version 2.4.1 -============= - -:Released: 2016-03-04 -:Maintainer: Kostiantyn Rybnikov - -Additions ---------- - -* :gh:`23`: Compared build component of a version. - - ----- - -Version 2.4.0 -============= - -:Released: 2016-02-12 -:Maintainer: Kostiantyn Rybnikov - -Bug Fixes ---------- - -* :gh:`21`: Compared alphanumeric components correctly. - - ----- - -Version 2.3.1 -============= - -:Released: 2016-01-30 -:Maintainer: Kostiantyn Rybnikov - -Additions ---------- - -* Declared granted license name in distribution metadata. - - ----- - -Version 2.3.0 -============= - -:Released: 2016-01-29 -:Maintainer: Kostiantyn Rybnikov - -Additions ---------- - -* Added functions to increment prerelease and build components in a - version. - - ----- - -Version 2.2.1 -============= - -:Released: 2015-08-04 -:Maintainer: Kostiantyn Rybnikov - -Bug Fixes ---------- - -* Corrected comparison when any component includes zero. - - ----- - -Version 2.2.0 -============= - -:Released: 2015-06-21 -:Maintainer: Kostiantyn Rybnikov - -Additions ---------- - -* Add functions to determined minimum and maximum version. - -* Add code examples for recently-added functions. - - ----- - -Version 2.1.2 -============= - -:Released: 2015-05-23 -:Maintainer: Kostiantyn Rybnikov - -Bug Fixes ---------- - -* Restored current README document to distribution manifest. - - ----- - -Version 2.1.1 -============= - -:Released: 2015-05-23 -:Maintainer: Kostiantyn Rybnikov - -Bug Fixes ---------- - -* Removed absent document from distribution manifest. - - ----- - -Version 2.1.0 -============= - -:Released: 2015-05-22 -:Maintainer: Kostiantyn Rybnikov - -Additions ---------- - -* Documented installation instructions. - -* Documented project home page. - -* Added function to format a version string from components. - -* Added functions to increment specific components in a version. - -Changes -------- - -* Migrated README document to Markdown format. - -Bug Fixes ---------- - -* Corrected code examples in README document. - - ----- - -Version 2.0.2 -============= - -:Released: 2015-04-14 -:Maintainer: Konstantine Rybnikov - -Additions ---------- - -* Added configuration for Travis continuous integration. - -* Explicitly declared supported Python versions. - - ----- - -Version 2.0.1 -============= - -:Released: 2014-09-24 -:Maintainer: Konstantine Rybnikov - -Bug Fixes ---------- - -* :gh:`9`: Fixed comparison of equal version strings. - - ----- - -Version 2.0.0 -============= - -:Released: 2014-05-24 -:Maintainer: Konstantine Rybnikov - -Additions ---------- - -* Grant license in this code base under BSD 3-clause license terms. - -Changes -------- - -* Update parser to SemVer standard 2.0.0. - -* Ignore build component for comparison. - - ----- - -Version 0.0.2 -============= - -:Released: 2012-05-10 -:Maintainer: Konstantine Rybnikov - -Changes -------- - -* Use standard library Distutils for distribution management. - - ----- - -Version 0.0.1 -============= - -:Released: 2012-04-28 -:Maintainer: Konstantine Rybnikov - -* Initial release. - - -.. - Local variables: - coding: utf-8 - mode: text - mode: rst - End: - vim: fileencoding=utf-8 filetype=rst : diff --git a/docs/changelog-semver3-devel.rst b/docs/changelog-semver3-devel.rst new file mode 100644 index 00000000..75579d0f --- /dev/null +++ b/docs/changelog-semver3-devel.rst @@ -0,0 +1,365 @@ + +############################# +Changelog semver3 development +############################# + +This site contains all the changes during the development phase. + +.. _semver-3.0.0-dev.4: + +Version 3.0.0-dev.4 +=================== + +:Released: 2022-12-18 +:Maintainer: + + +.. _semver-3.0.0-dev.4-bugfixes: + +Bug Fixes +--------- + +* :gh:`374`: Correct Towncrier's config entries in the :file:`pyproject.toml` file. + The old entries ``[[tool.towncrier.type]]`` are deprecated and need + to be replaced by ``[tool.towncrier.fragment.]``. + + + +.. _semver-3.0.0-dev.4-deprecations: + +Deprecations +------------ + +* :gh:`372`: Deprecate support for Python 3.6. + + Python 3.6 reached its end of life and isn't supported anymore. + At the time of writing (Dec 2022), the lowest version is 3.7. + + Although the `poll `_ + didn't cast many votes, the majority agree to remove support for + Python 3.6. + + +.. _semver-3.0.0-dev.4-doc: + +Improved Documentation +---------------------- + +* :gh:`335`: Add new section "Converting versions between PyPI and semver" the limitations + and possible use cases to convert from one into the other versioning scheme. + +* :gh:`340`: Describe how to get version from a file + +* :gh:`343`: Describe combining Pydantic with semver in the "Advanced topic" + section. + +* :gh:`350`: Restructure usage section. Create subdirectory "usage/" and splitted + all section into different files. + +* :gh:`351`: Introduce new topics for: + + * "Migration to semver3" + * "Advanced topics" + + +.. _semver-3.0.0-dev.4-features: + +Features +-------- + +* :pr:`359`: Add optional parameter ``optional_minor_and_patch`` in :meth:`.Version.parse` to allow optional + minor and patch parts. + +* :pr:`362`: Make :meth:`.Version.match` accept a bare version string as match expression, defaulting to + equality testing. + +* :gh:`364`: Enhance :file:`pyproject.toml` to make it possible to use the + :command:`pyproject-build` command from the build module. + For more information, see :ref:`build-semver`. + +* :gh:`365`: Improve :file:`pyproject.toml`. + + * Use setuptools, add metadata. Taken approach from + `A Practical Guide to Setuptools and Pyproject.toml + `_. + * Doc: Describe building of semver + * Remove :file:`.travis.yml` in :file:`MANIFEST.in` + (not needed anymore) + * Distinguish between Python 3.6 and others in :file:`tox.ini` + * Add skip_missing_interpreters option for :file:`tox.ini` + * GH Action: Upgrade setuptools and setuptools-scm and test + against 3.11.0-rc.2 + + +.. _semver-3.0.0-dev.4-internal: + +Trivial/Internal Changes +------------------------ + +* :gh:`378`: Fix some typos in Towncrier configuration + + + +---- + +.. _semver-3.0.0-dev.3: + +Version 3.0.0-dev.3 +=================== + +:Released: 2022-01-19 +:Maintainer: Tom Schraitle + + +.. _semver-3.0.0-dev.3-bugfixes: + +Bug Fixes +--------- + +* :gh:`310`: Rework API documentation. + Follow a more "semi-manual" attempt and add auto directives + into :file:`docs/api.rst`. + + +.. _semver-3.0.0-dev.3-docs: + +Improved Documentation +---------------------- + +* :gh:`312`: Rework "Usage" section. + + * Mention the rename of :class:`~semver.version.VersionInfo` to + :class:`~semver.version.Version` class + * Remove semver. prefix in doctests to make examples shorter + * Correct some references to dunder methods like + :func:`~semver.version.Version.__getitem__`, + :func:`~semver.version.Version.__gt__` etc. + * Remove inconsistencies and mention module level function as + deprecated and discouraged from using + * Make empty :py:class:`python:super` call in :file:`semverwithvprefix.py` example + +* :gh:`315`: Improve release procedure text + + +.. _semver-3.0.0-dev.3-trivial: + +Trivial/Internal Changes +------------------------ + +* :gh:`309`: Some (private) functions from the :mod:`semver.version` + module has been changed. + + The following functions got renamed: + + * function :func:`semver.version.comparator` got renamed to + :func:`semver.version._comparator` as it is only useful + inside the :class:`~semver.version.Version` class. + * function :func:`semver.version.cmp` got renamed to + :func:`semver.version._cmp` as it is only useful + inside the :class:`~semver.version.Version` class. + + The following functions got integrated into the + :class:`~semver.version.Version` class: + + * function :func:`semver.version._nat_cmd` as a classmethod + * function :func:`semver.version.ensure_str` + +* :gh:`313`: Correct :file:`tox.ini` for ``changelog`` entry to skip + installation for semver. This should speed up the execution + of towncrier. + +* :gh:`316`: Comparisons of :class:`~semver.version.Version` class and other + types return now a :py:data:`python:NotImplemented` constant instead + of a :py:exc:`python:TypeError` exception. + + In the Python documentation, :py:data:`python:NotImplemented` recommends + returning this constant when comparing with :py:meth:`__gt__ `, :py:meth:`__lt__ `, + and other comparison operators "to indicate that the operation is + not implemented with respect to the other type". + +* :gh:`319`: Introduce stages in :file:`.travis.yml` + The config file contains now two stages: check and test. If + check fails, the test stage won't be executed. This could + speed up things when some checks fails. + +* :gh:`322`: Switch from Travis CI to GitHub Actions. + +* :gh:`347`: Support Python 3.10 in GitHub Action and other config files. + + + +---- + +.. _semver-3.0.0-dev.2: + +Version 3.0.0-dev.2 +=================== + +:Released: 2020-11-01 +:Maintainer: Tom Schraitle + + +.. _semver-3.0.0-dev.2-deprecations: + +Deprecations +------------ + +* :gh:`169`: Deprecate CLI functions not imported from :mod:`semver.cli`. + + +.. _semver-3.0.0-dev.2-features: + +Features +-------- + +* :gh:`169`: Create semver package and split code among different modules in the packages. + + * Remove :file:`semver.py` + * Create :file:`src/semver/__init__.py` + * Create :file:`src/semver/cli.py` for all CLI methods + * Create :file:`src/semver/_deprecated.py` for the ``deprecated`` decorator and other deprecated functions + * Create :file:`src/semver/__main__.py` to allow calling the CLI using :command:`python -m semver` + * Create :file:`src/semver/_types.py` to hold type aliases + * Create :file:`src/semver/version.py` to hold the :class:`~semver.version.Version` class (old name :class:`~semver.version.VersionInfo`) and its utility functions + * Create :file:`src/semver/__about__.py` for all the metadata variables + +* :gh:`305`: Rename :class:`~semver.version.VersionInfo` to :class:`~semver.version.Version` but keep an alias for compatibility + + +.. _semver-3.0.0-dev.2-docs: + +Improved Documentation +---------------------- + +* :gh:`304`: Several improvements in documentation: + + * Reorganize API documentation. + * Add migration chapter from semver2 to semver3. + * Distinguish between changlog for version 2 and 3 + +* :gh:`305`: Add note about :class:`~semver.version.Version` rename. + + +.. _semver-3.0.0-dev.2-trivial: + +Trivial/Internal Changes +------------------------ + +* :gh:`169`: Adapted infrastructure code to the new project layout. + + * Replace :file:`setup.py` with :file:`setup.cfg` because the :file:`setup.cfg` is easier to use + * Adapt documentation code snippets where needed + * Adapt tests + * Changed the ``deprecated`` to hardcode the ``semver`` package name in the warning. + + Increase coverage to 100% for all non-deprecated APIs + +* :gh:`304`: Support PEP-561 :file:`py.typed`. + + According to the mentioned PEP: + + "Package maintainers who wish to support type checking + of their code MUST add a marker file named :file:`py.typed` + to their package supporting typing." + + Add package_data to :file:`setup.cfg` to include this marker in dist + and whl file. + + + +---- + +.. _semver-3.0.0-dev.1: + +Version 3.0.0-dev.1 +=================== + +:Released: 2020-10-26 +:Maintainer: Tom Schraitle + + +.. _semver-3.0.0-dev.1-deprecations: + +Deprecations +------------ + +* :pr:`290`: For semver 3.0.0-alpha0: + + * Remove anything related to Python2 + * In :file:`tox.ini` and :file:`.travis.yml` + Remove targets py27, py34, py35, and pypy. + Add py38, py39, and nightly (allow to fail) + * In :file:`setup.py` simplified file and remove + ``Tox`` and ``Clean`` classes + * Remove old Python versions (2.7, 3.4, 3.5, and pypy) + from Travis + +* :gh:`234`: In :file:`setup.py` simplified file and remove + ``Tox`` and ``Clean`` classes + + +.. _semver-3.0.0-dev.1-features: + +Features +-------- + +* :pr:`290`: Create semver 3.0.0-alpha0 + + * Update :file:`README.rst`, mention maintenance + branch ``maint/v2``. + * Remove old code mainly used for Python2 compatibility, + adjusted code to support Python3 features. + * Split test suite into separate files under :file:`tests/` + directory + * Adjust and update :file:`setup.py`. Requires Python >=3.6.* + Extract metadata directly from source (affects all the :data:`~semver.__about__.__version__`, + :data:`~semver.__about__.__author__` etc. variables) + +* :gh:`270`: Configure Towncrier (:pr:`273`:) + + * Add :file:`changelog.d/.gitignore` to keep this directory + * Create :file:`changelog.d/README.rst` with some descriptions + * Add :file:`changelog.d/_template.rst` as Towncrier template + * Add ``[tool.towncrier]`` section in :file:`pyproject.toml` + * Add "changelog" target into :file:`tox.ini`. Use it like + :command:`tox -e changelog -- CMD` whereas ``CMD`` is a + Towncrier command. The default :command:`tox -e changelog` + calls Towncrier to create a draft of the changelog file + and output it to stdout. + * Update documentation and add include a new section + "Changelog" included from :file:`changelog.d/README.rst`. + +* :gh:`276`: Document how to create a sublass from :class:`~semver.version.VersionInfo` class + +* :gh:`213`: Add typing information + + +.. _semver-3.0.0-dev.1-bugfixes: + +Bug Fixes +--------- + +* :gh:`291`: Disallow negative numbers in VersionInfo arguments + for ``major``, ``minor``, and ``patch``. + + +.. _semver-3.0.0-dev.1-docs: + +Improved Documentation +---------------------- + +* :pr:`290`: Several improvements in the documentation: + + * New layout to distinguish from the semver2 development line. + * Create new logo. + * Remove any occurances of Python2. + * Describe changelog process with Towncrier. + * Update the release process. + + +.. _semver-3.0.0-dev.1-trivial: + +Trivial/Internal Changes +------------------------ + +* :pr:`290`: Add supported Python versions to :command:`black`. diff --git a/docs/conf.py b/docs/conf.py index ed888361..872f943a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- # # python-semver documentation build configuration file # @@ -16,37 +15,31 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # -import codecs -from datetime import date + import os import re -import sys +from datetime import date +from pathlib import Path -SRC_DIR = os.path.abspath("../src/") -sys.path.insert(0, SRC_DIR) -# from semver import __version__ # noqa: E402 +SRC_DIR = Path(__file__).absolute().parent.parent / "src" YEAR = date.today().year -def read(*parts): - """ - Build an absolute path from *parts* and and return the contents of the - resulting file. Assume UTF-8 encoding. - """ - here = os.path.abspath(os.path.dirname(__file__)) - with codecs.open(os.path.join(here, *parts), "rb", "utf-8") as f: - return f.read() - - -def find_version(*file_paths): +def find_version(path: Path) -> str: """ Build a path from *file_paths* and search for a ``__version__`` string inside. """ - version_file = read(*file_paths) - version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M) + content = Path(path).read_text(encoding="utf-8") + + version_match = re.search( + r"^__version__ = ['\"]([^'\"]*)['\"]", + content, + re.MULTILINE + ) if version_match: return version_match.group(1) + raise RuntimeError("Unable to find version string.") @@ -86,14 +79,14 @@ def find_version(*file_paths): # General information about the project. project = "python-semver" copyright = f"{YEAR}, Kostiantyn Rybnikov and all" -author = "Kostiantyn Rybnikov and all" +author = "Kostiantyn Rybnikov, Tom Schraitle, Sebastien Celles, and others" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -release = find_version("../src/semver/__about__.py") +release = find_version(SRC_DIR / "semver/__about__.py") # The full version, including alpha/beta/rc tags. version = release # .rsplit(u".", 1)[0] @@ -118,9 +111,25 @@ def find_version(*file_paths): # Markup to shorten external links # See https://www.sphinx-doc.org/en/master/usage/extensions/extlinks.html extlinks = { - "gh": ("https://github.com/python-semver/python-semver/issues/%s", "#"), - "pr": ("https://github.com/python-semver/python-semver/pull/%s", "PR #"), + "gh": ("https://github.com/python-semver/python-semver/issues/%s", "#%s"), + "pr": ("https://github.com/python-semver/python-semver/pull/%s", "PR #%s"), +} + +# Link to other projects’ documentation +# See https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html +intersphinx_mapping = { + # Download it from the root with: + # wget -O docs/inventories/python-objects.inv https://docs.python.org/3/objects.inv + "python": ("https://docs.python.org/3", (None, "inventories/python-objects.inv")), + # wget -O docs/inventories/pydantic.inv https://docs.pydantic.dev/latest/objects.inv + "pydantic": ( + "https://docs.pydantic.dev/latest/", + (None, "inventories/pydantic.inv"), + ), } +# Avoid side-effects (namely that documentations local references can +# suddenly resolve to an external location.) +intersphinx_disabled_reftypes = ["*"] # -- Options for HTML output ---------------------------------------------- @@ -158,7 +167,7 @@ def find_version(*file_paths): "github_button": True, #: "github_type": "star", - #: whether to apply a ‘Fork me on Github’ banner + #: whether to apply a "Fork me on Github" banner #: in the top right corner of the page: # "github_banner": True, # @@ -195,6 +204,20 @@ def find_version(*file_paths): html_static_path = ["_static"] html_css_files = ["css/custom.css"] +html_sidebars = { + "**": [ + "about.html", # theme_logo + # 'relations.html', # prev and next + "searchbox.html", # basic/searchbox.html + "navigation.html", # TOC + # 'donate.html', + ] +} + +# Canonical link relation +if os.environ.get("READTHEDOCS_CANONICAL_URL"): + html_baseurl = f"{os.environ['READTHEDOCS_CANONICAL_URL']}" + # html_logo = "logo.svg" # -- Options for HTMLHelp output ------------------------------------------ diff --git a/docs/contribute/add-changelog-entry.rst b/docs/contribute/add-changelog-entry.rst new file mode 100644 index 00000000..30215c76 --- /dev/null +++ b/docs/contribute/add-changelog-entry.rst @@ -0,0 +1,11 @@ +.. _add-changelog: + +Adding a Changelog Entry +======================== + +.. meta:: + :description lang=en: + Adding a changelog entry with Towncrier + +.. include:: ../../changelog.d/README.rst + :start-after: -text-begin- \ No newline at end of file diff --git a/docs/contribute/doc-semver.rst b/docs/contribute/doc-semver.rst new file mode 100644 index 00000000..217b1d21 --- /dev/null +++ b/docs/contribute/doc-semver.rst @@ -0,0 +1,90 @@ +.. _doc: + +Documenting semver +================== + +.. meta:: + :description lang=en: + Documenting semver with type annotations, docstrings, Sphinx directives + +Documenting the features of semver is very important. It gives our developers +an overview what is possible with semver, how it "feels", and how it is +used efficiently. + +.. note:: + + To build the documentation locally use the following command:: + + $ tox -e docs + + The built documentation is available in :file:`docs/_build/html`. + + +A new feature is *not* complete if it isn't proberly documented. A good +documentation includes: + + * **Type annotations** + + This library supports type annotations. Therefore, each function + or method requires types for each arguments and return objects. + Exception of this rule is ``self``. + + * **A docstring** + + Each docstring contains a summary line, a linebreak, an optional + directive (see next item), the description of its arguments in + `Sphinx style`_, and an optional doctest. + The docstring is extracted and reused in the :ref:`api` section. + An appropriate docstring looks like this:: + + def to_tuple(self) -> VersionTuple: + """ + Convert the Version object to a tuple. + + .. versionadded:: 2.10.0 + Renamed ``VersionInfo._astuple`` to ``VersionInfo.to_tuple`` to + make this function available in the public API. + + :return: a tuple with all the parts + + >>> semver.Version(5, 3, 1).to_tuple() + (5, 3, 1, None, None) + + """ + + * **An optional directive** + + If you introduce a new feature, change a function/method, or remove something, + it is a good practice to introduce Sphinx directives into the docstring. + This gives the reader an idea what version is affected by this change. + + The first required argument, ``VERSION``, defines the version when this change + was introduced. You can choose from: + + * ``.. versionadded:: VERSION`` + + Use this directive to describe a new feature. + + * ``.. versionchanged:: VERSION`` + + Use this directive to describe when something has changed, for example, + new parameters were added, changed side effects, different return values, etc. + + * ``.. deprecated:: VERSION`` + + Use this directive when a feature is deprecated. Describe what should + be used instead, if appropriate. + + + Add such a directive *after* the summary line, as shown above. + + * **The documentation** + + A docstring is good, but in most cases it is too short. API documentation + cannot replace good user documentation. + Describe *how* to use your new feature in the documentation. + Here you can give your readers more examples, describe it in a broader + context, or show edge cases. + + +.. _Sphinx style: https://sphinx-rtd-tutorial.rtfd.io/en/latest/docstrings.html diff --git a/docs/contribute/finish-release.rst b/docs/contribute/finish-release.rst new file mode 100644 index 00000000..d1fcce2c --- /dev/null +++ b/docs/contribute/finish-release.rst @@ -0,0 +1,28 @@ +.. _finish-release: + +Finish the Release +================== + +.. meta:: + :description lang=en: + Finish the semver release by creating tags + +1. Create a tag: + + $ git tag -a x.x.x + + It’s recommended to use the generated Tox output from the Changelog. + +2. Push the tag: + + $ git push –tags + +3. In `GitHub Release + page `_ + document the new release. Select the tag from the last step and copy + the content of the tag description into the release description. + +4. Announce it in + https://github.com/python-semver/python-semver/discussions/categories/announcements. + +You’re done! Celebrate! diff --git a/docs/contribute/index.rst b/docs/contribute/index.rst new file mode 100644 index 00000000..30960ace --- /dev/null +++ b/docs/contribute/index.rst @@ -0,0 +1,32 @@ +.. _contributing: + +Contributing to semver +====================== + +.. meta:: + :description lang=en: + Contributing to Python semver + +The semver source code is managed using Git and is hosted on GitHub:: + + git clone git://github.com/python-semver/python-semver + + +.. include:: prerequisites.rst + :start-after: -text-begin- + + +.. toctree:: + :maxdepth: 1 + :caption: More topics + :includehidden: + + report-bugs + run-test-suite + doc-semver + add-changelog-entry + release-procedure + finish-release + + +.. _pull request: https://github.com/python-semver/python-semver/pulls diff --git a/docs/contribute/prerequisites.rst b/docs/contribute/prerequisites.rst new file mode 100644 index 00000000..1084d247 --- /dev/null +++ b/docs/contribute/prerequisites.rst @@ -0,0 +1,19 @@ +Prerequisites +------------- + +.. meta:: + :description lang=en: + Overview of prerequisites for contributing + +.. -text-begin- + +Before you make changes to the code, we would highly appreciate if you +consider the following general requirements: + +* Make sure your code adheres to the `Semantic Versioning`_ specification. + +* Check if your feature is covered by the Semantic Versioning specification. + If not, ask on its GitHub project https://github.com/semver/semver. + + +.. _Semantic Versioning: https://semver.org diff --git a/docs/contribute/release-procedure.rst b/docs/contribute/release-procedure.rst new file mode 100644 index 00000000..b18672fb --- /dev/null +++ b/docs/contribute/release-procedure.rst @@ -0,0 +1,127 @@ +Release Procedure +================= + +.. meta:: + :description lang=en: + Release procedure: prepare and create the release + +The following procedures gives a short overview of what steps are needed +to create a new release. + +These steps are interesting for the release manager only. + + +Prepare the Release +------------------- + +1. Verify that: + + - all issues for a new release are closed: + https://github.com/python-semver/python-semver/issues. + + - all pull requests that should be included in this release are + merged: https://github.com/python-semver/python-semver/pulls. + + - continuous integration for latest build was passing: + https://github.com/python-semver/python-semver/actions. + +2. Create a new branch ``release/``. + +3. If one or several supported Python versions have been removed or + added, verify that the following files have been updated: + + - :file:`setup.cfg` + - :file:`tox.ini` + - :file:`.git/workflows/pythonpackage.yml` + - :file:`.github/workflows/python-testing.yml` + +4. Verify that the version in file :file:`src/semver/__about__.py` + has been updated and follows the `Semver `_ + specification. + +5. Add eventually new contributor(s) to + `CONTRIBUTORS `_. + +6. Check if all changelog entries are created. If some are missing, + `create + them `__. + +7. Show the new draft + `CHANGELOG `_ entry for the latest release with: + + :: + + $ tox -e changelog + + Check the output. If you are not happy, update the files in the + ``changelog.d/`` directory. If everything is okay, build the new + ``CHANGELOG`` with: + + :: + + $ tox -e changelog -- build + +8. Build the documentation and check the output: + + :: + + $ tox -e docs + +9. Commit all changes, push, and create a pull request. + +Create the New Release +---------------------- + +1. Ensure that long description + (`README.rst `_) + can be correctly rendered by Pypi using + ``restview --long-description`` + +2. Clean up your local Git repository. Be careful, as it **will remove + all files** which are not versioned by Git: + + :: + + $ git clean -xfd + + Before you create your distribution files, clean the directory too: + + :: + + $ rm dist/* + +3. Create the distribution files (wheel and source): + + :: + + $ tox -e prepare-dist + +4. Upload the wheel and source to TestPyPI first: + + .. code:: bash + + $ twine upload --repository-url https://test.pypi.org/legacy/ dist/* + + If you have a ``~/.pypirc`` with a ``testpypi`` section, the upload + can be simplified: + + :: + + $ twine upload --repository testpypi dist/* + +5. Check if everything is okay with the wheel. Check also the web site + ``https://test.pypi.org/project//`` + +6. If everything looks fine, merge the pull request. + +7. Upload to PyPI: + + .. code:: bash + + $ git clean -xfd + $ tox -e prepare-dist + $ twine upload dist/* + +8. Go to https://pypi.org/project/semver/ to verify that new version is + online and the page is rendered correctly. + diff --git a/docs/contribute/report-bugs.rst b/docs/contribute/report-bugs.rst new file mode 100644 index 00000000..614098b9 --- /dev/null +++ b/docs/contribute/report-bugs.rst @@ -0,0 +1,22 @@ +.. _report-bugs: + +Reporting Bugs and Asking Questions +----------------------------------- + +.. meta:: + :description lang=en: + Reporting bugs and asking questions about semver + +If you think you have encountered a bug in semver or have an idea for a new +feature? Great! We like to hear from you! + +There are several options to participate: + +* Open a new topic on our `GitHub discussion `_ page. + Tell us our ideas or ask your questions. + +* Look into our GitHub `issues`_ tracker or open a new issue. + + +.. _issues: https://github.com/python-semver/python-semver/issues +.. _gh_discussions: https://github.com/python-semver/python-semver/discussions diff --git a/docs/contribute/run-test-suite.rst b/docs/contribute/run-test-suite.rst new file mode 100644 index 00000000..22f4ebc0 --- /dev/null +++ b/docs/contribute/run-test-suite.rst @@ -0,0 +1,68 @@ +.. _testsuite: + +Running the Test Suite +====================== + +.. meta:: + :description lang=en: + Running the test suite through tox + +We use `pytest`_ and `tox`_ to run tests against all supported Python +versions. All test dependencies are resolved automatically. + +You can decide to run the complete test suite or only part of it: + +* To run all tests, use:: + + $ tox + + If you have not all Python interpreters installed on your system + it will probably give you some errors (``InterpreterNotFound``). + To avoid such errors, use:: + + $ tox --skip-missing-interpreters + + It is possible to use one or more specific Python versions. Use the ``-e`` + option and one or more abbreviations (``py37`` for Python 3.7, + ``py38`` for Python 3.8 etc.):: + + $ tox -e py37 + $ tox -e py37,py38 + + To get a complete list and a short description, run:: + + $ tox -av + +* To run only a specific test, pytest requires the syntax + ``TEST_FILE::TEST_FUNCTION``. + + For example, the following line tests only the function + :func:`test_immutable_major` in the file :file:`test_bump.py` for all + Python versions:: + + $ tox -e py37 -- tests/test_bump.py::test_should_bump_major + + By default, pytest prints only a dot for each test function. To + reveal the executed test function, use the following syntax:: + + $ tox -- -v + + You can combine the specific test function with the ``-e`` option, for + example, to limit the tests for Python 3.7 and 3.8 only:: + + $ tox -e py37,py38 -- tests/test_bump.py::test_should_bump_major + +Our code is checked against formatting, style, type, and docstring issues +(`black`_, `flake8`_, `mypy`_, and `docformatter`_). +It is recommended to run your tests in combination with :command:`checks`, +for example:: + + $ tox -e checks,py37,py38 + + +.. _black: https://black.rtfd.io +.. _docformatter: https://pypi.org/project/docformatter/ +.. _flake8: https://flake8.rtfd.io +.. _mypy: http://mypy-lang.org/ +.. _pytest: http://pytest.org/ +.. _tox: https://tox.rtfd.org/ diff --git a/docs/development.rst b/docs/development.rst deleted file mode 100644 index e582053e..00000000 --- a/docs/development.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../CONTRIBUTING.rst diff --git a/docs/index.rst b/docs/index.rst index 2dce2a50..d0b15882 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,6 +1,10 @@ Semver |version| -- Semantic Versioning ======================================= +.. meta:: + :description lang=en: + Semantic versioning for Python + .. include:: readme.rst @@ -8,13 +12,15 @@ Semver |version| -- Semantic Versioning :maxdepth: 2 :caption: Contents :hidden: + :numbered: - build + build-semver install usage/index migration/index advanced/index - development + contribute/index + version-policy api .. toctree:: @@ -27,11 +33,11 @@ Semver |version| -- Semantic Versioning .. toctree:: :maxdepth: 1 - :caption: Changelogs + :caption: Development :hidden: changelog - changelog-semver2 + changelog-semver3-devel Indices and Tables diff --git a/docs/install.rst b/docs/install.rst index 5404882f..786ecc40 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -1,35 +1,35 @@ Installing semver ================= +.. meta:: + :description lang=en: + Installing semver on the system + Release Policy -------------- As semver uses `Semantic Versioning`_, breaking changes are only introduced in major releases (incremented ``X`` in "X.Y.Z"). +Refer to section :ref:`version-policy` for a general overview. -For users who want to stay with major 2 releases only, add the following version -restriction:: - - semver>=2,<3 +For users who want or need to stay with major 3 releases only, add the +following version restriction (:file:`setup.py`, :file:`requirements.txt`, +or :file:`pyproject.toml`):: -This line avoids surprises. You will get any updates within the major 2 release like -2.11.0 or above. However, you will never get an update for semver 3.0.0. + semver>=3,<4 -Keep in mind, as this line avoids any major version updates, you also will never -get new exciting features or bug fixes. +This line avoids surprises. You will get any updates within the major 3 release like 3.1.x and above. However, you will never get an update for semver 4.0.0. -Same applies for semver v3, if you want to get all updates for the semver v3 -development line, but not a major update to semver v4:: +For users who have to stay with major 2 releases only, use the following line:: - semver>=3,<4 + semver>=2,<3 -You can add this line in your file :file:`setup.py`, :file:`requirements.txt`, -:file:`pyproject.toml`, or any other file that lists your dependencies. -Pip ---- +With Pip +-------- .. code-block:: bash + :name: install-pip pip3 install semver @@ -41,6 +41,19 @@ with an URL and its version: pip3 install git+https://github.com/python-semver/python-semver.git@3.0.0 +With uv +------- + +First, install the :command:`uv` command. Refer to https://docs.astral.sh/uv/getting-started/installation/ for more information. + +Then use the command :command:`uv` to install the package: + +.. code-block:: bash + :name: install-uv + + uv pip install semver + + Linux Distributions ------------------- @@ -102,7 +115,9 @@ openSUSE 1. Enable the ``devel:languages:python`` repository of the Open Build Service:: - $ sudo zypper addrepo --refresh obs://devel:languages:python devel_languages_python + $ sudo zypper addrepo --refresh \ + --name devel_languages_python \ + "https://download.opensuse.org/repositories/devel:/languages:/python/\$releasever" 2. Install the package:: diff --git a/docs/inventories/pydantic.inv b/docs/inventories/pydantic.inv new file mode 100644 index 00000000..455215a7 Binary files /dev/null and b/docs/inventories/pydantic.inv differ diff --git a/docs/inventories/python-objects.inv b/docs/inventories/python-objects.inv new file mode 100644 index 00000000..90bfa0a6 Binary files /dev/null and b/docs/inventories/python-objects.inv differ diff --git a/docs/migration/index.rst b/docs/migration/index.rst index c6af7c05..74c3d866 100644 --- a/docs/migration/index.rst +++ b/docs/migration/index.rst @@ -1,6 +1,9 @@ Migrating to semver3 ==================== +.. meta:: + :description lang=en: + Migrating from semver version 2 to version 3 .. toctree:: :maxdepth: 1 diff --git a/docs/migration/migratetosemver3.rst b/docs/migration/migratetosemver3.rst index f869cad3..2c09fb41 100644 --- a/docs/migration/migratetosemver3.rst +++ b/docs/migration/migratetosemver3.rst @@ -3,8 +3,13 @@ Migrating from semver2 to semver3 ================================= -This document describes the visible differences for +.. meta:: + :description lang=en: + Migrating from semver2 to semver3 + +This section describes the visible differences for users and how your code stays compatible for semver3. +Some changes are backward incompatible. Although the development team tries to make the transition to semver3 as smooth as possible, at some point change @@ -17,10 +22,11 @@ to our :ref:`change-log`. Use Version instead of VersionInfo ---------------------------------- -The :class:`VersionInfo` has been renamed to :class:`Version` -to have a more succinct name. +The :class:`~semver.version.VersionInfo` has been renamed to +:class:`~semver.version.Version` to have a more succinct name. An alias has been created to preserve compatibility but -using the old name has been deprecated. +using the old name has been deprecated and will be removed +in future versions. If you still need the old version, use this line: @@ -34,9 +40,16 @@ Use semver.cli instead of semver -------------------------------- All functions related to CLI parsing are moved to :mod:`semver.cli`. -If you are such functions, like :func:`semver.cmd_bump `, +If you need such functions, like :meth:`~semver.cli.cmd_bump`, import it from :mod:`semver.cli` in the future: .. code-block:: python from semver.cli import cmd_bump + + +Use semver.Version.is_valid instead of semver.Version.isvalid +------------------------------------------------------------- + +The pull request :pr:`284` introduced the method :meth:`~semver.version.Version.is_compatible`. To keep consistency, the development team +decided to rename the :meth:`~semver.Version.isvalid` to :meth:`~semver.Version.is_valid`. diff --git a/docs/migration/replace-deprecated-functions.rst b/docs/migration/replace-deprecated-functions.rst index 9738001c..df0d799a 100644 --- a/docs/migration/replace-deprecated-functions.rst +++ b/docs/migration/replace-deprecated-functions.rst @@ -3,24 +3,35 @@ Replacing Deprecated Functions ============================== +.. meta:: + :description lang=en: + Replacing deprecated functions + .. versionchanged:: 2.10.0 The development team of semver has decided to deprecate certain functions on the module level. The preferred way of using semver is through the - :class:`semver.Version` class. + :class:`~semver.version.Version` class. -The deprecated functions can still be used in version 2.10.0 and above. In version 3 of -semver, the deprecated functions will be removed. +The deprecated functions can still be used in version 2.10.0 and above. +However, in future versions of semver, the deprecated functions will be removed. -The following list shows the deprecated functions and how you can replace -them with code which is compatible for future versions: +Deprecated Module Level Functions +--------------------------------- + +The following list shows the deprecated module level functions and how you can replace +them with code which is compatible for future versions: -* :func:`semver.bump_major`, :func:`semver.bump_minor`, :func:`semver.bump_patch`, :func:`semver.bump_prerelease`, :func:`semver.bump_build` +* :func:`semver.bump_major `, + :func:`semver.bump_minor `, + :func:`semver.bump_patch `, + :func:`semver.bump_prerelease `, + :func:`semver.bump_build ` - Replace them with the respective methods of the :class:`Version ` + Replace them with the respective methods of the :class:`~semver.version.Version` class. - For example, the function :func:`semver.bump_major` is replaced by - :func:`semver.Version.bump_major` and calling the ``str(versionobject)``: + For example, the function :func:`semver.bump_major ` is replaced by + :meth:`Version.bump_major ` and calling the ``str(versionobject)``: .. code-block:: python @@ -31,9 +42,9 @@ them with code which is compatible for future versions: Likewise with the other module level functions. -* :func:`semver.finalize_version` +* :func:`semver.finalize_version ` - Replace it with :func:`semver.Version.finalize_version`: + Replace it with :meth:`Version.finalize_version `: .. code-block:: python @@ -42,7 +53,7 @@ them with code which is compatible for future versions: >>> s1 == s2 True -* :func:`semver.format_version` +* :func:`semver.format_version ` Replace it with ``str(versionobject)``: @@ -53,9 +64,9 @@ them with code which is compatible for future versions: >>> s1 == s2 True -* :func:`semver.max_ver` +* :func:`semver.max_ver ` - Replace it with ``max(version1, version2, ...)`` or ``max([version1, version2, ...])``: + Replace it with ``max(version1, version2, ...)`` or ``max([version1, version2, ...])`` and a ``key``: .. code-block:: python @@ -64,9 +75,9 @@ them with code which is compatible for future versions: >>> s1 == s2 True -* :func:`semver.min_ver` +* :func:`semver.min_ver ` - Replace it with ``min(version1, version2, ...)`` or ``min([version1, version2, ...])``: + Replace it with ``min(version1, version2, ...)`` or ``min([version1, version2, ...])`` and a ``key``: .. code-block:: python @@ -75,10 +86,10 @@ them with code which is compatible for future versions: >>> s1 == s2 True -* :func:`semver.parse` +* :func:`semver.parse ` - Replace it with :func:`semver.Version.parse` and - :func:`semver.Version.to_dict`: + Replace it with :meth:`Version.parse ` and call + :meth:`Version.to_dict `: .. code-block:: python @@ -87,9 +98,9 @@ them with code which is compatible for future versions: >>> v1 == v2 True -* :func:`semver.parse_version_info` +* :func:`semver.parse_version_info ` - Replace it with :func:`semver.Version.parse`: + Replace it with :meth:`Version.parse `: .. code-block:: python @@ -98,9 +109,9 @@ them with code which is compatible for future versions: >>> v1 == v2 True -* :func:`semver.replace` +* :func:`semver.replace ` - Replace it with :func:`semver.Version.replace`: + Replace it with :meth:`Version.replace `: .. code-block:: python @@ -108,3 +119,22 @@ them with code which is compatible for future versions: >>> s2 = str(Version.parse('1.2.3').replace(major=2, patch=10)) >>> s1 == s2 True + + +Deprected Version methods +------------------------- + +The following list shows the deprecated methods of the :class:`~semver.version.Version` class. + +* :meth:`Version.isvalid ` + + Replace it with :meth:`Version.is_valid `: + + +Deprecated Classes +------------------ + +* :class:`VersionInfo ` + + The class was renamed to :class:`~semver.version.Version`. + Don't use the old name anymore. \ No newline at end of file diff --git a/docs/pysemver.rst b/docs/pysemver.rst index b0896eea..cea32e6c 100644 --- a/docs/pysemver.rst +++ b/docs/pysemver.rst @@ -3,6 +3,10 @@ pysemver |version| ================== +.. meta:: + :description lang=en: + Commandline tool pysemver describing all commands and options + Synopsis -------- diff --git a/docs/requirements.txt b/docs/requirements.txt index 1186b4c3..540348a5 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,4 +1,5 @@ -# requirements file for documentation -# sphinx -sphinx-argparse -sphinx-autodoc-typehints +# Sphinx requirements file for documentation +# Required in .readthedocs.yaml +sphinx==7.0.1 +sphinx-argparse==0.4.0 +sphinx-autodoc-typehints==1.24.0 diff --git a/docs/usage/access-parts-of-a-version.rst b/docs/usage/access-parts-of-a-version.rst index 4eb9274f..d8a15cb4 100644 --- a/docs/usage/access-parts-of-a-version.rst +++ b/docs/usage/access-parts-of-a-version.rst @@ -3,6 +3,10 @@ Accessing Parts of a Version Through Names ========================================== +.. meta:: + :description lang=en: + Accessing parts of a version through names + The :class:`~semver.version.Version` class contains attributes to access the different parts of a version: @@ -21,7 +25,7 @@ parts of a version: 'build.4' However, the attributes are read-only. You cannot change any of the above attributes. -If you do, you get an :py:exc:`AttributeError`:: +If you do, you get an :py:exc:`python:AttributeError`:: >>> v.minor = 5 Traceback (most recent call last): diff --git a/docs/usage/access-parts-through-index.rst b/docs/usage/access-parts-through-index.rst index a261fda4..cc6b0c2b 100644 --- a/docs/usage/access-parts-through-index.rst +++ b/docs/usage/access-parts-through-index.rst @@ -3,11 +3,15 @@ Accessing Parts Through Index Numbers ===================================== +.. meta:: + :description lang=en: + Accessing parts through index numbers + .. versionadded:: 2.10.0 Another way to access parts of a version is to use an index notation. The underlying :class:`~semver.version.Version` object allows to access its data through -the magic method :func:`~semver.version.Version.__getitem__`. +the magic method :meth:`~semver.version.Version.__getitem__`. For example, the ``major`` part can be accessed by index number 0 (zero). Likewise the other parts: @@ -33,7 +37,7 @@ Or, as an alternative, you can pass a :func:`slice` object: >>> ver[sl] (10, 3, 2) -Negative numbers or undefined parts raise an :py:exc:`IndexError` exception: +Negative numbers or undefined parts raise an :py:exc:`python:IndexError` exception: .. code-block:: python diff --git a/docs/usage/check-compatible-semver-version.rst b/docs/usage/check-compatible-semver-version.rst new file mode 100644 index 00000000..4cf47908 --- /dev/null +++ b/docs/usage/check-compatible-semver-version.rst @@ -0,0 +1,99 @@ +Checking for a Compatible Semver Version +======================================== + +.. meta:: + :description lang=en: + Check for a compatible semver version + +To check if a *change* from a semver version ``a`` to a semver +version ``b`` is *compatible* according to semver rule, use the method +:meth:`~semver.version.Version.is_compatible`. + +The expression ``a.is_compatible(b) is True`` if one of the following +statements is true: + +* both versions are equal, or +* both majors are equal and higher than 0. The same applies for both + minor parts. Both pre-releases are equal, or +* both majors are equal and higher than 0. The minor of ``b``'s + minor version is higher then ``a``'s. Both pre-releases are equal. + +In all other cases, the result is false. + +Keep in mind, the method *does not* check patches! + + +* Two different majors: + + .. code-block:: python + + >>> a = Version(1, 1, 1) + >>> b = Version(2, 0, 0) + >>> a.is_compatible(b) + False + >>> b.is_compatible(a) + False + +* Two different minors: + + .. code-block:: python + + >>> a = Version(1, 1, 0) + >>> b = Version(1, 0, 0) + >>> a.is_compatible(b) + False + >>> b.is_compatible(a) + True + +* The same two majors and minors: + + .. code-block:: python + + >>> a = Version(1, 1, 1) + >>> b = Version(1, 1, 0) + >>> a.is_compatible(b) + True + >>> b.is_compatible(a) + True + +* Release and pre-release: + + .. code-block:: python + + >>> a = Version(1, 1, 1) + >>> b = Version(1, 0, 0,'rc1') + >>> a.is_compatible(b) + False + >>> b.is_compatible(a) + False + +* Different pre-releases: + + .. code-block:: python + + >>> a = Version(1, 0, 0, 'rc1') + >>> b = Version(1, 0, 0, 'rc2') + >>> a.is_compatible(b) + False + >>> b.is_compatible(a) + False + +* Identical pre-releases: + + .. code-block:: python + + >>> a = Version(1, 0, 0,'rc1') + >>> b = Version(1, 0, 0,'rc1') + >>> a.is_compatible(b) + True + +* All major zero versions are incompatible with anything but itself: + + .. code-block:: python + + >>> Version(0, 1, 0).is_compatible(Version(0, 1, 1)) + False + + # Only identical versions are compatible for major zero versions: + >>> Version(0, 1, 0).is_compatible(Version(0, 1, 0)) + True diff --git a/docs/usage/check-valid-semver-version.rst b/docs/usage/check-valid-semver-version.rst index 7aa9615b..2e016c6d 100644 --- a/docs/usage/check-valid-semver-version.rst +++ b/docs/usage/check-valid-semver-version.rst @@ -1,12 +1,16 @@ Checking for a Valid Semver Version =================================== +.. meta:: + :description lang=en: + Checking for a valid semver version + If you need to check a string if it is a valid semver version, use the -classmethod :func:`Version.isvalid `: +classmethod :meth:`~semver.version.Version.is_valid`: .. code-block:: python - >>> Version.isvalid("1.0.0") + >>> Version.is_valid("1.0.0") True - >>> Version.isvalid("invalid") + >>> Version.is_valid("invalid") False diff --git a/docs/usage/compare-versions-through-expression.rst b/docs/usage/compare-versions-through-expression.rst index 28fad671..5b05a123 100644 --- a/docs/usage/compare-versions-through-expression.rst +++ b/docs/usage/compare-versions-through-expression.rst @@ -1,8 +1,12 @@ Comparing Versions through an Expression ======================================== +.. meta:: + :description lang=en: + Comparing versions through an expression + If you need a more fine-grained approach of comparing two versions, -use the :func:`semver.match` function. It expects two arguments: +use the :meth:`~semver.version.Version.match` function. It expects two arguments: 1. a version string 2. a match expression @@ -20,9 +24,9 @@ That gives you the following possibilities to express your condition: .. code-block:: python - >>> semver.match("2.0.0", ">=1.0.0") + >>> Version.parse("2.0.0").match(">=1.0.0") True - >>> semver.match("1.0.0", ">1.0.0") + >>> Version.parse("1.0.0").match(">1.0.0") False If no operator is specified, the match expression is interpreted as a @@ -33,7 +37,7 @@ handle both cases: .. code-block:: python - >>> semver.match("2.0.0", "2.0.0") + >>> Version.parse("2.0.0").match("2.0.0") True - >>> semver.match("1.0.0", "3.5.1") + >>> Version.parse("1.0.0").match("3.5.1") False diff --git a/docs/usage/compare-versions.rst b/docs/usage/compare-versions.rst index b42ba1a7..714cf8ba 100644 --- a/docs/usage/compare-versions.rst +++ b/docs/usage/compare-versions.rst @@ -1,11 +1,15 @@ Comparing Versions ================== +.. meta:: + :description lang=en: + Comparing versions with semver.compare and the Version class + To compare two versions depends on your type: * **Two strings** - Use :func:`semver.compare`:: + Use :func:`semver.compare `:: >>> semver.compare("1.0.0", "2.0.0") -1 @@ -17,7 +21,7 @@ To compare two versions depends on your type: The return value is negative if ``version1 < version2``, zero if ``version1 == version2`` and strictly positive if ``version1 > version2``. -* **Two** :class:`Version ` **instances** +* **Two** :class:`~semver.version.Version` **instances** Use the specific operator. Currently, the operators ``<``, ``<=``, ``>``, ``>=``, ``==``, and ``!=`` are supported:: @@ -29,9 +33,9 @@ To compare two versions depends on your type: >>> v1 > v2 False -* **A** :class:`Version ` **type and a** :func:`tuple` **or** :func:`list` +* **A** :class:`~semver.version.Version` **type and a** :func:`tuple` **or** :func:`list` - Use the operator as with two :class:`Version ` types:: + Use the operator as with two :class:`~semver.version.Version` types:: >>> v = Version.parse("3.4.5") >>> v > (1, 0) @@ -46,7 +50,7 @@ To compare two versions depends on your type: >>> [3, 5] > v True -* **A** :class:`Version ` **type and a** :func:`str` +* **A** :class:`~semver.version.Version` **type and a** :func:`str` You can use also raw strings to compare:: @@ -62,14 +66,14 @@ To compare two versions depends on your type: >>> "3.5.0" > v True - However, if you compare incomplete strings, you get a :py:exc:`ValueError` exception:: + However, if you compare incomplete strings, you get a :py:exc:`python:ValueError` exception:: >>> v > "1.0" Traceback (most recent call last): ... ValueError: 1.0 is not valid SemVer string -* **A** :class:`Version ` **type and a** :func:`dict` +* **A** :class:`~semver.version.Version` **type and a** :func:`dict` You can also use a dictionary. In contrast to strings, you can have an "incomplete" version (as the other parts are set to zero):: @@ -82,7 +86,7 @@ To compare two versions depends on your type: >>> dict(major=1) < v True - If the dictionary contains unknown keys, you get a :py:exc:`TypeError` exception:: + If the dictionary contains unknown keys, you get a :py:exc:`python:TypeError` exception:: >>> v > dict(major=1, unknown=42) Traceback (most recent call last): diff --git a/docs/usage/convert-version-into-different-types.rst b/docs/usage/convert-version-into-different-types.rst index 976283d8..c75992f1 100644 --- a/docs/usage/convert-version-into-different-types.rst +++ b/docs/usage/convert-version-into-different-types.rst @@ -3,23 +3,28 @@ Converting a Version instance into Different Types ================================================== -Sometimes it is needed to convert a :class:`Version ` instance into +.. meta:: + :description lang=en: + Converting a version instance into different types + + +Sometimes it is needed to convert a :class:`~semver.version.Version` instance into a different type. For example, for displaying or to access all parts. -It is possible to convert a :class:`Version ` instance: +It is possible to convert a :class:`~semver.version.Version` instance: * Into a string with the builtin function :func:`str`:: >>> str(Version.parse("3.4.5-pre.2+build.4")) '3.4.5-pre.2+build.4' -* Into a dictionary with :func:`to_dict `:: +* Into a dictionary with :meth:`~semver.version.Version.to_dict`:: >>> v = Version(major=3, minor=4, patch=5) >>> v.to_dict() - OrderedDict([('major', 3), ('minor', 4), ('patch', 5), ('prerelease', None), ('build', None)]) + {'major': 3, 'minor': 4, 'patch': 5, 'prerelease': None, 'build': None} -* Into a tuple with :func:`to_tuple `:: +* Into a tuple with :meth:`~semver.version.Version.to_tuple`:: >>> v = Version(major=5, minor=4, patch=2) >>> v.to_tuple() diff --git a/docs/usage/create-a-version.rst b/docs/usage/create-a-version.rst index 3acb4c03..06165235 100644 --- a/docs/usage/create-a-version.rst +++ b/docs/usage/create-a-version.rst @@ -1,9 +1,13 @@ Creating a Version ================== +.. meta:: + :description lang=en: + Creating a version using different methods + .. versionchanged:: 3.0.0 - The former :class:`~semver.version.VersionInfo` + The former :class:`~semver.version.VersionInfo` class has been renamed to :class:`~semver.version.Version`. The preferred way to create a new version is with the class @@ -15,7 +19,7 @@ The preferred way to create a new version is with the class create a version with module level functions. However, module level functions are marked as *deprecated* since version 2.x.y now. - These functions will be removed in semver 3.1.0. + These functions will be removed. For details, see the sections :ref:`sec_replace_deprecated_functions` and :ref:`sec_display_deprecation_warnings`. @@ -41,7 +45,7 @@ A :class:`~semver.version.Version` instance can be created in different ways: Version(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') Keep in mind, the ``major``, ``minor``, ``patch`` parts has to - be positive integers or strings: + be positive integers or strings, otherwise a :py:exc:`python:ValueError` is raised: >>> d = {'major': -3, 'minor': 4, 'patch': 5, 'prerelease': 'pre.2', 'build': 'build.4'} >>> Version(**d) @@ -50,7 +54,7 @@ A :class:`~semver.version.Version` instance can be created in different ways: ValueError: 'major' is negative. A version can only be positive. As a minimum requirement, your dictionary needs at least the ``major`` - key, others can be omitted. You get a ``TypeError`` if your + key, others can be omitted. You get a :py:exc:`python:TypeError` if your dictionary contains invalid keys. Only the keys ``major``, ``minor``, ``patch``, ``prerelease``, and ``build`` are allowed. @@ -87,12 +91,12 @@ Depending on your use case, the following methods are available: * From a string into a dictionary - To access individual parts, you can use the function :func:`semver.parse`:: + To access individual parts, you can use the function :func:`~semver._deprecated.parse`:: >>> semver.parse("3.4.5-pre.2+build.4") - OrderedDict([('major', 3), ('minor', 4), ('patch', 5), ('prerelease', 'pre.2'), ('build', 'build.4')]) + {'major': 3, 'minor': 4, 'patch': 5, 'prerelease': 'pre.2', 'build': 'build.4'} - If you pass an invalid version string you will get a :py:exc:`ValueError`:: + If you pass an invalid version string you will get a :py:exc:`python:ValueError`:: >>> semver.parse("1.2") Traceback (most recent call last): diff --git a/docs/usage/determine-version-equality.rst b/docs/usage/determine-version-equality.rst index 211743c9..ba4125ed 100644 --- a/docs/usage/determine-version-equality.rst +++ b/docs/usage/determine-version-equality.rst @@ -1,6 +1,10 @@ Determining Version Equality ============================ +.. meta:: + :description lang=en: + Determining verison equality + Version equality means for semver, that major, minor, patch, and prerelease parts are equal in both versions you compare. The build part is ignored. For example:: diff --git a/docs/usage/get-min-and-max-of-multiple-versions.rst b/docs/usage/get-min-and-max-of-multiple-versions.rst index 266ee50b..35ba77b4 100644 --- a/docs/usage/get-min-and-max-of-multiple-versions.rst +++ b/docs/usage/get-min-and-max-of-multiple-versions.rst @@ -3,13 +3,17 @@ Getting Minimum and Maximum of Multiple Versions ================================================ +.. meta:: + :description lang=en: + Getting minimum and maximum of multiple versions + .. versionchanged:: 2.10.2 The functions :func:`semver.max_ver` and :func:`semver.min_ver` are deprecated in favor of their builtin counterparts :func:`max` and :func:`min`. -Since :class:`Version ` implements -:func:`__gt__ ` and -:func:`__lt__ `, it can be used with builtins requiring: +Since :class:`~semver.version.Version` implements +:meth:`~semver.version.Version.__gt__` and +:meth:`~semver.version.Version.__lt__`, it can be used with builtins requiring: .. code-block:: python @@ -19,7 +23,7 @@ Since :class:`Version ` implements Version(major=0, minor=1, patch=0, prerelease=None, build=None) Incidentally, using :func:`map`, you can get the min or max version of any number of versions of the same type -(convertible to :class:`Version `). +(convertible to :class:`~semver.version.Version`). For example, here are the maximum and minimum versions of a list of version strings: @@ -40,12 +44,3 @@ And the same can be done with tuples: (0, 4, 99, None, None) For dictionaries, it is very similar to finding the max version tuple: see :ref:`sec.convert.versions`. - -The "old way" with :func:`semver.max_ver` or :func:`semver.min_ver` is still available, but not recommended: - -.. code-block:: python - - >>> semver.max_ver("1.0.0", "2.0.0") - '2.0.0' - >>> semver.min_ver("1.0.0", "2.0.0") - '1.0.0' diff --git a/docs/usage/increase-parts-of-a-version_prereleases.rst b/docs/usage/increase-parts-of-a-version_prereleases.rst index 98283937..845f2290 100644 --- a/docs/usage/increase-parts-of-a-version_prereleases.rst +++ b/docs/usage/increase-parts-of-a-version_prereleases.rst @@ -1,11 +1,13 @@ +.. _increase-parts-of-a-version: + Increasing Parts of a Version Taking into Account Prereleases ============================================================= .. versionadded:: 2.10.0 - Added :func:`Version.next_version `. + Added :meth:`~semver.version.Version.next_version`. If you want to raise your version and take prereleases into account, -the function :func:`next_version ` +the function :meth:`~semver.version.Version.next_version` would perhaps a better fit. diff --git a/docs/usage/index.rst b/docs/usage/index.rst index ddfc2284..4b8e3fc9 100644 --- a/docs/usage/index.rst +++ b/docs/usage/index.rst @@ -8,6 +8,7 @@ Using semver create-a-version parse-version-string check-valid-semver-version + check-compatible-semver-version access-parts-of-a-version access-parts-through-index replace-parts-of-a-version diff --git a/docs/usage/parse-version-string.rst b/docs/usage/parse-version-string.rst index 0a39c8a3..0cf02650 100644 --- a/docs/usage/parse-version-string.rst +++ b/docs/usage/parse-version-string.rst @@ -2,7 +2,7 @@ Parsing a Version String ======================== "Parsing" in this context means to identify the different parts in a string. -Use the function :func:`Version.parse `:: +Use the function :meth:`~semver.version.Version.parse`:: >>> Version.parse("3.4.5-pre.2+build.4") Version(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') diff --git a/docs/usage/raise-parts-of-a-version.rst b/docs/usage/raise-parts-of-a-version.rst index cc62fffb..101a8c35 100644 --- a/docs/usage/raise-parts-of-a-version.rst +++ b/docs/usage/raise-parts-of-a-version.rst @@ -1,18 +1,33 @@ Raising Parts of a Version ========================== +.. note:: + + Keep in mind, by default, "raising" the pre-release for a version without an existing + prerelease part, only will make your complete version *lower* than before. + + For example, having version ``1.0.0`` and raising the pre-release + will lead to ``1.0.0-rc.1``, but ``1.0.0-rc.1`` is smaller than ``1.0.0``. + + To avoid this, set `bump_when_empty=True` in the + :meth:`~semver.version.Version.bump_prerelease` method, or by using the + method :meth:`~semver.version.Version.next_version` + in section :ref:`increase-parts-of-a-version`. + + The ``semver`` module contains the following functions to raise parts of a version: -* :func:`Version.bump_major `: raises the major part and set all other parts to +* :meth:`~semver.version.Version.bump_major`: raises the major part and set all other parts to zero. Set ``prerelease`` and ``build`` to ``None``. -* :func:`Version.bump_minor `: raises the minor part and sets ``patch`` to zero. +* :meth:`~semver.version.Version.bump_minor`: raises the minor part and sets ``patch`` to zero. Set ``prerelease`` and ``build`` to ``None``. -* :func:`Version.bump_patch `: raises the patch part. Set ``prerelease`` and +* :meth:`~semver.version.Version.bump_patch`: raises the patch part. Set ``prerelease`` and ``build`` to ``None``. -* :func:`Version.bump_prerelease `: raises the prerelease part and set +* :meth:`~semver.version.Version.bump_prerelease`: raises the prerelease part and set ``build`` to ``None``. -* :func:`Version.bump_build `: raises the build part. +* :meth:`~semver.version.Version.bump_build`: raises the build part. + .. code-block:: python @@ -28,3 +43,39 @@ a version: '3.4.5-pre.2+build.5' Likewise the module level functions :func:`semver.bump_major`. + +For the methods :meth:`~semver.version.Version.bump_prerelease` +and :meth:`~semver.version.Version.bump_build` it's possible to pass an empty string or ``None``. +However, it gives different results: + +.. code-block:: python + + >>> str(Version.parse("3.4.5").bump_prerelease('')) + '3.4.5-1' + >>> str(Version.parse("3.4.5").bump_prerelease(None)) + '3.4.5-rc.1' + +An empty string removes any prefix whereas ``None`` is the same as calling +the method without any argument. + +If you already have a prerelease, the argument for the method +is not taken into account: + +.. code-block:: python + + >>> str(Version.parse("3.4.5-rc.1").bump_prerelease(None)) + '3.4.5-rc.2' + >>> str(Version.parse("3.4.5-rc.1").bump_prerelease('')) + '3.4.5-rc.2' + +To ensure correct ordering, we append `.0` to the last prerelease identifier +if it's not numeric. This prevents cases where `rc9` would incorrectly sort +lower than `rc10` (non-numeric identifiers are compared alphabetically): + +.. code-block:: python + + >>> str(Version.parse("3.4.5-rc9").bump_prerelease()) + '3.4.5-rc9.0' + >>> str(Version.parse("3.4.5-rc.9").bump_prerelease()) + '3.4.5-rc.10' + diff --git a/docs/usage/replace-parts-of-a-version.rst b/docs/usage/replace-parts-of-a-version.rst index b6c38865..57ab65e9 100644 --- a/docs/usage/replace-parts-of-a-version.rst +++ b/docs/usage/replace-parts-of-a-version.rst @@ -4,25 +4,14 @@ Replacing Parts of a Version ============================ If you want to replace different parts of a version, but leave other parts -unmodified, use the function :func:`replace `: - -* From a :class:`Version ` instance:: +unmodified, use the function :meth:`~semver.version.Version.replace`: >>> version = semver.Version.parse("1.4.5-pre.1+build.6") >>> version.replace(major=2, minor=2) Version(major=2, minor=2, patch=5, prerelease='pre.1', build='build.6') -* From a version string:: - - >>> semver.replace("1.4.5-pre.1+build.6", major=2) - '2.4.5-pre.1+build.6' - If you pass invalid keys you get an exception:: - >>> semver.replace("1.2.3", invalidkey=2) - Traceback (most recent call last): - ... - TypeError: replace() got 1 unexpected keyword argument(s): invalidkey >>> version = semver.Version.parse("1.4.5-pre.1+build.6") >>> version.replace(invalidkey=2) Traceback (most recent call last): diff --git a/docs/usage/semver-version.rst b/docs/usage/semver-version.rst index 8eeab62f..c771a006 100644 --- a/docs/usage/semver-version.rst +++ b/docs/usage/semver-version.rst @@ -4,4 +4,4 @@ Getting the Version of semver To know the version of semver itself, use the following construct:: >>> semver.__version__ - '3.0.0-dev.4' + '3.0.4' diff --git a/docs/version-policy.rst b/docs/version-policy.rst new file mode 100644 index 00000000..f0c423f8 --- /dev/null +++ b/docs/version-policy.rst @@ -0,0 +1,52 @@ +.. _version-policy: + +Version Policy +============== + +.. |MAINT| replace:: ``maint/v2`` +.. _MAINT: https://github.com/python-semver/python-semver/tree/maint/v2 +.. |CHANGELOG| replace:: ``Changelog`` +.. _CHANGELOG: https://github.com/python-semver/python-semver/blob/maint/v2/CHANGELOG.rst + +The move from v2 to v3 introduced many changes and deprecated module functions. +The main functionality is handled by the :class:`~semver.version.Version` class +now. Find more information in the section :ref:`semver2-to-3`. + + +semver Version 2 +---------------- + +Active development of major version 2 has stopped. No new features nor +backports will be integrated. +We recommend to upgrade your workflow to Python 3 to gain support, +bugfixes, and new features. + +If you still need this old version, use the |MAINT|_ branch. There you +can look for the |CHANGELOG|_ if you need some details about the history. + + +semver Version 3 +---------------- + +We will not intentionally make breaking changes in minor releases of V3. + +Methods marked as ``deprecated`` raise a warning message when used from the +:py:mod:`python:warnings` module. +Refer to section :ref:`sec_display_deprecation_warnings` to get more information about how to customize it. +Check section :ref:`sec_replace_deprecated_functions` to make your code +ready for future major releases. + + +semver Version 3 and beyond +--------------------------- + +Methods that were marked as deprecated will be very likely be removed. + + +Support for Python versions +--------------------------- + +This project will drop support for a Python version when the +following conditions are met: + +* The Python version has reached `EOL `_. diff --git a/pyproject.toml b/pyproject.toml index d288e68e..60481679 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,15 +7,138 @@ [build-system] requires = [ - # sync with setup.py until we discard non-pep-517/518 - "setuptools", - "setuptools-scm", - "wheel", - "build", + "setuptools>=61", + # "setuptools-scm>=8", ] build-backend = "setuptools.build_meta" +[project] +requires-python = ">=3.7" +name = "semver" +description = "Python helper for Semantic Versioning (https://semver.org)" +readme = "README.rst" +# PEP 639 +# licence = "BSD-2-Clause" +# readme.content-type = "text/x-rst" +license = { file = "LICENSE.txt" } +authors = [ + {name = "Kostiantyn Rybnikov", email = "k-bx@k-bx.com"}, + {name = "Tom Schraitle", email = "toms@suse.de"}, +] +maintainers = [ + {name = "Tom Schraitle", email = "toms@suse.de"}, + {name = "Sebastien Celles", email = "s.celles@gmail.com"}, +] +dynamic = ["version",] +classifiers = [ + "Environment :: Web Environment", + "Intended Audience :: Developers", + "Development Status :: 5 - Production/Stable", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Topic :: Software Development :: Libraries :: Python Modules", +] + + +[project.urls] +"GitHub Homepage" = "https://github.com/python-semver/python-semver" +Changelog = "https://python-semver.readthedocs.io/en/latest/changelog.html" +Documentation = "https://python-semver.rtfd.io" +Releases = "https://github.com/python-semver/python-semver/releases" +"Bug Tracker" = "https://github.com/python-semver/python-semver/issues" + + +[project.scripts] +pysemver = "semver.cli:main" + + +[dependency-groups] +# See https://github.com/astral-sh/uv/issues/5632 +# See https://peps.python.org/pep-0735/ +typing = [ + "mypy", + "pyright", +] +formatting = [ + "black", + "ruff", + "docformatter", +] +test = [ + "tox", + "pytest", + "pytest-cov", + +] +publish = [ + "twine", + # pyproject-build is required for building the package + "build", +] +changelog = [ + "towncrier", +] +docs = [ + # Required in .readthedocs.yaml + "sphinx", # ==7.0.1 + "sphinx-argparse", # ==0.4.0 + "sphinx-autodoc-typehints", # ==1.24.0 + "restview", +] +devel = [ + {include-group = "typing"}, + {include-group = "formatting"}, + {include-group = "test"}, + {include-group = "changelog"}, + {include-group = "docs"}, + {include-group = "publish"}, +] +# Only needed when using GitHub Actions +gh-action = [ + {include-group = "devel"}, + "tox-gh-actions", +] + +# --- Setuptools configuration --- +[tool.setuptools] +package-dir = {"" = "src"} +package-data = { "semver" = ["py.typed"] } +include-package-data = true + + +[tool.setuptools.packages.find] +# Is that still necessary? +# list of folders that contain the packages (["."] by default) +where = ["src"] + + +[tool.setuptools.dynamic] +version = {attr = "semver.__about__.__version__"} + + +# --------------------------------- +[tool.setuptools_scm] +version_scheme = "post-release" +local_scheme = "dirty-tag" + + +[tool.mypy] +# ignore_missing_imports = true +check_untyped_defs = true +show_error_codes = true +# strict = true +pretty = true + [tool.black] line-length = 88 @@ -23,6 +146,15 @@ target-version = ['py37', 'py38', 'py39', 'py310', 'py311'] # diff = true +[tool.docformatter] +wrap-summaries = 80 +close-quotes-on-newline = true +# make-summary-multi-line = true +black = true +pre-summary-newline = true +recursive = true + + [tool.towncrier] package = "semver" package_dir = "src" @@ -53,4 +185,7 @@ name = "Features" name = "Removals" [tool.towncrier.fragment.trivial] -name = "Trivial/Internal Changes" +name = "Trivial Changes" + +[tool.towncrier.fragment.internal] +name = "Internal Changes" diff --git a/release-procedure.md b/release-procedure.md index d6c1701e..f6c7c377 100644 --- a/release-procedure.md +++ b/release-procedure.md @@ -3,115 +3,142 @@ The following procedures gives a short overview of what steps are needed to create a new release. -## Prepare the Release +## Prepare your environment -1. Verify: +1. Create your API tokens: - * all issues for a new release are closed: . + 1. From the [PyPI test server](https://test.pypi.org/manage/account/token/). - * that all pull requests that should be included in this release are merged: . + 1. From the official [PyPI server](https://test.pypi.org/manage/account/token/). - * that continuous integration for latest build was passing: - . + 1. Save both tokens it in a safe place like your password manager. + +1. Create a file `~/.pypirc` with file mode 0600 and the following minimal content: + + # Protect the file with chmod 0600 ~/.pypirc + [distutils] + index-servers = + test-semver + semver + + [test-semver] + repository = https://test.pypi.org/legacy/ + username = __token__ + password = + + [semver] + repository = https://pypi.org/legacy/ + username = __token__ + password = + +1. Install uv as shown in Astral's [Installing uv](https://docs.astral.sh/uv/getting-started/installation/) documentation. + +1. Update the project's environment: + + uv sync --group devel + +1. Activate your environment: + + source .venv/bin/activate + +## Prepare the Release 1. Create a new branch `release/`. -1. If one or several supported Python versions have been removed or added, verify that the 3 following files have been updated: - * `setup.cfg` +1. If one or several supported **Python** versions have been removed or added, verify that the following files have been updated: + * `pyproject.toml` (look into the key `project.requires-python` and `project.classifiers`) * `tox.ini` * `.git/workflows/pythonpackage.yml` + * `CITATION.cff` + +1. Verify that: -1. Verify that the version has been updated and follow - : + * the version has been updated and follow : + * `src/semver/__about__.py` + * `docs/usage/semver-version.rst` + + * all issues for a new release are closed: . - * `src/semver/__about__.py` - * `docs/usage.rst` + * all pull requests that should be included in this release are merged: . + + * continuous integration for latest build was passing: + . 1. Add eventually new contributor(s) to [CONTRIBUTORS](https://github.com/python-semver/python-semver/blob/master/CONTRIBUTORS). +1. Create the changelog: -1. Check if all changelog entries are created. If some are missing, [create them](https://python-semver.readthedocs.io/en/latest/development.html#adding-a-changelog-entry). + 1. Check if all changelog entries are created. If some are missing, [create them](https://python-semver.readthedocs.io/en/latest/development.html#adding-a-changelog-entry). -1. Show the new draft [CHANGELOG](https://github.com/python-semver/python-semver/blob/master/CHANGELOG.rst) entry for the latest release with: + 1. Show the new draft [CHANGELOG](https://github.com/python-semver/python-semver/blob/master/CHANGELOG.rst) entry for the latest release with: - $ tox -e changelog + uvx tox r -e changelog - Check the output. If you are not happy, update the files in the - `changelog.d/` directory. - If everything is okay, build the new `CHANGELOG` with: + 1. Check the output. If you are not happy, update the files in the + `changelog.d/` directory. + If everything is okay, build the new `CHANGELOG` with: - $ tox -e changelog -- build + uvx tox r -e changelog -- build 1. Build the documentation and check the output: - $ tox -e docs + uvx tox r -e docs 1. Commit all changes, push, and create a pull request. ## Create the New Release -1. Ensure that long description ([README.rst](https://github.com/python-semver/python-semver/blob/master/README.rst)) can be correctly rendered by Pypi using `restview --long-description` +1. Ensure that long description ([README.rst](https://github.com/python-semver/python-semver/blob/master/README.rst)) can be correctly rendered by Pypi using `uvx restview -b README.rst` 1. Clean up your local Git repository. Be careful, as it **will remove all files** which are not versioned by Git: - $ git clean -xfd + git clean -xfd Before you create your distribution files, clean the directory too: - $ rm dist/* + rm dist/* 1. Create the distribution files (wheel and source): - $ tox -e prepare-dist + uvx tox r -e prepare-dist 1. Upload the wheel and source to TestPyPI first: - ```bash - $ twine upload --repository-url https://test.pypi.org/legacy/ dist/* - ``` + twine upload --verbose --repository test-semver dist/* - If you have a `~/.pypirc` with a `testpypi` section, the upload can be - simplified: - - $ twine upload --repository testpypi dist/* + (Normally you would do it with `uv publish`, but for some unknown reason it didn't work for me.) 1. Check if everything is okay with the wheel. Check also the web site `https://test.pypi.org/project//` +# Finish the release + 1. If everything looks fine, merge the pull request. 1. Upload to PyPI: - ```bash - $ git clean -xfd - $ tox -e prepare-dist - $ twine upload dist/* - ``` + $ git clean -xfd + $ tox r -e prepare-dist + $ twine upload --verbose --repository semver dist/* 1. Go to https://pypi.org/project/semver/ to verify that new version is online and the page is rendered correctly. -# Finish the release - 1. Create a tag: - $ git tag -a x.x.x + git tag -a x.y.z - It's recommended to use the generated Tox output - from the Changelog. + It's recommended to use the generated Tox output from the Changelog. 1. Push the tag: - $ git push --tags + git push origin x.y.z -1. In [GitHub Release page](https://github.com/python-semver/python-semver/release) - document the new release. - Select the tag from the last step and copy the - content of the tag description into the release - description. +1. In [GitHub Release page](https://github.com/python-semver/python-semver/release) document the new release. + Select the tag from the last step and copy the content of the tag description into the release description. 1. Announce it in . diff --git a/setup.cfg b/setup.cfg index 87ec3b8f..813d2533 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,87 +3,11 @@ # # See https://setuptools.rtfd.io/en/latest/userguide/declarative_config.html -[metadata] -name = semver -version = attr: semver.__about__.__version__ -description = Python helper for Semantic Versioning (https://semver.org) -long_description = file: README.rst -long_description_content_type = text/x-rst -author = Kostiantyn Rybnikov -author_email = k-bx@k-bx.com -maintainer = Sebastien Celles, Tom Schraitle -maintainer_email = s.celles@gmail.com -url = https://github.com/python-semver/python-semver -project_urls = - Changelog = https://python-semver.readthedocs.io/en/latest/changelog.html - Documentation = https://python-semver.rtfd.io - Releases = https://github.com/python-semver/python-semver/releases - Bug Tracker = https://github.com/python-semver/python-semver/issues -classifiers = - Environment :: Web Environment - Intended Audience :: Developers - License :: OSI Approved :: BSD License - Operating System :: OS Independent - Programming Language :: Python - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.11 - Programming Language :: Python :: 3.12 - Topic :: Software Development :: Libraries :: Python Modules -license = BSD - -[options] -package_dir = - =src -packages = find: -python_requires = >=3.7.* -include_package_data = True - -[options.entry_points] -console_scripts = - pysemver = semver.cli:main - -[options.packages.find] -where = src - -[options.package_data] -semver = py.typed - -[tool:pytest] -norecursedirs = .git build .env/ env/ .pyenv/ .tmp/ .eggs/ venv/ -testpaths = tests docs -# pythonpath = src -filterwarnings = - ignore:Function 'semver.*:DeprecationWarning - # ' <- This apostroph is just to fix syntax highlighting -addopts = - # --import-mode=importlib - --no-cov-on-fail - --cov=semver - --cov-report=term-missing - --doctest-glob='*.rst' - --doctest-modules - --doctest-report ndiff - -[flake8] -max-line-length = 88 -ignore = F821,W503 -extend-exclude = - .eggs - .env - build - docs - venv - conftest.py - src/semver/__init__.py - tasks.py [pycodestyle] count = False -# ignore = E226,E302,E41 +ignore = E203,E701 +# E226,E302,E41 max-line-length = 88 statistics = True exclude = diff --git a/src/semver/__about__.py b/src/semver/__about__.py index 0f7150bf..21494e68 100644 --- a/src/semver/__about__.py +++ b/src/semver/__about__.py @@ -16,7 +16,7 @@ """ #: Semver version -__version__ = "3.0.0-dev.4" +__version__ = "3.0.4" #: Original semver author __author__ = "Kostiantyn Rybnikov" diff --git a/src/semver/__init__.py b/src/semver/__init__.py index c6726f2e..19c88f78 100644 --- a/src/semver/__init__.py +++ b/src/semver/__init__.py @@ -1,5 +1,5 @@ """ -semver package major release 3. +Semver package major release 3. A Python module for semantic versioning. Simplifies comparing versions. """ @@ -9,8 +9,8 @@ bump_major, bump_minor, bump_patch, - bump_prerelease, compare, + bump_prerelease, finalize_version, format_version, match, @@ -37,3 +37,36 @@ __maintainer_email__, SEMVER_SPEC_VERSION, ) + +__all__ = [ + "bump_build", + "bump_major", + "bump_minor", + "bump_patch", + "compare", + "bump_prerelease", + "finalize_version", + "format_version", + "match", + "max_ver", + "min_ver", + "parse", + "parse_version_info", + "replace", + "cmd_bump", + "cmd_compare", + "cmd_nextver", + "cmd_check", + "createparser", + "process", + "main", + "Version", + "VersionInfo", + "__version__", + "__author__", + "__maintainer__", + "__author_email__", + "__description__", + "__maintainer_email__", + "SEMVER_SPEC_VERSION", +] diff --git a/src/semver/__main__.py b/src/semver/__main__.py index a6d448aa..c6adb834 100644 --- a/src/semver/__main__.py +++ b/src/semver/__main__.py @@ -9,6 +9,7 @@ $ python3 semver-3*-py3-none-any.whl/semver -h """ + import os.path import sys from typing import List, Optional @@ -25,4 +26,4 @@ def main(cliargs: Optional[List[str]] = None) -> int: if __name__ == "__main__": - sys.exit(main(sys.argv)) + sys.exit(main(sys.argv[1:])) diff --git a/src/semver/_deprecated.py b/src/semver/_deprecated.py index 5f51c8f3..df11bb02 100644 --- a/src/semver/_deprecated.py +++ b/src/semver/_deprecated.py @@ -3,6 +3,7 @@ .. autofunction: deprecated """ + import inspect import warnings from functools import partial, wraps @@ -11,13 +12,15 @@ from . import cli from .version import Version -from ._types import Decorator, F, String +from ._types import Decorator, F def deprecated( func: Optional[F] = None, + *, replace: Optional[str] = None, version: Optional[str] = None, + remove: Optional[str] = None, category: Type[Warning] = DeprecationWarning, ) -> Decorator: """ @@ -25,7 +28,7 @@ def deprecated( :param func: the function to decorate :param replace: the function to replace (use the full qualified - name like ``semver.Version.bump_major``. + name like ``semver.version.Version.bump_major``. :param version: the first version when this function was deprecated. :param category: allow you to specify the deprecation warning class of your choice. By default, it's :class:`DeprecationWarning`, but @@ -34,7 +37,13 @@ def deprecated( """ if func is None: - return partial(deprecated, replace=replace, version=version, category=category) + return partial( + deprecated, + replace=replace, + version=version, + remove=remove, + category=category, + ) @wraps(func) def wrapper(*args, **kwargs) -> Callable[..., F]: @@ -42,7 +51,12 @@ def wrapper(*args, **kwargs) -> Callable[..., F]: if version: msg_list.append("Deprecated since version {v}. ") - msg_list.append("This function will be removed in semver 3.") + + if not remove: + msg_list.append("This function will be removed in semver 3.") + else: + msg_list.append(str(remove)) + if replace: msg_list.append("Use {r!r} instead.") else: @@ -69,13 +83,43 @@ def wrapper(*args, **kwargs) -> Callable[..., F]: return wrapper +@deprecated( + version="3.0.0", + remove="Still under investigation, see #258.", + category=PendingDeprecationWarning, +) +def compare(ver1: str, ver2: str) -> int: + """ + Compare two versions strings. + + .. deprecated:: 3.0.0 + The situation of this function is unclear and it might + disappear in the future. + If possible, use :meth:`semver.version.Version.compare`. + See :gh:`258` for details. + + :param ver1: first version string + :param ver2: second version string + :return: The return value is negative if ver1 < ver2, + zero if ver1 == ver2 and strictly positive if ver1 > ver2 + + >>> semver.compare("1.0.0", "2.0.0") + -1 + >>> semver.compare("2.0.0", "1.0.0") + 1 + >>> semver.compare("2.0.0", "2.0.0") + 0 + """ + return Version.parse(ver1).compare(ver2) + + @deprecated(version="2.10.0") def parse(version): """ Parse version to major, minor, patch, pre-release, build parts. .. deprecated:: 2.10.0 - Use :func:`semver.Version.parse` instead. + Use :meth:`~semver.version.Version.parse` instead. :param version: version string :return: dictionary with the keys 'build', 'major', 'minor', 'patch', @@ -98,13 +142,13 @@ def parse(version): return Version.parse(version).to_dict() -@deprecated(replace="semver.Version.parse", version="2.10.0") +@deprecated(replace="semver.version.Version.parse", version="2.10.0") def parse_version_info(version): """ - Parse version string to a VersionInfo instance. + Parse version string to a Version instance. .. deprecated:: 2.10.0 - Use :func:`semver.VersionInfo.parse` instead. + Use :meth:`~semver.version.Version.parse` instead. .. versionadded:: 2.7.2 Added :func:`semver.parse_version_info` @@ -126,33 +170,14 @@ def parse_version_info(version): return Version.parse(version) -@deprecated(version="2.10.0") -def compare(ver1, ver2): - """ - Compare two versions strings. - - :param ver1: version string 1 - :param ver2: version string 2 - :return: The return value is negative if ver1 < ver2, - zero if ver1 == ver2 and strictly positive if ver1 > ver2 - :rtype: int - - >>> semver.compare("1.0.0", "2.0.0") - -1 - >>> semver.compare("2.0.0", "1.0.0") - 1 - >>> semver.compare("2.0.0", "2.0.0") - 0 - """ - v1 = Version.parse(ver1) - return v1.compare(ver2) - - @deprecated(version="2.10.0") def match(version, match_expr): """ Compare two versions strings through a comparison. + .. deprecated:: 2.10.0 + Use :meth:`~semver.version.Version.match` instead. + :param str version: a version string :param str match_expr: operator and version; valid operators are < smaller than @@ -178,6 +203,9 @@ def max_ver(ver1, ver2): """ Returns the greater version of two versions strings. + .. deprecated:: 2.10.2 + Use :func:`max` instead. + :param ver1: version string 1 :param ver2: version string 2 :return: the greater version of the two @@ -186,15 +214,7 @@ def max_ver(ver1, ver2): >>> semver.max_ver("1.0.0", "2.0.0") '2.0.0' """ - if isinstance(ver1, String.__args__): # type: ignore - ver1 = Version.parse(ver1) - elif not isinstance(ver1, Version): - raise TypeError() - cmp_res = ver1.compare(ver2) - if cmp_res >= 0: - return str(ver1) - else: - return ver2 + return str(max(ver1, ver2, key=Version.parse)) @deprecated(replace="min", version="2.10.2") @@ -202,6 +222,9 @@ def min_ver(ver1, ver2): """ Returns the smaller version of two versions strings. + .. deprecated:: 2.10.2 + Use Use :func:`min` instead. + :param ver1: version string 1 :param ver2: version string 2 :return: the smaller version of the two @@ -210,12 +233,7 @@ def min_ver(ver1, ver2): >>> semver.min_ver("1.0.0", "2.0.0") '1.0.0' """ - ver1 = Version.parse(ver1) - cmp_res = ver1.compare(ver2) - if cmp_res <= 0: - return str(ver1) - else: - return ver2 + return str(min(ver1, ver2, key=Version.parse)) @deprecated(replace="str(versionobject)", version="2.10.0") @@ -246,7 +264,7 @@ def bump_major(version): Raise the major part of the version string. .. deprecated:: 2.10.0 - Use :func:`semver.Version.bump_major` instead. + Use :meth:`~semver.version.Version.bump_major` instead. :param: version string :return: the raised version string @@ -264,7 +282,7 @@ def bump_minor(version): Raise the minor part of the version string. .. deprecated:: 2.10.0 - Use :func:`semver.Version.bump_minor` instead. + Use :meth:`~semver.version.Version.bump_minor` instead. :param: version string :return: the raised version string @@ -282,7 +300,7 @@ def bump_patch(version): Raise the patch part of the version string. .. deprecated:: 2.10.0 - Use :func:`semver.Version.bump_patch` instead. + Use :meth:`~semver.version.Version.bump_patch` instead. :param: version string :return: the raised version string @@ -300,7 +318,7 @@ def bump_prerelease(version, token="rc"): Raise the prerelease part of the version string. .. deprecated:: 2.10.0 - Use :func:`semver.Version.bump_prerelease` instead. + Use :meth:`~semver.version.Version.bump_prerelease` instead. :param version: version string :param token: defaults to 'rc' @@ -319,7 +337,7 @@ def bump_build(version, token="build"): Raise the build part of the version string. .. deprecated:: 2.10.0 - Use :func:`semver.Version.bump_build` instead. + Use :meth:`~semver.version.Version.bump_build` instead. :param version: version string :param token: defaults to 'build' @@ -338,7 +356,7 @@ def finalize_version(version): Remove any prerelease and build metadata from the version string. .. deprecated:: 2.10.0 - Use :func:`semver.Version.finalize_version` instead. + Use :meth:`~semver.version.Version.finalize_version` instead. .. versionadded:: 2.7.9 Added :func:`finalize_version` @@ -360,7 +378,7 @@ def replace(version, **parts): Replace one or more parts of a version and return the new string. .. deprecated:: 2.10.0 - Use :func:`semver.Version.replace` instead. + Use :meth:`~semver.version.Version.replace` instead. .. versionadded:: 2.9.0 Added :func:`replace` @@ -378,10 +396,16 @@ def replace(version, **parts): # CLI -cmd_bump = deprecated(cli.cmd_bump, "semver.cli.cmd_bump", "3.0.0") -cmd_check = deprecated(cli.cmd_check, "semver.cli.cmd_check", "3.0.0") -cmd_compare = deprecated(cli.cmd_compare, "semver.cli.cmd_compare", "3.0.0") -cmd_nextver = deprecated(cli.cmd_nextver, "semver.cli.cmd_nextver", "3.0.0") -createparser = deprecated(cli.createparser, "semver.cli.createparser", "3.0.0") -process = deprecated(cli.process, "semver.cli.process", "3.0.0") -main = deprecated(cli.main, "semver.cli.main", "3.0.0") +cmd_bump = deprecated(cli.cmd_bump, replace="semver.cli.cmd_bump", version="3.0.0") +cmd_check = deprecated(cli.cmd_check, replace="semver.cli.cmd_check", version="3.0.0") +cmd_compare = deprecated( + cli.cmd_compare, replace="semver.cli.cmd_compare", version="3.0.0" +) +cmd_nextver = deprecated( + cli.cmd_nextver, replace="semver.cli.cmd_nextver", version="3.0.0" +) +createparser = deprecated( + cli.createparser, replace="semver.cli.createparser", version="3.0.0" +) +process = deprecated(cli.process, replace="semver.cli.process", version="3.0.0") +main = deprecated(cli.main, replace="semver.cli.main", version="3.0.0") diff --git a/src/semver/cli.py b/src/semver/cli.py index 3c573d63..43e101e1 100644 --- a/src/semver/cli.py +++ b/src/semver/cli.py @@ -54,14 +54,14 @@ def cmd_check(args: argparse.Namespace) -> None: :param args: The parsed arguments """ - if Version.isvalid(args.version): + if Version.is_valid(args.version): return None raise ValueError("Invalid version %r" % args.version) def cmd_compare(args: argparse.Namespace) -> str: """ - Subcommand: Compare two versions + Subcommand: Compare two versions. Synopsis: compare diff --git a/src/semver/version.py b/src/semver/version.py index 96281192..f9450f95 100644 --- a/src/semver/version.py +++ b/src/semver/version.py @@ -1,19 +1,22 @@ -"""Version handling.""" +"""Version handling by a semver compatible version class.""" -import collections import re from functools import wraps from typing import ( Any, + ClassVar, Dict, Iterable, Optional, + Pattern, SupportsInt, Tuple, Union, cast, Callable, Collection, + Type, + TypeVar, ) from ._types import ( @@ -28,6 +31,9 @@ Comparable = Union["Version", Dict[str, VersionPart], Collection[VersionPart], str] Comparator = Callable[["Version", Comparable], bool] +T = TypeVar("T", bound="Version") +T_cmp = TypeVar("T_cmp", tuple, str, int) + def _comparator(operator: Comparator) -> Comparator: """Wrap a Version binary op method in a type-check.""" @@ -35,7 +41,7 @@ def _comparator(operator: Comparator) -> Comparator: @wraps(operator) def wrapper(self: "Version", other: Comparable) -> bool: comparable_types = ( - Version, + type(self), dict, tuple, list, @@ -48,7 +54,7 @@ def wrapper(self: "Version", other: Comparable) -> bool: return wrapper -def _cmp(a, b): # TODO: type hints +def _cmp(a: T_cmp, b: T_cmp) -> int: """Return negative if ab.""" return (a > b) - (a < b) @@ -57,19 +63,28 @@ class Version: """ A semver compatible version class. + See specification at https://semver.org. + :param major: version when you make incompatible API changes. - :param minor: version when you add functionality in - a backwards-compatible manner. + :param minor: version when you add functionality in a backwards-compatible manner. :param patch: version when you make backwards-compatible bug fixes. :param prerelease: an optional prerelease string :param build: an optional build string """ __slots__ = ("_major", "_minor", "_patch", "_prerelease", "_build") + + #: The names of the different parts of a version + NAMES: ClassVar[Tuple[str, ...]] = tuple([item[1:] for item in __slots__]) + + #: Regex for number in a build + _LAST_NUMBER: ClassVar[Pattern[str]] = re.compile(r"(?:[^\d]*(\d+)[^\d]*)+") #: Regex for number in a prerelease - _LAST_NUMBER = re.compile(r"(?:[^\d]*(\d+)[^\d]*)+") + _LAST_PRERELEASE: ClassVar[Pattern[str]] = re.compile(r"^(.*\.)?(\d+)$") #: Regex template for a semver version - _REGEX_TEMPLATE = r""" + _REGEX_TEMPLATE: ClassVar[ + str + ] = r""" ^ (?P0|[1-9]\d*) (?: @@ -91,12 +106,12 @@ class Version: $ """ #: Regex for a semver version - _REGEX = re.compile( + _REGEX: ClassVar[Pattern[str]] = re.compile( _REGEX_TEMPLATE.format(opt_patch="", opt_minor=""), re.VERBOSE, ) #: Regex for a semver version that might be shorter - _REGEX_OPTIONAL_MINOR_AND_PATCH = re.compile( + _REGEX_OPTIONAL_MINOR_AND_PATCH: ClassVar[Pattern[str]] = re.compile( _REGEX_TEMPLATE.format(opt_patch="?", opt_minor="?"), re.VERBOSE, ) @@ -125,7 +140,7 @@ def __init__( self._build = None if build is None else str(build) @classmethod - def _nat_cmp(cls, a, b): # TODO: type hints + def _nat_cmp(cls, a: Optional[str], b: Optional[str]) -> int: def cmp_prerelease_tag(a, b): if isinstance(a, int) and isinstance(b, int): return _cmp(a, b) @@ -136,16 +151,15 @@ def cmp_prerelease_tag(a, b): else: return _cmp(a, b) - a, b = a or "", b or "" - a_parts, b_parts = a.split("."), b.split(".") - a_parts = [int(x) if re.match(r"^\d+$", x) else x for x in a_parts] - b_parts = [int(x) if re.match(r"^\d+$", x) else x for x in b_parts] + a_parts = [int(x) if x.isdigit() else x for x in (a or "").split(".")] + b_parts = [int(x) if x.isdigit() else x for x in (b or "").split(".")] + for sub_a, sub_b in zip(a_parts, b_parts): cmp_result = cmp_prerelease_tag(sub_a, sub_b) if cmp_result != 0: return cmp_result - else: - return _cmp(len(a), len(b)) + + return _cmp(len(a_parts), len(b_parts)) @property def major(self) -> int: @@ -197,7 +211,7 @@ def to_tuple(self) -> VersionTuple: Convert the Version object to a tuple. .. versionadded:: 2.10.0 - Renamed ``VersionInfo._astuple`` to ``VersionInfo.to_tuple`` to + Renamed :meth:`Version._astuple` to :meth:`Version.to_tuple` to make this function available in the public API. :return: a tuple with all the parts @@ -209,33 +223,47 @@ def to_tuple(self) -> VersionTuple: def to_dict(self) -> VersionDict: """ - Convert the Version object to an OrderedDict. + Convert the Version object to an dict. .. versionadded:: 2.10.0 - Renamed ``VersionInfo._asdict`` to ``VersionInfo.to_dict`` to + Renamed :meth:`Version._asdict` to :meth:`Version.to_dict` to make this function available in the public API. - :return: an OrderedDict with the keys in the order ``major``, ``minor``, + :return: an dict with the keys in the order ``major``, ``minor``, ``patch``, ``prerelease``, and ``build``. >>> semver.Version(3, 2, 1).to_dict() - OrderedDict([('major', 3), ('minor', 2), ('patch', 1), \ -('prerelease', None), ('build', None)]) - """ - return collections.OrderedDict( - ( - ("major", self.major), - ("minor", self.minor), - ("patch", self.patch), - ("prerelease", self.prerelease), - ("build", self.build), - ) + {'major': 3, 'minor': 2, 'patch': 1, 'prerelease': None, 'build': None} + """ + return dict( + major=self.major, + minor=self.minor, + patch=self.patch, + prerelease=self.prerelease, + build=self.build, ) def __iter__(self) -> VersionIterator: """Return iter(self).""" yield from self.to_tuple() + @staticmethod + def _increment_prerelease(string: str) -> str: + """ + Check if the last part of a dot-separated string is numeric. If yes, + increase them. Else, add '.0' + + :param string: the prerelease version to increment + :return: the incremented string + """ + match = Version._LAST_PRERELEASE.search(string) + if match: + next_ = str(int(match.group(2)) + 1) + string = match.group(1) + next_ if match.group(1) else next_ + else: + string += ".0" + return string + @staticmethod def _increment_string(string: str) -> str: """ @@ -261,7 +289,6 @@ def bump_major(self) -> "Version": :return: new object with the raised major part - >>> ver = semver.parse("3.4.5") >>> ver.bump_major() Version(major=4, minor=0, patch=0, prerelease=None, build=None) @@ -297,30 +324,61 @@ def bump_patch(self) -> "Version": cls = type(self) return cls(self._major, self._minor, self._patch + 1) - def bump_prerelease(self, token: str = "rc") -> "Version": + def bump_prerelease( + self, + token: Optional[str] = "rc", + bump_when_empty: Optional[bool] = False + ) -> "Version": """ Raise the prerelease part of the version, return a new object but leave self untouched. - :param token: defaults to ``rc`` - :return: new object with the raised prerelease part + .. versionchanged:: 3.1.0 + Parameter `bump_when_empty` added. When set to true, bumps the patch version + when called with a version that has no prerelease segment, so the return + value will be considered a newer version. + + Adds `.0` to the prerelease if the last part of the dot-separated + prerelease is not a number. + + :param token: defaults to ``'rc'`` + :return: new :class:`Version` object with the raised prerelease part. + The original object is not modified. >>> ver = semver.parse("3.4.5") - >>> ver.bump_prerelease() - Version(major=3, minor=4, patch=5, prerelease='rc.2', \ -build=None) + >>> ver.bump_prerelease().prerelease + 'rc.1' + >>> ver.bump_prerelease('').prerelease + '1' + >>> ver.bump_prerelease(None).prerelease + 'rc.1' + >>> str(ver.bump_prerelease(bump_when_empty=True)) + '3.4.6-rc.1' """ cls = type(self) - prerelease = cls._increment_string(self._prerelease or (token or "rc") + ".0") - return cls(self._major, self._minor, self._patch, prerelease) + patch = self._patch + if self._prerelease is not None: + prerelease = cls._increment_prerelease(self._prerelease) + else: + if bump_when_empty: + patch += 1 + if token == "": + prerelease = "1" + elif token is None: + prerelease = "rc.1" + else: + prerelease = str(token) + ".1" + + return cls(self._major, self._minor, patch, prerelease) - def bump_build(self, token: str = "build") -> "Version": + def bump_build(self, token: Optional[str] = "build") -> "Version": """ Raise the build part of the version, return a new object but leave self untouched. - :param token: defaults to ``build`` - :return: new object with the raised build part + :param token: defaults to ``'build'`` + :return: new :class:`Version` object with the raised build part. + The original object is not modified. >>> ver = semver.parse("3.4.5-rc.1+build.9") >>> ver.bump_build() @@ -328,7 +386,16 @@ def bump_build(self, token: str = "build") -> "Version": build='build.10') """ cls = type(self) - build = cls._increment_string(self._build or (token or "build") + ".0") + if self._build is not None: + build = self._build + elif token == "": + build = "0" + elif token is None: + build = "build.0" + else: + build = str(token) + ".0" + + build = cls._increment_string(build) return cls(self._major, self._minor, self._patch, self._prerelease, build) def compare(self, other: Comparable) -> int: @@ -339,13 +406,11 @@ def compare(self, other: Comparable) -> int: :return: The return value is negative if ver1 < ver2, zero if ver1 == ver2 and strictly positive if ver1 > ver2 - >>> semver.compare("2.0.0") + >>> Version.parse("1.0.0").compare("2.0.0") -1 - >>> semver.compare("1.0.0") + >>> Version.parse("2.0.0").compare("1.0.0") 1 - >>> semver.compare("2.0.0") - 0 - >>> semver.compare(dict(major=2, minor=0, patch=0)) + >>> Version.parse("2.0.0").compare("2.0.0") 0 """ cls = type(self) @@ -388,7 +453,7 @@ def next_version(self, part: str, prerelease_token: str = "rc") -> "Version": This function is taking prereleases into account. The "major", "minor", and "patch" raises the respective parts like the ``bump_*`` functions. The real difference is using the - "preprelease" part. It gives you the next patch version of the + "prerelease" part. It gives you the next patch version of the prerelease, for example: >>> str(semver.parse("0.1.4").next_version("prerelease")) @@ -398,18 +463,12 @@ def next_version(self, part: str, prerelease_token: str = "rc") -> "Version": :param prerelease_token: prefix string of prerelease, defaults to 'rc' :return: new object with the appropriate part raised """ - validparts = { - "major", - "minor", - "patch", - "prerelease", - # "build", # currently not used - } + cls = type(self) + # "build" is currently not used, that's why we use [:-1] + validparts = cls.NAMES[:-1] if part not in validparts: raise ValueError( - "Invalid part. Expected one of {validparts}, but got {part!r}".format( - validparts=validparts, part=part - ) + f"Invalid part. Expected one of {validparts}, but got {part!r}" ) version = self if (version.prerelease or version.build) and ( @@ -419,12 +478,11 @@ def next_version(self, part: str, prerelease_token: str = "rc") -> "Version": ): return version.replace(prerelease=None, build=None) - if part in ("major", "minor", "patch"): + # Only check the main parts: + if part in cls.NAMES[:3]: return getattr(version, "bump_" + part)() - - if not version.prerelease: - version = version.bump_patch() - return version.bump_prerelease(prerelease_token) + else: + return version.bump_prerelease(prerelease_token, bump_when_empty=True) @_comparator def __eq__(self, other: Comparable) -> bool: # type: ignore @@ -460,7 +518,7 @@ def __getitem__( is undefined, it will throw an index error. Negative indices are not supported. - :param Union[int, slice] index: a positive integer indicating the + :param index: a positive integer indicating the offset or a :func:`slice` object :raises IndexError: if index is beyond the range or a part is None :return: the requested part of the version at position index @@ -522,7 +580,7 @@ def match(self, match_expr: str) -> bool: Compare self to match a match expression. :param match_expr: optional operator and version; valid operators are - ``<``` smaller than + ``<`` smaller than ``>`` greater than ``>=`` greator or equal than ``<=`` smaller or equal than @@ -570,8 +628,8 @@ def match(self, match_expr: str) -> bool: @classmethod def parse( - cls, version: String, optional_minor_and_patch: bool = False - ) -> "Version": + cls: Type[T], version: String, optional_minor_and_patch: bool = False + ) -> T: """ Parse version string to a Version instance. @@ -579,8 +637,8 @@ def parse( Changed method from static to classmethod to allow subclasses. .. versionchanged:: 3.0.0 - Added optional parameter optional_minor_and_patch to allow optional - minor and patch parts. + Added optional parameter ``optional_minor_and_patch`` to allow + optional minor and patch parts. :param version: version string :param optional_minor_and_patch: if set to true, the version string to parse \ @@ -617,22 +675,22 @@ def parse( def replace(self, **parts: Union[int, Optional[str]]) -> "Version": """ - Replace one or more parts of a version and return a new - :class:`Version` object, but leave self untouched + Replace one or more parts of a version and return a new :class:`Version` + object, but leave self untouched. .. versionadded:: 2.9.0 Added :func:`Version.replace` :param parts: the parts to be updated. Valid keys are: ``major``, ``minor``, ``patch``, ``prerelease``, or ``build`` - :return: the new :class:`Version` object with the changed - parts + :return: the new :class:`~semver.version.Version` object with + the changed parts :raises TypeError: if ``parts`` contain invalid keys """ version = self.to_dict() version.update(parts) try: - return Version(**version) # type: ignore + return type(self)(**version) # type: ignore except TypeError: unknownkeys = set(parts) - set(self.to_dict()) error = "replace() got %d unexpected keyword argument(s): %s" % ( @@ -642,12 +700,15 @@ def replace(self, **parts: Union[int, Optional[str]]) -> "Version": raise TypeError(error) @classmethod - def isvalid(cls, version: str) -> bool: + def is_valid(cls, version: str) -> bool: """ Check if the string is a valid semver version. .. versionadded:: 2.9.1 + .. versionchanged:: 3.0.0 + Renamed from :meth:`~semver.version.Version.isvalid` + :param version: the version string to check :return: True if the version string is a valid semver version, False otherwise. @@ -658,6 +719,44 @@ def isvalid(cls, version: str) -> bool: except ValueError: return False + def is_compatible(self, other: "Version") -> bool: + """ + Check if current version is compatible with other version. + + The result is True, if either of the following is true: + + * both versions are equal, or + * both majors are equal and higher than 0. Same for both minors. + Both pre-releases are equal, or + * both majors are equal and higher than 0. The minor of b's + minor version is higher then a's. Both pre-releases are equal. + + The algorithm does *not* check patches. + + .. versionadded:: 3.0.0 + + :param other: the version to check for compatibility + :return: True, if ``other`` is compatible with the old version, + otherwise False + + >>> Version(1, 1, 0).is_compatible(Version(1, 0, 0)) + False + >>> Version(1, 0, 0).is_compatible(Version(1, 1, 0)) + True + """ + if not isinstance(other, Version): + raise TypeError(f"Expected a Version type but got {type(other)}") + + # All major-0 versions should be incompatible with anything but itself + if (0 == self.major == other.major) and (self[:4] != other[:4]): + return False + + return ( + (self.major == other.major) + and (other.minor >= self.minor) + and (self.prerelease == other.prerelease) + ) + #: Keep the VersionInfo name for compatibility VersionInfo = Version diff --git a/tests/conftest.py b/tests/conftest.py index beecffc9..9017bbbe 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,8 +4,6 @@ import semver -# sys.path.insert(0, "docs/usage") - from coerce import coerce # noqa:E402 from semverwithvprefix import SemVerWithVPrefix # noqa:E402 import packaging.version diff --git a/tests/test_bump.py b/tests/test_bump.py index c28e1905..fcbedf4c 100644 --- a/tests/test_bump.py +++ b/tests/test_bump.py @@ -6,6 +6,7 @@ bump_minor, bump_patch, bump_prerelease, + compare, parse_version_info, ) @@ -32,38 +33,101 @@ def test_should_versioninfo_bump_minor_and_patch(): v = parse_version_info("3.4.5") expected = parse_version_info("3.5.1") assert v.bump_minor().bump_patch() == expected + assert v.compare(expected) == -1 def test_should_versioninfo_bump_patch_and_prerelease(): v = parse_version_info("3.4.5-rc.1") expected = parse_version_info("3.4.6-rc.1") assert v.bump_patch().bump_prerelease() == expected + assert v.compare(expected) == -1 def test_should_versioninfo_bump_patch_and_prerelease_with_token(): v = parse_version_info("3.4.5-dev.1") expected = parse_version_info("3.4.6-dev.1") assert v.bump_patch().bump_prerelease("dev") == expected + assert v.compare(expected) == -1 def test_should_versioninfo_bump_prerelease_and_build(): v = parse_version_info("3.4.5-rc.1+build.1") expected = parse_version_info("3.4.5-rc.2+build.2") assert v.bump_prerelease().bump_build() == expected + assert v.compare(expected) == -1 def test_should_versioninfo_bump_prerelease_and_build_with_token(): v = parse_version_info("3.4.5-rc.1+b.1") expected = parse_version_info("3.4.5-rc.2+b.2") assert v.bump_prerelease().bump_build("b") == expected + assert v.compare(expected) == -1 def test_should_versioninfo_bump_multiple(): v = parse_version_info("3.4.5-rc.1+build.1") expected = parse_version_info("3.4.5-rc.2+build.2") assert v.bump_prerelease().bump_build().bump_build() == expected + assert v.compare(expected) == -1 expected = parse_version_info("3.4.5-rc.3") assert v.bump_prerelease().bump_build().bump_build().bump_prerelease() == expected + assert v.compare(expected) == -1 + + +def test_should_versioninfo_bump_prerelease_with_empty_str(): + v = parse_version_info("3.4.5") + expected = parse_version_info("3.4.5-1") + assert v.bump_prerelease("") == expected + assert v.compare(expected) == 1 + + +def test_should_versioninfo_bump_prerelease_with_none(): + v = parse_version_info("3.4.5") + expected = parse_version_info("3.4.5-rc.1") + assert v.bump_prerelease(None) == expected + assert v.compare(expected) == 1 + + +def test_should_versioninfo_bump_prerelease_nonnumeric(): + v = parse_version_info("3.4.5-rc1") + expected = parse_version_info("3.4.5-rc1.0") + assert v.bump_prerelease(None) == expected + assert v.compare(expected) == -1 + + +def test_should_versioninfo_bump_prerelease_nonnumeric_nine(): + v = parse_version_info("3.4.5-rc9") + expected = parse_version_info("3.4.5-rc9.0") + assert v.bump_prerelease(None) == expected + assert v.compare(expected) == -1 + + +def test_should_versioninfo_bump_prerelease_bump_patch(): + v = parse_version_info("3.4.5") + expected = parse_version_info("3.4.6-rc.1") + assert v.bump_prerelease(bump_when_empty=True) == expected + assert v.compare(expected) == -1 + + +def test_should_versioninfo_bump_patch_and_prerelease_bump_patch(): + v = parse_version_info("3.4.5") + expected = parse_version_info("3.4.7-rc.1") + assert v.bump_patch().bump_prerelease(bump_when_empty=True) == expected + assert v.compare(expected) == -1 + + +def test_should_versioninfo_bump_build_with_empty_str(): + v = parse_version_info("3.4.5") + expected = parse_version_info("3.4.5+1") + assert v.bump_build("") == expected + assert v.compare(expected) == 0 + + +def test_should_versioninfo_bump_build_with_none(): + v = parse_version_info("3.4.5") + expected = parse_version_info("3.4.5+build.1") + assert v.bump_build(None) == expected + assert v.compare(expected) == 0 def test_should_ignore_extensions_for_bump(): @@ -71,18 +135,18 @@ def test_should_ignore_extensions_for_bump(): @pytest.mark.parametrize( - "version,token,expected", + "version,token,expected,expected_compare", [ - ("3.4.5-rc.9", None, "3.4.5-rc.10"), - ("3.4.5", None, "3.4.5-rc.1"), - ("3.4.5", "dev", "3.4.5-dev.1"), - ("3.4.5", "", "3.4.5-rc.1"), + ("3.4.5-rc.9", None, "3.4.5-rc.10", -1), + ("3.4.5", None, "3.4.5-rc.1", 1), + ("3.4.5", "dev", "3.4.5-dev.1", 1), + ("3.4.5", "", "3.4.5-rc.1", 1), ], ) -def test_should_bump_prerelease(version, token, expected): +def test_should_bump_prerelease(version, token, expected, expected_compare): token = "rc" if not token else token assert bump_prerelease(version, token) == expected - + assert compare(version, expected) == expected_compare def test_should_ignore_build_on_prerelease_bump(): assert bump_prerelease("3.4.5-rc.1+build.4") == "3.4.5-rc.2" @@ -99,3 +163,4 @@ def test_should_ignore_build_on_prerelease_bump(): ) def test_should_bump_build(version, expected): assert bump_build(version) == expected + assert compare(version, expected) == 0 \ No newline at end of file diff --git a/tests/test_deprecated_functions.py b/tests/test_deprecated_functions.py index 0b5123cc..88862689 100644 --- a/tests/test_deprecated_functions.py +++ b/tests/test_deprecated_functions.py @@ -5,7 +5,6 @@ from semver import ( parse, parse_version_info, - compare, match, max_ver, min_ver, @@ -36,7 +35,6 @@ (bump_minor, ("1.2.3",), {}), (bump_patch, ("1.2.3",), {}), (bump_prerelease, ("1.2.3",), {}), - (compare, ("1.2.1", "1.2.2"), {}), (format_version, (3, 4, 5), {}), (finalize_version, ("1.2.3-rc.5",), {}), (match, ("1.0.0", ">=1.0.0"), {}), diff --git a/tests/test_pysemver-cli.py b/tests/test_pysemver-cli.py index e783a0b4..5a0a2f82 100644 --- a/tests/test_pysemver-cli.py +++ b/tests/test_pysemver-cli.py @@ -55,7 +55,7 @@ def test_should_parse_cli_arguments(cli, expected): ( cmd_bump, Namespace(bump="prerelease", version="1.2.3-rc1"), - does_not_raise("1.2.3-rc2"), + does_not_raise("1.2.3-rc1.0"), ), ( cmd_bump, diff --git a/tests/test_semver.py b/tests/test_semver.py index b15bfeaf..9978b940 100644 --- a/tests/test_semver.py +++ b/tests/test_semver.py @@ -73,10 +73,64 @@ def test_should_be_able_to_use_integers_as_prerelease_build(): def test_should_versioninfo_isvalid(): - assert Version.isvalid("1.0.0") is True - assert Version.isvalid("foo") is False + assert Version.is_valid("1.0.0") is True + assert Version.is_valid("foo") is False def test_versioninfo_compare_should_raise_when_passed_invalid_value(): with pytest.raises(TypeError): Version(1, 2, 3).compare(4) + + +@pytest.mark.parametrize( + "old, new", + [ + ((1, 2, 3), (1, 2, 3)), + ((1, 2, 3), (1, 2, 4)), + ((1, 2, 4), (1, 2, 3)), + ((1, 2, 3, "rc.0"), (1, 2, 4, "rc.0")), + ((0, 1, 0), (0, 1, 0)), + ], +) +def test_should_succeed_compatible_match(old, new): + old = Version(*old) + new = Version(*new) + assert old.is_compatible(new) + + +@pytest.mark.parametrize( + "old, new", + [ + ((1, 1, 0), (1, 0, 0)), + ((2, 0, 0), (1, 5, 0)), + ((1, 2, 3, "rc.1"), (1, 2, 3, "rc.0")), + ((1, 2, 3, "rc.1"), (1, 2, 4, "rc.0")), + ((0, 1, 0), (0, 1, 1)), + ((1, 0, 0), (1, 0, 0, "rc1")), + ((1, 0, 0, "rc1"), (1, 0, 0)), + ], +) +def test_should_fail_compatible_match(old, new): + old = Version(*old) + new = Version(*new) + assert not old.is_compatible(new) + + +@pytest.mark.parametrize( + "wrongtype", + [ + "wrongtype", + dict(a=2), + list(), + ], +) +def test_should_fail_with_incompatible_type_for_compatible_match(wrongtype): + with pytest.raises(TypeError, match="Expected a Version type .*"): + v = Version(1, 2, 3) + v.is_compatible(wrongtype) + + +def test_should_succeed_with_compatible_subclass_for_is_compatible(): + class CustomVersion(Version): ... + + assert CustomVersion(1, 0, 0).is_compatible(Version(1, 0, 0)) diff --git a/tests/test_subclass.py b/tests/test_subclass.py index cbf9d271..0d390009 100644 --- a/tests/test_subclass.py +++ b/tests/test_subclass.py @@ -1,4 +1,5 @@ from semver import Version +import pytest def test_subclass_from_versioninfo(): @@ -17,3 +18,54 @@ def __str__(self): v = SemVerWithVPrefix.parse("v1.2.3") assert str(v) == "v1.2.3" + + +def test_replace_from_subclass(): + # Issue#426 + # Taken from the example "Creating Subclasses from Version" + class SemVerWithVPrefix(Version): + """ + A subclass of Version which allows a "v" prefix + """ + + @classmethod + def parse(cls, version: str) -> "SemVerWithVPrefix": + """ + Parse version string to a Version instance. + + :param version: version string with "v" or "V" prefix + :raises ValueError: when version does not start with "v" or "V" + :return: a new instance + """ + if not version[0] in ("v", "V"): + raise ValueError( + f"{version!r}: not a valid semantic version tag. " + "Must start with 'v' or 'V'" + ) + return super().parse(version[1:], optional_minor_and_patch=True) + + def __str__(self) -> str: + # Reconstruct the tag + return "v" + super().__str__() + + version = SemVerWithVPrefix.parse("v1.1.0") + dev_version = version.replace(prerelease="dev.0") + + assert str(dev_version) == "v1.1.0-dev.0" + + +def test_compare_with_subclass(): + class SemVerSubclass(Version): + pass + + with pytest.raises(TypeError): + SemVerSubclass.parse("1.0.0").compare(Version.parse("1.0.0")) + assert Version.parse("1.0.0").compare(SemVerSubclass.parse("1.0.0")) == 0 + + assert ( + SemVerSubclass.parse("1.0.0").__eq__(Version.parse("1.0.0")) is NotImplemented + ) + assert Version.parse("1.0.0").__eq__(SemVerSubclass.parse("1.0.0")) is True + + assert SemVerSubclass.parse("1.0.0") == Version.parse("1.0.0") + assert Version.parse("1.0.0") == SemVerSubclass.parse("1.0.0") diff --git a/tox.ini b/tox.ini index 8ca917b8..d39ce81a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,23 +1,25 @@ [tox] envlist = checks - py3{7,8,9,10,11,12} + py3{7,8,9,10,11,12,13} isolated_build = True skip_missing_interpreters = True [gh-actions] python = # setuptools >=62 needs Python >=3.7 - 3.7: py37,check + 3.7: py37,checks 3.8: py38 3.9: py39 3.10: py310 3.11: py311 3.12: py312 + 3.13: py313 [testenv] description = Run test suite for {basepython} +skip_install = true allowlist_externals = make commands = pytest {posargs:} deps = @@ -27,50 +29,31 @@ deps = setuptools-scm setenv = PIP_DISABLE_PIP_VERSION_CHECK = 1 - - -[testenv:black] -description = Check for formatting changes -basepython = python3 -skip_install = true -deps = black -commands = black --check {posargs:.} - - -[testenv:flake8] -description = Check code style -basepython = python3 -deps = flake8 -commands = flake8 {posargs:} +downloads = true [testenv:mypy] description = Check code style basepython = python3 deps = mypy -commands = mypy {posargs:--ignore-missing-imports --check-untyped-defs src} +commands = mypy {posargs:src} [testenv:docstrings] description = Check for PEP257 compatible docstrings basepython = python3 deps = docformatter -commands = docformatter --check {posargs:--pre-summary-newline -r src} +commands = + docformatter --check --diff {posargs:src} [testenv:checks] description = Run code style checks basepython = python3 deps = - {[testenv:black]deps} - {[testenv:flake8]deps} - {[testenv:mypy]deps} - {[testenv:docstrings]deps} + ruff commands = - {[testenv:black]commands} - {[testenv:flake8]commands} - {[testenv:mypy]commands} - {[testenv:docstrings]commands} + ruff check [testenv:docs] @@ -81,10 +64,12 @@ skip_install = true allowlist_externals = make echo + uvx commands = - make -C docs html + uvx make -C docs html commands_post = - echo "Find the HTML documentation at {toxinidir}/docs/_build/html/index.html" + echo "Find the HTML documentation at file://{toxinidir}/docs/_build/html/index.html" + [testenv:man] description = Build the manpage @@ -98,9 +83,7 @@ commands = make -C docs man description = Prepare for TestPyPI basepython = python3 deps = - wheel twine - # PEP 517 build frontend build commands = # Same as python3 -m build diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..c14d1363 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1427 @@ +version = 1 +requires-python = ">=3.7" + +[[package]] +name = "alabaster" +version = "0.7.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/71/a8ee96d1fd95ca04a0d2e2d9c4081dac4c2d2b12f7ddb899c8cb9bfd1532/alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2", size = 11454 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/88/c7083fc61120ab661c5d0b82cb77079fc1429d3f913a456c1c82cf4658f7/alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3", size = 13857 }, +] + +[[package]] +name = "babel" +version = "2.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytz", marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/80/cfbe44a9085d112e983282ee7ca4c00429bc4d1ce86ee5f4e60259ddff7f/Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363", size = 10795622 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/35/4196b21041e29a42dc4f05866d0c94fa26c9da88ce12c38c2265e42c82fb/Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287", size = 11034798 }, +] + +[[package]] +name = "black" +version = "23.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typed-ast", marker = "python_full_version < '3.8' and implementation_name == 'cpython'" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/36/66370f5017b100225ec4950a60caeef60201a10080da57ddb24124453fba/black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940", size = 582156 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/f4/7908f71cc71da08df1317a3619f002cbf91927fb5d3ffc7723905a2113f7/black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915", size = 1342273 }, + { url = "https://files.pythonhosted.org/packages/27/70/07aab2623cfd3789786f17e051487a41d5657258c7b1ef8f780512ffea9c/black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9", size = 2676721 }, + { url = "https://files.pythonhosted.org/packages/29/b1/b584fc863c155653963039664a592b3327b002405043b7e761b9b0212337/black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2", size = 1520336 }, + { url = "https://files.pythonhosted.org/packages/6d/b4/0f13ab7f5e364795ff82b76b0f9a4c9c50afda6f1e2feeb8b03fdd7ec57d/black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c", size = 1654611 }, + { url = "https://files.pythonhosted.org/packages/de/b4/76f152c5eb0be5471c22cd18380d31d188930377a1a57969073b89d6615d/black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c", size = 1286657 }, + { url = "https://files.pythonhosted.org/packages/d7/6f/d3832960a3b646b333b7f0d80d336a3c123012e9d9d5dba4a622b2b6181d/black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6", size = 1326112 }, + { url = "https://files.pythonhosted.org/packages/eb/a5/17b40bfd9b607b69fa726b0b3a473d14b093dcd5191ea1a1dd664eccfee3/black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b", size = 2643808 }, + { url = "https://files.pythonhosted.org/packages/69/49/7e1f0cf585b0d607aad3f971f95982cc4208fc77f92363d632d23021ee57/black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d", size = 1503287 }, + { url = "https://files.pythonhosted.org/packages/c0/53/42e312c17cfda5c8fc4b6b396a508218807a3fcbb963b318e49d3ddd11d5/black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70", size = 1638625 }, + { url = "https://files.pythonhosted.org/packages/3f/0d/81dd4194ce7057c199d4f28e4c2a885082d9d929e7a55c514b23784f7787/black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326", size = 1293585 }, + { url = "https://files.pythonhosted.org/packages/24/eb/2d2d2c27cb64cfd073896f62a952a802cd83cf943a692a2f278525b57ca9/black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b", size = 1447428 }, + { url = "https://files.pythonhosted.org/packages/49/36/15d2122f90ff1cd70f06892ebda777b650218cf84b56b5916a993dc1359a/black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2", size = 1576467 }, + { url = "https://files.pythonhosted.org/packages/ca/44/eb41edd3f558a6139f09eee052dead4a7a464e563b822ddf236f5a8ee286/black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925", size = 1226437 }, + { url = "https://files.pythonhosted.org/packages/ce/f4/2b0c6ac9e1f8584296747f66dd511898b4ebd51d6510dba118279bff53b6/black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27", size = 1331955 }, + { url = "https://files.pythonhosted.org/packages/21/14/d5a2bec5fb15f9118baab7123d344646fac0b1c6939d51c2b05259cd2d9c/black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331", size = 2658520 }, + { url = "https://files.pythonhosted.org/packages/13/0a/ed8b66c299e896780e4528eed4018f5b084da3b9ba4ee48328550567d866/black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5", size = 1509852 }, + { url = "https://files.pythonhosted.org/packages/12/4b/99c71d1cf1353edd5aff2700b8960f92e9b805c9dab72639b67dbb449d3a/black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961", size = 1641852 }, + { url = "https://files.pythonhosted.org/packages/d1/6e/5810b6992ed70403124c67e8b3f62858a32b35405177553f1a78ed6b6e31/black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8", size = 1297694 }, + { url = "https://files.pythonhosted.org/packages/13/25/cfa06788d0a936f2445af88f13604b5bcd5c9d050db618c718e6ebe66f74/black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30", size = 1341089 }, + { url = "https://files.pythonhosted.org/packages/fd/5b/fc2d7922c1a6bb49458d424b5be71d251f2d0dc97be9534e35d171bdc653/black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3", size = 2674699 }, + { url = "https://files.pythonhosted.org/packages/49/d7/f3b7da6c772800f5375aeb050a3dcf682f0bbeb41d313c9c2820d0156e4e/black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266", size = 1519946 }, + { url = "https://files.pythonhosted.org/packages/3c/d7/85f3d79f9e543402de2244c4d117793f262149e404ea0168841613c33e07/black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab", size = 1654176 }, + { url = "https://files.pythonhosted.org/packages/06/1e/273d610249f0335afb1ddb03664a03223f4826e3d1a95170a0142cb19fb4/black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb", size = 1286299 }, + { url = "https://files.pythonhosted.org/packages/ad/e7/4642b7f462381799393fbad894ba4b32db00870a797f0616c197b07129a9/black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4", size = 180965 }, +] + +[[package]] +name = "bleach" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7e/e6/d5f220ca638f6a25557a611860482cb6e54b2d97f0332966b1b005742e1f/bleach-6.0.0.tar.gz", hash = "sha256:1a1a85c1595e07d8db14c5f09f09e6433502c51c595970edc090551f0db99414", size = 201298 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/e2/dfcab68c9b2e7800c8f06b85c76e5f978d05b195a958daa9b1dda54a1db6/bleach-6.0.0-py3-none-any.whl", hash = "sha256:33c16e3353dbd13028ab4799a0f89a83f113405c766e9c122df8a06f5b85b3f4", size = 162493 }, +] + +[[package]] +name = "build" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "os_name == 'nt'" }, + { name = "importlib-metadata", marker = "python_full_version < '3.10.2'" }, + { name = "packaging" }, + { name = "pyproject-hooks" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/f7/7bd626bc41b59152248087c1b56dd9f5d09c3f817b96075dc3cbda539dc7/build-1.1.1.tar.gz", hash = "sha256:8eea65bb45b1aac2e734ba2cc8dad3a6d97d97901a395bd0ed3e7b46953d2a31", size = 44711 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/81/4849059526d02fcc9708e19346dd740e8b9edd2f0675ea7c38302d6729df/build-1.1.1-py3-none-any.whl", hash = "sha256:8ed0851ee76e6e38adce47e4bee3b51c771d86c64cf578d0c2245567ee200e73", size = 19719 }, +] + +[[package]] +name = "cachetools" +version = "5.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/38/a0f315319737ecf45b4319a8cd1f3a908e29d9277b46942263292115eee7/cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a", size = 27661 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/07/14f8ad37f2d12a5ce41206c21820d8cb6561b728e51fad4530dff0552a67/cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292", size = 9524 }, +] + +[[package]] +name = "certifi" +version = "2024.8.30" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 }, +] + +[[package]] +name = "cffi" +version = "1.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2b/a8/050ab4f0c3d4c1b8aaa805f70e26e84d0e27004907c5b8ecc1d31815f92a/cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9", size = 508501 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/a3/c5f01988ddb70a187c3e6112152e01696188c9f8a4fa4c68aa330adbb179/cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd", size = 421712 }, + { url = "https://files.pythonhosted.org/packages/ef/41/19da352d341963d29a33bdb28433ba94c05672fb16155f794fad3fd907b0/cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc", size = 449886 }, + { url = "https://files.pythonhosted.org/packages/af/da/9441d56d7dd19d07dcc40a2a5031a1f51c82a27cee3705edf53dadcac398/cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f", size = 450520 }, + { url = "https://files.pythonhosted.org/packages/aa/02/ab15b3aa572759df752491d5fa0f74128cd14e002e8e3257c1ab1587810b/cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e", size = 446015 }, + { url = "https://files.pythonhosted.org/packages/88/89/c34caf63029fb7628ec2ebd5c88ae0c9bd17db98c812e4065a4d020ca41f/cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4", size = 441830 }, + { url = "https://files.pythonhosted.org/packages/32/bd/d0809593f7976828f06a492716fbcbbfb62798bbf60ea1f65200b8d49901/cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01", size = 434743 }, + { url = "https://files.pythonhosted.org/packages/0e/65/0d7b5dad821ced4dcd43f96a362905a68ce71e6b5f5cfd2fada867840582/cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e", size = 464113 }, + { url = "https://files.pythonhosted.org/packages/10/72/617ee266192223a38b67149c830bd9376b69cf3551e1477abc72ff23ef8e/cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9", size = 441694 }, + { url = "https://files.pythonhosted.org/packages/91/bc/b7723c2fe7a22eee71d7edf2102cd43423d5f95ff3932ebaa2f82c7ec8d0/cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c", size = 470613 }, + { url = "https://files.pythonhosted.org/packages/5d/4e/4e0bb5579b01fdbfd4388bd1eb9394a989e1336203a4b7f700d887b233c1/cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325", size = 472199 }, + { url = "https://files.pythonhosted.org/packages/37/5a/c37631a86be838bdd84cc0259130942bf7e6e32f70f4cab95f479847fb91/cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c", size = 462588 }, + { url = "https://files.pythonhosted.org/packages/71/d7/0fe0d91b0bbf610fb7254bb164fa8931596e660d62e90fb6289b7ee27b09/cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef", size = 450543 }, + { url = "https://files.pythonhosted.org/packages/d3/56/3e94aa719ae96eeda8b68b3ec6e347e0a23168c6841dc276ccdcdadc9f32/cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8", size = 474253 }, + { url = "https://files.pythonhosted.org/packages/c2/0b/3b09a755ddb977c167e6d209a7536f6ade43bb0654bad42e08df1406b8e4/cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e", size = 405992 }, + { url = "https://files.pythonhosted.org/packages/5b/1a/e1ee5bed11d8b6540c05a8e3c32448832d775364d4461dd6497374533401/cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82", size = 435560 }, + { url = "https://files.pythonhosted.org/packages/d3/e1/e55ca2e0dd446caa2cc8f73c2b98879c04a1f4064ac529e1836683ca58b8/cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b", size = 435478 }, + { url = "https://files.pythonhosted.org/packages/2e/7a/68c35c151e5b7a12650ecc12fdfb85211aa1da43e9924598451c4a0a3839/cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c", size = 430395 }, + { url = "https://files.pythonhosted.org/packages/93/d0/2e2b27ea2f69b0ec9e481647822f8f77f5fc23faca2dd00d1ff009940eb7/cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426", size = 427911 }, + { url = "https://files.pythonhosted.org/packages/22/c6/df826563f55f7e9dd9a1d3617866282afa969fe0d57decffa1911f416ed8/cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a", size = 421947 }, + { url = "https://files.pythonhosted.org/packages/c1/25/16a082701378170559bb1d0e9ef2d293cece8dc62913d79351beb34c5ddf/cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5", size = 449906 }, + { url = "https://files.pythonhosted.org/packages/df/02/aef53d4aa43154b829e9707c8c60bab413cd21819c4a36b0d7aaa83e2a61/cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca", size = 451028 }, + { url = "https://files.pythonhosted.org/packages/79/4b/33494eb0adbcd884656c48f6db0c98ad8a5c678fb8fb5ed41ab546b04d8c/cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02", size = 446520 }, + { url = "https://files.pythonhosted.org/packages/b7/8b/06f30caa03b5b3ac006de4f93478dbd0239e2a16566d81a106c322dc4f79/cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192", size = 442655 }, + { url = "https://files.pythonhosted.org/packages/85/1f/a3c533f8d377da5ca7edb4f580cc3edc1edbebc45fac8bb3ae60f1176629/cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415", size = 420641 }, + { url = "https://files.pythonhosted.org/packages/77/b7/d3618d612be01e184033eab90006f8ca5b5edafd17bf247439ea4e167d8a/cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d", size = 448814 }, + { url = "https://files.pythonhosted.org/packages/a9/ba/e082df21ebaa9cb29f2c4e1d7e49a29b90fcd667d43632c6674a16d65382/cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984", size = 449647 }, + { url = "https://files.pythonhosted.org/packages/af/cb/53b7bba75a18372d57113ba934b27d0734206c283c1dfcc172347fbd9f76/cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35", size = 445191 }, + { url = "https://files.pythonhosted.org/packages/2d/86/3ca57cddfa0419f6a95d1c8478f8f622ba597e3581fd501bbb915b20eb75/cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27", size = 441236 }, + { url = "https://files.pythonhosted.org/packages/ad/26/7b3a73ab7d82a64664c7c4ea470e4ec4a3c73bb4f02575c543a41e272de5/cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76", size = 433865 }, + { url = "https://files.pythonhosted.org/packages/da/ff/ab939e2c7b3f40d851c0f7192c876f1910f3442080c9c846532993ec3cef/cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3", size = 463090 }, +] + +[[package]] +name = "chardet" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/4f/e1808dc01273379acc506d18f1504eb2d299bd4131743b9fc54d7be4df1e/charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", size = 106620 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/8b/825cc84cf13a28bfbcba7c416ec22bf85a9584971be15b21dd8300c65b7f/charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6", size = 196363 }, + { url = "https://files.pythonhosted.org/packages/23/81/d7eef6a99e42c77f444fdd7bc894b0ceca6c3a95c51239e74a722039521c/charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b", size = 125639 }, + { url = "https://files.pythonhosted.org/packages/21/67/b4564d81f48042f520c948abac7079356e94b30cb8ffb22e747532cf469d/charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99", size = 120451 }, + { url = "https://files.pythonhosted.org/packages/c2/72/12a7f0943dd71fb5b4e7b55c41327ac0a1663046a868ee4d0d8e9c369b85/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca", size = 140041 }, + { url = "https://files.pythonhosted.org/packages/67/56/fa28c2c3e31217c4c52158537a2cf5d98a6c1e89d31faf476c89391cd16b/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d", size = 150333 }, + { url = "https://files.pythonhosted.org/packages/f9/d2/466a9be1f32d89eb1554cf84073a5ed9262047acee1ab39cbaefc19635d2/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7", size = 142921 }, + { url = "https://files.pythonhosted.org/packages/f8/01/344ec40cf5d85c1da3c1f57566c59e0c9b56bcc5566c08804a95a6cc8257/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3", size = 144785 }, + { url = "https://files.pythonhosted.org/packages/73/8b/2102692cb6d7e9f03b9a33a710e0164cadfce312872e3efc7cfe22ed26b4/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907", size = 146631 }, + { url = "https://files.pythonhosted.org/packages/d8/96/cc2c1b5d994119ce9f088a9a0c3ebd489d360a2eb058e2c8049f27092847/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b", size = 140867 }, + { url = "https://files.pythonhosted.org/packages/c9/27/cde291783715b8ec30a61c810d0120411844bc4c23b50189b81188b273db/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912", size = 149273 }, + { url = "https://files.pythonhosted.org/packages/3a/a4/8633b0fc1a2d1834d5393dafecce4a1cc56727bfd82b4dc18fc92f0d3cc3/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95", size = 152437 }, + { url = "https://files.pythonhosted.org/packages/64/ea/69af161062166b5975ccbb0961fd2384853190c70786f288684490913bf5/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e", size = 150087 }, + { url = "https://files.pythonhosted.org/packages/3b/fd/e60a9d9fd967f4ad5a92810138192f825d77b4fa2a557990fd575a47695b/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe", size = 145142 }, + { url = "https://files.pythonhosted.org/packages/6d/02/8cb0988a1e49ac9ce2eed1e07b77ff118f2923e9ebd0ede41ba85f2dcb04/charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc", size = 94701 }, + { url = "https://files.pythonhosted.org/packages/d6/20/f1d4670a8a723c46be695dff449d86d6092916f9e99c53051954ee33a1bc/charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749", size = 102191 }, + { url = "https://files.pythonhosted.org/packages/9c/61/73589dcc7a719582bf56aae309b6103d2762b526bffe189d635a7fcfd998/charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c", size = 193339 }, + { url = "https://files.pythonhosted.org/packages/77/d5/8c982d58144de49f59571f940e329ad6e8615e1e82ef84584c5eeb5e1d72/charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944", size = 124366 }, + { url = "https://files.pythonhosted.org/packages/bf/19/411a64f01ee971bed3231111b69eb56f9331a769072de479eae7de52296d/charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee", size = 118874 }, + { url = "https://files.pythonhosted.org/packages/4c/92/97509850f0d00e9f14a46bc751daabd0ad7765cff29cdfb66c68b6dad57f/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c", size = 138243 }, + { url = "https://files.pythonhosted.org/packages/e2/29/d227805bff72ed6d6cb1ce08eec707f7cfbd9868044893617eb331f16295/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6", size = 148676 }, + { url = "https://files.pythonhosted.org/packages/13/bc/87c2c9f2c144bedfa62f894c3007cd4530ba4b5351acb10dc786428a50f0/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea", size = 141289 }, + { url = "https://files.pythonhosted.org/packages/eb/5b/6f10bad0f6461fa272bfbbdf5d0023b5fb9bc6217c92bf068fa5a99820f5/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc", size = 142585 }, + { url = "https://files.pythonhosted.org/packages/3b/a0/a68980ab8a1f45a36d9745d35049c1af57d27255eff8c907e3add84cf68f/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5", size = 144408 }, + { url = "https://files.pythonhosted.org/packages/d7/a1/493919799446464ed0299c8eef3c3fad0daf1c3cd48bff9263c731b0d9e2/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594", size = 139076 }, + { url = "https://files.pythonhosted.org/packages/fb/9d/9c13753a5a6e0db4a0a6edb1cef7aee39859177b64e1a1e748a6e3ba62c2/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c", size = 146874 }, + { url = "https://files.pythonhosted.org/packages/75/d2/0ab54463d3410709c09266dfb416d032a08f97fd7d60e94b8c6ef54ae14b/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365", size = 150871 }, + { url = "https://files.pythonhosted.org/packages/8d/c9/27e41d481557be53d51e60750b85aa40eaf52b841946b3cdeff363105737/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129", size = 148546 }, + { url = "https://files.pythonhosted.org/packages/ee/44/4f62042ca8cdc0cabf87c0fc00ae27cd8b53ab68be3605ba6d071f742ad3/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236", size = 143048 }, + { url = "https://files.pythonhosted.org/packages/01/f8/38842422988b795220eb8038745d27a675ce066e2ada79516c118f291f07/charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99", size = 94389 }, + { url = "https://files.pythonhosted.org/packages/0b/6e/b13bd47fa9023b3699e94abf565b5a2f0b0be6e9ddac9812182596ee62e4/charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27", size = 101752 }, + { url = "https://files.pythonhosted.org/packages/d3/0b/4b7a70987abf9b8196845806198975b6aab4ce016632f817ad758a5aa056/charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6", size = 194445 }, + { url = "https://files.pythonhosted.org/packages/50/89/354cc56cf4dd2449715bc9a0f54f3aef3dc700d2d62d1fa5bbea53b13426/charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf", size = 125275 }, + { url = "https://files.pythonhosted.org/packages/fa/44/b730e2a2580110ced837ac083d8ad222343c96bb6b66e9e4e706e4d0b6df/charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db", size = 119020 }, + { url = "https://files.pythonhosted.org/packages/9d/e4/9263b8240ed9472a2ae7ddc3e516e71ef46617fe40eaa51221ccd4ad9a27/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1", size = 139128 }, + { url = "https://files.pythonhosted.org/packages/6b/e3/9f73e779315a54334240353eaea75854a9a690f3f580e4bd85d977cb2204/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03", size = 149277 }, + { url = "https://files.pythonhosted.org/packages/1a/cf/f1f50c2f295312edb8a548d3fa56a5c923b146cd3f24114d5adb7e7be558/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284", size = 142174 }, + { url = "https://files.pythonhosted.org/packages/16/92/92a76dc2ff3a12e69ba94e7e05168d37d0345fa08c87e1fe24d0c2a42223/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15", size = 143838 }, + { url = "https://files.pythonhosted.org/packages/a4/01/2117ff2b1dfc61695daf2babe4a874bca328489afa85952440b59819e9d7/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8", size = 146149 }, + { url = "https://files.pythonhosted.org/packages/f6/9b/93a332b8d25b347f6839ca0a61b7f0287b0930216994e8bf67a75d050255/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2", size = 140043 }, + { url = "https://files.pythonhosted.org/packages/ab/f6/7ac4a01adcdecbc7a7587767c776d53d369b8b971382b91211489535acf0/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719", size = 148229 }, + { url = "https://files.pythonhosted.org/packages/9d/be/5708ad18161dee7dc6a0f7e6cf3a88ea6279c3e8484844c0590e50e803ef/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631", size = 151556 }, + { url = "https://files.pythonhosted.org/packages/5a/bb/3d8bc22bacb9eb89785e83e6723f9888265f3a0de3b9ce724d66bd49884e/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b", size = 149772 }, + { url = "https://files.pythonhosted.org/packages/f7/fa/d3fc622de05a86f30beea5fc4e9ac46aead4731e73fd9055496732bcc0a4/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565", size = 144800 }, + { url = "https://files.pythonhosted.org/packages/9a/65/bdb9bc496d7d190d725e96816e20e2ae3a6fa42a5cac99c3c3d6ff884118/charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7", size = 94836 }, + { url = "https://files.pythonhosted.org/packages/3e/67/7b72b69d25b89c0b3cea583ee372c43aa24df15f0e0f8d3982c57804984b/charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9", size = 102187 }, + { url = "https://files.pythonhosted.org/packages/f3/89/68a4c86f1a0002810a27f12e9a7b22feb198c59b2f05231349fbce5c06f4/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114", size = 194617 }, + { url = "https://files.pythonhosted.org/packages/4f/cd/8947fe425e2ab0aa57aceb7807af13a0e4162cd21eee42ef5b053447edf5/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed", size = 125310 }, + { url = "https://files.pythonhosted.org/packages/5b/f0/b5263e8668a4ee9becc2b451ed909e9c27058337fda5b8c49588183c267a/charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250", size = 119126 }, + { url = "https://files.pythonhosted.org/packages/ff/6e/e445afe4f7fda27a533f3234b627b3e515a1b9429bc981c9a5e2aa5d97b6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920", size = 139342 }, + { url = "https://files.pythonhosted.org/packages/a1/b2/4af9993b532d93270538ad4926c8e37dc29f2111c36f9c629840c57cd9b3/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64", size = 149383 }, + { url = "https://files.pythonhosted.org/packages/fb/6f/4e78c3b97686b871db9be6f31d64e9264e889f8c9d7ab33c771f847f79b7/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23", size = 142214 }, + { url = "https://files.pythonhosted.org/packages/2b/c9/1c8fe3ce05d30c87eff498592c89015b19fade13df42850aafae09e94f35/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc", size = 144104 }, + { url = "https://files.pythonhosted.org/packages/ee/68/efad5dcb306bf37db7db338338e7bb8ebd8cf38ee5bbd5ceaaaa46f257e6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d", size = 146255 }, + { url = "https://files.pythonhosted.org/packages/0c/75/1ed813c3ffd200b1f3e71121c95da3f79e6d2a96120163443b3ad1057505/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88", size = 140251 }, + { url = "https://files.pythonhosted.org/packages/7d/0d/6f32255c1979653b448d3c709583557a4d24ff97ac4f3a5be156b2e6a210/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90", size = 148474 }, + { url = "https://files.pythonhosted.org/packages/ac/a0/c1b5298de4670d997101fef95b97ac440e8c8d8b4efa5a4d1ef44af82f0d/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b", size = 151849 }, + { url = "https://files.pythonhosted.org/packages/04/4f/b3961ba0c664989ba63e30595a3ed0875d6790ff26671e2aae2fdc28a399/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d", size = 149781 }, + { url = "https://files.pythonhosted.org/packages/d8/90/6af4cd042066a4adad58ae25648a12c09c879efa4849c705719ba1b23d8c/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482", size = 144970 }, + { url = "https://files.pythonhosted.org/packages/cc/67/e5e7e0cbfefc4ca79025238b43cdf8a2037854195b37d6417f3d0895c4c2/charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67", size = 94973 }, + { url = "https://files.pythonhosted.org/packages/65/97/fc9bbc54ee13d33dc54a7fcf17b26368b18505500fc01e228c27b5222d80/charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b", size = 102308 }, + { url = "https://files.pythonhosted.org/packages/28/9b/64f11b42d34a9f1fcd05827dd695e91e0b30ac35a9a7aaeb93e84d9f8c76/charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2", size = 121758 }, + { url = "https://files.pythonhosted.org/packages/1f/18/0fc3d61a244ffdee01374b751387f9154ecb8a4d5f931227ecfd31ab46f0/charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7", size = 134949 }, + { url = "https://files.pythonhosted.org/packages/3f/b5/354d544f60614aeb6bf73d3b6c955933e0af5eeaf2eec8c0637293a995bc/charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51", size = 144221 }, + { url = "https://files.pythonhosted.org/packages/93/5f/a2acc6e2a47d053760caece2d7b7194e9949945091eff452019765b87146/charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574", size = 137301 }, + { url = "https://files.pythonhosted.org/packages/27/9f/68c828438af904d830e680a8d2f6182be97f3205d6d62edfeb4a029b4192/charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf", size = 138257 }, + { url = "https://files.pythonhosted.org/packages/27/4b/522e1c868960b6be2f88cd407a284f99801421a6c5ae214f0c33de131fde/charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455", size = 140933 }, + { url = "https://files.pythonhosted.org/packages/03/f8/f9b90f5aed190d63e620d9f61db1cef5f30dbb19075e5c114329483d069a/charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6", size = 134954 }, + { url = "https://files.pythonhosted.org/packages/0f/8e/44cde4d583038cbe37867efe7af4699212e6104fca0e7fc010e5f3d4a5c9/charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748", size = 142158 }, + { url = "https://files.pythonhosted.org/packages/28/2c/c6c5b3d70a9e09fcde6b0901789d8c3c2f44ef12abae070a33a342d239a9/charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62", size = 145701 }, + { url = "https://files.pythonhosted.org/packages/1c/9d/fb7f6b68f88e8becca86eb7cba1d5a5429fbfaaa6dd7a3a9f62adaee44d3/charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4", size = 144846 }, + { url = "https://files.pythonhosted.org/packages/b2/8d/fb3d3d3d5a09202d7ef1983848b41a5928b0c907e5692a8d13b1c07a10de/charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621", size = 138254 }, + { url = "https://files.pythonhosted.org/packages/28/d3/efc854ab04626167ad1709e527c4f2a01f5e5cd9c1d5691094d1b7d49154/charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149", size = 93163 }, + { url = "https://files.pythonhosted.org/packages/b6/33/cf8f2602715863219804c1790374b611f08be515176229de078f807d71e3/charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee", size = 99861 }, + { url = "https://files.pythonhosted.org/packages/86/f4/ccab93e631e7293cca82f9f7ba39783c967f823a0000df2d8dd743cad74f/charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578", size = 193961 }, + { url = "https://files.pythonhosted.org/packages/94/d4/2b21cb277bac9605026d2d91a4a8872bc82199ed11072d035dc674c27223/charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6", size = 124507 }, + { url = "https://files.pythonhosted.org/packages/9a/e0/a7c1fcdff20d9c667342e0391cfeb33ab01468d7d276b2c7914b371667cc/charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417", size = 119298 }, + { url = "https://files.pythonhosted.org/packages/70/de/1538bb2f84ac9940f7fa39945a5dd1d22b295a89c98240b262fc4b9fcfe0/charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51", size = 139328 }, + { url = "https://files.pythonhosted.org/packages/e9/ca/288bb1a6bc2b74fb3990bdc515012b47c4bc5925c8304fc915d03f94b027/charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41", size = 149368 }, + { url = "https://files.pythonhosted.org/packages/aa/75/58374fdaaf8406f373e508dab3486a31091f760f99f832d3951ee93313e8/charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f", size = 141944 }, + { url = "https://files.pythonhosted.org/packages/32/c8/0bc558f7260db6ffca991ed7166494a7da4fda5983ee0b0bfc8ed2ac6ff9/charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8", size = 143326 }, + { url = "https://files.pythonhosted.org/packages/0e/dd/7f6fec09a1686446cee713f38cf7d5e0669e0bcc8288c8e2924e998cf87d/charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab", size = 146171 }, + { url = "https://files.pythonhosted.org/packages/4c/a8/440f1926d6d8740c34d3ca388fbd718191ec97d3d457a0677eb3aa718fce/charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12", size = 139711 }, + { url = "https://files.pythonhosted.org/packages/e9/7f/4b71e350a3377ddd70b980bea1e2cc0983faf45ba43032b24b2578c14314/charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19", size = 148348 }, + { url = "https://files.pythonhosted.org/packages/1e/70/17b1b9202531a33ed7ef41885f0d2575ae42a1e330c67fddda5d99ad1208/charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea", size = 151290 }, + { url = "https://files.pythonhosted.org/packages/44/30/574b5b5933d77ecb015550aafe1c7d14a8cd41e7e6c4dcea5ae9e8d496c3/charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858", size = 149114 }, + { url = "https://files.pythonhosted.org/packages/0b/11/ca7786f7e13708687443082af20d8341c02e01024275a28bc75032c5ce5d/charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654", size = 143856 }, + { url = "https://files.pythonhosted.org/packages/f9/c2/1727c1438256c71ed32753b23ec2e6fe7b6dff66a598f6566cfe8139305e/charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613", size = 94333 }, + { url = "https://files.pythonhosted.org/packages/09/c8/0e17270496a05839f8b500c1166e3261d1226e39b698a735805ec206967b/charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade", size = 101454 }, + { url = "https://files.pythonhosted.org/packages/54/2f/28659eee7f5d003e0f5a3b572765bf76d6e0fe6601ab1f1b1dd4cba7e4f1/charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa", size = 196326 }, + { url = "https://files.pythonhosted.org/packages/d1/18/92869d5c0057baa973a3ee2af71573be7b084b3c3d428fe6463ce71167f8/charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a", size = 125614 }, + { url = "https://files.pythonhosted.org/packages/d6/27/327904c5a54a7796bb9f36810ec4173d2df5d88b401d2b95ef53111d214e/charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0", size = 120450 }, + { url = "https://files.pythonhosted.org/packages/a4/23/65af317914a0308495133b2d654cf67b11bbd6ca16637c4e8a38f80a5a69/charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a", size = 140135 }, + { url = "https://files.pythonhosted.org/packages/f2/41/6190102ad521a8aa888519bb014a74251ac4586cde9b38e790901684f9ab/charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242", size = 150413 }, + { url = "https://files.pythonhosted.org/packages/7b/ab/f47b0159a69eab9bd915591106859f49670c75f9a19082505ff16f50efc0/charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b", size = 142992 }, + { url = "https://files.pythonhosted.org/packages/28/89/60f51ad71f63aaaa7e51a2a2ad37919985a341a1d267070f212cdf6c2d22/charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62", size = 144871 }, + { url = "https://files.pythonhosted.org/packages/0c/48/0050550275fea585a6e24460b42465020b53375017d8596c96be57bfabca/charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0", size = 146756 }, + { url = "https://files.pythonhosted.org/packages/dc/b5/47f8ee91455946f745e6c9ddbb0f8f50314d2416dd922b213e7d5551ad09/charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd", size = 141034 }, + { url = "https://files.pythonhosted.org/packages/84/79/5c731059ebab43e80bf61fa51666b9b18167974b82004f18c76378ed31a3/charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be", size = 149434 }, + { url = "https://files.pythonhosted.org/packages/ca/f3/0719cd09fc4dc42066f239cb3c48ced17fc3316afca3e2a30a4756fe49ab/charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d", size = 152443 }, + { url = "https://files.pythonhosted.org/packages/f7/0e/c6357297f1157c8e8227ff337e93fd0a90e498e3d6ab96b2782204ecae48/charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3", size = 150294 }, + { url = "https://files.pythonhosted.org/packages/54/9a/acfa96dc4ea8c928040b15822b59d0863d6e1757fba8bd7de3dc4f761c13/charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742", size = 145314 }, + { url = "https://files.pythonhosted.org/packages/73/1c/b10a63032eaebb8d7bcb8544f12f063f41f5f463778ac61da15d9985e8b6/charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2", size = 94724 }, + { url = "https://files.pythonhosted.org/packages/c5/77/3a78bf28bfaa0863f9cfef278dbeadf55efe064eafff8c7c424ae3c4c1bf/charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca", size = 102159 }, + { url = "https://files.pythonhosted.org/packages/bf/9b/08c0432272d77b04803958a4598a51e2a4b51c06640af8b8f0f908c18bf2/charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", size = 49446 }, +] + +[[package]] +name = "click" +version = "8.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "importlib-metadata", marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 }, +] + +[[package]] +name = "click-default-group" +version = "1.2.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/ce/edb087fb53de63dad3b36408ca30368f438738098e668b78c87f93cd41df/click_default_group-1.2.4.tar.gz", hash = "sha256:eb3f3c99ec0d456ca6cd2a7f08f7d4e91771bef51b01bdd9580cc6450fe1251e", size = 3505 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/1a/aff8bb287a4b1400f69e09a53bd65de96aa5cee5691925b38731c67fc695/click_default_group-1.2.4-py2.py3-none-any.whl", hash = "sha256:9b60486923720e7fc61731bdb32b617039aba820e22e1c88766b1125592eaa5f", size = 4123 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "coverage" +version = "7.2.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/8b/421f30467e69ac0e414214856798d4bc32da1336df745e49e49ae5c1e2a8/coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59", size = 762575 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/24/be01e62a7bce89bcffe04729c540382caa5a06bee45ae42136c93e2499f5/coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8", size = 200724 }, + { url = "https://files.pythonhosted.org/packages/3d/80/7060a445e1d2c9744b683dc935248613355657809d6c6b2716cdf4ca4766/coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb", size = 201024 }, + { url = "https://files.pythonhosted.org/packages/b8/9d/926fce7e03dbfc653104c2d981c0fa71f0572a9ebd344d24c573bd6f7c4f/coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6", size = 229528 }, + { url = "https://files.pythonhosted.org/packages/d1/3a/67f5d18f911abf96857f6f7e4df37ca840e38179e2cc9ab6c0b9c3380f19/coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2", size = 227842 }, + { url = "https://files.pythonhosted.org/packages/b4/bd/1b2331e3a04f4cc9b7b332b1dd0f3a1261dfc4114f8479bebfcc2afee9e8/coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063", size = 228717 }, + { url = "https://files.pythonhosted.org/packages/2b/86/3dbf9be43f8bf6a5ca28790a713e18902b2d884bc5fa9512823a81dff601/coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1", size = 234632 }, + { url = "https://files.pythonhosted.org/packages/91/e8/469ed808a782b9e8305a08bad8c6fa5f8e73e093bda6546c5aec68275bff/coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353", size = 232875 }, + { url = "https://files.pythonhosted.org/packages/29/8f/4fad1c2ba98104425009efd7eaa19af9a7c797e92d40cd2ec026fa1f58cb/coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495", size = 234094 }, + { url = "https://files.pythonhosted.org/packages/94/4e/d4e46a214ae857be3d7dc5de248ba43765f60daeb1ab077cb6c1536c7fba/coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818", size = 203184 }, + { url = "https://files.pythonhosted.org/packages/1f/e9/d6730247d8dec2a3dddc520ebe11e2e860f0f98cee3639e23de6cf920255/coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850", size = 204096 }, + { url = "https://files.pythonhosted.org/packages/c6/fa/529f55c9a1029c840bcc9109d5a15ff00478b7ff550a1ae361f8745f8ad5/coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f", size = 200895 }, + { url = "https://files.pythonhosted.org/packages/67/d7/cd8fe689b5743fffac516597a1222834c42b80686b99f5b44ef43ccc2a43/coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe", size = 201120 }, + { url = "https://files.pythonhosted.org/packages/8c/95/16eed713202406ca0a37f8ac259bbf144c9d24f9b8097a8e6ead61da2dbb/coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3", size = 233178 }, + { url = "https://files.pythonhosted.org/packages/c1/49/4d487e2ad5d54ed82ac1101e467e8994c09d6123c91b2a962145f3d262c2/coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f", size = 230754 }, + { url = "https://files.pythonhosted.org/packages/a7/cd/3ce94ad9d407a052dc2a74fbeb1c7947f442155b28264eb467ee78dea812/coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb", size = 232558 }, + { url = "https://files.pythonhosted.org/packages/8f/a8/12cc7b261f3082cc299ab61f677f7e48d93e35ca5c3c2f7241ed5525ccea/coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833", size = 241509 }, + { url = "https://files.pythonhosted.org/packages/04/fa/43b55101f75a5e9115259e8be70ff9279921cb6b17f04c34a5702ff9b1f7/coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97", size = 239924 }, + { url = "https://files.pythonhosted.org/packages/68/5f/d2bd0f02aa3c3e0311986e625ccf97fdc511b52f4f1a063e4f37b624772f/coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a", size = 240977 }, + { url = "https://files.pythonhosted.org/packages/ba/92/69c0722882643df4257ecc5437b83f4c17ba9e67f15dc6b77bad89b6982e/coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a", size = 203168 }, + { url = "https://files.pythonhosted.org/packages/b1/96/c12ed0dfd4ec587f3739f53eb677b9007853fd486ccb0e7d5512a27bab2e/coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562", size = 204185 }, + { url = "https://files.pythonhosted.org/packages/ff/d5/52fa1891d1802ab2e1b346d37d349cb41cdd4fd03f724ebbf94e80577687/coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4", size = 201020 }, + { url = "https://files.pythonhosted.org/packages/24/df/6765898d54ea20e3197a26d26bb65b084deefadd77ce7de946b9c96dfdc5/coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4", size = 233994 }, + { url = "https://files.pythonhosted.org/packages/15/81/b108a60bc758b448c151e5abceed027ed77a9523ecbc6b8a390938301841/coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01", size = 231358 }, + { url = "https://files.pythonhosted.org/packages/61/90/c76b9462f39897ebd8714faf21bc985b65c4e1ea6dff428ea9dc711ed0dd/coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6", size = 233316 }, + { url = "https://files.pythonhosted.org/packages/04/d6/8cba3bf346e8b1a4fb3f084df7d8cea25a6b6c56aaca1f2e53829be17e9e/coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d", size = 240159 }, + { url = "https://files.pythonhosted.org/packages/6e/ea/4a252dc77ca0605b23d477729d139915e753ee89e4c9507630e12ad64a80/coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de", size = 238127 }, + { url = "https://files.pythonhosted.org/packages/9f/5c/d9760ac497c41f9c4841f5972d0edf05d50cad7814e86ee7d133ec4a0ac8/coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d", size = 239833 }, + { url = "https://files.pythonhosted.org/packages/69/8c/26a95b08059db1cbb01e4b0e6d40f2e9debb628c6ca86b78f625ceaf9bab/coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511", size = 203463 }, + { url = "https://files.pythonhosted.org/packages/b7/00/14b00a0748e9eda26e97be07a63cc911108844004687321ddcc213be956c/coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3", size = 204347 }, + { url = "https://files.pythonhosted.org/packages/80/d7/67937c80b8fd4c909fdac29292bc8b35d9505312cff6bcab41c53c5b1df6/coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f", size = 200580 }, + { url = "https://files.pythonhosted.org/packages/7a/05/084864fa4bbf8106f44fb72a56e67e0cd372d3bf9d893be818338c81af5d/coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb", size = 226237 }, + { url = "https://files.pythonhosted.org/packages/67/a2/6fa66a50e6e894286d79a3564f42bd54a9bd27049dc0a63b26d9924f0aa3/coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9", size = 224256 }, + { url = "https://files.pythonhosted.org/packages/e2/c0/73f139794c742840b9ab88e2e17fe14a3d4668a166ff95d812ac66c0829d/coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd", size = 225550 }, + { url = "https://files.pythonhosted.org/packages/03/ec/6f30b4e0c96ce03b0e64aec46b4af2a8c49b70d1b5d0d69577add757b946/coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a", size = 232440 }, + { url = "https://files.pythonhosted.org/packages/22/c1/2f6c1b6f01a0996c9e067a9c780e1824351dbe17faae54388a4477e6d86f/coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959", size = 230897 }, + { url = "https://files.pythonhosted.org/packages/8d/d6/53e999ec1bf7498ca4bc5f3b8227eb61db39068d2de5dcc359dec5601b5a/coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02", size = 232024 }, + { url = "https://files.pythonhosted.org/packages/e9/40/383305500d24122dbed73e505a4d6828f8f3356d1f68ab6d32c781754b81/coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f", size = 203293 }, + { url = "https://files.pythonhosted.org/packages/0e/bc/7e3a31534fabb043269f14fb64e2bb2733f85d4cf39e5bbc71357c57553a/coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0", size = 204040 }, + { url = "https://files.pythonhosted.org/packages/c6/fc/be19131010930a6cf271da48202c8cc1d3f971f68c02fb2d3a78247f43dc/coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5", size = 200689 }, + { url = "https://files.pythonhosted.org/packages/28/d7/9a8de57d87f4bbc6f9a6a5ded1eaac88a89bf71369bb935dac3c0cf2893e/coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5", size = 200986 }, + { url = "https://files.pythonhosted.org/packages/c8/e4/e6182e4697665fb594a7f4e4f27cb3a4dd00c2e3d35c5c706765de8c7866/coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9", size = 230648 }, + { url = "https://files.pythonhosted.org/packages/7b/e3/f552d5871943f747165b92a924055c5d6daa164ae659a13f9018e22f3990/coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6", size = 228511 }, + { url = "https://files.pythonhosted.org/packages/44/55/49f65ccdd4dfd6d5528e966b28c37caec64170c725af32ab312889d2f857/coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e", size = 229852 }, + { url = "https://files.pythonhosted.org/packages/0d/31/340428c238eb506feb96d4fb5c9ea614db1149517f22cc7ab8c6035ef6d9/coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050", size = 235578 }, + { url = "https://files.pythonhosted.org/packages/dd/ce/97c1dd6592c908425622fe7f31c017d11cf0421729b09101d4de75bcadc8/coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5", size = 234079 }, + { url = "https://files.pythonhosted.org/packages/de/a3/5a98dc9e239d0dc5f243ef5053d5b1bdcaa1dee27a691dfc12befeccf878/coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f", size = 234991 }, + { url = "https://files.pythonhosted.org/packages/4a/fb/78986d3022e5ccf2d4370bc43a5fef8374f092b3c21d32499dee8e30b7b6/coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e", size = 203160 }, + { url = "https://files.pythonhosted.org/packages/c3/1c/6b3c9c363fb1433c79128e0d692863deb761b1b78162494abb9e5c328bc0/coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c", size = 204085 }, + { url = "https://files.pythonhosted.org/packages/88/da/495944ebf0ad246235a6bd523810d9f81981f9b81c6059ba1f56e943abe0/coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9", size = 200725 }, + { url = "https://files.pythonhosted.org/packages/ca/0c/3dfeeb1006c44b911ee0ed915350db30325d01808525ae7cc8d57643a2ce/coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2", size = 201022 }, + { url = "https://files.pythonhosted.org/packages/61/af/5964b8d7d9a5c767785644d9a5a63cacba9a9c45cc42ba06d25895ec87be/coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7", size = 229102 }, + { url = "https://files.pythonhosted.org/packages/d9/1d/cd467fceb62c371f9adb1d739c92a05d4e550246daa90412e711226bd320/coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e", size = 227441 }, + { url = "https://files.pythonhosted.org/packages/fe/57/e4f8ad64d84ca9e759d783a052795f62a9f9111585e46068845b1cb52c2b/coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1", size = 228265 }, + { url = "https://files.pythonhosted.org/packages/88/8b/b0d9fe727acae907fa7f1c8194ccb6fe9d02e1c3e9001ecf74c741f86110/coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9", size = 234217 }, + { url = "https://files.pythonhosted.org/packages/66/2e/c99fe1f6396d93551aa352c75410686e726cd4ea104479b9af1af22367ce/coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250", size = 232466 }, + { url = "https://files.pythonhosted.org/packages/bb/e9/88747b40c8fb4a783b40222510ce6d66170217eb05d7f46462c36b4fa8cc/coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2", size = 233669 }, + { url = "https://files.pythonhosted.org/packages/b1/d5/a8e276bc005e42114468d4fe03e0a9555786bc51cbfe0d20827a46c1565a/coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb", size = 203199 }, + { url = "https://files.pythonhosted.org/packages/a9/0c/4a848ae663b47f1195abcb09a951751dd61f80b503303b9b9d768e0fd321/coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27", size = 204109 }, + { url = "https://files.pythonhosted.org/packages/67/fb/b3b1d7887e1ea25a9608b0776e480e4bbc303ca95a31fd585555ec4fff5a/coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d", size = 193207 }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[package]] +name = "cryptography" +version = "43.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/05/07b55d1fa21ac18c3a8c79f764e2514e6f6a9698f1be44994f5adf0d29db/cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805", size = 686989 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/01/4896f3d1b392025d4fcbecf40fdea92d3df8662123f6835d0af828d148fd/cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e", size = 3760905 }, + { url = "https://files.pythonhosted.org/packages/0a/be/f9a1f673f0ed4b7f6c643164e513dbad28dd4f2dcdf5715004f172ef24b6/cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f", size = 3977271 }, + { url = "https://files.pythonhosted.org/packages/4e/49/80c3a7b5514d1b416d7350830e8c422a4d667b6d9b16a9392ebfd4a5388a/cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6", size = 3746606 }, + { url = "https://files.pythonhosted.org/packages/0e/16/a28ddf78ac6e7e3f25ebcef69ab15c2c6be5ff9743dd0709a69a4f968472/cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18", size = 3986484 }, + { url = "https://files.pythonhosted.org/packages/01/f5/69ae8da70c19864a32b0315049866c4d411cce423ec169993d0434218762/cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd", size = 3852131 }, + { url = "https://files.pythonhosted.org/packages/fd/db/e74911d95c040f9afd3612b1f732e52b3e517cb80de8bf183be0b7d413c6/cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73", size = 4075647 }, + { url = "https://files.pythonhosted.org/packages/2f/78/55356eb9075d0be6e81b59f45c7b48df87f76a20e73893872170471f3ee8/cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5", size = 3762968 }, + { url = "https://files.pythonhosted.org/packages/2a/2c/488776a3dc843f95f86d2f957ca0fc3407d0242b50bede7fad1e339be03f/cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4", size = 3977754 }, + { url = "https://files.pythonhosted.org/packages/7c/04/2345ca92f7a22f601a9c62961741ef7dd0127c39f7310dffa0041c80f16f/cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7", size = 3749458 }, + { url = "https://files.pythonhosted.org/packages/ac/25/e715fa0bc24ac2114ed69da33adf451a38abb6f3f24ec207908112e9ba53/cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405", size = 3988220 }, + { url = "https://files.pythonhosted.org/packages/21/ce/b9c9ff56c7164d8e2edfb6c9305045fbc0df4508ccfdb13ee66eb8c95b0e/cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16", size = 3853898 }, + { url = "https://files.pythonhosted.org/packages/2a/33/b3682992ab2e9476b9c81fff22f02c8b0a1e6e1d49ee1750a67d85fd7ed2/cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73", size = 4076592 }, + { url = "https://files.pythonhosted.org/packages/93/90/116edd5f8ec23b2dc879f7a42443e073cdad22950d3c8ee834e3b8124543/cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3", size = 3679828 }, + { url = "https://files.pythonhosted.org/packages/d8/32/1e1d78b316aa22c0ba6493cc271c1c309969e5aa5c22c830a1d7ce3471e6/cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83", size = 3908132 }, + { url = "https://files.pythonhosted.org/packages/d7/29/a233efb3e98b13d9175dcb3c3146988ec990896c8fa07e8467cce27d5a80/cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08", size = 3681997 }, + { url = "https://files.pythonhosted.org/packages/c0/cf/c9eea7791b961f279fb6db86c3355cfad29a73141f46427af71852b23b95/cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa", size = 3905208 }, +] + +[[package]] +name = "distlib" +version = "0.3.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 }, +] + +[[package]] +name = "docformatter" +version = "1.7.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "charset-normalizer" }, + { name = "untokenize" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/44/aba2c40cf796121b35835ea8c00bc5d93f2f70730eca53b36b8bbbfaefe1/docformatter-1.7.5.tar.gz", hash = "sha256:ffed3da0daffa2e77f80ccba4f0e50bfa2755e1c10e130102571c890a61b246e", size = 25893 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/95/568a2fca29df365b82012b09b64964a05f4f20ac83c2137b262f3fa3188f/docformatter-1.7.5-py3-none-any.whl", hash = "sha256:a24f5545ed1f30af00d106f5d85dc2fce4959295687c24c8f39f5263afaf9186", size = 32798 }, +] + +[[package]] +name = "docutils" +version = "0.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/330ea8d383eb2ce973df34d1239b3b21e91cd8c865d21ff82902d952f91f/docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6", size = 2056383 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/69/e391bd51bc08ed9141ecd899a0ddb61ab6465309f1eb470905c0c8868081/docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc", size = 570472 }, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, +] + +[[package]] +name = "filelock" +version = "3.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/0b/c506e9e44e4c4b6c89fcecda23dc115bf8e7ff7eb127e0cb9c114cbc9a15/filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81", size = 12441 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/45/ec3407adf6f6b5bf867a4462b2b0af27597a26bd3cd6e2534cb6ab029938/filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec", size = 10923 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769 }, +] + +[[package]] +name = "importlib-metadata" +version = "6.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.8'" }, + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/82/f6e29c8d5c098b6be61460371c2c5591f4a335923639edec43b3830650a4/importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4", size = 53569 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/94/64287b38c7de4c90683630338cf28f129decbba0a44f0c6db35a873c73c4/importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5", size = 22934 }, +] + +[[package]] +name = "importlib-resources" +version = "5.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4e/a2/3cab1de83f95dd15297c15bdc04d50902391d707247cada1f021bbfe2149/importlib_resources-5.12.0.tar.gz", hash = "sha256:4be82589bf5c1d7999aedf2a45159d10cb3ca4f19b2271f8792bc8e6da7b22f6", size = 39894 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/71/c13ea695a4393639830bf96baea956538ba7a9d06fcce7cef10bfff20f72/importlib_resources-5.12.0-py3-none-any.whl", hash = "sha256:7b1deeebbf351c7578e09bf2f63fa2ce8b5ffec296e0d349139d43cca061a81a", size = 36211 }, +] + +[[package]] +name = "incremental" +version = "22.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/86/42/9e87f04fa2cd40e3016f27a4b4572290e95899c6dce317e2cdb580f3ff09/incremental-22.10.0.tar.gz", hash = "sha256:912feeb5e0f7e0188e6f42241d2f450002e11bbc0937c65865045854c24c0bd0", size = 18305 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/51/8073577012492fcd15628e811db585f447c500fa407e944ab3a18ec55fb7/incremental-22.10.0-py2.py3-none-any.whl", hash = "sha256:b864a1f30885ee72c5ac2835a761b8fe8aa9c28b9395cacf27286602688d3e51", size = 16361 }, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + +[[package]] +name = "jaraco-classes" +version = "3.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/02/a956c9bfd2dfe60b30c065ed8e28df7fcf72b292b861dca97e951c145ef6/jaraco.classes-3.2.3.tar.gz", hash = "sha256:89559fa5c1d3c34eff6f631ad80bb21f378dbcbb35dd161fd2c6b93f5be2f98a", size = 9416 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/28/220d3ae0829171c11e50dded4355d17824d60895285631d7eb9dee0ab5e5/jaraco.classes-3.2.3-py3-none-any.whl", hash = "sha256:2353de3288bc6b82120752201c6b1c1a14b058267fa424ed5ce5984e3b922158", size = 5961 }, +] + +[[package]] +name = "jeepney" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/f4/154cf374c2daf2020e05c3c6a03c91348d59b23c5366e968feb198306fdf/jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806", size = 106005 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/72/2a1e2290f1ab1e06f71f3d0f1646c9e4634e70e1d37491535e19266e8dc9/jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755", size = 48435 }, +] + +[[package]] +name = "jinja2" +version = "3.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/55/39036716d19cab0747a5020fc7e907f362fbf48c984b14e62127f7e68e5d/jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", size = 240245 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d", size = 133271 }, +] + +[[package]] +name = "keyring" +version = "24.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata", marker = "python_full_version < '3.12'" }, + { name = "importlib-resources", marker = "python_full_version < '3.9'" }, + { name = "jaraco-classes" }, + { name = "jeepney", marker = "sys_platform == 'linux'" }, + { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, + { name = "secretstorage", marker = "sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/17/9745959c3482eed095db21a459572808c2f735bcbf55adb93afcf9c605c7/keyring-24.1.1.tar.gz", hash = "sha256:3d44a48fa9a254f6c72879d7c88604831ebdaac6ecb0b214308b02953502c510", size = 59094 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/9e/9517ad9978abfd2c579c0f7bd6ff3c549b5e0ea8a0e7ad345879c83a5b87/keyring-24.1.1-py3-none-any.whl", hash = "sha256:bc402c5e501053098bcbd149c4ddbf8e36c6809e572c2d098d4961e88d4c270d", size = 37588 }, +] + +[[package]] +name = "markdown-it-py" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, + { name = "typing-extensions", marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/c0/59bd6d0571986f72899288a95d9d6178d0eebd70b6650f1bb3f0da90f8f7/markdown-it-py-2.2.0.tar.gz", hash = "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1", size = 67120 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/25/2d88e8feee8e055d015343f9b86e370a1ccbec546f2865c98397aaef24af/markdown_it_py-2.2.0-py3-none-any.whl", hash = "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30", size = 84466 }, +] + +[[package]] +name = "markupsafe" +version = "2.1.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/87/5b/aae44c6655f3801e81aa3eef09dbbf012431987ba564d7231722f68df02d/MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", size = 19384 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/54/ad5eb37bf9d51800010a74e4665425831a9db4e7c4e0fde4352e391e808e/MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc", size = 18206 }, + { url = "https://files.pythonhosted.org/packages/6a/4a/a4d49415e600bacae038c67f9fecc1d5433b9d3c71a4de6f33537b89654c/MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5", size = 14079 }, + { url = "https://files.pythonhosted.org/packages/0a/7b/85681ae3c33c385b10ac0f8dd025c30af83c78cec1c37a6aa3b55e67f5ec/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46", size = 26620 }, + { url = "https://files.pythonhosted.org/packages/7c/52/2b1b570f6b8b803cef5ac28fdf78c0da318916c7d2fe9402a84d591b394c/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f", size = 25818 }, + { url = "https://files.pythonhosted.org/packages/29/fe/a36ba8c7ca55621620b2d7c585313efd10729e63ef81e4e61f52330da781/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900", size = 25493 }, + { url = "https://files.pythonhosted.org/packages/60/ae/9c60231cdfda003434e8bd27282b1f4e197ad5a710c14bee8bea8a9ca4f0/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff", size = 30630 }, + { url = "https://files.pythonhosted.org/packages/65/dc/1510be4d179869f5dafe071aecb3f1f41b45d37c02329dfba01ff59e5ac5/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad", size = 29745 }, + { url = "https://files.pythonhosted.org/packages/30/39/8d845dd7d0b0613d86e0ef89549bfb5f61ed781f59af45fc96496e897f3a/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd", size = 30021 }, + { url = "https://files.pythonhosted.org/packages/c7/5c/356a6f62e4f3c5fbf2602b4771376af22a3b16efa74eb8716fb4e328e01e/MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4", size = 16659 }, + { url = "https://files.pythonhosted.org/packages/69/48/acbf292615c65f0604a0c6fc402ce6d8c991276e16c80c46a8f758fbd30c/MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5", size = 17213 }, + { url = "https://files.pythonhosted.org/packages/11/e7/291e55127bb2ae67c64d66cef01432b5933859dfb7d6949daa721b89d0b3/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", size = 18219 }, + { url = "https://files.pythonhosted.org/packages/6b/cb/aed7a284c00dfa7c0682d14df85ad4955a350a21d2e3b06d8240497359bf/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", size = 14098 }, + { url = "https://files.pythonhosted.org/packages/1c/cf/35fe557e53709e93feb65575c93927942087e9b97213eabc3fe9d5b25a55/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", size = 29014 }, + { url = "https://files.pythonhosted.org/packages/97/18/c30da5e7a0e7f4603abfc6780574131221d9148f323752c2755d48abad30/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5", size = 28220 }, + { url = "https://files.pythonhosted.org/packages/0c/40/2e73e7d532d030b1e41180807a80d564eda53babaf04d65e15c1cf897e40/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c", size = 27756 }, + { url = "https://files.pythonhosted.org/packages/18/46/5dca760547e8c59c5311b332f70605d24c99d1303dd9a6e1fc3ed0d73561/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f", size = 33988 }, + { url = "https://files.pythonhosted.org/packages/6d/c5/27febe918ac36397919cd4a67d5579cbbfa8da027fa1238af6285bb368ea/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a", size = 32718 }, + { url = "https://files.pythonhosted.org/packages/f8/81/56e567126a2c2bc2684d6391332e357589a96a76cb9f8e5052d85cb0ead8/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f", size = 33317 }, + { url = "https://files.pythonhosted.org/packages/00/0b/23f4b2470accb53285c613a3ab9ec19dc944eaf53592cb6d9e2af8aa24cc/MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906", size = 16670 }, + { url = "https://files.pythonhosted.org/packages/b7/a2/c78a06a9ec6d04b3445a949615c4c7ed86a0b2eb68e44e7541b9d57067cc/MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617", size = 17224 }, + { url = "https://files.pythonhosted.org/packages/53/bd/583bf3e4c8d6a321938c13f49d44024dbe5ed63e0a7ba127e454a66da974/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", size = 18215 }, + { url = "https://files.pythonhosted.org/packages/48/d6/e7cd795fc710292c3af3a06d80868ce4b02bfbbf370b7cee11d282815a2a/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", size = 14069 }, + { url = "https://files.pythonhosted.org/packages/51/b5/5d8ec796e2a08fc814a2c7d2584b55f889a55cf17dd1a90f2beb70744e5c/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", size = 29452 }, + { url = "https://files.pythonhosted.org/packages/0a/0d/2454f072fae3b5a137c119abf15465d1771319dfe9e4acbb31722a0fff91/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", size = 28462 }, + { url = "https://files.pythonhosted.org/packages/2d/75/fd6cb2e68780f72d47e6671840ca517bda5ef663d30ada7616b0462ad1e3/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", size = 27869 }, + { url = "https://files.pythonhosted.org/packages/b0/81/147c477391c2750e8fc7705829f7351cf1cd3be64406edcf900dc633feb2/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", size = 33906 }, + { url = "https://files.pythonhosted.org/packages/8b/ff/9a52b71839d7a256b563e85d11050e307121000dcebc97df120176b3ad93/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", size = 32296 }, + { url = "https://files.pythonhosted.org/packages/88/07/2dc76aa51b481eb96a4c3198894f38b480490e834479611a4053fbf08623/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", size = 33038 }, + { url = "https://files.pythonhosted.org/packages/96/0c/620c1fb3661858c0e37eb3cbffd8c6f732a67cd97296f725789679801b31/MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", size = 16572 }, + { url = "https://files.pythonhosted.org/packages/3f/14/c3554d512d5f9100a95e737502f4a2323a1959f6d0d01e0d0997b35f7b10/MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", size = 17127 }, + { url = "https://files.pythonhosted.org/packages/a7/88/a940e11827ea1c136a34eca862486178294ae841164475b9ab216b80eb8e/MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f", size = 13982 }, + { url = "https://files.pythonhosted.org/packages/cb/06/0d28bd178db529c5ac762a625c335a9168a7a23f280b4db9c95e97046145/MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf", size = 26335 }, + { url = "https://files.pythonhosted.org/packages/4a/1d/c4f5016f87ced614eacc7d5fb85b25bcc0ff53e8f058d069fc8cbfdc3c7a/MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a", size = 25557 }, + { url = "https://files.pythonhosted.org/packages/b3/fb/c18b8c9fbe69e347fdbf782c6478f1bc77f19a830588daa224236678339b/MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52", size = 25245 }, + { url = "https://files.pythonhosted.org/packages/2f/69/30d29adcf9d1d931c75001dd85001adad7374381c9c2086154d9f6445be6/MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9", size = 31013 }, + { url = "https://files.pythonhosted.org/packages/3a/03/63498d05bd54278b6ca340099e5b52ffb9cdf2ee4f2d9b98246337e21689/MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df", size = 30178 }, + { url = "https://files.pythonhosted.org/packages/68/79/11b4fe15124692f8673b603433e47abca199a08ecd2a4851bfbdc97dc62d/MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50", size = 30429 }, + { url = "https://files.pythonhosted.org/packages/ed/88/408bdbf292eb86f03201c17489acafae8358ba4e120d92358308c15cea7c/MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371", size = 16633 }, + { url = "https://files.pythonhosted.org/packages/6c/4c/3577a52eea1880538c435176bc85e5b3379b7ab442327ccd82118550758f/MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2", size = 17215 }, + { url = "https://files.pythonhosted.org/packages/f8/ff/2c942a82c35a49df5de3a630ce0a8456ac2969691b230e530ac12314364c/MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a", size = 18192 }, + { url = "https://files.pythonhosted.org/packages/4f/14/6f294b9c4f969d0c801a4615e221c1e084722ea6114ab2114189c5b8cbe0/MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46", size = 14072 }, + { url = "https://files.pythonhosted.org/packages/81/d4/fd74714ed30a1dedd0b82427c02fa4deec64f173831ec716da11c51a50aa/MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532", size = 26928 }, + { url = "https://files.pythonhosted.org/packages/c7/bd/50319665ce81bb10e90d1cf76f9e1aa269ea6f7fa30ab4521f14d122a3df/MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab", size = 26106 }, + { url = "https://files.pythonhosted.org/packages/4c/6f/f2b0f675635b05f6afd5ea03c094557bdb8622fa8e673387444fe8d8e787/MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68", size = 25781 }, + { url = "https://files.pythonhosted.org/packages/51/e0/393467cf899b34a9d3678e78961c2c8cdf49fb902a959ba54ece01273fb1/MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0", size = 30518 }, + { url = "https://files.pythonhosted.org/packages/f6/02/5437e2ad33047290dafced9df741d9efc3e716b75583bbd73a9984f1b6f7/MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4", size = 29669 }, + { url = "https://files.pythonhosted.org/packages/0e/7d/968284145ffd9d726183ed6237c77938c021abacde4e073020f920e060b2/MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3", size = 29933 }, + { url = "https://files.pythonhosted.org/packages/bf/f3/ecb00fc8ab02b7beae8699f34db9357ae49d9f21d4d3de6f305f34fa949e/MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff", size = 16656 }, + { url = "https://files.pythonhosted.org/packages/92/21/357205f03514a49b293e214ac39de01fadd0970a6e05e4bf1ddd0ffd0881/MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029", size = 17206 }, + { url = "https://files.pythonhosted.org/packages/0f/31/780bb297db036ba7b7bbede5e1d7f1e14d704ad4beb3ce53fb495d22bc62/MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf", size = 18193 }, + { url = "https://files.pythonhosted.org/packages/6c/77/d77701bbef72892affe060cdacb7a2ed7fd68dae3b477a8642f15ad3b132/MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2", size = 14073 }, + { url = "https://files.pythonhosted.org/packages/d9/a7/1e558b4f78454c8a3a0199292d96159eb4d091f983bc35ef258314fe7269/MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8", size = 26486 }, + { url = "https://files.pythonhosted.org/packages/5f/5a/360da85076688755ea0cceb92472923086993e86b5613bbae9fbc14136b0/MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3", size = 25685 }, + { url = "https://files.pythonhosted.org/packages/6a/18/ae5a258e3401f9b8312f92b028c54d7026a97ec3ab20bfaddbdfa7d8cce8/MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465", size = 25338 }, + { url = "https://files.pythonhosted.org/packages/0b/cc/48206bd61c5b9d0129f4d75243b156929b04c94c09041321456fd06a876d/MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e", size = 30439 }, + { url = "https://files.pythonhosted.org/packages/d1/06/a41c112ab9ffdeeb5f77bc3e331fdadf97fa65e52e44ba31880f4e7f983c/MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea", size = 29531 }, + { url = "https://files.pythonhosted.org/packages/02/8c/ab9a463301a50dab04d5472e998acbd4080597abc048166ded5c7aa768c8/MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6", size = 29823 }, + { url = "https://files.pythonhosted.org/packages/bc/29/9bc18da763496b055d8e98ce476c8e718dcfd78157e17f555ce6dd7d0895/MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf", size = 16658 }, + { url = "https://files.pythonhosted.org/packages/f6/f8/4da07de16f10551ca1f640c92b5f316f9394088b183c6a57183df6de5ae4/MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5", size = 17211 }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, +] + +[[package]] +name = "more-itertools" +version = "9.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/d0/bea165535891bd1dcb5152263603e902c0ec1f4c9a2e152cc4adff6b3a38/more-itertools-9.1.0.tar.gz", hash = "sha256:cabaa341ad0389ea83c17a94566a53ae4c9d07349861ecb14dc6d0345cf9ac5d", size = 107389 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/01/e2678ee4e0d7eed4fd6be9e5b043fff9d22d245d06c8c91def8ced664189/more_itertools-9.1.0-py3-none-any.whl", hash = "sha256:d2bc7f02446e86a68911e58ded76d6561eea00cddfb2a91e7019bbb586c799f3", size = 54207 }, +] + +[[package]] +name = "mypy" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typed-ast", marker = "python_full_version < '3.8'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/28/d8a8233ff167d06108e53b7aefb4a8d7350adbbf9d7abd980f17fdb7a3a6/mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b", size = 2855162 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/3b/1c7363863b56c059f60a1dfdca9ac774a22ba64b7a4da0ee58ee53e5243f/mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8", size = 10451043 }, + { url = "https://files.pythonhosted.org/packages/a7/24/6f0df1874118839db1155fed62a4bd7e80c181367ff8ea07d40fbaffcfb4/mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878", size = 9542079 }, + { url = "https://files.pythonhosted.org/packages/04/5c/deeac94fcccd11aa621e6b350df333e1b809b11443774ea67582cc0205da/mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd", size = 11974913 }, + { url = "https://files.pythonhosted.org/packages/e5/2f/de3c455c54e8cf5e37ea38705c1920f2df470389f8fc051084d2dd8c9c59/mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc", size = 12044492 }, + { url = "https://files.pythonhosted.org/packages/e7/d3/6f65357dcb68109946de70cd55bd2e60f10114f387471302f48d54ff5dae/mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1", size = 8831655 }, + { url = "https://files.pythonhosted.org/packages/94/01/e34e37a044325af4d4af9825c15e8a0d26d89b5a9624b4d0908449d3411b/mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462", size = 10338636 }, + { url = "https://files.pythonhosted.org/packages/92/58/ccc0b714ecbd1a64b34d8ce1c38763ff6431de1d82551904ecc3711fbe05/mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258", size = 9444172 }, + { url = "https://files.pythonhosted.org/packages/73/72/dfc0b46e6905eafd598e7c48c0c4f2e232647e4e36547425c64e6c850495/mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2", size = 11855450 }, + { url = "https://files.pythonhosted.org/packages/66/f4/60739a2d336f3adf5628e7c9b920d16e8af6dc078550d615e4ba2a1d7759/mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7", size = 11928679 }, + { url = "https://files.pythonhosted.org/packages/8c/26/6ff2b55bf8b605a4cc898883654c2ca4dd4feedf0bb04ecaacf60d165cde/mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01", size = 8831134 }, + { url = "https://files.pythonhosted.org/packages/95/47/fb69dad9634af9f1dab69f8b4031d674592384b59c7171852b1fbed6de15/mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b", size = 10101278 }, + { url = "https://files.pythonhosted.org/packages/65/f7/77339904a3415cadca5551f2ea0c74feefc9b7187636a292690788f4d4b3/mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b", size = 11643877 }, + { url = "https://files.pythonhosted.org/packages/f5/93/ae39163ae84266d24d1fcf8ee1e2db1e0346e09de97570dd101a07ccf876/mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7", size = 11702718 }, + { url = "https://files.pythonhosted.org/packages/13/3b/3b7de921626547b36c34b91c74cfbda260210df7c49bd3d315015cfd6005/mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9", size = 8551181 }, + { url = "https://files.pythonhosted.org/packages/49/7d/63bab763e4d44e1a7c341fb64496ddf20970780935596ffed9ed2d85eae7/mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042", size = 10390236 }, + { url = "https://files.pythonhosted.org/packages/23/3f/54a87d933440416a1efd7a42b45f8cf22e353efe889eb3903cc34177ab44/mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3", size = 9496760 }, + { url = "https://files.pythonhosted.org/packages/4e/89/26230b46e27724bd54f76cd73a2759eaaf35292b32ba64f36c7c47836d4b/mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6", size = 11927489 }, + { url = "https://files.pythonhosted.org/packages/64/7d/156e721376951c449554942eedf4d53e9ca2a57e94bf0833ad2821d59bfa/mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f", size = 11990009 }, + { url = "https://files.pythonhosted.org/packages/27/ab/21230851e8137c9ef9a095cc8cb70d8ff8cac21014e4b249ac7a9eae7df9/mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc", size = 8816535 }, + { url = "https://files.pythonhosted.org/packages/1d/1b/9050b5c444ef82c3d59bdbf21f91b259cf20b2ac1df37d55bc6b91d609a1/mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828", size = 10447897 }, + { url = "https://files.pythonhosted.org/packages/da/00/ac2b58b321d85cac25be0dcd1bc2427dfc6cf403283fc205a0031576f14b/mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3", size = 9534091 }, + { url = "https://files.pythonhosted.org/packages/c4/10/26240f14e854a95af87d577b288d607ebe0ccb75cb37052f6386402f022d/mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816", size = 11970165 }, + { url = "https://files.pythonhosted.org/packages/b7/34/a3edaec8762181bfe97439c7e094f4c2f411ed9b79ac8f4d72156e88d5ce/mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c", size = 12040792 }, + { url = "https://files.pythonhosted.org/packages/d1/f3/0d0622d5a83859a992b01741a7b97949d6fb9efc9f05f20a09f0df10dc1e/mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f", size = 8831367 }, + { url = "https://files.pythonhosted.org/packages/3d/9a/e13addb8d652cb068f835ac2746d9d42f85b730092f581bb17e2059c28f1/mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4", size = 2451741 }, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, +] + +[[package]] +name = "packaging" +version = "24.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/b5/b43a27ac7472e1818c4bafd44430e69605baefe1f34440593e0332ec8b4d/packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9", size = 147882 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", size = 53488 }, +] + +[[package]] +name = "pathspec" +version = "0.11.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a0/2a/bd167cdf116d4f3539caaa4c332752aac0b3a0cc0174cdb302ee68933e81/pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3", size = 47032 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/2a/9b1be29146139ef459188f5e420a66e835dda921208db600b7037093891f/pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20", size = 29603 }, +] + +[[package]] +name = "pkginfo" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/72/347ec5be4adc85c182ed2823d8d1c7b51e13b9a6b0c1aae59582eca652df/pkginfo-1.10.0.tar.gz", hash = "sha256:5df73835398d10db79f8eecd5cd86b1f6d29317589ea70796994d49399af6297", size = 378457 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/09/054aea9b7534a15ad38a363a2bd974c20646ab1582a387a95b8df1bfea1c/pkginfo-1.10.0-py3-none-any.whl", hash = "sha256:889a6da2ed7ffc58ab5b900d888ddce90bce912f2d2de1dc1c26f4cb9fe65097", size = 30392 }, +] + +[[package]] +name = "platformdirs" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/31/28/e40d24d2e2eb23135f8533ad33d582359c7825623b1e022f9d460def7c05/platformdirs-4.0.0.tar.gz", hash = "sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731", size = 19914 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/16/70be3b725073035aa5fc3229321d06e22e73e3e09f6af78dcfdf16c7636c/platformdirs-4.0.0-py3-none-any.whl", hash = "sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b", size = 17562 }, +] + +[[package]] +name = "pluggy" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata", marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/42/8f2833655a29c4e9cb52ee8a2be04ceac61bcff4a680fb338cbd3d1e322d/pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3", size = 61613 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/32/4a79112b8b87b21450b066e102d6608907f4c885ed7b04c3fdb085d4d6ae/pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849", size = 17695 }, +] + +[[package]] +name = "pycparser" +version = "2.21" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/0b/95d387f5f4433cb0f53ff7ad859bd2c6051051cebbb564f139a999ab46de/pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206", size = 170877 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/d5/5f610ebe421e85889f2e55e33b7f9a6795bd982198517d912eb1c76e1a53/pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", size = 118697 }, +] + +[[package]] +name = "pygments" +version = "2.17.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/55/59/8bccf4157baf25e4aa5a0bb7fa3ba8600907de105ebc22b0c78cfbf6f565/pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367", size = 4827772 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/9c/372fef8377a6e340b1704768d20daaded98bf13282b5327beb2e2fe2c7ef/pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c", size = 1179756 }, +] + +[[package]] +name = "pyproject-api" +version = "1.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f7/70/a63493ea5066b32053f80fdc24fae7c5a2fc65d8f01a1883b30fd850aa84/pyproject_api-1.5.3.tar.gz", hash = "sha256:ffb5b2d7cad43f5b2688ab490de7c4d3f6f15e0b819cb588c4b771567c9729eb", size = 22128 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/53/b225115e177eb54664ede5b68a23d6806d9890baa8ee66b8d87f0bdb6346/pyproject_api-1.5.3-py3-none-any.whl", hash = "sha256:14cf09828670c7b08842249c1f28c8ee6581b872e893f81b62d5465bec41502f", size = 12889 }, +] + +[[package]] +name = "pyproject-hooks" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216 }, +] + +[[package]] +name = "pyright" +version = "1.1.387" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nodeenv" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/32/e7187478d3105d6d7edc9b754d56472ee06557c25cc404911288fee1796a/pyright-1.1.387.tar.gz", hash = "sha256:577de60224f7fe36505d5b181231e3a395d427b7873be0bbcaa962a29ea93a60", size = 21939 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/18/c497df36641b0572f5bd59ae147b08ccaa6b8086397d50e1af97cc2ddcf6/pyright-1.1.387-py3-none-any.whl", hash = "sha256:6a1f495a261a72e12ad17e20d1ae3df4511223c773b19407cfa006229b1b08a5", size = 18577 }, +] + +[[package]] +name = "pytest" +version = "7.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "importlib-metadata", marker = "python_full_version < '3.8'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/1f/9d8e98e4133ffb16c90f3b405c43e38d3abb715bb5d7a63a5a684f7e46a3/pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280", size = 1357116 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/ff/f6e8b8f39e08547faece4bd80f89d5a8de68a38b2d179cc1c4490ffa3286/pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8", size = 325287 }, +] + +[[package]] +name = "pytest-cov" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/15/da3df99fd551507694a9b01f512a2f6cf1254f33601605843c3775f39460/pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6", size = 63245 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/4b/8b78d126e275efa2379b1c2e09dc52cf70df16fc3b90613ef82531499d73/pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a", size = 21949 }, +] + +[[package]] +name = "pytz" +version = "2024.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/31/3c70bf7603cc2dca0f19bdc53b4537a797747a58875b552c8c413d963a3f/pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a", size = 319692 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/c3/005fcca25ce078d2cc29fd559379817424e94885510568bc1bc53d7d5846/pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725", size = 508002 }, +] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756 }, +] + +[[package]] +name = "readme-renderer" +version = "36.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bleach" }, + { name = "docutils" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/15/4e/0ffa80eb3e0d0fcc0c6b901b36d4faa11c47d10b9a066fdd42f24c7e646a/readme_renderer-36.0.tar.gz", hash = "sha256:f71aeef9a588fcbed1f4cc001ba611370e94a0cd27c75b1140537618ec78f0a2", size = 28680 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/3e/ce07c86adbc46cfedf637dd77333184bcd0a913f9c169b0b3afc25f67b6b/readme_renderer-36.0-py3-none-any.whl", hash = "sha256:2c37e472ca96755caba6cc58bcbf673a5574bc033385a2ac91d85dfef2799876", size = 14076 }, +] + +[[package]] +name = "requests" +version = "2.31.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/be/10918a2eac4ae9f02f6cfe6414b7a155ccd8f7f9d4380d62fd5b955065c3/requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1", size = 110794 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", size = 62574 }, +] + +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481 }, +] + +[[package]] +name = "restview" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "pygments" }, + { name = "readme-renderer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/d4/36ed06051e9702d3dae5f7bb0296b79b18621ff5a1bf43247509cbfeff8d/restview-3.0.2.tar.gz", hash = "sha256:8b4d75a0bed76b67b456ef7011f4eb6c98a556c9f837642df2202c7312fccc1a", size = 50048 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/69/4046cbec75d4574e84fa3b2be53487ae9054129527211961555631805849/restview-3.0.2-py3-none-any.whl", hash = "sha256:9fbd26507f6ddc436a49d0ffced5bc8102a898f289fe53c1234a17be46eb660d", size = 38020 }, +] + +[[package]] +name = "rfc3986" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/40/1520d68bfa07ab5a6f065a186815fb6610c86fe957bc065754e47f7b0840/rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c", size = 49026 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", size = 31326 }, +] + +[[package]] +name = "rich" +version = "13.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, + { name = "typing-extensions", marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/92/76/40f084cb7db51c9d1fa29a7120717892aeda9a7711f6225692c957a93535/rich-13.8.1.tar.gz", hash = "sha256:8260cda28e3db6bf04d2d1ef4dbc03ba80a824c88b0e7668a0f23126a424844a", size = 222080 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/11/dadb85e2bd6b1f1ae56669c3e1f0410797f9605d752d68fb47b77f525b31/rich-13.8.1-py3-none-any.whl", hash = "sha256:1760a3c0848469b97b558fc61c85233e3dafb69c7a071b4d60c38099d3cd4c06", size = 241608 }, +] + +[[package]] +name = "ruff" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/51/231bb3790e5b0b9fd4131f9a231d73d061b3667522e3f406fd9b63334d0e/ruff-0.7.2.tar.gz", hash = "sha256:2b14e77293380e475b4e3a7a368e14549288ed2931fce259a6f99978669e844f", size = 3210036 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/56/0caa2b5745d66a39aa239c01059f6918fc76ed8380033d2f44bf297d141d/ruff-0.7.2-py3-none-linux_armv6l.whl", hash = "sha256:b73f873b5f52092e63ed540adefc3c36f1f803790ecf2590e1df8bf0a9f72cb8", size = 10373973 }, + { url = "https://files.pythonhosted.org/packages/1a/33/cad6ff306731f335d481c50caa155b69a286d5b388e87ff234cd2a4b3557/ruff-0.7.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5b813ef26db1015953daf476202585512afd6a6862a02cde63f3bafb53d0b2d4", size = 10171140 }, + { url = "https://files.pythonhosted.org/packages/97/f5/6a2ca5c9ba416226eac9cf8121a1baa6f06655431937e85f38ffcb9d0d01/ruff-0.7.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:853277dbd9675810c6826dad7a428d52a11760744508340e66bf46f8be9701d9", size = 9809333 }, + { url = "https://files.pythonhosted.org/packages/16/83/e3e87f13d1a1dc205713632978cd7bc287a59b08bc95780dbe359b9aefcb/ruff-0.7.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21aae53ab1490a52bf4e3bf520c10ce120987b047c494cacf4edad0ba0888da2", size = 10622987 }, + { url = "https://files.pythonhosted.org/packages/22/16/97ccab194480e99a2e3c77ae132b3eebfa38c2112747570c403a4a13ba3a/ruff-0.7.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ccc7e0fc6e0cb3168443eeadb6445285abaae75142ee22b2b72c27d790ab60ba", size = 10184640 }, + { url = "https://files.pythonhosted.org/packages/97/1b/82ff05441b036f68817296c14f24da47c591cb27acfda473ee571a5651ac/ruff-0.7.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd77877a4e43b3a98e5ef4715ba3862105e299af0c48942cc6d51ba3d97dc859", size = 11210203 }, + { url = "https://files.pythonhosted.org/packages/a6/96/7ecb30a7ef7f942e2d8e0287ad4c1957dddc6c5097af4978c27cfc334f97/ruff-0.7.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e00163fb897d35523c70d71a46fbaa43bf7bf9af0f4534c53ea5b96b2e03397b", size = 11870894 }, + { url = "https://files.pythonhosted.org/packages/06/6a/c716bb126218227f8e604a9c484836257708a05ee3d2ebceb666ff3d3867/ruff-0.7.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3c54b538633482dc342e9b634d91168fe8cc56b30a4b4f99287f4e339103e88", size = 11449533 }, + { url = "https://files.pythonhosted.org/packages/e6/2f/3a5f9f9478904e5ae9506ea699109070ead1e79aac041e872cbaad8a7458/ruff-0.7.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b792468e9804a204be221b14257566669d1db5c00d6bb335996e5cd7004ba80", size = 12607919 }, + { url = "https://files.pythonhosted.org/packages/a0/57/4642e57484d80d274750dcc872ea66655bbd7e66e986fede31e1865b463d/ruff-0.7.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dba53ed84ac19ae4bfb4ea4bf0172550a2285fa27fbb13e3746f04c80f7fa088", size = 11016915 }, + { url = "https://files.pythonhosted.org/packages/4d/6d/59be6680abee34c22296ae3f46b2a3b91662b8b18ab0bf388b5eb1355c97/ruff-0.7.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b19fafe261bf741bca2764c14cbb4ee1819b67adb63ebc2db6401dcd652e3748", size = 10625424 }, + { url = "https://files.pythonhosted.org/packages/82/e7/f6a643683354c9bc7879d2f228ee0324fea66d253de49273a0814fba1927/ruff-0.7.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:28bd8220f4d8f79d590db9e2f6a0674f75ddbc3847277dd44ac1f8d30684b828", size = 10233692 }, + { url = "https://files.pythonhosted.org/packages/d7/48/b4e02fc835cd7ed1ee7318d9c53e48bcf6b66301f55925a7dcb920e45532/ruff-0.7.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9fd67094e77efbea932e62b5d2483006154794040abb3a5072e659096415ae1e", size = 10751825 }, + { url = "https://files.pythonhosted.org/packages/1e/06/6c5ee6ab7bb4cbad9e8bb9b2dd0d818c759c90c1c9e057c6ed70334b97f4/ruff-0.7.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:576305393998b7bd6c46018f8104ea3a9cb3fa7908c21d8580e3274a3b04b691", size = 11074811 }, + { url = "https://files.pythonhosted.org/packages/a1/16/8969304f25bcd0e4af1778342e63b715e91db8a2dbb51807acd858cba915/ruff-0.7.2-py3-none-win32.whl", hash = "sha256:fa993cfc9f0ff11187e82de874dfc3611df80852540331bc85c75809c93253a8", size = 8650268 }, + { url = "https://files.pythonhosted.org/packages/d9/18/c4b00d161def43fe5968e959039c8f6ce60dca762cec4a34e4e83a4210a0/ruff-0.7.2-py3-none-win_amd64.whl", hash = "sha256:dd8800cbe0254e06b8fec585e97554047fb82c894973f7ff18558eee33d1cb88", size = 9433693 }, + { url = "https://files.pythonhosted.org/packages/7f/7b/c920673ac01c19814dd15fc617c02301c522f3d6812ca2024f4588ed4549/ruff-0.7.2-py3-none-win_arm64.whl", hash = "sha256:bb8368cd45bba3f57bb29cbb8d64b4a33f8415d0149d2655c5c8539452ce7760", size = 8735845 }, +] + +[[package]] +name = "secretstorage" +version = "3.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "jeepney" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/a4/f48c9d79cb507ed1373477dbceaba7401fd8a23af63b837fa61f1dcd3691/SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77", size = 19739 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/24/b4293291fa1dd830f353d2cb163295742fa87f179fcc8a20a306a81978b7/SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99", size = 15221 }, +] + +[[package]] +name = "semver" +source = { editable = "." } + +[package.dev-dependencies] +changelog = [ + { name = "towncrier" }, +] +devel = [ + { name = "black" }, + { name = "build" }, + { name = "docformatter" }, + { name = "mypy" }, + { name = "pyright" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "restview" }, + { name = "ruff" }, + { name = "sphinx" }, + { name = "sphinx-argparse" }, + { name = "sphinx-autodoc-typehints" }, + { name = "towncrier" }, + { name = "tox" }, + { name = "twine" }, +] +docs = [ + { name = "restview" }, + { name = "sphinx" }, + { name = "sphinx-argparse" }, + { name = "sphinx-autodoc-typehints" }, +] +formatting = [ + { name = "black" }, + { name = "docformatter" }, + { name = "ruff" }, +] +gh-action = [ + { name = "black" }, + { name = "build" }, + { name = "docformatter" }, + { name = "mypy" }, + { name = "pyright" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "restview" }, + { name = "ruff" }, + { name = "sphinx" }, + { name = "sphinx-argparse" }, + { name = "sphinx-autodoc-typehints" }, + { name = "towncrier" }, + { name = "tox" }, + { name = "tox-gh-actions" }, + { name = "twine" }, +] +publish = [ + { name = "build" }, + { name = "twine" }, +] +test = [ + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "tox" }, +] +typing = [ + { name = "mypy" }, + { name = "pyright" }, +] + +[package.metadata] + +[package.metadata.requires-dev] +changelog = [{ name = "towncrier" }] +devel = [ + { name = "black" }, + { name = "build" }, + { name = "docformatter" }, + { name = "mypy" }, + { name = "pyright" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "restview" }, + { name = "ruff" }, + { name = "sphinx" }, + { name = "sphinx-argparse" }, + { name = "sphinx-autodoc-typehints" }, + { name = "towncrier" }, + { name = "tox" }, + { name = "twine" }, +] +docs = [ + { name = "restview" }, + { name = "sphinx" }, + { name = "sphinx-argparse" }, + { name = "sphinx-autodoc-typehints" }, +] +formatting = [ + { name = "black" }, + { name = "docformatter" }, + { name = "ruff" }, +] +gh-action = [ + { name = "black" }, + { name = "build" }, + { name = "docformatter" }, + { name = "mypy" }, + { name = "pyright" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "restview" }, + { name = "ruff" }, + { name = "sphinx" }, + { name = "sphinx-argparse" }, + { name = "sphinx-autodoc-typehints" }, + { name = "towncrier" }, + { name = "tox" }, + { name = "tox-gh-actions" }, + { name = "twine" }, +] +publish = [ + { name = "build" }, + { name = "twine" }, +] +test = [ + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "tox" }, +] +typing = [ + { name = "mypy" }, + { name = "pyright" }, +] + +[[package]] +name = "six" +version = "1.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 }, +] + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/7b/af302bebf22c749c56c9c3e8ae13190b5b5db37a33d9068652e8f73b7089/snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", size = 86699 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a", size = 93002 }, +] + +[[package]] +name = "sphinx" +version = "5.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/af/b2/02a43597980903483fe5eb081ee8e0ba2bb62ea43a70499484343795f3bf/Sphinx-5.3.0.tar.gz", hash = "sha256:51026de0a9ff9fc13c05d74913ad66047e104f56a129ff73e174eb5c3ee794b5", size = 6811365 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/a7/01dd6fd9653c056258d65032aa09a615b5d7b07dd840845a9f41a8860fbc/sphinx-5.3.0-py3-none-any.whl", hash = "sha256:060ca5c9f7ba57a08a1219e547b269fadf125ae25b06b9fa7f66768efb652d6d", size = 3183160 }, +] + +[[package]] +name = "sphinx-argparse" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/0b/d98f799d4283e8b6c403fd8102acf2b552ad78e947b6899a1344521e9d86/sphinx_argparse-0.4.0.tar.gz", hash = "sha256:e0f34184eb56f12face774fbc87b880abdb9017a0998d1ec559b267e9697e449", size = 15020 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/98/d32f45b19b60e52b4ddc714dee139a92c6ea8fa9115f994884d321c3454d/sphinx_argparse-0.4.0-py3-none-any.whl", hash = "sha256:73bee01f7276fae2bf621ccfe4d167af7306e7288e3482005405d9f826f9b037", size = 12625 }, +] + +[[package]] +name = "sphinx-autodoc-typehints" +version = "1.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/30/9764a2c735c655c3065f32072fb3d8c6fd5dda8df294d4e9f05670d60e31/sphinx_autodoc_typehints-1.23.0.tar.gz", hash = "sha256:5d44e2996633cdada499b6d27a496ddf9dbc95dd1f0f09f7b37940249e61f6e9", size = 35945 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/be/792b64ddacfcff362062077689ce37eb9750b9924fc0a14f623fa71ffaf6/sphinx_autodoc_typehints-1.23.0-py3-none-any.whl", hash = "sha256:ac099057e66b09e51b698058ba7dd76e57e1fe696cd91b54e121d3dad188f91d", size = 17896 }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "1.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/01/ad9d4ebbceddbed9979ab4a89ddb78c9760e74e6757b1880f1b2760e8295/sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58", size = 24548 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/47/86022665a9433d89a66f5911b558ddff69861766807ba685de2e324bd6ed/sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a", size = 121180 }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "1.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/33/dc28393f16385f722c893cb55539c641c9aaec8d1bc1c15b69ce0ac2dbb3/sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4", size = 17398 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/09/5de5ed43a521387f18bdf5f5af31d099605c992fd25372b2b9b825ce48ee/sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e", size = 84690 }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/85/93464ac9bd43d248e7c74573d58a791d48c475230bcf000df2b2700b9027/sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2", size = 28144 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/40/c854ef09500e25f6432dcbad0f37df87fd7046d376272292d8654cc71c95/sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07", size = 100451 }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071 }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "1.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/8e/c4846e59f38a5f2b4a0e3b27af38f2fcf904d4bfd82095bf92de0b114ebd/sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72", size = 21658 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/14/05f9206cf4e9cfca1afb5fd224c7cd434dcc3a433d6d9e4e0264d29c6cdb/sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6", size = 90609 }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "1.1.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/72/835d6fadb9e5d02304cf39b18f93d227cd93abd3c41ebf58e6853eeb1455/sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952", size = 21019 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/77/5464ec50dd0f1c1037e3c93249b040c8fc8078fdda97530eeb02424b6eea/sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd", size = 94021 }, +] + +[[package]] +name = "tomli" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/3f/d7af728f075fb08564c5949a9c95e44352e23dee646869fa104a3b2060a3/tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f", size = 15164 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", size = 12757 }, +] + +[[package]] +name = "towncrier" +version = "23.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "click-default-group" }, + { name = "importlib-resources", marker = "python_full_version < '3.10'" }, + { name = "incremental" }, + { name = "jinja2" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/01/095289468ee2f7aaf745e8f21449f0b6db3491fcb6add71048c078058605/towncrier-23.6.0.tar.gz", hash = "sha256:fc29bd5ab4727c8dacfbe636f7fb5dc53b99805b62da1c96b214836159ff70c1", size = 48002 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/b8/e436e5d8097f4deb1f96b7f9c5b1397586ca2accdb123701a3239115d41e/towncrier-23.6.0-py3-none-any.whl", hash = "sha256:da552f29192b3c2b04d630133f194c98e9f14f0558669d427708e203fea4d0a5", size = 46289 }, +] + +[[package]] +name = "tox" +version = "4.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "chardet" }, + { name = "colorama" }, + { name = "filelock" }, + { name = "importlib-metadata", marker = "python_full_version < '3.8'" }, + { name = "packaging" }, + { name = "platformdirs" }, + { name = "pluggy" }, + { name = "pyproject-api" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.8'" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/21/8a0d95e6a502c6a0be81c583af9c11066bf1f4e6eeb6551ee8b6f4c7292d/tox-4.8.0.tar.gz", hash = "sha256:2adacf435b12ccf10b9dfa9975d8ec0afd7cbae44d300463140d2117b968037b", size = 173370 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/0c/9ad67a4f8ed18c2619b6b8c41cea4c99e7617d5d712670ab4193d439f1f8/tox-4.8.0-py3-none-any.whl", hash = "sha256:4991305a56983d750a0d848a34242be290452aa88d248f1bf976e4036ee8b213", size = 152622 }, +] + +[[package]] +name = "tox-gh-actions" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tox" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/a1/3e7b3185031f905ddfc0c65dfd85949dae612a4387cbacb286b9f2931314/tox-gh-actions-3.2.0.tar.gz", hash = "sha256:ac6fa3b8da51bc90dd77985fd55f09e746c6558c55910c0a93d643045a2b0ccc", size = 18834 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/7a/d59f4eb08d156787af19fb44371586fd8b25580a1837ce92ae203a847c49/tox_gh_actions-3.2.0-py2.py3-none-any.whl", hash = "sha256:821b66a4751a788fa3e9617bd796d696507b08c6e1d929ee4faefba06b73b694", size = 9951 }, +] + +[[package]] +name = "twine" +version = "4.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata" }, + { name = "keyring" }, + { name = "pkginfo" }, + { name = "readme-renderer" }, + { name = "requests" }, + { name = "requests-toolbelt" }, + { name = "rfc3986" }, + { name = "rich" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b7/1a/a7884359429d801cd63c2c5512ad0a337a509994b0e42d9696d4778d71f6/twine-4.0.2.tar.gz", hash = "sha256:9e102ef5fdd5a20661eb88fad46338806c3bd32cf1db729603fe3697b1bc83c8", size = 215249 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/38/a3f27a9e8ce45523d7d1e28c09e9085b61a98dab15d35ec086f36a44b37c/twine-4.0.2-py3-none-any.whl", hash = "sha256:929bc3c280033347a00f847236564d1c52a3e61b1ac2516c97c48f3ceab756d8", size = 36394 }, +] + +[[package]] +name = "typed-ast" +version = "1.5.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/7e/a424029f350aa8078b75fd0d360a787a273ca753a678d1104c5fa4f3072a/typed_ast-1.5.5.tar.gz", hash = "sha256:94282f7a354f36ef5dbce0ef3467ebf6a258e370ab33d5b40c249fa996e590dd", size = 252841 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/07/5defe18d4fc16281cd18c4374270abc430c3d852d8ac29b5db6599d45cfe/typed_ast-1.5.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4bc1efe0ce3ffb74784e06460f01a223ac1f6ab31c6bc0376a21184bf5aabe3b", size = 223267 }, + { url = "https://files.pythonhosted.org/packages/a0/5c/e379b00028680bfcd267d845cf46b60e76d8ac6f7009fd440d6ce030cc92/typed_ast-1.5.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f7a8c46a8b333f71abd61d7ab9255440d4a588f34a21f126bbfc95f6049e686", size = 208260 }, + { url = "https://files.pythonhosted.org/packages/3b/99/5cc31ef4f3c80e1ceb03ed2690c7085571e3fbf119cbd67a111ec0b6622f/typed_ast-1.5.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597fc66b4162f959ee6a96b978c0435bd63791e31e4f410622d19f1686d5e769", size = 842272 }, + { url = "https://files.pythonhosted.org/packages/e2/ed/b9b8b794b37b55c9247b1e8d38b0361e8158795c181636d34d6c11b506e7/typed_ast-1.5.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d41b7a686ce653e06c2609075d397ebd5b969d821b9797d029fccd71fdec8e04", size = 824651 }, + { url = "https://files.pythonhosted.org/packages/ca/59/dbbbe5a0e91c15d14a0896b539a5ed01326b0d468e75c1a33274d128d2d1/typed_ast-1.5.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5fe83a9a44c4ce67c796a1b466c270c1272e176603d5e06f6afbc101a572859d", size = 854960 }, + { url = "https://files.pythonhosted.org/packages/90/f0/0956d925f87bd81f6e0f8cf119eac5e5c8f4da50ca25bb9f5904148d4611/typed_ast-1.5.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d5c0c112a74c0e5db2c75882a0adf3133adedcdbfd8cf7c9d6ed77365ab90a1d", size = 839321 }, + { url = "https://files.pythonhosted.org/packages/43/17/4bdece9795da6f3345c4da5667ac64bc25863617f19c28d81f350f515be6/typed_ast-1.5.5-cp310-cp310-win_amd64.whl", hash = "sha256:e1a976ed4cc2d71bb073e1b2a250892a6e968ff02aa14c1f40eba4f365ffec02", size = 139380 }, + { url = "https://files.pythonhosted.org/packages/75/53/b685e10da535c7b3572735f8bea0d4abb35a04722a7d44ca9c163a0cf822/typed_ast-1.5.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c631da9710271cb67b08bd3f3813b7af7f4c69c319b75475436fcab8c3d21bee", size = 223264 }, + { url = "https://files.pythonhosted.org/packages/96/fd/fc8ccf19fc16a40a23e7c7802d0abc78c1f38f1abb6e2447c474f8a076d8/typed_ast-1.5.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b445c2abfecab89a932b20bd8261488d574591173d07827c1eda32c457358b18", size = 208158 }, + { url = "https://files.pythonhosted.org/packages/bf/9a/598e47f2c3ecd19d7f1bb66854d0d3ba23ffd93c846448790a92524b0a8d/typed_ast-1.5.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc95ffaaab2be3b25eb938779e43f513e0e538a84dd14a5d844b8f2932593d88", size = 878366 }, + { url = "https://files.pythonhosted.org/packages/60/ca/765e8bf8b24d0ed7b9fc669f6826c5bc3eb7412fc765691f59b83ae195b2/typed_ast-1.5.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61443214d9b4c660dcf4b5307f15c12cb30bdfe9588ce6158f4a005baeb167b2", size = 860314 }, + { url = "https://files.pythonhosted.org/packages/d9/3c/4af750e6c673a0dd6c7b9f5b5e5ed58ec51a2e4e744081781c664d369dfa/typed_ast-1.5.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6eb936d107e4d474940469e8ec5b380c9b329b5f08b78282d46baeebd3692dc9", size = 898108 }, + { url = "https://files.pythonhosted.org/packages/03/8d/d0a4d1e060e1e8dda2408131a0cc7633fc4bc99fca5941dcb86c461dfe01/typed_ast-1.5.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e48bf27022897577d8479eaed64701ecaf0467182448bd95759883300ca818c8", size = 881971 }, + { url = "https://files.pythonhosted.org/packages/90/83/f28d2c912cd010a09b3677ac69d23181045eb17e358914ab739b7fdee530/typed_ast-1.5.5-cp311-cp311-win_amd64.whl", hash = "sha256:83509f9324011c9a39faaef0922c6f720f9623afe3fe220b6d0b15638247206b", size = 139286 }, + { url = "https://files.pythonhosted.org/packages/d5/00/635353c31b71ed307ab020eff6baed9987da59a1b2ba489f885ecbe293b8/typed_ast-1.5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2188bc33d85951ea4ddad55d2b35598b2709d122c11c75cffd529fbc9965508e", size = 222315 }, + { url = "https://files.pythonhosted.org/packages/01/95/11be104446bb20212a741d30d40eab52a9cfc05ea34efa074ff4f7c16983/typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0635900d16ae133cab3b26c607586131269f88266954eb04ec31535c9a12ef1e", size = 793541 }, + { url = "https://files.pythonhosted.org/packages/32/f1/75bd58fb1410cb72fbc6e8adf163015720db2c38844b46a9149c5ff6bf38/typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57bfc3cf35a0f2fdf0a88a3044aafaec1d2f24d8ae8cd87c4f58d615fb5b6311", size = 778348 }, + { url = "https://files.pythonhosted.org/packages/47/97/0bb4dba688a58ff9c08e63b39653e4bcaa340ce1bb9c1d58163e5c2c66f1/typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:fe58ef6a764de7b4b36edfc8592641f56e69b7163bba9f9c8089838ee596bfb2", size = 809447 }, + { url = "https://files.pythonhosted.org/packages/a8/cd/9a867f5a96d83a9742c43914e10d3a2083d8fe894ab9bf60fd467c6c497f/typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d09d930c2d1d621f717bb217bf1fe2584616febb5138d9b3e8cdd26506c3f6d4", size = 796707 }, + { url = "https://files.pythonhosted.org/packages/eb/06/73ca55ee5303b41d08920de775f02d2a3e1e59430371f5adf7fbb1a21127/typed_ast-1.5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:d40c10326893ecab8a80a53039164a224984339b2c32a6baf55ecbd5b1df6431", size = 138403 }, + { url = "https://files.pythonhosted.org/packages/19/e3/88b65e46643006592f39e0fdef3e29454244a9fdaa52acfb047dc68cae6a/typed_ast-1.5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fd946abf3c31fb50eee07451a6aedbfff912fcd13cf357363f5b4e834cc5e71a", size = 222951 }, + { url = "https://files.pythonhosted.org/packages/15/e0/182bdd9edb6c6a1c068cecaa87f58924a817f2807a0b0d940f578b3328df/typed_ast-1.5.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ed4a1a42df8a3dfb6b40c3d2de109e935949f2f66b19703eafade03173f8f437", size = 208247 }, + { url = "https://files.pythonhosted.org/packages/8d/09/bba083f2c11746288eaf1859e512130420405033de84189375fe65d839ba/typed_ast-1.5.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:045f9930a1550d9352464e5149710d56a2aed23a2ffe78946478f7b5416f1ede", size = 861010 }, + { url = "https://files.pythonhosted.org/packages/31/f3/38839df509b04fb54205e388fc04b47627377e0ad628870112086864a441/typed_ast-1.5.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:381eed9c95484ceef5ced626355fdc0765ab51d8553fec08661dce654a935db4", size = 840026 }, + { url = "https://files.pythonhosted.org/packages/45/1e/aa5f1dae4b92bc665ae9a655787bb2fe007a881fa2866b0408ce548bb24c/typed_ast-1.5.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bfd39a41c0ef6f31684daff53befddae608f9daf6957140228a08e51f312d7e6", size = 875615 }, + { url = "https://files.pythonhosted.org/packages/94/88/71a1c249c01fbbd66f9f28648f8249e737a7fe19056c1a78e7b3b9250eb1/typed_ast-1.5.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8c524eb3024edcc04e288db9541fe1f438f82d281e591c548903d5b77ad1ddd4", size = 858320 }, + { url = "https://files.pythonhosted.org/packages/12/1e/19f53aad3984e351e6730e4265fde4b949a66c451e10828fdbc4dfb050f1/typed_ast-1.5.5-cp38-cp38-win_amd64.whl", hash = "sha256:7f58fabdde8dcbe764cef5e1a7fcb440f2463c1bbbec1cf2a86ca7bc1f95184b", size = 139414 }, + { url = "https://files.pythonhosted.org/packages/b1/88/6e7f36f5fab6fbf0586a2dd866ac337924b7d4796a4d1b2b04443a864faf/typed_ast-1.5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:042eb665ff6bf020dd2243307d11ed626306b82812aba21836096d229fdc6a10", size = 223329 }, + { url = "https://files.pythonhosted.org/packages/71/30/09d27e13824495547bcc665bd07afc593b22b9484f143b27565eae4ccaac/typed_ast-1.5.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:622e4a006472b05cf6ef7f9f2636edc51bda670b7bbffa18d26b255269d3d814", size = 208314 }, + { url = "https://files.pythonhosted.org/packages/07/3d/564308b7a432acb1f5399933cbb1b376a1a64d2544b90f6ba91894674260/typed_ast-1.5.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1efebbbf4604ad1283e963e8915daa240cb4bf5067053cf2f0baadc4d4fb51b8", size = 840900 }, + { url = "https://files.pythonhosted.org/packages/ea/f4/262512d14f777ea3666a089e2675a9b1500a85b8329a36de85d63433fb0e/typed_ast-1.5.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0aefdd66f1784c58f65b502b6cf8b121544680456d1cebbd300c2c813899274", size = 823435 }, + { url = "https://files.pythonhosted.org/packages/a1/25/b3ccb948166d309ab75296ac9863ebe2ff209fbc063f1122a2d3979e47c3/typed_ast-1.5.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:48074261a842acf825af1968cd912f6f21357316080ebaca5f19abbb11690c8a", size = 853125 }, + { url = "https://files.pythonhosted.org/packages/1c/09/012da182242f168bb5c42284297dcc08dc0a1b3668db5b3852aec467f56f/typed_ast-1.5.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:429ae404f69dc94b9361bb62291885894b7c6fb4640d561179548c849f8492ba", size = 837280 }, + { url = "https://files.pythonhosted.org/packages/30/bd/c815051404c4293265634d9d3e292f04fcf681d0502a9484c38b8f224d04/typed_ast-1.5.5-cp39-cp39-win_amd64.whl", hash = "sha256:335f22ccb244da2b5c296e6f96b06ee9bed46526db0de38d2f0e5a6597b81155", size = 139486 }, +] + +[[package]] +name = "typing-extensions" +version = "4.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3c/8b/0111dd7d6c1478bf83baa1cab85c686426c7a6274119aceb2bd9d35395ad/typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2", size = 72876 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/6b/63cc3df74987c36fe26157ee12e09e8f9db4de771e0f3404263117e75b95/typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36", size = 33232 }, +] + +[[package]] +name = "untokenize" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz", hash = "sha256:3865dbbbb8efb4bb5eaa72f1be7f3e0be00ea8b7f125c69cbd1f5fda926f37a2", size = 3099 } + +[[package]] +name = "urllib3" +version = "2.0.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/47/b215df9f71b4fdba1025fc05a77db2ad243fa0926755a52c5e71659f4e3c/urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84", size = 282546 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/b2/b157855192a68541a91ba7b2bbcb91f1b4faa51f8bae38d8005c034be524/urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e", size = 124213 }, +] + +[[package]] +name = "virtualenv" +version = "20.26.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "importlib-metadata", marker = "python_full_version < '3.8'" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3f/40/abc5a766da6b0b2457f819feab8e9203cbeae29327bd241359f866a3da9d/virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48", size = 9372482 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/90/57b8ac0c8a231545adc7698c64c5a36fa7cd8e376c691b9bde877269f2eb/virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2", size = 5999862 }, +] + +[[package]] +name = "webencodings" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774 }, +] + +[[package]] +name = "zipp" +version = "3.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/27/f0ac6b846684cecce1ee93d32450c45ab607f65c2e0255f0092032d91f07/zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b", size = 18454 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/fa/c9e82bbe1af6266adf08afb563905eb87cab83fde00a0a08963510621047/zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556", size = 6758 }, +]