Skip to content

Commit 27990c1

Browse files
authored
Merge pull request #313 from sneakers-the-rat/sort-classes
Add inheritance ordering, unify element ordering
2 parents 3b0f817 + e5b903e commit 27990c1

File tree

2 files changed

+63
-20
lines changed

2 files changed

+63
-20
lines changed

linkml_runtime/exceptions.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class OrderingError(RuntimeError):
2+
"""Exception raised when there is a problem with SchemaView ordering"""

linkml_runtime/utils/schemaview.py

Lines changed: 61 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@
66
from copy import copy, deepcopy
77
from collections import defaultdict, deque
88
from pathlib import Path
9-
from typing import Mapping, Tuple
9+
from typing import Mapping, Tuple, TypeVar
1010
import warnings
1111

1212
from linkml_runtime.utils.namespaces import Namespaces
1313
from deprecated.classic import deprecated
1414
from linkml_runtime.utils.context_utils import parse_import_map, map_import
1515
from linkml_runtime.utils.pattern import PatternResolver
1616
from linkml_runtime.linkml_model.meta import *
17+
from linkml_runtime.exceptions import OrderingError
1718
from enum import Enum
1819

1920
logger = logging.getLogger(__name__)
@@ -33,11 +34,23 @@
3334
TYPE_NAME = Union[TypeDefinitionName, str]
3435
ENUM_NAME = Union[EnumDefinitionName, str]
3536

37+
ElementType = TypeVar("ElementType", bound=Element)
38+
ElementNameType = TypeVar("ElementNameType", bound=Union[ElementName,str])
39+
DefinitionType = TypeVar("DefinitionType", bound=Definition)
40+
DefinitionNameType = TypeVar("DefinitionNameType", bound=Union[DefinitionName,str])
41+
ElementDict = Dict[ElementNameType, ElementType]
42+
DefDict = Dict[DefinitionNameType, DefinitionType]
43+
3644

3745
class OrderedBy(Enum):
3846
RANK = "rank"
3947
LEXICAL = "lexical"
4048
PRESERVE = "preserve"
49+
INHERITANCE = "inheritance"
50+
"""
51+
Order according to inheritance such that if C is a child of P then C appears after P
52+
"""
53+
4154

4255

4356
def _closure(f, x, reflexive=True, depth_first=True, **kwargs):
@@ -305,7 +318,22 @@ def all_class(self, imports=True) -> Dict[ClassDefinitionName, ClassDefinition]:
305318
"""
306319
return self._get_dict(CLASSES, imports)
307320

308-
def _order_lexically(self, elements: dict):
321+
def ordered(self, elements: ElementDict, ordered_by: Optional[OrderedBy] = None) -> ElementDict:
322+
"""
323+
Order a dictionary of elements with some ordering method in :class:`.OrderedBy`
324+
"""
325+
if ordered_by in (OrderedBy.LEXICAL, OrderedBy.LEXICAL.value):
326+
return self._order_lexically(elements)
327+
elif ordered_by in (OrderedBy.RANK, OrderedBy.RANK.value):
328+
return self._order_rank(elements)
329+
elif ordered_by in (OrderedBy.INHERITANCE, OrderedBy.INHERITANCE.value):
330+
return self._order_inheritance(elements)
331+
elif ordered_by is None or ordered_by in (OrderedBy.PRESERVE, OrderedBy.PRESERVE.value):
332+
return elements
333+
else:
334+
raise ValueError(f"ordered_by must be in OrderedBy or None, got {ordered_by}")
335+
336+
def _order_lexically(self, elements: ElementDict) -> ElementDict:
309337
"""
310338
:param element: slots or class type to order
311339
:param imports
@@ -320,7 +348,7 @@ def _order_lexically(self, elements: dict):
320348
ordered_elements[self.get_element(name).name] = self.get_element(name)
321349
return ordered_elements
322350

323-
def _order_rank(self, elements: dict):
351+
def _order_rank(self, elements: ElementDict) -> ElementDict:
324352
"""
325353
:param elements: slots or classes to order
326354
:return: all classes or slots sorted by their rank in schema view
@@ -342,6 +370,32 @@ def _order_rank(self, elements: dict):
342370
rank_ordered_elements.update(unranked_map)
343371
return rank_ordered_elements
344372

373+
def _order_inheritance(self, elements: DefDict) -> DefDict:
374+
"""
375+
sort classes such that if C is a child of P then C appears after P in the list
376+
"""
377+
clist = list(elements.values())
378+
slist = [] # sorted
379+
can_add = False
380+
while len(clist) > 0:
381+
for i in range(len(clist)):
382+
candidate = clist[i]
383+
can_add = False
384+
if candidate.is_a is None:
385+
can_add = True
386+
else:
387+
if candidate.is_a in [p.name for p in slist]:
388+
can_add = True
389+
if can_add:
390+
slist = slist + [candidate]
391+
del clist[i]
392+
break
393+
if not can_add:
394+
raise OrderingError(f"could not find suitable element in {clist} that does not ref {slist}")
395+
396+
return {s.name: s for s in slist}
397+
398+
345399
@lru_cache(None)
346400
def all_classes(self, ordered_by=OrderedBy.PRESERVE, imports=True) -> Dict[ClassDefinitionName, ClassDefinition]:
347401
"""
@@ -350,15 +404,8 @@ def all_classes(self, ordered_by=OrderedBy.PRESERVE, imports=True) -> Dict[Class
350404
:return: all classes in schema view
351405
"""
352406
classes = copy(self._get_dict(CLASSES, imports))
353-
354-
if ordered_by == OrderedBy.LEXICAL:
355-
ordered_classes = self._order_lexically(elements=classes)
356-
elif ordered_by == OrderedBy.RANK:
357-
ordered_classes = self._order_rank(elements=classes)
358-
else: # else preserve the order in the yaml
359-
ordered_classes = classes
360-
361-
return ordered_classes
407+
classes = self.ordered(classes, ordered_by=ordered_by)
408+
return classes
362409

363410
@deprecated("Use `all_slots` instead")
364411
@lru_cache(None)
@@ -386,14 +433,8 @@ def all_slots(self, ordered_by=OrderedBy.PRESERVE, imports=True, attributes=True
386433
if aname not in slots:
387434
slots[aname] = a
388435

389-
if ordered_by == OrderedBy.LEXICAL:
390-
ordered_slots = self._order_lexically(elements=slots)
391-
elif ordered_by == OrderedBy.RANK:
392-
ordered_slots = self._order_rank(elements=slots)
393-
else:
394-
# preserve order in YAML
395-
ordered_slots = slots
396-
return ordered_slots
436+
slots = self.ordered(slots, ordered_by=ordered_by)
437+
return slots
397438

398439
@deprecated("Use `all_enums` instead")
399440
@lru_cache(None)

0 commit comments

Comments
 (0)