66from copy import copy , deepcopy
77from collections import defaultdict , deque
88from pathlib import Path
9- from typing import Mapping , Tuple
9+ from typing import Mapping , Tuple , TypeVar
1010import warnings
1111
1212from linkml_runtime .utils .namespaces import Namespaces
1313from deprecated .classic import deprecated
1414from linkml_runtime .utils .context_utils import parse_import_map , map_import
1515from linkml_runtime .utils .pattern import PatternResolver
1616from linkml_runtime .linkml_model .meta import *
17+ from linkml_runtime .exceptions import OrderingError
1718from enum import Enum
1819
1920logger = logging .getLogger (__name__ )
3334TYPE_NAME = Union [TypeDefinitionName , str ]
3435ENUM_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
3745class 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
4356def _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