6
6
from copy import copy , deepcopy
7
7
from collections import defaultdict , deque
8
8
from pathlib import Path
9
- from typing import Mapping , Tuple
9
+ from typing import Mapping , Tuple , TypeVar
10
10
import warnings
11
11
12
12
from linkml_runtime .utils .namespaces import Namespaces
13
13
from deprecated .classic import deprecated
14
14
from linkml_runtime .utils .context_utils import parse_import_map , map_import
15
15
from linkml_runtime .utils .pattern import PatternResolver
16
16
from linkml_runtime .linkml_model .meta import *
17
+ from linkml_runtime .exceptions import OrderingError
17
18
from enum import Enum
18
19
19
20
logger = logging .getLogger (__name__ )
33
34
TYPE_NAME = Union [TypeDefinitionName , str ]
34
35
ENUM_NAME = Union [EnumDefinitionName , str ]
35
36
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
+
36
44
37
45
class OrderedBy (Enum ):
38
46
RANK = "rank"
39
47
LEXICAL = "lexical"
40
48
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
+
41
54
42
55
43
56
def _closure (f , x , reflexive = True , depth_first = True , ** kwargs ):
@@ -305,7 +318,22 @@ def all_class(self, imports=True) -> Dict[ClassDefinitionName, ClassDefinition]:
305
318
"""
306
319
return self ._get_dict (CLASSES , imports )
307
320
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 :
309
337
"""
310
338
:param element: slots or class type to order
311
339
:param imports
@@ -320,7 +348,7 @@ def _order_lexically(self, elements: dict):
320
348
ordered_elements [self .get_element (name ).name ] = self .get_element (name )
321
349
return ordered_elements
322
350
323
- def _order_rank (self , elements : dict ) :
351
+ def _order_rank (self , elements : ElementDict ) -> ElementDict :
324
352
"""
325
353
:param elements: slots or classes to order
326
354
:return: all classes or slots sorted by their rank in schema view
@@ -342,6 +370,32 @@ def _order_rank(self, elements: dict):
342
370
rank_ordered_elements .update (unranked_map )
343
371
return rank_ordered_elements
344
372
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
+
345
399
@lru_cache (None )
346
400
def all_classes (self , ordered_by = OrderedBy .PRESERVE , imports = True ) -> Dict [ClassDefinitionName , ClassDefinition ]:
347
401
"""
@@ -350,15 +404,8 @@ def all_classes(self, ordered_by=OrderedBy.PRESERVE, imports=True) -> Dict[Class
350
404
:return: all classes in schema view
351
405
"""
352
406
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
362
409
363
410
@deprecated ("Use `all_slots` instead" )
364
411
@lru_cache (None )
@@ -386,14 +433,8 @@ def all_slots(self, ordered_by=OrderedBy.PRESERVE, imports=True, attributes=True
386
433
if aname not in slots :
387
434
slots [aname ] = a
388
435
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
397
438
398
439
@deprecated ("Use `all_enums` instead" )
399
440
@lru_cache (None )
0 commit comments