refactor: drop packaging dependency, inline version parsing
Lint & Test / test (push) Successful in 3m9s
Lint & Test / test (push) Successful in 3m9s
The only user of 'packaging' was version_check.py — two small functions (normalize_version, is_newer) that just need to parse "1.2.3-alpha.1" and compare PEP 440-style versions. That's well within stdlib reach. - Inline a NamedTuple-based Version with kind/pre_num ordering (dev < alpha < beta < rc < release), same regex-normalized format - Define a local InvalidVersion exception - Remove packaging>=23.0 from pyproject.toml dependencies Why now: the Windows cross-build uses a hard-coded DEPS array in build-dist-windows.sh, which was never updated when 'packaging' was added on March 25. Result: importable from pip-installed dev envs, missing from the portable installer — tray icon appeared but uvicorn died with ModuleNotFoundError: No module named 'packaging'. Removing the dep entirely is cleaner than adding one more hard-coded entry to the Windows DEPS list. Tests (678 passing) and a manual test matrix covering dev/alpha/beta/rc/release ordering all pass.
This commit is contained in:
@@ -26,7 +26,6 @@ dependencies = [
|
||||
"fastapi>=0.115.0",
|
||||
"uvicorn[standard]>=0.32.0",
|
||||
"httpx>=0.27.2",
|
||||
"packaging>=23.0",
|
||||
"mss>=9.0.2",
|
||||
"numpy>=2.1.3",
|
||||
"pydantic>=2.9.2",
|
||||
|
||||
@@ -1,40 +1,99 @@
|
||||
"""Version comparison utilities.
|
||||
|
||||
Normalizes Gitea-style tags (v0.3.0-alpha.1) to PEP 440 (0.3.0a1)
|
||||
so that ``packaging.version.Version`` can compare them correctly.
|
||||
and compares them using a tuple-based ordering. Deliberately does
|
||||
not depend on the external ``packaging`` library — it's just one
|
||||
more dependency to ship in the portable installer for a handful
|
||||
of simple comparisons we can do with stdlib.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
from packaging.version import InvalidVersion, Version
|
||||
from typing import NamedTuple
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class InvalidVersion(ValueError):
|
||||
"""Raised when a version string cannot be parsed."""
|
||||
|
||||
|
||||
# Release-kind ordering — smaller = earlier.
|
||||
# dev < alpha < beta < rc < release
|
||||
_KIND_DEV = 0
|
||||
_KIND_ALPHA = 1
|
||||
_KIND_BETA = 2
|
||||
_KIND_RC = 3
|
||||
_KIND_RELEASE = 4
|
||||
|
||||
|
||||
class Version(NamedTuple):
|
||||
"""Comparable version tuple. Larger tuples are newer versions."""
|
||||
|
||||
major: int
|
||||
minor: int
|
||||
patch: int
|
||||
kind: int
|
||||
pre_num: int
|
||||
|
||||
|
||||
_PRE_MAP = {
|
||||
"alpha": "a",
|
||||
"beta": "b",
|
||||
"rc": "rc",
|
||||
"alpha": _KIND_ALPHA,
|
||||
"a": _KIND_ALPHA,
|
||||
"beta": _KIND_BETA,
|
||||
"b": _KIND_BETA,
|
||||
"rc": _KIND_RC,
|
||||
}
|
||||
|
||||
_PRE_PATTERN = re.compile(
|
||||
r"^(\d+\.\d+\.\d+)[-.]?(alpha|beta|rc)[.-]?(\d+)$", re.IGNORECASE
|
||||
# Matches "1.2.3" optionally followed by a pre-release segment.
|
||||
# Accepts Gitea-style ("1.2.3-alpha.1") and PEP 440 ("1.2.3a1", "1.2.3.dev0").
|
||||
_VERSION_PATTERN = re.compile(
|
||||
r"""
|
||||
^
|
||||
(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)
|
||||
(?:
|
||||
[-.]? # optional separator
|
||||
(?P<pre_label>alpha|beta|rc|a|b|dev) # pre-release label
|
||||
[.-]? # optional separator
|
||||
(?P<pre_num>\d+) # pre-release number
|
||||
)?
|
||||
$
|
||||
""",
|
||||
re.IGNORECASE | re.VERBOSE,
|
||||
)
|
||||
|
||||
|
||||
def normalize_version(raw: str) -> Version:
|
||||
"""Convert a tag like ``v0.3.0-alpha.1`` to a PEP 440 ``Version``.
|
||||
"""Parse a version string into a comparable ``Version`` tuple.
|
||||
|
||||
Raises ``InvalidVersion`` if the string cannot be parsed.
|
||||
Accepts Gitea-style tags (``v0.3.0-alpha.1``) and PEP 440
|
||||
(``0.3.0a1``, ``0.0.0.dev0``). Raises ``InvalidVersion`` if
|
||||
the string cannot be parsed.
|
||||
"""
|
||||
cleaned = raw.lstrip("v").strip()
|
||||
m = _PRE_PATTERN.match(cleaned)
|
||||
if m:
|
||||
base, pre_label, pre_num = m.group(1), m.group(2).lower(), m.group(3)
|
||||
pep_label = _PRE_MAP.get(pre_label, pre_label)
|
||||
cleaned = f"{base}{pep_label}{pre_num}"
|
||||
return Version(cleaned)
|
||||
cleaned = raw.lstrip("vV").strip()
|
||||
m = _VERSION_PATTERN.match(cleaned)
|
||||
if not m:
|
||||
raise InvalidVersion(f"Unparseable version: {raw!r}")
|
||||
|
||||
major = int(m.group("major"))
|
||||
minor = int(m.group("minor"))
|
||||
patch = int(m.group("patch"))
|
||||
|
||||
pre_label = m.group("pre_label")
|
||||
pre_num_str = m.group("pre_num")
|
||||
|
||||
if pre_label is None:
|
||||
kind = _KIND_RELEASE
|
||||
pre_num = 0
|
||||
else:
|
||||
label = pre_label.lower()
|
||||
if label == "dev":
|
||||
kind = _KIND_DEV
|
||||
else:
|
||||
kind = _PRE_MAP[label]
|
||||
pre_num = int(pre_num_str) if pre_num_str is not None else 0
|
||||
|
||||
return Version(major, minor, patch, kind, pre_num)
|
||||
|
||||
|
||||
def is_newer(candidate: str, current: str) -> bool:
|
||||
|
||||
Reference in New Issue
Block a user