-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
from __future__ import annotations + migrate #12467
from __future__ import annotations + migrate #12467
Conversation
RonnyPfannschmidt
commented
Jun 17, 2024
- reconfigured ruff for upgrade
- added Self type for the errors
- fixed test warnings locations
2ecf59e
to
d5d2a27
Compare
798c82c
to
987207a
Compare
the way sphinx autodoc fails on deferred type annotations has been most infurating its better on python 3.12 but rtd is still on 3.9 |
987207a
to
e712c69
Compare
e712c69
to
cf4ae25
Compare
Reviewed by reproducing the automated changes: $ git checkout -b ronny-repro c46a3a9920b38164fea4e22ef99b4b66f42e77bf
$ git checkout ronny/new-annotations-try-2 -- pyproject.toml
$ git commit -am pyproject
$ tox -e linting
$ git commit -am linting
$ git diff ronny-repro..ronny/new-annotations-try-2 Which leads to a more digestable diff:
diff --git a/changelog/11797.bugfix.rst b/changelog/11797.bugfix.rst
new file mode 100644
index 000000000..94b72da00
--- /dev/null
+++ b/changelog/11797.bugfix.rst
@@ -0,0 +1 @@
+:func:`pytest.approx` now correctly handles :class:`Sequence <collections.abc.Sequence>`-like objects.
diff --git a/doc/en/conf.py b/doc/en/conf.py
index 2904b141f..afef9b8f6 100644
--- a/doc/en/conf.py
+++ b/doc/en/conf.py
@@ -21,9 +21,11 @@
from textwrap import dedent
from typing import TYPE_CHECKING
-from _pytest import __version__ as version
+from _pytest import __version__ as full_version
+version = full_version.split("+")[0]
+
if TYPE_CHECKING:
import sphinx.application
@@ -38,6 +40,9 @@
autodoc_member_order = "bysource"
autodoc_typehints = "description"
autodoc_typehints_description_target = "documented"
+
+
+autodoc2_packages = ["pytest", "_pytest"]
todo_include_todos = 1
latex_engine = "lualatex"
@@ -178,6 +183,7 @@
("py:class", "SubRequest"),
("py:class", "TerminalReporter"),
("py:class", "_pytest._code.code.TerminalRepr"),
+ ("py:class", "TerminalRepr"),
("py:class", "_pytest.fixtures.FixtureFunctionMarker"),
("py:class", "_pytest.logging.LogCaptureHandler"),
("py:class", "_pytest.mark.structures.ParameterSet"),
@@ -199,13 +205,16 @@
("py:class", "_PluggyPlugin"),
# TypeVars
("py:class", "_pytest._code.code.E"),
+ ("py:class", "E"), # due to delayed annotation
("py:class", "_pytest.fixtures.FixtureFunction"),
("py:class", "_pytest.nodes._NodeType"),
+ ("py:class", "_NodeType"), # due to delayed annotation
("py:class", "_pytest.python_api.E"),
("py:class", "_pytest.recwarn.T"),
("py:class", "_pytest.runner.TResult"),
("py:obj", "_pytest.fixtures.FixtureValue"),
("py:obj", "_pytest.stash.T"),
+ ("py:class", "_ScopeName"),
]
diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py
index d30e80436..9400e834a 100644
--- a/src/_pytest/_code/code.py
+++ b/src/_pytest/_code/code.py
@@ -30,7 +30,10 @@
from typing import Pattern
from typing import Sequence
from typing import SupportsIndex
+from typing import Tuple
+from typing import Type
from typing import TypeVar
+from typing import Union
import pluggy
@@ -53,6 +56,10 @@
TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"]
+EXCEPTION_OR_MORE = Union[Type[Exception], Tuple[Type[Exception], ...]]
+
+type_alias = type # to sidestep shadowing
+
class Code:
"""Wrapper around Python code objects."""
@@ -592,9 +599,7 @@ def exconly(self, tryshort: bool = False) -> str:
text = text[len(self._striptext) :]
return text
- def errisinstance(
- self, exc: type[BaseException] | tuple[type[BaseException], ...]
- ) -> bool:
+ def errisinstance(self, exc: EXCEPTION_OR_MORE) -> bool:
"""Return True if the exception is an instance of exc.
Consider using ``isinstance(excinfo.value, exc)`` instead.
@@ -617,7 +622,8 @@ def getrepr(
showlocals: bool = False,
style: TracebackStyle = "long",
abspath: bool = False,
- tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback] = True,
+ tbfilter: bool
+ | Callable[[ExceptionInfo[BaseException]], _pytest._code.code.Traceback] = True,
funcargs: bool = False,
truncate_locals: bool = True,
truncate_args: bool = True,
@@ -722,7 +728,7 @@ def match(self, regexp: str | Pattern[str]) -> Literal[True]:
def _group_contains(
self,
exc_group: BaseExceptionGroup[BaseException],
- expected_exception: type[BaseException] | tuple[type[BaseException], ...],
+ expected_exception: EXCEPTION_OR_MORE,
match: str | Pattern[str] | None,
target_depth: int | None = None,
current_depth: int = 1,
@@ -751,7 +757,7 @@ def _group_contains(
def group_contains(
self,
- expected_exception: type[BaseException] | tuple[type[BaseException], ...],
+ expected_exception: EXCEPTION_OR_MORE,
*,
match: str | Pattern[str] | None = None,
depth: int | None = None,
diff --git a/src/_pytest/assertion/__init__.py b/src/_pytest/assertion/__init__.py
index 357833054..f2f1d029b 100644
--- a/src/_pytest/assertion/__init__.py
+++ b/src/_pytest/assertion/__init__.py
@@ -6,8 +6,6 @@
import sys
from typing import Any
from typing import Generator
-from typing import List
-from typing import Optional
from typing import TYPE_CHECKING
from _pytest.assertion import rewrite
diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py
index 4272db454..c4dfcc275 100644
--- a/src/_pytest/capture.py
+++ b/src/_pytest/capture.py
@@ -26,6 +26,10 @@
from typing import TextIO
from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from typing_extensions import Self
+
from _pytest.config import Config
from _pytest.config import hookimpl
from _pytest.config.argparsing import Parser
@@ -254,7 +258,7 @@ def writelines(self, lines: Iterable[str]) -> None:
def writable(self) -> bool:
return False
- def __enter__(self) -> DontReadFromInput:
+ def __enter__(self) -> Self:
return self
def __exit__(
diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py
index 7180d2067..23a2c4797 100644
--- a/src/_pytest/config/__init__.py
+++ b/src/_pytest/config/__init__.py
@@ -13,7 +13,7 @@
import importlib.metadata
import inspect
import os
-from pathlib import Path
+import pathlib
import re
import shlex
import sys
@@ -23,22 +23,16 @@
from typing import Any
from typing import Callable
from typing import cast
-from typing import Dict
from typing import Final
from typing import final
from typing import Generator
from typing import IO
from typing import Iterable
from typing import Iterator
-from typing import List
-from typing import Optional
from typing import Sequence
-from typing import Set
from typing import TextIO
-from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
-from typing import Union
import warnings
import pluggy
@@ -120,7 +114,7 @@ class ExitCode(enum.IntEnum):
class ConftestImportFailure(Exception):
def __init__(
self,
- path: Path,
+ path: pathlib.Path,
*,
cause: Exception,
) -> None:
@@ -296,7 +290,7 @@ def get_config(
invocation_params=Config.InvocationParams(
args=args or (),
plugins=plugins,
- dir=Path.cwd(),
+ dir=pathlib.Path.cwd(),
),
)
@@ -353,7 +347,7 @@ def _prepareconfig(
raise
-def _get_directory(path: Path) -> Path:
+def _get_directory(path: pathlib.Path) -> pathlib.Path:
"""Get the directory of a path - itself if already a directory."""
if path.is_file():
return path.parent
@@ -414,9 +408,9 @@ def __init__(self) -> None:
# All conftest modules applicable for a directory.
# This includes the directory's own conftest modules as well
# as those of its parent directories.
- self._dirpath2confmods: dict[Path, list[types.ModuleType]] = {}
+ self._dirpath2confmods: dict[pathlib.Path, list[types.ModuleType]] = {}
# Cutoff directory above which conftests are no longer discovered.
- self._confcutdir: Path | None = None
+ self._confcutdir: pathlib.Path | None = None
# If set, conftest loading is skipped.
self._noconftest = False
@@ -550,12 +544,12 @@ def pytest_configure(self, config: Config) -> None:
#
def _set_initial_conftests(
self,
- args: Sequence[str | Path],
+ args: Sequence[str | pathlib.Path],
pyargs: bool,
noconftest: bool,
- rootpath: Path,
- confcutdir: Path | None,
- invocation_dir: Path,
+ rootpath: pathlib.Path,
+ confcutdir: pathlib.Path | None,
+ invocation_dir: pathlib.Path,
importmode: ImportMode | str,
*,
consider_namespace_packages: bool,
@@ -599,7 +593,7 @@ def _set_initial_conftests(
consider_namespace_packages=consider_namespace_packages,
)
- def _is_in_confcutdir(self, path: Path) -> bool:
+ def _is_in_confcutdir(self, path: pathlib.Path) -> bool:
"""Whether to consider the given path to load conftests from."""
if self._confcutdir is None:
return True
@@ -616,9 +610,9 @@ def _is_in_confcutdir(self, path: Path) -> bool:
def _try_load_conftest(
self,
- anchor: Path,
+ anchor: pathlib.Path,
importmode: str | ImportMode,
- rootpath: Path,
+ rootpath: pathlib.Path,
*,
consider_namespace_packages: bool,
) -> None:
@@ -641,9 +635,9 @@ def _try_load_conftest(
def _loadconftestmodules(
self,
- path: Path,
+ path: pathlib.Path,
importmode: str | ImportMode,
- rootpath: Path,
+ rootpath: pathlib.Path,
*,
consider_namespace_packages: bool,
) -> None:
@@ -671,14 +665,14 @@ def _loadconftestmodules(
clist.append(mod)
self._dirpath2confmods[directory] = clist
- def _getconftestmodules(self, path: Path) -> Sequence[types.ModuleType]:
+ def _getconftestmodules(self, path: pathlib.Path) -> Sequence[types.ModuleType]:
directory = self._get_directory(path)
return self._dirpath2confmods.get(directory, ())
def _rget_with_confmod(
self,
name: str,
- path: Path,
+ path: pathlib.Path,
) -> tuple[types.ModuleType, Any]:
modules = self._getconftestmodules(path)
for mod in reversed(modules):
@@ -690,9 +684,9 @@ def _rget_with_confmod(
def _importconftest(
self,
- conftestpath: Path,
+ conftestpath: pathlib.Path,
importmode: str | ImportMode,
- rootpath: Path,
+ rootpath: pathlib.Path,
*,
consider_namespace_packages: bool,
) -> types.ModuleType:
@@ -744,7 +738,7 @@ def _importconftest(
def _check_non_top_pytest_plugins(
self,
mod: types.ModuleType,
- conftestpath: Path,
+ conftestpath: pathlib.Path,
) -> None:
if (
hasattr(mod, "pytest_plugins")
@@ -1001,15 +995,15 @@ class InvocationParams:
"""The command-line arguments as passed to :func:`pytest.main`."""
plugins: Sequence[str | _PluggyPlugin] | None
"""Extra plugins, might be `None`."""
- dir: Path
- """The directory from which :func:`pytest.main` was invoked."""
+ dir: pathlib.Path
+ """The directory from which :func:`pytest.main` was invoked. :type: pathlib.Path"""
def __init__(
self,
*,
args: Iterable[str],
plugins: Sequence[str | _PluggyPlugin] | None,
- dir: Path,
+ dir: pathlib.Path,
) -> None:
object.__setattr__(self, "args", tuple(args))
object.__setattr__(self, "plugins", plugins)
@@ -1043,7 +1037,7 @@ def __init__(
if invocation_params is None:
invocation_params = self.InvocationParams(
- args=(), plugins=None, dir=Path.cwd()
+ args=(), plugins=None, dir=pathlib.Path.cwd()
)
self.option = argparse.Namespace()
@@ -1094,7 +1088,7 @@ def __init__(
self.args: list[str] = []
@property
- def rootpath(self) -> Path:
+ def rootpath(self) -> pathlib.Path:
"""The path to the :ref:`rootdir <rootdir>`.
:type: pathlib.Path
@@ -1104,11 +1098,9 @@ def rootpath(self) -> Path:
return self._rootpath
@property
- def inipath(self) -> Path | None:
+ def inipath(self) -> pathlib.Path | None:
"""The path to the :ref:`configfile <configfiles>`.
- :type: Optional[pathlib.Path]
-
.. versionadded:: 6.1
"""
return self._inipath
@@ -1319,8 +1311,8 @@ def _decide_args(
args: list[str],
pyargs: bool,
testpaths: list[str],
- invocation_dir: Path,
- rootpath: Path,
+ invocation_dir: pathlib.Path,
+ rootpath: pathlib.Path,
warn: bool,
) -> tuple[list[str], ArgsSource]:
"""Decide the args (initial paths/nodeids) to use given the relevant inputs.
@@ -1646,17 +1638,19 @@ def _getini(self, name: str):
else:
return self._getini_unknown_type(name, type, value)
- def _getconftest_pathlist(self, name: str, path: Path) -> list[Path] | None:
+ def _getconftest_pathlist(
+ self, name: str, path: pathlib.Path
+ ) -> list[pathlib.Path] | None:
try:
mod, relroots = self.pluginmanager._rget_with_confmod(name, path)
except KeyError:
return None
assert mod.__file__ is not None
- modpath = Path(mod.__file__).parent
- values: list[Path] = []
+ modpath = pathlib.Path(mod.__file__).parent
+ values: list[pathlib.Path] = []
for relroot in relroots:
if isinstance(relroot, os.PathLike):
- relroot = Path(relroot)
+ relroot = pathlib.Path(relroot)
else:
relroot = relroot.replace("/", os.sep)
relroot = absolutepath(modpath / relroot)
diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py
index 13f4fddbd..996148999 100644
--- a/src/_pytest/hookspec.py
+++ b/src/_pytest/hookspec.py
@@ -321,6 +321,7 @@ def pytest_ignore_collect(
Stops at first non-None result, see :ref:`firstresult`.
:param collection_path: The path to analyze.
+ :type collection_path: pathlib.Path
:param path: The path to analyze (deprecated).
:param config: The pytest config object.
@@ -354,6 +355,7 @@ def pytest_collect_directory(path: Path, parent: Collector) -> Collector | None:
Stops at first non-None result, see :ref:`firstresult`.
:param path: The path to analyze.
+ :type path: pathlib.Path
See :ref:`custom directory collectors` for a simple example of use of this
hook.
@@ -386,6 +388,7 @@ def pytest_collect_file(
The new node needs to have the specified ``parent`` as a parent.
:param file_path: The path to analyze.
+ :type file_path: pathlib.Path
:param path: The path to collect (deprecated).
.. versionchanged:: 7.0.0
@@ -507,6 +510,7 @@ def pytest_pycollect_makemodule(
Stops at first non-None result, see :ref:`firstresult`.
:param module_path: The path of the module to collect.
+ :type module_path: pathlib.Path
:param path: The path of the module to collect (deprecated).
.. versionchanged:: 7.0.0
@@ -1026,6 +1030,7 @@ def pytest_report_header( # type:ignore[empty-body]
:param config: The pytest config object.
:param start_path: The starting dir.
+ :type start_path: pathlib.Path
:param startdir: The starting dir (deprecated).
.. note::
@@ -1069,6 +1074,7 @@ def pytest_report_collectionfinish( # type:ignore[empty-body]
:param config: The pytest config object.
:param start_path: The starting dir.
+ :type start_path: pathlib.Path
:param startdir: The starting dir (deprecated).
:param items: List of pytest items that are going to be executed; this list should not be modified.
diff --git a/src/_pytest/main.py b/src/_pytest/main.py
index a19ddef58..47ebad471 100644
--- a/src/_pytest/main.py
+++ b/src/_pytest/main.py
@@ -510,6 +510,7 @@ def from_parent( # type: ignore[override]
:param parent: The parent collector of this Dir.
:param path: The directory's path.
+ :type path: pathlib.Path
"""
return super().from_parent(parent=parent, path=path)
diff --git a/src/_pytest/mark/__init__.py b/src/_pytest/mark/__init__.py
index 68b79a11e..b8a309215 100644
--- a/src/_pytest/mark/__init__.py
+++ b/src/_pytest/mark/__init__.py
@@ -5,10 +5,8 @@
import dataclasses
from typing import AbstractSet
from typing import Collection
-from typing import List
from typing import Optional
from typing import TYPE_CHECKING
-from typing import Union
from .expression import Expression
from .expression import ParseError
diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py
index e27648507..5c6ce5e88 100644
--- a/src/_pytest/pytester.py
+++ b/src/_pytest/pytester.py
@@ -909,6 +909,7 @@ def mkdir(self, name: str | os.PathLike[str]) -> Path:
The name of the directory, relative to the pytester path.
:returns:
The created directory.
+ :rtype: pathlib.Path
"""
p = self.path / name
p.mkdir()
@@ -932,6 +933,7 @@ def copy_example(self, name: str | None = None) -> Path:
The name of the file to copy.
:return:
Path to the copied directory (inside ``self.path``).
+ :rtype: pathlib.Path
"""
example_dir_ = self._request.config.getini("pytester_example_dir")
if example_dir_ is None:
@@ -1390,8 +1392,10 @@ def run(
- Otherwise, it is passed through to :py:class:`subprocess.Popen`.
For further information in this case, consult the document of the
``stdin`` parameter in :py:class:`subprocess.Popen`.
+ :type stdin: _pytest.compat.NotSetType | bytes | IO[Any] | int
:returns:
The result.
+
"""
__tracebackhide__ = True
diff --git a/src/_pytest/python.py b/src/_pytest/python.py
index 2904c3a1e..9182ce7df 100644
--- a/src/_pytest/python.py
+++ b/src/_pytest/python.py
@@ -1168,7 +1168,7 @@ def parametrize(
If N argnames were specified, argvalues must be a list of
N-tuples, where each tuple-element specifies a value for its
respective argname.
-
+ :type argvalues: Iterable[_pytest.mark.structures.ParameterSet | Sequence[object] | object]
:param indirect:
A list of arguments' names (subset of argnames) or a boolean.
If True the list contains all names from the argnames. Each
diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py
index d55575e4c..c1e851391 100644
--- a/src/_pytest/python_api.py
+++ b/src/_pytest/python_api.py
@@ -128,6 +128,8 @@ def _recursive_sequence_map(f, x):
if isinstance(x, (list, tuple)):
seq_type = type(x)
return seq_type(_recursive_sequence_map(f, xi) for xi in x)
+ elif _is_sequence_like(x):
+ return [_recursive_sequence_map(f, xi) for xi in x]
else:
return f(x)
@@ -720,11 +722,7 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
elif _is_numpy_array(expected):
expected = _as_numpy_array(expected)
cls = ApproxNumpy
- elif (
- hasattr(expected, "__getitem__")
- and isinstance(expected, Sized)
- and not isinstance(expected, (str, bytes))
- ):
+ elif _is_sequence_like(expected):
cls = ApproxSequenceLike
elif isinstance(expected, Collection) and not isinstance(expected, (str, bytes)):
msg = f"pytest.approx() only supports ordered sequences, but got: {expected!r}"
@@ -735,6 +733,14 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
return cls(expected, rel, abs, nan_ok)
+def _is_sequence_like(expected: object) -> bool:
+ return (
+ hasattr(expected, "__getitem__")
+ and isinstance(expected, Sized)
+ and not isinstance(expected, (str, bytes))
+ )
+
+
def _is_numpy_array(obj: object) -> bool:
"""
Return true if the given object is implicitly convertible to ndarray,
diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py
index b2a369653..0e34fd0d2 100644
--- a/src/_pytest/recwarn.py
+++ b/src/_pytest/recwarn.py
@@ -13,7 +13,13 @@
from typing import Iterator
from typing import overload
from typing import Pattern
+from typing import TYPE_CHECKING
from typing import TypeVar
+
+
+if TYPE_CHECKING:
+ from typing_extensions import Self
+
import warnings
from _pytest.deprecated import check_ispytest
@@ -222,7 +228,7 @@ def clear(self) -> None:
# Type ignored because it doesn't exactly warnings.catch_warnings.__enter__
# -- it returns a List but we only emulate one.
- def __enter__(self) -> WarningsRecorder: # type: ignore
+ def __enter__(self) -> Self:
if self._entered:
__tracebackhide__ = True
raise RuntimeError(f"Cannot enter {self!r} twice")
diff --git a/src/_pytest/reports.py b/src/_pytest/reports.py
index ae7de506e..2f39adbfa 100644
--- a/src/_pytest/reports.py
+++ b/src/_pytest/reports.py
@@ -14,7 +14,6 @@
from typing import Mapping
from typing import NoReturn
from typing import TYPE_CHECKING
-from typing import TypeVar
from _pytest._code.code import ExceptionChainRepr
from _pytest._code.code import ExceptionInfo
@@ -35,6 +34,8 @@
if TYPE_CHECKING:
+ from typing_extensions import Self
+
from _pytest.runner import CallInfo
@@ -50,9 +51,6 @@ def getworkerinfoline(node):
return s
-_R = TypeVar("_R", bound="BaseReport")
-
-
class BaseReport:
when: str | None
location: tuple[str, int | None, str] | None
@@ -209,7 +207,7 @@ def _to_json(self) -> dict[str, Any]:
return _report_to_json(self)
@classmethod
- def _from_json(cls: type[_R], reportdict: dict[str, object]) -> _R:
+ def _from_json(cls, reportdict: dict[str, object]) -> Self:
"""Create either a TestReport or CollectReport, depending on the calling class.
It is the callers responsibility to know which class to pass here.
diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py
index bf30a7d2d..716c4948f 100644
--- a/src/_pytest/runner.py
+++ b/src/_pytest/runner.py
@@ -327,6 +327,7 @@ def from_call(
:param func:
The function to call. Called without arguments.
+ :type func: Callable[[], _pytest.runner.TResult]
:param when:
The phase in which the function is called.
:param reraise:
diff --git a/src/_pytest/threadexception.py b/src/_pytest/threadexception.py
index bfdcd381f..f525a7bca 100644
--- a/src/_pytest/threadexception.py
+++ b/src/_pytest/threadexception.py
@@ -6,11 +6,18 @@
from typing import Any
from typing import Callable
from typing import Generator
+from typing import TYPE_CHECKING
import warnings
import pytest
+if TYPE_CHECKING:
+ from typing_extensions import Self
+
+type_alias = type
+
+
# Copied from cpython/Lib/test/support/threading_helper.py, with modifications.
class catch_threading_exception:
"""Context manager catching threading.Thread exception using
@@ -40,7 +47,7 @@ def __init__(self) -> None:
def _hook(self, args: threading.ExceptHookArgs) -> None:
self.args = args
- def __enter__(self) -> catch_threading_exception:
+ def __enter__(self) -> Self:
self._old_hook = threading.excepthook
threading.excepthook = self._hook
return self
diff --git a/src/_pytest/unraisableexception.py b/src/_pytest/unraisableexception.py
index 24ab528ee..c191703a3 100644
--- a/src/_pytest/unraisableexception.py
+++ b/src/_pytest/unraisableexception.py
@@ -6,11 +6,16 @@
from typing import Any
from typing import Callable
from typing import Generator
+from typing import TYPE_CHECKING
import warnings
import pytest
+if TYPE_CHECKING:
+ from typing_extensions import Self
+
+
# Copied from cpython/Lib/test/support/__init__.py, with modifications.
class catch_unraisable_exception:
"""Context manager catching unraisable exception using sys.unraisablehook.
@@ -42,7 +47,7 @@ def _hook(self, unraisable: sys.UnraisableHookArgs) -> None:
# finalized. Storing unraisable.exc_value creates a reference cycle.
self.unraisable = unraisable
- def __enter__(self) -> catch_unraisable_exception:
+ def __enter__(self) -> Self:
self._old_hook = sys.unraisablehook
sys.unraisablehook = self._hook
return self
diff --git a/testing/python/approx.py b/testing/python/approx.py
index 17a5d29bc..69743cdbe 100644
--- a/testing/python/approx.py
+++ b/testing/python/approx.py
@@ -953,6 +953,43 @@ def test_allow_ordered_sequences_only(self) -> None:
with pytest.raises(TypeError, match="only supports ordered sequences"):
assert {1, 2, 3} == approx({1, 2, 3})
+ def test_strange_sequence(self):
+ """https://github.com/pytest-dev/pytest/issues/11797"""
+ a = MyVec3(1, 2, 3)
+ b = MyVec3(0, 1, 2)
+
+ # this would trigger the error inside the test
+ pytest.approx(a, abs=0.5)._repr_compare(b)
+
+ assert b == pytest.approx(a, abs=2)
+ assert b != pytest.approx(a, abs=0.5)
+
+
+class MyVec3: # incomplete
+ """sequence like"""
+
+ _x: int
+ _y: int
+ _z: int
+
+ def __init__(self, x: int, y: int, z: int):
+ self._x, self._y, self._z = x, y, z
+
+ def __repr__(self) -> str:
+ return f"<MyVec3 {self._x} {self._y} {self._z}>"
+
+ def __len__(self) -> int:
+ return 3
+
+ def __getitem__(self, key: int) -> int:
+ if key == 0:
+ return self._x
+ if key == 1:
+ return self._y
+ if key == 2:
+ return self._z
+ raise IndexError(key)
+
class TestRecursiveSequenceMap:
def test_map_over_scalar(self):
@@ -980,3 +1017,6 @@ def test_map_over_mixed_sequence(self):
(5, 8),
[(7)],
]
+
+ def test_map_over_sequence_like(self):
+ assert _recursive_sequence_map(int, MyVec3(1, 2, 3)) == [1, 2, 3]
diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py
index 8d9dd49a8..bc091bb1f 100644
--- a/testing/python/fixtures.py
+++ b/testing/python/fixtures.py
@@ -4516,7 +4516,7 @@ def test_fixture_named_request(pytester: Pytester) -> None:
result.stdout.fnmatch_lines(
[
"*'request' is a reserved word for fixtures, use another name:",
- " *test_fixture_named_request.py:6",
+ " *test_fixture_named_request.py:8",
]
)
diff --git a/testing/test_warnings.py b/testing/test_warnings.py
index 3ddcd8d8c..d4d0e0b7f 100644
--- a/testing/test_warnings.py
+++ b/testing/test_warnings.py
@@ -617,11 +617,11 @@ def test_group_warnings_by_message_summary(pytester: Pytester) -> None:
f"*== {WARNINGS_SUMMARY_HEADER} ==*",
"test_1.py: 21 warnings",
"test_2.py: 1 warning",
- " */test_1.py:8: UserWarning: foo",
+ " */test_1.py:10: UserWarning: foo",
" warnings.warn(UserWarning(msg))",
"",
"test_1.py: 20 warnings",
- " */test_1.py:8: UserWarning: bar",
+ " */test_1.py:10: UserWarning: bar",
" warnings.warn(UserWarning(msg))",
"",
"-- Docs: *",
diff --git a/tox.ini b/tox.ini
index 35b335a01..dff6e0017 100644
--- a/tox.ini
+++ b/tox.ini
@@ -81,18 +81,25 @@ setenv =
PYTHONWARNDEFAULTENCODING=
[testenv:docs]
-basepython = python3
+basepython = python3.9 # sync with rtd to get errors
usedevelop = True
deps =
-r{toxinidir}/doc/en/requirements.txt
# https://github.com/twisted/towncrier/issues/340
towncrier<21.3.0
+
+
+
commands =
python scripts/towncrier-draft-to-file.py
# the '-t changelog_towncrier_draft' tags makes sphinx include the draft
# changelog in the docs; this does not happen on ReadTheDocs because it uses
# the standard sphinx command so the 'changelog_towncrier_draft' is never set there
- sphinx-build -W --keep-going -b html doc/en doc/en/_build/html -t changelog_towncrier_draft {posargs:}
+ sphinx-build \
+ -j auto \
+ -W --keep-going \
+ -b html doc/en doc/en/_build/html \
+ -t changelog_towncrier_draft {posargs:}
setenv =
# Sphinx is not clean of this warning.
PYTHONWARNDEFAULTENCODING= |
44ad01d
to
d847844
Compare
add the extra sphinx annotations to refer to Path instances add Path to nitpicky ignore
34e0619
to
4e54f19
Compare
rebased |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, now I can finally forget about Union, Optional and friends :)
still working on the backport |
|
||
|
||
version = full_version.split("+")[0] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RonnyPfannschmidt this results in the |release|
RST substitution having the value of 8.2
which might have a weird perception in changelog draft titles: https://docs.pytest.org/en/8.2.x/changelog.html#to-be-included-in-vrelease-if-present / https://docs.pytest.org/en/latest/changelog.html#to-be-included-in-vrelease-if-present.
Should this be something like +dev
in the end but without a variable component, something static? Though, perhaps in RTD it'd be okay to use the long original version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, I cut off too much
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, I cut off too much