ansible-later/env_27/lib/python2.7/site-packages/testfixtures/replace.py

142 lines
4.5 KiB
Python
Raw Normal View History

2019-04-11 13:56:20 +00:00
from functools import partial
from testfixtures.compat import ClassType
from testfixtures.resolve import resolve, not_there
from testfixtures.utils import wrap, extend_docstring
import warnings
def not_same_descriptor(x, y, descriptor):
return isinstance(x, descriptor) and not isinstance(y, descriptor)
class Replacer:
"""
These are used to manage the mocking out of objects so that units
of code can be tested without having to rely on their normal
dependencies.
"""
def __init__(self):
self.originals = {}
def _replace(self, container, name, method, value, strict=True):
if value is not_there:
if method == 'a':
try:
delattr(container, name)
except AttributeError:
pass
if method == 'i':
try:
del container[name]
except KeyError:
pass
else:
if method == 'a':
setattr(container, name, value)
if method == 'i':
container[name] = value
def __call__(self, target, replacement, strict=True):
"""
Replace the specified target with the supplied replacement.
"""
container, method, attribute, t_obj = resolve(target)
if method is None:
raise ValueError('target must contain at least one dot!')
if t_obj is not_there and strict:
raise AttributeError('Original %r not found' % attribute)
replacement_to_use = replacement
if isinstance(container, (type, ClassType)):
if not_same_descriptor(t_obj, replacement, classmethod):
replacement_to_use = classmethod(replacement)
elif not_same_descriptor(t_obj, replacement, staticmethod):
replacement_to_use = staticmethod(replacement)
self._replace(container, attribute, method, replacement_to_use, strict)
if target not in self.originals:
self.originals[target] = t_obj
return replacement
def replace(self, target, replacement, strict=True):
"""
Replace the specified target with the supplied replacement.
"""
self(target, replacement, strict)
def restore(self):
"""
Restore all the original objects that have been replaced by
calls to the :meth:`replace` method of this :class:`Replacer`.
"""
for target, original in tuple(self.originals.items()):
container, method, attribute, found = resolve(target)
self._replace(container, attribute, method, original, strict=False)
del self.originals[target]
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
self.restore()
def __del__(self):
if self.originals:
# no idea why coverage misses the following statement
# it's covered by test_replace.TestReplace.test_replacer_del
warnings.warn( # pragma: no cover
'Replacer deleted without being restored, '
'originals left: %r' % self.originals
)
def replace(target, replacement, strict=True):
"""
A decorator to replace a target object for the duration of a test
function.
"""
r = Replacer()
return wrap(partial(r.__call__, target, replacement, strict), r.restore)
class Replace(object):
"""
A context manager that uses a :class:`Replacer` to replace a single target.
"""
def __init__(self, target, replacement, strict=True):
self.target = target
self.replacement = replacement
self.strict = strict
self._replacer = Replacer()
def __enter__(self):
return self._replacer(self.target, self.replacement, self.strict)
def __exit__(self, exc_type, exc_val, exc_tb):
self._replacer.restore()
replace_params_doc = """
:param target: A string containing the dotted-path to the
object to be replaced. This path may specify a
module in a package, an attribute of a module,
or any attribute of something contained within
a module.
:param replacement: The object to use as a replacement.
:param strict: When `True`, an exception will be raised if an
attempt is made to replace an object that does
not exist.
"""
# add the param docs, so we only have one copy of them!
extend_docstring(replace_params_doc,
[Replacer.__call__, Replacer.replace, replace, Replace])