def change_detection(cls):
class NonExistentAttribute(object):
pass
class JNoneMeta(type):
def __subclasscheck__(currentCls, parentCls):
return currentCls == JNone and parentCls == type(None)
class JBoolMeta(type):
def __subclasscheck__(currentCls, parentCls):
return currentCls == JBool and parentCls == type(bool)
class JInt(int):
pass
class JString(str):
pass
class JBool(object, metaclass = JBoolMeta):
def __init__(self, value):
self._value = value
def __bool__(self):
return type(self._value) == type(bool) and self._value
def __eq__(self, value):
return self._value == value
class JNone(object, metaclass = JNoneMeta):
def __bool__(self):
return False
def __eq__(self, value):
return value == None
class Journaled(cls):
@staticmethod
def createAttribute(value, state):
if value == None:
value = JNone()
elif isinstance(value, bool):
value = JBool(value)
elif isinstance(value, int):
value = JInt(value)
elif isinstance(value, str):
value = JString(value)
try: # for functions/methods but allows for lambda
value.get_change = state
except AttributeError:
pass
return value
def __init__(self, *args, **kwargs):
super().__setattr__("__modified__", set())
super().__setattr__("__deleted__", set())
super().__init__(*args, **kwargs)
def __getattribute__(self, name):
try:
v = super().__getattribute__(name)
except AttributeError:
v = NonExistentAttribute()
if not name.startswith("__"):
if name in self.__deleted__:
s = "DEL"
elif name in self.__modified__:
s = "MOD"
else:
s = "INIT" if type(v) != NonExistentAttribute else ""
return Journaled.createAttribute(v, s)
return v
def __setattr__(self, name, value):
if not name.startswith("__") or name not in self.__modified__:
try:
v = self.__getattribute__(name)
if type(v) != NonExistentAttribute and (v != value or typesAreDifferent(type(v), type(value))):
self.__modified__.add(name)
except AttributeError:
pass
super().__setattr__(name, value)
def __delattr__(self, name):
if name in self.__modified__:
self.__modified__.remove(name)
if hasattr(self, name):
self.__deleted__.add(name)
super().__setattr__(name, None)
def typesAreDifferent(subClass, parentClass):
return not (issubclass(subClass, parentClass) or issubclass(parentClass, subClass))
#copy original class attributes to Journaled class
for clsAttr in filter(lambda x: not x.startswith("__"), dir(cls)):
setattr(Journaled, clsAttr, cls.__dict__[clsAttr])
return Journaled
@change_detection
class Struct:
x = 42
def __init__(self, y=0):
self.y = y
a = Struct(11)
print("Struct.x ", Struct.x)
# Struct.x.get_change - will not be tested
a.x = 114
a.x.get_change == a.y.get_change == 'INIT'
Struct.x = 0
print(a.x)
a.z.get_change == ''
a.y = 11
a.y.get_change == 'INIT'
a.y = 12
a.y.get_change == 'MOD'
a.x = '42'
a.x.get_change == 'MOD'
del a.y
a.y.get_change == 'DEL'
To embed this program on your website, copy the following code and paste it into your website's HTML: