Skip to content

More type annotations, fix some typing bugs #7358

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/_pytest/_code/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -928,8 +928,13 @@ def toterminal(self, tw: TerminalWriter) -> None:
raise NotImplementedError()


# This class is abstract -- only subclasses are instantiated.
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class ExceptionRepr(TerminalRepr):
# Provided by in subclasses.
reprcrash = None # type: Optional[ReprFileLocation]
reprtraceback = None # type: ReprTraceback

def __attrs_post_init__(self):
self.sections = [] # type: List[Tuple[str, str, str]]

Expand Down
8 changes: 5 additions & 3 deletions src/_pytest/assertion/rewrite.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
from typing import Tuple
from typing import Union

import py

from _pytest._io.saferepr import saferepr
from _pytest._version import version
from _pytest.assertion import util
Expand Down Expand Up @@ -177,10 +179,10 @@ def _early_rewrite_bailout(self, name: str, state: "AssertionState") -> bool:
"""
if self.session is not None and not self._session_paths_checked:
self._session_paths_checked = True
for path in self.session._initialpaths:
for initial_path in self.session._initialpaths:
# Make something as c:/projects/my_project/path.py ->
# ['c:', 'projects', 'my_project', 'path.py']
parts = str(path).split(os.path.sep)
parts = str(initial_path).split(os.path.sep)
# add 'path' to basenames to be checked.
self._basenames_to_check_rewrite.add(os.path.splitext(parts[-1])[0])

Expand Down Expand Up @@ -213,7 +215,7 @@ def _should_rewrite(self, name: str, fn: str, state: "AssertionState") -> bool:
return True

if self.session is not None:
if self.session.isinitpath(fn):
if self.session.isinitpath(py.path.local(fn)):
state.trace(
"matched test file (was specified on cmdline): {!r}".format(fn)
)
Expand Down
5 changes: 2 additions & 3 deletions src/_pytest/cacheprovider.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,8 +464,7 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:

@pytest.hookimpl(tryfirst=True)
def pytest_configure(config: Config) -> None:
# Type ignored: pending mechanism to store typed objects scoped to config.
config.cache = Cache.for_config(config) # type: ignore # noqa: F821
config.cache = Cache.for_config(config)
config.pluginmanager.register(LFPlugin(config), "lfplugin")
config.pluginmanager.register(NFPlugin(config), "nfplugin")

Expand Down Expand Up @@ -496,7 +495,7 @@ def pytest_report_header(config: Config) -> Optional[str]:
# starting with .., ../.. if sensible

try:
displaypath = cachedir.relative_to(config.rootdir)
displaypath = cachedir.relative_to(str(config.rootdir))
except ValueError:
displaypath = cachedir
return "cachedir: {}".format(displaypath)
Expand Down
12 changes: 5 additions & 7 deletions src/_pytest/capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -519,11 +519,10 @@ def start_capturing(self) -> None:
def pop_outerr_to_orig(self):
""" pop current snapshot out/err capture and flush to orig streams. """
out, err = self.readouterr()
# TODO: Fix type ignores.
if out:
self.out.writeorg(out) # type: ignore[union-attr] # noqa: F821
self.out.writeorg(out)
if err:
self.err.writeorg(err) # type: ignore[union-attr] # noqa: F821
self.err.writeorg(err)
return out, err

def suspend_capturing(self, in_: bool = False) -> None:
Expand All @@ -543,8 +542,7 @@ def resume_capturing(self) -> None:
if self.err:
self.err.resume()
if self._in_suspended:
# TODO: Fix type ignore.
self.in_.resume() # type: ignore[union-attr] # noqa: F821
self.in_.resume()
self._in_suspended = False

def stop_capturing(self) -> None:
Expand Down Expand Up @@ -751,11 +749,11 @@ def pytest_runtest_teardown(self, item: Item) -> Generator[None, None, None]:
yield

@pytest.hookimpl(tryfirst=True)
def pytest_keyboard_interrupt(self, excinfo):
def pytest_keyboard_interrupt(self) -> None:
self.stop_global_capturing()

@pytest.hookimpl(tryfirst=True)
def pytest_internalerror(self, excinfo):
def pytest_internalerror(self) -> None:
self.stop_global_capturing()


Expand Down
28 changes: 18 additions & 10 deletions src/_pytest/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
if TYPE_CHECKING:
from typing import Type

from _pytest._code.code import _TracebackStyle
from .argparsing import Argument


Expand Down Expand Up @@ -307,10 +308,9 @@ def __init__(self) -> None:
self._dirpath2confmods = {} # type: Dict[Any, List[object]]
# Maps a py.path.local to a module object.
self._conftestpath2mod = {} # type: Dict[Any, object]
self._confcutdir = None
self._confcutdir = None # type: Optional[py.path.local]
self._noconftest = False
# Set of py.path.local's.
self._duplicatepaths = set() # type: Set[Any]
self._duplicatepaths = set() # type: Set[py.path.local]

self.add_hookspecs(_pytest.hookspec)
self.register(self)
Expand Down Expand Up @@ -893,9 +893,13 @@ def pytest_cmdline_parse(

return self

def notify_exception(self, excinfo, option=None):
def notify_exception(
self,
excinfo: ExceptionInfo[BaseException],
option: Optional[argparse.Namespace] = None,
) -> None:
if option and getattr(option, "fulltrace", False):
style = "long"
style = "long" # type: _TracebackStyle
else:
style = "native"
excrepr = excinfo.getrepr(
Expand Down Expand Up @@ -940,13 +944,12 @@ def _initini(self, args: Sequence[str]) -> None:
ns, unknown_args = self._parser.parse_known_and_unknown_args(
args, namespace=copy.copy(self.option)
)
r = determine_setup(
self.rootdir, self.inifile, self.inicfg = determine_setup(
ns.inifilename,
ns.file_or_dir + unknown_args,
rootdir_cmd_arg=ns.rootdir or None,
config=self,
)
self.rootdir, self.inifile, self.inicfg = r
self._parser.extra_info["rootdir"] = self.rootdir
self._parser.extra_info["inifile"] = self.inifile
self._parser.addini("addopts", "extra command line options", "args")
Expand Down Expand Up @@ -988,9 +991,7 @@ def _mark_plugins_for_rewrite(self, hook) -> None:
package_files = (
str(file)
for dist in importlib_metadata.distributions()
# Type ignored due to missing stub:
# https://github.com/python/typeshed/pull/3795
if any(ep.group == "pytest11" for ep in dist.entry_points) # type: ignore
if any(ep.group == "pytest11" for ep in dist.entry_points)
for file in dist.files or []
)

Expand Down Expand Up @@ -1066,6 +1067,11 @@ def _checkversion(self):
# Imported lazily to improve start-up time.
from packaging.version import Version

if not isinstance(minver, str):
raise pytest.UsageError(
"%s: 'minversion' must be a single value" % self.inifile
)

if Version(minver) > Version(pytest.__version__):
raise pytest.UsageError(
"%s: 'minversion' requires pytest-%s, actual pytest-%s'"
Expand Down Expand Up @@ -1159,6 +1165,8 @@ def _getini(self, name: str) -> Any:
# in this case, we already have a list ready to use
#
if type == "pathlist":
# TODO: This assert is probably not valid in all cases.
assert self.inifile is not None
dp = py.path.local(self.inifile).dirpath()
input_values = shlex.split(value) if isinstance(value, str) else value
return [dp.join(x, abs=True) for x in input_values]
Expand Down
15 changes: 9 additions & 6 deletions src/_pytest/config/findpaths.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def load_config_dict_from_file(
elif filepath.ext == ".toml":
import toml

config = toml.load(filepath)
config = toml.load(str(filepath))

result = config.get("tool", {}).get("pytest", {}).get("ini_options", None)
if result is not None:
Expand Down Expand Up @@ -161,24 +161,26 @@ def determine_setup(
args: List[str],
rootdir_cmd_arg: Optional[str] = None,
config: Optional["Config"] = None,
) -> Tuple[py.path.local, Optional[str], Dict[str, Union[str, List[str]]]]:
) -> Tuple[py.path.local, Optional[py.path.local], Dict[str, Union[str, List[str]]]]:
rootdir = None
dirs = get_dirs_from_args(args)
if inifile:
inicfg = load_config_dict_from_file(py.path.local(inifile)) or {}
inipath_ = py.path.local(inifile)
inipath = inipath_ # type: Optional[py.path.local]
inicfg = load_config_dict_from_file(inipath_) or {}
if rootdir_cmd_arg is None:
rootdir = get_common_ancestor(dirs)
else:
ancestor = get_common_ancestor(dirs)
rootdir, inifile, inicfg = locate_config([ancestor])
rootdir, inipath, inicfg = locate_config([ancestor])
if rootdir is None and rootdir_cmd_arg is None:
for possible_rootdir in ancestor.parts(reverse=True):
if possible_rootdir.join("setup.py").exists():
rootdir = possible_rootdir
break
else:
if dirs != [ancestor]:
rootdir, inifile, inicfg = locate_config(dirs)
rootdir, inipath, inicfg = locate_config(dirs)
if rootdir is None:
if config is not None:
cwd = config.invocation_dir
Expand All @@ -196,4 +198,5 @@ def determine_setup(
rootdir
)
)
return rootdir, inifile, inicfg or {}
assert rootdir is not None
return rootdir, inipath, inicfg or {}
14 changes: 10 additions & 4 deletions src/_pytest/debugging.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
import argparse
import functools
import sys
import types
from typing import Generator
from typing import Tuple
from typing import Union

from _pytest import outcomes
from _pytest._code import ExceptionInfo
from _pytest.compat import TYPE_CHECKING
from _pytest.config import Config
from _pytest.config import ConftestImportFailure
Expand Down Expand Up @@ -280,9 +282,10 @@ def pytest_exception_interact(
out, err = capman.read_global_capture()
sys.stdout.write(out)
sys.stdout.write(err)
assert call.excinfo is not None
_enter_pdb(node, call.excinfo, report)

def pytest_internalerror(self, excrepr, excinfo) -> None:
def pytest_internalerror(self, excinfo: ExceptionInfo[BaseException]) -> None:
tb = _postmortem_traceback(excinfo)
post_mortem(tb)

Expand Down Expand Up @@ -320,7 +323,9 @@ def maybe_wrap_pytest_function_for_tracing(pyfuncitem):
wrap_pytest_function_for_tracing(pyfuncitem)


def _enter_pdb(node: Node, excinfo, rep: BaseReport) -> BaseReport:
def _enter_pdb(
node: Node, excinfo: ExceptionInfo[BaseException], rep: BaseReport
) -> BaseReport:
# XXX we re-use the TerminalReporter's terminalwriter
# because this seems to avoid some encoding related troubles
# for not completely clear reasons.
Expand Down Expand Up @@ -349,7 +354,7 @@ def _enter_pdb(node: Node, excinfo, rep: BaseReport) -> BaseReport:
return rep


def _postmortem_traceback(excinfo):
def _postmortem_traceback(excinfo: ExceptionInfo[BaseException]) -> types.TracebackType:
from doctest import UnexpectedException

if isinstance(excinfo.value, UnexpectedException):
Expand All @@ -361,10 +366,11 @@ def _postmortem_traceback(excinfo):
# Use the underlying exception instead:
return excinfo.value.excinfo[2]
else:
assert excinfo._excinfo is not None
return excinfo._excinfo[2]


def post_mortem(t) -> None:
def post_mortem(t: types.TracebackType) -> None:
p = pytestPDB._init_pdb("post_mortem")
p.reset()
p.interaction(None, t)
Expand Down
17 changes: 14 additions & 3 deletions src/_pytest/hookspec.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import warnings
from typing_extensions import Literal

from _pytest._code.code import ExceptionRepr
from _pytest.code import ExceptionInfo
from _pytest.config import Config
from _pytest.config import ExitCode
from _pytest.config import PytestPluginManager
Expand All @@ -30,6 +32,7 @@
from _pytest.nodes import Collector
from _pytest.nodes import Item
from _pytest.nodes import Node
from _pytest.outcomes import Exit
from _pytest.python import Function
from _pytest.python import Metafunc
from _pytest.python import Module
Expand Down Expand Up @@ -757,11 +760,19 @@ def pytest_doctest_prepare_content(content):
# -------------------------------------------------------------------------


def pytest_internalerror(excrepr, excinfo):
""" called for internal errors. """
def pytest_internalerror(
excrepr: "ExceptionRepr", excinfo: "ExceptionInfo[BaseException]",
) -> Optional[bool]:
"""Called for internal errors.

