From 5b29b03ce9581cfcd867dd6c04a970fb2c861291 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 4 Sep 2025 18:03:56 -0300 Subject: [PATCH 1/8] Fix gen-release-notes script We now only include the version in the release title, with the date below it. Because of this, need to adjust the script to understand the underlines that define the title are now shorter. --- scripts/gen-release-notes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/gen-release-notes.py b/scripts/gen-release-notes.py index 9e6c4bb..635d81f 100644 --- a/scripts/gen-release-notes.py +++ b/scripts/gen-release-notes.py @@ -18,7 +18,7 @@ capture = False for line in rst_text.splitlines(): # Only start capturing after the latest release section. - if line.startswith("-------"): + if line.startswith("----"): capture = not capture if not capture: # We only need to capture the latest release, so stop. From e696bf02c199b1f7d0c48adb450f40e5a75b699a Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 5 Sep 2025 09:15:20 -0300 Subject: [PATCH 2/8] Fix standalone mock support (#531) It was removed by accident in #528 after running `pyupgrade --py39-plus`. Fix #530 --- src/pytest_mock/_util.py | 2 +- tox.ini | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pytest_mock/_util.py b/src/pytest_mock/_util.py index ad830ca..d3a732a 100644 --- a/src/pytest_mock/_util.py +++ b/src/pytest_mock/_util.py @@ -15,7 +15,7 @@ def get_mock_module(config): config.getini("mock_use_standalone_module") ) if use_standalone_module: - from unittest import mock + import mock _mock_module = mock else: diff --git a/tox.ini b/tox.ini index 92bcba7..20273f7 100644 --- a/tox.ini +++ b/tox.ini @@ -5,6 +5,8 @@ envlist = py{39,310,311,312,313,314}, norewrite, pytest6 [testenv] deps = coverage + # Used for standalone mock support. + mock pytest-asyncio pytest6: pytest==6.2.5 commands = From 95ad5700609aae73c6f767b8cc2ccfb2483e0f5c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 18:48:23 -0300 Subject: [PATCH 3/8] [pre-commit.ci] pre-commit autoupdate (#532) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.12.11 → v0.12.12](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.11...v0.12.12) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f416f4b..80cedb4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,7 +9,7 @@ repos: language: python additional_dependencies: [pygments, restructuredtext_lint] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.11 + rev: v0.12.12 hooks: - id: ruff args: ["--fix"] From adc41873c9d6aa69b87e3f108c93a29c847869aa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 18:48:33 -0300 Subject: [PATCH 4/8] Bump actions/setup-python from 5 to 6 in the github-actions group (#533) Bumps the github-actions group with 1 update: [actions/setup-python](https://github.com/actions/setup-python). Updates `actions/setup-python` from 5 to 6 - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 3cfe123..b4757e4 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -58,7 +58,7 @@ jobs: git push origin v${{ github.event.inputs.version }} - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.10" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a5682d4..46a5d5b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -61,7 +61,7 @@ jobs: path: dist - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python }} allow-prereleases: true From f5aff33ce71ed4620acc43dc41cb3b198bcf4cb0 Mon Sep 17 00:00:00 2001 From: Charles Bousseau <16641587+cbouss@users.noreply.github.com> Date: Fri, 12 Sep 2025 14:26:32 -0400 Subject: [PATCH 5/8] Fix test failure with pytest 8+ and verbose mode (#535) Fixes #534 --- tests/test_pytest_mock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_pytest_mock.py b/tests/test_pytest_mock.py index e9b06c8..c1b6c50 100644 --- a/tests/test_pytest_mock.py +++ b/tests/test_pytest_mock.py @@ -643,7 +643,7 @@ def assert_argument_introspection(left: Any, right: Any) -> Generator[None, None expected = "\n ".join(util._compare_eq_iterable(left, right, verbose)) # type:ignore[arg-type] else: expected = "\n ".join( - util._compare_eq_iterable(left, right, lambda t, *_: t, verbose) + util._compare_eq_iterable(left, right, lambda t, *_, **__: t, verbose) # type:ignore[arg-type] ) assert expected in str(e) else: From 4fa0088a0aa85eefb1313bd97adf43889bf1f647 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 16 Sep 2025 09:23:27 -0300 Subject: [PATCH 6/8] [pre-commit.ci] pre-commit autoupdate (#536) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.12.12 → v0.13.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.12...v0.13.0) - [github.com/pre-commit/mirrors-mypy: v1.17.1 → v1.18.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.17.1...v1.18.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 80cedb4..05bdbb1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,13 +9,13 @@ repos: language: python additional_dependencies: [pygments, restructuredtext_lint] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.12 + rev: v0.13.0 hooks: - id: ruff args: ["--fix"] - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.17.1 + rev: v1.18.1 hooks: - id: mypy files: ^(src|tests) From 184eb190d6be417f5f33727bcbc9704909479498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Vokr=C3=A1=C4=8Dko?= Date: Tue, 16 Sep 2025 18:33:12 +0200 Subject: [PATCH 7/8] Set `spy_return_iter` only when explicitly requested (#537) Fixes #529 --------- Co-authored-by: Bruno Oliveira --- CHANGELOG.rst | 7 +++++++ docs/usage.rst | 4 ++-- src/pytest_mock/plugin.py | 7 +++++-- tests/test_pytest_mock.py | 31 ++++++++++++++++++++++++++----- 4 files changed, 40 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0cde5ad..2549e16 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,13 @@ Releases ======== +UNRELEASED +---------- + +*UNRELEASED* + +* `#529 `_: Fixed ``itertools._tee object has no attribute error`` -- now ``duplicate_iterators=True`` must be passed to ``mocker.spy`` to duplicate iterators. + 3.15.0 ------ diff --git a/docs/usage.rst b/docs/usage.rst index 587fcb3..80d7c66 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -78,10 +78,10 @@ also tracks function/method calls, return values and exceptions raised. The object returned by ``mocker.spy`` is a ``MagicMock`` object, so all standard checking functions are available (like ``assert_called_once_with`` or ``call_count`` in the examples above). -In addition, spy objects contain two extra attributes: +In addition, spy objects contain four extra attributes: * ``spy_return``: contains the last returned value of the spied function. -* ``spy_return_iter``: contains a duplicate of the last returned value of the spied function if the value was an iterator. Uses `tee `__) to duplicate the iterator. +* ``spy_return_iter``: contains a duplicate of the last returned value of the spied function if the value was an iterator and spy was created using ``.spy(..., duplicate_iterators=True)``. Uses `tee `__) to duplicate the iterator. * ``spy_return_list``: contains a list of all returned values of the spied function (new in ``3.13``). * ``spy_exception``: contain the last exception value raised by the spied function/method when it was last called, or ``None`` if no exception was raised. diff --git a/src/pytest_mock/plugin.py b/src/pytest_mock/plugin.py index f4dbfc3..ef99612 100644 --- a/src/pytest_mock/plugin.py +++ b/src/pytest_mock/plugin.py @@ -157,13 +157,16 @@ def stop(self, mock: unittest.mock.MagicMock) -> None: """ self._mock_cache.remove(mock) - def spy(self, obj: object, name: str) -> MockType: + def spy( + self, obj: object, name: str, duplicate_iterators: bool = False + ) -> MockType: """ Create a spy of method. It will run method normally, but it is now possible to use `mock` call features with it, like call count. :param obj: An object. :param name: A method in object. + :param duplicate_iterators: Whether to keep a copy of the returned iterator in `spy_return_iter`. :return: Spy object. """ method = getattr(obj, name) @@ -177,7 +180,7 @@ def wrapper(*args, **kwargs): spy_obj.spy_exception = e raise else: - if isinstance(r, Iterator): + if duplicate_iterators and isinstance(r, Iterator): r, duplicated_iterator = itertools.tee(r, 2) spy_obj.spy_return_iter = duplicated_iterator else: diff --git a/tests/test_pytest_mock.py b/tests/test_pytest_mock.py index c1b6c50..79d59e1 100644 --- a/tests/test_pytest_mock.py +++ b/tests/test_pytest_mock.py @@ -540,13 +540,15 @@ def __call__(self, x): @pytest.mark.parametrize("iterator", [(i for i in range(3)), iter([0, 1, 2])]) -def test_spy_return_iter(mocker: MockerFixture, iterator: Iterator[int]) -> None: +def test_spy_return_iter_duplicates_iterator_when_enabled( + mocker: MockerFixture, iterator: Iterator[int] +) -> None: class Foo: def bar(self) -> Iterator[int]: return iterator foo = Foo() - spy = mocker.spy(foo, "bar") + spy = mocker.spy(foo, "bar", duplicate_iterators=True) result = list(foo.bar()) assert result == [0, 1, 2] @@ -558,8 +560,27 @@ def bar(self) -> Iterator[int]: assert isinstance(return_value, Iterator) +@pytest.mark.parametrize("iterator", [(i for i in range(3)), iter([0, 1, 2])]) +def test_spy_return_iter_is_not_set_when_disabled( + mocker: MockerFixture, iterator: Iterator[int] +) -> None: + class Foo: + def bar(self) -> Iterator[int]: + return iterator + + foo = Foo() + spy = mocker.spy(foo, "bar", duplicate_iterators=False) + result = list(foo.bar()) + + assert result == [0, 1, 2] + assert spy.spy_return is not None + assert spy.spy_return_iter is None + [return_value] = spy.spy_return_list + assert isinstance(return_value, Iterator) + + @pytest.mark.parametrize("iterable", [(0, 1, 2), [0, 1, 2], range(3)]) -def test_spy_return_iter_ignore_plain_iterable( +def test_spy_return_iter_ignores_plain_iterable( mocker: MockerFixture, iterable: Iterable[int] ) -> None: class Foo: @@ -567,7 +588,7 @@ def bar(self) -> Iterable[int]: return iterable foo = Foo() - spy = mocker.spy(foo, "bar") + spy = mocker.spy(foo, "bar", duplicate_iterators=True) result = foo.bar() assert result == iterable @@ -587,7 +608,7 @@ def bar(self) -> Any: return self.iterables.pop(0) foo = Foo() - spy = mocker.spy(foo, "bar") + spy = mocker.spy(foo, "bar", duplicate_iterators=True) result_iterator = list(foo.bar()) assert result_iterator == [0, 1, 2] From e1b5c62a38c5a05cae614aef3847f240ba50d269 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 16 Sep 2025 13:34:11 -0300 Subject: [PATCH 8/8] Release 3.15.1 --- CHANGELOG.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2549e16..987407c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,10 +1,10 @@ Releases ======== -UNRELEASED ----------- +3.15.1 +------ -*UNRELEASED* +*2025-09-16* * `#529 `_: Fixed ``itertools._tee object has no attribute error`` -- now ``duplicate_iterators=True`` must be passed to ``mocker.spy`` to duplicate iterators.