Skip to content

Commit 733b7b3

Browse files
Carl Meyerfacebook-github-bot
authored andcommitted
Port mock performance improvements
Summary: Modified port of D35118477 (793f7d0) from 3.8, and backport of upstream PR python/cpython#100252 Changes from the 3.8 version: - We don't need to keep `_spec_asyncs` around at all, it's not public API. - Changing the `__code__` attribute of an `AsyncMock` to be a real code object instead of a mock is probably fine, but it's technically backwards incompatible and could break someone's test suite, so I wasn't sure upstream would go for it. Instead we can create the mock object in a more manual way that avoids a lot of the introspection cost (by doing it just once up front.) Reviewed By: mpage Differential Revision: D42039568 fbshipit-source-id: a939f9c
1 parent e9c14b0 commit 733b7b3

File tree

1 file changed

+17
-18
lines changed

1 file changed

+17
-18
lines changed

Lib/unittest/mock.py

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -401,15 +401,18 @@ def __init__(self, /, *args, **kwargs):
401401
class NonCallableMock(Base):
402402
"""A non-callable version of `Mock`"""
403403

404-
def __new__(cls, /, *args, **kw):
404+
def __new__(
405+
cls, spec=None, wraps=None, name=None, spec_set=None,
406+
parent=None, _spec_state=None, _new_name='', _new_parent=None,
407+
_spec_as_instance=False, _eat_self=None, unsafe=False, **kwargs
408+
):
405409
# every instance has its own class
406410
# so we can create magic methods on the
407411
# class without stomping on other mocks
408412
bases = (cls,)
409413
if not issubclass(cls, AsyncMockMixin):
410414
# Check if spec is an async object or function
411-
bound_args = _MOCK_SIG.bind_partial(cls, *args, **kw).arguments
412-
spec_arg = bound_args.get('spec_set', bound_args.get('spec'))
415+
spec_arg = spec_set or spec
413416
if spec_arg is not None and _is_async_obj(spec_arg):
414417
bases = (AsyncMockMixin, cls)
415418
new = type(cls.__name__, bases, {'__doc__': cls.__doc__})
@@ -490,11 +493,6 @@ def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False,
490493
_eat_self=False):
491494
_spec_class = None
492495
_spec_signature = None
493-
_spec_asyncs = []
494-
495-
for attr in dir(spec):
496-
if iscoroutinefunction(getattr(spec, attr, None)):
497-
_spec_asyncs.append(attr)
498496

499497
if spec is not None and not _is_list(spec):
500498
if isinstance(spec, type):
@@ -512,7 +510,6 @@ def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False,
512510
__dict__['_spec_set'] = spec_set
513511
__dict__['_spec_signature'] = _spec_signature
514512
__dict__['_mock_methods'] = spec
515-
__dict__['_spec_asyncs'] = _spec_asyncs
516513

517514
def __get_return_value(self):
518515
ret = self._mock_return_value
@@ -1001,7 +998,8 @@ def _get_child_mock(self, /, **kw):
1001998
For non-callable mocks the callable variant will be used (rather than
1002999
any custom subclass)."""
10031000
_new_name = kw.get("_new_name")
1004-
if _new_name in self.__dict__['_spec_asyncs']:
1001+
_spec_val = getattr(self.__dict__["_spec_class"], _new_name, None)
1002+
if _spec_val is not None and asyncio.iscoroutinefunction(_spec_val):
10051003
return AsyncMock(**kw)
10061004

10071005
if self._mock_sealed:
@@ -1043,9 +1041,6 @@ def _calls_repr(self, prefix="Calls"):
10431041
return f"\n{prefix}: {safe_repr(self.mock_calls)}."
10441042

10451043

1046-
_MOCK_SIG = inspect.signature(NonCallableMock.__init__)
1047-
1048-
10491044
class _AnyComparer(list):
10501045
"""A list which checks if it contains a call which may have an
10511046
argument of ANY, flipping the components of item and self from
@@ -2121,10 +2116,8 @@ def mock_add_spec(self, spec, spec_set=False):
21212116

21222117

21232118
class AsyncMagicMixin(MagicMixin):
2124-
def __init__(self, /, *args, **kw):
2125-
self._mock_set_magics() # make magic work for kwargs in init
2126-
_safe_super(AsyncMagicMixin, self).__init__(*args, **kw)
2127-
self._mock_set_magics() # fix magic broken by upper level init
2119+
pass
2120+
21282121

21292122
class MagicMock(MagicMixin, Mock):
21302123
"""
@@ -2166,6 +2159,10 @@ def __get__(self, obj, _type=None):
21662159
return self.create_mock()
21672160

21682161

2162+
_CODE_ATTRS = dir(CodeType)
2163+
_CODE_SIG = inspect.signature(partial(CodeType.__init__, None))
2164+
2165+
21692166
class AsyncMockMixin(Base):
21702167
await_count = _delegating_property('await_count')
21712168
await_args = _delegating_property('await_args')
@@ -2183,7 +2180,9 @@ def __init__(self, /, *args, **kwargs):
21832180
self.__dict__['_mock_await_count'] = 0
21842181
self.__dict__['_mock_await_args'] = None
21852182
self.__dict__['_mock_await_args_list'] = _CallList()
2186-
code_mock = NonCallableMock(spec_set=CodeType)
2183+
code_mock = NonCallableMock(spec_set=_CODE_ATTRS)
2184+
code_mock.__dict__["_spec_class"] = CodeType
2185+
code_mock.__dict__["_spec_signature"] = _CODE_SIG
21872186
code_mock.co_flags = inspect.CO_COROUTINE
21882187
self.__dict__['__code__'] = code_mock
21892188

0 commit comments

Comments
 (0)