├── README.md ├── inject.py └── test_inject.py /README.md: -------------------------------------------------------------------------------- 1 | #Inject python locals# 2 | 3 | 4 | A bit of hackery to experiment with alternative call flow in python. 5 | 6 | To use: 7 | 8 | def foo(): 9 | print a 10 | 11 | inject(a=5).into(foo) 12 | >> 5 13 | 14 | @inject(cat='man') 15 | def bar(): 16 | print "the cat is a", cat 17 | 18 | bar() 19 | >> the cat is a man 20 | 21 | @inject(a=5) 22 | def baz(a=6): 23 | print a 24 | 25 | baz() 26 | >> 5 27 | 28 | 29 | **Q: Why should I use this?** 30 | 31 | A: You shouldn't 32 | 33 | **Q: Why does it exist?** 34 | 35 | A: Because it can 36 | 37 | **Q: Are you crazy?** 38 | 39 | A: ? 40 | 41 | **Q: Is it good?** 42 | 43 | A: yes 44 | -------------------------------------------------------------------------------- /inject.py: -------------------------------------------------------------------------------- 1 | 2 | import sys 3 | 4 | class inject(object): 5 | def __init__(self, **kwargs): 6 | self.kwargs = kwargs 7 | 8 | def __call__(self, fn): 9 | from functools import wraps 10 | @wraps(fn) 11 | def wrapped(*args, **kwargs): 12 | old_trace = sys.gettrace() 13 | def tracefn(frame, event, arg): 14 | # define a new tracefn each time, since it needs to 15 | # call the *current* tracefn if they're in debug mode 16 | frame.f_locals.update(self.kwargs) 17 | if old_trace: 18 | return old_trace(frame, event, arg) 19 | else: 20 | return None 21 | 22 | sys.settrace(tracefn) 23 | try: 24 | retval = fn(*args, **kwargs) 25 | finally: 26 | sys.settrace(old_trace) 27 | return retval 28 | 29 | return wrapped 30 | 31 | def into(self, fn, *args, **kwargs): 32 | return self(fn)(*args, **kwargs) 33 | 34 | -------------------------------------------------------------------------------- /test_inject.py: -------------------------------------------------------------------------------- 1 | 2 | from inject import inject 3 | 4 | @inject(a=5) 5 | def test_inject(): 6 | print locals() 7 | assert locals() == {'a': 5} 8 | 9 | @inject(cat='foo', whatever=15) 10 | def test_inject2(): 11 | print locals() 12 | assert locals() == {'cat': 'foo', 'whatever': 15} 13 | 14 | def test_inject_keeps_old_trace_behavior(): 15 | import sys 16 | events = [] 17 | def tracefn(frame, event, arg): 18 | # only grab the events for our function 19 | if frame.f_code.co_name == 'foo': 20 | events.append(event) 21 | return tracefn 22 | 23 | @inject(a=5) 24 | def foo(): 25 | assert locals() == {'a': 5} 26 | 27 | # be a good citizen and reset our tracefn 28 | old_trace = sys.gettrace() 29 | sys.settrace(tracefn) 30 | foo() 31 | assert sys.gettrace() == tracefn 32 | sys.settrace(old_trace) 33 | assert events == ['call', 'line', 'return'] 34 | 35 | 36 | def test_inject_apply(): 37 | 38 | def foo(): 39 | return locals() 40 | 41 | assert inject(a=5).into(foo) == {'a': 5} 42 | 43 | 44 | @inject(a='cat') 45 | def test_binding_order(a=6): 46 | assert a == 'cat' 47 | 48 | def test_inject_works_with_exception(): 49 | class MyException(Exception): pass 50 | 51 | @inject(bar=1) 52 | def foo(): 53 | print locals() 54 | assert locals() == dict(bar=1, MyException=MyException) 55 | raise MyException('hi') 56 | 57 | @inject(baz=2) 58 | def fuz(): 59 | return locals() == dict(baz=2, MyException=MyException) 60 | 61 | # if inject doesn't clean up correctly it will leak a tracefn 62 | try: 63 | foo() 64 | assert False, "foo() was supposed to raise an exception" 65 | except MyException, e: 66 | assert True, "foo raised an exception correctly" 67 | 68 | assert fuz(), "and the tracefn was cleaned up correctly" 69 | 70 | def test_inject_cleans_up_tracefn_on_except(): 71 | import sys 72 | old_trace = sys.gettrace() 73 | @inject(a=5) 74 | def foo(): 75 | raise Exception() 76 | 77 | try: 78 | foo() 79 | assert False, "should have raised an exception" 80 | except: 81 | pass 82 | assert sys.gettrace() == old_trace 83 | sys.settrace(old_trace) 84 | 85 | 86 | --------------------------------------------------------------------------------