From 8a157fb6d79b503e820e04e523b2cc6a6061e011 Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Thu, 21 Mar 2024 05:36:42 -0700 Subject: [PATCH 1/7] pretty print models --- linkml_runtime/utils/yamlutils.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/linkml_runtime/utils/yamlutils.py b/linkml_runtime/utils/yamlutils.py index a4b0677f..4c41f3a9 100644 --- a/linkml_runtime/utils/yamlutils.py +++ b/linkml_runtime/utils/yamlutils.py @@ -1,6 +1,7 @@ from copy import copy from json import JSONDecoder from typing import Union, Any, List, Optional, Type, Callable, Dict +from pprint import pformat import yaml from deprecated.classic import deprecated @@ -11,6 +12,7 @@ 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 remove_empty_items YAMLObjTypes = Union[JsonObjTypes, "YAMLRoot"] @@ -277,6 +279,11 @@ def MissingRequiredField(self, field_name: str) -> None: """ Generic loader error handler """ raise ValueError(f"{field_name} must be supplied") + def __str__(self): + res = remove_empty_items(self) + return pformat(as_dict(res),indent=2,compact=True) + + def root_representer(dumper: yaml.Dumper, data: YAMLRoot): """ YAML callback -- used to filter out empty values (None, {}, [] and false) From 48a6465f254556af701e2a3810357fb0189394f9 Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Thu, 21 Mar 2024 05:45:40 -0700 Subject: [PATCH 2/7] whoops thatos the same module --- linkml_runtime/utils/yamlutils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/linkml_runtime/utils/yamlutils.py b/linkml_runtime/utils/yamlutils.py index 4c41f3a9..1042c769 100644 --- a/linkml_runtime/utils/yamlutils.py +++ b/linkml_runtime/utils/yamlutils.py @@ -11,8 +11,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 remove_empty_items +from linkml_runtime.utils.formatutils import is_empty, remove_empty_items YAMLObjTypes = Union[JsonObjTypes, "YAMLRoot"] From 9613bd4772bb833d6fcce337627c6b348464cd09 Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Mon, 25 Mar 2024 17:08:38 -0700 Subject: [PATCH 3/7] separate repr and str --- linkml_runtime/utils/yamlutils.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/linkml_runtime/utils/yamlutils.py b/linkml_runtime/utils/yamlutils.py index 1042c769..4b4d184a 100644 --- a/linkml_runtime/utils/yamlutils.py +++ b/linkml_runtime/utils/yamlutils.py @@ -1,7 +1,9 @@ +import pdb 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 yaml from deprecated.classic import deprecated @@ -11,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, remove_empty_items +from linkml_runtime.utils.formatutils import is_empty, remove_empty_items, is_list, is_dict, items YAMLObjTypes = Union[JsonObjTypes, "YAMLRoot"] @@ -278,10 +280,20 @@ def MissingRequiredField(self, field_name: str) -> None: """ Generic loader error handler """ raise ValueError(f"{field_name} must be supplied") + def __repr__(self): + """Only reformat 1-layer deep to preserve __repr__ of child objects""" + res = {} + for key, val in items(self): + if val == [] or val == {} or val is None: + continue + res[key] = val + return self.__class__.__name__ + '(' + pformat(res, indent=2, + compact=True) + ')' + def __str__(self): + """Dump everything into a dict, recursively, stringifying it all""" res = remove_empty_items(self) - return pformat(as_dict(res),indent=2,compact=True) - + return self.__class__.__name__ + '(' + pformat(res, indent=2, compact=True) + ')' def root_representer(dumper: yaml.Dumper, data: YAMLRoot): From 23c7c6225d9379fbaffa580d96f397c4cbe736ef Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Mon, 25 Mar 2024 17:37:02 -0700 Subject: [PATCH 4/7] convert string comparison tests to instance comparisons --- .../test_utils/test_inlined_as_dict_forms.py | 78 +++++++++++-------- .../test_utils/test_inlined_as_list_forms.py | 70 +++++++++-------- 2 files changed, 84 insertions(+), 64 deletions(-) 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__': From 8b8b48aebc3818497519d2b2f47c6d1e552c58cb Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Mon, 25 Mar 2024 17:45:58 -0700 Subject: [PATCH 5/7] remove unused imports --- linkml_runtime/utils/yamlutils.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/linkml_runtime/utils/yamlutils.py b/linkml_runtime/utils/yamlutils.py index 4b4d184a..8f06f78b 100644 --- a/linkml_runtime/utils/yamlutils.py +++ b/linkml_runtime/utils/yamlutils.py @@ -1,9 +1,7 @@ -import pdb 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 yaml from deprecated.classic import deprecated From 2acff669b8cfb3ca19d5aaa04a124daddcf0d39d Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Tue, 26 Mar 2024 18:50:27 -0700 Subject: [PATCH 6/7] add no repr to enum parent class, and to tested metaclasses --- linkml_runtime/linkml_model/meta.py | 70 ++++++++++++++-------------- linkml_runtime/utils/enumerations.py | 2 +- linkml_runtime/utils/yamlutils.py | 2 +- 3 files changed, 37 insertions(+), 37 deletions(-) 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 8f06f78b..1c4803a0 100644 --- a/linkml_runtime/utils/yamlutils.py +++ b/linkml_runtime/utils/yamlutils.py @@ -286,7 +286,7 @@ def __repr__(self): continue res[key] = val return self.__class__.__name__ + '(' + pformat(res, indent=2, - compact=True) + ')' + compact=True, sort_dicts=False) + ')' def __str__(self): """Dump everything into a dict, recursively, stringifying it all""" From 641c4d8a822e937d7dd3df4cc28922928f6b568e Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Tue, 26 Mar 2024 22:24:13 -0700 Subject: [PATCH 7/7] actually pretty printing that doesn't get all bunched up all over the console --- linkml_runtime/utils/yamlutils.py | 50 ++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/linkml_runtime/utils/yamlutils.py b/linkml_runtime/utils/yamlutils.py index 1c4803a0..219cb135 100644 --- a/linkml_runtime/utils/yamlutils.py +++ b/linkml_runtime/utils/yamlutils.py @@ -2,6 +2,8 @@ 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 @@ -279,19 +281,45 @@ def MissingRequiredField(self, field_name: str) -> None: raise ValueError(f"{field_name} must be supplied") def __repr__(self): - """Only reformat 1-layer deep to preserve __repr__ of child objects""" - res = {} - for key, val in items(self): - if val == [] or val == {} or val is None: - continue - res[key] = val - return self.__class__.__name__ + '(' + pformat(res, indent=2, - compact=True, sort_dicts=False) + ')' + return _pformat(items(self), self.__class__.__name__) def __str__(self): - """Dump everything into a dict, recursively, stringifying it all""" - res = remove_empty_items(self) - return self.__class__.__name__ + '(' + pformat(res, indent=2, compact=True) + ')' + 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):