Return True to suppress the fallback handling of printing an
INTERNALERROR message directly to sys.stderr.
"""


def pytest_keyboard_interrupt(excinfo):
def pytest_keyboard_interrupt(
excinfo: "ExceptionInfo[Union[KeyboardInterrupt, Exit]]",
) -> None:
""" called for keyboard interrupt. """


Expand Down
3 changes: 2 additions & 1 deletion src/_pytest/junitxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from _pytest import deprecated
from _pytest import nodes
from _pytest import timing
from _pytest._code.code import ExceptionRepr
from _pytest.compat import TYPE_CHECKING
from _pytest.config import Config
from _pytest.config import filename_arg
Expand Down Expand Up @@ -642,7 +643,7 @@ def pytest_collectreport(self, report: TestReport) -> None:
else:
reporter.append_collect_skipped(report)

def pytest_internalerror(self, excrepr) -> None:
def pytest_internalerror(self, excrepr: ExceptionRepr) -> None:
reporter = self.node_reporter("internal")
reporter.attrs.update(classname="pytest", name="internal")
reporter._add_simple(Junit.error, "internal error", excrepr)
Expand Down
2 changes: 1 addition & 1 deletion src/_pytest/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@ def set_log_path(self, fname: str) -> None:
fpath = Path(fname)

if not fpath.is_absolute():
fpath = Path(self._config.rootdir, fpath)
fpath = Path(str(self._config.rootdir), fpath)

if not fpath.parent.exists():
fpath.parent.mkdir(exist_ok=True, parents=True)
Expand Down
8 changes: 4 additions & 4 deletions src/_pytest/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ def __init__(self, config: Config) -> None:
) # type: Dict[Tuple[Type[nodes.Collector], str], CollectReport]

# Dirnames of pkgs with dunder-init files.
self._collection_pkg_roots = {} # type: Dict[py.path.local, Package]
self._collection_pkg_roots = {} # type: Dict[str, Package]

self._bestrelpathcache = _bestrelpath_cache(
config.rootdir
Expand Down Expand Up @@ -601,7 +601,7 @@ def _collect(
col = self._collectfile(pkginit, handle_dupes=False)
if col:
if isinstance(col[0], Package):
self._collection_pkg_roots[parent] = col[0]
self._collection_pkg_roots[str(parent)] = col[0]
# always store a list in the cache, matchnodes expects it
self._collection_node_cache1[col[0].fspath] = [col[0]]

Expand All @@ -623,8 +623,8 @@ def _collect(
for x in self._collectfile(pkginit):
yield x
if isinstance(x, Package):
self._collection_pkg_roots[dirpath] = x
if dirpath in self._collection_pkg_roots:
self._collection_pkg_roots[str(dirpath)] = x
if str(dirpath) in self._collection_pkg_roots:
# Do not collect packages here.
continue

Expand Down
4 changes: 1 addition & 3 deletions src/_pytest/mark/structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,7 @@ def get_empty_parameterset_mark(
fs,
lineno,
)
# Type ignored because MarkDecorator.__call__() is a bit tough to
# annotate ATM.
return mark(reason=reason) # type: ignore[no-any-return] # noqa: F723
return mark(reason=reason)


class ParameterSet(
Expand Down
Loading