Add tagger
This commit is contained in:
parent
f648724205
commit
76b331178c
@ -11,6 +11,7 @@ from chainlib.chain import ChainSpec
|
|||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
import clicada.cli.user as cmd_user
|
import clicada.cli.user as cmd_user
|
||||||
|
import clicada.cli.tag as cmd_tag
|
||||||
|
|
||||||
script_dir = os.path.dirname(os.path.realpath(__file__))
|
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
data_dir = os.path.join(script_dir, '..', 'data')
|
data_dir = os.path.join(script_dir, '..', 'data')
|
||||||
@ -21,6 +22,7 @@ class CmdCtrl:
|
|||||||
|
|
||||||
__cmd_alias = {
|
__cmd_alias = {
|
||||||
'u': 'user',
|
'u': 'user',
|
||||||
|
't': 'tag',
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, argv=None, description=None, logger=None, *args, **kwargs):
|
def __init__(self, argv=None, description=None, logger=None, *args, **kwargs):
|
||||||
@ -35,6 +37,8 @@ class CmdCtrl:
|
|||||||
sub.dest = 'command'
|
sub.dest = 'command'
|
||||||
sub_user = sub.add_parser('user', aliases=['u'], help='retrieve transactions for a user')
|
sub_user = sub.add_parser('user', aliases=['u'], help='retrieve transactions for a user')
|
||||||
cmd_user.process_args(sub_user)
|
cmd_user.process_args(sub_user)
|
||||||
|
sub_tag = sub.add_parser('tag', aliases=['t'], help='locally assign a display value to an identifier')
|
||||||
|
cmd_tag.process_args(sub_tag)
|
||||||
|
|
||||||
self.cmd_args = self.argparser.parse_args(argv)
|
self.cmd_args = self.argparser.parse_args(argv)
|
||||||
|
|
||||||
@ -57,10 +61,6 @@ class CmdCtrl:
|
|||||||
logger.debug('using module {}'.format(modname))
|
logger.debug('using module {}'.format(modname))
|
||||||
self.cmd_mod = importlib.import_module(modname)
|
self.cmd_mod = importlib.import_module(modname)
|
||||||
|
|
||||||
# if self.cmd_args.c:
|
|
||||||
# self.config = confini.Config(base_config_dir, override_dirs=self.cmd_args.c)
|
|
||||||
# else:
|
|
||||||
# self.config = confini.Config(base_config_dir)
|
|
||||||
extra_args = self.cmd_mod.extra_args()
|
extra_args = self.cmd_mod.extra_args()
|
||||||
logger.debug('using extra args {}'.format(extra_args))
|
logger.debug('using extra args {}'.format(extra_args))
|
||||||
if self.cmd_args.config:
|
if self.cmd_args.config:
|
||||||
@ -87,9 +87,14 @@ class CmdCtrl:
|
|||||||
return self.config.true(k)
|
return self.config.true(k)
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
def chain(self):
|
def chain(self):
|
||||||
return self.chain_spec
|
return self.chain_spec
|
||||||
|
|
||||||
|
|
||||||
def conn(self):
|
def conn(self):
|
||||||
return self.__conn
|
return self.__conn
|
||||||
|
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
self.cmd_mod.execute(self)
|
||||||
|
41
clicada/cli/tag.py
Normal file
41
clicada/cli/tag.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# standard imports
|
||||||
|
import json
|
||||||
|
|
||||||
|
# external imports
|
||||||
|
from clicada.user import FileUserStore
|
||||||
|
|
||||||
|
|
||||||
|
categories = [
|
||||||
|
'phone',
|
||||||
|
'address',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def process_args(argparser):
|
||||||
|
argparser.add_argument('--category', required=True, type=str, help='Identifier category')
|
||||||
|
argparser.add_argument('identifier', type=str, help='Identifier to store a display tag for')
|
||||||
|
argparser.add_argument('tag', type=str, help='Display tag to store for the identifier')
|
||||||
|
|
||||||
|
|
||||||
|
def extra_args():
|
||||||
|
return {
|
||||||
|
'category': None,
|
||||||
|
'identifier': None,
|
||||||
|
'tag': None,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def apply_args(config, args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def validate(config, args):
|
||||||
|
if category not in categories:
|
||||||
|
raise ValueError('Invalid category. Valid categories are: {}'.format(','.join(categories)))
|
||||||
|
|
||||||
|
|
||||||
|
def execute(ctrl):
|
||||||
|
store_path = '.clicada'
|
||||||
|
user_store = FileUserStore(ctrl.chain(), ctrl.get('_CATEGORY'), store_path, int(ctrl.get('FILESTORE_TTL')))
|
||||||
|
user_store.put(ctrl.get('_IDENTIFIER'), json.dumps(ctrl.get('_TAG')), force=True)
|
||||||
|
user_store.stick(ctrl.get('_IDENTIFIER'))
|
@ -1,3 +1,22 @@
|
|||||||
|
# standard imports
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# external imports
|
||||||
|
from cic_eth_registry import CICRegistry
|
||||||
|
from cic_eth_registry.lookup.tokenindex import TokenIndexLookup
|
||||||
|
from cic_types.ext.metadata import MetadataRequestsHandler
|
||||||
|
from chainlib.eth.address import to_checksum_address
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from clicada.tx import TxGetter
|
||||||
|
from clicada.user import FileUserStore
|
||||||
|
from clicada.token import FileTokenStore
|
||||||
|
from clicada.tx import ResolvedTokenTx
|
||||||
|
|
||||||
|
logg = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def process_args(argparser):
|
def process_args(argparser):
|
||||||
argparser.add_argument('-m', '--method', type=str, help='lookup method')
|
argparser.add_argument('-m', '--method', type=str, help='lookup method')
|
||||||
argparser.add_argument('--meta-url', dest='meta_url', type=str, help='Url to retrieve metadata from')
|
argparser.add_argument('--meta-url', dest='meta_url', type=str, help='Url to retrieve metadata from')
|
||||||
@ -14,13 +33,45 @@ def extra_args():
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def validate_args(args):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def apply_args(config, args):
|
def apply_args(config, args):
|
||||||
|
if config.get('META_LOOKUP_METHOD'):
|
||||||
|
raise NotImplementedError('Sorry, currently only "phone" lookup method is implemented')
|
||||||
|
|
||||||
|
|
||||||
|
def validate(config, args):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def execute(config, args):
|
def execute(ctrl):
|
||||||
pass
|
tx_getter = TxGetter(ctrl.get('TX_CACHE_URL'))
|
||||||
|
|
||||||
|
MetadataRequestsHandler.base_url = ctrl.get('META_URL')
|
||||||
|
|
||||||
|
store_path = '.clicada'
|
||||||
|
user_phone_file_label = 'phone'
|
||||||
|
user_phone_store = FileUserStore(ctrl.chain(), user_phone_file_label, store_path, int(ctrl.get('FILESTORE_TTL')))
|
||||||
|
|
||||||
|
user_address = user_phone_store.by_phone(ctrl.get('_ARG_USER_IDENTIFIER'), update=ctrl.get('_FORCE'))
|
||||||
|
if user_address == None:
|
||||||
|
sys.stderr.write('unknown identifier: {}\n'.format(ctrl.get('_ARG_USER_IDENTIFIER')))
|
||||||
|
sys.exit(1)
|
||||||
|
try:
|
||||||
|
user_address = to_checksum_address(user_address)
|
||||||
|
except ValueError:
|
||||||
|
sys.stderr.write('invalid response "{}" for {}\n'.format(user_address, ctrl.get('_ARG_USER_IDENTIFIER')))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
logg.debug('loaded user address {} for {}'.format(user_address, ctrl.get('_ARG_USER_IDENTIFIER')))
|
||||||
|
|
||||||
|
txs = tx_getter.get(user_address)
|
||||||
|
|
||||||
|
token_store = FileTokenStore(ctrl.chain(), ctrl.conn(), 'token', store_path)
|
||||||
|
|
||||||
|
user_address_file_label = 'address'
|
||||||
|
user_address_store = FileUserStore(ctrl.chain(), user_address_file_label, store_path, int(ctrl.get('FILESTORE_TTL')))
|
||||||
|
|
||||||
|
for tx_src in txs['data']:
|
||||||
|
tx = ResolvedTokenTx.from_dict(tx_src)
|
||||||
|
tx.resolve(token_store, user_address_store, update=ctrl.get('_FORCE'))
|
||||||
|
print(tx)
|
||||||
|
@ -2,53 +2,17 @@
|
|||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
# external imports
|
|
||||||
from cic_eth_registry import CICRegistry
|
|
||||||
from cic_eth_registry.lookup.tokenindex import TokenIndexLookup
|
|
||||||
from cic_types.ext.metadata import MetadataRequestsHandler
|
|
||||||
from chainlib.eth.address import to_checksum_address
|
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from clicada.tx import TxGetter
|
|
||||||
from clicada.user import FileUserStore
|
|
||||||
from clicada.token import FileTokenStore
|
|
||||||
from clicada.cli import CmdCtrl
|
from clicada.cli import CmdCtrl
|
||||||
from clicada.tx import ResolvedTokenTx
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.WARNING)
|
logging.basicConfig(level=logging.WARNING)
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|
||||||
|
|
||||||
|
def main(ctrl):
|
||||||
|
ctrl.execute()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
ctrl = CmdCtrl(argv=sys.argv[1:], logger=logg)
|
ctrl = CmdCtrl(argv=sys.argv[1:], logger=logg)
|
||||||
|
main(ctrl)
|
||||||
tx_getter = TxGetter(ctrl.get('TX_CACHE_URL'))
|
|
||||||
|
|
||||||
MetadataRequestsHandler.base_url = ctrl.get('META_URL')
|
|
||||||
|
|
||||||
store_path = '.clicada'
|
|
||||||
user_phone_file_label = 'phone'
|
|
||||||
user_phone_store = FileUserStore(ctrl.chain(), user_phone_file_label, store_path, int(ctrl.get('FILESTORE_TTL')))
|
|
||||||
|
|
||||||
user_address = user_phone_store.by_phone(ctrl.get('_ARG_USER_IDENTIFIER'), update=ctrl.get('_FORCE'))
|
|
||||||
if user_address == None:
|
|
||||||
sys.stderr.write('unknown identifier: {}\n'.format(ctrl.get('_ARG_USER_IDENTIFIER')))
|
|
||||||
sys.exit(1)
|
|
||||||
try:
|
|
||||||
user_address = to_checksum_address(user_address)
|
|
||||||
except ValueError:
|
|
||||||
sys.stderr.write('invalid response "{}" for {}\n'.format(user_address, ctrl.get('_ARG_USER_IDENTIFIER')))
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
logg.debug('loaded user address {} for {}'.format(user_address, ctrl.get('_ARG_USER_IDENTIFIER')))
|
|
||||||
|
|
||||||
txs = tx_getter.get(user_address)
|
|
||||||
|
|
||||||
token_store = FileTokenStore(ctrl.chain(), ctrl.conn(), 'token', store_path)
|
|
||||||
|
|
||||||
user_address_file_label = 'address'
|
|
||||||
user_address_store = FileUserStore(ctrl.chain(), user_address_file_label, store_path, int(ctrl.get('FILESTORE_TTL')))
|
|
||||||
|
|
||||||
for tx_src in txs['data']:
|
|
||||||
tx = ResolvedTokenTx.from_dict(tx_src)
|
|
||||||
tx.resolve(token_store, user_address_store, update=ctrl.get('_FORCE'))
|
|
||||||
print(tx)
|
|
||||||
|
@ -29,6 +29,7 @@ class FileUserStore:
|
|||||||
def __init__(self, chain_spec, label, store_base_path, ttl):
|
def __init__(self, chain_spec, label, store_base_path, ttl):
|
||||||
invalidate_before = datetime.datetime.now() - datetime.timedelta(seconds=ttl)
|
invalidate_before = datetime.datetime.now() - datetime.timedelta(seconds=ttl)
|
||||||
self.invalidate_before = int(invalidate_before.timestamp())
|
self.invalidate_before = int(invalidate_before.timestamp())
|
||||||
|
self.have_xattr = False
|
||||||
self.label = label
|
self.label = label
|
||||||
self.store_path = os.path.join(
|
self.store_path = os.path.join(
|
||||||
store_base_path,
|
store_base_path,
|
||||||
@ -69,14 +70,64 @@ class FileUserStore:
|
|||||||
logg.info('added user store {} record {} -> {}'.format(self.label, k, v))
|
logg.info('added user store {} record {} -> {}'.format(self.label, k, v))
|
||||||
|
|
||||||
|
|
||||||
|
def stick(self, k):
|
||||||
|
p = os.path.join(self.store_path, k)
|
||||||
|
|
||||||
|
if self.have_xattr:
|
||||||
|
os.setxattr(p, 'cic_sticky', b'1')
|
||||||
|
else:
|
||||||
|
(s_h, s_t) = os.path.split(p)
|
||||||
|
sp = os.path.join(s_h, '.stick_' + s_t)
|
||||||
|
f = open(sp, 'w')
|
||||||
|
f.close()
|
||||||
|
logg.debug('wrote {}'.format(sp))
|
||||||
|
|
||||||
|
|
||||||
|
def __is_sticky(self, p):
|
||||||
|
if self.have_xattr:
|
||||||
|
if not os.getxattr(p, 'cic_sticky'):
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
(s_h, s_t) = os.path.split(p)
|
||||||
|
sp = os.path.join(s_h, '.stick_' + s_t)
|
||||||
|
try:
|
||||||
|
os.stat(sp)
|
||||||
|
except FileNotFoundError:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def __unstick(self, p):
|
||||||
|
(s_h, s_t) = os.path.split(p)
|
||||||
|
sp = os.path.join(s_h, '.stick_' + s_t)
|
||||||
|
os.unlink(sp)
|
||||||
|
|
||||||
|
|
||||||
|
def path(self, k):
|
||||||
|
return os.path.join(self.store_path, k)
|
||||||
|
|
||||||
|
|
||||||
|
def sticky(self, k):
|
||||||
|
p = self.path(k)
|
||||||
|
return self.__is_sticky(p)
|
||||||
|
|
||||||
|
|
||||||
def check_expiry(self, p):
|
def check_expiry(self, p):
|
||||||
st = os.stat(p)
|
st = os.stat(p)
|
||||||
if st[stat.ST_MTIME] < self.invalidate_before:
|
if st[stat.ST_MTIME] < self.invalidate_before:
|
||||||
|
if self.__is_sticky(p):
|
||||||
|
logg.debug('ignoring sticky record expiry for {}'.format(p))
|
||||||
|
return
|
||||||
raise ExpiredRecordError('record in {} is expired'.format(p))
|
raise ExpiredRecordError('record in {} is expired'.format(p))
|
||||||
|
|
||||||
|
|
||||||
def get(self, k):
|
def get(self, k, ignore_expired=False):
|
||||||
|
try:
|
||||||
p = os.path.join(self.store_path, k)
|
p = os.path.join(self.store_path, k)
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
if self.__is_sticky(p):
|
||||||
|
logg.warning('removed orphaned sticky record for ' + p)
|
||||||
|
self.__unstick(p)
|
||||||
self.check_expiry(p)
|
self.check_expiry(p)
|
||||||
|
|
||||||
f = open(p, 'r')
|
f = open(p, 'r')
|
||||||
@ -90,9 +141,11 @@ class FileUserStore:
|
|||||||
phone = phonenumbers.format_number(phone, phonenumbers.PhoneNumberFormat.E164)
|
phone = phonenumbers.format_number(phone, phonenumbers.PhoneNumberFormat.E164)
|
||||||
phone_file = phone.replace('+', '')
|
phone_file = phone.replace('+', '')
|
||||||
|
|
||||||
|
ignore_expired = self.sticky(phone_file)
|
||||||
|
|
||||||
if not update:
|
if not update:
|
||||||
try:
|
try:
|
||||||
v = self.get(phone_file)
|
v = self.get(phone_file, ignore_expired=ignore_expired)
|
||||||
return v
|
return v
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
@ -118,13 +171,17 @@ class FileUserStore:
|
|||||||
|
|
||||||
def get_label(self, address, update=False):
|
def get_label(self, address, update=False):
|
||||||
add = tx_normalize.wallet_address(address)
|
add = tx_normalize.wallet_address(address)
|
||||||
|
ignore_expired = self.sticky(address)
|
||||||
|
|
||||||
if not update:
|
if not update:
|
||||||
try:
|
try:
|
||||||
v = self.get(address)
|
v = self.get(address, ignore_expired=ignore_expired)
|
||||||
v = json.loads(v)
|
v = json.loads(v)
|
||||||
person = Person()
|
person = Person()
|
||||||
|
try:
|
||||||
person_data = person.deserialize(person_data=v)
|
person_data = person.deserialize(person_data=v)
|
||||||
|
except Exception as e:
|
||||||
|
person_data = v
|
||||||
return str(person_data)
|
return str(person_data)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
|
Loading…
Reference in New Issue
Block a user