From 246c977dde21cf5cb6f0bc1437c0a9a3eaac2799 Mon Sep 17 00:00:00 2001 From: Kien Dang Date: Mon, 16 Oct 2023 11:43:32 +0800 Subject: [PATCH 1/3] fix: fix and refactor version tests to handle release candidates correctly --- graphql_server/version.py | 4 +- tests/test_version.py | 150 +++++++++++++++++++++++++------------- 2 files changed, 102 insertions(+), 52 deletions(-) diff --git a/graphql_server/version.py b/graphql_server/version.py index 0be0bd8..6462802 100644 --- a/graphql_server/version.py +++ b/graphql_server/version.py @@ -36,7 +36,9 @@ def from_str(cls, v: str) -> "VersionInfo": def __str__(self) -> str: v = f"{self.major}.{self.minor}.{self.micro}" level = self.releaselevel - if level and level != "final": + if level == "candidate": + v = f"{v}rc{self.serial}" + elif level and level != "final": v = f"{v}{level[:1]}{self.serial}" return v diff --git a/tests/test_version.py b/tests/test_version.py index a69c95e..cd57650 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -1,70 +1,115 @@ import re +from typing import Tuple, Union + +import pytest import graphql_server from graphql_server.version import VersionInfo, version, version_info -_re_version = re.compile(r"(\d+)\.(\d+)\.(\d+)(?:([abc])(\d+))?$") - - -def test_create_version_info_from_fields(): - v = VersionInfo(1, 2, 3, "alpha", 4) - assert v.major == 1 - assert v.minor == 2 - assert v.micro == 3 - assert v.releaselevel == "alpha" - assert v.serial == 4 - - -def test_create_version_info_from_str(): - v = VersionInfo.from_str("1.2.3") - assert v.major == 1 - assert v.minor == 2 - assert v.micro == 3 - assert v.releaselevel == "final" - assert v.serial == 0 - v = VersionInfo.from_str("1.2.3a4") - assert v.major == 1 - assert v.minor == 2 - assert v.micro == 3 - assert v.releaselevel == "alpha" - assert v.serial == 4 - v = VersionInfo.from_str("1.2.3beta4") - assert v.major == 1 - assert v.minor == 2 - assert v.micro == 3 - assert v.releaselevel == "beta" - assert v.serial == 4 - v = VersionInfo.from_str("12.34.56rc789") - assert v.major == 12 - assert v.minor == 34 - assert v.micro == 56 - assert v.releaselevel == "candidate" - assert v.serial == 789 - - -def test_serialize_as_str(): - v = VersionInfo(1, 2, 3, "final", 0) - assert str(v) == "1.2.3" - v = VersionInfo(1, 2, 3, "alpha", 4) - assert str(v) == "1.2.3a4" - - -def test_base_package_has_correct_version(): +_re_version = re.compile(r"(\d+)\.(\d+)\.(\d+)(?:([abc]|rc)(\d+))?$") + +TEST_CASES = [ + ( + (1, 2, 3), + "1.2.3", + ), + ( + (1, 2, 3, "alpha", 4), + "1.2.3a4", + ), + ( + (1, 2, 3, "beta", 4), + "1.2.3b4", + ), + ( + (12, 34, 56, "candidate", 789), + "12.34.56rc789", + ), +] + + +PARSED_VERSIONS = [parsed_version for parsed_version, _ in TEST_CASES] +VERSION_STRINGS = [version_string for _, version_string in TEST_CASES] + + +def extract_version_components( + parsed_version: Union[Tuple[int, int, int], Tuple[int, int, int, str, int]], +) -> Tuple[int, int, int, str, int]: + if len(parsed_version) == 5: + return parsed_version # type: ignore + elif len(parsed_version) == 3: + return tuple([*parsed_version, "final", 0]) # type: ignore + + raise ValueError("parsed_version length should be either 3 or 5") + + +@pytest.mark.parametrize("parsed_version", PARSED_VERSIONS) +def test_create_version_info_from_fields( + parsed_version: Union[Tuple[int, int, int], Tuple[int, int, int, str, int]], +) -> None: + version_components = extract_version_components(parsed_version) + major, minor, micro, releaselevel, serial = version_components + v = VersionInfo(*version_components) + + assert v.major == major + assert v.minor == minor + assert v.micro == micro + assert v.releaselevel == releaselevel + assert v.serial == serial + + +@pytest.mark.parametrize("parsed_version, version_string", TEST_CASES) +def test_create_version_info_from_str( + parsed_version: Union[Tuple[int, int, int], Tuple[int, int, int, str, int]], + version_string: str, +) -> None: + v = VersionInfo.from_str(version_string) + major, minor, micro, releaselevel, serial = extract_version_components( + parsed_version + ) + + assert v.major == major + assert v.minor == minor + assert v.micro == micro + assert v.releaselevel == releaselevel + assert v.serial == serial + + +@pytest.mark.parametrize("parsed_version, version_string", TEST_CASES) +def test_serialize_as_str( + parsed_version: Union[Tuple[int, int, int], Tuple[int, int, int, str, int]], + version_string: str, +) -> None: + v = VersionInfo(*extract_version_components(parsed_version)) + assert str(v) == version_string + + +def test_base_package_has_correct_version() -> None: assert graphql_server.__version__ == version assert graphql_server.version == version -def test_base_package_has_correct_version_info(): +def test_base_package_has_correct_version_info() -> None: assert graphql_server.__version_info__ is version_info assert graphql_server.version_info is version_info -def test_version_has_correct_format(): +@pytest.mark.parametrize("version", VERSION_STRINGS + [version]) +def test_version_has_correct_format(version: str) -> None: assert isinstance(version, str) assert _re_version.match(version) -def test_version_info_has_correct_fields(): +@pytest.mark.parametrize( + "version,version_info", + zip( + VERSION_STRINGS + [version], + [VersionInfo.from_str(v) for v in VERSION_STRINGS] + [version_info], + ), +) +def test_version_info_has_correct_fields( + version: str, version_info: VersionInfo +) -> None: assert isinstance(version_info, tuple) assert str(version_info) == version groups = _re_version.match(version).groups() # type: ignore @@ -74,5 +119,8 @@ def test_version_info_has_correct_fields(): if groups[3] is None: # pragma: no cover assert groups[4] is None else: # pragma: no cover - assert version_info.releaselevel[:1] == groups[3] + if version_info.releaselevel == "candidate": + assert groups[3] == "rc" + else: + assert version_info.releaselevel[:1] == groups[3] assert version_info.serial == int(groups[4]) From 6f229a359105051221b307a463e27fdadd2e85dd Mon Sep 17 00:00:00 2001 From: Kien Dang Date: Tue, 17 Oct 2023 15:04:29 +0800 Subject: [PATCH 2/3] fix: remove VersionInfo entirely MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit keep graphql_server.version_info as a plain tuple add tests using the packaging package to make sure graphql_server.version and graphql_server.version_info are PEP 440–compliant and pointing to the same version --- graphql_server/version.py | 53 +++--------- setup.py | 1 + tests/test_version.py | 172 +++++++++++--------------------------- 3 files changed, 60 insertions(+), 166 deletions(-) diff --git a/graphql_server/version.py b/graphql_server/version.py index 6462802..138d67d 100644 --- a/graphql_server/version.py +++ b/graphql_server/version.py @@ -1,46 +1,15 @@ -import re -from typing import NamedTuple - __all__ = ["version", "version_info"] version = "3.0.0b7" - -_re_version = re.compile(r"(\d+)\.(\d+)\.(\d+)(\D*)(\d*)") - - -class VersionInfo(NamedTuple): - major: int - minor: int - micro: int - releaselevel: str - serial: int - - @classmethod - def from_str(cls, v: str) -> "VersionInfo": - groups = _re_version.match(v).groups() # type: ignore - major, minor, micro = map(int, groups[:3]) - level = (groups[3] or "")[:1] - if level == "a": - level = "alpha" - elif level == "b": - level = "beta" - elif level in ("c", "r"): - level = "candidate" - else: - level = "final" - serial = groups[4] - serial = int(serial) if serial else 0 - return cls(major, minor, micro, level, serial) - - def __str__(self) -> str: - v = f"{self.major}.{self.minor}.{self.micro}" - level = self.releaselevel - if level == "candidate": - v = f"{v}rc{self.serial}" - elif level and level != "final": - v = f"{v}{level[:1]}{self.serial}" - return v - - -version_info = VersionInfo.from_str(version) +version_info = (3, 0, 0, "beta", 7) +# version_info has the same format as django.VERSION +# https://github.com/django/django/blob/4a5048b036fd9e965515e31fdd70b0af72655cba/django/utils/version.py#L22 +# +# examples +# "3.0.0" -> (3, 0, 0, "final", 0) +# "3.0.0rc1" -> (3, 0, 0, "rc", 1) +# "3.0.0b7" -> (3, 0, 0, "beta", 7) +# "3.0.0a2" -> (3, 0, 0, "alpha", 2) +# +# also see tests/test_version.py diff --git a/setup.py b/setup.py index 9048304..3531daa 100644 --- a/setup.py +++ b/setup.py @@ -13,6 +13,7 @@ "pytest-cov>=4,<5", "Jinja2>=3.1,<4", "sanic-testing>=22.3,<24", + "packaging==23.2", ] dev_requires = [ diff --git a/tests/test_version.py b/tests/test_version.py index cd57650..0ac89e5 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -1,126 +1,50 @@ -import re -from typing import Tuple, Union +import packaging +from packaging.version import Version -import pytest - -import graphql_server -from graphql_server.version import VersionInfo, version, version_info - -_re_version = re.compile(r"(\d+)\.(\d+)\.(\d+)(?:([abc]|rc)(\d+))?$") - -TEST_CASES = [ - ( - (1, 2, 3), - "1.2.3", - ), - ( - (1, 2, 3, "alpha", 4), - "1.2.3a4", - ), - ( - (1, 2, 3, "beta", 4), - "1.2.3b4", - ), - ( - (12, 34, 56, "candidate", 789), - "12.34.56rc789", - ), -] - - -PARSED_VERSIONS = [parsed_version for parsed_version, _ in TEST_CASES] -VERSION_STRINGS = [version_string for _, version_string in TEST_CASES] - - -def extract_version_components( - parsed_version: Union[Tuple[int, int, int], Tuple[int, int, int, str, int]], -) -> Tuple[int, int, int, str, int]: - if len(parsed_version) == 5: - return parsed_version # type: ignore - elif len(parsed_version) == 3: - return tuple([*parsed_version, "final", 0]) # type: ignore - - raise ValueError("parsed_version length should be either 3 or 5") - - -@pytest.mark.parametrize("parsed_version", PARSED_VERSIONS) -def test_create_version_info_from_fields( - parsed_version: Union[Tuple[int, int, int], Tuple[int, int, int, str, int]], -) -> None: - version_components = extract_version_components(parsed_version) - major, minor, micro, releaselevel, serial = version_components - v = VersionInfo(*version_components) - - assert v.major == major - assert v.minor == minor - assert v.micro == micro - assert v.releaselevel == releaselevel - assert v.serial == serial - - -@pytest.mark.parametrize("parsed_version, version_string", TEST_CASES) -def test_create_version_info_from_str( - parsed_version: Union[Tuple[int, int, int], Tuple[int, int, int, str, int]], - version_string: str, -) -> None: - v = VersionInfo.from_str(version_string) - major, minor, micro, releaselevel, serial = extract_version_components( - parsed_version - ) - - assert v.major == major - assert v.minor == minor - assert v.micro == micro - assert v.releaselevel == releaselevel - assert v.serial == serial - - -@pytest.mark.parametrize("parsed_version, version_string", TEST_CASES) -def test_serialize_as_str( - parsed_version: Union[Tuple[int, int, int], Tuple[int, int, int, str, int]], - version_string: str, -) -> None: - v = VersionInfo(*extract_version_components(parsed_version)) - assert str(v) == version_string - - -def test_base_package_has_correct_version() -> None: - assert graphql_server.__version__ == version - assert graphql_server.version == version - - -def test_base_package_has_correct_version_info() -> None: - assert graphql_server.__version_info__ is version_info - assert graphql_server.version_info is version_info - - -@pytest.mark.parametrize("version", VERSION_STRINGS + [version]) -def test_version_has_correct_format(version: str) -> None: - assert isinstance(version, str) - assert _re_version.match(version) - - -@pytest.mark.parametrize( - "version,version_info", - zip( - VERSION_STRINGS + [version], - [VersionInfo.from_str(v) for v in VERSION_STRINGS] + [version_info], - ), -) -def test_version_info_has_correct_fields( - version: str, version_info: VersionInfo -) -> None: +from graphql_server.version import version, version_info + +RELEASE_LEVEL = {"alpha": "a", "beta": "b", "rc": "rc", "final": None} + + +parsed_version = Version(version) + + +def test_valid_version() -> None: + packaging.version.parse(version) + + +def test_valid_version_info() -> None: + """version_info has to be a tuple[int, int, int, str, int]""" assert isinstance(version_info, tuple) - assert str(version_info) == version - groups = _re_version.match(version).groups() # type: ignore - assert version_info.major == int(groups[0]) - assert version_info.minor == int(groups[1]) - assert version_info.micro == int(groups[2]) - if groups[3] is None: # pragma: no cover - assert groups[4] is None - else: # pragma: no cover - if version_info.releaselevel == "candidate": - assert groups[3] == "rc" - else: - assert version_info.releaselevel[:1] == groups[3] - assert version_info.serial == int(groups[4]) + assert len(version_info) == 5 + + major, minor, micro, release_level, serial = version_info + assert isinstance(major, int) + assert isinstance(minor, int) + assert isinstance(micro, int) + assert isinstance(release_level, str) + assert isinstance(serial, int) + + +def test_valid_version_release_level() -> None: + if parsed_version.pre is not None: + valid_release_levels = {v for v in RELEASE_LEVEL.values() if v is not None} + assert parsed_version.pre[0] in valid_release_levels + + +def test_valid_version_info_release_level() -> None: + assert version_info[3] in RELEASE_LEVEL.keys() + + +def test_version_same_as_version_info() -> None: + assert ( + parsed_version.major, + parsed_version.minor, + parsed_version.micro, + ) == version_info[:3] + + release_level, serial = version_info[-2:] + if parsed_version.is_prerelease: + assert (RELEASE_LEVEL[release_level], serial) == parsed_version.pre + else: + assert (release_level, serial) == ("final", 0) From f593210bcadb110cf3a12b94a7ffb02653a9627a Mon Sep 17 00:00:00 2001 From: Kien Dang Date: Tue, 17 Oct 2023 13:59:20 +0800 Subject: [PATCH 3/3] chore: update pre-commit hooks --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0fab074..c99bdd8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,9 +1,9 @@ default_language_version: - python: python3.10 + python: python3.11 exclude: LICENSE repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-merge-conflict - id: check-json @@ -20,11 +20,11 @@ repos: hooks: - id: check-manifest - repo: https://github.com/psf/black - rev: 23.7.0 + rev: 23.9.1 hooks: - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.283 + rev: v0.1.0 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix, --show-fixes]