Skip to content

Commit

Permalink
Add a NewType for normalized names (#292)
Browse files Browse the repository at this point in the history
* Make our mypy type-casting magic more robust

Newer versions of mypy are better at following code flow, and assign
Any types to our reimplemented cast function. This commit significantly
restructures our typing.cast re-definition, to make it more robust.

* Rename MYPY_CHECK_RUNNING -> TYPE_CHECKING

This allows for some significant cleanups in our typing helpers, and
makes it much easier to document our... workarounds.

* Bump to newer mypy

* Improve `canonicalize_name` safety with type hints
  • Loading branch information
pradyunsg authored Apr 8, 2020
1 parent 9731c00 commit 61672bf
Show file tree
Hide file tree
Showing 10 changed files with 44 additions and 28 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
~~~~~~~~~~~~~~~~~
Expand Down
4 changes: 2 additions & 2 deletions packaging/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
29 changes: 19 additions & 10 deletions packaging/_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 2 additions & 2 deletions packaging/markers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
4 changes: 2 additions & 2 deletions packaging/requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
4 changes: 2 additions & 2 deletions packaging/specifiers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions packaging/tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
13 changes: 8 additions & 5 deletions packaging/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
4 changes: 2 additions & 2 deletions packaging/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 61672bf

Please sign in to comment.