Skip to content

Commit 010ce2a

Browse files
authored
Add typing to from_parent return values (#11916)
Up to now the return values of `from_parent` were untyped, this is an attempt to make it work with `typing.Self`.
1 parent 1640f2e commit 010ce2a

File tree

6 files changed

+41
-33
lines changed

6 files changed

+41
-33
lines changed

src/_pytest/doctest.py

+5-6
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747

4848
if TYPE_CHECKING:
4949
import doctest
50+
from typing import Self
5051

5152
DOCTEST_REPORT_CHOICE_NONE = "none"
5253
DOCTEST_REPORT_CHOICE_CDIFF = "cdiff"
@@ -133,11 +134,9 @@ def pytest_collect_file(
133134
if config.option.doctestmodules and not any(
134135
(_is_setup_py(file_path), _is_main_py(file_path))
135136
):
136-
mod: DoctestModule = DoctestModule.from_parent(parent, path=file_path)
137-
return mod
137+
return DoctestModule.from_parent(parent, path=file_path)
138138
elif _is_doctest(config, file_path, parent):
139-
txt: DoctestTextfile = DoctestTextfile.from_parent(parent, path=file_path)
140-
return txt
139+
return DoctestTextfile.from_parent(parent, path=file_path)
141140
return None
142141

143142

@@ -272,14 +271,14 @@ def __init__(
272271
self._initrequest()
273272

274273
@classmethod
275-
def from_parent( # type: ignore
274+
def from_parent( # type: ignore[override]
276275
cls,
277276
parent: "Union[DoctestTextfile, DoctestModule]",
278277
*,
279278
name: str,
280279
runner: "doctest.DocTestRunner",
281280
dtest: "doctest.DocTest",
282-
):
281+
) -> "Self":
283282
# incompatible signature due to imposed limits on subclass
284283
"""The public named constructor."""
285284
return super().from_parent(name=name, parent=parent, runner=runner, dtest=dtest)

src/_pytest/main.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from typing import overload
2222
from typing import Sequence
2323
from typing import Tuple
24+
from typing import TYPE_CHECKING
2425
from typing import Union
2526
import warnings
2627

@@ -49,6 +50,10 @@
4950
from _pytest.warning_types import PytestWarning
5051

5152

53+
if TYPE_CHECKING:
54+
from typing import Self
55+
56+
5257
def pytest_addoption(parser: Parser) -> None:
5358
parser.addini(
5459
"norecursedirs",
@@ -491,16 +496,16 @@ class Dir(nodes.Directory):
491496
@classmethod
492497
def from_parent( # type: ignore[override]
493498
cls,
494-
parent: nodes.Collector, # type: ignore[override]
499+
parent: nodes.Collector,
495500
*,
496501
path: Path,
497-
) -> "Dir":
502+
) -> "Self":
498503
"""The public constructor.
499504
500505
:param parent: The parent collector of this Dir.
501506
:param path: The directory's path.
502507
"""
503-
return super().from_parent(parent=parent, path=path) # type: ignore[no-any-return]
508+
return super().from_parent(parent=parent, path=path)
504509

505510
def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
506511
config = self.config

src/_pytest/nodes.py

+13-9
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from typing import Iterator
1212
from typing import List
1313
from typing import MutableMapping
14+
from typing import NoReturn
1415
from typing import Optional
1516
from typing import overload
1617
from typing import Set
@@ -41,6 +42,8 @@
4142

4243

4344
if TYPE_CHECKING:
45+
from typing import Self
46+
4447
# Imported here due to circular import.
4548
from _pytest._code.code import _TracebackStyle
4649
from _pytest.main import Session
@@ -51,6 +54,7 @@
5154
tracebackcutdir = Path(_pytest.__file__).parent
5255

5356

57+
_T = TypeVar("_T")
5458
_NodeType = TypeVar("_NodeType", bound="Node")
5559

5660

@@ -69,33 +73,33 @@ class NodeMeta(abc.ABCMeta):
6973
progress on detangling the :class:`Node` classes.
7074
"""
7175

72-
def __call__(self, *k, **kw):
76+
def __call__(cls, *k, **kw) -> NoReturn:
7377
msg = (
7478
"Direct construction of {name} has been deprecated, please use {name}.from_parent.\n"
7579
"See "
7680
"https://docs.pytest.org/en/stable/deprecations.html#node-construction-changed-to-node-from-parent"
7781
" for more details."
78-
).format(name=f"{self.__module__}.{self.__name__}")
82+
).format(name=f"{cls.__module__}.{cls.__name__}")
7983
fail(msg, pytrace=False)
8084

81-
def _create(self, *k, **kw):
85+
def _create(cls: Type[_T], *k, **kw) -> _T:
8286
try:
83-
return super().__call__(*k, **kw)
87+
return super().__call__(*k, **kw) # type: ignore[no-any-return,misc]
8488
except TypeError:
85-
sig = signature(getattr(self, "__init__"))
89+
sig = signature(getattr(cls, "__init__"))
8690
known_kw = {k: v for k, v in kw.items() if k in sig.parameters}
8791
from .warning_types import PytestDeprecationWarning
8892

8993
warnings.warn(
9094
PytestDeprecationWarning(
91-
f"{self} is not using a cooperative constructor and only takes {set(known_kw)}.\n"
95+
f"{cls} is not using a cooperative constructor and only takes {set(known_kw)}.\n"
9296
"See https://docs.pytest.org/en/stable/deprecations.html"
9397
"#constructors-of-custom-pytest-node-subclasses-should-take-kwargs "
9498
"for more details."
9599
)
96100
)
97101

98-
return super().__call__(*k, **known_kw)
102+
return super().__call__(*k, **known_kw) # type: ignore[no-any-return,misc]
99103

100104

101105
class Node(abc.ABC, metaclass=NodeMeta):
@@ -181,7 +185,7 @@ def __init__(
181185
self._store = self.stash
182186

183187
@classmethod
184-
def from_parent(cls, parent: "Node", **kw):
188+
def from_parent(cls, parent: "Node", **kw) -> "Self":
185189
"""Public constructor for Nodes.
186190
187191
This indirection got introduced in order to enable removing
@@ -583,7 +587,7 @@ def from_parent(
583587
*,
584588
path: Optional[Path] = None,
585589
**kw,
586-
):
590+
) -> "Self":
587591
"""The public constructor."""
588592
return super().from_parent(parent=parent, path=path, **kw)
589593

src/_pytest/python.py

+13-12
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from typing import Sequence
2828
from typing import Set
2929
from typing import Tuple
30+
from typing import TYPE_CHECKING
3031
from typing import Union
3132
import warnings
3233

@@ -81,6 +82,10 @@
8182
from _pytest.warning_types import PytestUnhandledCoroutineWarning
8283

8384

85+
if TYPE_CHECKING:
86+
from typing import Self
87+
88+
8489
_PYTEST_DIR = Path(_pytest.__file__).parent
8590

8691

@@ -204,8 +209,7 @@ def pytest_collect_directory(
204209
) -> Optional[nodes.Collector]:
205210
pkginit = path / "__init__.py"
206211
if pkginit.is_file():
207-
pkg: Package = Package.from_parent(parent, path=path)
208-
return pkg
212+
return Package.from_parent(parent, path=path)
209213
return None
210214

211215

@@ -230,8 +234,7 @@ def path_matches_patterns(path: Path, patterns: Iterable[str]) -> bool:
230234

231235

232236
def pytest_pycollect_makemodule(module_path: Path, parent) -> "Module":
233-
mod: Module = Module.from_parent(parent, path=module_path)
234-
return mod
237+
return Module.from_parent(parent, path=module_path)
235238

236239

237240
@hookimpl(trylast=True)
@@ -242,8 +245,7 @@ def pytest_pycollect_makeitem(
242245
# Nothing was collected elsewhere, let's do it here.
243246
if safe_isclass(obj):
244247
if collector.istestclass(obj, name):
245-
klass: Class = Class.from_parent(collector, name=name, obj=obj)
246-
return klass
248+
return Class.from_parent(collector, name=name, obj=obj)
247249
elif collector.istestfunction(obj, name):
248250
# mock seems to store unbound methods (issue473), normalize it.
249251
obj = getattr(obj, "__func__", obj)
@@ -262,7 +264,7 @@ def pytest_pycollect_makeitem(
262264
)
263265
elif getattr(obj, "__test__", True):
264266
if is_generator(obj):
265-
res: Function = Function.from_parent(collector, name=name)
267+
res = Function.from_parent(collector, name=name)
266268
reason = (
267269
f"yield tests were removed in pytest 4.0 - {name} will be ignored"
268270
)
@@ -465,9 +467,7 @@ def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]:
465467
clscol = self.getparent(Class)
466468
cls = clscol and clscol.obj or None
467469

468-
definition: FunctionDefinition = FunctionDefinition.from_parent(
469-
self, name=name, callobj=funcobj
470-
)
470+
definition = FunctionDefinition.from_parent(self, name=name, callobj=funcobj)
471471
fixtureinfo = definition._fixtureinfo
472472

473473
# pytest_generate_tests impls call metafunc.parametrize() which fills
@@ -751,7 +751,7 @@ class Class(PyCollector):
751751
"""Collector for test methods (and nested classes) in a Python class."""
752752

753753
@classmethod
754-
def from_parent(cls, parent, *, name, obj=None, **kw):
754+
def from_parent(cls, parent, *, name, obj=None, **kw) -> "Self": # type: ignore[override]
755755
"""The public constructor."""
756756
return super().from_parent(name=name, parent=parent, **kw)
757757

@@ -1730,8 +1730,9 @@ def __init__(
17301730
self.fixturenames = fixtureinfo.names_closure
17311731
self._initrequest()
17321732

1733+
# todo: determine sound type limitations
17331734
@classmethod
1734-
def from_parent(cls, parent, **kw): # todo: determine sound type limitations
1735+
def from_parent(cls, parent, **kw) -> "Self":
17351736
"""The public constructor."""
17361737
return super().from_parent(parent=parent, **kw)
17371738

src/_pytest/unittest.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,7 @@ def pytest_pycollect_makeitem(
5555
except Exception:
5656
return None
5757
# Yes, so let's collect it.
58-
item: UnitTestCase = UnitTestCase.from_parent(collector, name=name, obj=obj)
59-
return item
58+
return UnitTestCase.from_parent(collector, name=name, obj=obj)
6059

6160

6261
class UnitTestCase(Class):

testing/test_collection.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1613,7 +1613,7 @@ def collect(self):
16131613
assert collector.x == 10
16141614

16151615

1616-
def test_class_from_parent(pytester: Pytester, request: FixtureRequest) -> None:
1616+
def test_class_from_parent(request: FixtureRequest) -> None:
16171617
"""Ensure Class.from_parent can forward custom arguments to the constructor."""
16181618

16191619
class MyCollector(pytest.Class):

0 commit comments

Comments
 (0)