Skip to content

Commit d501d42

Browse files
authored
Merge pull request #314 from sneakers-the-rat/pretty-print
pretty print models
2 parents 641bb1c + 641c4d8 commit d501d42

File tree

5 files changed

+165
-101
lines changed

5 files changed

+165
-101
lines changed

linkml_runtime/linkml_model/meta.py

+35-35
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ class TypeMappingFramework(extended_str):
148148

149149
Anything = Any
150150

151-
@dataclass
151+
@dataclass(repr=False)
152152
class CommonMetadata(YAMLRoot):
153153
"""
154154
Generic metadata shared across definitions
@@ -311,7 +311,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
311311
super().__post_init__(**kwargs)
312312

313313

314-
@dataclass
314+
@dataclass(repr=False)
315315
class Element(YAMLRoot):
316316
"""
317317
A named element in the model
@@ -516,7 +516,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
516516
super().__post_init__(**kwargs)
517517

518518

519-
@dataclass
519+
@dataclass(repr=False)
520520
class SchemaDefinition(Element):
521521
"""
522522
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]):
628628
super().__post_init__(**kwargs)
629629

630630

631-
@dataclass
631+
@dataclass(repr=False)
632632
class AnonymousTypeExpression(YAMLRoot):
633633
"""
634634
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]):
696696
super().__post_init__(**kwargs)
697697

698698

699-
@dataclass
699+
@dataclass(repr=False)
700700
class TypeDefinition(Element):
701701
"""
702702
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]):
791791
super().__post_init__(**kwargs)
792792

793793

794-
@dataclass
794+
@dataclass(repr=False)
795795
class SubsetDefinition(Element):
796796
"""
797797
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]):
814814
super().__post_init__(**kwargs)
815815

816816

817-
@dataclass
817+
@dataclass(repr=False)
818818
class Definition(Element):
819819
"""
820820
abstract base class for core metaclasses
@@ -863,7 +863,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
863863
super().__post_init__(**kwargs)
864864

865865

866-
@dataclass
866+
@dataclass(repr=False)
867867
class AnonymousEnumExpression(YAMLRoot):
868868
"""
869869
An enum_expression that is not named
@@ -927,7 +927,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
927927
super().__post_init__(**kwargs)
928928

929929

930-
@dataclass
930+
@dataclass(repr=False)
931931
class EnumDefinition(Definition):
932932
"""
933933
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]):
10011001
super().__post_init__(**kwargs)
10021002

10031003

1004-
@dataclass
1004+
@dataclass(repr=False)
10051005
class EnumBinding(YAMLRoot):
10061006
"""
10071007
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]):
11861186
super().__post_init__(**kwargs)
11871187

11881188

1189-
@dataclass
1189+
@dataclass(repr=False)
11901190
class MatchQuery(YAMLRoot):
11911191
"""
11921192
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]):
12121212
super().__post_init__(**kwargs)
12131213

12141214

1215-
@dataclass
1215+
@dataclass(repr=False)
12161216
class ReachabilityQuery(YAMLRoot):
12171217
"""
12181218
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]):
12561256
super().__post_init__(**kwargs)
12571257

12581258

1259-
@dataclass
1259+
@dataclass(repr=False)
12601260
class StructuredAlias(YAMLRoot):
12611261
"""
12621262
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):
14531453
class_model_uri: ClassVar[URIRef] = LINKML.Expression
14541454

14551455

1456-
@dataclass
1456+
@dataclass(repr=False)
14571457
class TypeExpression(Expression):
14581458
"""
14591459
An abstract class grouping named types and anonymous type expressions
@@ -1521,7 +1521,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
15211521
super().__post_init__(**kwargs)
15221522

15231523

1524-
@dataclass
1524+
@dataclass(repr=False)
15251525
class EnumExpression(Expression):
15261526
"""
15271527
An expression that constrains the range of a slot
@@ -1585,7 +1585,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
15851585
super().__post_init__(**kwargs)
15861586

15871587

1588-
@dataclass
1588+
@dataclass(repr=False)
15891589
class AnonymousExpression(YAMLRoot):
15901590
"""
15911591
An abstract parent class for any nested expression
@@ -1754,7 +1754,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
17541754
super().__post_init__(**kwargs)
17551755

17561756

1757-
@dataclass
1757+
@dataclass(repr=False)
17581758
class PathExpression(YAMLRoot):
17591759
"""
17601760
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]):
19591959
super().__post_init__(**kwargs)
19601960

19611961

1962-
@dataclass
1962+
@dataclass(repr=False)
19631963
class SlotExpression(Expression):
19641964
"""
19651965
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]):
20922092
super().__post_init__(**kwargs)
20932093

20942094

2095-
@dataclass
2095+
@dataclass(repr=False)
20962096
class AnonymousSlotExpression(AnonymousExpression):
20972097
_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"]
20982098

@@ -2222,7 +2222,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
22222222
super().__post_init__(**kwargs)
22232223

22242224

2225-
@dataclass
2225+
@dataclass(repr=False)
22262226
class SlotDefinition(Definition):
22272227
"""
22282228
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]):
25312531
super().__post_init__(**kwargs)
25322532

25332533

2534-
@dataclass
2534+
@dataclass(repr=False)
25352535
class ClassExpression(YAMLRoot):
25362536
"""
25372537
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]):
25712571
super().__post_init__(**kwargs)
25722572

25732573

2574-
@dataclass
2574+
@dataclass(repr=False)
25752575
class AnonymousClassExpression(AnonymousExpression):
25762576
_inherited_slots: ClassVar[List[str]] = []
25772577

@@ -2612,7 +2612,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
26122612
super().__post_init__(**kwargs)
26132613

26142614

2615-
@dataclass
2615+
@dataclass(repr=False)
26162616
class ClassDefinition(Definition):
26172617
"""
26182618
an element whose instances are complex objects that may have slot-value assignments
@@ -2747,7 +2747,7 @@ class ClassLevelRule(YAMLRoot):
27472747
class_model_uri: ClassVar[URIRef] = LINKML.ClassLevelRule
27482748

27492749

2750-
@dataclass
2750+
@dataclass(repr=False)
27512751
class ClassRule(ClassLevelRule):
27522752
"""
27532753
A rule that applies to instances of a class
@@ -2940,7 +2940,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
29402940
super().__post_init__(**kwargs)
29412941

29422942

2943-
@dataclass
2943+
@dataclass(repr=False)
29442944
class ArrayExpression(YAMLRoot):
29452945
"""
29462946
defines the dimensions of an array
@@ -3123,7 +3123,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
31233123
super().__post_init__(**kwargs)
31243124

31253125

3126-
@dataclass
3126+
@dataclass(repr=False)
31273127
class DimensionExpression(YAMLRoot):
31283128
"""
31293129
defines one of the dimensions of an array
@@ -3308,7 +3308,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
33083308
super().__post_init__(**kwargs)
33093309

33103310

3311-
@dataclass
3311+
@dataclass(repr=False)
33123312
class PatternExpression(YAMLRoot):
33133313
"""
33143314
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]):
34893489
super().__post_init__(**kwargs)
34903490

34913491

3492-
@dataclass
3492+
@dataclass(repr=False)
34933493
class ImportExpression(YAMLRoot):
34943494
"""
34953495
an expression describing an import
@@ -3671,7 +3671,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
36713671
super().__post_init__(**kwargs)
36723672

36733673

3674-
@dataclass
3674+
@dataclass(repr=False)
36753675
class Setting(YAMLRoot):
36763676
"""
36773677
assignment of a key to a value
@@ -3700,7 +3700,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
37003700
super().__post_init__(**kwargs)
37013701

37023702

3703-
@dataclass
3703+
@dataclass(repr=False)
37043704
class Prefix(YAMLRoot):
37053705
"""
37063706
prefix URI tuple
@@ -3729,7 +3729,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
37293729
super().__post_init__(**kwargs)
37303730

37313731

3732-
@dataclass
3732+
@dataclass(repr=False)
37333733
class LocalName(YAMLRoot):
37343734
"""
37353735
an attributed label
@@ -3758,7 +3758,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
37583758
super().__post_init__(**kwargs)
37593759

37603760

3761-
@dataclass
3761+
@dataclass(repr=False)
37623762
class Example(YAMLRoot):
37633763
"""
37643764
usage example and description
@@ -3784,7 +3784,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
37843784
super().__post_init__(**kwargs)
37853785

37863786

3787-
@dataclass
3787+
@dataclass(repr=False)
37883788
class AltDescription(YAMLRoot):
37893789
"""
37903790
an attributed description
@@ -3813,7 +3813,7 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
38133813
super().__post_init__(**kwargs)
38143814

38153815

3816-
@dataclass
3816+
@dataclass(repr=False)
38173817
class PermissibleValue(YAMLRoot):
38183818
"""
38193819
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]):
40154015
super().__post_init__(**kwargs)
40164016

40174017

4018-
@dataclass
4018+
@dataclass(repr=False)
40194019
class UniqueKey(YAMLRoot):
40204020
"""
40214021
a collection of slots whose values uniquely identify an instance of a class

linkml_runtime/utils/enumerations.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,4 @@ def __str__(self) -> str:
102102

103103
def __repr__(self) -> str:
104104
rlist = [(f.name, getattr(self._code, f.name)) for f in fields(self._code)]
105-
return '(' + ', '.join([f"{f[0]}={repr(f[1])}" for f in rlist if f[1]]) + ')'
105+
return self.__class__.__name__ + '(' + ', '.join([f"{f[0]}={repr(f[1])}" for f in rlist if f[1]]) + ')'

linkml_runtime/utils/yamlutils.py

+45-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
from copy import copy
22
from json import JSONDecoder
33
from typing import Union, Any, List, Optional, Type, Callable, Dict
4+
from pprint import pformat
5+
import textwrap
6+
import re
47

58
import yaml
69
from deprecated.classic import deprecated
@@ -10,7 +13,7 @@
1013
from yaml.constructor import ConstructorError
1114

1215
from linkml_runtime.utils.context_utils import CONTEXTS_PARAM_TYPE, merge_contexts
13-
from linkml_runtime.utils.formatutils import is_empty
16+
from linkml_runtime.utils.formatutils import is_empty, remove_empty_items, is_list, is_dict, items
1417

1518
YAMLObjTypes = Union[JsonObjTypes, "YAMLRoot"]
1619

@@ -277,6 +280,47 @@ def MissingRequiredField(self, field_name: str) -> None:
277280
""" Generic loader error handler """
278281
raise ValueError(f"{field_name} must be supplied")
279282

283+
def __repr__(self):
284+
return _pformat(items(self), self.__class__.__name__)
285+
286+
def __str__(self):
287+
return repr(self)
288+
289+
def _pformat(fields:dict, cls_name:str, indent:str = ' ') -> str:
290+
"""
291+
pretty format the fields of the items of a ``YAMLRoot`` object without the wonky indentation of pformat.
292+
see ``YAMLRoot.__repr__``.
293+
294+
formatting is similar to black - items at similar levels of nesting have similar levels of indentation,
295+
rather than getting placed at essentially random levels of indentation depending on what came before them.
296+
"""
297+
res = []
298+
total_len = 0
299+
for key, val in fields:
300+
if val == [] or val == {} or val is None:
301+
continue
302+
# pformat handles everything else that isn't a YAMLRoot object, but it sure does look ugly
303+
# use it to split lines and as the thing of last resort, but otherwise indent = 0, we'll do that
304+
val_str = pformat(val, indent=0, compact=True, sort_dicts=False)
305+
# now we indent everything except the first line by indenting and then using regex to remove just the first indent
306+
val_str = re.sub(rf'\A{re.escape(indent)}', '', textwrap.indent(val_str, indent))
307+
# now recombine with the key in a format that can be re-eval'd into an object if indent is just whitespace
308+
val_str = f"'{key}': " + val_str
309+
310+
# count the total length of this string so we know if we need to linebreak or not later
311+
total_len += len(val_str)
312+
res.append(val_str)
313+
314+
if total_len > 80:
315+
inside = ',\n'.join(res)
316+
# we indent twice - once for the inner contents of every inner object, and one to
317+
# offset from the root element. that keeps us from needing to be recursive except for the
318+
# single pformat call
319+
inside = textwrap.indent(inside, indent)
320+
return cls_name + '({\n' + inside + '\n})'
321+
else:
322+
return cls_name + '({' + ', '.join(res) + '})'
323+
280324

281325
def root_representer(dumper: yaml.Dumper, data: YAMLRoot):
282326
""" YAML callback -- used to filter out empty values (None, {}, [] and false)

0 commit comments

Comments
 (0)