From c1c279274ca536aed667dca5c00f0f1c3a8a42a9 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Sun, 15 Jun 2025 17:06:04 +0200 Subject: [PATCH 01/26] Add py.typed Signed-off-by: jaimergp --- src/univers/py.typed | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/univers/py.typed diff --git a/src/univers/py.typed b/src/univers/py.typed new file mode 100644 index 00000000..e69de29b From bc21626904d3b42d8e30ff2f9cd38db8be7f6fac Mon Sep 17 00:00:00 2001 From: jaimergp Date: Sun, 15 Jun 2025 17:10:45 +0200 Subject: [PATCH 02/26] Add type hints for univers.versions Signed-off-by: jaimergp --- src/univers/versions.py | 132 +++++++++++++++++++++------------------- 1 file changed, 70 insertions(+), 62 deletions(-) diff --git a/src/univers/versions.py b/src/univers/versions.py index c06f4ff0..60f6b7ed 100644 --- a/src/univers/versions.py +++ b/src/univers/versions.py @@ -4,6 +4,8 @@ # # Visit https://aboutcode.org and https://github.com/aboutcode-org/univers for support and download. +from typing import TYPE_CHECKING + import attr import semantic_version from packaging import version as packaging_version @@ -18,6 +20,12 @@ from univers.conan.version import Version as conan_version from univers.utils import remove_spaces +if TYPE_CHECKING: + from typing import Any + + from univers.version_constraint import VersionConstraint + + """ Version classes encapsulating the details of each version syntax. For instance semver is a version syntax. Python and Debian use another syntax. @@ -34,7 +42,7 @@ class InvalidVersion(ValueError): pass -def is_valid_alpine_version(s): +def is_valid_alpine_version(s: str) -> bool: """ Return True is the string `s` is a valid Alpine version. We do not support yet version strings that start with @@ -81,7 +89,7 @@ class Version: # a comparable scheme-specific version object constructed from the version string value = attr.ib(default=None, repr=False, eq=True, order=True, hash=True) - def __attrs_post_init__(self): + def __attrs_post_init__(self) -> None: normalized_string = self.normalize(self.string) if not self.is_valid(normalized_string): raise InvalidVersion(f"{self.string!r} is not a valid {self.__class__!r}") @@ -95,7 +103,7 @@ def __attrs_post_init__(self): object.__setattr__(self, "value", value) @classmethod - def is_valid(cls, string): + def is_valid(cls, string) -> bool: """ Return True if the ``string`` is a valid version for its scheme or False if not valid. The empty string, None, False and 0 are considered invalid. @@ -104,7 +112,7 @@ def is_valid(cls, string): return bool(string) @classmethod - def normalize(cls, string): + def normalize(cls, string: str) -> str: """ Return a normalized version string from ``string ``. Subclass can override. """ @@ -112,7 +120,7 @@ def normalize(cls, string): return remove_spaces(string).lstrip("vV") @classmethod - def build_value(self, string): + def build_value(self, string: str) -> str: """ Return a wrapped version "value" object for a version ``string``. Subclasses can override. The default is a no-op and returns the string @@ -121,7 +129,7 @@ def build_value(self, string): """ return string - def satisfies(self, constraint): + def satisfies(self, constraint: VersionConstraint) -> bool: """ Return True if this Version satisfies the ``constraint`` VersionConstraint. Satisfying means that this version is "within" the @@ -129,25 +137,25 @@ def satisfies(self, constraint): """ return self in constraint - def __str__(self): + def __str__(self) -> str: return str(self.value) class AllVersion(Version): @classmethod - def is_valid(cls, string): + def is_valid(cls, string: str) -> bool: return string == "vers:all/*" class NoneVersion(Version): @classmethod - def is_valid(cls, string): + def is_valid(cls, string: str) -> bool: return string == "vers:none/*" class GenericVersion(Version): @classmethod - def is_valid(cls, string): + def is_valid(cls, string: str) -> bool: # generic implementation ... # TODO: Should use # https://github.com/repology/libversion/blob/master/doc/ALGORITHM.md#core-algorithm @@ -168,14 +176,14 @@ class PypiVersion(Version): # TODO: use packvers and handle legacy versions @classmethod - def build_value(cls, string): + def build_value(cls, string: str) -> packaging_version.Version: """ Return a packaging.version.LegacyVersion or packaging.version.Version """ return packaging_version.Version(string) @classmethod - def is_valid(cls, string): + def is_valid(cls, string: str) -> bool: try: # Note: we consider only modern pep440 versions as valid. legacy # will fail validation for now. @@ -187,7 +195,7 @@ def is_valid(cls, string): class EnhancedSemanticVersion(semantic_version.Version): @property - def precedence_key(self): + def precedence_key(self) -> tuple[Any, ...]: key = super(EnhancedSemanticVersion, self).precedence_key return key + (self.build or ()) @@ -198,11 +206,11 @@ class SemverVersion(Version): """ @classmethod - def build_value(cls, string): + def build_value(cls, string: str) -> EnhancedSemanticVersion | semantic_version.Version: return EnhancedSemanticVersion.coerce(string) @classmethod - def is_valid(cls, string): + def is_valid(cls, string: str) -> bool: try: cls.build_value(string) return True @@ -229,26 +237,26 @@ def prerelease(self): def build(self): return self.value and self.value.build - def next_major(self): + def next_major(self) -> "SemverVersion": return self.value and SemverVersion(str(self.value.next_major())) - def next_minor(self): + def next_minor(self) -> "SemverVersion": return self.value and SemverVersion(str(self.value.next_minor())) - def next_patch(self): + def next_patch(self) -> "SemverVersion": return self.value and SemverVersion(str(self.value.next_patch())) -def is_even(s): +def is_even(s: str) -> bool: """ Return True if the string "s" is an even number and False if this is an odd number. For example: - >>> is_even(4) + >>> is_even("4") True - >>> is_even(123) + >>> is_even("123") False - >>> is_even(0) + >>> is_even("0") True """ return (int(s) % 2) == 0 @@ -260,7 +268,7 @@ class NginxVersion(SemverVersion): """ @property - def is_stable(self): + def is_stable(self) -> bool: """ True if this is a "stable "version """ @@ -275,36 +283,36 @@ class RubygemsVersion(Version): """ @classmethod - def build_value(cls, string): + def build_value(cls, string) -> gem.GemVersion: return gem.GemVersion(string) @classmethod - def is_valid(cls, string): + def is_valid(cls, string) -> bool: return gem.GemVersion.is_correct(string) class ArchLinuxVersion(Version): - def __eq__(self, other): + def __eq__(self, other: "ArchLinuxVersion") -> bool: if not isinstance(other, self.__class__): return NotImplemented return arch.vercmp(self.value, other.value) == 0 - def __lt__(self, other): + def __lt__(self, other: "ArchLinuxVersion") -> bool: if not isinstance(other, self.__class__): return NotImplemented return arch.vercmp(self.value, other.value) < 0 - def __gt__(self, other): + def __gt__(self, other: "ArchLinuxVersion") -> bool: if not isinstance(other, self.__class__): return NotImplemented return arch.vercmp(self.value, other.value) > 0 - def __le__(self, other): + def __le__(self, other: "ArchLinuxVersion") -> bool: if not isinstance(other, self.__class__): return NotImplemented return arch.vercmp(self.value, other.value) <= 0 - def __ge__(self, other): + def __ge__(self, other: "ArchLinuxVersion") -> bool: if not isinstance(other, self.__class__): return NotImplemented return arch.vercmp(self.value, other.value) >= 0 @@ -312,11 +320,11 @@ def __ge__(self, other): class DebianVersion(Version): @classmethod - def build_value(cls, string): + def build_value(cls, string: str) -> debian.Version: return debian.Version.from_string(string) @classmethod - def is_valid(cls, string): + def is_valid(cls, string: str) -> bool: return debian.Version.is_valid(string) @@ -325,11 +333,11 @@ class MavenVersion(Version): # https://github.com/apache/maven/tree/master/maven-artifact/src/main/java/org/apache/maven/artifact/versioning @classmethod - def build_value(cls, string): + def build_value(cls, string: str) -> maven.Version: return maven.Version(string) @classmethod - def is_valid(cls, string): + def is_valid(cls, string: str) -> bool: try: cls.build_value(string) return True @@ -342,11 +350,11 @@ class NugetVersion(Version): # See https://docs.microsoft.com/en-us/nuget/concepts/package-versioning @classmethod - def build_value(cls, string): + def build_value(cls, string: str) -> nuget.Version: return nuget.Version.from_string(string) @classmethod - def is_valid(cls, string): + def is_valid(cls, string: str) -> bool: try: cls.build_value(string) return True @@ -360,26 +368,26 @@ class RpmVersion(Version): """ @classmethod - def build_value(cls, string): + def build_value(cls, string: str) -> rpm.RpmVersion: return rpm.RpmVersion.from_string(string) class GentooVersion(Version): @classmethod - def is_valid(cls, string): + def is_valid(cls, string: str) -> bool: return gentoo.is_valid(string) - def __eq__(self, other): + def __eq__(self, other: "GentooVersion") -> bool: if not isinstance(other, self.__class__): return NotImplemented return gentoo.vercmp(self.value, other.value) == 0 - def __lt__(self, other): + def __lt__(self, other: "GentooVersion") -> bool: if not isinstance(other, self.__class__): return NotImplemented return gentoo.vercmp(self.value, other.value) == -1 - def __gt__(self, other): + def __gt__(self, other: "GentooVersion") -> bool: if not isinstance(other, self.__class__): return NotImplemented return gentoo.vercmp(self.value, other.value) == 1 @@ -387,19 +395,19 @@ def __gt__(self, other): class AlpineLinuxVersion(GentooVersion): @classmethod - def is_valid(cls, string): + def is_valid(cls, string: str) -> bool: return is_valid_alpine_version(string) and gentoo.is_valid(string) class ComposerVersion(SemverVersion): @classmethod - def build_value(cls, string): + def build_value(cls, string: str) -> SemverVersion: return super().build_value(string.lstrip("vV")) class GolangVersion(SemverVersion): @classmethod - def build_value(cls, string): + def build_value(cls, string: str) -> SemverVersion: return super().build_value(string.lstrip("vV")) @@ -434,11 +442,11 @@ def __attrs_post_init__(self): object.__setattr__(self, "patch", patch) @classmethod - def is_valid(cls, string): + def is_valid(cls, string: str) -> bool: return bool(cls.parse(string)) @classmethod - def parse(cls, string): + def parse(cls, string: str) -> bool | tuple[int, int, int, str]: """ Return a four-tuple of (major, minor, build, patch) version segments where major, minor, build are integers and patch is a string possibly empty. @@ -489,13 +497,13 @@ def parse(cls, string): return major, minor, build, patch @classmethod - def build_value(cls, string): + def build_value(cls, string: str) -> "LegacyOpensslVersion": return cls.parse(string) - def __str__(self): + def __str__(self) -> str: return f"{self.major}.{self.minor}.{self.build}{self.patch}" - def __lt__(self, other): + def __lt__(self, other: "LegacyOpensslVersion") -> bool: if not isinstance(other, self.__class__): return NotImplemented # Check if versions have the same base, and `one and only one` of them is a pre-release. @@ -505,7 +513,7 @@ def __lt__(self, other): return self.is_prerelease() return self.value.__lt__(other.value) - def __gt__(self, other): + def __gt__(self, other: "LegacyOpensslVersion") -> bool: if not isinstance(other, self.__class__): return NotImplemented if (self.major, self.minor, self.build) == (other.major, other.minor, other.build) and ( @@ -514,7 +522,7 @@ def __gt__(self, other): return other.is_prerelease() return self.value.__gt__(other.value) - def is_prerelease(self): + def is_prerelease(self) -> bool: return self.patch.startswith(("-beta", "-alpha")) @@ -537,11 +545,11 @@ class OpensslVersion(Version): """ @classmethod - def is_valid(cls, string): + def is_valid(cls, string: str) -> bool: return cls.is_valid_new(string) or cls.is_valid_legacy(string) @classmethod - def build_value(cls, string): + def build_value(cls, string: str) -> LegacyOpensslVersion | SemverVersion: """ Return a wrapped version "value" object depending on whether version is legacy or semver. @@ -552,7 +560,7 @@ def build_value(cls, string): return SemverVersion(string) @classmethod - def is_valid_new(cls, string): + def is_valid_new(cls, string: str) -> bool | None: """ Check the validity of new Openssl Version. @@ -569,17 +577,17 @@ def is_valid_new(cls, string): return sem.major >= 3 @classmethod - def is_valid_legacy(cls, string): + def is_valid_legacy(cls, string: str) -> bool: return LegacyOpensslVersion.is_valid(string) - def __eq__(self, other): + def __eq__(self, other: "OpensslVersion") -> bool: if not isinstance(other, self.__class__): return NotImplemented if not isinstance(other.value, self.value.__class__): return NotImplemented return self.value.__eq__(other.value) - def __lt__(self, other): + def __lt__(self, other: "OpensslVersion") -> bool: if not isinstance(other, self.__class__): return NotImplemented if isinstance(other.value, self.value.__class__): @@ -587,7 +595,7 @@ def __lt__(self, other): # By construction legacy version is always behind Semver return isinstance(self.value, LegacyOpensslVersion) - def __gt__(self, other): + def __gt__(self, other: "OpensslVersion") -> bool: if not isinstance(other, self.__class__): return NotImplemented if isinstance(other.value, self.value.__class__): @@ -595,7 +603,7 @@ def __gt__(self, other): # By construction semver version is always ahead of legacy return isinstance(self.value, SemverVersion) - def __le__(self, other): + def __le__(self, other: "OpensslVersion") -> bool: if not isinstance(other, self.__class__): return NotImplemented if isinstance(other.value, self.value.__class__): @@ -603,7 +611,7 @@ def __le__(self, other): # version value are of diff type, then legacy one is always behind semver return isinstance(self.value, LegacyOpensslVersion) - def __ge__(self, other): + def __ge__(self, other: "OpensslVersion") -> bool: if not isinstance(other, self.__class__): return NotImplemented if isinstance(other.value, self.value.__class__): @@ -614,11 +622,11 @@ def __ge__(self, other): class ConanVersion(Version): @classmethod - def build_value(cls, string): + def build_value(cls, string: str) -> conan_version: return conan_version(string) @classmethod - def is_valid(cls, string): + def is_valid(cls, string: str) -> bool: try: cls.build_value(string) return True From 8a374e791daeb837af7c4a02fc14523c3525ccbb Mon Sep 17 00:00:00 2001 From: jaimergp Date: Sun, 15 Jun 2025 17:10:50 +0200 Subject: [PATCH 03/26] Add type hints for univers.utils Signed-off-by: jaimergp --- src/univers/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/univers/utils.py b/src/univers/utils.py index 63952ce4..d4d82d88 100644 --- a/src/univers/utils.py +++ b/src/univers/utils.py @@ -4,12 +4,14 @@ # # Visit https://aboutcode.org and https://github.com/aboutcode-org/univers for support and download. +from typing import Any -def remove_spaces(string): + +def remove_spaces(string: str) -> str: return "".join(string.split()) -def cmp(x, y): +def cmp(x: Any, y: Any) -> int: """ Replacement for built-in Python 2 function cmp that was removed in Python 3 From https://docs.python.org/2/library/functions.html?highlight=cmp#cmp : From db837cfd5335d46e7b40ffa86c8bb8a1e16c662f Mon Sep 17 00:00:00 2001 From: jaimergp Date: Sun, 15 Jun 2025 17:21:24 +0200 Subject: [PATCH 04/26] Add type hints for univers.version_constraint Signed-off-by: jaimergp --- src/univers/version_constraint.py | 48 ++++++++++++++++--------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/src/univers/version_constraint.py b/src/univers/version_constraint.py index 36ec6d81..783c5ca0 100644 --- a/src/univers/version_constraint.py +++ b/src/univers/version_constraint.py @@ -5,7 +5,9 @@ # Visit https://aboutcode.org and https://github.com/aboutcode-org/univers for support and download. import operator +from collections.abc import Iterable from functools import total_ordering +from typing import Any, Callable, Dict, List, Literal, Tuple import attr @@ -19,7 +21,7 @@ # back from docs at https://docs.python.org/3/library/itertools.html#itertools.pairwise import itertools - def pairwise(iterable): + def pairwise(iterable: Iterable[Any]) -> Iterable[Tuple[Any, Any]]: a, b = itertools.tee(iterable) next(b, None) return zip(a, b) @@ -32,7 +34,7 @@ def pairwise(iterable): """ -def operator_star(a, b): +def operator_star(a: Any, b: Any) -> Literal[True]: """ Comparison operator for the star "*" constraint comparator. Since it matches any version, it is always True. @@ -65,18 +67,18 @@ class VersionConstraint: """ # one of the COMPARATORS - comparator = attr.ib(type=str, default="=") + comparator: str = attr.ib(type=str, default="=") # a Version subclass instance - version = attr.ib(type=Version, default=None) + version: Version = attr.ib(type=Version, default=None) # a function for the comparator - comp_operator = attr.ib(default=None, repr=False) + comp_operator: Callable = attr.ib(default=None, repr=False) # a Version subclass - version_class = attr.ib(type=Version, default=None, repr=False) + version_class: Version = attr.ib(type=Version, default=None, repr=False) - def __attrs_post_init__(self): + def __attrs_post_init__(self) -> None: # Notes: setattr is used because this is an immutable frozen instance. # See https://www.attrs.org/en/stable/init.html?#post-init try: @@ -95,7 +97,7 @@ def __attrs_post_init__(self): else: raise ValueError("Cannot build a VersionConstraint without a version class") - def __str__(self): + def __str__(self) -> str: """ Return a string representing this constraint. For example:: @@ -116,27 +118,27 @@ def __str__(self): to_string = __str__ - def to_dict(self): + def to_dict(self) -> Dict[str, str]: return dict(comparator=self.comparator, version=str(self.version)) - def __hash__(self): + def __hash__(self) -> int: return hash(str(self)) - def __eq__(self, other): + def __eq__(self, other: "VersionConstraint") -> bool: if not isinstance(other, self.__class__): return NotImplemented return self.comparator == other.comparator and self.version == other.version - def __lt__(self, other): + def __lt__(self, other: "VersionConstraint") -> bool: if not isinstance(other, self.__class__): return NotImplemented # we compare tuples, version first return (self.version, self.comparator).__lt__((other.version, other.comparator)) - def is_star(self): + def is_star(self) -> bool: return self.comparator == "*" - def invert(self): + def invert(self) -> "VersionConstraint": """ Return a new VersionConstraint instance with the comparator inverted. For example:: @@ -161,7 +163,7 @@ def invert(self): ) @classmethod - def from_string(cls, string, version_class): + def from_string(cls, string: str, version_class: Version) -> "VersionConstraint": """ Return a single VersionConstraint built from a constraint ``string`` and a ``version_class`` Version class. @@ -188,7 +190,7 @@ def from_string(cls, string, version_class): return cls(comparator=comparator, version=version, version_class=version_class) @staticmethod - def split(string): + def split(string: str) -> Tuple[str, str]: """ Return a tuple of (comparator, version) strings given a constraint ``string`` such as ">=2.3". @@ -220,7 +222,7 @@ def split(string): # default to equality return "=", constraint_string - def __contains__(self, version): + def __contains__(self, version: Version) -> bool: """ Return a True if the ``version`` Version is contained in this VersionConstraint or "satisfies" this VersionConstraint. @@ -262,7 +264,7 @@ def __contains__(self, version): contains = __contains__ @classmethod - def validate(cls, constraints): + def validate(cls, constraints: List["VersionConstraint"] | Tuple["VersionConstraint"]): """ Raise an assertion error if the ``constraints`` is not a list of VersionConstraint objects or if two VersionConstraint contain the same @@ -292,7 +294,7 @@ def validate(cls, constraints): return validate_comparators(constraints) @classmethod - def simplify(cls, constraints): + def simplify(cls, constraints: Iterable["VersionConstraint"]) -> List["VersionConstraint"]: """ Return a new simplified ``constraints`` list with duplicated constraints removed. This includes removing exact duplicates and redundant @@ -303,7 +305,7 @@ def simplify(cls, constraints): return constraints -def deduplicate(constraints): +def deduplicate(constraints: Iterable[Any]) -> List[Any]: """ Return a new ``constraints`` list with exact duplicated constraints removed. """ @@ -316,7 +318,7 @@ def deduplicate(constraints): return unique -def validate_comparators(constraints): +def validate_comparators(constraints: Iterable[VersionConstraint]) -> bool: """ Raise an assertion error if the ``constraints`` list contains an invalid sequence of constraint comparators according to ``vers`` rules. @@ -399,7 +401,7 @@ def validate_comparators(constraints): return True -def simplify_constraints(constraints): +def simplify_constraints(constraints: Iterable[VersionConstraint]) -> List[VersionConstraint]: """ Return a list of VersionConstraint given a ``constraints`` list by discarding redundant constraints according to ``vers`` rules. @@ -475,7 +477,7 @@ class InvalidConstraintsError(Exception): pass -def contains_version(version, constraints): +def contains_version(version: Version, constraints: Iterable[VersionConstraint]) -> bool: """ Return True an assertion error if the ``constraints`` list contains the ``version`` Version object according to ``vers`` rules. From 30c4a7c4c91f86b6bdb3c5067152109524034431 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Wed, 25 Jun 2025 18:23:39 +0200 Subject: [PATCH 05/26] wip version_range Signed-off-by: jaimergp --- src/univers/version_range.py | 67 ++++++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/src/univers/version_range.py b/src/univers/version_range.py index bee601ef..4592f6cd 100644 --- a/src/univers/version_range.py +++ b/src/univers/version_range.py @@ -4,6 +4,9 @@ # # Visit https://aboutcode.org and https://github.com/aboutcode-org/univers for support and download. +from collections.abc import Iterable +from typing import Any +from typing import Dict from typing import List from typing import Union @@ -66,14 +69,14 @@ class VersionRange: # timeline constraints = attr.ib(type=tuple, default=attr.Factory(tuple)) - def __attrs_post_init__(self, *args, **kwargs): + def __attrs_post_init__(self, *args, **kwargs) -> None: constraints = tuple(sorted(self.constraints)) # Notes: setattr is used because this is an immutable frozen instance. # See https://www.attrs.org/en/stable/init.html?#post-init object.__setattr__(self, "constraints", constraints) @classmethod - def from_native(cls, string): + def from_native(cls, string: str): """ Return a VersionRange built from a scheme-specific, native version range ``string``. Subclasses can implement. @@ -81,7 +84,7 @@ def from_native(cls, string): return NotImplementedError @classmethod - def from_natives(cls, strings): + def from_natives(cls, strings: Iterable[str]): """ Return a VersionRange built from a ``strings`` list of scheme- specific native version range strings. Subclasses can implement. @@ -98,7 +101,9 @@ def to_native(self, *args, **kwargs): return NotImplementedError @classmethod - def from_string(cls, vers, simplify=False, validate=False): + def from_string( + cls, vers: str, simplify: bool = False, validate: bool = False + ) -> "RangeClassType": """ Return a VersionRange built from a ``vers`` version range spec string, such as "vers:npm/1.2.3,>=2.0.0" @@ -170,7 +175,7 @@ def from_string(cls, vers, simplify=False, validate=False): return range_class(parsed_constraints) @classmethod - def from_versions(cls, sequence): + def from_versions(cls, sequence: Iterable[str]) -> "VersionRange": """ Return a VersionRange built from a list of version strings, such as ["3.0.0", "1.0.1b", "3.0.2", "0.9.7a", "1.1.1ka"] @@ -185,10 +190,10 @@ def from_versions(cls, sequence): constraints.append(constraint) return cls(constraints=constraints) - def is_star(self): + def is_star(self) -> bool: return len(self.constraints) == 1 and self.constraints[0].is_star() - def invert(self): + def invert(self) -> "VersionRange": """ Return the inverse or complement of this VersionRange. For example, if this range is ">=1.0.0", the inverse is "<1.0.0". @@ -206,17 +211,17 @@ def invert(self): return self.__class__(constraints=inverted_constraints) - def __str__(self): + def __str__(self) -> str: constraints = "|".join(str(c) for c in sorted(self.constraints)) return f"vers:{self.scheme}/{constraints}" to_string = __str__ - def to_dict(self): + def to_dict(self) -> Dict[str, Any]: constraints = [c.to_dict() for c in self.constraints] return dict(scheme=self.scheme, constraints=constraints) - def __contains__(self, version): + def __contains__(self, version: versions.Version) -> bool: """ Return True if this VersionRange contains the ``version`` Version object. A version is contained in a VersionRange if it satisfies its @@ -237,14 +242,14 @@ def __contains__(self, version): contains = __contains__ - def __eq__(self, other): + def __eq__(self, other: "VersionRange") -> bool: return ( self.scheme == other.scheme and self.version_class == other.version_class and self.constraints == other.constraints ) - def normalize(self, known_versions: List[str]): + def normalize(self, known_versions: List[str]) -> "VersionRange": """ Return a new VersionRange normalized and simplified using the universe of ``known_versions`` list of version strings. @@ -276,14 +281,14 @@ def normalize(self, known_versions: List[str]): return self.__class__(constraints=version_constraints) -def from_cve_v4(data, scheme): +def from_cve_v4(data: Dict[str, Any], scheme: str) -> None: """ Return a VersionRange build from the provided CVE V4 API ``data`` using the provided versioning vers ``scheme``. """ -def from_cve_v5(data, scheme): +def from_cve_v5(data: Dict[str, Any], scheme: str) -> None: """ Return a VersionRange build from the provided CVE V5 API ``data`` using the provided versioning vers ``scheme``. @@ -296,14 +301,14 @@ def from_cve_v5(data, scheme): """ -def from_osv_v1(data, scheme): +def from_osv_v1(data: Dict[str, Any], scheme: str) -> None: """ Return a VersionRange build from the provided CVE V4 API data using the provided versioning vers ``scheme``. """ -def get_allof_constraints(cls, clause): +def get_allof_constraints(cls: VersionRange, clause: AllOf) -> List[VersionConstraint]: """ Return a list of VersionConstraint given an AllOf ``clause``. """ @@ -318,7 +323,9 @@ def get_allof_constraints(cls, clause): return allof_constraints -def get_npm_version_constraints_from_semver_npm_spec(string, cls): +def get_npm_version_constraints_from_semver_npm_spec( + string: str, cls: VersionRange +) -> List[VersionConstraint]: """ Return a VersionConstraint for the provided ``string``. """ @@ -1415,7 +1422,31 @@ def build_range_from_snyk_advisory_string(scheme: str, string: Union[str, List]) ) return vrc(constraints=version_constraints) - +RangeClassType = Union[ + NpmVersionRange, + DebianVersionRange, + PypiVersionRange, + MavenVersionRange, + NugetVersionRange, + ComposerVersionRange, + GemVersionRange, + RpmVersionRange, + GolangVersionRange, + GenericVersionRange, + ApacheVersionRange, + HexVersionRange, + CargoVersionRange, + MozillaVersionRange, + GitHubVersionRange, + EbuildVersionRange, + ArchLinuxVersionRange, + NginxVersionRange, + OpensslVersionRange, + MattermostVersionRange, + ConanVersionRange, + AllVersionRange, + NoneVersionRange, +] RANGE_CLASS_BY_SCHEMES = { "npm": NpmVersionRange, "deb": DebianVersionRange, From 9b7660ac2596d209db3f9415991d705b954dca22 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Tue, 24 Jun 2025 12:09:24 -0700 Subject: [PATCH 06/26] Raise NotImplementedError instead of returning it (#158) * Raise NotImplementedError instead of returning it Signed-off-by: Jono Yang * Update CHANGELOG.rst Signed-off-by: Jono Yang --------- Signed-off-by: Jono Yang Signed-off-by: jaimergp --- CHANGELOG.rst | 5 +++++ src/univers/version_range.py | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b495b28b..20ddc8c9 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,11 @@ Changelog ========= +Version v31.0.0 +---------------- + +- Raise instead of returning ``NotImplementedError`` in ``version_range.VersionRange`` methods. https://github.com/aboutcode-org/univers/pull/158 + Version v30.12.1 ---------------- diff --git a/src/univers/version_range.py b/src/univers/version_range.py index 4592f6cd..4ad784cd 100644 --- a/src/univers/version_range.py +++ b/src/univers/version_range.py @@ -81,7 +81,7 @@ def from_native(cls, string: str): Return a VersionRange built from a scheme-specific, native version range ``string``. Subclasses can implement. """ - return NotImplementedError + raise NotImplementedError @classmethod def from_natives(cls, strings: Iterable[str]): @@ -89,7 +89,7 @@ def from_natives(cls, strings: Iterable[str]): Return a VersionRange built from a ``strings`` list of scheme- specific native version range strings. Subclasses can implement. """ - return NotImplementedError + raise NotImplementedError def to_native(self, *args, **kwargs): """ @@ -98,7 +98,7 @@ def to_native(self, *args, **kwargs): extra arguments (such as a package name that some scheme may require like for deb and rpm.) """ - return NotImplementedError + raise NotImplementedError @classmethod def from_string( @@ -181,7 +181,7 @@ def from_versions(cls, sequence: Iterable[str]) -> "VersionRange": such as ["3.0.0", "1.0.1b", "3.0.2", "0.9.7a", "1.1.1ka"] """ if not cls.scheme or not cls.version_class: - return NotImplementedError + raise NotImplementedError constraints = [] for version in sequence: From 81249bc6e6f3ec8d214f0f2f18f680e893fc72f2 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Thu, 3 Jul 2025 16:33:16 +0200 Subject: [PATCH 07/26] add type hints for univers.conan.errors Signed-off-by: jaimergp --- setup.cfg | 1 + src/univers/conan/errors.py | 27 ++++++++++++++++++++------- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/setup.cfg b/setup.cfg index a40267a1..cfb24711 100644 --- a/setup.cfg +++ b/setup.cfg @@ -62,6 +62,7 @@ install_requires = packaging semantic-version semver + typing-extensions >= 4.1.0 [options.packages.find] diff --git a/src/univers/conan/errors.py b/src/univers/conan/errors.py index 40d1e886..4050c23c 100644 --- a/src/univers/conan/errors.py +++ b/src/univers/conan/errors.py @@ -15,16 +15,27 @@ """ from contextlib import contextmanager +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Iterable, Iterator + from typing import Any, Callable + try: + from typing import Never + except ImportError: + from typing_extensions import Never @contextmanager -def conanfile_remove_attr(conanfile, names, method): +def conanfile_remove_attr( + conanfile: Any, names: Iterable[str], method: Callable[..., Any] +) -> Iterator[Never]: """remove some self.xxxx attribute from the class, so it raises an exception if used within a given conanfile method """ original_class = type(conanfile) - def _prop(attr_name): + def _prop(attr_name: str) -> property: def _m(_): raise ConanException(f"'self.{attr_name}' access in '{method}()' method is forbidden") @@ -41,7 +52,7 @@ def _m(_): @contextmanager -def conanfile_exception_formatter(conanfile_name, func_name): +def conanfile_exception_formatter(conanfile_name: str, func_name: str) -> Iterator[Never]: """ Decorator to throw an exception formatted with the line of the conanfile where the error ocurrs. """ @@ -74,7 +85,9 @@ def _raise_conanfile_exc(e): _raise_conanfile_exc(exc) -def _format_conanfile_exception(scope, method, exception): +def _format_conanfile_exception( + scope: str, method: Callable[..., Any], exception: BaseException +) -> str: """ It will iterate the traceback lines, when it finds that the source code is inside the users conanfile it "start recording" the messages, when the trace exits the conanfile we return @@ -125,12 +138,12 @@ def __init__(self, *args, **kwargs): self.remote = kwargs.pop("remote", None) super(ConanException, self).__init__(*args, **kwargs) - def remote_message(self): + def remote_message(self) -> str: if self.remote: return " [Remote: {}]".format(self.remote.name) return "" - def __str__(self): + def __str__(self) -> str: msg = super(ConanException, self).__str__() @@ -242,7 +255,7 @@ class UserInterfaceErrorException(RequestErrorException): pass -EXCEPTION_CODE_MAPPING = { +EXCEPTION_CODE_MAPPING: dict[ConanException, int] = { InternalErrorException: 500, RequestErrorException: 400, AuthenticationException: 401, From a1d685eddd0515caf93d7fefa8f92e0f1948e48c Mon Sep 17 00:00:00 2001 From: jaimergp Date: Thu, 3 Jul 2025 16:35:17 +0200 Subject: [PATCH 08/26] type hints for univers.conan.version_range Signed-off-by: jaimergp --- src/univers/conan/version_range.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/univers/conan/version_range.py b/src/univers/conan/version_range.py index f64980fa..4cba794b 100644 --- a/src/univers/conan/version_range.py +++ b/src/univers/conan/version_range.py @@ -13,7 +13,7 @@ class _ConditionSet: - def __init__(self, expression, prerelease): + def __init__(self, expression: str, prerelease: bool): expressions = expression.split() self.prerelease = prerelease self.conditions = [] @@ -25,7 +25,7 @@ def __init__(self, expression, prerelease): self.conditions.extend(self._parse_expression(e)) @staticmethod - def _parse_expression(expression): + def _parse_expression(expression: str) -> list[_Condition | ConanVersion]: if expression == "" or expression == "*": return [_Condition(">=", ConanVersion("0.0.0"))] @@ -60,7 +60,7 @@ def first_non_zero(main): else: return [_Condition(operator, ConanVersion(version))] - def valid(self, version): + def valid(self, version: ConanVersion) -> bool: if version.pre: if not self.prerelease: return False @@ -84,7 +84,7 @@ def valid(self, version): class VersionRange: - def __init__(self, expression): + def __init__(self, expression: str): self._expression = expression tokens = expression.split(",") prereleases = None @@ -97,10 +97,10 @@ def __init__(self, expression): for alternative in version_expr.split("||"): self.condition_sets.append(_ConditionSet(alternative, prereleases)) - def __str__(self): + def __str__(self) -> str: return self._expression - def __contains__(self, version): + def __contains__(self, version: ConanVersion) -> bool: assert isinstance(version, ConanVersion), type(version) for condition_set in self.condition_sets: if condition_set.valid(version): From 29452dcaeed1f2c89ff6c95ff69099958aa18eb7 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Thu, 3 Jul 2025 16:41:32 +0200 Subject: [PATCH 09/26] add type hints for univers.conan.version Signed-off-by: jaimergp --- src/univers/conan/version.py | 51 ++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/src/univers/conan/version.py b/src/univers/conan/version.py index 7cac8635..928a9684 100644 --- a/src/univers/conan/version.py +++ b/src/univers/conan/version.py @@ -5,9 +5,16 @@ # Visit https://aboutcode.org and https://github.com/aboutcode-org/univers for support and download. from functools import total_ordering +from typing import TYPE_CHECKING from univers.conan.errors import ConanException +if TYPE_CHECKING: + try: + from typing import Self + except ImportError: + from typing_extensions import Self + @total_ordering class _VersionItem: @@ -15,32 +22,32 @@ class _VersionItem: They can be int or strings """ - def __init__(self, item): + def __init__(self, item: str | int | Self): try: self._v = int(item) except ValueError: self._v = item @property - def value(self): + def value(self) -> int: return self._v - def __str__(self): + def __str__(self) -> str: return str(self._v) - def __add__(self, other): + def __add__(self, other: Self) -> int: # necessary for the "bump()" functionality. Other aritmetic operations are missing return self._v + other - def __eq__(self, other): + def __eq__(self, other: str | int | Self) -> bool: if not isinstance(other, _VersionItem): other = _VersionItem(other) return self._v == other._v - def __hash__(self): + def __hash__(self) -> int: return hash(self._v) - def __lt__(self, other): + def __lt__(self, other: str | int | Self) -> bool: """ @type other: _VersionItem """ @@ -59,7 +66,7 @@ class Version: It is just a helper to parse "." or "-" and compare taking into account integers when possible """ - def __init__(self, value): + def __init__(self, value: int | str | Self): value = str(value) self._value = value @@ -85,7 +92,7 @@ def __init__(self, value): del items[-1] self._nonzero_items = tuple(items) - def bump(self, index): + def bump(self, index: int) -> Self: """ :meta private: Bump the version @@ -110,7 +117,7 @@ def bump(self, index): result = Version(v) return result - def upper_bound(self, index): + def upper_bound(self, index: int) -> Self: items = list(self._items[:index]) try: items.append(self._items[index] + 1) @@ -123,52 +130,52 @@ def upper_bound(self, index): return result @property - def pre(self): + def pre(self) -> Self | None: return self._pre @property - def build(self): + def build(self) -> Self | None: return self._build @property - def main(self): + def main(self) -> tuple[_VersionItem, ...]: return self._items @property - def major(self): + def major(self) -> _VersionItem | None: try: return self.main[0] except IndexError: return None @property - def minor(self): + def minor(self) -> _VersionItem | None: try: return self.main[1] except IndexError: return None @property - def patch(self): + def patch(self) -> _VersionItem | None: try: return self.main[2] except IndexError: return None @property - def micro(self): + def micro(self) -> _VersionItem | None: try: return self.main[3] except IndexError: return None - def __str__(self): + def __str__(self) -> str: return self._value - def __repr__(self): + def __repr__(self) -> str: return self._value - def __eq__(self, other): + def __eq__(self, other: int | str | Self | None) -> bool: if other is None: return False if not isinstance(other, Version): @@ -180,10 +187,10 @@ def __eq__(self, other): other._build, ) - def __hash__(self): + def __hash__(self) -> int: return hash((self._nonzero_items, self._pre, self._build)) - def __lt__(self, other): + def __lt__(self, other: int | str | Self | None) -> bool: if other is None: return False if not isinstance(other, Version): From 86f3b2a71d4110516746701c577bf1e0dff2bf5c Mon Sep 17 00:00:00 2001 From: jaimergp Date: Thu, 3 Jul 2025 16:54:10 +0200 Subject: [PATCH 10/26] add type hints for univers.debian Signed-off-by: jaimergp --- src/univers/debian.py | 63 ++++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/src/univers/debian.py b/src/univers/debian.py index 463c2e72..fc62e2d9 100644 --- a/src/univers/debian.py +++ b/src/univers/debian.py @@ -13,11 +13,20 @@ import re from functools import cmp_to_key from itertools import zip_longest +from typing import TYPE_CHECKING from attr import asdict from attr import attrib from attr import attrs +if TYPE_CHECKING: + from re import Match + try: + from typing import Self + except ImportError: + from typing_extensions import Self + + logger = logging.getLogger(__name__) """ @@ -81,11 +90,11 @@ class Version(object): sorting and 'natural order sorting'. """ - epoch = attrib(default=0) - upstream = attrib(default=None) - revision = attrib(default="0") + epoch: int = attrib(default=0) + upstream: str | None = attrib(default=None) + revision: str = attrib(default="0") - def __str__(self, *args, **kwargs): + def __str__(self, *args, **kwargs) -> str: if self.epoch: version = f"{self.epoch}:{self.upstream}" else: @@ -96,42 +105,42 @@ def __str__(self, *args, **kwargs): return version - def __repr__(self, *args, **kwargs): + def __repr__(self, *args, **kwargs) -> str: return str(self) - def __hash__(self): + def __hash__(self) -> int: return hash(self.tuple()) - def __eq__(self, other): + def __eq__(self, other: Self) -> bool: if not isinstance(other, self.__class__): return NotImplemented return self.tuple() == other.tuple() - def __ne__(self, other): + def __ne__(self, other: Self) -> bool: return not self.__eq__(other) - def __lt__(self, other): + def __lt__(self, other: Self) -> bool: if not isinstance(other, self.__class__): return NotImplemented return eval_constraint(self, "<<", other) - def __le__(self, other): + def __le__(self, other: Self) -> bool: if not isinstance(other, self.__class__): return NotImplemented return eval_constraint(self, "<=", other) - def __gt__(self, other): + def __gt__(self, other: Self) -> bool: if not isinstance(other, self.__class__): return NotImplemented return eval_constraint(self, ">>", other) - def __ge__(self, other): + def __ge__(self, other: Self) -> bool: if not isinstance(other, self.__class__): return NotImplemented return eval_constraint(self, ">=", other) @classmethod - def from_string(cls, version): + def from_string(cls, version: str) -> Self: if not version and not isinstance(version, str): raise ValueError('Invalid version string: "{}"'.format(version)) version = version.strip() @@ -154,16 +163,16 @@ def from_string(cls, version): return cls(epoch=epoch, upstream=upstream, revision=revision) @classmethod - def is_valid(cls, version): + def is_valid(cls, version: str) -> Match[str] | None: return is_valid_debian_version(version) - def compare(self, other_version): + def compare(self, other_version: str | Self) -> int: return compare_versions(self, other_version) - def to_dict(self): + def to_dict(self) -> dict[str, str | int | None]: return asdict(self) - def tuple(self): + def tuple(self) -> tuple[int, str | None, str]: return self.epoch, self.upstream, self.revision @@ -185,7 +194,7 @@ def tuple(self): ).match -def eval_constraint(version1, operator, version2): +def eval_constraint(version1: str | Version, operator: str, version2: str | Version) -> bool: """ Evaluate a versions constraint where two Debian package versions are compared with an operator such as < or >. Return True if the constraint is @@ -216,21 +225,21 @@ def eval_constraint(version1, operator, version2): return operator(result, 0) -def compare_versions_key(x): +def compare_versions_key(x): # type: ignore """ Return a key version function suitable for use in sorted(). """ return cmp_to_key(compare_versions)(x) -def compare_strings_key(x): +def compare_strings_key(x): # type: ignore """ Return a key string function suitable for use in sorted(). """ return cmp_to_key(compare_strings)(x) -def compare_strings(version1, version2): +def compare_strings(version1: str, version2: str) -> int: """ Compare two version strings (upstream or revision) using Debian semantics and return one of the following integer numbers: @@ -305,7 +314,7 @@ def compare_strings(version1, version2): return 0 -def compare_versions(version1, version2): +def compare_versions(version1: str | Version, version2: str | Version) -> int: """ Compare two Version objects or strings and return one of the following integer numbers: @@ -319,7 +328,7 @@ def compare_versions(version1, version2): return compare_version_objects(version1, version2) -def coerce_version(value): +def coerce_version(value: str | Version) -> Version: """ Return a Version object from value. @@ -331,7 +340,7 @@ def coerce_version(value): return value -def compare_version_objects(version1, version2): +def compare_version_objects(version1: Version, version2: Version) -> int: """ Compare two Version objects and return one of the following integer numbers: @@ -352,7 +361,7 @@ def compare_version_objects(version1, version2): return 0 -def get_digit_prefix(characters): +def get_digit_prefix(characters: list[str]) -> int: """ Return the digit prefix from a list of characters. """ @@ -362,7 +371,7 @@ def get_digit_prefix(characters): return value -def get_non_digit_prefix(characters): +def get_non_digit_prefix(characters: list[str]) -> list[str]: """ Return the non-digit prefix from a list of characters. """ @@ -373,7 +382,7 @@ def get_non_digit_prefix(characters): # a mapping of characters to integers representing the Debian sort order. -characters_order = { +characters_order: dict[str, int] = { # The tilde sorts before everything. "~": 0, # The empty string sort before everything except a tilde. From a534ef4f2dd450d8f118eb219942dea42555fc06 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Thu, 3 Jul 2025 17:05:23 +0200 Subject: [PATCH 11/26] add type hints for univers.gem Signed-off-by: jaimergp --- src/univers/gem.py | 85 +++++++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 38 deletions(-) diff --git a/src/univers/gem.py b/src/univers/gem.py index 2b7259e5..5ce3ca08 100644 --- a/src/univers/gem.py +++ b/src/univers/gem.py @@ -18,6 +18,15 @@ import re from collections import namedtuple from itertools import dropwhile +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Iterable + from typing import Any + try: + from typing import Self + except ImportError: + from typing_extensions import Self class InvalidRequirementError(AttributeError): @@ -184,7 +193,7 @@ class GemVersion: VERSION_PATTERN = r"[0-9]+(?:\.[0-9a-zA-Z]+)*(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?" is_correct = re.compile(rf"^\s*({VERSION_PATTERN})?\s*$").match - def __init__(self, version): + def __init__(self, version: int | str | Self): """ Construct a Version from the ``version`` string. A version string is a series of digits or ASCII letters separated by dots and may contain dash @@ -208,37 +217,37 @@ def __init__(self, version): self.version = version.replace("-", ".pre.") - def __str__(self): + def __str__(self) -> str: return self.original to_string = __str__ - def __repr__(self): + def __repr__(self) -> str: return f"GemVersion({self.original!r})" - def equal_strictly(self, other): + def equal_strictly(self, other: Self) -> bool: return self.version == other.version - def __hash__(self): + def __hash__(self) -> int: return hash(self.canonical_segments) - def __eq__(self, other): + def __eq__(self, other: Self) -> bool: return self.canonical_segments == other.canonical_segments - def __lt__(self, other): + def __lt__(self, other: Self) -> bool: return self.__cmp__(other) < 0 - def __le__(self, other): + def __le__(self, other: Self) -> bool: return self.__cmp__(other) <= 0 - def __gt__(self, other): + def __gt__(self, other: Self) -> bool: return self.__cmp__(other) > 0 - def __ge__(self, other): + def __ge__(self, other: Self) -> bool: return self.__cmp__(other) >= 0 @property - def segments(self): + def segments(self) -> tuple[int | str, ...]: """ Returns segments for this version where segments are ints or strings parsed from the original version string. @@ -251,7 +260,7 @@ def segments(self): return tuple(segments) @property - def canonical_segments(self): + def canonical_segments(self) -> tuple[int | str, ...]: """ Returns "canonical segments" for this version using the Rubygems way for canonicalization. @@ -263,7 +272,7 @@ def canonical_segments(self): canonical_segments.extend(reversed(segs)) return tuple(canonical_segments) - def bump(self): + def bump(self) -> Self: """ Return a new version object where the next to the last revision number is one greater (e.g., 5.3.1 => 5.4) i.e., incrementing this GemVersion @@ -287,7 +296,7 @@ def bump(self): segments = [str(r) for r in segments] return GemVersion(".".join(segments)) - def release(self): + def release(self) -> Self: """ Return a new GemVersion which is the release for this version (e.g., 1.2.0.a -> 1.2.0). Non-prerelease versions return themselves. A release @@ -301,14 +310,14 @@ def release(self): return GemVersion(".".join(segments)) return self - def prerelease(self): + def prerelease(self) -> bool: """ Return True if this is considered as a prerelease version. A version is considered a prerelease if it contains a letter. """ return any(not str(s).isdigit() for s in self.segments) - def split_segments(self): + def split_segments(self) -> tuple[list[str], list[str]]: """ Return a two-tuple of segments: - the first is a list of numeric-only segments starting from the left @@ -328,7 +337,7 @@ def split_segments(self): string_segments.append(seg) return numeric_segments, string_segments - def __cmp__(self, other, trace=False): + def __cmp__(self, other: Any, trace: bool = False) -> int | None: """ Compare this version with ``other`` returning -1, 0, or 1 if the other version is larger, the same, or smaller than this @@ -363,7 +372,7 @@ def __cmp__(self, other, trace=False): if lhsegments == rhsegments: if trace: - print(f" lhsegments == rhsegments: returning 0") + print(" lhsegments == rhsegments: returning 0") return 0 lhsize = len(lhsegments) @@ -410,21 +419,21 @@ def __cmp__(self, other, trace=False): if lhs == rhs: if trace: - print(f" lhs == rhs: continue") + print(" lhs == rhs: continue") continue if isinstance(lhs, str) and isinstance(rhs, int): if trace: print(f" isinstance(lhs, str): {type(lhs)!r}") print(f" isinstance(rhs, int): {type(rhs)!r}") - print(f" return -1") + print(" return -1") return -1 if isinstance(lhs, int) and isinstance(rhs, str): if trace: print(f" isinstance(lhs, int): {type(lhs)!r}") print(f" isinstance(rhs, str): {type(rhs)!r}") - print(f" return 1") + print(" return 1") return 1 result = (lhs > rhs) - (lhs < rhs) @@ -435,7 +444,7 @@ def __cmp__(self, other, trace=False): return result if trace: - print(f" all options evaluated: return 0") + print(" all options evaluated: return 0") return 0 @@ -443,7 +452,7 @@ def __cmp__(self, other, trace=False): GemConstraint.to_string = lambda gc: f"{gc.op} {gc.version}" -def sort_constraints(constraints): +def sort_constraints(constraints: Iterable[GemVersion]) -> list[GemConstraint]: """ Return a sorted sequence of unique GemConstraints. """ @@ -456,7 +465,7 @@ def sort_constraints(constraints): return consts -def tilde_comparator(version, requirement, trace=False): +def tilde_comparator(version: GemVersion, requirement: GemVersion, trace: bool = False) -> bool: """ Return True if ``version`` GemVersion satisfies ``requirement`` GemVersion according to the Rubygems tilde semantics. @@ -499,7 +508,7 @@ class GemRequirement: # The default requirement matches any version DEFAULT_CONSTRAINT = GemConstraint(">=", GemVersion(0)) - def __init__(self, *requirements): + def __init__(self, *requirements: str): """ Initialize a GemRequirement from a sequence of ``requirements`` converted to a constraints sequence of GemConstraint. @@ -509,16 +518,16 @@ def __init__(self, *requirements): else: self.constraints = tuple([GemRequirement.parse(r) for r in requirements]) - def __str__(self): + def __str__(self) -> str: gcs = [gc.to_string() for gc in sort_constraints(self.constraints)] return ", ".join(gcs) - def __repr__(self): + def __repr__(self) -> str: gcs = ", ".join(repr(gc.to_string()) for gc in sort_constraints(self.constraints)) return f"GemRequirement({gcs})" @classmethod - def from_string(cls, requirements): + def from_string(cls, requirements: str): """ Return a GemRequirement build from a lockfile-style ``requirements`` string. @@ -532,7 +541,7 @@ def from_string(cls, requirements): reqs = [r.strip() for r in reqs.split(",")] return cls(*reqs) - def for_lockfile(self): + def for_lockfile(self) -> str: """ Return a string representing this list of requirements suitable for use in a lockfile. @@ -546,13 +555,13 @@ def for_lockfile(self): gcs = ", ".join(gcs) return f" ({gcs})" - def dedupe(self): + def dedupe(self) -> Self: """ Return a new GemRequirement with sorted and unique constraints. """ return GemRequirement(*sort_constraints(self.constraints)) - def simplify(self): + def simplify(self) -> Self: """ Return a new simplified GemRequirement with: - sorted and unique constraints. @@ -567,7 +576,7 @@ def simplify(self): constraints.append(const) return GemRequirement(*sort_constraints(constraints)) - def __eq__(self, other): + def __eq__(self, other: Any) -> bool: if not isinstance(other, self.__class__): return False @@ -589,7 +598,7 @@ def __eq__(self, other): return True return False - def exact(self): + def exact(self) -> bool: """ Return True if the requirement is for only an exact version. @@ -604,7 +613,7 @@ def exact(self): return len(self.constraints) == 1 and self.constraints[0].op == "=" @classmethod - def create(cls, reqs): + def create(cls, reqs: str | Iterable[str]) -> Self: """ Return a GemRequirement built from a single requirement string or a list of requirement strings. @@ -615,7 +624,7 @@ def create(cls, reqs): return cls(reqs) @classmethod - def parse(cls, requirement): + def parse(cls, requirement: str) -> GemConstraint: """ Return a GemConstraint tuple of (operator string, GemVersion object) parsed from a single ``requirement`` string such as "> 3.0". Also @@ -641,7 +650,7 @@ def parse(cls, requirement): op = match.group(1) if match.group(1) else "=" return GemConstraint(op, GemVersion(match.group(2))) - def satisfied_by(self, version, trace=False): + def satisfied_by(self, version: str | GemVersion, trace: bool = False) -> bool: """ Return True if the ``version`` GemVersion or version string or int satisfies all the constraints of this requirement. Raise an @@ -674,7 +683,7 @@ def satisfied_by(self, version, trace=False): return True - def tilde_requirements(self): + def tilde_requirements(self) -> list[GemConstraint]: """ Return a sorted sequence of all pessimistic "~>" GemConstraint. """ @@ -682,7 +691,7 @@ def tilde_requirements(self): return [gc for gc in constraints if gc.op == "~>"] -def get_tilde_constraints(constraint): +def get_tilde_constraints(constraint: GemConstraint) -> tuple[GemConstraint, GemConstraint]: """ Return a tuple of two GemConstraint representing the lower and upper bound of a version range ``string`` that uses a tilde "~>" pessimistic operator. From cdb539957b8b8576471d26d33dca672fe4f5caf9 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Thu, 3 Jul 2025 17:06:35 +0200 Subject: [PATCH 12/26] add type hints for univers.gentoo Signed-off-by: jaimergp --- src/univers/gentoo.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/univers/gentoo.py b/src/univers/gentoo.py index 543f8548..a92a6dab 100644 --- a/src/univers/gentoo.py +++ b/src/univers/gentoo.py @@ -25,12 +25,12 @@ """ -def is_valid(string): +def is_valid(string: str) -> re.Match | None: version, _ = parse_version_and_revision(remove_spaces(string)) return _is_gentoo_version(version) -def parse_version_and_revision(version_string): +def parse_version_and_revision(version_string: str) -> tuple[str, int]: """ Return a tuple of (version string, revision int) given a ``version_string``. """ @@ -44,7 +44,7 @@ def parse_version_and_revision(version_string): return version, revision -def vercmp(ver1, ver2): +def vercmp(ver1: str , ver2: str ) -> int: """ Compare two versions ``ver1`` and ``ver2`` and return 0, 1, or -1 according to the Python 2 cmp() semantics: From d252325c7d9253b3bc47e4bdb2291ac06e113dae Mon Sep 17 00:00:00 2001 From: jaimergp Date: Thu, 3 Jul 2025 17:31:54 +0200 Subject: [PATCH 13/26] add type hints for univers.maven Signed-off-by: jaimergp --- src/univers/maven.py | 116 +++++++++++++++++++++++++------------------ 1 file changed, 67 insertions(+), 49 deletions(-) diff --git a/src/univers/maven.py b/src/univers/maven.py index 368437a4..e8f42577 100644 --- a/src/univers/maven.py +++ b/src/univers/maven.py @@ -5,12 +5,22 @@ # and significantly modified from the original at pymaven # # Visit https://aboutcode.org and https://github.com/aboutcode-org/univers for support and download. +from __future__ import annotations import functools from itertools import zip_longest +from typing import TYPE_CHECKING from univers.utils import cmp +if TYPE_CHECKING: + from collections.abc import Iterable + from typing import Any, Final + try: + from typing import Self + except ImportError: + from typing_extensions import Self + # TODO: Use common exceptions with other modules class VersionRangeParseError(ValueError): @@ -21,23 +31,23 @@ class RestrictionParseError(ValueError): pass -EXCLUSIVE_CLOSE = ")" -EXCLUSIVE_OPEN = "(" -INCLUSIVE_CLOSE = "]" -INCLUSIVE_OPEN = "[" +EXCLUSIVE_CLOSE: Final[str] = ")" +EXCLUSIVE_OPEN: Final[str] = "(" +INCLUSIVE_CLOSE: Final[str] = "]" +INCLUSIVE_OPEN: Final[str] = "[" # Known qualifiers, oldest to newest -QUALIFIERS = ["alpha", "beta", "milestone", "rc", "snapshot", "", "sp"] +QUALIFIERS: Final[list[str]] = ["alpha", "beta", "milestone", "rc", "snapshot", "", "sp"] # Well defined aliases -ALIASES = { +ALIASES: Final[dict[str, str]] = { "ga": "", "final": "", "cr": "rc", } -def list2tuple(l): +def list2tuple(l: Iterable[Any]) -> tuple[Any, ...]: return tuple(list2tuple(x) if isinstance(x, list) else x for x in l) @@ -45,7 +55,7 @@ def list2tuple(l): class Restriction(object): """Describes a restriction in versioning""" - def __init__(self, spec=None): + def __init__(self, spec: str | None = None): """Create a restriction Restrictions are specified using a semi-mathematical notation: @@ -93,7 +103,7 @@ def __init__(self, spec=None): self.lower_bound = version self.upper_bound = version - def __contains__(self, version): + def __contains__(self, version: Version) -> bool: """Return true if version is contained within the restriction version must be greater than the lower bound (or equal to it if the @@ -116,7 +126,7 @@ def __contains__(self, version): return True - def __cmp__(self, other): + def __cmp__(self, other: Any) -> int: if self is other: return 0 @@ -134,10 +144,10 @@ def __cmp__(self, other): result = cmp(self.upper_bound_inclusive, other.upper_bound_inclusive) return result - def __eq__(self, other): + def __eq__(self, other: Any) -> bool: return self.__cmp__(other) == 0 - def __hash__(self): + def __hash__(self) -> int: return hash( ( self.lower_bound, @@ -147,13 +157,13 @@ def __hash__(self): ) ) - def __lt__(self, other): + def __lt__(self, other: Any) -> bool: return self.__cmp__(other) < 0 - def __ne__(self, other): + def __ne__(self, other: Any) -> bool: return self.__cmp__(other) != 0 - def __str__(self): + def __str__(self) -> str: s = "{open}{lower}{comma}{upper}{close}".format( open=(INCLUSIVE_OPEN if self.lower_bound_inclusive else EXCLUSIVE_OPEN), lower=self.lower_bound if self.lower_bound is not None else "", @@ -167,7 +177,7 @@ def __str__(self): ) return s - def __repr__(self): + def __repr__(self) -> str: return "<%s.%s(%r, %r, %r, %r)>" % ( self.__module__, "Restriction", @@ -178,7 +188,7 @@ def __repr__(self): ) @classmethod - def fromstring(cls, spec): + def fromstring(cls, spec: str) -> Self: return cls(spec) @@ -189,7 +199,7 @@ class VersionRange(object): Valid ranges are comma separated range specifications """ - def __init__(self, spec): + def __init__(self, spec: str): """Create a VersionRange from a string specification :param spec string representation of a version or version range @@ -245,7 +255,7 @@ def __init__(self, spec): self.version = version self.restrictions = tuple(restrictions) - def __cmp__(self, other): + def __cmp__(self, other: Any) -> int: if self is other: return 0 @@ -262,28 +272,28 @@ def __cmp__(self, other): return result - def __contains__(self, version): + def __contains__(self, version: Version) -> bool: return any((version in r) for r in self.restrictions) - def __eq__(self, other): + def __eq__(self, other: Any) -> bool: return self.__cmp__(other) == 0 - def __hash__(self): + def __hash__(self) -> int: return hash((self.version, self.restrictions)) - def __lt__(self, other): + def __lt__(self, other: Any) -> bool: return self.__cmp__(other) < 0 - def __ne__(self, other): + def __ne__(self, other: Any) -> bool: return self.__cmp__(other) != 0 - def __str__(self): + def __str__(self) -> str: if self.version: return str(self.version) else: return ",".join(str(r) for r in self.restrictions) - def __repr__(self): + def __repr__(self) -> str: return "<%s.%s(%r, %r)>" % ( self.__module__, "VersionRange", @@ -291,7 +301,9 @@ def __repr__(self): self.restrictions, ) - def _intersection(self, l1, l2): + def _intersection( + self, l1: Iterable[Restriction], l2: Iterable[Restriction] + ) -> Iterable[Restriction]: """Return the intersection of l1 and l2 :param l1 list of restrictions @@ -304,14 +316,14 @@ def _intersection(self, l1, l2): raise NotImplementedError @classmethod - def fromstring(cls, spec): + def fromstring(cls, spec: str) -> Self: return cls(spec) @classmethod - def from_version(cls, version): + def from_version(cls, version: Version) -> Self: return cls(str(version)) - def restrict(self, version_range): + def restrict(self, version_range: VersionRange) -> VersionRange: """Returns a new VersionRange that is a restriction of this and the specified version range. @@ -324,7 +336,7 @@ def restrict(self, version_range): """ raise NotImplementedError - def match_version(self, versions): + def match_version(self, versions: Iterable[Version]) -> Version | None: matched = None for version in sorted(versions, reverse=True): if version in self: @@ -335,9 +347,9 @@ def match_version(self, versions): @functools.total_ordering class Version(object): - """Maven version objecjt""" + """Maven version object""" - def __init__(self, version): + def __init__(self, version: str): """Create a maven version The version string is examined one character at a time. @@ -402,7 +414,7 @@ def __init__(self, version): self._parsed = list2tuple(self._normalize(parsed)) - def __cmp__(self, other): + def __cmp__(self, other: str | VersionRange | Self) -> int: if self is other: return 0 @@ -415,25 +427,29 @@ def __cmp__(self, other): return self._compare(self._parsed, other._parsed) - def __eq__(self, other): + def __eq__(self, other: str | VersionRange | Self) -> bool: return self.__cmp__(other) == 0 - def __hash__(self): + def __hash__(self) -> int: return hash(self._unparsed) - def __lt__(self, other): + def __lt__(self, other: str | VersionRange | Self) -> bool: return self.__cmp__(other) < 0 - def __ne__(self, other): + def __ne__(self, other: str | VersionRange | Self) -> bool: return self.__cmp__(other) != 0 - def __repr__(self): + def __repr__(self) -> str: return "<%s.%s(%r)>" % (self.__module__, "Version", self._unparsed) - def __str__(self): + def __str__(self) -> str: return self._unparsed - def _compare(self, this, other): + def _compare( + self, + this: int | str | list[int | str] | tuple[int | str, ...], + other: int | str | list[int | str] | tuple[int | str, ...], + ) -> int: if isinstance(this, int): return self._int_compare(this, other) elif isinstance(this, str): @@ -443,7 +459,7 @@ def _compare(self, this, other): else: raise RuntimeError("Unknown type for t: %r" % this) - def _int_compare(self, this, other): + def _int_compare(self, this: int, other: int | str | list[Any] | tuple[Any, ...] | None) -> int: if isinstance(other, int): return this - other elif isinstance(other, (str, list, tuple)): @@ -453,7 +469,9 @@ def _int_compare(self, this, other): else: raise RuntimeError("other is of invalid type: %s" % type(other)) - def _list_compare(self, l, other): + def _list_compare( + self, l: Iterable[Any], other: int | str | None | list[None | int | str] + ) -> int: if other is None: if len(l) == 0: return 0 @@ -478,7 +496,7 @@ def _list_compare(self, l, other): else: raise RuntimeError("other is of invalid type: %s" % type(other)) - def _new_list(self, l): + def _new_list(self, l: list[Any]) -> list[Any]: """Create a new sublist, append it to the current list and return the sublist @@ -491,7 +509,7 @@ def _new_list(self, l): l.append(sublist) return sublist - def _normalize(self, l): + def _normalize(self, l: list[Any]) -> list[Any]: for item in l[::-1]: if not item: l.pop() @@ -499,7 +517,7 @@ def _normalize(self, l): break return l - def _string_compare(self, s, other): + def _string_compare(self, s: str, other: str | int | None | list[Any] | tuple[Any, ...]) -> int: """Compare string item `s` to `other` :param str s: string item to compare @@ -523,7 +541,7 @@ def _string_compare(self, s, other): else: raise RuntimeError("other is of invalid type: %s" % type(other)) - def _parse_buffer(self, buf, followed_by_digit=False): + def _parse_buffer(self, buf: str, followed_by_digit: bool = False) -> str: """Parse the string buf to determine if it is string or an int :param str buf: string to parse @@ -543,7 +561,7 @@ def _parse_buffer(self, buf, followed_by_digit=False): return ALIASES.get(buf, buf) - def _string_value(self, s): + def _string_value(self, s: str) -> str: """Convert a string into a comparable value. If the string is a known qualifier, or an alias of a known qualifier, @@ -560,5 +578,5 @@ def _string_value(self, s): return "%d-%s" % (len(QUALIFIERS), s) @classmethod - def fromstring(cls, spec): + def fromstring(cls, spec: str) -> Self: return cls(spec) From 4eaf01cbbc4e12f9db5e40129a0586523237eb2a Mon Sep 17 00:00:00 2001 From: jaimergp Date: Thu, 3 Jul 2025 17:38:08 +0200 Subject: [PATCH 14/26] add type hints for univers.nuget Signed-off-by: jaimergp --- src/univers/nuget.py | 62 +++++++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/src/univers/nuget.py b/src/univers/nuget.py index a5cd8678..bcde654d 100644 --- a/src/univers/nuget.py +++ b/src/univers/nuget.py @@ -8,14 +8,23 @@ import functools import re +from typing import TYPE_CHECKING import semver -_PAD_WIDTH = 8 -_FAKE_PRE_WIDTH = 16 +if TYPE_CHECKING: + from typing import Any, Final, SupportsInt + try: + from typing import Self + except ImportError: + from typing_extensions import Self -def _strip_leading_v(version): +_PAD_WIDTH: Final[int] = 8 +_FAKE_PRE_WIDTH: Final[int] = 16 + + +def _strip_leading_v(version: str) -> str: """Strip leading v from the version, if any.""" # Versions starting with "v" aren't valid SemVer, but we handle them just in # case. @@ -25,7 +34,7 @@ def _strip_leading_v(version): return version -def _remove_leading_zero(component): +def _remove_leading_zero(component: str) -> str: """Remove leading zeros from a component.""" if component[0] == ".": return "." + str(int(component[1:])) @@ -33,7 +42,7 @@ def _remove_leading_zero(component): return str(int(component)) -def coerce(version): +def coerce(version: str) -> str: """Coerce a potentially invalid semver into valid semver.""" version = _strip_leading_v(version) version_pattern = re.compile(r"^(\d+)(\.\d+)?(\.\d+)?(.*)$") @@ -49,17 +58,17 @@ def coerce(version): ) -def is_valid(version): +def is_valid(version: str) -> bool: """Returns whether or not the version is a valid semver.""" return semver.VersionInfo.isvalid(_strip_leading_v(version)) -def parse(version): +def parse(version: str) -> semver.VersionInfo: """Parse a SemVer.""" return semver.VersionInfo.parse(coerce(version)) -def normalize(version): +def normalize(version: str) -> str: """Normalize semver version for indexing (to allow for lexical sorting/filtering).""" version = parse(version) @@ -122,7 +131,7 @@ def normalize(version): return f"{core_parts}-{pre}" -def _extract_revision(str_version): +def _extract_revision(str_version: str) -> tuple[str, int]: """ Extract revision from ``str_version`` and return a tuple of: (dotted version string without revision, revision integer). @@ -149,7 +158,7 @@ class InvalidNuGetVersion(Exception): class Version: """NuGet version.""" - def __init__(self, base_semver, revision=0): + def __init__(self, base_semver: semver.VersionInfo, revision: int = 0): self._base_semver = base_semver if self._base_semver.prerelease: # TODO: why lowercasing this and not the build and why here and now? @@ -157,14 +166,14 @@ def __init__(self, base_semver, revision=0): self._revision = revision or 0 self._original_version = None - def __eq__(self, other): + def __eq__(self, other: Any) -> bool: return ( isinstance(other, Version) and self._base_semver == other._base_semver and self._revision == other._revision ) - def __lt__(self, other): + def __lt__(self, other: Any) -> bool: if not isinstance(other, Version): return NotImplemented if self._base_semver.replace(prerelease="") == other._base_semver.replace(prerelease=""): @@ -175,7 +184,7 @@ def __lt__(self, other): # Revision is the same, so ignore it for comparison purposes. return self._base_semver < other._base_semver - def __hash__(self): + def __hash__(self) -> int: return hash( ( self._base_semver.to_tuple(), @@ -184,7 +193,7 @@ def __hash__(self): ) @classmethod - def from_string(cls, str_version): + def from_string(cls, str_version: str) -> Self: if not str_version: return @@ -202,10 +211,15 @@ def from_string(cls, str_version): vers._original_version = original return vers - def __repr__(self): + def __repr__(self) -> str: return f"Version<{self.to_string()}>" - def to_string(self, with_empty_revision=False, include_prerelease=True, include_build=True): + def to_string( + self, + with_empty_revision: bool = False, + include_prerelease: bool = True, + include_build: bool = True, + ) -> str: major = self.major or "0" minor = self.minor or "0" patch = self.patch or "0" @@ -226,13 +240,13 @@ def to_string(self, with_empty_revision=False, include_prerelease=True, include_ return f"{major}.{minor}.{patch}{revision}{prerelease}{build}" - def __str__(self): + def __str__(self) -> str: return self.to_string( with_empty_revision=False, include_prerelease=True, include_build=True ) @property - def base_version(self): + def base_version(self) -> str: """ Return the base version dotted string composed of the four numerical segments including revision and ignoring prerelease and build. @@ -242,25 +256,25 @@ def base_version(self): ) @property - def major(self): + def major(self) -> SupportsInt: return self._base_semver.major @property - def minor(self): + def minor(self) -> SupportsInt: return self._base_semver.minor @property - def patch(self): + def patch(self) -> SupportsInt: return self._base_semver.patch @property - def revision(self): + def revision(self) -> SupportsInt: return self._revision @property - def prerelease(self): + def prerelease(self) -> str: return self._base_semver.prerelease and self._base_semver.prerelease or "" @property - def build(self): + def build(self) -> str: return self._base_semver.build and self._base_semver.build or "" From 1edacea3ee10e484e2c7a061f8d7886f739ad3d9 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Thu, 3 Jul 2025 17:40:41 +0200 Subject: [PATCH 15/26] add type hints for univers.rpm Signed-off-by: jaimergp --- src/univers/rpm.py | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/univers/rpm.py b/src/univers/rpm.py index f597aca8..c4684e8a 100644 --- a/src/univers/rpm.py +++ b/src/univers/rpm.py @@ -11,7 +11,14 @@ import re from typing import NamedTuple -from typing import Union +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any, Final, SupportsInt + try: + from typing import Self + except ImportError: + from typing_extensions import Self class RpmVersion(NamedTuple): @@ -23,10 +30,10 @@ class RpmVersion(NamedTuple): version: str release: str - def __str__(self, *args, **kwargs): + def __str__(self, *args, **kwargs) -> str: return self.to_string() - def to_string(self): + def to_string(self) -> str: if self.release: vr = f"{self.version}-{self.release}" else: @@ -37,28 +44,28 @@ def to_string(self): return vr @classmethod - def from_string(cls, s): + def from_string(cls, s: str) -> Self: s.strip() e, v, r = from_evr(s) return cls(e, v, r) - def __lt__(self, other): + def __lt__(self, other: Self | str) -> bool: return compare_rpm_versions(self, other) < 0 - def __gt__(self, other): + def __gt__(self, other: Self | str) -> bool: return compare_rpm_versions(self, other) > 0 - def __eq__(self, other): + def __eq__(self, other: Self | str) -> bool: return compare_rpm_versions(self, other) == 0 - def __le__(self, other): + def __le__(self, other: Self | str) -> bool: return compare_rpm_versions(self, other) <= 0 - def __ge__(self, other): + def __ge__(self, other: Self | str) -> bool: return compare_rpm_versions(self, other) >= 0 -def from_evr(s): +def from_evr(s: str) -> tuple[int, str, str]: """ Return an (E, V, R) tuple given a string by splitting [e:]version-release into the three possible subcomponents. @@ -83,7 +90,7 @@ def from_evr(s): return e, v, r -def compare_rpm_versions(a: Union[RpmVersion, str], b: Union[RpmVersion, str]) -> int: +def compare_rpm_versions(a: RpmVersion | str, b: RpmVersion | str) -> int: """ Compare two RPM versions ``a`` and ``b`` and return: - 1 if the version of a is newer than b @@ -136,7 +143,7 @@ class Vercmp: R_ALPHA = re.compile(rb"^([a-zA-Z]+)(.*)$") @classmethod - def compare(cls, first, second): + def compare(cls, first: str, second: str) -> int: # Rpm versions can only be ascii, anything else is just ignored first = first.encode("ascii", "ignore") second = second.encode("ascii", "ignore") @@ -236,5 +243,5 @@ def compare(cls, first, second): return -1 -def vercmp(first, second): +def vercmp(first: str, second: str) -> int: return Vercmp.compare(first, second) From 58dadc89eea2f9cf0cdc14f17bed9c271c493f96 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Thu, 3 Jul 2025 17:41:45 +0200 Subject: [PATCH 16/26] add type hints for univers.univers_semver Signed-off-by: jaimergp --- src/univers/univers_semver.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/univers/univers_semver.py b/src/univers/univers_semver.py index d617b4aa..54a50201 100644 --- a/src/univers/univers_semver.py +++ b/src/univers/univers_semver.py @@ -13,7 +13,7 @@ """ -def get_caret_constraints(string): +def get_caret_constraints(string: str) -> tuple[VersionConstraint, VersionConstraint]: """ Return a tuple of two VersionConstraint of ``SemverVersion`` representing the lower and upper bound of version constraint ``string`` that contains a @@ -41,7 +41,9 @@ def get_caret_constraints(string): ) -def get_tilde_constraints(string, operator="~"): +def get_tilde_constraints( + string: str, operator: str = "~" +) -> tuple[VersionConstraint, VersionConstraint]: """ Return a tuple of two VersionConstraint of ``SemverVersion`` representing the lower and upper bound of a version range ``string`` that contains a @@ -70,7 +72,7 @@ def get_tilde_constraints(string, operator="~"): # FIXME: this is unlikely correct https://github.com/npm/node-semver/issues/112 -def get_pessimistic_constraints(string): +def get_pessimistic_constraints(string: str) -> tuple[VersionConstraint, VersionConstraint]: """ Return a tuple of two VersionConstraint of ``SemverVersion`` representing the lower and upper bound of version range ``string`` that contains a From 8167e08718ecf0d9aac20fe20fe73968221100f4 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Thu, 3 Jul 2025 17:56:28 +0200 Subject: [PATCH 17/26] add type hints for univers.version_range Signed-off-by: jaimergp --- src/univers/version_range.py | 171 ++++++++++++++++++----------------- 1 file changed, 90 insertions(+), 81 deletions(-) diff --git a/src/univers/version_range.py b/src/univers/version_range.py index 4ad784cd..6951541c 100644 --- a/src/univers/version_range.py +++ b/src/univers/version_range.py @@ -3,12 +3,9 @@ # SPDX-License-Identifier: Apache-2.0 # # Visit https://aboutcode.org and https://github.com/aboutcode-org/univers for support and download. +from __future__ import annotations -from collections.abc import Iterable -from typing import Any -from typing import Dict -from typing import List -from typing import Union +from typing import TYPE_CHECKING import attr import semantic_version @@ -27,6 +24,14 @@ from univers.versions import AllVersion from univers.versions import NoneVersion +if TYPE_CHECKING: + from collections.abc import Iterable + from typing import Any, Final, TypeAlias + try: + from typing import Self + except ImportError: + from typing_extensions import Self + class InvalidVersionRange(Exception): """ @@ -34,7 +39,7 @@ class InvalidVersionRange(Exception): """ -INVERTED_COMPARATORS = { +INVERTED_COMPARATORS: Final[dict[str, str]] = { ">=": "<", "<=": ">", "!=": "=", @@ -76,7 +81,7 @@ def __attrs_post_init__(self, *args, **kwargs) -> None: object.__setattr__(self, "constraints", constraints) @classmethod - def from_native(cls, string: str): + def from_native(cls, string: str) -> Self: """ Return a VersionRange built from a scheme-specific, native version range ``string``. Subclasses can implement. @@ -84,14 +89,14 @@ def from_native(cls, string: str): raise NotImplementedError @classmethod - def from_natives(cls, strings: Iterable[str]): + def from_natives(cls, strings: str | Iterable[str]) -> Self: """ Return a VersionRange built from a ``strings`` list of scheme- specific native version range strings. Subclasses can implement. """ raise NotImplementedError - def to_native(self, *args, **kwargs): + def to_native(self, *args, **kwargs) -> Any: """ Return a native range string for this VersionRange. Subclasses can implement. Optional ``args`` and ``kwargs`` allow subclass to require @@ -103,7 +108,7 @@ def to_native(self, *args, **kwargs): @classmethod def from_string( cls, vers: str, simplify: bool = False, validate: bool = False - ) -> "RangeClassType": + ) -> RangeClassType: """ Return a VersionRange built from a ``vers`` version range spec string, such as "vers:npm/1.2.3,>=2.0.0" @@ -175,7 +180,7 @@ def from_string( return range_class(parsed_constraints) @classmethod - def from_versions(cls, sequence: Iterable[str]) -> "VersionRange": + def from_versions(cls, sequence: Iterable[str]) -> Self: """ Return a VersionRange built from a list of version strings, such as ["3.0.0", "1.0.1b", "3.0.2", "0.9.7a", "1.1.1ka"] @@ -193,7 +198,7 @@ def from_versions(cls, sequence: Iterable[str]) -> "VersionRange": def is_star(self) -> bool: return len(self.constraints) == 1 and self.constraints[0].is_star() - def invert(self) -> "VersionRange": + def invert(self) -> Self: """ Return the inverse or complement of this VersionRange. For example, if this range is ">=1.0.0", the inverse is "<1.0.0". @@ -217,7 +222,7 @@ def __str__(self) -> str: to_string = __str__ - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: constraints = [c.to_dict() for c in self.constraints] return dict(scheme=self.scheme, constraints=constraints) @@ -242,14 +247,14 @@ def __contains__(self, version: versions.Version) -> bool: contains = __contains__ - def __eq__(self, other: "VersionRange") -> bool: + def __eq__(self, other: Self) -> bool: return ( self.scheme == other.scheme and self.version_class == other.version_class and self.constraints == other.constraints ) - def normalize(self, known_versions: List[str]) -> "VersionRange": + def normalize(self, known_versions: list[str]) -> Self: """ Return a new VersionRange normalized and simplified using the universe of ``known_versions`` list of version strings. @@ -281,14 +286,14 @@ def normalize(self, known_versions: List[str]) -> "VersionRange": return self.__class__(constraints=version_constraints) -def from_cve_v4(data: Dict[str, Any], scheme: str) -> None: +def from_cve_v4(data: dict[str, Any], scheme: str) -> None: """ Return a VersionRange build from the provided CVE V4 API ``data`` using the provided versioning vers ``scheme``. """ -def from_cve_v5(data: Dict[str, Any], scheme: str) -> None: +def from_cve_v5(data: dict[str, Any], scheme: str) -> None: """ Return a VersionRange build from the provided CVE V5 API ``data`` using the provided versioning vers ``scheme``. @@ -301,14 +306,14 @@ def from_cve_v5(data: Dict[str, Any], scheme: str) -> None: """ -def from_osv_v1(data: Dict[str, Any], scheme: str) -> None: +def from_osv_v1(data: dict[str, Any], scheme: str) -> None: """ Return a VersionRange build from the provided CVE V4 API data using the provided versioning vers ``scheme``. """ -def get_allof_constraints(cls: VersionRange, clause: AllOf) -> List[VersionConstraint]: +def get_allof_constraints(cls: VersionRange, clause: AllOf) -> list[VersionConstraint]: """ Return a list of VersionConstraint given an AllOf ``clause``. """ @@ -325,7 +330,7 @@ def get_allof_constraints(cls: VersionRange, clause: AllOf) -> List[VersionConst def get_npm_version_constraints_from_semver_npm_spec( string: str, cls: VersionRange -) -> List[VersionConstraint]: +) -> list[VersionConstraint]: """ Return a VersionConstraint for the provided ``string``. """ @@ -348,7 +353,7 @@ class NpmVersionRange(VersionRange): scheme = "npm" version_class = versions.SemverVersion - vers_by_native_comparators = { + vers_by_native_comparators: Final[dict[str, str]] = { "==": "=", "<=": "<=", ">=": ">=", @@ -358,7 +363,7 @@ class NpmVersionRange(VersionRange): } @classmethod - def from_native(cls, string): + def from_native(cls, string: str) -> Self: """ Return a VersionRange built from an npm "node-semver" range ``string``. """ @@ -449,11 +454,11 @@ def from_native(cls, string): class ConanVersionRange(VersionRange): - scheme = "conan" + scheme: str = "conan" version_class = versions.ConanVersion @classmethod - def from_native(cls, string): + def from_native(cls, string: str) -> Self: """ Return a VersionRange built from a conan range ``string``. """ @@ -483,10 +488,10 @@ class GemVersionRange(VersionRange): See https://github.com/npm/node-semver/issues/112 """ - scheme = "gem" + scheme: str = "gem" version_class = versions.RubygemsVersion - vers_by_native_comparators = { + vers_by_native_comparators: dict[str, str] = { "=": "=", "!=": "!=", "<=": "<=", @@ -496,7 +501,7 @@ class GemVersionRange(VersionRange): } @classmethod - def from_native(cls, string): + def from_native(cls, string: str) -> Self: """ Return a VersionRange built from a Rubygem version range ``string``. @@ -516,7 +521,7 @@ def from_native(cls, string): return cls(constraints=constraints) -def split_req(string, comparators, default=None, strip=""): +def split_req(string: str, comparators: dict[str, str], default=None, strip="") -> tuple[str, str]: """ Return a tuple of (vers comparator, version) strings given an common version requirement``string`` such as "> 2.3" or "<= 2.3" using the ``comparators`` @@ -579,7 +584,7 @@ class DebianVersionRange(VersionRange): scheme = "deb" version_class = versions.DebianVersion - vers_by_native_comparators = { + vers_by_native_comparators: Final[dict[str, str]] = { "=": "=", "<=": "<=", ">=": ">=", @@ -591,7 +596,7 @@ class DebianVersionRange(VersionRange): } @classmethod - def split(cls, string): + def split(cls, string: str) -> tuple[str, str]: """ Return a tuple of (vers comparator, version) strings given a Debian version relationship ``string`` such as ">>2.3" or "(<< 2.3)". Raise a @@ -620,7 +625,7 @@ def split(cls, string): ) @classmethod - def build_constraint_from_string(cls, string): + def build_constraint_from_string(cls, string: str) -> VersionConstraint: """ Return a VersionConstraint built from a single Debian version relationship ``string``. @@ -637,7 +642,7 @@ def build_constraint_from_string(cls, string): return VersionConstraint(comparator=comparator, version=version) @classmethod - def from_native(cls, string): + def from_native(cls, string: str) -> Self: """ Return a VersionRange built from a ``string`` single Debian version relationship string. @@ -650,7 +655,7 @@ def from_native(cls, string): return cls(constraints=[cls.build_constraint_from_string(string)]) @classmethod - def from_natives(cls, strings): + def from_natives(cls, strings: str | Iterable[str]) -> Self: """ Return a VersionRange built from a ``strings`` list of Debian version relationships or a single relationship string. @@ -706,7 +711,7 @@ class PypiVersionRange(VersionRange): scheme = "pypi" version_class = versions.PypiVersion - vers_by_native_comparators = { + vers_by_native_comparators: Final[dict[str, str]] = { # 01.01.01 is equal 1.1.1 e.g., with version normalization "==": "=", "!=": "!=", @@ -725,7 +730,7 @@ class PypiVersionRange(VersionRange): } @classmethod - def from_native(cls, string): + def from_native(cls, string: str) -> Self: """ Return a VersionRange built from a PyPI PEP440 version specifiers ``string``. Raise a univers.versions.InvalidVersion @@ -790,7 +795,7 @@ class MavenVersionRange(VersionRange): version_class = versions.MavenVersion @classmethod - def from_native(cls, string): + def from_native(cls, string: str) -> Self: """ Return a VersionRange built from a Maven version specifier ``string``. """ @@ -839,7 +844,7 @@ def from_native(cls, string): return cls(constraints=constraints) @classmethod - def from_natives(cls, strings): + def from_natives(cls, strings: str | Iterable[str]) -> Self: if isinstance(strings, str): return cls.from_native(strings) constraints = [] @@ -863,7 +868,7 @@ class ComposerVersionRange(VersionRange): scheme = "composer" version_class = versions.ComposerVersion - vers_by_native_comparators = { + vers_by_native_comparators: Final[dict[str, str]] = { "==": "=", "<=": "<=", ">=": ">=", @@ -879,7 +884,7 @@ class RpmVersionRange(VersionRange): scheme = "rpm" version_class = versions.RpmVersion - vers_by_native_comparators = { + vers_by_native_comparators: Final[dict[str, str]] = { "=": "=", "<=": "<=", ">=": ">=", @@ -893,7 +898,7 @@ class RpmVersionRange(VersionRange): } @classmethod - def build_constraint_from_string(cls, string): + def build_constraint_from_string(cls, string: str) -> VersionConstraint: """ Return a VersionConstraint built from a single RPM version relationship ``string``. @@ -914,7 +919,7 @@ def build_constraint_from_string(cls, string): return VersionConstraint(comparator=comparator, version=version) @classmethod - def from_native(cls, string): + def from_native(cls, string: str) -> Self: """ Return a VersionRange built from a ``string`` single RPM version requirement string. @@ -927,7 +932,7 @@ def from_native(cls, string): return cls(constraints=[cls.build_constraint_from_string(string)]) @classmethod - def from_natives(cls, strings): + def from_natives(cls, strings: str | Iterable[str]) -> Self: """ Return a VersionRange built from a ``strings`` list of RPM version requirements or a single requirement string. @@ -965,7 +970,7 @@ class GolangVersionRange(VersionRange): scheme = "golang" version_class = versions.GolangVersion - vers_by_native_comparators = { + vers_by_native_comparators: Final[dict[str, str]] = { "==": "=", "<=": "<=", ">=": ">=", @@ -1075,7 +1080,7 @@ class NginxVersionRange(VersionRange): scheme = "nginx" version_class = versions.NginxVersion - vers_by_native_comparators = { + vers_by_native_comparators: Final[dict[str, str]] = { "==": "=", "<=": "<=", ">=": ">=", @@ -1084,7 +1089,7 @@ class NginxVersionRange(VersionRange): } @classmethod - def from_native(cls, string): + def from_native(cls, string: str) -> Self: """ Return a VersionRange built from an nginx range ``string``. @@ -1178,7 +1183,7 @@ class OpensslVersionRange(VersionRange): version_class = versions.OpensslVersion @classmethod - def from_native(cls, string): + def from_native(cls, string: str) -> Self: cleaned = remove_spaces(string).lower() constraints = [] for version in cleaned.split(","): @@ -1203,7 +1208,7 @@ class NoneVersionRange(VersionRange): version_class = versions.NoneVersion -def from_gitlab_native(gitlab_scheme, string): +def from_gitlab_native(gitlab_scheme: str, string: str) -> RangeClassType: purl_scheme = gitlab_scheme if gitlab_scheme not in PURL_TYPE_BY_GITLAB_SCHEME.values(): purl_scheme = PURL_TYPE_BY_GITLAB_SCHEME[gitlab_scheme] @@ -1263,7 +1268,7 @@ def from_gitlab_native(gitlab_scheme, string): return vrc(constraints=constraints) -vers_by_github_native_comparators = { +vers_by_github_native_comparators: Final[dict[str, str]] = { "=": "=", "!=": "!=", "<=": "<=", @@ -1273,7 +1278,7 @@ def from_gitlab_native(gitlab_scheme, string): } -def build_constraint_from_github_advisory_string(scheme: str, string: str): +def build_constraint_from_github_advisory_string(scheme: str, string: str) -> VersionConstraint: """ Return a VersionConstraint built from a single github-native version relationship ``string``. @@ -1294,7 +1299,9 @@ def build_constraint_from_github_advisory_string(scheme: str, string: str): return VersionConstraint(comparator=comparator, version=version) -def build_range_from_github_advisory_constraint(scheme: str, string: Union[str, List]): +def build_range_from_github_advisory_constraint( + scheme: str, string: str | Iterable[str] +) -> RangeClassType: """ Github has a special syntax for version ranges. For example: @@ -1330,7 +1337,7 @@ def build_range_from_github_advisory_constraint(scheme: str, string: Union[str, return vrc(constraints=constraints) -vers_by_snyk_native_comparators = { +vers_by_snyk_native_comparators: Final[dict[str, str]] = { "==": "=", "=": "=", "!=": "!=", @@ -1341,7 +1348,7 @@ def build_range_from_github_advisory_constraint(scheme: str, string: Union[str, } -def split_req_bracket_notation(string): +def split_req_bracket_notation(string: str) -> tuple[str, str]: """ Return a tuple of (vers comparator, version) strings given an bracket notation version requirement ``string`` such as "(2.3" or "3.9]" @@ -1369,7 +1376,9 @@ def split_req_bracket_notation(string): raise ValueError(f"Unknown comparator in version requirement: {string!r} ") -def build_range_from_snyk_advisory_string(scheme: str, string: Union[str, List]): +def build_range_from_snyk_advisory_string( + scheme: str, string: str | Iterable[str] +) -> RangeClassType: """ Return a VersionRange built from a ``string`` single or multiple snyk version relationship string. @@ -1390,7 +1399,7 @@ def build_range_from_snyk_advisory_string(scheme: str, string: Union[str, List]) >>> assert str(vr) == "vers:pypi/<=9.21" """ version_constraints = [] - vrc = RANGE_CLASS_BY_SCHEMES[scheme] + vrc: RangeClassType = RANGE_CLASS_BY_SCHEMES[scheme] if isinstance(string, str): string = [string] @@ -1422,32 +1431,32 @@ def build_range_from_snyk_advisory_string(scheme: str, string: Union[str, List]) ) return vrc(constraints=version_constraints) -RangeClassType = Union[ - NpmVersionRange, - DebianVersionRange, - PypiVersionRange, - MavenVersionRange, - NugetVersionRange, - ComposerVersionRange, - GemVersionRange, - RpmVersionRange, - GolangVersionRange, - GenericVersionRange, - ApacheVersionRange, - HexVersionRange, - CargoVersionRange, - MozillaVersionRange, - GitHubVersionRange, - EbuildVersionRange, - ArchLinuxVersionRange, - NginxVersionRange, - OpensslVersionRange, - MattermostVersionRange, - ConanVersionRange, - AllVersionRange, - NoneVersionRange, -] -RANGE_CLASS_BY_SCHEMES = { +RangeClassType: TypeAlias = ( + NpmVersionRange + | DebianVersionRange + | PypiVersionRange + | MavenVersionRange + | NugetVersionRange + | ComposerVersionRange + | GemVersionRange + | RpmVersionRange + | GolangVersionRange + | GenericVersionRange + | ApacheVersionRange + | HexVersionRange + | CargoVersionRange + | MozillaVersionRange + | GitHubVersionRange + | EbuildVersionRange + | ArchLinuxVersionRange + | NginxVersionRange + | OpensslVersionRange + | MattermostVersionRange + | ConanVersionRange + | AllVersionRange + | NoneVersionRange +) +RANGE_CLASS_BY_SCHEMES: Final[dict[str, RangeClassType]] = { "npm": NpmVersionRange, "deb": DebianVersionRange, "pypi": PypiVersionRange, @@ -1473,7 +1482,7 @@ def build_range_from_snyk_advisory_string(scheme: str, string: Union[str, List]) "none": NoneVersionRange, } -PURL_TYPE_BY_GITLAB_SCHEME = { +PURL_TYPE_BY_GITLAB_SCHEME: Final[dict[str, str]] = { "gem": "gem", "go": "golang", "maven": "maven", From 99a07997e006aa1407b6c64299cf9c638e67999b Mon Sep 17 00:00:00 2001 From: jaimergp Date: Thu, 3 Jul 2025 18:01:40 +0200 Subject: [PATCH 18/26] bump typing-extensions dev requirement Signed-off-by: jaimergp --- requirements-dev.txt | 2 +- setup.cfg | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 82a59db3..c3a864f1 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ pytest==7.2.0 pytest-forked==1.4.0 pytest-xdist==3.2.0 toml==0.10.2 -typing-extensions==3.10.0.2 +typing-extensions==4.14.0 zipp==3.6.0 black==22.3.0 typed-ast==1.4.3 diff --git a/setup.cfg b/setup.cfg index cfb24711..a40267a1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -62,7 +62,6 @@ install_requires = packaging semantic-version semver - typing-extensions >= 4.1.0 [options.packages.find] From 8d901393755de5022438e0bd062ed9c747b39b0f Mon Sep 17 00:00:00 2001 From: jaimergp Date: Thu, 3 Jul 2025 18:04:18 +0200 Subject: [PATCH 19/26] bump black too Signed-off-by: jaimergp --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index c3a864f1..54826207 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -9,6 +9,6 @@ pytest-xdist==3.2.0 toml==0.10.2 typing-extensions==4.14.0 zipp==3.6.0 -black==22.3.0 +black==25.1.0 typed-ast==1.4.3 pathspec==0.9.0 \ No newline at end of file From 99672d1914c4d28090a65e98302d246c75015fa1 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Thu, 3 Jul 2025 18:08:34 +0200 Subject: [PATCH 20/26] move to setup.cfg Signed-off-by: jaimergp --- requirements-dev.txt | 1 - setup.cfg | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 54826207..be5831af 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -9,6 +9,5 @@ pytest-xdist==3.2.0 toml==0.10.2 typing-extensions==4.14.0 zipp==3.6.0 -black==25.1.0 typed-ast==1.4.3 pathspec==0.9.0 \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index a40267a1..9bb8f307 100644 --- a/setup.cfg +++ b/setup.cfg @@ -75,7 +75,7 @@ testing = aboutcode-toolkit >= 7.0.2 pycodestyle >= 2.8.0 twine - black + black>=25.1.0 commoncode isort>=5.10.1 From 6b3d8378ff7c93cb2fbfc0e62d13c8791c477bbe Mon Sep 17 00:00:00 2001 From: jaimergp Date: Thu, 3 Jul 2025 18:10:04 +0200 Subject: [PATCH 21/26] relax? Signed-off-by: jaimergp --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 9bb8f307..a40267a1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -75,7 +75,7 @@ testing = aboutcode-toolkit >= 7.0.2 pycodestyle >= 2.8.0 twine - black>=25.1.0 + black commoncode isort>=5.10.1 From 0f03c092b57a3f47bbb4a51bc1a883a2906019a3 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Thu, 3 Jul 2025 18:12:10 +0200 Subject: [PATCH 22/26] bump packaging Signed-off-by: jaimergp --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f253417d..04cd23d4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ attrs==21.2.0 -packaging==21.0 +packaging==25.0 pyparsing==2.4.7 semantic-version==2.8.5 semver==2.13.0 From e1461256db5157ec206f1f3467fd631959bb237f Mon Sep 17 00:00:00 2001 From: jaimergp Date: Thu, 3 Jul 2025 18:14:04 +0200 Subject: [PATCH 23/26] typing-extensions 4.1.0 might be enough Signed-off-by: jaimergp --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index be5831af..c16d9a34 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ pytest==7.2.0 pytest-forked==1.4.0 pytest-xdist==3.2.0 toml==0.10.2 -typing-extensions==4.14.0 +typing-extensions==4.1.0 zipp==3.6.0 typed-ast==1.4.3 pathspec==0.9.0 \ No newline at end of file From 55cf83021747141e327a1af510820b1a2e53ebd4 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Thu, 3 Jul 2025 18:22:54 +0200 Subject: [PATCH 24/26] fix tests Signed-off-by: jaimergp --- src/univers/arch.py | 2 ++ src/univers/conan/errors.py | 2 ++ src/univers/conan/version.py | 2 ++ src/univers/conan/version_range.py | 2 ++ src/univers/debian.py | 8 +++-- src/univers/gem.py | 2 ++ src/univers/gentoo.py | 2 ++ src/univers/maven.py | 1 + src/univers/nuget.py | 2 ++ src/univers/rpm.py | 3 +- src/univers/univers_semver.py | 2 ++ src/univers/utils.py | 2 ++ src/univers/version_constraint.py | 10 +++--- src/univers/version_range.py | 53 +++++++++++++++--------------- src/univers/versions.py | 2 ++ 15 files changed, 61 insertions(+), 34 deletions(-) diff --git a/src/univers/arch.py b/src/univers/arch.py index 3eae6c9f..32789ade 100644 --- a/src/univers/arch.py +++ b/src/univers/arch.py @@ -6,6 +6,8 @@ # # Visit https://aboutcode.org and https://github.com/aboutcode-org/univers for support and download. +from __future__ import annotations + import re from itertools import zip_longest from typing import Dict diff --git a/src/univers/conan/errors.py b/src/univers/conan/errors.py index 4050c23c..86bd8b0e 100644 --- a/src/univers/conan/errors.py +++ b/src/univers/conan/errors.py @@ -14,6 +14,8 @@ see return_plugin.py """ +from __future__ import annotations + from contextlib import contextmanager from typing import TYPE_CHECKING diff --git a/src/univers/conan/version.py b/src/univers/conan/version.py index 928a9684..3dac0d43 100644 --- a/src/univers/conan/version.py +++ b/src/univers/conan/version.py @@ -4,6 +4,8 @@ # # Visit https://aboutcode.org and https://github.com/aboutcode-org/univers for support and download. +from __future__ import annotations + from functools import total_ordering from typing import TYPE_CHECKING diff --git a/src/univers/conan/version_range.py b/src/univers/conan/version_range.py index 4cba794b..23890f35 100644 --- a/src/univers/conan/version_range.py +++ b/src/univers/conan/version_range.py @@ -4,6 +4,8 @@ # # Visit https://aboutcode.org and https://github.com/aboutcode-org/univers for support and download. +from __future__ import annotations + from collections import namedtuple from univers.conan.errors import ConanException diff --git a/src/univers/debian.py b/src/univers/debian.py index fc62e2d9..a66c8a21 100644 --- a/src/univers/debian.py +++ b/src/univers/debian.py @@ -8,6 +8,8 @@ # # Visit https://aboutcode.org and https://github.com/aboutcode-org/univers for support and download. +from __future__ import annotations + import logging import operator as operator_module import re @@ -90,9 +92,9 @@ class Version(object): sorting and 'natural order sorting'. """ - epoch: int = attrib(default=0) - upstream: str | None = attrib(default=None) - revision: str = attrib(default="0") + epoch = attrib(default=0) + upstream = attrib(default=None) + revision = attrib(default="0") def __str__(self, *args, **kwargs) -> str: if self.epoch: diff --git a/src/univers/gem.py b/src/univers/gem.py index 5ce3ca08..3e41d125 100644 --- a/src/univers/gem.py +++ b/src/univers/gem.py @@ -14,6 +14,8 @@ # Originally from https://github.com/rubygems/rubygems and # https://github.com/coi-gov-pl/puppeter +from __future__ import annotations + import operator import re from collections import namedtuple diff --git a/src/univers/gentoo.py b/src/univers/gentoo.py index a92a6dab..28195b93 100644 --- a/src/univers/gentoo.py +++ b/src/univers/gentoo.py @@ -5,6 +5,8 @@ # # Visit https://aboutcode.org and https://github.com/aboutcode-org/univers for support and download. +from __future__ import annotations + import re from univers.utils import cmp diff --git a/src/univers/maven.py b/src/univers/maven.py index e8f42577..c08c3a71 100644 --- a/src/univers/maven.py +++ b/src/univers/maven.py @@ -5,6 +5,7 @@ # and significantly modified from the original at pymaven # # Visit https://aboutcode.org and https://github.com/aboutcode-org/univers for support and download. + from __future__ import annotations import functools diff --git a/src/univers/nuget.py b/src/univers/nuget.py index bcde654d..c5bd4346 100644 --- a/src/univers/nuget.py +++ b/src/univers/nuget.py @@ -6,6 +6,8 @@ # # Visit https://aboutcode.org and https://github.com/aboutcode-org/univers for support and download. +from __future__ import annotations + import functools import re from typing import TYPE_CHECKING diff --git a/src/univers/rpm.py b/src/univers/rpm.py index c4684e8a..adcc0097 100644 --- a/src/univers/rpm.py +++ b/src/univers/rpm.py @@ -9,12 +9,13 @@ # # Visit https://aboutcode.org and https://github.com/aboutcode-org/univers for support and download. +from __future__ import annotations + import re from typing import NamedTuple from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import Any, Final, SupportsInt try: from typing import Self except ImportError: diff --git a/src/univers/univers_semver.py b/src/univers/univers_semver.py index 54a50201..d3c6f053 100644 --- a/src/univers/univers_semver.py +++ b/src/univers/univers_semver.py @@ -4,6 +4,8 @@ # # Visit https://aboutcode.org and https://github.com/aboutcode-org/univers for support and download. +from __future__ import annotations + from univers.utils import remove_spaces from univers.version_constraint import VersionConstraint from univers.versions import SemverVersion diff --git a/src/univers/utils.py b/src/univers/utils.py index d4d82d88..6abcdfe3 100644 --- a/src/univers/utils.py +++ b/src/univers/utils.py @@ -4,6 +4,8 @@ # # Visit https://aboutcode.org and https://github.com/aboutcode-org/univers for support and download. +from __future__ import annotations + from typing import Any diff --git a/src/univers/version_constraint.py b/src/univers/version_constraint.py index 783c5ca0..ab261c9a 100644 --- a/src/univers/version_constraint.py +++ b/src/univers/version_constraint.py @@ -4,6 +4,8 @@ # # Visit https://aboutcode.org and https://github.com/aboutcode-org/univers for support and download. +from __future__ import annotations + import operator from collections.abc import Iterable from functools import total_ordering @@ -67,16 +69,16 @@ class VersionConstraint: """ # one of the COMPARATORS - comparator: str = attr.ib(type=str, default="=") + comparator = attr.ib(type=str, default="=") # a Version subclass instance - version: Version = attr.ib(type=Version, default=None) + version = attr.ib(type=Version, default=None) # a function for the comparator - comp_operator: Callable = attr.ib(default=None, repr=False) + comp_operator = attr.ib(default=None, repr=False) # a Version subclass - version_class: Version = attr.ib(type=Version, default=None, repr=False) + version_class = attr.ib(type=Version, default=None, repr=False) def __attrs_post_init__(self) -> None: # Notes: setattr is used because this is an immutable frozen instance. diff --git a/src/univers/version_range.py b/src/univers/version_range.py index 6951541c..a3aeef8b 100644 --- a/src/univers/version_range.py +++ b/src/univers/version_range.py @@ -3,9 +3,10 @@ # SPDX-License-Identifier: Apache-2.0 # # Visit https://aboutcode.org and https://github.com/aboutcode-org/univers for support and download. + from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Union import attr import semantic_version @@ -1431,31 +1432,31 @@ def build_range_from_snyk_advisory_string( ) return vrc(constraints=version_constraints) -RangeClassType: TypeAlias = ( - NpmVersionRange - | DebianVersionRange - | PypiVersionRange - | MavenVersionRange - | NugetVersionRange - | ComposerVersionRange - | GemVersionRange - | RpmVersionRange - | GolangVersionRange - | GenericVersionRange - | ApacheVersionRange - | HexVersionRange - | CargoVersionRange - | MozillaVersionRange - | GitHubVersionRange - | EbuildVersionRange - | ArchLinuxVersionRange - | NginxVersionRange - | OpensslVersionRange - | MattermostVersionRange - | ConanVersionRange - | AllVersionRange - | NoneVersionRange -) +RangeClassType: TypeAlias = Union[ + NpmVersionRange, + DebianVersionRange, + PypiVersionRange, + MavenVersionRange, + NugetVersionRange, + ComposerVersionRange, + GemVersionRange, + RpmVersionRange, + GolangVersionRange, + GenericVersionRange, + ApacheVersionRange, + HexVersionRange, + CargoVersionRange, + MozillaVersionRange, + GitHubVersionRange, + EbuildVersionRange, + ArchLinuxVersionRange, + NginxVersionRange, + OpensslVersionRange, + MattermostVersionRange, + ConanVersionRange, + AllVersionRange, + NoneVersionRange, +] RANGE_CLASS_BY_SCHEMES: Final[dict[str, RangeClassType]] = { "npm": NpmVersionRange, "deb": DebianVersionRange, diff --git a/src/univers/versions.py b/src/univers/versions.py index 60f6b7ed..51919b7a 100644 --- a/src/univers/versions.py +++ b/src/univers/versions.py @@ -4,6 +4,8 @@ # # Visit https://aboutcode.org and https://github.com/aboutcode-org/univers for support and download. +from __future__ import annotations + from typing import TYPE_CHECKING import attr From b011cc69aa55716504c6d37c6f2948648e88863a Mon Sep 17 00:00:00 2001 From: jaimergp Date: Thu, 3 Jul 2025 18:23:00 +0200 Subject: [PATCH 25/26] run black Signed-off-by: jaimergp --- src/univers/conan/errors.py | 13 +++++++------ src/univers/debian.py | 1 + src/univers/gem.py | 3 ++- src/univers/gentoo.py | 2 +- src/univers/maven.py | 1 + src/univers/nuget.py | 1 + src/univers/version_range.py | 2 ++ 7 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/univers/conan/errors.py b/src/univers/conan/errors.py index 86bd8b0e..04000986 100644 --- a/src/univers/conan/errors.py +++ b/src/univers/conan/errors.py @@ -5,13 +5,13 @@ # Visit https://aboutcode.org and https://github.com/aboutcode-org/univers for support and download. """ - Exceptions raised and handled in Conan - These exceptions are mapped between server (as an HTTP response) and client - through the REST API. When an error happens in server its translated to an HTTP - error code that its sent to client. Client reads the server code and raise the - matching exception. +Exceptions raised and handled in Conan +These exceptions are mapped between server (as an HTTP response) and client +through the REST API. When an error happens in server its translated to an HTTP +error code that its sent to client. Client reads the server code and raise the +matching exception. - see return_plugin.py +see return_plugin.py """ from __future__ import annotations @@ -22,6 +22,7 @@ if TYPE_CHECKING: from collections.abc import Iterable, Iterator from typing import Any, Callable + try: from typing import Never except ImportError: diff --git a/src/univers/debian.py b/src/univers/debian.py index a66c8a21..aeb8ce69 100644 --- a/src/univers/debian.py +++ b/src/univers/debian.py @@ -23,6 +23,7 @@ if TYPE_CHECKING: from re import Match + try: from typing import Self except ImportError: diff --git a/src/univers/gem.py b/src/univers/gem.py index 3e41d125..9f31e0a3 100644 --- a/src/univers/gem.py +++ b/src/univers/gem.py @@ -25,6 +25,7 @@ if TYPE_CHECKING: from collections.abc import Iterable from typing import Any + try: from typing import Self except ImportError: @@ -227,7 +228,7 @@ def __str__(self) -> str: def __repr__(self) -> str: return f"GemVersion({self.original!r})" - def equal_strictly(self, other: Self) -> bool: + def equal_strictly(self, other: Self) -> bool: return self.version == other.version def __hash__(self) -> int: diff --git a/src/univers/gentoo.py b/src/univers/gentoo.py index 28195b93..6d4f2a9e 100644 --- a/src/univers/gentoo.py +++ b/src/univers/gentoo.py @@ -46,7 +46,7 @@ def parse_version_and_revision(version_string: str) -> tuple[str, int]: return version, revision -def vercmp(ver1: str , ver2: str ) -> int: +def vercmp(ver1: str, ver2: str) -> int: """ Compare two versions ``ver1`` and ``ver2`` and return 0, 1, or -1 according to the Python 2 cmp() semantics: diff --git a/src/univers/maven.py b/src/univers/maven.py index c08c3a71..b6b2f23a 100644 --- a/src/univers/maven.py +++ b/src/univers/maven.py @@ -17,6 +17,7 @@ if TYPE_CHECKING: from collections.abc import Iterable from typing import Any, Final + try: from typing import Self except ImportError: diff --git a/src/univers/nuget.py b/src/univers/nuget.py index c5bd4346..3f334e7f 100644 --- a/src/univers/nuget.py +++ b/src/univers/nuget.py @@ -16,6 +16,7 @@ if TYPE_CHECKING: from typing import Any, Final, SupportsInt + try: from typing import Self except ImportError: diff --git a/src/univers/version_range.py b/src/univers/version_range.py index a3aeef8b..9fd3812d 100644 --- a/src/univers/version_range.py +++ b/src/univers/version_range.py @@ -28,6 +28,7 @@ if TYPE_CHECKING: from collections.abc import Iterable from typing import Any, Final, TypeAlias + try: from typing import Self except ImportError: @@ -1432,6 +1433,7 @@ def build_range_from_snyk_advisory_string( ) return vrc(constraints=version_constraints) + RangeClassType: TypeAlias = Union[ NpmVersionRange, DebianVersionRange, From bcb7e6de0fae2e8aed4e414d6edfbab30511afc2 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Thu, 3 Jul 2025 18:25:07 +0200 Subject: [PATCH 26/26] run isort Signed-off-by: jaimergp --- src/univers/conan/errors.py | 6 ++++-- src/univers/maven.py | 3 ++- src/univers/nuget.py | 4 +++- src/univers/rpm.py | 2 +- src/univers/version_constraint.py | 7 ++++++- src/univers/version_range.py | 7 +++++-- 6 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/univers/conan/errors.py b/src/univers/conan/errors.py index 04000986..fd3ee552 100644 --- a/src/univers/conan/errors.py +++ b/src/univers/conan/errors.py @@ -20,8 +20,10 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from collections.abc import Iterable, Iterator - from typing import Any, Callable + from collections.abc import Iterable + from collections.abc import Iterator + from typing import Any + from typing import Callable try: from typing import Never diff --git a/src/univers/maven.py b/src/univers/maven.py index b6b2f23a..1b646c64 100644 --- a/src/univers/maven.py +++ b/src/univers/maven.py @@ -16,7 +16,8 @@ if TYPE_CHECKING: from collections.abc import Iterable - from typing import Any, Final + from typing import Any + from typing import Final try: from typing import Self diff --git a/src/univers/nuget.py b/src/univers/nuget.py index 3f334e7f..4953dfc8 100644 --- a/src/univers/nuget.py +++ b/src/univers/nuget.py @@ -15,7 +15,9 @@ import semver if TYPE_CHECKING: - from typing import Any, Final, SupportsInt + from typing import Any + from typing import Final + from typing import SupportsInt try: from typing import Self diff --git a/src/univers/rpm.py b/src/univers/rpm.py index adcc0097..f34c7ab9 100644 --- a/src/univers/rpm.py +++ b/src/univers/rpm.py @@ -12,8 +12,8 @@ from __future__ import annotations import re -from typing import NamedTuple from typing import TYPE_CHECKING +from typing import NamedTuple if TYPE_CHECKING: try: diff --git a/src/univers/version_constraint.py b/src/univers/version_constraint.py index ab261c9a..d54ebd7f 100644 --- a/src/univers/version_constraint.py +++ b/src/univers/version_constraint.py @@ -9,7 +9,12 @@ import operator from collections.abc import Iterable from functools import total_ordering -from typing import Any, Callable, Dict, List, Literal, Tuple +from typing import Any +from typing import Callable +from typing import Dict +from typing import List +from typing import Literal +from typing import Tuple import attr diff --git a/src/univers/version_range.py b/src/univers/version_range.py index 9fd3812d..00670abe 100644 --- a/src/univers/version_range.py +++ b/src/univers/version_range.py @@ -6,7 +6,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Union +from typing import TYPE_CHECKING +from typing import Union import attr import semantic_version @@ -27,7 +28,9 @@ if TYPE_CHECKING: from collections.abc import Iterable - from typing import Any, Final, TypeAlias + from typing import Any + from typing import Final + from typing import TypeAlias try: from typing import Self