diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..b5a35be --- /dev/null +++ b/.flake8 @@ -0,0 +1,3 @@ +[flake8] +max-line-length = 88 +ignore = E203,W503,W504 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..712704c --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,55 @@ +name: Linting + +on: + pull_request: + paths: + - ".github/workflows/lint.yml" + - ".pre-commit-config.yaml" + - "**.py" + push: + paths: + - ".github/workflows/lint.yml" + - ".pre-commit-config.yaml" + - "**.py" + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +jobs: + lint: + name: nox -s lint + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-python@v4 + name: Install Python + with: + python-version: "3.9" + cache: "pip" + + - name: Run `nox -s lint` + run: pipx run nox --error-on-missing-interpreters -s lint -- --show-diff-on-failure + + build: + name: Build sdist and wheel + runs-on: ubuntu-latest + # Linting verifies that the project is in an acceptable state to create files + # for releasing. + # And this action should be run whenever a release is ready to go public as + # the version number will be changed by editing __init__.py. + needs: lint + + steps: + - uses: actions/checkout@v3 + + - name: Build + run: pipx run build + + - name: Archive files + uses: actions/upload-artifact@v3 + with: + name: dist + path: dist diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..b4b4066 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,43 @@ +name: Test + +on: + pull_request: + paths: + - ".github/workflows/test.yml" + - "**.py" + push: + paths: + - ".github/workflows/test.yml" + - "**.py" + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +jobs: + test: + name: ${{ matrix.os }} / ${{ matrix.python_version }} + runs-on: ${{ matrix.os }}-latest + strategy: + fail-fast: false + matrix: + os: [Ubuntu, Windows, macOS] + python_version: + ["3.7", "3.8", "3.9", "3.10", "3.11", "pypy3.7", "pypy3.8", "pypy3.9"] + + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-python@v4 + name: Install Python ${{ matrix.python_version }} + with: + python-version: ${{ matrix.python_version }} + cache: "pip" + + - name: Run nox + run: | + # Need to remove "-dev" suffix + INTERPRETER=${{ matrix.python_version }} + INTERPRETER=${INTERPRETER/-dev/} + pipx run nox --error-on-missing-interpreters -s tests-${INTERPRETER} + shell: bash diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..05e554a --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +*.egg-info/ +*.egg +*.py[co] + +.[nt]ox/ +.cache/ +.coverage +.idea +.venv* +.vscode/ + +.mypy_cache/ +.pytest_cache/ +__pycache__/ +_build/ +build/ +dist/ +htmlcov/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..f0b033f --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,40 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.2.0 + hooks: + - id: check-toml + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v0.950 + hooks: + - id: mypy + exclude: '^(docs|tasks|tests)|setup\.py' + args: [] + additional_dependencies: [pyparsing, nox] + + - repo: https://github.com/asottile/pyupgrade + rev: v2.32.0 + hooks: + - id: pyupgrade + args: [--py36-plus] + + - repo: https://github.com/psf/black + rev: 22.3.0 + hooks: + - id: black + + - repo: https://github.com/PyCQA/isort + rev: 5.10.1 + hooks: + - id: isort + + - repo: https://github.com/PyCQA/flake8 + rev: "3.9.2" + hooks: + - id: flake8 + additional_dependencies: ["pep8-naming"] + # Ignore all format-related checks as Black takes care of those. + args: ["--ignore", "E2,W5", "--select", "E,W,F,N"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6f62d44 --- /dev/null +++ b/LICENSE @@ -0,0 +1,3 @@ +This software is made available under the terms of *either* of the licenses +found in LICENSE.APACHE or LICENSE.BSD. Contributions to this software is made +under the terms of *both* these licenses. diff --git a/LICENSE.APACHE b/LICENSE.APACHE new file mode 100644 index 0000000..f433b1a --- /dev/null +++ b/LICENSE.APACHE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/LICENSE.BSD b/LICENSE.BSD new file mode 100644 index 0000000..42ce7b7 --- /dev/null +++ b/LICENSE.BSD @@ -0,0 +1,23 @@ +Copyright (c) Donald Stufft and individual contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ce24a4e --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# Legacy Packaging + +This library provides support for "legacy" Python Packaging functionality +removed from https://github.com/pypa/packaging. + + +## Supported APIs + +This library includes support for the following deprecated/removed APIs: + +### `LegacyVersion` +Removed in https://github.com/pypa/packaging/pull/407, this includes a +`packaging_legacy.version.parse` function that handles legacy versions. + +```diff +- from packaging.version import parse, LegacyVersion ++ from packaging_legacy.version import parse, LegacyVersion +``` diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/noxfile.py b/noxfile.py new file mode 100644 index 0000000..97cd6ca --- /dev/null +++ b/noxfile.py @@ -0,0 +1,53 @@ +# mypy: disallow-untyped-defs=False, disallow-untyped-calls=False + +import glob + +import nox + +nox.options.sessions = ["lint"] +nox.options.reuse_existing_virtualenvs = True + + +@nox.session( + python=["3.7", "3.8", "3.9", "3.10", "3.11", "pypy3.7", "pypy3.8", "pypy3.9"] +) +def tests(session): + def coverage(*args): + session.run("python", "-m", "coverage", *args) + + session.install("-r", "tests/requirements.txt") + session.install(".") + + if "pypy" not in session.python: + coverage( + "run", + "--source", + "packaging_legacy", + "-m", + "pytest", + "--strict-markers", + *session.posargs, + ) + coverage("report", "-m", "--fail-under", "100") + else: + # Don't do coverage tracking for PyPy, since it's SLOW. + session.run( + "python", + "-m", + "pytest", + "--capture=no", + "--strict-markers", + *session.posargs, + ) + + +@nox.session(python="3.9") +def lint(session): + # Run the linters (via pre-commit) + session.install("pre-commit") + session.run("pre-commit", "run", "--all-files", *session.posargs) + + # Check the distribution + session.install("build", "twine") + session.run("pyproject-build") + session.run("twine", "check", *glob.glob("dist/*")) diff --git a/packaging_legacy/version.py b/packaging_legacy/version.py new file mode 100644 index 0000000..8982a3f --- /dev/null +++ b/packaging_legacy/version.py @@ -0,0 +1,135 @@ +import re +from typing import Iterator, List, Tuple + +from packaging.version import InvalidVersion, _BaseVersion, parse as _parse + +LegacyCmpKey = Tuple[int, Tuple[str, ...]] + + +def parse(version: str) -> "_BaseVersion": + """ + Parse the given version string and return either a :class:`Version` object + or a :class:`LegacyVersion` object depending on if the given version is + a valid PEP 440 version or a legacy version. + Parse the given version string. + Returns a :class:`Version` object, if the given version is a valid PEP 440 version. + Raises :class:`InvalidVersion` otherwise. + """ + try: + return _parse(version) + except InvalidVersion: + return LegacyVersion(version) + + +class LegacyVersion(_BaseVersion): + + _key: LegacyCmpKey + + def __init__(self, version: str) -> None: + self._version = str(version) + self._key: LegacyCmpKey = _legacy_cmpkey(self._version) + + def __str__(self) -> str: + return self._version + + def __repr__(self) -> str: + return f"" + + @property + def public(self) -> str: + return self._version + + @property + def base_version(self) -> str: + return self._version + + @property + def epoch(self) -> int: + return -1 + + @property + def release(self) -> None: + return None + + @property + def pre(self) -> None: + return None + + @property + def post(self) -> None: + return None + + @property + def dev(self) -> None: + return None + + @property + def local(self) -> None: + return None + + @property + def is_prerelease(self) -> bool: + return False + + @property + def is_postrelease(self) -> bool: + return False + + @property + def is_devrelease(self) -> bool: + return False + + +_legacy_version_component_re = re.compile(r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE) + +_legacy_version_replacement_map = { + "pre": "c", + "preview": "c", + "-": "final-", + "rc": "c", + "dev": "@", +} + + +def _parse_version_parts(s: str) -> Iterator[str]: + for part in _legacy_version_component_re.split(s): + part = _legacy_version_replacement_map.get(part, part) + + if not part or part == ".": + continue + + if part[:1] in "0123456789": + # pad for numeric comparison + yield part.zfill(8) + else: + yield "*" + part + + # ensure that alpha/beta/candidate are before final + yield "*final" + + +def _legacy_cmpkey(version: str) -> LegacyCmpKey: + + # We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch + # greater than or equal to 0. This will effectively put the LegacyVersion, + # which uses the defacto standard originally implemented by setuptools, + # as before all PEP 440 versions. + epoch = -1 + + # This scheme is taken from pkg_resources.parse_version setuptools prior to + # it's adoption of the packaging library. + parts: List[str] = [] + for part in _parse_version_parts(version.lower()): + if part.startswith("*"): + # remove "-" before a prerelease tag + if part < "*final": + while parts and parts[-1] == "*final-": + parts.pop() + + # remove trailing zeros from each series of numeric parts + while parts and parts[-1] == "00000000": + parts.pop() + + parts.append(part) + + return epoch, tuple(parts) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..5d2b403 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,46 @@ +[build-system] +requires = ["setuptools >= 40.6.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "packaging_legacy" +description = "Core utilities for legacy Python packages" +version = "23.0" +readme = "README.md" +authors = [{name = "Dustin Ingram", email = "di@python.org"}] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "License :: OSI Approved :: BSD License", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "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 :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +dependencies = ["packaging>=23.0"] + +[project.urls] +Source = "https://github.com/di/packaging_legacy" + +[tool.coverage.run] +branch = true + +[tool.mypy] +strict = true +show_error_codes = true +enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] + +[[tool.mypy.overrides]] +module = ["_manylinux"] +ignore_missing_imports = true + +[tool.isort] +profile = "black" +combine_as_imports = true diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 0000000..6f00119 --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1,4 @@ +coverage[toml]>=5.0.0 +pip>=9.0.2 +pretend +pytest>=6.2.0 diff --git a/tests/test_version.py b/tests/test_version.py new file mode 100644 index 0000000..9179eaf --- /dev/null +++ b/tests/test_version.py @@ -0,0 +1,201 @@ +import itertools +import operator + +import pretend +import pytest +from packaging.version import Version + +from packaging_legacy.version import LegacyVersion, parse + + +@pytest.mark.parametrize( + ("version", "klass"), [("1.0", Version), ("1-1-1", LegacyVersion)] +) +def test_parse(version, klass): + assert isinstance(parse(version), klass) + + +class TestVersion: + def test_compare_legacyversion_version(self): + result = sorted([Version("0"), LegacyVersion("1")]) + assert result == [LegacyVersion("1"), Version("0")] + + +VERSIONS = [ + # Implicit epoch of 0 + "1.0.dev456", + "1.0a1", + "1.0a2.dev456", + "1.0a12.dev456", + "1.0a12", + "1.0b1.dev456", + "1.0b2", + "1.0b2.post345.dev456", + "1.0b2.post345", + "1.0b2-346", + "1.0c1.dev456", + "1.0c1", + "1.0rc2", + "1.0c3", + "1.0", + "1.0.post456.dev34", + "1.0.post456", + "1.1.dev1", + "1.2+123abc", + "1.2+123abc456", + "1.2+abc", + "1.2+abc123", + "1.2+abc123def", + "1.2+1234.abc", + "1.2+123456", + "1.2.r32+123456", + "1.2.rev33+123456", + # Explicit epoch of 1 + "1!1.0.dev456", + "1!1.0a1", + "1!1.0a2.dev456", + "1!1.0a12.dev456", + "1!1.0a12", + "1!1.0b1.dev456", + "1!1.0b2", + "1!1.0b2.post345.dev456", + "1!1.0b2.post345", + "1!1.0b2-346", + "1!1.0c1.dev456", + "1!1.0c1", + "1!1.0rc2", + "1!1.0c3", + "1!1.0", + "1!1.0.post456.dev34", + "1!1.0.post456", + "1!1.1.dev1", + "1!1.2+123abc", + "1!1.2+123abc456", + "1!1.2+abc", + "1!1.2+abc123", + "1!1.2+abc123def", + "1!1.2+1234.abc", + "1!1.2+123456", + "1!1.2.r32+123456", + "1!1.2.rev33+123456", +] +LEGACY_VERSIONS = ["foobar", "a cat is fine too", "lolwut", "1-0", "2.0-a1"] + + +class TestLegacyVersion: + @pytest.mark.parametrize("version", VERSIONS + LEGACY_VERSIONS) + def test_valid_legacy_versions(self, version): + LegacyVersion(version) + + @pytest.mark.parametrize("version", VERSIONS + LEGACY_VERSIONS) + def test_legacy_version_str_repr(self, version): + assert str(LegacyVersion(version)) == version + assert repr(LegacyVersion(version)) == "".format( + repr(version) + ) + + @pytest.mark.parametrize("version", VERSIONS + LEGACY_VERSIONS) + def test_legacy_version_hash(self, version): + assert hash(LegacyVersion(version)) == hash(LegacyVersion(version)) + + @pytest.mark.parametrize("version", VERSIONS + LEGACY_VERSIONS) + def test_legacy_version_public(self, version): + assert LegacyVersion(version).public == version + + @pytest.mark.parametrize("version", VERSIONS + LEGACY_VERSIONS) + def test_legacy_version_base_version(self, version): + assert LegacyVersion(version).base_version == version + + @pytest.mark.parametrize("version", VERSIONS + LEGACY_VERSIONS) + def test_legacy_version_epoch(self, version): + assert LegacyVersion(version).epoch == -1 + + @pytest.mark.parametrize("version", VERSIONS + LEGACY_VERSIONS) + def test_legacy_version_release(self, version): + assert LegacyVersion(version).release is None + + @pytest.mark.parametrize("version", VERSIONS + LEGACY_VERSIONS) + def test_legacy_version_local(self, version): + assert LegacyVersion(version).local is None + + @pytest.mark.parametrize("version", VERSIONS + LEGACY_VERSIONS) + def test_legacy_version_pre(self, version): + assert LegacyVersion(version).pre is None + + @pytest.mark.parametrize("version", VERSIONS + LEGACY_VERSIONS) + def test_legacy_version_is_prerelease(self, version): + assert not LegacyVersion(version).is_prerelease + + @pytest.mark.parametrize("version", VERSIONS + LEGACY_VERSIONS) + def test_legacy_version_dev(self, version): + assert LegacyVersion(version).dev is None + + @pytest.mark.parametrize("version", VERSIONS + LEGACY_VERSIONS) + def test_legacy_version_is_devrelease(self, version): + assert not LegacyVersion(version).is_devrelease + + @pytest.mark.parametrize("version", VERSIONS + LEGACY_VERSIONS) + def test_legacy_version_post(self, version): + assert LegacyVersion(version).post is None + + @pytest.mark.parametrize("version", VERSIONS + LEGACY_VERSIONS) + def test_legacy_version_is_postrelease(self, version): + assert not LegacyVersion(version).is_postrelease + + @pytest.mark.parametrize( + ("left", "right", "op"), + # Below we'll generate every possible combination of + # VERSIONS + LEGACY_VERSIONS that should be True for the given operator + itertools.chain( + * + # Verify that the equal (==) operator works correctly + [[(x, x, operator.eq) for x in VERSIONS + LEGACY_VERSIONS]] + + + # Verify that the not equal (!=) operator works correctly + [ + [ + (x, y, operator.ne) + for j, y in enumerate(VERSIONS + LEGACY_VERSIONS) + if i != j + ] + for i, x in enumerate(VERSIONS + LEGACY_VERSIONS) + ] + ), + ) + def test_comparison_true(self, left, right, op): + assert op(LegacyVersion(left), LegacyVersion(right)) + + @pytest.mark.parametrize( + ("left", "right", "op"), + # Below we'll generate every possible combination of + # VERSIONS + LEGACY_VERSIONS that should be False for the given + # operator + itertools.chain( + * + # Verify that the equal (==) operator works correctly + [ + [ + (x, y, operator.eq) + for j, y in enumerate(VERSIONS + LEGACY_VERSIONS) + if i != j + ] + for i, x in enumerate(VERSIONS + LEGACY_VERSIONS) + ] + + + # Verify that the not equal (!=) operator works correctly + [[(x, x, operator.ne) for x in VERSIONS + LEGACY_VERSIONS]] + ), + ) + def test_comparison_false(self, left, right, op): + assert not op(LegacyVersion(left), LegacyVersion(right)) + + @pytest.mark.parametrize("op", ["lt", "le", "eq", "ge", "gt", "ne"]) + def test_dunder_op_returns_notimplemented(self, op): + method = getattr(LegacyVersion, f"__{op}__") + assert method(LegacyVersion("1"), 1) is NotImplemented + + @pytest.mark.parametrize(("op", "expected"), [("eq", False), ("ne", True)]) + def test_compare_other(self, op, expected): + other = pretend.stub(**{f"__{op}__": lambda other: NotImplemented}) + + assert getattr(operator, op)(LegacyVersion("1"), other) is expected