From 9f3bfe82cf1200f7a4249a0fbc1e7db2c8369e63 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 16 Aug 2019 10:08:18 +0300 Subject: [PATCH 1/6] Fix TypeError when importing pytest on Python 3.5.0 and 3.5.1 The typing module on these versions have these issues: - `typing.Pattern` cannot appear in a Union since it is not considered a class. - `@overload` is not supported in runtime. (On the other hand, mypy doesn't support putting it under `if False`, so we need some runtime hack). Refs #5751. --- changelog/5751.bugfix.rst | 1 + src/_pytest/_code/code.py | 2 +- src/_pytest/python_api.py | 17 ++++++++++++----- src/_pytest/recwarn.py | 18 ++++++++++++------ 4 files changed, 26 insertions(+), 12 deletions(-) create mode 100644 changelog/5751.bugfix.rst diff --git a/changelog/5751.bugfix.rst b/changelog/5751.bugfix.rst new file mode 100644 index 000000000..879909c8b --- /dev/null +++ b/changelog/5751.bugfix.rst @@ -0,0 +1 @@ +Fixed ``TypeError`` when importing pytest on Python 3.5.0 and 3.5.1. diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index 7d72234e7..a0f4d15ce 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -591,7 +591,7 @@ class ExceptionInfo(Generic[_E]): ) return fmt.repr_excinfo(self) - def match(self, regexp: Union[str, Pattern]) -> bool: + def match(self, regexp: "Union[str, Pattern]") -> bool: """ Check whether the regular expression 'regexp' is found in the string representation of the exception using ``re.search``. If it matches diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index fbc3d914e..c5e06d491 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -1,6 +1,7 @@ import inspect import math import pprint +import sys from collections.abc import Iterable from collections.abc import Mapping from collections.abc import Sized @@ -28,6 +29,12 @@ from _pytest.outcomes import fail if False: # TYPE_CHECKING from typing import Type # noqa: F401 (used in type string) +if sys.version_info <= (3, 5, 1): + + def overload(f): # noqa: F811 + return f + + BASE_TYPE = (type, STRING_TYPES) @@ -547,12 +554,12 @@ _E = TypeVar("_E", bound=BaseException) def raises( expected_exception: Union["Type[_E]", Tuple["Type[_E]", ...]], *, - match: Optional[Union[str, Pattern]] = ... + match: "Optional[Union[str, Pattern]]" = ... ) -> "RaisesContext[_E]": ... # pragma: no cover -@overload +@overload # noqa: F811 def raises( expected_exception: Union["Type[_E]", Tuple["Type[_E]", ...]], func: Callable, @@ -563,10 +570,10 @@ def raises( ... # pragma: no cover -def raises( +def raises( # noqa: F811 expected_exception: Union["Type[_E]", Tuple["Type[_E]", ...]], *args: Any, - match: Optional[Union[str, Pattern]] = None, + match: Optional[Union[str, "Pattern"]] = None, **kwargs: Any ) -> Union["RaisesContext[_E]", Optional[_pytest._code.ExceptionInfo[_E]]]: r""" @@ -724,7 +731,7 @@ class RaisesContext(Generic[_E]): self, expected_exception: Union["Type[_E]", Tuple["Type[_E]", ...]], message: str, - match_expr: Optional[Union[str, Pattern]] = None, + match_expr: Optional[Union[str, "Pattern"]] = None, ) -> None: self.expected_exception = expected_exception self.message = message diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py index 19e3938c3..27519fd46 100644 --- a/src/_pytest/recwarn.py +++ b/src/_pytest/recwarn.py @@ -1,5 +1,6 @@ """ recording warnings during test function execution. """ import re +import sys import warnings from types import TracebackType from typing import Any @@ -18,6 +19,11 @@ from _pytest.outcomes import fail if False: # TYPE_CHECKING from typing import Type +if sys.version_info <= (3, 5, 1): + + def overload(f): # noqa: F811 + return f + @yield_fixture def recwarn(): @@ -58,26 +64,26 @@ def deprecated_call(func=None, *args, **kwargs): def warns( expected_warning: Union["Type[Warning]", Tuple["Type[Warning]", ...]], *, - match: Optional[Union[str, Pattern]] = ... + match: "Optional[Union[str, Pattern]]" = ... ) -> "WarningsChecker": ... # pragma: no cover -@overload +@overload # noqa: F811 def warns( expected_warning: Union["Type[Warning]", Tuple["Type[Warning]", ...]], func: Callable, *args: Any, - match: Optional[Union[str, Pattern]] = ..., + match: Optional[Union[str, "Pattern"]] = ..., **kwargs: Any ) -> Union[Any]: ... # pragma: no cover -def warns( +def warns( # noqa: F811 expected_warning: Union["Type[Warning]", Tuple["Type[Warning]", ...]], *args: Any, - match: Optional[Union[str, Pattern]] = None, + match: Optional[Union[str, "Pattern"]] = None, **kwargs: Any ) -> Union["WarningsChecker", Any]: r"""Assert that code raises a particular class of warning. @@ -207,7 +213,7 @@ class WarningsChecker(WarningsRecorder): expected_warning: Optional[ Union["Type[Warning]", Tuple["Type[Warning]", ...]] ] = None, - match_expr: Optional[Union[str, Pattern]] = None, + match_expr: Optional[Union[str, "Pattern"]] = None, ) -> None: super().__init__() From 1e3205e7cfff63ab87fce4adfd4118d8fe1543d6 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 17 Aug 2019 22:07:48 +0200 Subject: [PATCH 2/6] ci: Travis: use 3.5.0 Ref: https://github.com/pytest-dev/pytest/pull/5752#issuecomment-522241225 --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5de40f3a4..c1f7ad357 100644 --- a/.travis.yml +++ b/.travis.yml @@ -43,7 +43,8 @@ jobs: python: 'pypy3' - env: TOXENV=py35-xdist - python: '3.5' + dist: trusty + python: '3.5.0' # Coverage for: # - pytester's LsofFdLeakChecker From a7ede64f4262d1acbc4d50442d980f54631a14c7 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 18 Aug 2019 14:54:52 -0700 Subject: [PATCH 3/6] Move `@overload` to compat --- .pre-commit-config.yaml | 2 +- src/_pytest/compat.py | 7 +++++++ src/_pytest/python_api.py | 8 +------- src/_pytest/recwarn.py | 8 +------- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4d127d3c5..e9a970ca7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,7 +26,7 @@ repos: hooks: - id: flake8 language_version: python3 - additional_dependencies: [flake8-typing-imports] + additional_dependencies: [flake8-typing-imports==1.3.0] - repo: https://github.com/asottile/reorder_python_imports rev: v1.4.0 hooks: diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index 2d11231a4..596a8fd0f 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -9,6 +9,7 @@ import sys from contextlib import contextmanager from inspect import Parameter from inspect import signature +from typing import overload import attr import py @@ -347,3 +348,9 @@ class FuncargnamesCompatAttr: warnings.warn(FUNCARGNAMES, stacklevel=2) return self.fixturenames + + +if sys.version_info < (3, 5, 2): + + def overload(f): # noqa: F811 + return f diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index c5e06d491..f03d45ab7 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -1,7 +1,6 @@ import inspect import math import pprint -import sys from collections.abc import Iterable from collections.abc import Mapping from collections.abc import Sized @@ -14,7 +13,6 @@ from typing import Callable from typing import cast from typing import Generic from typing import Optional -from typing import overload from typing import Pattern from typing import Tuple from typing import TypeVar @@ -23,17 +21,13 @@ from typing import Union from more_itertools.more import always_iterable import _pytest._code +from _pytest.compat import overload from _pytest.compat import STRING_TYPES from _pytest.outcomes import fail if False: # TYPE_CHECKING from typing import Type # noqa: F401 (used in type string) -if sys.version_info <= (3, 5, 1): - - def overload(f): # noqa: F811 - return f - BASE_TYPE = (type, STRING_TYPES) diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py index 27519fd46..58076d66b 100644 --- a/src/_pytest/recwarn.py +++ b/src/_pytest/recwarn.py @@ -1,6 +1,5 @@ """ recording warnings during test function execution. """ import re -import sys import warnings from types import TracebackType from typing import Any @@ -8,22 +7,17 @@ from typing import Callable from typing import Iterator from typing import List from typing import Optional -from typing import overload from typing import Pattern from typing import Tuple from typing import Union +from _pytest.compat import overload from _pytest.fixtures import yield_fixture from _pytest.outcomes import fail if False: # TYPE_CHECKING from typing import Type -if sys.version_info <= (3, 5, 1): - - def overload(f): # noqa: F811 - return f - @yield_fixture def recwarn(): From cec2183aebe8106f740b2891422d5818dd01a399 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 20 Aug 2019 11:19:25 +0300 Subject: [PATCH 4/6] Add workaround for test_raises_cyclic_reference in Python 3.5.{0,1} --- testing/python/raises.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/testing/python/raises.py b/testing/python/raises.py index 668be57fc..2b7e92615 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -159,13 +159,19 @@ class TestRaises: """ Ensure pytest.raises does not leave a reference cycle (#1965). """ - import gc class T: def __call__(self): + # Early versions of Python 3.5 have some bug causing the + # __call__ frame to still refer to t even after everything + # is done. This makes the test pass for them. + if sys.version_info < (3, 5, 2): + del self raise ValueError t = T() + refcount = sys.getrefcount(t) + if method == "function": pytest.raises(ValueError, t) else: @@ -175,14 +181,7 @@ class TestRaises: # ensure both forms of pytest.raises don't leave exceptions in sys.exc_info() assert sys.exc_info() == (None, None, None) - del t - # Make sure this does get updated in locals dict - # otherwise it could keep a reference - locals() - - # ensure the t instance is not stuck in a cyclic reference - for o in gc.get_objects(): - assert type(o) is not T + assert sys.getrefcount(t) == refcount def test_raises_match(self): msg = r"with base \d+" From a7c235732a10ac94f5c0881de49419d234fe2caf Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 20 Aug 2019 11:43:29 +0300 Subject: [PATCH 5/6] Pypy doesn't have sys.getrefcount(), so go back to gc --- testing/python/raises.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/testing/python/raises.py b/testing/python/raises.py index 2b7e92615..4607ef327 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -159,6 +159,7 @@ class TestRaises: """ Ensure pytest.raises does not leave a reference cycle (#1965). """ + import gc class T: def __call__(self): @@ -170,7 +171,7 @@ class TestRaises: raise ValueError t = T() - refcount = sys.getrefcount(t) + refcount = len(gc.get_referrers(t)) if method == "function": pytest.raises(ValueError, t) @@ -181,7 +182,7 @@ class TestRaises: # ensure both forms of pytest.raises don't leave exceptions in sys.exc_info() assert sys.exc_info() == (None, None, None) - assert sys.getrefcount(t) == refcount + assert refcount == len(gc.get_referrers(t)) def test_raises_match(self): msg = r"with base \d+" From 43eab917a1d174808f1975364f64214f98b094a8 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 20 Aug 2019 15:41:32 +0300 Subject: [PATCH 6/6] Fix coverage --- src/_pytest/compat.py | 2 +- testing/python/raises.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index 596a8fd0f..2d6e8eb6b 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -350,7 +350,7 @@ class FuncargnamesCompatAttr: return self.fixturenames -if sys.version_info < (3, 5, 2): +if sys.version_info < (3, 5, 2): # pragma: no cover def overload(f): # noqa: F811 return f diff --git a/testing/python/raises.py b/testing/python/raises.py index 4607ef327..28b0715c0 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -166,7 +166,7 @@ class TestRaises: # Early versions of Python 3.5 have some bug causing the # __call__ frame to still refer to t even after everything # is done. This makes the test pass for them. - if sys.version_info < (3, 5, 2): + if sys.version_info < (3, 5, 2): # pragma: no cover del self raise ValueError