From e46f39b03cdfa8089ac224b82c0fe5e25d050c0f Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 14 Jan 2022 05:16:17 -0600 Subject: [PATCH 1/5] deps(pyproject): Add packaging for python 3.10+ --- poetry.lock | 4 ++-- pyproject.toml | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index 32ad02b97..91efbc060 100644 --- a/poetry.lock +++ b/poetry.lock @@ -439,7 +439,7 @@ testing = ["beautifulsoup4", "coverage[toml]", "pytest (>=6,<7)", "pytest-cov", name = "packaging" version = "22.0" description = "Core utilities for Python packages" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" @@ -945,7 +945,7 @@ test = [] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "6a05ee2efdef9d79f3c1f8283f8c6ada1be53819a998d129fb15e69477019cca" +content-hash = "20cb0283c8677c189b02f0dd7c1f7d29a5579f11116c68a6031e27d93ca6102c" [metadata.files] alabaster = [ diff --git a/pyproject.toml b/pyproject.toml index 48f0dcec0..a2b402c6e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,7 @@ Changes = "https://github.com/tmux-python/libtmux/blob/master/CHANGES" [tool.poetry.dependencies] python = "^3.7" +packaging = {version = "*", python="^3.10.0"} [tool.poetry.dev-dependencies] ### Docs ### From d502d6b51e98a850b1eeb87e2c143414f4d17fba Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 14 Jan 2022 05:01:46 -0600 Subject: [PATCH 2/5] Use packaging --- src/libtmux/_compat.py | 6 ++++++ src/libtmux/common.py | 4 +++- tests/test_common.py | 7 ++++--- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/libtmux/_compat.py b/src/libtmux/_compat.py index d7b6bb274..e47146b5d 100644 --- a/src/libtmux/_compat.py +++ b/src/libtmux/_compat.py @@ -31,3 +31,9 @@ def str_from_console(s: t.Union[str, bytes]) -> str: return str(s) except UnicodeDecodeError: return str(s, encoding="utf_8") if isinstance(s, bytes) else s + + +try: + from packaging.version import LegacyVersion as LooseVersion, Version +except ImportError: + from distutils.version import LooseVersion, Version diff --git a/src/libtmux/common.py b/src/libtmux/common.py index 430053963..9f27b7f53 100644 --- a/src/libtmux/common.py +++ b/src/libtmux/common.py @@ -11,7 +11,6 @@ import subprocess import sys import typing as t -from distutils.version import LooseVersion from typing import Dict, Generic, KeysView, List, Optional, TypeVar, Union, overload from . import exc @@ -25,6 +24,9 @@ from libtmux.window import Window +from . import exc +from ._compat import LooseVersion, console_to_str, str_from_console + logger = logging.getLogger(__name__) diff --git a/tests/test_common.py b/tests/test_common.py index d09d21e84..00524fb51 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -3,12 +3,12 @@ import re import sys import typing as t -from distutils.version import LooseVersion from typing import Optional import pytest import libtmux +from libtmux._compat import LooseVersion from libtmux.common import ( TMUX_MAX_VERSION, TMUX_MIN_VERSION, @@ -43,7 +43,7 @@ def mock_tmux_cmd(*args: t.Any, **kwargs: t.Any) -> Hi: assert has_gte_version(TMUX_MIN_VERSION) assert has_gt_version(TMUX_MAX_VERSION), "Greater than the max-supported version" assert ( - "%s-master" % TMUX_MAX_VERSION == get_version() + LooseVersion("%s-master" % TMUX_MAX_VERSION) == get_version() ), "Is the latest supported version with -master appended" @@ -63,6 +63,7 @@ def mock_tmux_cmd(*args: t.Any, **kwargs: t.Any) -> Hi: assert has_gte_version(TMUX_MIN_VERSION) assert has_gt_version(TMUX_MAX_VERSION), "Greater than the max-supported version" assert TMUX_NEXT_VERSION == get_version() + assert LooseVersion("2.9") == get_version() def test_get_version_openbsd(monkeypatch: pytest.MonkeyPatch) -> None: @@ -78,7 +79,7 @@ def mock_tmux_cmd(*args: t.Any, **kwargs: t.Any) -> Hi: assert has_gte_version(TMUX_MIN_VERSION) assert has_gt_version(TMUX_MAX_VERSION), "Greater than the max-supported version" assert ( - "%s-openbsd" % TMUX_MAX_VERSION == get_version() + LooseVersion("%s-openbsd" % TMUX_MAX_VERSION) == get_version() ), "Is the latest supported version with -openbsd appended" From 3e1e5d1b19d9be3776c69a3ff71b3ebade015210 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 14 Jan 2022 05:09:13 -0600 Subject: [PATCH 3/5] Vendorize LegacyVersion behavior for patterns like "2.4-openbsd" We still need those to work - not to break pypi conformance, but since we use this for tmux versions. --- src/libtmux/_compat.py | 94 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/src/libtmux/_compat.py b/src/libtmux/_compat.py index e47146b5d..40066f2a6 100644 --- a/src/libtmux/_compat.py +++ b/src/libtmux/_compat.py @@ -34,6 +34,98 @@ def str_from_console(s: t.Union[str, bytes]) -> str: try: - from packaging.version import LegacyVersion as LooseVersion, Version + import re + + from packaging.version import Version, _BaseVersion + + ### + ### Legacy support for LooseVersion / LegacyVersion, e.g. 2.4-openbsd + ### https://github.com/pypa/packaging/blob/21.3/packaging/version.py#L106-L115 + ### License: BSD, Accessed: Jan 14th, 2022 + ### + + _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): + # type: (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): + # type: (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 = [] # type: 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) + + class LegacyVersion(_BaseVersion): + def __init__(self, version): + # type: (str) -> None + self._version = str(version) + self._key = _legacy_cmpkey(self._version) + + def __str__(self): + # type: () -> str + return self._version + + def __repr__(self): + # type: () -> str + return "".format(repr(str(self))) + + @property + def public(self): + # type: () -> str + return self._version + + @property + def base_version(self): + # type: () -> str + return self._version + + @property + def epoch(self): + # type: () -> int + return -1 + + LooseVersion = LegacyVersion except ImportError: from distutils.version import LooseVersion, Version From 1a5daa8c940767655ed0dbc7352f96c57b0ef50b Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 14 Jan 2022 05:12:58 -0600 Subject: [PATCH 4/5] LegacyVersion: Consolidate typings --- src/libtmux/_compat.py | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/src/libtmux/_compat.py b/src/libtmux/_compat.py index 40066f2a6..12a06ce75 100644 --- a/src/libtmux/_compat.py +++ b/src/libtmux/_compat.py @@ -35,6 +35,7 @@ def str_from_console(s: t.Union[str, bytes]) -> str: try: import re + from typing import Iterator, List, Tuple from packaging.version import Version, _BaseVersion @@ -44,6 +45,8 @@ def str_from_console(s: t.Union[str, bytes]) -> str: ### License: BSD, Accessed: Jan 14th, 2022 ### + LegacyCmpKey = Tuple[int, Tuple[str, ...]] + _legacy_version_component_re = re.compile(r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE) _legacy_version_replacement_map = { "pre": "c", @@ -53,8 +56,7 @@ def str_from_console(s: t.Union[str, bytes]) -> str: "dev": "@", } - def _parse_version_parts(s): - # type: (str) -> Iterator[str] + 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) @@ -70,9 +72,7 @@ def _parse_version_parts(s): # ensure that alpha/beta/candidate are before final yield "*final" - def _legacy_cmpkey(version): - # type: (str) -> LegacyCmpKey - + 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, @@ -81,7 +81,7 @@ def _legacy_cmpkey(version): # This scheme is taken from pkg_resources.parse_version setuptools prior to # it's adoption of the packaging library. - parts = [] # type: List[str] + parts: List[str] = [] for part in _parse_version_parts(version.lower()): if part.startswith("*"): # remove "-" before a prerelease tag @@ -98,32 +98,26 @@ def _legacy_cmpkey(version): return epoch, tuple(parts) class LegacyVersion(_BaseVersion): - def __init__(self, version): - # type: (str) -> None + def __init__(self, version: str) -> None: self._version = str(version) self._key = _legacy_cmpkey(self._version) - def __str__(self): - # type: () -> str + def __str__(self) -> str: return self._version - def __repr__(self): - # type: () -> str + def __repr__(self) -> str: return "".format(repr(str(self))) @property - def public(self): - # type: () -> str + def public(self) -> str: return self._version @property - def base_version(self): - # type: () -> str + def base_version(self) -> str: return self._version @property - def epoch(self): - # type: () -> int + def epoch(self) -> int: return -1 LooseVersion = LegacyVersion From 9306d467d86df10a9386bb29c4d53df2e6998b13 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 15 Jan 2022 09:36:11 -0600 Subject: [PATCH 5/5] Handle string comparisons --- src/libtmux/_compat.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/libtmux/_compat.py b/src/libtmux/_compat.py index 12a06ce75..2650306e2 100644 --- a/src/libtmux/_compat.py +++ b/src/libtmux/_compat.py @@ -105,6 +105,19 @@ def __init__(self, version: str) -> None: def __str__(self) -> str: return self._version + def __lt__(self, other): + if isinstance(other, str): + other = LegacyVersion(other) + return super().__lt__(other) + + def __eq__(self, other) -> bool: + if isinstance(other, str): + other = LegacyVersion(other) + if not isinstance(other, LegacyVersion): + return NotImplemented + + return self._key == other._key + def __repr__(self) -> str: return "".format(repr(str(self)))