Skip to content

Commit f551cab

Browse files
authored
Merge pull request #7358 from bluetech/typing2
More type annotations, fix some typing bugs
2 parents aaa6f1c + a5ab7c1 commit f551cab

21 files changed

+216
-126
lines changed

src/_pytest/_code/code.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -928,8 +928,13 @@ def toterminal(self, tw: TerminalWriter) -> None:
928928
raise NotImplementedError()
929929

930930

931+
# This class is abstract -- only subclasses are instantiated.
931932
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
932933
class ExceptionRepr(TerminalRepr):
934+
# Provided by in subclasses.
935+
reprcrash = None # type: Optional[ReprFileLocation]
936+
reprtraceback = None # type: ReprTraceback
937+
933938
def __attrs_post_init__(self):
934939
self.sections = [] # type: List[Tuple[str, str, str]]
935940

src/_pytest/assertion/rewrite.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
from typing import Tuple
2424
from typing import Union
2525

26+
import py
27+
2628
from _pytest._io.saferepr import saferepr
2729
from _pytest._version import version
2830
from _pytest.assertion import util
@@ -177,10 +179,10 @@ def _early_rewrite_bailout(self, name: str, state: "AssertionState") -> bool:
177179
"""
178180
if self.session is not None and not self._session_paths_checked:
179181
self._session_paths_checked = True
180-
for path in self.session._initialpaths:
182+
for initial_path in self.session._initialpaths:
181183
# Make something as c:/projects/my_project/path.py ->
182184
# ['c:', 'projects', 'my_project', 'path.py']
183-
parts = str(path).split(os.path.sep)
185+
parts = str(initial_path).split(os.path.sep)
184186
# add 'path' to basenames to be checked.
185187
self._basenames_to_check_rewrite.add(os.path.splitext(parts[-1])[0])
186188

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

215217
if self.session is not None:
216-
if self.session.isinitpath(fn):
218+
if self.session.isinitpath(py.path.local(fn)):
217219
state.trace(
218220
"matched test file (was specified on cmdline): {!r}".format(fn)
219221
)

src/_pytest/cacheprovider.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -464,8 +464,7 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
464464

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

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

498497
try:
499-
displaypath = cachedir.relative_to(config.rootdir)
498+
displaypath = cachedir.relative_to(str(config.rootdir))
500499
except ValueError:
501500
displaypath = cachedir
502501
return "cachedir: {}".format(displaypath)

src/_pytest/capture.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -519,11 +519,10 @@ def start_capturing(self) -> None:
519519
def pop_outerr_to_orig(self):
520520
""" pop current snapshot out/err capture and flush to orig streams. """
521521
out, err = self.readouterr()
522-
# TODO: Fix type ignores.
523522
if out:
524-
self.out.writeorg(out) # type: ignore[union-attr] # noqa: F821
523+
self.out.writeorg(out)
525524
if err:
526-
self.err.writeorg(err) # type: ignore[union-attr] # noqa: F821
525+
self.err.writeorg(err)
527526
return out, err
528527

529528
def suspend_capturing(self, in_: bool = False) -> None:
@@ -543,8 +542,7 @@ def resume_capturing(self) -> None:
543542
if self.err:
544543
self.err.resume()
545544
if self._in_suspended:
546-
# TODO: Fix type ignore.
547-
self.in_.resume() # type: ignore[union-attr] # noqa: F821
545+
self.in_.resume()
548546
self._in_suspended = False
549547

550548
def stop_capturing(self) -> None:
@@ -751,11 +749,11 @@ def pytest_runtest_teardown(self, item: Item) -> Generator[None, None, None]:
751749
yield
752750

753751
@pytest.hookimpl(tryfirst=True)
754-
def pytest_keyboard_interrupt(self, excinfo):
752+
def pytest_keyboard_interrupt(self) -> None:
755753
self.stop_global_capturing()
756754

757755
@pytest.hookimpl(tryfirst=True)
758-
def pytest_internalerror(self, excinfo):
756+
def pytest_internalerror(self) -> None:
759757
self.stop_global_capturing()
760758

761759

src/_pytest/config/__init__.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
if TYPE_CHECKING:
4949
from typing import Type
5050

51+
from _pytest._code.code import _TracebackStyle
5152
from .argparsing import Argument
5253

5354

@@ -307,10 +308,9 @@ def __init__(self) -> None:
307308
self._dirpath2confmods = {} # type: Dict[Any, List[object]]
308309
# Maps a py.path.local to a module object.
309310
self._conftestpath2mod = {} # type: Dict[Any, object]
310-
self._confcutdir = None
311+
self._confcutdir = None # type: Optional[py.path.local]
311312
self._noconftest = False
312-
# Set of py.path.local's.
313-
self._duplicatepaths = set() # type: Set[Any]
313+
self._duplicatepaths = set() # type: Set[py.path.local]
314314

315315
self.add_hookspecs(_pytest.hookspec)
316316
self.register(self)
@@ -893,9 +893,13 @@ def pytest_cmdline_parse(
893893

894894
return self
895895

896-
def notify_exception(self, excinfo, option=None):
896+
def notify_exception(
897+
self,
898+
excinfo: ExceptionInfo[BaseException],
899+
option: Optional[argparse.Namespace] = None,
900+
) -> None:
897901
if option and getattr(option, "fulltrace", False):
898-
style = "long"
902+
style = "long" # type: _TracebackStyle
899903
else:
900904
style = "native"
901905
excrepr = excinfo.getrepr(
@@ -940,13 +944,12 @@ def _initini(self, args: Sequence[str]) -> None:
940944
ns, unknown_args = self._parser.parse_known_and_unknown_args(
941945
args, namespace=copy.copy(self.option)
942946
)
943-
r = determine_setup(
947+
self.rootdir, self.inifile, self.inicfg = determine_setup(
944948
ns.inifilename,
945949
ns.file_or_dir + unknown_args,
946950
rootdir_cmd_arg=ns.rootdir or None,
947951
config=self,
948952
)
949-
self.rootdir, self.inifile, self.inicfg = r
950953
self._parser.extra_info["rootdir"] = self.rootdir
951954
self._parser.extra_info["inifile"] = self.inifile
952955
self._parser.addini("addopts", "extra command line options", "args")
@@ -994,9 +997,7 @@ def _mark_plugins_for_rewrite(self, hook) -> None:
994997
package_files = (
995998
str(file)
996999
for dist in importlib_metadata.distributions()
997-
# Type ignored due to missing stub:
998-
# https://github.com/python/typeshed/pull/3795
999-
if any(ep.group == "pytest11" for ep in dist.entry_points) # type: ignore
1000+
if any(ep.group == "pytest11" for ep in dist.entry_points)
10001001
for file in dist.files or []
10011002
)
10021003

@@ -1073,6 +1074,11 @@ def _checkversion(self):
10731074
# Imported lazily to improve start-up time.
10741075
from packaging.version import Version
10751076

1077+
if not isinstance(minver, str):
1078+
raise pytest.UsageError(
1079+
"%s: 'minversion' must be a single value" % self.inifile
1080+
)
1081+
10761082
if Version(minver) > Version(pytest.__version__):
10771083
raise pytest.UsageError(
10781084
"%s: 'minversion' requires pytest-%s, actual pytest-%s'"
@@ -1187,6 +1193,8 @@ def _getini(self, name: str) -> Any:
11871193
# in this case, we already have a list ready to use
11881194
#
11891195
if type == "pathlist":
1196+
# TODO: This assert is probably not valid in all cases.
1197+
assert self.inifile is not None
11901198
dp = py.path.local(self.inifile).dirpath()
11911199
input_values = shlex.split(value) if isinstance(value, str) else value
11921200
return [dp.join(x, abs=True) for x in input_values]

src/_pytest/config/findpaths.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def load_config_dict_from_file(
6363
elif filepath.ext == ".toml":
6464
import toml
6565

66-
config = toml.load(filepath)
66+
config = toml.load(str(filepath))
6767

6868
result = config.get("tool", {}).get("pytest", {}).get("ini_options", None)
6969
if result is not None:
@@ -161,24 +161,26 @@ def determine_setup(
161161
args: List[str],
162162
rootdir_cmd_arg: Optional[str] = None,
163163
config: Optional["Config"] = None,
164-
) -> Tuple[py.path.local, Optional[str], Dict[str, Union[str, List[str]]]]:
164+
) -> Tuple[py.path.local, Optional[py.path.local], Dict[str, Union[str, List[str]]]]:
165165
rootdir = None
166166
dirs = get_dirs_from_args(args)
167167
if inifile:
168-
inicfg = load_config_dict_from_file(py.path.local(inifile)) or {}
168+
inipath_ = py.path.local(inifile)
169+
inipath = inipath_ # type: Optional[py.path.local]
170+
inicfg = load_config_dict_from_file(inipath_) or {}
169171
if rootdir_cmd_arg is None:
170172
rootdir = get_common_ancestor(dirs)
171173
else:
172174
ancestor = get_common_ancestor(dirs)
173-
rootdir, inifile, inicfg = locate_config([ancestor])
175+
rootdir, inipath, inicfg = locate_config([ancestor])
174176
if rootdir is None and rootdir_cmd_arg is None:
175177
for possible_rootdir in ancestor.parts(reverse=True):
176178
if possible_rootdir.join("setup.py").exists():
177179
rootdir = possible_rootdir
178180
break
179181
else:
180182
if dirs != [ancestor]:
181-
rootdir, inifile, inicfg = locate_config(dirs)
183+
rootdir, inipath, inicfg = locate_config(dirs)
182184
if rootdir is None:
183185
if config is not None:
184186
cwd = config.invocation_dir
@@ -196,4 +198,5 @@ def determine_setup(
196198
rootdir
197199
)
198200
)
199-
return rootdir, inifile, inicfg or {}
201+
assert rootdir is not None
202+
return rootdir, inipath, inicfg or {}

src/_pytest/debugging.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
import argparse
33
import functools
44
import sys
5+
import types
56
from typing import Generator
67
from typing import Tuple
78
from typing import Union
89

910
from _pytest import outcomes
11+
from _pytest._code import ExceptionInfo
1012
from _pytest.compat import TYPE_CHECKING
1113
from _pytest.config import Config
1214
from _pytest.config import ConftestImportFailure
@@ -280,9 +282,10 @@ def pytest_exception_interact(
280282
out, err = capman.read_global_capture()
281283
sys.stdout.write(out)
282284
sys.stdout.write(err)
285+
assert call.excinfo is not None
283286
_enter_pdb(node, call.excinfo, report)
284287

285-
def pytest_internalerror(self, excrepr, excinfo) -> None:
288+
def pytest_internalerror(self, excinfo: ExceptionInfo[BaseException]) -> None:
286289
tb = _postmortem_traceback(excinfo)
287290
post_mortem(tb)
288291

@@ -320,7 +323,9 @@ def maybe_wrap_pytest_function_for_tracing(pyfuncitem):
320323
wrap_pytest_function_for_tracing(pyfuncitem)
321324

322325

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

351356

352-
def _postmortem_traceback(excinfo):
357+
def _postmortem_traceback(excinfo: ExceptionInfo[BaseException]) -> types.TracebackType:
353358
from doctest import UnexpectedException
354359

355360
if isinstance(excinfo.value, UnexpectedException):
@@ -361,10 +366,11 @@ def _postmortem_traceback(excinfo):
361366
# Use the underlying exception instead:
362367
return excinfo.value.excinfo[2]
363368
else:
369+
assert excinfo._excinfo is not None
364370
return excinfo._excinfo[2]
365371

366372

367-
def post_mortem(t) -> None:
373+
def post_mortem(t: types.TracebackType) -> None:
368374
p = pytestPDB._init_pdb("post_mortem")
369375
p.reset()
370376
p.interaction(None, t)

src/_pytest/hookspec.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import warnings
2020
from typing_extensions import Literal
2121

22+
from _pytest._code.code import ExceptionRepr
23+
from _pytest.code import ExceptionInfo
2224
from _pytest.config import Config
2325
from _pytest.config import ExitCode
2426
from _pytest.config import PytestPluginManager
@@ -30,6 +32,7 @@
3032
from _pytest.nodes import Collector
3133
from _pytest.nodes import Item
3234
from _pytest.nodes import Node
35+
from _pytest.outcomes import Exit
3336
from _pytest.python import Function
3437
from _pytest.python import Metafunc
3538
from _pytest.python import Module
@@ -757,11 +760,19 @@ def pytest_doctest_prepare_content(content):
757760
# -------------------------------------------------------------------------
758761

759762

760-
def pytest_internalerror(excrepr, excinfo):
761-
""" called for internal errors. """
763+
def pytest_internalerror(
764+
excrepr: "ExceptionRepr", excinfo: "ExceptionInfo[BaseException]",
765+
) -> Optional[bool]:
766+
"""Called for internal errors.
767+
768+
Return True to suppress the fallback handling of printing an
769+
INTERNALERROR message directly to sys.stderr.
770+
"""
762771

763772

764-
def pytest_keyboard_interrupt(excinfo):
773+
def pytest_keyboard_interrupt(
774+
excinfo: "ExceptionInfo[Union[KeyboardInterrupt, Exit]]",
775+
) -> None:
765776
""" called for keyboard interrupt. """
766777

767778

src/_pytest/junitxml.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from _pytest import deprecated
2727
from _pytest import nodes
2828
from _pytest import timing
29+
from _pytest._code.code import ExceptionRepr
2930
from _pytest.compat import TYPE_CHECKING
3031
from _pytest.config import Config
3132
from _pytest.config import filename_arg
@@ -642,7 +643,7 @@ def pytest_collectreport(self, report: TestReport) -> None:
642643
else:
643644
reporter.append_collect_skipped(report)
644645

645-
def pytest_internalerror(self, excrepr) -> None:
646+
def pytest_internalerror(self, excrepr: ExceptionRepr) -> None:
646647
reporter = self.node_reporter("internal")
647648
reporter.attrs.update(classname="pytest", name="internal")
648649
reporter._add_simple(Junit.error, "internal error", excrepr)

src/_pytest/logging.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,7 @@ def set_log_path(self, fname: str) -> None:
586586
fpath = Path(fname)
587587

588588
if not fpath.is_absolute():
589-
fpath = Path(self._config.rootdir, fpath)
589+
fpath = Path(str(self._config.rootdir), fpath)
590590

591591
if not fpath.parent.exists():
592592
fpath.parent.mkdir(exist_ok=True, parents=True)

src/_pytest/main.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,7 @@ def __init__(self, config: Config) -> None:
439439
) # type: Dict[Tuple[Type[nodes.Collector], str], CollectReport]
440440

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

444444
self._bestrelpathcache = _bestrelpath_cache(
445445
config.rootdir
@@ -601,7 +601,7 @@ def _collect(
601601
col = self._collectfile(pkginit, handle_dupes=False)
602602
if col:
603603
if isinstance(col[0], Package):
604-
self._collection_pkg_roots[parent] = col[0]
604+
self._collection_pkg_roots[str(parent)] = col[0]
605605
# always store a list in the cache, matchnodes expects it
606606
self._collection_node_cache1[col[0].fspath] = [col[0]]
607607

@@ -623,8 +623,8 @@ def _collect(
623623
for x in self._collectfile(pkginit):
624624
yield x
625625
if isinstance(x, Package):
626-
self._collection_pkg_roots[dirpath] = x
627-
if dirpath in self._collection_pkg_roots:
626+
self._collection_pkg_roots[str(dirpath)] = x
627+
if str(dirpath) in self._collection_pkg_roots:
628628
# Do not collect packages here.
629629
continue
630630

src/_pytest/mark/structures.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,7 @@ def get_empty_parameterset_mark(
6666
fs,
6767
lineno,
6868
)
69-
# Type ignored because MarkDecorator.__call__() is a bit tough to
70-
# annotate ATM.
71-
return mark(reason=reason) # type: ignore[no-any-return] # noqa: F723
69+
return mark(reason=reason)
7270

7371

7472
class ParameterSet(

0 commit comments

Comments
 (0)