Skip to content

Commit 793f7d0

Browse files
DinoVfacebook-github-bot
authored andcommitted
Port mock performance improvements
Summary: It looks like updating to Mock 3.8 has caused some perf regressions. There's some known issues called out here:https://bugs.python.org/issue38895 - this: * Don't use `inspect` to get `spec` and `spec_sec` - instead now we fully spell out the signature and use the args directly. * provides a variation on not reflecting over all async methods on creation. The `_spec_asyncs` list still exists, but it is just not populated by default. Instead we'll consult it or check if the function is async. * Don't create the mock code object on instantiation of the `AsyncMock` - this is actually a huge portion of the cost of creating `AsyncMock` objects. * Avoid quadruple `self._mock_set_magics()` calls. This is actually done in the `MagicMixin` class so there's no need to do it in the `AsyncMagicMixin` Reviewed By: carljm Differential Revision: D35118477 fbshipit-source-id: 38779e0
1 parent 3342e16 commit 793f7d0

File tree

1 file changed

+14
-22
lines changed

1 file changed

+14
-22
lines changed

Lib/unittest/mock.py

+14-22
Original file line numberDiff line numberDiff line change
@@ -398,22 +398,19 @@ def __init__(self, /, *args, **kwargs):
398398
class NonCallableMock(Base):
399399
"""A non-callable version of `Mock`"""
400400

401-
def __new__(cls, /, *args, **kw):
401+
def __new__(cls, spec=None, wraps=None, name=None, spec_set=None,
402+
parent=None, _spec_state=None, _new_name='', _new_parent=None,
403+
_spec_as_instance=False, _eat_self=None, unsafe=False, **kwargs):
402404
# every instance has its own class
403405
# so we can create magic methods on the
404406
# class without stomping on other mocks
405407
bases = (cls,)
406408
if not issubclass(cls, AsyncMock):
407409
# Check if spec is an async object or function
408-
sig = inspect.signature(NonCallableMock.__init__)
409-
bound_args = sig.bind_partial(cls, *args, **kw).arguments
410-
spec_arg = [
411-
arg for arg in bound_args.keys()
412-
if arg.startswith('spec')
413-
]
414-
if spec_arg:
410+
spec_arg = spec_set or spec
411+
if spec_arg is not None:
415412
# what if spec_set is different than spec?
416-
if _is_async_obj(bound_args[spec_arg[0]]):
413+
if _is_async_obj(spec_arg):
417414
bases = (AsyncMockMixin, cls,)
418415
new = type(cls.__name__, bases, {'__doc__': cls.__doc__})
419416
instance = _safe_super(NonCallableMock, cls).__new__(new)
@@ -495,10 +492,6 @@ def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False,
495492
_spec_signature = None
496493
_spec_asyncs = []
497494

498-
for attr in dir(spec):
499-
if asyncio.iscoroutinefunction(getattr(spec, attr, None)):
500-
_spec_asyncs.append(attr)
501-
502495
if spec is not None and not _is_list(spec):
503496
if isinstance(spec, type):
504497
_spec_class = spec
@@ -995,7 +988,8 @@ def _get_child_mock(self, /, **kw):
995988
For non-callable mocks the callable variant will be used (rather than
996989
any custom subclass)."""
997990
_new_name = kw.get("_new_name")
998-
if _new_name in self.__dict__['_spec_asyncs']:
991+
if (_new_name in self.__dict__['_spec_asyncs'] or
992+
asyncio.iscoroutinefunction(getattr(self.__dict__['_spec_class'], _new_name, None))):
999993
return AsyncMock(**kw)
1000994

1001995
_type = type(self)
@@ -1038,7 +1032,6 @@ def _calls_repr(self, prefix="Calls"):
10381032
return f"\n{prefix}: {safe_repr(self.mock_calls)}."
10391033

10401034

1041-
10421035
def _try_iter(obj):
10431036
if obj is None:
10441037
return obj
@@ -2044,10 +2037,7 @@ def mock_add_spec(self, spec, spec_set=False):
20442037

20452038

20462039
class AsyncMagicMixin(MagicMixin):
2047-
def __init__(self, /, *args, **kw):
2048-
self._mock_set_magics() # make magic work for kwargs in init
2049-
_safe_super(AsyncMagicMixin, self).__init__(*args, **kw)
2050-
self._mock_set_magics() # fix magic broken by upper level init
2040+
pass
20512041

20522042
class MagicMock(MagicMixin, Mock):
20532043
"""
@@ -2106,9 +2096,7 @@ def __init__(self, /, *args, **kwargs):
21062096
self.__dict__['_mock_await_count'] = 0
21072097
self.__dict__['_mock_await_args'] = None
21082098
self.__dict__['_mock_await_args_list'] = _CallList()
2109-
code_mock = NonCallableMock(spec_set=CodeType)
2110-
code_mock.co_flags = inspect.CO_COROUTINE
2111-
self.__dict__['__code__'] = code_mock
2099+
self.__dict__['__code__'] = _CODE_DUMMY
21122100

21132101
async def _execute_mock_call(self, /, *args, **kwargs):
21142102
# This is nearly just like super(), except for sepcial handling
@@ -2727,6 +2715,10 @@ def __init__(self, spec, spec_set=False, parent=None,
27272715
file_spec = None
27282716

27292717

2718+
async def _dummy_async(): pass
2719+
_CODE_DUMMY = _dummy_async.__code__
2720+
2721+
27302722
def _to_stream(read_data):
27312723
if isinstance(read_data, bytes):
27322724
return io.BytesIO(read_data)

0 commit comments

Comments
 (0)