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:
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):