Skip to content

Instantly share code, notes, and snippets.

@grampelberg
Created September 9, 2009 20:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save grampelberg/184044 to your computer and use it in GitHub Desktop.
Save grampelberg/184044 to your computer and use it in GitHub Desktop.
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