From 8b80615ce10ed6914ade6bc73a27a84d94269b49 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sun, 14 Aug 2022 16:00:10 -0700 Subject: [PATCH 1/9] Use strict mypy checking --- .pre-commit-config.yaml | 2 ++ mypy.ini | 24 +++++------------------- scripts/fuzz.py | 2 +- src/blackd/middlewares.py | 4 ++-- src/blib2to3/pytree.py | 20 ++++++++++---------- tests/optional.py | 2 +- tests/test_blackd.py | 23 ++++++++++++++--------- 7 files changed, 35 insertions(+), 42 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 87bb6e62987..0be8dc42890 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -49,6 +49,8 @@ repos: - types-typed-ast >= 1.4.1 - click >= 8.1.0 - platformdirs >= 2.1.0 + - pytest + - hypothesis - repo: https://github.com/pre-commit/mirrors-prettier rev: v2.7.1 diff --git a/mypy.ini b/mypy.ini index 244e8ae92f5..52f3b1164fd 100644 --- a/mypy.ini +++ b/mypy.ini @@ -7,32 +7,18 @@ python_version=3.6 mypy_path=src show_column_numbers=True - -# show error messages from unrelated files -follow_imports=normal - -# suppress errors about unsatisfied imports -ignore_missing_imports=True +show_error_codes=True # be strict -disallow_untyped_calls=True -warn_return_any=True -strict_optional=True -warn_no_return=True -warn_redundant_casts=True -warn_unused_ignores=True -disallow_any_generics=True -no_implicit_optional=True +strict=True + +# except for... +no_implicit_reexport = False # Unreachable blocks have been an issue when compiling mypyc, let's try # to avoid 'em in the first place. warn_unreachable=True -# The following are off by default. Flip them on if you feel -# adventurous. -disallow_untyped_defs=True -check_untyped_defs=True - [mypy-black] # The following is because of `patch_click()`. Remove when # we drop Python 3.6 support. diff --git a/scripts/fuzz.py b/scripts/fuzz.py index 83e02f45152..4a81162e3aa 100644 --- a/scripts/fuzz.py +++ b/scripts/fuzz.py @@ -85,5 +85,5 @@ def test_idempotent_any_syntatically_valid_python( pass else: test = test_idempotent_any_syntatically_valid_python - atheris.Setup(sys.argv, test.hypothesis.fuzz_one_input) + atheris.Setup(sys.argv, test.hypothesis.fuzz_one_input) # type: ignore[attr-defined] atheris.Fuzz() diff --git a/src/blackd/middlewares.py b/src/blackd/middlewares.py index 7abde525bfd..e71f5082686 100644 --- a/src/blackd/middlewares.py +++ b/src/blackd/middlewares.py @@ -9,7 +9,7 @@ def cors(allow_headers: Iterable[str]) -> Middleware: - @middleware + @middleware # type: ignore[misc] async def impl(request: Request, handler: Handler) -> StreamResponse: is_options = request.method == "OPTIONS" is_preflight = is_options and "Access-Control-Request-Method" in request.headers @@ -32,4 +32,4 @@ async def impl(request: Request, handler: Handler) -> StreamResponse: return resp - return impl # type: ignore + return impl # type: ignore[no-any-return] diff --git a/src/blib2to3/pytree.py b/src/blib2to3/pytree.py index 10b4690218e..6553189f884 100644 --- a/src/blib2to3/pytree.py +++ b/src/blib2to3/pytree.py @@ -10,7 +10,7 @@ There's also a pattern matching implementation here. """ -# mypy: allow-untyped-defs +# mypy: allow-untyped-defs, allow-incomplete-defs from typing import ( Any, @@ -291,7 +291,7 @@ def __str__(self) -> Text: """ return "".join(map(str, self.children)) - def _eq(self, other) -> bool: + def _eq(self, other: Base) -> bool: """Compare two nodes for equality.""" return (self.type, self.children) == (other.type, other.children) @@ -326,7 +326,7 @@ def prefix(self) -> Text: return self.children[0].prefix @prefix.setter - def prefix(self, prefix) -> None: + def prefix(self, prefix: Text) -> None: if self.children: self.children[0].prefix = prefix @@ -439,7 +439,7 @@ def __str__(self) -> Text: """ return self._prefix + str(self.value) - def _eq(self, other) -> bool: + def _eq(self, other: "Leaf") -> bool: """Compare two nodes for equality.""" return (self.type, self.value) == (other.type, other.value) @@ -472,7 +472,7 @@ def prefix(self) -> Text: return self._prefix @prefix.setter - def prefix(self, prefix) -> None: + def prefix(self, prefix: Text) -> None: self.changed() self._prefix = prefix @@ -618,7 +618,7 @@ def __init__( self.content = content self.name = name - def match(self, node: NL, results=None): + def match(self, node: NL, results=None) -> bool: """Override match() to insist on a leaf node.""" if not isinstance(node, Leaf): return False @@ -678,7 +678,7 @@ def __init__( if isinstance(item, WildcardPattern): # type: ignore[unreachable] self.wildcards = True # type: ignore[unreachable] self.type = type - self.content = newcontent + self.content = newcontent # TODO: this is unbound when content is None self.name = name def _submatch(self, node, results=None) -> bool: @@ -920,7 +920,7 @@ def _recursive_matches(self, nodes, count) -> Iterator[Tuple[int, _Results]]: class NegatedPattern(BasePattern): - def __init__(self, content: Optional[Any] = None) -> None: + def __init__(self, content: Optional[BasePattern] = None) -> None: """ Initializer. @@ -931,7 +931,7 @@ def __init__(self, content: Optional[Any] = None) -> None: """ if content is not None: assert isinstance(content, BasePattern), repr(content) - self.content = content + self.content: Optional[BasePattern] = content def match(self, node, results=None) -> bool: # We never match a node in its entirety @@ -941,7 +941,7 @@ def match_seq(self, nodes, results=None) -> bool: # We only match an empty sequence of nodes in its entirety return len(nodes) == 0 - def generate_matches(self, nodes) -> Iterator[Tuple[int, _Results]]: + def generate_matches(self, nodes: List[NL]) -> Iterator[Tuple[int, _Results]]: if self.content is None: # Return a match if there is an empty sequence if len(nodes) == 0: diff --git a/tests/optional.py b/tests/optional.py index 853ecaa2a43..8a39cc440a6 100644 --- a/tests/optional.py +++ b/tests/optional.py @@ -26,7 +26,7 @@ from pytest import StashKey except ImportError: # pytest < 7 - from _pytest.store import StoreKey as StashKey + from _pytest.store import StoreKey as StashKey # type: ignore[no-redef] log = logging.getLogger(__name__) diff --git a/tests/test_blackd.py b/tests/test_blackd.py index 1d12113a3f3..55aee57f42d 100644 --- a/tests/test_blackd.py +++ b/tests/test_blackd.py @@ -1,5 +1,5 @@ import re -from typing import Any +from typing import Any, Callable, TypeVar, TYPE_CHECKING from unittest.mock import patch import pytest @@ -15,17 +15,22 @@ except ImportError as e: raise RuntimeError("Please install Black with the 'd' extra") from e -try: - from aiohttp.test_utils import unittest_run_loop -except ImportError: - # unittest_run_loop is unnecessary and a no-op since aiohttp 3.8, and aiohttp 4 - # removed it. To maintain compatibility we can make our own no-op decorator. - def unittest_run_loop(func: Any, *args: Any, **kwargs: Any) -> Any: - return func +if TYPE_CHECKING: + F = TypeVar("F", bound=Callable[..., Any]) + + unittest_run_loop: Callable[[F], F] = lambda x: x +else: + try: + from aiohttp.test_utils import unittest_run_loop + except ImportError: + # unittest_run_loop is unnecessary and a no-op since aiohttp 3.8, and aiohttp 4 + # removed it. To maintain compatibility we can make our own no-op decorator. + def unittest_run_loop(func, *args, **kwargs): + return func @pytest.mark.blackd -class BlackDTestCase(AioHTTPTestCase): +class BlackDTestCase(AioHTTPTestCase): # type: ignore[misc] def test_blackd_main(self) -> None: with patch("blackd.web.run_app"): result = CliRunner().invoke(blackd.main, []) From dbc4cba5d894b612eac640d0ea684571545818f7 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sun, 14 Aug 2022 16:05:37 -0700 Subject: [PATCH 2/9] lint --- tests/test_blackd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_blackd.py b/tests/test_blackd.py index 55aee57f42d..019ce4c439b 100644 --- a/tests/test_blackd.py +++ b/tests/test_blackd.py @@ -1,5 +1,5 @@ import re -from typing import Any, Callable, TypeVar, TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Callable, TypeVar from unittest.mock import patch import pytest From 393a5afe1ac6de4199628fe0310bbc45cb5728a2 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sun, 14 Aug 2022 16:06:01 -0700 Subject: [PATCH 3/9] lint --- scripts/fuzz.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/fuzz.py b/scripts/fuzz.py index 4a81162e3aa..25362c927d4 100644 --- a/scripts/fuzz.py +++ b/scripts/fuzz.py @@ -85,5 +85,8 @@ def test_idempotent_any_syntatically_valid_python( pass else: test = test_idempotent_any_syntatically_valid_python - atheris.Setup(sys.argv, test.hypothesis.fuzz_one_input) # type: ignore[attr-defined] + atheris.Setup( + sys.argv, + test.hypothesis.fuzz_one_input, # type: ignore[attr-defined] + ) atheris.Fuzz() From 80de1593391ee2720c3478a98388149c5683877c Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Wed, 24 Aug 2022 14:35:22 -0700 Subject: [PATCH 4/9] refix after merge --- tests/test_blackd.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/tests/test_blackd.py b/tests/test_blackd.py index 8e739063f6e..3a7ab71e055 100644 --- a/tests/test_blackd.py +++ b/tests/test_blackd.py @@ -1,6 +1,6 @@ import re import sys -from typing import Any +from typing import TYPE_CHECKING, Any, Callable, TypeVar from unittest.mock import patch import pytest @@ -19,16 +19,21 @@ except ImportError as e: raise RuntimeError("Please install Black with the 'd' extra") from e - try: - from aiohttp.test_utils import unittest_run_loop - except ImportError: - # unittest_run_loop is unnecessary and a no-op since aiohttp 3.8, and aiohttp 4 - # removed it. To maintain compatibility we can make our own no-op decorator. - def unittest_run_loop(func: Any, *args: Any, **kwargs: Any) -> Any: - return func + if TYPE_CHECKING: + F = TypeVar("F", bound=Callable[..., Any]) + + unittest_run_loop: Callable[[F], F] = lambda x: x + else: + try: + from aiohttp.test_utils import unittest_run_loop + except ImportError: + # unittest_run_loop is unnecessary and a no-op since aiohttp 3.8, and aiohttp 4 + # removed it. To maintain compatibility we can make our own no-op decorator. + def unittest_run_loop(func, *args, **kwargs): + return func @pytest.mark.blackd - class BlackDTestCase(AioHTTPTestCase): + class BlackDTestCase(AioHTTPTestCase): # type: ignore[misc] def test_blackd_main(self) -> None: with patch("blackd.web.run_app"): result = CliRunner().invoke(blackd.main, []) From 7e456de3e02e76a51c8e4c7bf9102af924139e9a Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Wed, 24 Aug 2022 14:39:56 -0700 Subject: [PATCH 5/9] lint after merge --- tests/test_blackd.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_blackd.py b/tests/test_blackd.py index 3a7ab71e055..1da4ab702d2 100644 --- a/tests/test_blackd.py +++ b/tests/test_blackd.py @@ -27,8 +27,9 @@ try: from aiohttp.test_utils import unittest_run_loop except ImportError: - # unittest_run_loop is unnecessary and a no-op since aiohttp 3.8, and aiohttp 4 - # removed it. To maintain compatibility we can make our own no-op decorator. + # unittest_run_loop is unnecessary and a no-op since aiohttp 3.8, and + # aiohttp 4 removed it. To maintain compatibility we can make our own + # no-op decorator. def unittest_run_loop(func, *args, **kwargs): return func From b714410546023838e48ec858aea77fdcac2f1885 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Wed, 24 Aug 2022 15:06:30 -0700 Subject: [PATCH 6/9] ignore missing imports --- mypy.ini | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/mypy.ini b/mypy.ini index 52f3b1164fd..b4a626e8a65 100644 --- a/mypy.ini +++ b/mypy.ini @@ -23,3 +23,18 @@ warn_unreachable=True # The following is because of `patch_click()`. Remove when # we drop Python 3.6 support. warn_unused_ignores=False + +[mypy-blib2to3.driver.*] +ignore_missing_imports = True + +[mypy-IPython.*] +ignore_missing_imports = True + +[mypy-colorama.*] +ignore_missing_imports = True + +[mypy-pathspec.*] +ignore_missing_imports = True + +[mypy-tokenize_rt.*] +ignore_missing_imports = True From 7625eabb5f0e6fa348a050a88eaf81c0301ec39a Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Wed, 24 Aug 2022 15:26:07 -0700 Subject: [PATCH 7/9] missed a spot --- mypy.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mypy.ini b/mypy.ini index b4a626e8a65..2ddfb03a0fc 100644 --- a/mypy.ini +++ b/mypy.ini @@ -38,3 +38,6 @@ ignore_missing_imports = True [mypy-tokenize_rt.*] ignore_missing_imports = True + +[mypy-uvloop.*] +ignore_missing_imports = True From 70c9d4ea94479f4306e5b8b1cf8dd87cd454446f Mon Sep 17 00:00:00 2001 From: Richard Si <63936253+ichard26@users.noreply.github.com> Date: Fri, 26 Aug 2022 17:52:55 -0400 Subject: [PATCH 8/9] Work around mypyc bug, sigh Redeclaring an attribute in a subclass with a more narrow type seems to make mypyc think the subclass has two attributes of the same name! --- src/blib2to3/pytree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blib2to3/pytree.py b/src/blib2to3/pytree.py index 6553189f884..15a1420ef7d 100644 --- a/src/blib2to3/pytree.py +++ b/src/blib2to3/pytree.py @@ -931,7 +931,7 @@ def __init__(self, content: Optional[BasePattern] = None) -> None: """ if content is not None: assert isinstance(content, BasePattern), repr(content) - self.content: Optional[BasePattern] = content + self.content = content def match(self, node, results=None) -> bool: # We never match a node in its entirety From 5f4c43875fbbd391dabeecaec90d8512fd2626ec Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Fri, 26 Aug 2022 19:48:56 -0700 Subject: [PATCH 9/9] ignore another import --- mypy.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mypy.ini b/mypy.ini index 2ddfb03a0fc..4811cc0be76 100644 --- a/mypy.ini +++ b/mypy.ini @@ -41,3 +41,6 @@ ignore_missing_imports = True [mypy-uvloop.*] ignore_missing_imports = True + +[mypy-_black_version.*] +ignore_missing_imports = True