Skip to content

Instantly share code, notes, and snippets.

@rmg
Created August 22, 2011 02:16
Show Gist options
  • Save rmg/1161517 to your computer and use it in GitHub Desktop.
Save rmg/1161517 to your computer and use it in GitHub Desktop.
Base class extensions to allow versioning and logging of operations on base types.
http://code.google.com/p/google-diff-match-patch/
http://stackoverflow.com/questions/4599456/textually-diffing-json/4599500#4599500
http://docs.python.org/reference/datamodel.html#new-style-and-classic-classes
#!/usr/bin/env python2.7
# I fully acknowledge that this is probably a really really bad idea..
"""
doc = getObjFromServer('obj-name')
mydoc = committable(doc)
mydoc['users_count'] += 1
mydoc['users'].append('newuser')
for retry in xrange(3):
try:
success = sendObjToServer('obj-name', mydoc)
except Conflict:
mydoc.rebase(getObjFromServer)
if not success:
raise Exception("Even version control couldn't save us this time")
"""
class Committable(object):
def __init__(self, orig):
self.ops = []
self.orig = orig
self.cur = self.orig
def rebase(self, new_orig):
self.orig = new_orig
self.cur = self.orig
for operation in self.ops:
self.cur = operation(self.cur)
@classmethod
def operation(cls, ifn):
"""
__iadd__ = Committable.operation(lambda l,r: l + int(r))
@Committable.operation
def __idiv__(self, other):
return self.cur / other
"""
#print "wrapping fn in " + cls.__name__
def f(self, *args):
#print "calling wrapped " + repr(ifn) + " on " + self.__class__.__name__
op = lambda lhs: ifn(lhs, *args)
self.ops.append(op)
self.cur = op(self.cur)
return self
return f
@classmethod
def proxy(cls, ifn):
"""Wrapper that prepends self.cur to given function call"""
def f(self, *args):
return ifn(self.cur, *args)
return f
@classmethod
def rproxy(cls, ifn):
"""Wrapper that prepends self.cur to given function call"""
def f(self, *args):
rargs = list((self.cur,) + args)
rargs.reverse()
return ifn(*tuple(rargs))
return f
def __repr__(self):
return "<{0} base: {1}, cur: {2}, ops: {3}>".format(
self.__class__.__name__,
self.orig, self.cur,
len(self.ops))
def __str__(self):
return str(self.cur)
def __int__(self):
return int(self.cur)
class Int(Committable):
"""Extension of int that records operations for later rebasing"""
# implementing __cmp__ is redundant because the __lt__, __le__, etc. are
# implemented below
def __cmp__(self, other):
return int(self) - int(other)
import operator
inplace_operators = [
'__iadd__', '__isub__', '__imul__', '__idiv__',
'__itruediv__', '__ifloordiv__', '__imod__', '__ipow__',
'__ilshift__', '__irshift__',
'__iand__', '__ixor__', '__ior__']
numeric_operators = [
'__lt__', '__le__', '__eq__', '__ne__', '__ge__', '__gt__',
'__add__', '__sub__', '__mul__', '__div__',
'__truediv__', '__floordiv__', '__mod__', '__pow__',
'__lshift__', '__rshift__',
'__and__', '__xor__', '__or__',
'__neg__', '__pos__', '__abs__', '__invert__', '__index__']
reversed_operators = [
'add__', 'sub__', 'mul__', 'div__',
'truediv__', 'floordiv__', 'mod__', 'pow__',
'lshift__', 'rshift__',
'and__', 'xor__', 'or__']
builtins_operators = [
'complex', 'int', 'long', 'float',
'oct', 'hex', 'coerce']
for name in inplace_operators:
setattr(Int, name, Committable.operation(getattr(operator, name)))
for name in numeric_operators:
setattr(Int, name, Committable.proxy(getattr(operator, name)))
for suffix in reversed_operators:
setattr(Int, '__r' + suffix, Committable.rproxy(getattr(operator, '__' + suffix)))
for builtin in builtins_operators:
setattr(Int, '__' + builtin + '__', Committable.proxy(getattr(__builtins__, builtin)))
class Str(Committable, str):
"""Extended str that records operations for later replay"""
pass
class Bool(Int):
"""Extended bool type that records operations... whatever those may be"""
def __notzero__(self):
bool(self.cur)
def __truth__(self):
bool(self.cur)
# class List(Committable, list):
# """An extended list class that records operations as they happen for later rebasing"""
# pass
#
#
#
# class Dict(Committable, dict):
# """An extended dict class that records operations as they happen for later rebasing"""
# def __init__(self, oDict):
# Committable.__init__(self, oDict)
# self.reset(oDict)
# def reset(self, baseDict):
# for k, v in baseDict.items():
# dict.__setitem__(self, k, committable(v))
# def __setitem__(self, key, val):
# #record operation, deferring to val's logger
# #then store modified value here
# dict.__setitem__(self, key, val)
#
#
def committable(obj):
"""Box @obj in a versioned wrapper"""
mapping = {
int : lambda x: Int(x),
list : lambda x: List(x),
dict : lambda x: Dict(x),
str : lambda x: Str(x),
bool : lambda x: Bool(x)
}
return mapping[obj.__class__](obj)
import unittest
class IntTests(unittest.TestCase):
def do_math(self, number):
number += 10
number -= 2
number *= 6
number /= 3
number %= 7
return number
def runTest(self):
cint = committable(0)
cint = self.do_math(cint)
op_count = len(cint.ops)
for i in [1, 1, 2, 3, 5, 8]:
cint.rebase(i)
res = self.do_math(i)
self.assertEqual(res, cint)
self.assertEqual(cint, res)
self.assertEqual(op_count, len(cint.ops))
if __name__ == '__main__':
unittest.main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment