typing: set no_implicit_reexport

In Python, if module A defines a name `name`, and module B does `import
name from A`, then another module C can `import name from B`.

Sometimes it is intentional -- module B is meant to "reexport" `name`.
But sometimes it is just confusion/inconsistency on where `name` should
be imported from.

mypy has a flag `--no-implicit-reexport` which puts some order into
this. A name can only be imported from a module if

1. The module defines the name
2. The module's `__all__` includes the name
3. The module imports the name as `from ... import .. as name`.

This flag is included in mypy's `--strict` flag.

I like this flag, but I realize it is a bit controversial, and in
particular item 3 above is a bit unfriendly to contributors who don't
know about it. So I didn't intend to add it to pytest.

But while investigating issue 7589 I came upon mypy issue 8754 which
causes `--no-implicit-reexport` to leak into installed libraries and
causes some unexpected typing differences *in pytest* if the user uses
this flag.

Since the diff mostly makes sense, let's just conform to it.
This commit is contained in:
Ran Benita 2020-07-31 09:46:56 +03:00
parent 645cbc91fc
commit 8d98de8f8a
11 changed files with 39 additions and 28 deletions

View File

@ -104,3 +104,4 @@ strict_equality = True
warn_redundant_casts = True
warn_return_any = True
warn_unused_configs = True
no_implicit_reexport = True

View File

