def mock_constructor(target, class_name): if not _is_string(class_name): raise ValueError( "Second argument must be a string with the name of the class.") if _is_string(target): target = testslide._importer(target) mocked_class_id = (id(target), class_name) if mocked_class_id in _mocked_classes: original_class, mocked_class = _mocked_classes[mocked_class_id] if not getattr(target, class_name) is mocked_class: raise AssertionError( "The class {} at {} was changed after mock_constructor() mocked " "it!".format(class_name, target)) callable_mock = getattr(mocked_class, "__new__") else: original_class = getattr(target, class_name) if not inspect.isclass(original_class): raise ValueError("Target must be a class.") def unpatcher(): setattr(target, class_name, original_class) del _mocked_classes[mocked_class_id] _unpatchers.append(unpatcher) callable_mock = _CallableMock(original_class, "__new__") mocked_class = type( str(original_class.__name__ + "Mock"), (original_class, ), {"__new__": callable_mock}, ) setattr(target, class_name, mocked_class) _mocked_classes[mocked_class_id] = (original_class, mocked_class) def original_callable(_, *args, **kwargs): return original_class(*args, **kwargs) return _MockCallableDSL( mocked_class, "__new__", callable_mock=callable_mock, original_callable=original_callable, prepend_first_arg=ANY, )
def mock_constructor(target, class_name, allow_private=False): if not isinstance(class_name, str): raise ValueError( "Second argument must be a string with the name of the class.") _bail_if_private(class_name, allow_private) if isinstance(target, str): target = testslide._importer(target) target_class_id = (id(target), class_name) if target_class_id in _mocked_target_classes: original_class, mocked_class = _mocked_target_classes[target_class_id] if not getattr(target, class_name) is mocked_class: raise AssertionError( "The class {} at {} was changed after mock_constructor() mocked " "it!".format(class_name, target)) callable_mock = mocked_class.__new__ else: original_class = getattr(target, class_name) if "__new__" in original_class.__dict__: raise NotImplementedError( "Usage with classes that define __new__() is currently not supported." ) gc.collect() instances = [ obj for obj in gc.get_referrers(original_class) if type(obj) is original_class ] if instances: raise RuntimeError( "mock_constructor() can not be used after instances of {} were created: {}" .format(class_name, instances)) if not inspect.isclass(original_class): raise ValueError("Target must be a class.") elif not issubclass(original_class, object): raise ValueError("Old style classes are not supported.") callable_mock = _CallableMock(original_class, "__new__") mocked_class = _patch_and_return_mocked_class(target, class_name, target_class_id, original_class, callable_mock) def original_callable(cls, *args, **kwargs): global _init_args_from_original_callable, _init_kwargs_from_original_callable assert cls is mocked_class # Python unconditionally calls __init__ with the same arguments as # __new__ once it is invoked. We save the correct arguments here, # so that __init__ can use them when invoked for the first time. _init_args_from_original_callable = args _init_kwargs_from_original_callable = kwargs return object.__new__(cls) return _MockConstructorDSL( target=mocked_class, method="__new__", cls=mocked_class, callable_mock=callable_mock, original_callable=original_callable, )
def mock_constructor( target: str, class_name: str, allow_private: bool = False, type_validation: bool = True, ) -> _MockConstructorDSL: if not isinstance(class_name, str): raise ValueError("Second argument must be a string with the name of the class.") _bail_if_private(class_name, allow_private) if isinstance(target, str): target = testslide._importer(target) target_class_id = (id(target), class_name) if target_class_id in _mocked_target_classes: original_class, mocked_class = _mocked_target_classes[target_class_id] if not getattr(target, class_name) is mocked_class: raise AssertionError( "The class {} at {} was changed after mock_constructor() mocked " "it!".format(class_name, target) ) callable_mock = mocked_class.__new__ else: original_class = getattr(target, class_name) if "__new__" in original_class.__dict__: raise NotImplementedError( "Usage with classes that define __new__() is currently not supported." ) gc.collect() instances = [ obj for obj in gc.get_referrers(original_class) if type(obj) is original_class ] if instances: raise RuntimeError( "mock_constructor() can not be used after instances of {} were created: {}".format( class_name, instances ) ) if not inspect.isclass(original_class): raise ValueError("Target must be a class.") elif not issubclass(original_class, object): raise ValueError("Old style classes are not supported.") caller_frame = inspect.currentframe().f_back # type: ignore # loading the context ends up reading files from disk and that might block # the event loop, so we don't do it. caller_frame_info = inspect.getframeinfo(caller_frame, context=0) # type: ignore callable_mock = _CallableMock(original_class, "__new__", caller_frame_info) mocked_class = _patch_and_return_mocked_class( target, class_name, target_class_id, original_class, callable_mock, type_validation, ) def original_callable(cls: type, *args: Any, **kwargs: Any) -> Any: global _init_args_from_original_callable, _init_kwargs_from_original_callable assert cls is mocked_class # Python unconditionally calls __init__ with the same arguments as # __new__ once it is invoked. We save the correct arguments here, # so that __init__ can use them when invoked for the first time. _init_args_from_original_callable = args _init_kwargs_from_original_callable = kwargs return object.__new__(cls) return _MockConstructorDSL( target=mocked_class, method="__new__", cls=mocked_class, callable_mock=callable_mock, original_callable=original_callable, )