-
- Notifications
You must be signed in to change notification settings - Fork 2.9k
from __future__ import annotations + migrate #12467
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
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 |
from _pytest import __version__ as full_version | ||
| ||
| ||
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