Created
September 9, 2009 20:18
-
-
Save grampelberg/184044 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys | |
import time | |
import types | |
from traceback import extract_stack, format_list | |
def wrap_func(base_class, f): | |
old_func_name = 'old%s' % (f.__name__,) | |
if hasattr(base_class, f.__name__): | |
setattr(base_class, old_func_name, | |
getattr(base_class, f.__name__)) | |
def new_func(*args, **kwargs): | |
f(*args, **kwargs) | |
if hasattr(base_class, old_func_name): | |
return getattr(base_class, old_func_name)(*args, **kwargs) | |
setattr(base_class, f.__name__, new_func) | |
def wrap_attr(base_class, key, value): | |
setattr(base_class, '__record%s' % (key,), value) | |
class state: | |
def __init__(self, key, action, value, stack, when): | |
self.key = key | |
self.action = action | |
self.when = when | |
self.value = value | |
self.stack = stack | |
def record(verbose=False, output=sys.stdout.write): | |
"""Record the interactions on a specific class. | |
This method will take a class and wrap __setattr__ and record any time | |
a __setattr__ occurs with the name, value, time and *where* it was set | |
from via. the stack. Any potential name clashes after being wrapped | |
first call the wrapper's functions and then the base class'. The | |
eventual, return value will be from your function and not this | |
class. To get any object's history after being wrapped by this class, | |
just call dump_history(). | |
Usage: | |
@record | |
class A: | |
foo = 1 | |
bar = A() | |
bar.foo = 2 | |
bar.dump_history() | |
At runtime (or in the interpreter), instead of using the class | |
decorator, you can use: A = record(A) | |
Note that __getattribute__/__getattr__ aren't supported. | |
:Parameters: | |
- `verbose`: Output state to the configured output stream each state | |
change. Default = False | |
- `output`: Method to call with the output | |
(ex. sys.stderr.write, logging.debug). | |
Default = sys.stdout.write | |
""" | |
local_params = locals() | |
def modify_class(base_class): | |
def __init__(self, *args, **kwargs): | |
self._history = [] | |
def __save_state(self, key, value, action): | |
if key != '_history': | |
instant = state(key, action, repr(value), extract_stack()[:-4], | |
time.time()) | |
if self.__recordverbose: | |
self.output_history([instant], False) | |
self._history.append(instant) | |
def __setattr__(self, key, value): | |
self.__save_state(key, value, '__setattr__') | |
if not hasattr(self, 'old__setattr__'): | |
self.__dict__[key] = value | |
def __delattr__(self, key): | |
self.__save_state(key, getattr(self, key), '__delattr__') | |
def attr_history(self, key, stack=False): | |
self.output_history([x for x in self._history if x.key == key], | |
stack) | |
def dump_history(self, stack=False): | |
self.output_history(self._history, stack) | |
def output_history(self, history, stack=False): | |
for instant in history: | |
self.__recordoutput( | |
'%s\t%s\t%s=%s\n' % (instant.when, instant.action, | |
instant.key, instant.value)) | |
if stack: | |
self.__recordoutput( | |
'%s\n' % ''.join(format_list(instant.stack))) | |
local_params.update(locals()) | |
for key, value in local_params.iteritems(): | |
if isinstance(value, types.FunctionType): | |
wrap_func(base_class, value) | |
else: | |
wrap_attr(base_class, key, value) | |
return base_class | |
return modify_class |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment