Skip to content

Commit 38ce104

Browse files
dsemserhiy-storchakaclaude
authored
gh-80762: Fix dir() on Mock with tuple specs (GH-12753)
A tuple spec has been supported since the initial import and is used in tests, but it was never documented. Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent e96cf73 commit 38ce104

4 files changed

Lines changed: 18 additions & 7 deletions

File tree

Doc/library/unittest.mock.rst

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -231,16 +231,20 @@ the *new_callable* argument to :func:`patch`.
231231
Create a new :class:`Mock` object. :class:`Mock` takes several optional arguments
232232
that specify the behaviour of the Mock object:
233233

234-
* *spec*: This can be either a list of strings or an existing object (a
235-
class or instance) that acts as the specification for the mock object. If
236-
you pass in an object then a list of strings is formed by calling dir on
234+
* *spec*: This can be either a list or tuple of strings,
235+
or an existing object (a class or instance)
236+
that acts as the specification for the mock object.
237+
If you pass in an object then a list of strings is formed by calling dir on
237238
the object (excluding unsupported magic attributes and methods).
238239
Accessing any attribute not in this list will raise an :exc:`AttributeError`.
239240

240241
If *spec* is an object (rather than a list of strings) then
241242
:attr:`~object.__class__` returns the class of the spec object. This
242243
allows mocks to pass :func:`isinstance` tests.
243244

245+
.. versionchanged:: next
246+
:func:`dir` now works for a mock created with a tuple *spec*.
247+
244248
* *spec_set*: A stricter variant of *spec*. If used, attempting to *set*
245249
or get an attribute on the mock that isn't on the object passed as
246250
*spec_set* will raise an :exc:`AttributeError`.
@@ -448,9 +452,9 @@ the *new_callable* argument to :func:`patch`.
448452

449453
.. method:: mock_add_spec(spec, spec_set=False)
450454

451-
Add a spec to a mock. *spec* can either be an object or a
452-
list of strings. Only attributes on the *spec* can be fetched as
453-
attributes from the mock.
455+
Add a spec to a mock.
456+
*spec* can either be an object or a list or tuple of strings.
457+
Only attributes on the *spec* can be fetched as attributes from the mock.
454458

455459
If *spec_set* is true then only attributes on the spec can be set.
456460

Lib/test/test_unittest/testmock/testmock.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,6 +1073,10 @@ def test_dir(self):
10731073
mock.__iter__ = lambda s: iter([])
10741074
self.assertIn('__iter__', dir(mock))
10751075

1076+
# spec from a tuple
1077+
mock_tuple_spec = Mock(spec=('something',))
1078+
self.assertIn('something', dir(mock_tuple_spec))
1079+
10761080

10771081
def test_dir_from_spec(self):
10781082
mock = Mock(spec=unittest.TestCase)

Lib/unittest/mock.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -798,7 +798,7 @@ def __dir__(self):
798798
from_type = [e for e in from_type if not e.startswith('_')]
799799
from_dict = [e for e in from_dict if not e.startswith('_') or
800800
_is_magic(e)]
801-
return sorted(set(extras + from_type + from_dict + from_child_mocks))
801+
return sorted({*extras, *from_type, *from_dict, *from_child_mocks})
802802

803803

804804
def __setattr__(self, name, value):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :func:`dir` on :class:`unittest.mock.Mock` objects
2+
created with a tuple *spec*: it raised :exc:`TypeError`.
3+
Such *spec* is now also documented.

0 commit comments

Comments
 (0)