chainlib

Generic blockchain access library and tooling
Log | Files | Refs | README | LICENSE

commit 5bfdb51676a8a19c7c179edcbe4188528a6363ba
parent 7d7209dd31e88d5eb9df55bd36545f49f4f0a7a3
Author: nolash <dev@holbrook.no>
Date:   Thu, 28 Oct 2021 12:16:23 +0200

Add custom fields to chain spec, harden custom field imports, improve dumpconfig handle

Diffstat:
Mchainlib/chain.py | 100+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mchainlib/cli/config.py | 3++-
Msetup.cfg | 2+-
Mtests/test_chain.py | 53+++++++++++++++++++++++++++++++++++++++++++++++------
Mtests/test_cli.py | 3+++
5 files changed, 141 insertions(+), 20 deletions(-)

diff --git a/chainlib/chain.py b/chainlib/chain.py @@ -1,5 +1,15 @@ # standard imports import copy +import re + + +def is_valid_label(v, alpha_only=False): + re_m = None + if alpha_only: + re_m = r'^[a-zA-Z]+$' + else: + re_m = r'^[a-zA-Z0-9]+$' + return re.match(re_m, v) class ChainSpec: @@ -16,13 +26,37 @@ class ChainSpec: :param tag: Descriptive tag :type tag: str """ - def __init__(self, arch, fork, network_id, common_name=None): + def __init__(self, arch, fork, network_id, common_name=None, custom=[], safe=True): + if custom == None: + custom = [] + elif not isinstance(custom, list): + raise ValueError('custom value must be list') + self.o = { - 'arch': arch, - 'fork': fork, - 'network_id': network_id, - 'common_name': common_name, - } + 'arch': arch, + 'fork': fork, + 'network_id': network_id, + 'common_name': common_name, + 'custom': custom, + } + + if safe: + self.validate() + + + def validate(self): + self.o['network_id'] = int(self.o['network_id']) + if not is_valid_label(self.o['arch'], alpha_only=True): + raise ValueError('arch: ' + self.o['arch']) + if not is_valid_label(self.o['fork'], alpha_only=True): + raise ValueError('fork: ' + self.o['fork']) + if self.o.get('common_name') and not is_valid_label(self.o['common_name']): + raise ValueError('common_name: ' + self.o['common_name']) + if self.o.get('custom'): + for i, v in enumerate(self.o['custom']): + if not is_valid_label(v): + raise ValueError('common_name {}: {}'.format(i, v)) + def network_id(self): """Returns the network id part of the spec. @@ -43,6 +77,12 @@ class ChainSpec: def engine(self): + """Alias of self.arch() + """ + return self.arch() + + + def arch(self): """Returns the chain architecture part of the spec :rtype: str @@ -51,6 +91,15 @@ class ChainSpec: return self.o['arch'] + def fork(self): + """Returns the fork part of the spec + + :rtype: str + :returns: fork + """ + return self.o['fork'] + + def common_name(self): """Returns the common name part of the spec @@ -60,6 +109,21 @@ class ChainSpec: return self.o['common_name'] + def is_same_as(self, chain_spec_cmp, use_common_name=False, use_custom=False): + a = ['arch', 'fork', 'network_id'] + if use_common_name: + a += ['common_name'] + if use_custom: + a += ['custom'] + try: + for k in a: + assert(chain_spec_cmp.o[k] == self.o[k]) + except AssertionError: + return False + return True + + + @staticmethod def from_chain_str(chain_str): """Create a new ChainSpec object from a colon-separated string, as output by the string representation of the ChainSpec object. @@ -79,9 +143,13 @@ class ChainSpec: if len(o) < 3: raise ValueError('Chain string must have three sections, got {}'.format(len(o))) common_name = None - if len(o) == 4: + if len(o) > 3: common_name = o[3] - return ChainSpec(o[0], o[1], int(o[2]), common_name) + custom = [] + if len(o) > 4: + for i in range(4, len(o)): + custom.append(o[i]) + return ChainSpec(o[0], o[1], int(o[2]), common_name=common_name, custom=custom) @staticmethod @@ -100,20 +168,28 @@ class ChainSpec: :rtype: chainlib.chain.ChainSpec :returns: Resulting chain spec """ - return ChainSpec(o['arch'], o['fork'], o['network_id'], common_name=o['common_name']) + return ChainSpec(o['arch'], o['fork'], o['network_id'], common_name=o.get('common_name'), custom=o.get('custom')) - def asdict(self): + def asdict(self, use_common_name=True, use_custom=True): """Create a dictionary representation of the chain spec. :rtype: dict :returns: Chain spec dictionary """ - return copy.copy(self.o) + r = copy.copy(self.o) + if not use_common_name: + del r['common_name'] + del r['custom'] + if not use_custom: + del r['custom'] + return r def __str__(self): s = '{}:{}:{}'.format(self.o['arch'], self.o['fork'], self.o['network_id']) - if self.o['common_name'] != None: + if self.o.get('common_name'): s += ':' + self.o['common_name'] + if self.o.get('custom'): + s += ':' + ':'.join(self.o['custom']) return s diff --git a/chainlib/cli/config.py b/chainlib/cli/config.py @@ -236,7 +236,8 @@ class Config(confini.Config): if existing_r == None or r != None: config.add(r, v, exists_ok=True) - if getattr(args, 'dumpconfig', None) != None: + logg.debug('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>' + str(getattr(args, 'dumpconfig'))) + if getattr(args, 'dumpconfig', None): config_keys = config.all() with_values = not config.get('_RAW') for k in config_keys: diff --git a/setup.cfg b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = chainlib -version = 0.0.10a8 +version = 0.0.10a9 description = Generic blockchain access library and tooling author = Louis Holbrook author_email = dev@holbrook.no diff --git a/tests/test_chain.py b/tests/test_chain.py @@ -1,40 +1,81 @@ +# standard imports import unittest +import logging +# local imports from chainlib.chain import ChainSpec +# test imports from tests.base import TestBase +logging.basicConfig(level=logging.DEBUG) +logg = logging.getLogger() +logg.setLevel(logging.DEBUG) + class TestChain(TestBase): def test_chain_spec_str(self): + s = ChainSpec('foo', 'bar', 3) + self.assertEqual('foo:bar:3', str(s)) + s = ChainSpec('foo', 'bar', 3, 'baz') self.assertEqual('foo:bar:3:baz', str(s)) - s = ChainSpec('foo', 'bar', 3) - self.assertEqual('foo:bar:3', str(s)) - def test_chain_spec(self): + s = ChainSpec('foo', 'bar', 3, 'baz', ['inky', 'pinky', 'blinky']) + self.assertEqual('foo:bar:3:baz:inky:pinky:blinky', str(s)) + + def test_chain_spec(self): s = ChainSpec.from_chain_str('foo:bar:3') s = ChainSpec.from_chain_str('foo:bar:3:baz') + s = ChainSpec.from_chain_str('foo:bar:3:baz:inky:pinky:blinky') with self.assertRaises(ValueError): s = ChainSpec.from_chain_str('foo:bar:a') s = ChainSpec.from_chain_str('foo:bar') s = ChainSpec.from_chain_str('foo') + s = ChainSpec.from_chain_str('foo1:bar:3') + s = ChainSpec.from_chain_str('foo:bar2:3') def test_chain_spec_dict(self): - s = 'foo:bar:3:baz' - c = ChainSpec.from_chain_str('foo:bar:3:baz') + ss = 'foo:bar:3:baz:inky:pinky:blinky' + c = ChainSpec.from_chain_str(ss) d = c.asdict() self.assertEqual(d['arch'], 'foo') self.assertEqual(d['fork'], 'bar') self.assertEqual(d['network_id'], 3) self.assertEqual(d['common_name'], 'baz') + self.assertEqual(d['custom'], ['inky', 'pinky', 'blinky']) cc = ChainSpec.from_dict(d) - self.assertEqual(s, str(cc)) + self.assertEqual(ss, str(cc)) + + d = c.asdict(use_custom=False) + cc = ChainSpec.from_dict(d) + self.assertEqual(str(cc), 'foo:bar:3:baz') + + d = c.asdict(use_common_name=False) + cc = ChainSpec.from_dict(d) + self.assertEqual(str(cc), 'foo:bar:3') + + def test_chain_spec_compare(self): + a = 'foo:bar:42:baz' + b = 'foo:bar:42:barbar' + c = 'foo:bar:42:baz:inky:pinky:blinky' + + ca = ChainSpec.from_chain_str(a) + cb = ChainSpec.from_chain_str(b) + + self.assertTrue(ca.is_same_as(cb)) + self.assertFalse(ca.is_same_as(cb, use_common_name=True)) + + cc = ChainSpec.from_chain_str(c) + logg.debug('chain_spec_cmp ' + str(cc.o)) + self.assertTrue(ca.is_same_as(cc)) + self.assertTrue(ca.is_same_as(cc, use_common_name=True)) + self.assertFalse(ca.is_same_as(cc, use_common_name=True, use_custom=True)) diff --git a/tests/test_cli.py b/tests/test_cli.py @@ -1,6 +1,7 @@ # standard imports import unittest import os +import logging # local imports import chainlib.cli @@ -10,6 +11,8 @@ script_dir = os.path.dirname(os.path.realpath(__file__)) data_dir = os.path.join(script_dir, 'testdata') config_dir = os.path.join(data_dir, 'config') +logging.basicConfig(level=logging.DEBUG) + class TestCli(unittest.TestCase):