From ea9f023f93f28142f66761d36505d9696cf86331 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sat, 6 Feb 2021 11:14:41 -0800 Subject: [PATCH] Clean up and improve typing - Enable "warn_unused_ignores" in mypy.ini - Replace internal _typing module with stdlib - Remove TYPE_CHECKING guard - Move type comments to inline annotations --- mypy.ini | 5 +- packaging/_structures.py | 54 +++----- packaging/_typing.py | 48 ------- packaging/markers.py | 76 +++++------ packaging/requirements.py | 26 ++-- packaging/specifiers.py | 257 +++++++++++++++----------------------- packaging/tags.py | 207 ++++++++++++------------------ packaging/utils.py | 26 ++-- packaging/version.py | 217 ++++++++++++-------------------- 9 files changed, 333 insertions(+), 583 deletions(-) delete mode 100644 packaging/_typing.py diff --git a/mypy.ini b/mypy.ini index e1973485..d88ab8f1 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2,9 +2,6 @@ ignore_missing_imports = True # The following are the flags enabled by --strict -# -# Note: warn_unused_ignores is False due to incorrect typeshed annotations for -# platform.mac_ver() warn_unused_configs = True disallow_subclassing_any = True disallow_any_generics = True @@ -15,6 +12,6 @@ check_untyped_defs = True disallow_untyped_decorators = True no_implicit_optional = True warn_redundant_casts = True -warn_unused_ignores = False +warn_unused_ignores = True warn_return_any = True no_implicit_reexport = True diff --git a/packaging/_structures.py b/packaging/_structures.py index a62d600e..95154975 100644 --- a/packaging/_structures.py +++ b/packaging/_structures.py @@ -4,40 +4,31 @@ class InfinityType: - def __repr__(self): - # type: () -> str + def __repr__(self) -> str: return "Infinity" - def __hash__(self): - # type: () -> int + def __hash__(self) -> int: return hash(repr(self)) - def __lt__(self, other): - # type: (object) -> bool + def __lt__(self, other: object) -> bool: return False - def __le__(self, other): - # type: (object) -> bool + def __le__(self, other: object) -> bool: return False - def __eq__(self, other): - # type: (object) -> bool + def __eq__(self, other: object) -> bool: return isinstance(other, self.__class__) - def __ne__(self, other): - # type: (object) -> bool + def __ne__(self, other: object) -> bool: return not isinstance(other, self.__class__) - def __gt__(self, other): - # type: (object) -> bool + def __gt__(self, other: object) -> bool: return True - def __ge__(self, other): - # type: (object) -> bool + def __ge__(self, other: object) -> bool: return True - def __neg__(self): - # type: (object) -> NegativeInfinityType + def __neg__(self: object) -> "NegativeInfinityType": return NegativeInfinity @@ -45,40 +36,31 @@ def __neg__(self): class NegativeInfinityType: - def __repr__(self): - # type: () -> str + def __repr__(self) -> str: return "-Infinity" - def __hash__(self): - # type: () -> int + def __hash__(self) -> int: return hash(repr(self)) - def __lt__(self, other): - # type: (object) -> bool + def __lt__(self, other: object) -> bool: return True - def __le__(self, other): - # type: (object) -> bool + def __le__(self, other: object) -> bool: return True - def __eq__(self, other): - # type: (object) -> bool + def __eq__(self, other: object) -> bool: return isinstance(other, self.__class__) - def __ne__(self, other): - # type: (object) -> bool + def __ne__(self, other: object) -> bool: return not isinstance(other, self.__class__) - def __gt__(self, other): - # type: (object) -> bool + def __gt__(self, other: object) -> bool: return False - def __ge__(self, other): - # type: (object) -> bool + def __ge__(self, other: object) -> bool: return False - def __neg__(self): - # type: (object) -> InfinityType + def __neg__(self: object) -> InfinityType: return Infinity diff --git a/packaging/_typing.py b/packaging/_typing.py deleted file mode 100644 index 77a8b918..00000000 --- a/packaging/_typing.py +++ /dev/null @@ -1,48 +0,0 @@ -"""For neatly implementing static typing in packaging. - -`mypy` - the static type analysis tool we use - uses the `typing` module, which -provides core functionality fundamental to mypy's functioning. - -Generally, `typing` would be imported at runtime and used in that fashion - -it acts as a no-op at runtime and does not have any run-time overhead by -design. - -As it turns out, `typing` is not vendorable - it uses separate sources for -Python 2/Python 3. Thus, this codebase can not expect it to be present. -To work around this, mypy allows the typing import to be behind a False-y -optional to prevent it from running at runtime and type-comments can be used -to remove the need for the types to be accessible directly during runtime. - -This module provides the False-y guard in a nicely named fashion so that a -curious maintainer can reach here to read this. - -In packaging, all static-typing related imports should be guarded as follows: - - from packaging._typing import TYPE_CHECKING - - if TYPE_CHECKING: - from typing import ... - -Ref: https://github.com/python/mypy/issues/3216 -""" - -__all__ = ["TYPE_CHECKING", "cast"] - -# The TYPE_CHECKING constant defined by the typing module is False at runtime -# but True while type checking. -if False: # pragma: no cover - from typing import TYPE_CHECKING -else: - TYPE_CHECKING = False - -# typing's cast syntax requires calling typing.cast at runtime, but we don't -# want to import typing at runtime. Here, we inform the type checkers that -# we're importing `typing.cast` as `cast` and re-implement typing.cast's -# runtime behavior in a block that is ignored by type checkers. -if TYPE_CHECKING: # pragma: no cover - # not executed at runtime - from typing import cast -else: - # executed at runtime - def cast(type_, value): # noqa - return value diff --git a/packaging/markers.py b/packaging/markers.py index c56e004d..cb640e8f 100644 --- a/packaging/markers.py +++ b/packaging/markers.py @@ -6,6 +6,7 @@ import os import platform import sys +from typing import Any, Callable, Dict, List, Optional, Tuple, Union from pyparsing import ( # noqa: N817 Forward, @@ -19,15 +20,8 @@ stringStart, ) -from ._typing import TYPE_CHECKING from .specifiers import InvalidSpecifier, Specifier -if TYPE_CHECKING: # pragma: no cover - from typing import Any, Callable, Dict, List, Optional, Tuple, Union - - Operator = Callable[[str, str], bool] - - __all__ = [ "InvalidMarker", "UndefinedComparison", @@ -36,6 +30,8 @@ "default_environment", ] +Operator = Callable[[str, str], bool] + class InvalidMarker(ValueError): """ @@ -57,38 +53,31 @@ class UndefinedEnvironmentName(ValueError): class Node: - def __init__(self, value): - # type: (Any) -> None + def __init__(self, value: Any) -> None: self.value = value - def __str__(self): - # type: () -> str + def __str__(self) -> str: return str(self.value) - def __repr__(self): - # type: () -> str + def __repr__(self) -> str: return f"<{self.__class__.__name__}('{self}')>" - def serialize(self): - # type: () -> str + def serialize(self) -> str: raise NotImplementedError class Variable(Node): - def serialize(self): - # type: () -> str + def serialize(self) -> str: return str(self) class Value(Node): - def serialize(self): - # type: () -> str + def serialize(self) -> str: return f'"{self}"' class Op(Node): - def serialize(self): - # type: () -> str + def serialize(self) -> str: return str(self) @@ -149,16 +138,16 @@ def serialize(self): MARKER = stringStart + MARKER_EXPR + stringEnd -def _coerce_parse_result(results): - # type: (Union[ParseResults, List[Any]]) -> List[Any] +def _coerce_parse_result(results: Union[ParseResults, List[Any]]) -> List[Any]: if isinstance(results, ParseResults): return [_coerce_parse_result(i) for i in results] else: return results -def _format_marker(marker, first=True): - # type: (Union[List[str], Tuple[Node, ...], str], Optional[bool]) -> str +def _format_marker( + marker: Union[List[str], Tuple[Node, ...], str], first: Optional[bool] = True +) -> str: assert isinstance(marker, (list, tuple, str)) @@ -185,7 +174,7 @@ def _format_marker(marker, first=True): return marker -_operators = { +_operators: Dict[str, Operator] = { "in": lambda lhs, rhs: lhs in rhs, "not in": lambda lhs, rhs: lhs not in rhs, "<": operator.lt, @@ -194,11 +183,10 @@ def _format_marker(marker, first=True): "!=": operator.ne, ">=": operator.ge, ">": operator.gt, -} # type: Dict[str, Operator] +} -def _eval_op(lhs, op, rhs): - # type: (str, Op, str) -> bool +def _eval_op(lhs: str, op: Op, rhs: str) -> bool: try: spec = Specifier("".join([op.serialize(), rhs])) except InvalidSpecifier: @@ -206,7 +194,7 @@ def _eval_op(lhs, op, rhs): else: return spec.contains(lhs) - oper = _operators.get(op.serialize()) # type: Optional[Operator] + oper: Optional[Operator] = _operators.get(op.serialize()) if oper is None: raise UndefinedComparison(f"Undefined {op!r} on {lhs!r} and {rhs!r}.") @@ -220,9 +208,8 @@ class Undefined: _undefined = Undefined() -def _get_env(environment, name): - # type: (Dict[str, str], str) -> str - value = environment.get(name, _undefined) # type: Union[str, Undefined] +def _get_env(environment: Dict[str, str], name: str) -> str: + value: Union[str, Undefined] = environment.get(name, _undefined) if isinstance(value, Undefined): raise UndefinedEnvironmentName( @@ -232,9 +219,8 @@ def _get_env(environment, name): return value -def _evaluate_markers(markers, environment): - # type: (List[Any], Dict[str, str]) -> bool - groups = [[]] # type: List[List[bool]] +def _evaluate_markers(markers: List[Any], environment: Dict[str, str]) -> bool: + groups: List[List[bool]] = [[]] for marker in markers: assert isinstance(marker, (list, tuple, str)) @@ -260,8 +246,7 @@ def _evaluate_markers(markers, environment): return any(all(item) for item in groups) -def format_full_version(info): - # type: (sys._version_info) -> str +def format_full_version(info: "sys._version_info") -> str: version = "{0.major}.{0.minor}.{0.micro}".format(info) kind = info.releaselevel if kind != "final": @@ -269,8 +254,7 @@ def format_full_version(info): return version -def default_environment(): - # type: () -> Dict[str, str] +def default_environment() -> Dict[str, str]: iver = format_full_version(sys.implementation.version) implementation_name = sys.implementation.name return { @@ -289,8 +273,7 @@ def default_environment(): class Marker: - def __init__(self, marker): - # type: (str) -> None + def __init__(self, marker: str) -> None: try: self._markers = _coerce_parse_result(MARKER.parseString(marker)) except ParseException as e: @@ -299,16 +282,13 @@ def __init__(self, marker): f"{marker[e.loc : e.loc + 8]!r}" ) - def __str__(self): - # type: () -> str + def __str__(self) -> str: return _format_marker(self._markers) - def __repr__(self): - # type: () -> str + def __repr__(self) -> str: return f"" - def evaluate(self, environment=None): - # type: (Optional[Dict[str, str]]) -> bool + def evaluate(self, environment: Optional[Dict[str, str]] = None) -> bool: """Evaluate a marker. Return the boolean from evaluating the given marker against the diff --git a/packaging/requirements.py b/packaging/requirements.py index 8c6668ec..53f9a3aa 100644 --- a/packaging/requirements.py +++ b/packaging/requirements.py @@ -5,6 +5,7 @@ import re import string import urllib.parse +from typing import List, Optional as TOptional, Set from pyparsing import ( # noqa Combine, @@ -19,13 +20,9 @@ stringStart, ) -from ._typing import TYPE_CHECKING from .markers import MARKER_EXPR, Marker from .specifiers import LegacySpecifier, Specifier, SpecifierSet -if TYPE_CHECKING: # pragma: no cover - from typing import List, Optional as TOptional, Set - class InvalidRequirement(ValueError): """ @@ -100,8 +97,7 @@ class Requirement: # the thing as well as the version? What about the markers? # TODO: Can we normalize the name and extra name? - def __init__(self, requirement_string): - # type: (str) -> None + def __init__(self, requirement_string: str) -> None: try: req = REQUIREMENT.parseString(requirement_string) except ParseException as e: @@ -109,7 +105,7 @@ def __init__(self, requirement_string): f'Parse error at "{ requirement_string[e.loc : e.loc + 8]!r}": {e.msg}' ) - self.name = req.name # type: str + self.name: str = req.name if req.url: parsed_url = urllib.parse.urlparse(req.url) if parsed_url.scheme == "file": @@ -119,16 +115,15 @@ def __init__(self, requirement_string): not parsed_url.scheme and not parsed_url.netloc ): raise InvalidRequirement(f"Invalid URL: {req.url}") - self.url = req.url # type: TOptional[str] + self.url: TOptional[str] = req.url else: self.url = None - self.extras = set(req.extras.asList() if req.extras else []) # type: Set[str] - self.specifier = SpecifierSet(req.specifier) # type: SpecifierSet - self.marker = req.marker if req.marker else None # type: TOptional[Marker] + self.extras: Set[str] = set(req.extras.asList() if req.extras else []) + self.specifier: SpecifierSet = SpecifierSet(req.specifier) + self.marker: TOptional[Marker] = req.marker if req.marker else None - def __str__(self): - # type: () -> str - parts = [self.name] # type: List[str] + def __str__(self) -> str: + parts: List[str] = [self.name] if self.extras: formatted_extras = ",".join(sorted(self.extras)) @@ -147,6 +142,5 @@ def __str__(self): return "".join(parts) - def __repr__(self): - # type: () -> str + def __repr__(self) -> str: return f"" diff --git a/packaging/specifiers.py b/packaging/specifiers.py index 49500f5c..ef7ef5a9 100644 --- a/packaging/specifiers.py +++ b/packaging/specifiers.py @@ -7,27 +7,25 @@ import itertools import re import warnings +from typing import ( + Callable, + Dict, + Iterable, + Iterator, + List, + Optional, + Pattern, + Set, + Tuple, + Union, +) -from ._typing import TYPE_CHECKING from .utils import canonicalize_version from .version import LegacyVersion, Version, parse -if TYPE_CHECKING: # pragma: no cover - from typing import ( - Callable, - Dict, - Iterable, - Iterator, - List, - Optional, - Set, - Tuple, - Union, - ) - - ParsedVersion = Union[Version, LegacyVersion] - UnparsedVersion = Union[Version, LegacyVersion, str] - CallableOperator = Callable[[ParsedVersion, str], bool] +ParsedVersion = Union[Version, LegacyVersion] +UnparsedVersion = Union[Version, LegacyVersion, str] +CallableOperator = Callable[[ParsedVersion, str], bool] class InvalidSpecifier(ValueError): @@ -38,62 +36,56 @@ class InvalidSpecifier(ValueError): class BaseSpecifier(metaclass=abc.ABCMeta): @abc.abstractmethod - def __str__(self): - # type: () -> str + def __str__(self) -> str: """ Returns the str representation of this Specifier like object. This should be representative of the Specifier itself. """ @abc.abstractmethod - def __hash__(self): - # type: () -> int + def __hash__(self) -> int: """ Returns a hash value for this Specifier like object. """ @abc.abstractmethod - def __eq__(self, other): - # type: (object) -> bool + def __eq__(self, other: object) -> bool: """ Returns a boolean representing whether or not the two Specifier like objects are equal. """ @abc.abstractmethod - def __ne__(self, other): - # type: (object) -> bool + def __ne__(self, other: object) -> bool: """ Returns a boolean representing whether or not the two Specifier like objects are not equal. """ @abc.abstractproperty - def prereleases(self): - # type: () -> Optional[bool] + def prereleases(self) -> Optional[bool]: """ Returns whether or not pre-releases as a whole are allowed by this specifier. """ @prereleases.setter - def prereleases(self, value): - # type: (bool) -> None + def prereleases(self, value: bool) -> None: """ Sets whether or not pre-releases as a whole are allowed by this specifier. """ @abc.abstractmethod - def contains(self, item, prereleases=None): - # type: (str, Optional[bool]) -> bool + def contains(self, item: str, prereleases: Optional[bool] = None) -> bool: """ Determines if the given item is contained within this specifier. """ @abc.abstractmethod - def filter(self, iterable, prereleases=None): - # type: (Iterable[UnparsedVersion], Optional[bool]) -> Iterable[UnparsedVersion] + def filter( + self, iterable: Iterable[UnparsedVersion], prereleases: Optional[bool] = None + ) -> Iterable[UnparsedVersion]: """ Takes an iterable of items and filters them so that only items which are contained within this specifier are allowed in it. @@ -102,25 +94,23 @@ def filter(self, iterable, prereleases=None): class _IndividualSpecifier(BaseSpecifier): - _operators = {} # type: Dict[str, str] - _regex = None # type: re.Pattern[str] + _operators: Dict[str, str] = {} + _regex: Pattern[str] - def __init__(self, spec="", prereleases=None): - # type: (str, Optional[bool]) -> None + def __init__(self, spec: str = "", prereleases: Optional[bool] = None) -> None: match = self._regex.search(spec) if not match: raise InvalidSpecifier(f"Invalid specifier: '{spec}'") - self._spec = ( + self._spec: Tuple[str, str] = ( match.group("operator").strip(), match.group("version").strip(), - ) # type: Tuple[str, str] + ) # Store whether or not this Specifier should accept prereleases self._prereleases = prereleases - def __repr__(self): - # type: () -> str + def __repr__(self) -> str: pre = ( f", prereleases={self.prereleases!r}" if self._prereleases is not None @@ -129,21 +119,17 @@ def __repr__(self): return "<{}({!r}{})>".format(self.__class__.__name__, str(self), pre) - def __str__(self): - # type: () -> str + def __str__(self) -> str: return "{}{}".format(*self._spec) @property - def _canonical_spec(self): - # type: () -> Tuple[str, Union[Version, str]] + def _canonical_spec(self) -> Tuple[str, Union[Version, str]]: return self._spec[0], canonicalize_version(self._spec[1]) - def __hash__(self): - # type: () -> int + def __hash__(self) -> int: return hash(self._canonical_spec) - def __eq__(self, other): - # type: (object) -> bool + def __eq__(self, other: object) -> bool: if isinstance(other, str): try: other = self.__class__(str(other)) @@ -154,8 +140,7 @@ def __eq__(self, other): return self._canonical_spec == other._canonical_spec - def __ne__(self, other): - # type: (object) -> bool + def __ne__(self, other: object) -> bool: if isinstance(other, str): try: other = self.__class__(str(other)) @@ -166,45 +151,39 @@ def __ne__(self, other): return self._spec != other._spec - def _get_operator(self, op): - # type: (str) -> CallableOperator - operator_callable = getattr( + def _get_operator(self, op: str) -> CallableOperator: + operator_callable: CallableOperator = getattr( self, f"_compare_{self._operators[op]}" - ) # type: CallableOperator + ) return operator_callable - def _coerce_version(self, version): - # type: (UnparsedVersion) -> ParsedVersion + def _coerce_version(self, version: UnparsedVersion) -> ParsedVersion: if not isinstance(version, (LegacyVersion, Version)): version = parse(version) return version @property - def operator(self): - # type: () -> str + def operator(self) -> str: return self._spec[0] @property - def version(self): - # type: () -> str + def version(self) -> str: return self._spec[1] @property - def prereleases(self): - # type: () -> Optional[bool] + def prereleases(self) -> Optional[bool]: return self._prereleases @prereleases.setter - def prereleases(self, value): - # type: (bool) -> None + def prereleases(self, value: bool) -> None: self._prereleases = value - def __contains__(self, item): - # type: (str) -> bool + def __contains__(self, item: str) -> bool: return self.contains(item) - def contains(self, item, prereleases=None): - # type: (UnparsedVersion, Optional[bool]) -> bool + def contains( + self, item: UnparsedVersion, prereleases: Optional[bool] = None + ) -> bool: # Determine if prereleases are to be allowed or not. if prereleases is None: @@ -222,11 +201,12 @@ def contains(self, item, prereleases=None): # Actually do the comparison to determine if this item is contained # within this Specifier or not. - operator_callable = self._get_operator(self.operator) # type: CallableOperator + operator_callable: CallableOperator = self._get_operator(self.operator) return operator_callable(normalized_item, self.version) - def filter(self, iterable, prereleases=None): - # type: (Iterable[UnparsedVersion], Optional[bool]) -> Iterable[UnparsedVersion] + def filter( + self, iterable: Iterable[UnparsedVersion], prereleases: Optional[bool] = None + ) -> Iterable[UnparsedVersion]: yielded = False found_prereleases = [] @@ -285,8 +265,7 @@ class LegacySpecifier(_IndividualSpecifier): ">": "greater_than", } - def __init__(self, spec="", prereleases=None): - # type: (str, Optional[bool]) -> None + def __init__(self, spec: str = "", prereleases: Optional[bool] = None) -> None: super().__init__(spec, prereleases) warnings.warn( @@ -295,44 +274,37 @@ def __init__(self, spec="", prereleases=None): DeprecationWarning, ) - def _coerce_version(self, version): - # type: (Union[ParsedVersion, str]) -> LegacyVersion + def _coerce_version(self, version: Union[ParsedVersion, str]) -> LegacyVersion: if not isinstance(version, LegacyVersion): version = LegacyVersion(str(version)) return version - def _compare_equal(self, prospective, spec): - # type: (LegacyVersion, str) -> bool + def _compare_equal(self, prospective: LegacyVersion, spec: str) -> bool: return prospective == self._coerce_version(spec) - def _compare_not_equal(self, prospective, spec): - # type: (LegacyVersion, str) -> bool + def _compare_not_equal(self, prospective: LegacyVersion, spec: str) -> bool: return prospective != self._coerce_version(spec) - def _compare_less_than_equal(self, prospective, spec): - # type: (LegacyVersion, str) -> bool + def _compare_less_than_equal(self, prospective: LegacyVersion, spec: str) -> bool: return prospective <= self._coerce_version(spec) - def _compare_greater_than_equal(self, prospective, spec): - # type: (LegacyVersion, str) -> bool + def _compare_greater_than_equal( + self, prospective: LegacyVersion, spec: str + ) -> bool: return prospective >= self._coerce_version(spec) - def _compare_less_than(self, prospective, spec): - # type: (LegacyVersion, str) -> bool + def _compare_less_than(self, prospective: LegacyVersion, spec: str) -> bool: return prospective < self._coerce_version(spec) - def _compare_greater_than(self, prospective, spec): - # type: (LegacyVersion, str) -> bool + def _compare_greater_than(self, prospective: LegacyVersion, spec: str) -> bool: return prospective > self._coerce_version(spec) def _require_version_compare( - fn, # type: (Callable[[Specifier, ParsedVersion, str], bool]) -): - # type: (...) -> Callable[[Specifier, ParsedVersion, str], bool] + fn: Callable[["Specifier", ParsedVersion, str], bool] +) -> Callable[["Specifier", ParsedVersion, str], bool]: @functools.wraps(fn) - def wrapped(self, prospective, spec): - # type: (Specifier, ParsedVersion, str) -> bool + def wrapped(self: "Specifier", prospective: ParsedVersion, spec: str) -> bool: if not isinstance(prospective, Version): return False return fn(self, prospective, spec) @@ -449,8 +421,7 @@ class Specifier(_IndividualSpecifier): } @_require_version_compare - def _compare_compatible(self, prospective, spec): - # type: (ParsedVersion, str) -> bool + def _compare_compatible(self, prospective: ParsedVersion, spec: str) -> bool: # Compatible releases have an equivalent combination of >= and ==. That # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to @@ -472,8 +443,7 @@ def _compare_compatible(self, prospective, spec): ) @_require_version_compare - def _compare_equal(self, prospective, spec): - # type: (ParsedVersion, str) -> bool + def _compare_equal(self, prospective: ParsedVersion, spec: str) -> bool: # We need special logic to handle prefix matching if spec.endswith(".*"): @@ -513,13 +483,11 @@ def _compare_equal(self, prospective, spec): return prospective == spec_version @_require_version_compare - def _compare_not_equal(self, prospective, spec): - # type: (ParsedVersion, str) -> bool + def _compare_not_equal(self, prospective: ParsedVersion, spec: str) -> bool: return not self._compare_equal(prospective, spec) @_require_version_compare - def _compare_less_than_equal(self, prospective, spec): - # type: (ParsedVersion, str) -> bool + def _compare_less_than_equal(self, prospective: ParsedVersion, spec: str) -> bool: # NB: Local version identifiers are NOT permitted in the version # specifier, so local version labels can be universally removed from @@ -527,8 +495,9 @@ def _compare_less_than_equal(self, prospective, spec): return Version(prospective.public) <= Version(spec) @_require_version_compare - def _compare_greater_than_equal(self, prospective, spec): - # type: (ParsedVersion, str) -> bool + def _compare_greater_than_equal( + self, prospective: ParsedVersion, spec: str + ) -> bool: # NB: Local version identifiers are NOT permitted in the version # specifier, so local version labels can be universally removed from @@ -536,8 +505,7 @@ def _compare_greater_than_equal(self, prospective, spec): return Version(prospective.public) >= Version(spec) @_require_version_compare - def _compare_less_than(self, prospective, spec_str): - # type: (ParsedVersion, str) -> bool + def _compare_less_than(self, prospective: ParsedVersion, spec_str: str) -> bool: # Convert our spec to a Version instance, since we'll want to work with # it as a version. @@ -563,8 +531,7 @@ def _compare_less_than(self, prospective, spec_str): return True @_require_version_compare - def _compare_greater_than(self, prospective, spec_str): - # type: (ParsedVersion, str) -> bool + def _compare_greater_than(self, prospective: ParsedVersion, spec_str: str) -> bool: # Convert our spec to a Version instance, since we'll want to work with # it as a version. @@ -595,13 +562,11 @@ def _compare_greater_than(self, prospective, spec_str): # same version in the spec. return True - def _compare_arbitrary(self, prospective, spec): - # type: (Version, str) -> bool + def _compare_arbitrary(self, prospective: Version, spec: str) -> bool: return str(prospective).lower() == str(spec).lower() @property - def prereleases(self): - # type: () -> bool + def prereleases(self) -> bool: # If there is an explicit prereleases set for this, then we'll just # blindly use that. @@ -626,17 +591,15 @@ def prereleases(self): return False @prereleases.setter - def prereleases(self, value): - # type: (bool) -> None + def prereleases(self, value: bool) -> None: self._prereleases = value _prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$") -def _version_split(version): - # type: (str) -> List[str] - result = [] # type: List[str] +def _version_split(version: str) -> List[str]: + result: List[str] = [] for item in version.split("."): match = _prefix_regex.search(item) if match: @@ -646,15 +609,13 @@ def _version_split(version): return result -def _is_not_suffix(segment): - # type: (str) -> bool +def _is_not_suffix(segment: str) -> bool: return not any( segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post") ) -def _pad_version(left, right): - # type: (List[str], List[str]) -> Tuple[List[str], List[str]] +def _pad_version(left: List[str], right: List[str]) -> Tuple[List[str], List[str]]: left_split, right_split = [], [] # Get the release segment of our versions @@ -673,8 +634,9 @@ def _pad_version(left, right): class SpecifierSet(BaseSpecifier): - def __init__(self, specifiers="", prereleases=None): - # type: (str, Optional[bool]) -> None + def __init__( + self, specifiers: str = "", prereleases: Optional[bool] = None + ) -> None: # Split on , to break each individual specifier into it's own item, and # strip each item to remove leading/trailing whitespace. @@ -682,7 +644,7 @@ def __init__(self, specifiers="", prereleases=None): # Parsed each individual specifier, attempting first to make it a # Specifier and falling back to a LegacySpecifier. - parsed = set() # type: Set[_IndividualSpecifier] + parsed: Set[_IndividualSpecifier] = set() for specifier in split_specifiers: try: parsed.add(Specifier(specifier)) @@ -696,8 +658,7 @@ def __init__(self, specifiers="", prereleases=None): # we accept prereleases or not. self._prereleases = prereleases - def __repr__(self): - # type: () -> str + def __repr__(self) -> str: pre = ( f", prereleases={self.prereleases!r}" if self._prereleases is not None @@ -706,16 +667,13 @@ def __repr__(self): return "".format(str(self), pre) - def __str__(self): - # type: () -> str + def __str__(self) -> str: return ",".join(sorted(str(s) for s in self._specs)) - def __hash__(self): - # type: () -> int + def __hash__(self) -> int: return hash(self._specs) - def __and__(self, other): - # type: (Union[SpecifierSet, str]) -> SpecifierSet + def __and__(self, other: Union["SpecifierSet", str]) -> "SpecifierSet": if isinstance(other, str): other = SpecifierSet(other) elif not isinstance(other, SpecifierSet): @@ -738,8 +696,7 @@ def __and__(self, other): return specifier - def __eq__(self, other): - # type: (object) -> bool + def __eq__(self, other: object) -> bool: if isinstance(other, (str, _IndividualSpecifier)): other = SpecifierSet(str(other)) elif not isinstance(other, SpecifierSet): @@ -747,8 +704,7 @@ def __eq__(self, other): return self._specs == other._specs - def __ne__(self, other): - # type: (object) -> bool + def __ne__(self, other: object) -> bool: if isinstance(other, (str, _IndividualSpecifier)): other = SpecifierSet(str(other)) elif not isinstance(other, SpecifierSet): @@ -756,17 +712,14 @@ def __ne__(self, other): return self._specs != other._specs - def __len__(self): - # type: () -> int + def __len__(self) -> int: return len(self._specs) - def __iter__(self): - # type: () -> Iterator[_IndividualSpecifier] + def __iter__(self) -> Iterator[_IndividualSpecifier]: return iter(self._specs) @property - def prereleases(self): - # type: () -> Optional[bool] + def prereleases(self) -> Optional[bool]: # If we have been given an explicit prerelease modifier, then we'll # pass that through here. @@ -784,16 +737,15 @@ def prereleases(self): return any(s.prereleases for s in self._specs) @prereleases.setter - def prereleases(self, value): - # type: (bool) -> None + def prereleases(self, value: bool) -> None: self._prereleases = value - def __contains__(self, item): - # type: (Union[ParsedVersion, str]) -> bool + def __contains__(self, item: Union[ParsedVersion, str]) -> bool: return self.contains(item) - def contains(self, item, prereleases=None): - # type: (Union[ParsedVersion, str], Optional[bool]) -> bool + def contains( + self, item: Union[ParsedVersion, str], prereleases: Optional[bool] = None + ) -> bool: # Ensure that our item is a Version or LegacyVersion instance. if not isinstance(item, (LegacyVersion, Version)): @@ -822,10 +774,9 @@ def contains(self, item, prereleases=None): def filter( self, - iterable, # type: Iterable[Union[ParsedVersion, str]] - prereleases=None, # type: Optional[bool] - ): - # type: (...) -> Iterable[Union[ParsedVersion, str]] + iterable: Iterable[Union[ParsedVersion, str]], + prereleases: Optional[bool] = None, + ) -> Iterable[Union[ParsedVersion, str]]: # Determine if we're forcing a prerelease or not, if we're not forcing # one for this particular filter call, then we'll use whatever the @@ -844,8 +795,8 @@ def filter( # which will filter out any pre-releases, unless there are no final # releases, and which will filter out LegacyVersion in general. else: - filtered = [] # type: List[Union[ParsedVersion, str]] - found_prereleases = [] # type: List[Union[ParsedVersion, str]] + filtered: List[Union[ParsedVersion, str]] = [] + found_prereleases: List[Union[ParsedVersion, str]] = [] for item in iterable: # Ensure that we some kind of Version class for this item. diff --git a/packaging/tags.py b/packaging/tags.py index 4e74e9fa..7aea4315 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -13,37 +13,33 @@ import sysconfig import warnings from importlib.machinery import EXTENSION_SUFFIXES - -from ._typing import TYPE_CHECKING, cast - -if TYPE_CHECKING: # pragma: no cover - from typing import ( - IO, - Dict, - FrozenSet, - Iterable, - Iterator, - List, - Optional, - Sequence, - Tuple, - Union, - ) - - PythonVersion = Sequence[int] - MacVersion = Tuple[int, int] - GlibcVersion = Tuple[int, int] - +from typing import ( + IO, + Dict, + FrozenSet, + Iterable, + Iterator, + List, + Optional, + Sequence, + Tuple, + Union, + cast, +) logger = logging.getLogger(__name__) -INTERPRETER_SHORT_NAMES = { +PythonVersion = Sequence[int] +MacVersion = Tuple[int, int] +GlibcVersion = Tuple[int, int] + +INTERPRETER_SHORT_NAMES: Dict[str, str] = { "python": "py", # Generic. "cpython": "cp", "pypy": "pp", "ironpython": "ip", "jython": "jy", -} # type: Dict[str, str] +} _32_BIT_INTERPRETER = sys.maxsize <= 2 ** 32 @@ -63,7 +59,7 @@ # For now, guess what the highest minor version might be, assume it will # be 50 for testing. Once this actually happens, update the dictionary # with the actual value. -_LAST_GLIBC_MINOR = collections.defaultdict(lambda: 50) # type: Dict[int, int] +_LAST_GLIBC_MINOR: Dict[int, int] = collections.defaultdict(lambda: 50) glibcVersion = collections.namedtuple("Version", ["major", "minor"]) @@ -77,8 +73,7 @@ class Tag: __slots__ = ["_interpreter", "_abi", "_platform", "_hash"] - def __init__(self, interpreter, abi, platform): - # type: (str, str, str) -> None + def __init__(self, interpreter: str, abi: str, platform: str) -> None: self._interpreter = interpreter.lower() self._abi = abi.lower() self._platform = platform.lower() @@ -90,22 +85,18 @@ def __init__(self, interpreter, abi, platform): self._hash = hash((self._interpreter, self._abi, self._platform)) @property - def interpreter(self): - # type: () -> str + def interpreter(self) -> str: return self._interpreter @property - def abi(self): - # type: () -> str + def abi(self) -> str: return self._abi @property - def platform(self): - # type: () -> str + def platform(self) -> str: return self._platform - def __eq__(self, other): - # type: (object) -> bool + def __eq__(self, other: object) -> bool: if not isinstance(other, Tag): return NotImplemented @@ -115,21 +106,17 @@ def __eq__(self, other): and (self.interpreter == other.interpreter) ) - def __hash__(self): - # type: () -> int + def __hash__(self) -> int: return self._hash - def __str__(self): - # type: () -> str + def __str__(self) -> str: return f"{self._interpreter}-{self._abi}-{self._platform}" - def __repr__(self): - # type: () -> str + def __repr__(self) -> str: return "<{self} @ {self_id}>".format(self=self, self_id=id(self)) -def parse_tag(tag): - # type: (str) -> FrozenSet[Tag] +def parse_tag(tag: str) -> FrozenSet[Tag]: """ Parses the provided tag (e.g. `py3-none-any`) into a frozenset of Tag instances. @@ -145,8 +132,7 @@ def parse_tag(tag): return frozenset(tags) -def _warn_keyword_parameter(func_name, kwargs): - # type: (str, Dict[str, bool]) -> bool +def _warn_keyword_parameter(func_name: str, kwargs: Dict[str, bool]) -> bool: """ Backwards-compatibility with Python 2.7 to allow treating 'warn' as keyword-only. """ @@ -159,8 +145,7 @@ def _warn_keyword_parameter(func_name, kwargs): return kwargs["warn"] -def _get_config_var(name, warn=False): - # type: (str, bool) -> Union[int, str, None] +def _get_config_var(name: str, warn: bool = False) -> Union[int, str, None]: value = sysconfig.get_config_var(name) if value is None and warn: logger.debug( @@ -169,13 +154,11 @@ def _get_config_var(name, warn=False): return value -def _normalize_string(string): - # type: (str) -> str +def _normalize_string(string: str) -> str: return string.replace(".", "_").replace("-", "_") -def _abi3_applies(python_version): - # type: (PythonVersion) -> bool +def _abi3_applies(python_version: PythonVersion) -> bool: """ Determine if the Python version supports abi3. @@ -184,8 +167,7 @@ def _abi3_applies(python_version): return len(python_version) > 1 and tuple(python_version) >= (3, 2) -def _cpython_abis(py_version, warn=False): - # type: (PythonVersion, bool) -> List[str] +def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> List[str]: py_version = tuple(py_version) # To allow for version comparison. abis = [] version = _version_nodot(py_version[:2]) @@ -222,12 +204,11 @@ def _cpython_abis(py_version, warn=False): def cpython_tags( - python_version=None, # type: Optional[PythonVersion] - abis=None, # type: Optional[Iterable[str]] - platforms=None, # type: Optional[Iterable[str]] - **kwargs, # type: bool -): - # type: (...) -> Iterator[Tag] + python_version: Optional[PythonVersion] = None, + abis: Optional[Iterable[str]] = None, + platforms: Optional[Iterable[str]] = None, + **kwargs: bool, +) -> Iterator[Tag]: """ Yields the tags for a CPython interpreter. @@ -279,20 +260,18 @@ def cpython_tags( yield Tag(interpreter, "abi3", platform_) -def _generic_abi(): - # type: () -> Iterator[str] +def _generic_abi() -> Iterator[str]: abi = sysconfig.get_config_var("SOABI") if abi: yield _normalize_string(abi) def generic_tags( - interpreter=None, # type: Optional[str] - abis=None, # type: Optional[Iterable[str]] - platforms=None, # type: Optional[Iterable[str]] - **kwargs, # type: bool -): - # type: (...) -> Iterator[Tag] + interpreter: Optional[str] = None, + abis: Optional[Iterable[str]] = None, + platforms: Optional[Iterable[str]] = None, + **kwargs: bool, +) -> Iterator[Tag]: """ Yields the tags for a generic interpreter. @@ -317,8 +296,7 @@ def generic_tags( yield Tag(interpreter, abi, platform_) -def _py_interpreter_range(py_version): - # type: (PythonVersion) -> Iterator[str] +def _py_interpreter_range(py_version: PythonVersion) -> Iterator[str]: """ Yields Python versions in descending order. @@ -334,11 +312,10 @@ def _py_interpreter_range(py_version): def compatible_tags( - python_version=None, # type: Optional[PythonVersion] - interpreter=None, # type: Optional[str] - platforms=None, # type: Optional[Iterable[str]] -): - # type: (...) -> Iterator[Tag] + python_version: Optional[PythonVersion] = None, + interpreter: Optional[str] = None, + platforms: Optional[Iterable[str]] = None, +) -> Iterator[Tag]: """ Yields the sequence of tags that are compatible with a specific version of Python. @@ -359,8 +336,7 @@ def compatible_tags( yield Tag(version, "none", "any") -def _mac_arch(arch, is_32bit=_32_BIT_INTERPRETER): - # type: (str, bool) -> str +def _mac_arch(arch: str, is_32bit: bool = _32_BIT_INTERPRETER) -> str: if not is_32bit: return arch @@ -370,8 +346,7 @@ def _mac_arch(arch, is_32bit=_32_BIT_INTERPRETER): return "i386" -def _mac_binary_formats(version, cpu_arch): - # type: (MacVersion, str) -> List[str] +def _mac_binary_formats(version: MacVersion, cpu_arch: str) -> List[str]: formats = [cpu_arch] if cpu_arch == "x86_64": if version < (10, 4): @@ -403,8 +378,9 @@ def _mac_binary_formats(version, cpu_arch): return formats -def mac_platforms(version=None, arch=None): - # type: (Optional[MacVersion], Optional[str]) -> Iterator[str] +def mac_platforms( + version: Optional[MacVersion] = None, arch: Optional[str] = None +) -> Iterator[str]: """ Yields the platform tags for a macOS system. @@ -413,7 +389,7 @@ def mac_platforms(version=None, arch=None): generate platform tags for. Both parameters default to the appropriate value for the current system. """ - version_str, _, cpu_arch = platform.mac_ver() # type: ignore + version_str, _, cpu_arch = platform.mac_ver() if version is None: version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2]))) else: @@ -475,8 +451,7 @@ def mac_platforms(version=None, arch=None): # From PEP 513, PEP 600 -def _is_manylinux_compatible(name, arch, glibc_version): - # type: (str, str, GlibcVersion) -> bool +def _is_manylinux_compatible(name: str, arch: str, glibc_version: GlibcVersion) -> bool: sys_glibc = _get_glibc_version() if sys_glibc < glibc_version: return False @@ -505,14 +480,12 @@ def _is_manylinux_compatible(name, arch, glibc_version): return True -def _glibc_version_string(): - # type: () -> Optional[str] +def _glibc_version_string() -> Optional[str]: # Returns glibc version string, or None if not using glibc. return _glibc_version_string_confstr() or _glibc_version_string_ctypes() -def _glibc_version_string_confstr(): - # type: () -> Optional[str] +def _glibc_version_string_confstr() -> Optional[str]: """ Primary implementation of glibc_version_string using os.confstr. """ @@ -522,9 +495,7 @@ def _glibc_version_string_confstr(): # https://github.com/python/cpython/blob/fcf1d003bf4f0100c9d0921ff3d70e1127ca1b71/Lib/platform.py#L175-L183 try: # os.confstr("CS_GNU_LIBC_VERSION") returns a string like "glibc 2.17". - version_string = os.confstr( # type: ignore[attr-defined] # noqa: F821 - "CS_GNU_LIBC_VERSION" - ) + version_string = os.confstr("CS_GNU_LIBC_VERSION") assert version_string is not None _, version = version_string.split() # type: Tuple[str, str] except (AssertionError, AttributeError, OSError, ValueError): @@ -533,8 +504,7 @@ def _glibc_version_string_confstr(): return version -def _glibc_version_string_ctypes(): - # type: () -> Optional[str] +def _glibc_version_string_ctypes() -> Optional[str]: """ Fallback implementation of glibc_version_string using ctypes. """ @@ -557,8 +527,7 @@ def _glibc_version_string_ctypes(): # hard code here. In any case, failure to call dlopen() means we # can proceed, so we bail on our attempt. try: - # Note: typeshed is wrong here so we are ignoring this line. - process_namespace = ctypes.CDLL(None) # type: ignore + process_namespace = ctypes.CDLL(None) except OSError: return None @@ -571,7 +540,7 @@ def _glibc_version_string_ctypes(): # Call gnu_get_libc_version, which returns a string like "2.5" gnu_get_libc_version.restype = ctypes.c_char_p - version_str = gnu_get_libc_version() # type: str + version_str: str = gnu_get_libc_version() # py2 / py3 compatibility: if not isinstance(version_str, str): version_str = version_str.decode("ascii") @@ -579,8 +548,7 @@ def _glibc_version_string_ctypes(): return version_str -def _parse_glibc_version(version_str): - # type: (str) -> Tuple[int, int] +def _parse_glibc_version(version_str: str) -> Tuple[int, int]: # Parse glibc version. # # We use a regexp instead of str.split because we want to discard any @@ -598,11 +566,10 @@ def _parse_glibc_version(version_str): return (int(m.group("major")), int(m.group("minor"))) -_glibc_version = [] # type: List[Tuple[int, int]] +_glibc_version: List[Tuple[int, int]] = [] -def _get_glibc_version(): - # type: () -> Tuple[int, int] +def _get_glibc_version() -> Tuple[int, int]: if _glibc_version: return _glibc_version[0] version_str = _glibc_version_string() @@ -637,10 +604,8 @@ class _InvalidELFFileHeader(ValueError): EF_ARM_ABI_VER5 = 0x05000000 EF_ARM_ABI_FLOAT_HARD = 0x00000400 - def __init__(self, file): - # type: (IO[bytes]) -> None - def unpack(fmt): - # type: (str) -> int + def __init__(self, file: IO[bytes]) -> None: + def unpack(fmt: str) -> int: try: (result,) = struct.unpack( fmt, file.read(struct.calcsize(fmt)) @@ -681,8 +646,7 @@ def unpack(fmt): self.e_shstrndx = unpack(format_h) -def _get_elf_header(): - # type: () -> Optional[_ELFFileHeader] +def _get_elf_header() -> Optional[_ELFFileHeader]: try: with open(sys.executable, "rb") as f: elf_header = _ELFFileHeader(f) @@ -691,8 +655,7 @@ def _get_elf_header(): return elf_header -def _is_linux_armhf(): - # type: () -> bool +def _is_linux_armhf() -> bool: # hard-float ABI can be detected from the ELF header of the running # process # https://static.docs.arm.com/ihi0044/g/aaelf32.pdf @@ -711,8 +674,7 @@ def _is_linux_armhf(): return result -def _is_linux_i686(): - # type: () -> bool +def _is_linux_i686() -> bool: elf_header = _get_elf_header() if elf_header is None: return False @@ -722,8 +684,7 @@ def _is_linux_i686(): return result -def _have_compatible_manylinux_abi(arch): - # type: (str) -> bool +def _have_compatible_manylinux_abi(arch: str) -> bool: if arch == "armv7l": return _is_linux_armhf() if arch == "i686": @@ -731,8 +692,7 @@ def _have_compatible_manylinux_abi(arch): return arch in {"x86_64", "aarch64", "ppc64", "ppc64le", "s390x"} -def _manylinux_tags(linux, arch): - # type: (str, str) -> Iterator[str] +def _manylinux_tags(linux: str, arch: str) -> Iterator[str]: # Oldest glibc to be supported regardless of architecture is (2, 17). too_old_glibc2 = glibcVersion(2, 16) if arch in {"x86_64", "i686"}: @@ -766,8 +726,7 @@ def _manylinux_tags(linux, arch): yield linux.replace("linux", legacy_tag) -def _linux_platforms(is_32bit=_32_BIT_INTERPRETER): - # type: (bool) -> Iterator[str] +def _linux_platforms(is_32bit: bool = _32_BIT_INTERPRETER) -> Iterator[str]: linux = _normalize_string(distutils.util.get_platform()) if is_32bit: if linux == "linux_x86_64": @@ -780,13 +739,11 @@ def _linux_platforms(is_32bit=_32_BIT_INTERPRETER): yield linux -def _generic_platforms(): - # type: () -> Iterator[str] +def _generic_platforms() -> Iterator[str]: yield _normalize_string(distutils.util.get_platform()) -def _platform_tags(): - # type: () -> Iterator[str] +def _platform_tags() -> Iterator[str]: """ Provides the platform tags for this installation. """ @@ -798,8 +755,7 @@ def _platform_tags(): return _generic_platforms() -def interpreter_name(): - # type: () -> str +def interpreter_name() -> str: """ Returns the name of the running interpreter. """ @@ -807,8 +763,7 @@ def interpreter_name(): return INTERPRETER_SHORT_NAMES.get(name) or name -def interpreter_version(**kwargs): - # type: (bool) -> str +def interpreter_version(**kwargs: bool) -> str: """ Returns the version of the running interpreter. """ @@ -821,13 +776,11 @@ def interpreter_version(**kwargs): return version -def _version_nodot(version): - # type: (PythonVersion) -> str +def _version_nodot(version: PythonVersion) -> str: return "".join(map(str, version)) -def sys_tags(**kwargs): - # type: (bool) -> Iterator[Tag] +def sys_tags(**kwargs: bool) -> Iterator[Tag]: """ Returns the sequence of tag triples for the running interpreter. diff --git a/packaging/utils.py b/packaging/utils.py index 315c5e95..96d1ef8e 100644 --- a/packaging/utils.py +++ b/packaging/utils.py @@ -3,19 +3,13 @@ # for complete details. import re +from typing import FrozenSet, NewType, Tuple, Union, cast -from ._typing import TYPE_CHECKING, cast from .tags import Tag, parse_tag from .version import InvalidVersion, Version -if TYPE_CHECKING: # pragma: no cover - from typing import FrozenSet, NewType, Tuple, Union - - BuildTag = Union[Tuple[()], Tuple[int, str]] - NormalizedName = NewType("NormalizedName", str) -else: - BuildTag = tuple - NormalizedName = str +BuildTag = Union[Tuple[()], Tuple[int, str]] +NormalizedName = NewType("NormalizedName", str) class InvalidWheelFilename(ValueError): @@ -35,15 +29,13 @@ class InvalidSdistFilename(ValueError): _build_tag_regex = re.compile(r"(\d+)(.*)") -def canonicalize_name(name): - # type: (str) -> NormalizedName +def canonicalize_name(name: str) -> NormalizedName: # This is taken from PEP 503. value = _canonicalize_regex.sub("-", name).lower() return cast(NormalizedName, value) -def canonicalize_version(version): - # type: (Union[Version, str]) -> Union[Version, str] +def canonicalize_version(version: Union[Version, str]) -> Union[Version, str]: """ This is very similar to Version.__str__, but has one subtle difference with the way it handles the release segment. @@ -84,8 +76,9 @@ def canonicalize_version(version): return "".join(parts) -def parse_wheel_filename(filename): - # type: (str) -> Tuple[NormalizedName, Version, BuildTag, FrozenSet[Tag]] +def parse_wheel_filename( + filename: str +) -> Tuple[NormalizedName, Version, BuildTag, FrozenSet[Tag]]: if not filename.endswith(".whl"): raise InvalidWheelFilename( f"Invalid wheel filename (extension must be '.whl'): {filename}" @@ -119,8 +112,7 @@ def parse_wheel_filename(filename): return (name, version, build, tags) -def parse_sdist_filename(filename): - # type: (str) -> Tuple[NormalizedName, Version] +def parse_sdist_filename(filename: str) -> Tuple[NormalizedName, Version]: if not filename.endswith(".tar.gz"): raise InvalidSdistFilename( f"Invalid sdist filename (extension must be '.tar.gz'): {filename}" diff --git a/packaging/version.py b/packaging/version.py index 1c79eda5..de9a09a4 100644 --- a/packaging/version.py +++ b/packaging/version.py @@ -6,47 +6,40 @@ import itertools import re import warnings +from typing import Callable, Iterator, List, Optional, SupportsInt, Tuple, Union -from ._structures import Infinity, NegativeInfinity -from ._typing import TYPE_CHECKING - -if TYPE_CHECKING: # pragma: no cover - from typing import Callable, Iterator, List, Optional, SupportsInt, Tuple, Union - - from ._structures import InfinityType, NegativeInfinityType - - InfiniteTypes = Union[InfinityType, NegativeInfinityType] - PrePostDevType = Union[InfiniteTypes, Tuple[str, int]] - SubLocalType = Union[InfiniteTypes, int, str] - LocalType = Union[ - NegativeInfinityType, - Tuple[ - Union[ - SubLocalType, - Tuple[SubLocalType, str], - Tuple[NegativeInfinityType, SubLocalType], - ], - ..., - ], - ] - CmpKey = Tuple[ - int, Tuple[int, ...], PrePostDevType, PrePostDevType, PrePostDevType, LocalType - ] - LegacyCmpKey = Tuple[int, Tuple[str, ...]] - VersionComparisonMethod = Callable[ - [Union[CmpKey, LegacyCmpKey], Union[CmpKey, LegacyCmpKey]], bool - ] +from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType __all__ = ["parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"] +InfiniteTypes = Union[InfinityType, NegativeInfinityType] +PrePostDevType = Union[InfiniteTypes, Tuple[str, int]] +SubLocalType = Union[InfiniteTypes, int, str] +LocalType = Union[ + NegativeInfinityType, + Tuple[ + Union[ + SubLocalType, + Tuple[SubLocalType, str], + Tuple[NegativeInfinityType, SubLocalType], + ], + ..., + ], +] +CmpKey = Tuple[ + int, Tuple[int, ...], PrePostDevType, PrePostDevType, PrePostDevType, LocalType +] +LegacyCmpKey = Tuple[int, Tuple[str, ...]] +VersionComparisonMethod = Callable[ + [Union[CmpKey, LegacyCmpKey], Union[CmpKey, LegacyCmpKey]], bool +] _Version = collections.namedtuple( "_Version", ["epoch", "release", "dev", "pre", "post", "local"] ) -def parse(version): - # type: (str) -> Union[LegacyVersion, Version] +def parse(version: str) -> Union["LegacyVersion", "Version"]: """ Parse the given version string and return either a :class:`Version` object or a :class:`LegacyVersion` object depending on if the given version is @@ -65,52 +58,45 @@ class InvalidVersion(ValueError): class _BaseVersion: - _key = None # type: Union[CmpKey, LegacyCmpKey] + _key: Union[CmpKey, LegacyCmpKey] - def __hash__(self): - # type: () -> int + def __hash__(self) -> int: return hash(self._key) # Please keep the duplicated `isinstance` check # in the six comparisons hereunder # unless you find a way to avoid adding overhead function calls. - def __lt__(self, other): - # type: (_BaseVersion) -> bool + def __lt__(self, other: "_BaseVersion") -> bool: if not isinstance(other, _BaseVersion): return NotImplemented return self._key < other._key - def __le__(self, other): - # type: (_BaseVersion) -> bool + def __le__(self, other: "_BaseVersion") -> bool: if not isinstance(other, _BaseVersion): return NotImplemented return self._key <= other._key - def __eq__(self, other): - # type: (object) -> bool + def __eq__(self, other: object) -> bool: if not isinstance(other, _BaseVersion): return NotImplemented return self._key == other._key - def __ge__(self, other): - # type: (_BaseVersion) -> bool + def __ge__(self, other: "_BaseVersion") -> bool: if not isinstance(other, _BaseVersion): return NotImplemented return self._key >= other._key - def __gt__(self, other): - # type: (_BaseVersion) -> bool + def __gt__(self, other: "_BaseVersion") -> bool: if not isinstance(other, _BaseVersion): return NotImplemented return self._key > other._key - def __ne__(self, other): - # type: (object) -> bool + def __ne__(self, other: object) -> bool: if not isinstance(other, _BaseVersion): return NotImplemented @@ -118,8 +104,7 @@ def __ne__(self, other): class LegacyVersion(_BaseVersion): - def __init__(self, version): - # type: (str) -> None + def __init__(self, version: str) -> None: self._version = str(version) self._key = _legacy_cmpkey(self._version) @@ -129,67 +114,54 @@ def __init__(self, version): DeprecationWarning, ) - def __str__(self): - # type: () -> str + def __str__(self) -> str: return self._version - def __repr__(self): - # type: () -> str + def __repr__(self) -> str: return f"" @property - def public(self): - # type: () -> str + def public(self) -> str: return self._version @property - def base_version(self): - # type: () -> str + def base_version(self) -> str: return self._version @property - def epoch(self): - # type: () -> int + def epoch(self) -> int: return -1 @property - def release(self): - # type: () -> None + def release(self) -> None: return None @property - def pre(self): - # type: () -> None + def pre(self) -> None: return None @property - def post(self): - # type: () -> None + def post(self) -> None: return None @property - def dev(self): - # type: () -> None + def dev(self) -> None: return None @property - def local(self): - # type: () -> None + def local(self) -> None: return None @property - def is_prerelease(self): - # type: () -> bool + def is_prerelease(self) -> bool: return False @property - def is_postrelease(self): - # type: () -> bool + def is_postrelease(self) -> bool: return False @property - def is_devrelease(self): - # type: () -> bool + def is_devrelease(self) -> bool: return False @@ -204,8 +176,7 @@ def is_devrelease(self): } -def _parse_version_parts(s): - # type: (str) -> Iterator[str] +def _parse_version_parts(s: str) -> Iterator[str]: for part in _legacy_version_component_re.split(s): part = _legacy_version_replacement_map.get(part, part) @@ -222,8 +193,7 @@ def _parse_version_parts(s): yield "*final" -def _legacy_cmpkey(version): - # type: (str) -> LegacyCmpKey +def _legacy_cmpkey(version: str) -> LegacyCmpKey: # We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch # greater than or equal to 0. This will effectively put the LegacyVersion, @@ -233,7 +203,7 @@ def _legacy_cmpkey(version): # This scheme is taken from pkg_resources.parse_version setuptools prior to # it's adoption of the packaging library. - parts = [] # type: List[str] + parts: List[str] = [] for part in _parse_version_parts(version.lower()): if part.startswith("*"): # remove "-" before a prerelease tag @@ -288,8 +258,7 @@ class Version(_BaseVersion): _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE) - def __init__(self, version): - # type: (str) -> None + def __init__(self, version: str) -> None: # Validate the version and parse it into pieces match = self._regex.search(version) @@ -318,12 +287,10 @@ def __init__(self, version): self._version.local, ) - def __repr__(self): - # type: () -> str + def __repr__(self) -> str: return f"" - def __str__(self): - # type: () -> str + def __str__(self) -> str: parts = [] # Epoch @@ -352,49 +319,41 @@ def __str__(self): return "".join(parts) @property - def epoch(self): - # type: () -> int - _epoch = self._version.epoch # type: int + def epoch(self) -> int: + _epoch: int = self._version.epoch return _epoch @property - def release(self): - # type: () -> Tuple[int, ...] - _release = self._version.release # type: Tuple[int, ...] + def release(self) -> Tuple[int, ...]: + _release: Tuple[int, ...] = self._version.release return _release @property - def pre(self): - # type: () -> Optional[Tuple[str, int]] - _pre = self._version.pre # type: Optional[Tuple[str, int]] + def pre(self) -> Optional[Tuple[str, int]]: + _pre: Optional[Tuple[str, int]] = self._version.pre return _pre @property - def post(self): - # type: () -> Optional[int] + def post(self) -> Optional[int]: return self._version.post[1] if self._version.post else None @property - def dev(self): - # type: () -> Optional[int] + def dev(self) -> Optional[int]: return self._version.dev[1] if self._version.dev else None @property - def local(self): - # type: () -> Optional[str] + def local(self) -> Optional[str]: if self._version.local: return ".".join(str(x) for x in self._version.local) else: return None @property - def public(self): - # type: () -> str + def public(self) -> str: return str(self).split("+", 1)[0] @property - def base_version(self): - # type: () -> str + def base_version(self) -> str: parts = [] # Epoch @@ -407,41 +366,33 @@ def base_version(self): return "".join(parts) @property - def is_prerelease(self): - # type: () -> bool + def is_prerelease(self) -> bool: return self.dev is not None or self.pre is not None @property - def is_postrelease(self): - # type: () -> bool + def is_postrelease(self) -> bool: return self.post is not None @property - def is_devrelease(self): - # type: () -> bool + def is_devrelease(self) -> bool: return self.dev is not None @property - def major(self): - # type: () -> int + def major(self) -> int: return self.release[0] if len(self.release) >= 1 else 0 @property - def minor(self): - # type: () -> int + def minor(self) -> int: return self.release[1] if len(self.release) >= 2 else 0 @property - def micro(self): - # type: () -> int + def micro(self) -> int: return self.release[2] if len(self.release) >= 3 else 0 def _parse_letter_version( - letter, # type: str - number, # type: Union[str, bytes, SupportsInt] -): - # type: (...) -> Optional[Tuple[str, int]] + letter: str, number: Union[str, bytes, SupportsInt] +) -> Optional[Tuple[str, int]]: if letter: # We consider there to be an implicit 0 in a pre-release if there is @@ -478,8 +429,7 @@ def _parse_letter_version( _local_version_separators = re.compile(r"[\._-]") -def _parse_local_version(local): - # type: (str) -> Optional[LocalType] +def _parse_local_version(local: str) -> Optional[LocalType]: """ Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve"). """ @@ -492,14 +442,13 @@ def _parse_local_version(local): def _cmpkey( - epoch, # type: int - release, # type: Tuple[int, ...] - pre, # type: Optional[Tuple[str, int]] - post, # type: Optional[Tuple[str, int]] - dev, # type: Optional[Tuple[str, int]] - local, # type: Optional[Tuple[SubLocalType]] -): - # type: (...) -> CmpKey + epoch: int, + release: Tuple[int, ...], + pre: Optional[Tuple[str, int]], + post: Optional[Tuple[str, int]], + dev: Optional[Tuple[str, int]], + local: Optional[Tuple[SubLocalType]], +) -> CmpKey: # When we compare a release version, we want to compare it with all of the # trailing zeros removed. So we'll use a reverse the list, drop all the now @@ -515,7 +464,7 @@ def _cmpkey( # if there is not a pre or a post segment. If we have one of those then # the normal sorting rules will handle this case correctly. if pre is None and post is None and dev is not None: - _pre = NegativeInfinity # type: PrePostDevType + _pre: PrePostDevType = NegativeInfinity # Versions without a pre-release (except as noted above) should sort after # those with one. elif pre is None: @@ -525,21 +474,21 @@ def _cmpkey( # Versions without a post segment should sort before those with one. if post is None: - _post = NegativeInfinity # type: PrePostDevType + _post: PrePostDevType = NegativeInfinity else: _post = post # Versions without a development segment should sort after those with one. if dev is None: - _dev = Infinity # type: PrePostDevType + _dev: PrePostDevType = Infinity else: _dev = dev if local is None: # Versions without a local segment should sort before those with one. - _local = NegativeInfinity # type: LocalType + _local: LocalType = NegativeInfinity else: # Versions with a local segment need that segment parsed to implement # the sorting rules in PEP440.