diff --git a/linkml_runtime/linkml_model/meta.py b/linkml_runtime/linkml_model/meta.py index a56a0ad9..fee7f676 100644 --- a/linkml_runtime/linkml_model/meta.py +++ b/linkml_runtime/linkml_model/meta.py @@ -148,7 +148,7 @@ class TypeMappingFramework(extended_str): Anything = Any -@dataclass +@dataclass(repr=False) class CommonMetadata(YAMLRoot): """ Generic metadata shared across definitions @@ -311,7 +311,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class Element(YAMLRoot): """ A named element in the model @@ -516,7 +516,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class SchemaDefinition(Element): """ A collection of definitions that make up a schema or a data model. @@ -628,7 +628,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class AnonymousTypeExpression(YAMLRoot): """ A type expression that is not a top-level named type definition. Used for nesting. @@ -696,7 +696,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class TypeDefinition(Element): """ an element that whose instances are atomic scalar values that can be mapped to primitive types @@ -791,7 +791,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class SubsetDefinition(Element): """ an element that can be used to group other metamodel elements @@ -814,7 +814,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class Definition(Element): """ abstract base class for core metaclasses @@ -863,7 +863,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class AnonymousEnumExpression(YAMLRoot): """ An enum_expression that is not named @@ -927,7 +927,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class EnumDefinition(Definition): """ an element whose instances must be drawn from a specified set of permissible values @@ -1001,7 +1001,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class EnumBinding(YAMLRoot): """ A binding of a slot or a class to a permissible value from an enumeration. @@ -1186,7 +1186,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class MatchQuery(YAMLRoot): """ A query that is used on an enum expression to dynamically obtain a set of permissivle values via a query that @@ -1212,7 +1212,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class ReachabilityQuery(YAMLRoot): """ A query that is used on an enum expression to dynamically obtain a set of permissible values via walking from a @@ -1256,7 +1256,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class StructuredAlias(YAMLRoot): """ object that contains meta data about a synonym or alias including where it came from (source) and its scope @@ -1453,7 +1453,7 @@ class Expression(YAMLRoot): class_model_uri: ClassVar[URIRef] = LINKML.Expression -@dataclass +@dataclass(repr=False) class TypeExpression(Expression): """ An abstract class grouping named types and anonymous type expressions @@ -1521,7 +1521,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class EnumExpression(Expression): """ An expression that constrains the range of a slot @@ -1585,7 +1585,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class AnonymousExpression(YAMLRoot): """ An abstract parent class for any nested expression @@ -1754,7 +1754,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class PathExpression(YAMLRoot): """ An expression that describes an abstract path from an object to another through a sequence of slot lookups @@ -1959,7 +1959,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class SlotExpression(Expression): """ an expression that constrains the range of values a slot can take @@ -2092,7 +2092,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class AnonymousSlotExpression(AnonymousExpression): _inherited_slots: ClassVar[List[str]] = ["range", "required", "recommended", "multivalued", "inlined", "inlined_as_list", "minimum_value", "maximum_value", "pattern", "structured_pattern", "value_presence", "equals_string", "equals_string_in", "equals_number", "equals_expression", "exact_cardinality", "minimum_cardinality", "maximum_cardinality"] @@ -2222,7 +2222,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class SlotDefinition(Definition): """ an element that describes how instances are related to other instances @@ -2531,7 +2531,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class ClassExpression(YAMLRoot): """ A boolean expression that can be used to dynamically determine membership of a class @@ -2571,7 +2571,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class AnonymousClassExpression(AnonymousExpression): _inherited_slots: ClassVar[List[str]] = [] @@ -2612,7 +2612,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class ClassDefinition(Definition): """ an element whose instances are complex objects that may have slot-value assignments @@ -2747,7 +2747,7 @@ class ClassLevelRule(YAMLRoot): class_model_uri: ClassVar[URIRef] = LINKML.ClassLevelRule -@dataclass +@dataclass(repr=False) class ClassRule(ClassLevelRule): """ A rule that applies to instances of a class @@ -2940,7 +2940,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class ArrayExpression(YAMLRoot): """ defines the dimensions of an array @@ -3123,7 +3123,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class DimensionExpression(YAMLRoot): """ defines one of the dimensions of an array @@ -3308,7 +3308,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class PatternExpression(YAMLRoot): """ a regular expression pattern used to evaluate conformance of a string @@ -3489,7 +3489,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class ImportExpression(YAMLRoot): """ an expression describing an import @@ -3671,7 +3671,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class Setting(YAMLRoot): """ assignment of a key to a value @@ -3700,7 +3700,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class Prefix(YAMLRoot): """ prefix URI tuple @@ -3729,7 +3729,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class LocalName(YAMLRoot): """ an attributed label @@ -3758,7 +3758,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class Example(YAMLRoot): """ usage example and description @@ -3784,7 +3784,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class AltDescription(YAMLRoot): """ an attributed description @@ -3813,7 +3813,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class PermissibleValue(YAMLRoot): """ a permissible value, accompanied by intended text and an optional mapping to a concept URI @@ -4015,7 +4015,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): super().__post_init__(**kwargs) -@dataclass +@dataclass(repr=False) class UniqueKey(YAMLRoot): """ a collection of slots whose values uniquely identify an instance of a class diff --git a/linkml_runtime/utils/enumerations.py b/linkml_runtime/utils/enumerations.py index 75e5a8a6..5cd06ed2 100644 --- a/linkml_runtime/utils/enumerations.py +++ b/linkml_runtime/utils/enumerations.py @@ -102,4 +102,4 @@ def __str__(self) -> str: def __repr__(self) -> str: rlist = [(f.name, getattr(self._code, f.name)) for f in fields(self._code)] - return '(' + ', '.join([f"{f[0]}={repr(f[1])}" for f in rlist if f[1]]) + ')' + return self.__class__.__name__ + '(' + ', '.join([f"{f[0]}={repr(f[1])}" for f in rlist if f[1]]) + ')' diff --git a/linkml_runtime/utils/yamlutils.py b/linkml_runtime/utils/yamlutils.py index a4b0677f..219cb135 100644 --- a/linkml_runtime/utils/yamlutils.py +++ b/linkml_runtime/utils/yamlutils.py @@ -1,6 +1,9 @@ from copy import copy from json import JSONDecoder from typing import Union, Any, List, Optional, Type, Callable, Dict +from pprint import pformat +import textwrap +import re import yaml from deprecated.classic import deprecated @@ -10,7 +13,7 @@ from yaml.constructor import ConstructorError from linkml_runtime.utils.context_utils import CONTEXTS_PARAM_TYPE, merge_contexts -from linkml_runtime.utils.formatutils import is_empty +from linkml_runtime.utils.formatutils import is_empty, remove_empty_items, is_list, is_dict, items YAMLObjTypes = Union[JsonObjTypes, "YAMLRoot"] @@ -277,6 +280,47 @@ def MissingRequiredField(self, field_name: str) -> None: """ Generic loader error handler """ raise ValueError(f"{field_name} must be supplied") + def __repr__(self): + return _pformat(items(self), self.__class__.__name__) + + def __str__(self): + return repr(self) + +def _pformat(fields:dict, cls_name:str, indent:str = ' ') -> str: + """ + pretty format the fields of the items of a ``YAMLRoot`` object without the wonky indentation of pformat. + see ``YAMLRoot.__repr__``. + + formatting is similar to black - items at similar levels of nesting have similar levels of indentation, + rather than getting placed at essentially random levels of indentation depending on what came before them. + """ + res = [] + total_len = 0 + for key, val in fields: + if val == [] or val == {} or val is None: + continue + # pformat handles everything else that isn't a YAMLRoot object, but it sure does look ugly + # use it to split lines and as the thing of last resort, but otherwise indent = 0, we'll do that + val_str = pformat(val, indent=0, compact=True, sort_dicts=False) + # now we indent everything except the first line by indenting and then using regex to remove just the first indent + val_str = re.sub(rf'\A{re.escape(indent)}', '', textwrap.indent(val_str, indent)) + # now recombine with the key in a format that can be re-eval'd into an object if indent is just whitespace + val_str = f"'{key}': " + val_str + + # count the total length of this string so we know if we need to linebreak or not later + total_len += len(val_str) + res.append(val_str) + + if total_len > 80: + inside = ',\n'.join(res) + # we indent twice - once for the inner contents of every inner object, and one to + # offset from the root element. that keeps us from needing to be recursive except for the + # single pformat call + inside = textwrap.indent(inside, indent) + return cls_name + '({\n' + inside + '\n})' + else: + return cls_name + '({' + ', '.join(res) + '})' + def root_representer(dumper: yaml.Dumper, data: YAMLRoot): """ YAML callback -- used to filter out empty values (None, {}, [] and false) diff --git a/tests/test_utils/test_inlined_as_dict_forms.py b/tests/test_utils/test_inlined_as_dict_forms.py index 406bdb81..01aa4ac1 100644 --- a/tests/test_utils/test_inlined_as_dict_forms.py +++ b/tests/test_utils/test_inlined_as_dict_forms.py @@ -9,25 +9,25 @@ class InlinedAsDictTestcase(unittest.TestCase): """ Test the various YAML forms for inlined_as_dict entries""" def test_list_variations(self): v = E() - self.assertEqual("E(ev={})", str(v), "No entries, period") + self.assertEqual(v.ev, {}, "No entries, period") v = E({}) - self.assertEqual("E(ev={})", str(v), "Default is empty dictionary") + self.assertEqual(v.ev, {}, "Default is empty dictionary") v = E([]) - self.assertEqual('E(ev={})', str(v), "Empty list becomes empty dictionary") + self.assertEqual(v.ev, {}, "Empty list becomes empty dictionary") v = E(JsonObj()) - self.assertEqual('E(ev={})', str(v), "Empty JsonObj becomes empty dictionary") + self.assertEqual(v.ev, {}, "Empty JsonObj becomes empty dictionary") # Form 5 -- list of keys v1 = JsonObj(["k1", "k2"]) v = E(v1) - self.assertEqual("E(ev={'k1': EInst(s1='k1', s2=None, s3=None), 'k2': EInst(s1='k2', s2=None, s3=None)})", - str(v)) + self.assertEqual(v.ev, {'k1': EInst(s1='k1'), 'k2': EInst(s1='k2')}) # Form 4: -- list of key/object pairs v = E([{"k1": {"s1": "k1", "s2": "v21", "s3": "v23"}}, {"k2": {"s2": "v22", "s3": "v23"}}, {"k3": {}}]) - self.assertEqual("E(ev={'k1': EInst(s1='k1', s2='v21', s3='v23')," - " 'k2': EInst(s1='k2', s2='v22', s3='v23')," - " 'k3': EInst(s1='k3', s2=None, s3=None)})", str(v), + self.assertEqual(v.ev, + {'k1': EInst(s1='k1', s2='v21', s3='v23'), + 'k2': EInst(s1='k2', s2='v22', s3='v23'), + 'k3': EInst(s1='k3', s2=None, s3=None)}, "List of key value constructors") with self.assertRaises(ValueError) as e: @@ -41,28 +41,34 @@ def test_list_variations(self): v = E([{"k1": EInst(s1="k1", s2="v21", s3="v23")}, {"k2": JsonObj({"s2": "v22", "s3": "v23"})}, {"k3": None}]) - self.assertEqual("E(ev={'k1': EInst(s1='k1', s2='v21', s3='v23')," - " 'k2': EInst(s1='k2', s2='v22', s3='v23')," - " 'k3': EInst(s1='k3', s2=None, s3=None)})", str(v)) + self.assertEqual(v.ev, + { + 'k1': EInst(s1='k1', s2='v21', s3='v23'), + 'k2': EInst(s1='k2', s2='v22', s3='v23'), + 'k3': EInst(s1='k3', s2=None, s3=None) + }) # More Form 5 variations v = E(["k1", "k2", {"k3": "v3"}, ["k4", "v4"], {"s1": "k5", "s2": "v52"}]) - self.assertEqual("E(ev={'k1': EInst(s1='k1', s2=None, s3=None), " - "'k2': EInst(s1='k2', s2=None, s3=None), " - "'k3': EInst(s1='k3', s2='v3', s3=None), " - "'k4': EInst(s1='k4', s2='v4', s3=None), " - "'k5': EInst(s1='k5', s2='v52', s3=None)})", str(v), "Key value tuples") + self.assertEqual(v.ev, + {'k1': EInst(s1='k1', s2=None, s3=None), + 'k2': EInst(s1='k2', s2=None, s3=None), + 'k3': EInst(s1='k3', s2='v3', s3=None), + 'k4': EInst(s1='k4', s2='v4', s3=None), + 'k5': EInst(s1='k5', s2='v52', s3=None)}, "Key value tuples") # Form 6 - list of positional object values v = E([["k1", "v12", "v13"], ["k2", "v22"], ["k3"]]) - self.assertEqual("E(ev={'k1': EInst(s1='k1', s2='v12', s3='v13'), " - "'k2': EInst(s1='k2', s2='v22', s3=None), " - "'k3': EInst(s1='k3', s2=None, s3=None)})", str(v), "Positional objects") + self.assertEqual(v.ev, + {'k1': EInst(s1='k1', s2='v12', s3='v13'), + 'k2': EInst(s1='k2', s2='v22', s3=None), + 'k3': EInst(s1='k3', s2=None, s3=None)}, "Positional objects") # Form 7 - list of kv dictionaries v = E([{"s1": "v11", "s2": "v12"}, {"s1": "v21", "s2": "v22", "s3": "v23"}]) - self.assertEqual("E(ev={'v11': EInst(s1='v11', s2='v12', s3=None), " - "'v21': EInst(s1='v21', s2='v22', s3='v23')})", str(v), "List of dictionaries") + self.assertEqual(v.ev, + {'v11': EInst(s1='v11', s2='v12', s3=None), + 'v21': EInst(s1='v21', s2='v22', s3='v23')}, "List of dictionaries") def test_dict_variations(self): @@ -72,23 +78,30 @@ def test_dict_variations(self): "k2": JsonObj({"s2": "v22", "s3": "v23"}), "k3": {"s2": "v32", "s3": "v33"}, "k4": {"s1": "k4"}}) - self.assertEqual(("E(ev={'k1': EInst(s1='k1', s2='v21', s3='v23'), " - "'k2': EInst(s1='k2', s2='v22', s3='v23'), " - "'k3': EInst(s1='k3', s2='v32', s3='v33'), " - "'k4': EInst(s1='k4', s2=None, s3=None)})"), str(v), "Dictionary of key/object entries") + self.assertEqual(v.ev, + {'k1': EInst(s1='k1', s2='v21', s3='v23'), + 'k2': EInst(s1='k2', s2='v22', s3='v23'), + 'k3': EInst(s1='k3', s2='v32', s3='v33'), + 'k4': EInst(s1='k4', s2=None, s3=None)}, + "Dictionary of key/object entries") # Form 2: key/value tuples (only works when at most two values are required v = E(ev={"k1": "v11", "k2": "v21", "k3": {}}) - self.assertEqual("E(ev={'k1': EInst(s1='k1', s2='v11', s3=None)," - " 'k2': EInst(s1='k2', s2='v21', s3=None), " - "'k3': EInst(s1='k3', s2=None, s3=None)})", str(v), "Dictionary of two-key entries") + expected = ("E({ 'ev': { 'k1': {'s1': 'k1', 's2': 'v11'},\n" + " 'k2': {'s1': 'k2', 's2': 'v21'},\n" + " 'k3': {'s1': 'k3'}}})") + self.assertEqual(v.ev, + {'k1': EInst(s1='k1', s2='v11', s3=None), + 'k2': EInst(s1='k2', s2='v21', s3=None), + 'k3': EInst(s1='k3', s2=None, s3=None)}, + "Dictionary of two-key entries") # Form 3: Basic single object (differentiated from form2 by the presence of the key name v = E({"s1": "k1"}) - self.assertEqual("E(ev={'k1': EInst(s1='k1', s2=None, s3=None)})", - str(v), "Single entry dictionary") + self.assertEqual(v.ev, {'k1': EInst(s1='k1', s2=None, s3=None)}, + "Single entry dictionary") v = E({"s1": "k1", "s2": "v12"}) - self.assertEqual("E(ev={'k1': EInst(s1='k1', s2='v12', s3=None)})", str(v), "Single entry dictionary") + self.assertEqual(v.ev, {'k1': EInst(s1='k1', s2='v12', s3=None)}, "Single entry dictionary") def test_isempties(self): base = E() @@ -106,3 +119,4 @@ def test_isempties(self): if __name__ == '__main__': unittest.main() + diff --git a/tests/test_utils/test_inlined_as_list_forms.py b/tests/test_utils/test_inlined_as_list_forms.py index 22b862db..6c606097 100644 --- a/tests/test_utils/test_inlined_as_list_forms.py +++ b/tests/test_utils/test_inlined_as_list_forms.py @@ -9,25 +9,25 @@ class InlinedAsListTestcase(unittest.TestCase): """ Test the various YAML forms for inlined_as_list entries""" def test_list_variations(self): v = E() - self.assertEqual("E(ev=[])", str(v), "No entries, period") + self.assertEqual(v.ev, [], "No entries, period") v = E({}) - self.assertEqual("E(ev=[])", str(v), "Default is empty dictionary") + self.assertEqual(v.ev, [], "Default is empty dictionary") v = E([]) - self.assertEqual('E(ev=[])', str(v), "Empty list becomes empty dictionary") + self.assertEqual(v.ev, [], "Empty list becomes empty dictionary") v = E(JsonObj()) - self.assertEqual('E(ev=[])', str(v), "Empty JsonObj becomes empty dictionary") + self.assertEqual(v.ev, [], "Empty JsonObj becomes empty dictionary") # Form 5 -- list of keys v1 = JsonObj(["k1", "k2"]) v = E(v1) - self.assertEqual("E(ev=[EInst(s1='k1', s2=None, s3=None), EInst(s1='k2', s2=None, s3=None)])", - str(v)) + self.assertEqual(v.ev, [EInst(s1='k1', s2=None, s3=None), EInst(s1='k2', s2=None, s3=None)]) # Form 4: -- list of key/object pairs v = E([{"k1": {"s1": "k1", "s2": "v21", "s3": "v23"}}, {"k2": {"s2": "v22", "s3": "v23"}}, {"k3": {}}]) - self.assertEqual("E(ev=[EInst(s1='k1', s2='v21', s3='v23')," - " EInst(s1='k2', s2='v22', s3='v23')," - " EInst(s1='k3', s2=None, s3=None)])", str(v), + self.assertEqual(v.ev, + [EInst(s1='k1', s2='v21', s3='v23'), + EInst(s1='k2', s2='v22', s3='v23'), + EInst(s1='k3', s2=None, s3=None)], "List of key value constructors") with self.assertRaises(ValueError) as e: @@ -41,28 +41,32 @@ def test_list_variations(self): v = E([{"k1": EInst(s1="k1", s2="v21", s3="v23")}, {"k2": JsonObj({"s2": "v22", "s3": "v23"})}, {"k3": None}]) - self.assertEqual("E(ev=[EInst(s1='k1', s2='v21', s3='v23')," - " EInst(s1='k2', s2='v22', s3='v23')," - " EInst(s1='k3', s2=None, s3=None)])", str(v)) + self.assertEqual(v.ev, + [EInst(s1='k1', s2='v21', s3='v23'), + EInst(s1='k2', s2='v22', s3='v23'), + EInst(s1='k3', s2=None, s3=None)]) # More Form 5 variations v = E(["k1", "k2", {"k3": "v3"}, ["k4", "v4"], {"s1": "k5", "s2": "v52"}]) - self.assertEqual("E(ev=[EInst(s1='k1', s2=None, s3=None), " - "EInst(s1='k2', s2=None, s3=None), " - "EInst(s1='k3', s2='v3', s3=None), " - "EInst(s1='k4', s2='v4', s3=None), " - "EInst(s1='k5', s2='v52', s3=None)])", str(v), "Key value tuples") + self.assertEqual(v.ev, + [EInst(s1='k1', s2=None, s3=None), + EInst(s1='k2', s2=None, s3=None), + EInst(s1='k3', s2='v3', s3=None), + EInst(s1='k4', s2='v4', s3=None), + EInst(s1='k5', s2='v52', s3=None)], "Key value tuples") # Form 6 - list of positional object values v = E([["k1", "v12", "v13"], ["k2", "v22"], ["k3"]]) - self.assertEqual("E(ev=[EInst(s1='k1', s2='v12', s3='v13'), " - "EInst(s1='k2', s2='v22', s3=None), " - "EInst(s1='k3', s2=None, s3=None)])", str(v), "Positional objects") + self.assertEqual(v.ev, + [EInst(s1='k1', s2='v12', s3='v13'), + EInst(s1='k2', s2='v22', s3=None), + EInst(s1='k3', s2=None, s3=None)], "Positional objects") # Form 7 - list of kv dictionaries v = E([{"s1": "v11", "s2": "v12"}, {"s1": "v21", "s2": "v22", "s3": "v23"}]) - self.assertEqual("E(ev=[EInst(s1='v11', s2='v12', s3=None), " - "EInst(s1='v21', s2='v22', s3='v23')])", str(v), "List of dictionaries") + self.assertEqual(v.ev, + [EInst(s1='v11', s2='v12', s3=None), + EInst(s1='v21', s2='v22', s3='v23')], "List of dictionaries") def test_dict_variations(self): @@ -72,23 +76,25 @@ def test_dict_variations(self): "k2": JsonObj({"s2": "v22", "s3": "v23"}), "k3": {"s2": "v32", "s3": "v33"}, "k4": {"s1": "k4"}}) - self.assertEqual(("E(ev=[EInst(s1='k1', s2='v21', s3='v23'), " - "EInst(s1='k2', s2='v22', s3='v23'), " - "EInst(s1='k3', s2='v32', s3='v33'), " - "EInst(s1='k4', s2=None, s3=None)])"), str(v), "Dictionary of key/object entries") + self.assertEqual(v.ev, + [EInst(s1='k1', s2='v21', s3='v23'), + EInst(s1='k2', s2='v22', s3='v23'), + EInst(s1='k3', s2='v32', s3='v33'), + EInst(s1='k4', s2=None, s3=None)], "Dictionary of key/object entries") # Form 2: key/value tuples (only works when at most two values are required v = E(ev={"k1": "v11", "k2": "v21", "k3": {}}) - self.assertEqual("E(ev=[EInst(s1='k1', s2='v11', s3=None)," - " EInst(s1='k2', s2='v21', s3=None), " - "EInst(s1='k3', s2=None, s3=None)])", str(v), "Dictionary of two-key entries") + self.assertEqual(v.ev, + [EInst(s1='k1', s2='v11', s3=None), + EInst(s1='k2', s2='v21', s3=None), + EInst(s1='k3', s2=None, s3=None)], "Dictionary of two-key entries") # Form 3: Basic single object (differentiated from form2 by the presence of the key name v = E({"s1": "k1"}) - self.assertEqual("E(ev=[EInst(s1='k1', s2=None, s3=None)])", - str(v), "Single entry dictionary") + self.assertEqual(v.ev, [EInst(s1='k1', s2=None, s3=None)], + "Single entry dictionary") v = E({"s1": "k1", "s2": "v12"}) - self.assertEqual("E(ev=[EInst(s1='k1', s2='v12', s3=None)])", str(v), "Single entry dictionary") + self.assertEqual(v.ev, [EInst(s1='k1', s2='v12', s3=None)], "Single entry dictionary") if __name__ == '__main__':