From 434d8d3a89923fa748d2a114a34546881b1784dc Mon Sep 17 00:00:00 2001 From: Dylan Semler Date: Tue, 9 Apr 2019 18:51:52 -0400 Subject: [PATCH 1/4] bpo-36581: Fix dir() on MagicMock with tuple specs --- Lib/unittest/mock.py | 7 ++++--- Lib/unittest/test/testmock/testmock.py | 8 ++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 1e8057d5f5bb5f7..8b244c8fae646c0 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -121,9 +121,9 @@ def _callable(obj): def _is_list(obj): - # checks for list or tuples + # checks for list, tuples, or sets # XXXX badly named! - return type(obj) in (list, tuple) + return type(obj) in (list, tuple, set) def _instance_callable(obj): @@ -663,7 +663,8 @@ def __dir__(self): from_type = [e for e in from_type if not e.startswith('_')] from_dict = [e for e in from_dict if not e.startswith('_') or _is_magic(e)] - return sorted(set(extras + from_type + from_dict + from_child_mocks)) + return sorted(set(list(extras) + from_type + from_dict + + from_child_mocks)) def __setattr__(self, name, value): diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py index 5f917dd20f1dde4..8e32dfa0066dead 100644 --- a/Lib/unittest/test/testmock/testmock.py +++ b/Lib/unittest/test/testmock/testmock.py @@ -862,6 +862,14 @@ def test_dir(self): mock.__iter__ = lambda s: iter([]) self.assertIn('__iter__', dir(mock)) + # spec from a tuple + mock_tuple_spec = Mock(spec=('something',)) + self.assertIn('something', dir(mock_tuple_spec)) + + # spec from a set + mock_set_spec = Mock(spec={'something'}) + self.assertIn('something', dir(mock_set_spec)) + def test_dir_from_spec(self): mock = Mock(spec=unittest.TestCase) From ccb6a839ff6d725cce9b5d337a332e9b2dc62591 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 12 Aug 2026 12:28:32 +0300 Subject: [PATCH 2/4] Apply suggestion from @serhiy-storchaka --- Lib/unittest/mock.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 2c212824d8ff910..c7fe978efc47fe5 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -798,8 +798,7 @@ def __dir__(self): from_type = [e for e in from_type if not e.startswith('_')] from_dict = [e for e in from_dict if not e.startswith('_') or _is_magic(e)] - return sorted(set(list(extras) + from_type + from_dict + - from_child_mocks)) + return sorted({*extras, *from_type, *from_dict, *from_child_mocks}) def __setattr__(self, name, value): From 55131293e512ad2c6129a92bdb80458bd7a86ddf Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 12 Aug 2026 15:16:46 +0300 Subject: [PATCH 3/4] Revert the _is_list() change and document tuple specs Accepting sets in _is_list() would change the meaning of a set *spec* from mirroring a set object to a list of attribute names, and affect also __class__, isinstance() and create_autospec(). A tuple *spec* has been supported since the initial import and is used in tests, but it was never documented. Co-Authored-By: Claude Opus 5 (1M context) --- Doc/library/unittest.mock.rst | 16 ++++++++++------ Lib/test/test_unittest/testmock/testmock.py | 4 ---- Lib/unittest/mock.py | 4 ++-- ...2026-08-12-14-30-00.gh-issue-80762.Kp3vQa.rst | 3 +++ 4 files changed, 15 insertions(+), 12 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-08-12-14-30-00.gh-issue-80762.Kp3vQa.rst diff --git a/Doc/library/unittest.mock.rst b/Doc/library/unittest.mock.rst index cce8f2833ac5a02..7a48f0d99b18308 100644 --- a/Doc/library/unittest.mock.rst +++ b/Doc/library/unittest.mock.rst @@ -231,9 +231,10 @@ the *new_callable* argument to :func:`patch`. Create a new :class:`Mock` object. :class:`Mock` takes several optional arguments that specify the behaviour of the Mock object: - * *spec*: This can be either a list of strings or an existing object (a - class or instance) that acts as the specification for the mock object. If - you pass in an object then a list of strings is formed by calling dir on + * *spec*: This can be either a list or tuple of strings, + or an existing object (a class or instance) + that acts as the specification for the mock object. + If you pass in an object then a list of strings is formed by calling dir on the object (excluding unsupported magic attributes and methods). Accessing any attribute not in this list will raise an :exc:`AttributeError`. @@ -241,6 +242,9 @@ the *new_callable* argument to :func:`patch`. :attr:`~object.__class__` returns the class of the spec object. This allows mocks to pass :func:`isinstance` tests. + .. versionchanged:: next + *spec* can now be a tuple of strings. + * *spec_set*: A stricter variant of *spec*. If used, attempting to *set* or get an attribute on the mock that isn't on the object passed as *spec_set* will raise an :exc:`AttributeError`. @@ -448,9 +452,9 @@ the *new_callable* argument to :func:`patch`. .. method:: mock_add_spec(spec, spec_set=False) - Add a spec to a mock. *spec* can either be an object or a - list of strings. Only attributes on the *spec* can be fetched as - attributes from the mock. + Add a spec to a mock. + *spec* can either be an object or a list or tuple of strings. + Only attributes on the *spec* can be fetched as attributes from the mock. If *spec_set* is true then only attributes on the spec can be set. diff --git a/Lib/test/test_unittest/testmock/testmock.py b/Lib/test/test_unittest/testmock/testmock.py index 7dba7621026fb3a..ffc89dc5ce1562d 100644 --- a/Lib/test/test_unittest/testmock/testmock.py +++ b/Lib/test/test_unittest/testmock/testmock.py @@ -1077,10 +1077,6 @@ def test_dir(self): mock_tuple_spec = Mock(spec=('something',)) self.assertIn('something', dir(mock_tuple_spec)) - # spec from a set - mock_set_spec = Mock(spec={'something'}) - self.assertIn('something', dir(mock_set_spec)) - def test_dir_from_spec(self): mock = Mock(spec=unittest.TestCase) diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index c7fe978efc47fe5..d0ca94668e05bc2 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -161,9 +161,9 @@ def _callable(obj): def _is_list(obj): - # checks for list, tuples, or sets + # checks for list or tuples # XXXX badly named! - return type(obj) in (list, tuple, set) + return type(obj) in (list, tuple) def _instance_callable(obj): diff --git a/Misc/NEWS.d/next/Library/2026-08-12-14-30-00.gh-issue-80762.Kp3vQa.rst b/Misc/NEWS.d/next/Library/2026-08-12-14-30-00.gh-issue-80762.Kp3vQa.rst new file mode 100644 index 000000000000000..724ed9f1919115c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-12-14-30-00.gh-issue-80762.Kp3vQa.rst @@ -0,0 +1,3 @@ +:class:`unittest.mock.Mock` now supports a tuple of strings as *spec*, +in addition to a list. +Previously :func:`dir` on such a mock raised :exc:`TypeError`. From 0879591f41d07efecac2f6e3de623e6d9de48169 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 12 Aug 2026 15:25:58 +0300 Subject: [PATCH 4/4] Reword the NEWS entry and versionchanged A tuple *spec* worked before, except in dir(), so this is a fix, not a new feature. Co-Authored-By: Claude Opus 5 (1M context) --- Doc/library/unittest.mock.rst | 2 +- .../Library/2026-08-12-14-30-00.gh-issue-80762.Kp3vQa.rst | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/library/unittest.mock.rst b/Doc/library/unittest.mock.rst index 7a48f0d99b18308..b9e6f65cb917df0 100644 --- a/Doc/library/unittest.mock.rst +++ b/Doc/library/unittest.mock.rst @@ -243,7 +243,7 @@ the *new_callable* argument to :func:`patch`. allows mocks to pass :func:`isinstance` tests. .. versionchanged:: next - *spec* can now be a tuple of strings. + :func:`dir` now works for a mock created with a tuple *spec*. * *spec_set*: A stricter variant of *spec*. If used, attempting to *set* or get an attribute on the mock that isn't on the object passed as diff --git a/Misc/NEWS.d/next/Library/2026-08-12-14-30-00.gh-issue-80762.Kp3vQa.rst b/Misc/NEWS.d/next/Library/2026-08-12-14-30-00.gh-issue-80762.Kp3vQa.rst index 724ed9f1919115c..080fee4855a5e27 100644 --- a/Misc/NEWS.d/next/Library/2026-08-12-14-30-00.gh-issue-80762.Kp3vQa.rst +++ b/Misc/NEWS.d/next/Library/2026-08-12-14-30-00.gh-issue-80762.Kp3vQa.rst @@ -1,3 +1,3 @@ -:class:`unittest.mock.Mock` now supports a tuple of strings as *spec*, -in addition to a list. -Previously :func:`dir` on such a mock raised :exc:`TypeError`. +Fix :func:`dir` on :class:`unittest.mock.Mock` objects +created with a tuple *spec*: it raised :exc:`TypeError`. +Such *spec* is now also documented.