Created
August 22, 2011 02:16
-
-
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.
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
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 |
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
#!/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