Skip to content
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
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@ exclude_lines =

^\s*if TYPE_CHECKING:
^\s*@overload( |$)
^\s*def .+: \.\.\.$

^\s*@pytest\.mark\.xfail
18 changes: 18 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,24 @@ repos:
# for mypy running on python>=3.11 since exceptiongroup is only a dependency
# on <3.11
- exceptiongroup>=1.0.0rc8
- repo: https://github.com/RobertCraigie/pyright-python
rev: v1.1.401
hooks:
- id: pyright
files: ^(src/|scripts/)
additional_dependencies:
- iniconfig>=1.1.0
- attrs>=19.2.0
- pluggy>=1.5.0
- packaging
- tomli
- types-setuptools
- types-tabulate
# for mypy running on python>=3.11 since exceptiongroup is only a dependency
# on <3.11
- exceptiongroup>=1.0.0rc8
# Manual because passing pyright is a work in progress.
stages: [manual]
- repo: https://github.com/tox-dev/pyproject-fmt
rev: "v2.6.0"
hooks:
Expand Down
14 changes: 14 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -528,3 +528,17 @@ warn_unreachable = true
warn_unused_configs = true
no_implicit_reexport = true
warn_unused_ignores = true

[tool.pyright]
include = [
"src",
"testing",
"scripts",
]
extraPaths = [
"src",
]
pythonVersion = "3.9"
typeCheckingMode = "basic"
reportMissingImports = "none"
reportMissingModuleSource = "none"
14 changes: 9 additions & 5 deletions src/_pytest/assertion/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from collections.abc import Generator
import sys
from typing import Any
from typing import Protocol
from typing import TYPE_CHECKING

from _pytest.assertion import rewrite
Expand Down Expand Up @@ -82,15 +83,18 @@ def register_assert_rewrite(*names: str) -> None:
if not isinstance(name, str):
msg = "expected module names as *args, got {0} instead" # type: ignore[unreachable]
raise TypeError(msg.format(repr(names)))
rewrite_hook: RewriteHook
for hook in sys.meta_path:
if isinstance(hook, rewrite.AssertionRewritingHook):
importhook = hook
rewrite_hook = hook
break
else:
# TODO(typing): Add a protocol for mark_rewrite() and use it
# for importhook and for PytestPluginManager.rewrite_hook.
importhook = DummyRewriteHook() # type: ignore
importhook.mark_rewrite(*names)
rewrite_hook = DummyRewriteHook()
rewrite_hook.mark_rewrite(*names)


class RewriteHook(Protocol):
def mark_rewrite(self, *names: str) -> None: ...


class DummyRewriteHook:
Expand Down
29 changes: 16 additions & 13 deletions src/_pytest/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@


if TYPE_CHECKING:
from _pytest.assertions.rewrite import AssertionRewritingHook
from _pytest.assertion.rewrite import AssertionRewritingHook
from _pytest.cacheprovider import Cache
from _pytest.terminal import TerminalReporter

Expand Down Expand Up @@ -273,9 +273,11 @@ def directory_arg(path: str, optname: str) -> str:
"faulthandler",
)

builtin_plugins = set(default_plugins)
builtin_plugins.add("pytester")
builtin_plugins.add("pytester_assertions")
builtin_plugins = {
*default_plugins,
"pytester",
"pytester_assertions",
}


def get_config(
Expand Down Expand Up @@ -397,7 +399,8 @@ class PytestPluginManager(PluginManager):
"""

def __init__(self) -> None:
import _pytest.assertion
from _pytest.assertion import DummyRewriteHook
from _pytest.assertion import RewriteHook

super().__init__("pytest")

Expand Down Expand Up @@ -443,7 +446,7 @@ def __init__(self) -> None:
self.enable_tracing()

# Config._consider_importhook will set a real object if required.
self.rewrite_hook = _pytest.assertion.DummyRewriteHook()
self.rewrite_hook: RewriteHook = DummyRewriteHook()
# Used to know when we are importing conftests after the pytest_configure stage.
self._configured = False

Expand All @@ -469,21 +472,21 @@ def parse_hookimpl_opts(
if not inspect.isroutine(method):
return None
# Collect unmarked hooks as long as they have the `pytest_' prefix.
return _get_legacy_hook_marks( # type: ignore[return-value]
legacy = _get_legacy_hook_marks(
method, "impl", ("tryfirst", "trylast", "optionalhook", "hookwrapper")
)
return cast(HookimplOpts, legacy)

def parse_hookspec_opts(self, module_or_class, name: str) -> HookspecOpts | None:
""":meta private:"""
opts = super().parse_hookspec_opts(module_or_class, name)
if opts is None:
method = getattr(module_or_class, name)
if name.startswith("pytest_"):
opts = _get_legacy_hook_marks( # type: ignore[assignment]
method,
"spec",
("firstresult", "historic"),
legacy = _get_legacy_hook_marks(
method, "spec", ("firstresult", "historic")
)
opts = cast(HookspecOpts, legacy)
return opts

def register(self, plugin: _PluggyPlugin, name: str | None = None) -> str | None:
Expand Down Expand Up @@ -1581,7 +1584,7 @@ def addinivalue_line(self, name: str, line: str) -> None:
assert isinstance(x, list)
x.append(line) # modifies the cached list inline

def getini(self, name: str):
def getini(self, name: str) -> Any:
"""Return configuration value from an :ref:`ini file <configfiles>`.

If a configuration value is not defined in an
Expand Down Expand Up @@ -1725,7 +1728,7 @@ def _get_override_ini_value(self, name: str) -> str | None:
value = user_ini_value
return value

def getoption(self, name: str, default=notset, skip: bool = False):
def getoption(self, name: str, default: Any = notset, skip: bool = False):
"""Return command line option value.

:param name: Name of the option. You may also specify
Expand Down
2 changes: 1 addition & 1 deletion src/_pytest/mark/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ def __init__(self, code: types.CodeType) -> None:
self.code = code

@classmethod
def compile(self, input: str) -> Expression:
def compile(cls, input: str) -> Expression:
"""Compile a match expression.

:param input: The input expression - one line.
Expand Down
3 changes: 2 additions & 1 deletion src/_pytest/pytester.py
Original file line number Diff line number Diff line change
Expand Up @@ -1116,7 +1116,8 @@ def inline_run(
rec = []

class Collect:
def pytest_configure(x, config: Config) -> None:
@staticmethod
def pytest_configure(config: Config) -> None:
rec.append(self.make_hook_recorder(config.pluginmanager))

plugins.append(Collect())
Expand Down
10 changes: 4 additions & 6 deletions src/_pytest/raises.py
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,7 @@ class RaisesExc(AbstractRaises[BaseExcT_co_default]):
# At least one of the three parameters must be passed.
@overload
def __init__(
self: RaisesExc[BaseExcT_co_default],
self,
expected_exception: (
type[BaseExcT_co_default] | tuple[type[BaseExcT_co_default], ...]
),
Expand Down Expand Up @@ -969,9 +969,7 @@ def __init__(
# that are *very* hard to reconcile while adhering to the overloads, so we cast
# it to avoid an error when passing it to super().__init__
check = cast(
"Callable[["
"BaseExceptionGroup[ExcT_1|BaseExcT_1|BaseExceptionGroup[BaseExcT_2]]"
"], bool]",
"Callable[[BaseExceptionGroup[ExcT_1|BaseExcT_1|BaseExceptionGroup[BaseExcT_2]]], bool]",
check,
)
super().__init__(match=match, check=check)
Expand Down Expand Up @@ -1107,7 +1105,7 @@ def matches(
def matches(
self,
exception: BaseException | None,
) -> TypeGuard[BaseExceptionGroup[BaseExcT_co]]:
) -> bool:
"""Check if an exception matches the requirements of this RaisesGroup.
If it fails, `RaisesGroup.fail_reason` will be set.

Expand Down Expand Up @@ -1271,7 +1269,7 @@ def _check_exceptions(
self,
_exception: BaseException,
actual_exceptions: Sequence[BaseException],
) -> TypeGuard[BaseExceptionGroup[BaseExcT_co]]:
) -> bool:
"""Helper method for RaisesGroup.matches that attempts to pair up expected and actual exceptions"""
# The _exception parameter is not used, but necessary for the TypeGuard

Expand Down
1 change: 0 additions & 1 deletion src/_pytest/tmpdir.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,6 @@ def tmp_path(
yield path

# Remove the tmpdir if the policy is "failed" and the test passed.
tmp_path_factory: TempPathFactory = request.session.config._tmp_path_factory # type: ignore
policy = tmp_path_factory._retention_policy
result_dict = request.node.stash[tmppath_result_key]

Expand Down