From aae5f122b4b22fd3c07f3342652fe6e292f208b5 Mon Sep 17 00:00:00 2001 From: philemon Date: Tue, 8 Aug 2017 11:31:57 +0300 Subject: [PATCH] immutable attr improvements --- mmgen/addr.py | 16 +++++------ mmgen/obj.py | 70 +++++++++++++++++++++--------------------------- mmgen/tw.py | 19 +++++++------ mmgen/tx.py | 12 ++++----- test/tooltest.py | 10 ++++--- 5 files changed, 58 insertions(+), 69 deletions(-) diff --git a/mmgen/addr.py b/mmgen/addr.py index 6297a8ef..bfc7d040 100755 --- a/mmgen/addr.py +++ b/mmgen/addr.py @@ -91,18 +91,16 @@ class KeyGeneratorSecp256k1(KeyGenerator): return PubKey(hexlify(priv2pub(unhexlify(privhex),int(privhex.compressed))),compressed=privhex.compressed) class AddrListEntry(MMGenListItem): - reassign_ok = 'label', addr = MMGenListItemAttr('addr','BTCAddr') - idx = MMGenListItemAttr('idx','AddrIdx') - label = MMGenListItemAttr('label','TwComment') - sec = MMGenImmutableAttr('sec',PrivKey) + idx = MMGenImmutableAttr('idx','AddrIdx') + label = MMGenListItemAttr('label','TwComment',reassign_ok=True) + sec = MMGenListItemAttr('sec',PrivKey,typeconv=False) class PasswordListEntry(MMGenListItem): - reassign_ok = 'label', - passwd = MMGenImmutableAttr('passwd',unicode) # TODO: create Password type - idx = MMGenListItemAttr('idx','AddrIdx') - label = MMGenListItemAttr('label','TwComment') - sec = MMGenImmutableAttr('sec',PrivKey) + passwd = MMGenImmutableAttr('passwd',unicode,typeconv=False) # TODO: create Password type + idx = MMGenImmutableAttr('idx','AddrIdx') + label = MMGenListItemAttr('label','TwComment',reassign_ok=True) + sec = MMGenListItemAttr('sec',PrivKey,typeconv=False) class AddrListChksum(str,Hilite): color = 'pink' diff --git a/mmgen/obj.py b/mmgen/obj.py index cdf23da5..524a8da0 100755 --- a/mmgen/obj.py +++ b/mmgen/obj.py @@ -101,19 +101,13 @@ class MMGenObject(object): class MMGenList(list,MMGenObject): pass class MMGenDict(dict,MMGenObject): pass +# for attrs that are always present in the data instance +# reassignment and deletion forbidden class MMGenImmutableAttr(object): # Descriptor - typeconv = False - builtin_typeconv = False - - def __init__(self,name,dtype,typeconv=None,builtin_typeconv=None): - if typeconv is not None: - assert typeconv in (True,False) - self.typeconv = typeconv - if builtin_typeconv is not None: - assert builtin_typeconv - self.builtin_typeconv = builtin_typeconv - self.typeconv = False # override + def __init__(self,name,dtype,typeconv=True): + self.typeconv = typeconv + assert type(dtype) in (str,type) self.name = name self.dtype = dtype @@ -121,17 +115,16 @@ class MMGenImmutableAttr(object): # Descriptor return instance.__dict__[self.name] # forbid all reassignment - def chk_ok_set_attr(self,instance): - if hasattr(instance,self.name): - m = "Attribute '{}' of {} instance cannot be reassigned" - raise AttributeError(m.format(self.name,type(instance))) + def set_attr_ok(self,instance): + return not hasattr(instance,self.name) def __set__(self,instance,value): - self.chk_ok_set_attr(instance) + if not self.set_attr_ok(instance): + m = "Attribute '{}' of {} instance cannot be reassigned" + raise AttributeError(m.format(self.name,type(instance))) if self.typeconv: # convert type - instance.__dict__[self.name] = globals()[self.dtype](value) - elif self.builtin_typeconv: - instance.__dict__[self.name] = self.dtype(value) + instance.__dict__[self.name] = \ + globals()[self.dtype](value) if type(self.dtype) == str else self.dtype(value) else: # check type if type(value) != self.dtype: m = "Attribute '{}' of {} instance must of type {}" @@ -139,36 +132,35 @@ class MMGenImmutableAttr(object): # Descriptor instance.__dict__[self.name] = value def __delete__(self,instance): - if self.name in instance.delete_ok: - if self.name in instance.__dict__: - del instance.__dict__[self.name] - else: - m = "Atribute '{}' of {} instance cannot be deleted" - raise AttributeError(m.format(self.name,type(instance))) + m = "Atribute '{}' of {} instance cannot be deleted" + raise AttributeError(m.format(self.name,type(instance))) +# for attrs that might not be present in the data instance +# reassignment or deletion allowed if specified class MMGenListItemAttr(MMGenImmutableAttr): - typeconv = True - builtin_typeconv = False + def __init__(self,name,dtype,typeconv=True,reassign_ok=False,delete_ok=False): + self.reassign_ok = reassign_ok + self.delete_ok = delete_ok + MMGenImmutableAttr.__init__(self,name,dtype,typeconv=typeconv) # return None if attribute doesn't exist def __get__(self,instance,owner): try: return instance.__dict__[self.name] except: return None - # allow reassignment if value is None or attr in reassign_ok list - def chk_ok_set_attr(self,instance): - if hasattr(instance,self.name) and not ( - getattr(instance,self.name) == None or self.name in instance.reassign_ok - ): - m = "Attribute '{}' of {} instance cannot be reassigned" - raise AttributeError(m.format(self.name,type(instance))) + def set_attr_ok(self,instance): + return getattr(instance,self.name) == None or self.reassign_ok + + def __delete__(self,instance): + if self.delete_ok: + if self.name in instance.__dict__: + del instance.__dict__[self.name] + else: + MMGenImmutableAttr.__delete__(self,instance) class MMGenListItem(MMGenObject): - reassign_ok = () - delete_ok = () - def __init__(self,*args,**kwargs): if args: raise ValueError, 'Non-keyword args not allowed' @@ -576,8 +568,8 @@ class PrivKey(str,Hilite,InitErrors,MMGenObject): width = 64 trunc_ok = False - compressed = MMGenImmutableAttr('compressed',bool) - wif = MMGenImmutableAttr('wif',WifKey) + compressed = MMGenImmutableAttr('compressed',bool,typeconv=False) + wif = MMGenImmutableAttr('wif',WifKey,typeconv=False) def __new__(*args,**kwargs): # initialize with (priv_bin,compressed), WIF or self cls = args[0] diff --git a/mmgen/tw.py b/mmgen/tw.py index e98713a2..6acaec3f 100755 --- a/mmgen/tw.py +++ b/mmgen/tw.py @@ -40,17 +40,16 @@ class MMGenTrackingWallet(MMGenObject): class MMGenTwUnspentOutput(MMGenListItem): # attrs = 'txid','vout','amt','label','twmmid','addr','confs','scriptPubKey','days','skip' - reassign_ok = 'label','skip' - txid = MMGenListItemAttr('txid','BitcoinTxID') - vout = MMGenListItemAttr('vout',int,typeconv=False), - amt = MMGenListItemAttr('amt','BTCAmt'), - label = MMGenListItemAttr('label','TwComment'), - twmmid = MMGenListItemAttr('twmmid','TwMMGenID') - addr = MMGenListItemAttr('addr','BTCAddr'), - confs = MMGenListItemAttr('confs',int,typeconv=False), - scriptPubKey = MMGenListItemAttr('scriptPubKey','HexStr') + txid = MMGenImmutableAttr('txid','BitcoinTxID') + vout = MMGenImmutableAttr('vout',int,typeconv=False), + amt = MMGenImmutableAttr('amt','BTCAmt'), + label = MMGenListItemAttr('label','TwComment',reassign_ok=True), + twmmid = MMGenImmutableAttr('twmmid','TwMMGenID') + addr = MMGenImmutableAttr('addr','BTCAddr'), + confs = MMGenImmutableAttr('confs',int,typeconv=False), + scriptPubKey = MMGenImmutableAttr('scriptPubKey','HexStr') days = MMGenListItemAttr('days',int,typeconv=False), - skip = MMGenListItemAttr('skip',bool,typeconv=False), + skip = MMGenListItemAttr('skip',bool,typeconv=False,reassign_ok=True), wmsg = { 'no_spendable_outputs': """ diff --git a/mmgen/tx.py b/mmgen/tx.py index 9b85df46..a92f89c3 100755 --- a/mmgen/tx.py +++ b/mmgen/tx.py @@ -114,16 +114,14 @@ class DeserializedTX(OrderedDict,MMGenObject): # need to add MMGen types return OrderedDict.__init__(self, ((k,d[k]) for k in keys)) txio_attrs = { - 'reassign_ok': ('label',), - 'delete_ok': ('have_wif',), 'vout': MMGenListItemAttr('vout',int,typeconv=False), - 'amt': MMGenListItemAttr('amt','BTCAmt'), - 'label': MMGenListItemAttr('label','TwComment'), + 'amt': MMGenImmutableAttr('amt','BTCAmt'), + 'label': MMGenListItemAttr('label','TwComment',reassign_ok=True), 'mmid': MMGenListItemAttr('mmid','MMGenID'), - 'addr': MMGenListItemAttr('addr','BTCAddr'), - 'confs': MMGenListItemAttr('confs',int,builtin_typeconv=True), # long confs found in the wild, so convert + 'addr': MMGenImmutableAttr('addr','BTCAddr'), + 'confs': MMGenListItemAttr('confs',int,typeconv=True), # long confs exist in the wild, so convert 'txid': MMGenListItemAttr('txid','BitcoinTxID'), - 'have_wif': MMGenListItemAttr('have_wif',bool,typeconv=False) + 'have_wif': MMGenListItemAttr('have_wif',bool,typeconv=False,delete_ok=True) } class MMGenTX(MMGenObject): diff --git a/test/tooltest.py b/test/tooltest.py index 3fb99b32..afe2fb94 100755 --- a/test/tooltest.py +++ b/test/tooltest.py @@ -235,13 +235,15 @@ class MMGenToolTestSuite(object): die(1,red('Called process returned with an error (retcode %s)' % retcode)) return (a,a.rstrip())[bool(strip)] - def run_cmd_chk(self,name,f1,f2,kwargs='',extra_msg=''): + def run_cmd_chk(self,name,f1,f2,kwargs='',extra_msg='',strip_hex=False): idata = read_from_file(f1).rstrip() odata = read_from_file(f2).rstrip() ret = self.run_cmd(name,[odata],kwargs=kwargs,extra_msg=extra_msg) vmsg('In: ' + repr(odata)) vmsg('Out: ' + repr(ret)) - if ret == idata: ok() + def cmp_equal(a,b): + return (a.lstrip('0') == b.lstrip('0')) if strip_hex else (a == b) + if cmp_equal(ret,idata): ok() else: die(3,red( "Error: values don't match:\nIn: %s\nOut: %s" % (repr(idata),repr(ret)))) @@ -284,12 +286,12 @@ class MMGenToolTestSuite(object): def Strtob58(self,name): self.run_cmd_out(name,getrandstr(16)) def B58tostr(self,name,f1,f2): self.run_cmd_chk(name,f1,f2) def Hextob58(self,name): self.run_cmd_out(name,getrandhex(32)) - def B58tohex(self,name,f1,f2): self.run_cmd_chk(name,f1,f2) + def B58tohex(self,name,f1,f2): self.run_cmd_chk(name,f1,f2,strip_hex=True) def B58randenc(self,name): ret = self.run_cmd_out(name,Return=True) ok_or_die(ret,is_b58_str,'base 58 string') def Hextob32(self,name): self.run_cmd_out(name,getrandhex(24)) - def B32tohex(self,name,f1,f2): self.run_cmd_chk(name,f1,f2) + def B32tohex(self,name,f1,f2): self.run_cmd_chk(name,f1,f2,strip_hex=True) def Randhex(self,name): ret = self.run_cmd_out(name,Return=True) ok_or_die(ret,binascii.unhexlify,'hex string')