Given an object A, which contains a callable object B, is there a way to determine "A" from inside B.__call__()
?
This is for use in test injection, where A.B is originally a method.
The code is something like this:
# Class to be tested.
class Outer(object):
def __init__(self, arg):
self.arg = arg
def func(self, *args, **kwargs):
print ('Outer self: %r' % (self,))
# Framework-provided:
class Injected(object):
def __init__(self):
self._replay = False
self._calls = []
def replay(self):
self._replay = True
self._calls.reverse()
def __call__(self, *args, **kwargs):
if not self._replay:
expected = (args[0], args[1:], kwargs)
self._calls.append(expected)
else:
expected_s, expected_a, expected_k = self._calls.pop()
ok = True
# verify args, similar for expected_k == kwargs
ok = reduce(lambda x, comp: x and comp[0](comp[1]),
zip(expected_a, args),
ok)
# what to do for expected_s == self?
# ok = ok and expected_s(this) ### <= need "this". ###
if not ok:
raise Exception('Expectations not matched.')
# Inject:
setattr(Outer, 'func', Injected())
# Set expectations:
# - One object, initialised with "3", must pass "here" as 2nd arg.
# - One object, initialised with "4", must pass "whatever" as 1st arg.
Outer.func(lambda x: x.arg == 3, lambda x: True, lambda x: x=='here')
Outer.func(lambda x: x.arg == 4, lambda x: x=='whatever', lambda x: True)
Outer.func.replay()
# Invoke other code, which ends up doing:
o = Outer(3)
p = Outer(5)
o.func('something', 'here')
p.func('whatever', 'next') # <- This should fail, 5 != 4
The question is: Is there a way (black magic is fine) within Injected.__call__()
to access what "self" would have been in non-overwritten Outer.func()
, to use as "this" (line marked with "###")?
Naturally, the code is a bit more complex (the calls can be configured to be in arbitrary order, return values can be set, etc.), but this is the minimal example I could come up with that demonstrates both the problem and most of the constraints.
I cannot inject a function with a default argument instead of Outer.func
- that breaks recording (if a function were injected, it'd be an unbound method and require an instance of "Outer" as its first argument, rather than a comparator/validator).
Theoretically, I could mock out "Outer" completely for its caller. However, setting up the expectations there would probably be more code, and not reusable elsewhere - any similar case/project would also have to reimplement "Outer" (or its equivalent) as a mock.