diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 69124678..50d1f6c8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.750 + rev: v0.770 hooks: - id: mypy exclude: '^(docs|tasks|tests)|setup\.py' diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2b6af82a..3ce556a3 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,10 @@ Changelog ~~~~~~~~~~~~ * Canonicalize version before comparing specifiers. (:issue:`282`) +* Change type hint for ``canonicalize_name`` to return + ``packaging.utils.NormalizedName``. + This enables the use of static typing tools (like mypy) to detect mixing of + normalized and un-normalized names. 20.3 - 2020-03-05 ~~~~~~~~~~~~~~~~~ diff --git a/packaging/_compat.py b/packaging/_compat.py index a145f7ee..e54bd4ed 100644 --- a/packaging/_compat.py +++ b/packaging/_compat.py @@ -5,9 +5,9 @@ import sys -from ._typing import MYPY_CHECK_RUNNING +from ._typing import TYPE_CHECKING -if MYPY_CHECK_RUNNING: # pragma: no cover +if TYPE_CHECKING: # pragma: no cover from typing import Any, Dict, Tuple, Type diff --git a/packaging/_typing.py b/packaging/_typing.py index dc6dfce7..77a8b918 100644 --- a/packaging/_typing.py +++ b/packaging/_typing.py @@ -18,22 +18,31 @@ In packaging, all static-typing related imports should be guarded as follows: - from packaging._typing import MYPY_CHECK_RUNNING + from packaging._typing import TYPE_CHECKING - if MYPY_CHECK_RUNNING: + if TYPE_CHECKING: from typing import ... Ref: https://github.com/python/mypy/issues/3216 """ -MYPY_CHECK_RUNNING = False +__all__ = ["TYPE_CHECKING", "cast"] -if MYPY_CHECK_RUNNING: # pragma: no cover - import typing - - cast = typing.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: - # typing's cast() is needed at runtime, but we don't want to import typing. - # Thus, we use a dummy no-op version, which we tell mypy to ignore. - def cast(type_, value): # type: ignore + # executed at runtime + def cast(type_, value): # noqa return value diff --git a/packaging/markers.py b/packaging/markers.py index f0174711..87cd3f95 100644 --- a/packaging/markers.py +++ b/packaging/markers.py @@ -13,10 +13,10 @@ from pyparsing import Literal as L # noqa from ._compat import string_types -from ._typing import MYPY_CHECK_RUNNING +from ._typing import TYPE_CHECKING from .specifiers import Specifier, InvalidSpecifier -if MYPY_CHECK_RUNNING: # pragma: no cover +if TYPE_CHECKING: # pragma: no cover from typing import Any, Callable, Dict, List, Optional, Tuple, Union Operator = Callable[[str, str], bool] diff --git a/packaging/requirements.py b/packaging/requirements.py index 1b547927..91f81ede 100644 --- a/packaging/requirements.py +++ b/packaging/requirements.py @@ -11,11 +11,11 @@ from pyparsing import Literal as L # noqa from six.moves.urllib import parse as urlparse -from ._typing import MYPY_CHECK_RUNNING +from ._typing import TYPE_CHECKING from .markers import MARKER_EXPR, Marker from .specifiers import LegacySpecifier, Specifier, SpecifierSet -if MYPY_CHECK_RUNNING: # pragma: no cover +if TYPE_CHECKING: # pragma: no cover from typing import List diff --git a/packaging/specifiers.py b/packaging/specifiers.py index 4eeef14f..9f7dd278 100644 --- a/packaging/specifiers.py +++ b/packaging/specifiers.py @@ -9,11 +9,11 @@ import re from ._compat import string_types, with_metaclass -from ._typing import MYPY_CHECK_RUNNING +from ._typing import TYPE_CHECKING from .utils import canonicalize_version from .version import Version, LegacyVersion, parse -if MYPY_CHECK_RUNNING: # pragma: no cover +if TYPE_CHECKING: # pragma: no cover from typing import ( List, Dict, diff --git a/packaging/tags.py b/packaging/tags.py index 25d8e008..9064910b 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -22,9 +22,9 @@ import sysconfig import warnings -from ._typing import MYPY_CHECK_RUNNING, cast +from ._typing import TYPE_CHECKING, cast -if MYPY_CHECK_RUNNING: # pragma: no cover +if TYPE_CHECKING: # pragma: no cover from typing import ( Dict, FrozenSet, diff --git a/packaging/utils.py b/packaging/utils.py index 44f1bf98..19579c1a 100644 --- a/packaging/utils.py +++ b/packaging/utils.py @@ -5,19 +5,22 @@ import re -from ._typing import MYPY_CHECK_RUNNING +from ._typing import TYPE_CHECKING, cast from .version import InvalidVersion, Version -if MYPY_CHECK_RUNNING: # pragma: no cover - from typing import Union +if TYPE_CHECKING: # pragma: no cover + from typing import NewType, Union + + NormalizedName = NewType("NormalizedName", str) _canonicalize_regex = re.compile(r"[-_.]+") def canonicalize_name(name): - # type: (str) -> str + # type: (str) -> NormalizedName # This is taken from PEP 503. - return _canonicalize_regex.sub("-", name).lower() + value = _canonicalize_regex.sub("-", name).lower() + return cast("NormalizedName", value) def canonicalize_version(_version): diff --git a/packaging/version.py b/packaging/version.py index f39a2a12..00371e86 100644 --- a/packaging/version.py +++ b/packaging/version.py @@ -8,9 +8,9 @@ import re from ._structures import Infinity, NegativeInfinity -from ._typing import MYPY_CHECK_RUNNING +from ._typing import TYPE_CHECKING -if MYPY_CHECK_RUNNING: # pragma: no cover +if TYPE_CHECKING: # pragma: no cover from typing import Callable, Iterator, List, Optional, SupportsInt, Tuple, Union from ._structures import InfinityType, NegativeInfinityType