@ -4,9 +4,9 @@ from .code import ExceptionInfo
from .code import filter_traceback
from .code import Frame
from .code import getfslineno
from .code import getrawcode
from .code import Traceback
from .code import TracebackEntry
from .source import getrawcode
from .source import Source
__all__ = [

View File

@ -14,7 +14,7 @@ from typing import Any
from typing import Callable
from typing import Generic
from typing import Optional
from typing import overload
from typing import overload as overload
from typing import Tuple
from typing import TypeVar
from typing import Union
@ -208,7 +208,7 @@ if sys.version_info < (3, 7):
else:
from contextlib import nullcontext # noqa
from contextlib import nullcontext as nullcontext # noqa: F401
def get_default_arg_names(function: Callable[..., Any]) -> Tuple[str, ...]:
@ -363,7 +363,7 @@ else:
if sys.version_info >= (3, 8):
from functools import cached_property
from functools import cached_property as cached_property
else:
class cached_property(Generic[_S, _T]):

View File

@ -35,8 +35,8 @@ from pluggy import PluginManager
import _pytest._code
import _pytest.deprecated
import _pytest.hookspec # the extension point definitions
from .exceptions import PrintHelp
from .exceptions import UsageError
from .exceptions import PrintHelp as PrintHelp
from .exceptions import UsageError as UsageError
from .findpaths import determine_setup
from _pytest._code import ExceptionInfo
from _pytest._code import filter_traceback

View File

@ -28,7 +28,14 @@ if TYPE_CHECKING:
from _pytest.nodes import Item
__all__ = ["Mark", "MarkDecorator", "MarkGenerator", "get_empty_parameterset_mark"]
__all__ = [
"MARK_GEN",
"Mark",
"MarkDecorator",
"MarkGenerator",
"ParameterSet",
"get_empty_parameterset_mark",
]
old_mark_config_key = StoreKey[Optional[Config]]()

View File

@ -32,6 +32,7 @@ from _pytest import nodes
from _pytest._code import filter_traceback
from _pytest._code import getfslineno
from _pytest._code.code import ExceptionInfo
from _pytest._code.code import TerminalRepr
from _pytest._io import TerminalWriter
from _pytest._io.saferepr import saferepr
from _pytest.compat import ascii_escaped
@ -66,7 +67,6 @@ from _pytest.pathlib import import_path
from _pytest.pathlib import ImportPathMismatchError
from _pytest.pathlib import parts
from _pytest.pathlib import visit
from _pytest.reports import TerminalRepr
from _pytest.warning_types import PytestCollectionWarning
from _pytest.warning_types import PytestUnhandledCoroutineWarning
@ -581,7 +581,7 @@ class Module(nodes.File, PyCollector):
"Traceback:\n"
"{traceback}".format(fspath=self.fspath, traceback=formatted_tb)
) from e
except _pytest.runner.Skipped as e:
except skip.Exception as e:
if e.allow_module_level:
raise
raise self.CollectError(

View File

@ -1018,7 +1018,7 @@ class TestTracebackCutting:
See: https://bitbucket.org/pytest-dev/py/issues/71
This fixes #995.
"""
from _pytest.python import filter_traceback
from _pytest._code import filter_traceback
try:
ns = {} # type: Dict[str, Any]
@ -1038,7 +1038,7 @@ class TestTracebackCutting:
In this case, one of the files in the traceback no longer exists.
This fixes #1133.
"""
from _pytest.python import filter_traceback
from _pytest._code import filter_traceback
testdir.syspathinsert()
testdir.makepyfile(

View File

@ -3,6 +3,7 @@ import textwrap
import pytest
from _pytest import fixtures
from _pytest.compat import getfuncargnames
from _pytest.config import ExitCode
from _pytest.fixtures import FixtureRequest
from _pytest.pathlib import Path
@ -15,22 +16,22 @@ def test_getfuncargnames_functions():
def f():
raise NotImplementedError()
assert not fixtures.getfuncargnames(f)
assert not getfuncargnames(f)
def g(arg):
raise NotImplementedError()
assert fixtures.getfuncargnames(g) == ("arg",)
assert getfuncargnames(g) == ("arg",)
def h(arg1, arg2="hello"):
raise NotImplementedError()
assert fixtures.getfuncargnames(h) == ("arg1",)
assert getfuncargnames(h) == ("arg1",)
def j(arg1, arg2, arg3="hello"):
raise NotImplementedError()
assert fixtures.getfuncargnames(j) == ("arg1", "arg2")
assert getfuncargnames(j) == ("arg1", "arg2")
def test_getfuncargnames_methods():
@ -40,7 +41,7 @@ def test_getfuncargnames_methods():
def f(self, arg1, arg2="hello"):
raise NotImplementedError()
assert fixtures.getfuncargnames(A().f) == ("arg1",)
assert getfuncargnames(A().f) == ("arg1",)
def test_getfuncargnames_staticmethod():
@ -51,7 +52,7 @@ def test_getfuncargnames_staticmethod():
def static(arg1, arg2, x=1):
raise NotImplementedError()
assert fixtures.getfuncargnames(A.static, cls=A) == ("arg1", "arg2")
assert getfuncargnames(A.static, cls=A) == ("arg1", "arg2")
def test_getfuncargnames_partial():
@ -64,7 +65,7 @@ def test_getfuncargnames_partial():
class T:
test_ok = functools.partial(check, i=2)
values = fixtures.getfuncargnames(T().test_ok, name="test_ok")
values = getfuncargnames(T().test_ok, name="test_ok")
assert values == ("arg1", "arg2")
@ -78,7 +79,7 @@ def test_getfuncargnames_staticmethod_partial():
class T:
test_ok = staticmethod(functools.partial(check, i=2))
values = fixtures.getfuncargnames(T().test_ok, name="test_ok")
values = getfuncargnames(T().test_ok, name="test_ok")
assert values == ("arg1", "arg2")

View File

@ -1,8 +1,8 @@
from typing import Any
import pytest
from _pytest import python
from _pytest import runner
from _pytest._code import getfslineno
class TestOEJSKITSpecials:
@ -87,8 +87,8 @@ def test_wrapped_getfslineno() -> None:
def wrapped_func(x, y, z):
pass
fs, lineno = python.getfslineno(wrapped_func)
fs2, lineno2 = python.getfslineno(wrap)
fs, lineno = getfslineno(wrapped_func)
fs2, lineno2 = getfslineno(wrap)
assert lineno > lineno2, "getfslineno does not unwrap correctly"

View File

@ -19,6 +19,8 @@ from hypothesis import strategies
import pytest
from _pytest import fixtures
from _pytest import python
from _pytest.compat import _format_args
from _pytest.compat import getfuncargnames
from _pytest.outcomes import fail
from _pytest.pytester import Testdir
from _pytest.python import _idval
@ -41,7 +43,7 @@ class TestMetafunc:
obj = attr.ib()
_nodeid = attr.ib()
names = fixtures.getfuncargnames(func)
names = getfuncargnames(func)
fixtureinfo = FuncFixtureInfoMock(names) # type: Any
definition = DefinitionMock._create(func, "mock::nodeid") # type: Any
return python.Metafunc(definition, fixtureinfo, config)
@ -954,22 +956,22 @@ class TestMetafunc:
def function1():
pass
assert fixtures._format_args(function1) == "()"
assert _format_args(function1) == "()"
def function2(arg1):
pass
assert fixtures._format_args(function2) == "(arg1)"
assert _format_args(function2) == "(arg1)"
def function3(arg1, arg2="qwe"):
pass
assert fixtures._format_args(function3) == "(arg1, arg2='qwe')"
assert _format_args(function3) == "(arg1, arg2='qwe')"
def function4(arg1, *args, **kwargs):
pass
assert fixtures._format_args(function4) == "(arg1, *args, **kwargs)"
assert _format_args(function4) == "(arg1, *args, **kwargs)"
class TestMetafuncFunctional:

View File

@ -4,8 +4,8 @@ from unittest import mock
import pytest
from _pytest.config import ExitCode
from _pytest.mark import EMPTY_PARAMETERSET_OPTION
from _pytest.mark import MarkGenerator as Mark
from _pytest.mark.structures import EMPTY_PARAMETERSET_OPTION
from _pytest.nodes import Collector
from _pytest.nodes import Node