Skip to content

Commit

Permalink
Merge pull request #313 from sneakers-the-rat/sort-classes
Browse files Browse the repository at this point in the history
Add inheritance ordering, unify element ordering
  • Loading branch information
cmungall authored Mar 21, 2024
2 parents 3b0f817 + e5b903e commit 27990c1
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 20 deletions.
2 changes: 2 additions & 0 deletions linkml_runtime/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class OrderingError(RuntimeError):
"""Exception raised when there is a problem with SchemaView ordering"""
81 changes: 61 additions & 20 deletions linkml_runtime/utils/schemaview.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@
from copy import copy, deepcopy
from collections import defaultdict, deque
from pathlib import Path
from typing import Mapping, Tuple
from typing import Mapping, Tuple, TypeVar
import warnings

from linkml_runtime.utils.namespaces import Namespaces
from deprecated.classic import deprecated
from linkml_runtime.utils.context_utils import parse_import_map, map_import
from linkml_runtime.utils.pattern import PatternResolver
from linkml_runtime.linkml_model.meta import *
from linkml_runtime.exceptions import OrderingError
from enum import Enum

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

ElementType = TypeVar("ElementType", bound=Element)
ElementNameType = TypeVar("ElementNameType", bound=Union[ElementName,str])
DefinitionType = TypeVar("DefinitionType", bound=Definition)
DefinitionNameType = TypeVar("DefinitionNameType", bound=Union[DefinitionName,str])
ElementDict = Dict[ElementNameType, ElementType]
DefDict = Dict[DefinitionNameType, DefinitionType]


class OrderedBy(Enum):
RANK = "rank"
LEXICAL = "lexical"
PRESERVE = "preserve"
INHERITANCE = "inheritance"
"""
Order according to inheritance such that if C is a child of P then C appears after P
"""



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

def _order_lexically(self, elements: dict):
def ordered(self, elements: ElementDict, ordered_by: Optional[OrderedBy] = None) -> ElementDict:
"""
Order a dictionary of elements with some ordering method in :class:`.OrderedBy`
"""
if ordered_by in (OrderedBy.LEXICAL, OrderedBy.LEXICAL.value):
return self._order_lexically(elements)
elif ordered_by in (OrderedBy.RANK, OrderedBy.RANK.value):
return self._order_rank(elements)
elif ordered_by in (OrderedBy.INHERITANCE, OrderedBy.INHERITANCE.value):
return self._order_inheritance(elements)
elif ordered_by is None or ordered_by in (OrderedBy.PRESERVE, OrderedBy.PRESERVE.value):
return elements
else:
raise ValueError(f"ordered_by must be in OrderedBy or None, got {ordered_by}")

def _order_lexically(self, elements: ElementDict) -> ElementDict:
"""
:param element: slots or class type to order
:param imports
Expand All @@ -320,7 +348,7 @@ def _order_lexically(self, elements: dict):
ordered_elements[self.get_element(name).name] = self.get_element(name)
return ordered_elements

def _order_rank(self, elements: dict):
def _order_rank(self, elements: ElementDict) -> ElementDict:
"""
:param elements: slots or classes to order
:return: all classes or slots sorted by their rank in schema view
Expand All @@ -342,6 +370,32 @@ def _order_rank(self, elements: dict):
rank_ordered_elements.update(unranked_map)
return rank_ordered_elements

def _order_inheritance(self, elements: DefDict) -> DefDict:
"""
sort classes such that if C is a child of P then C appears after P in the list
"""
clist = list(elements.values())
slist = [] # sorted
can_add = False
while len(clist) > 0:
for i in range(len(clist)):
candidate = clist[i]
can_add = False
if candidate.is_a is None:
can_add = True
else:
if candidate.is_a in [p.name for p in slist]:
can_add = True
if can_add:
slist = slist + [candidate]
del clist[i]
break
if not can_add:
raise OrderingError(f"could not find suitable element in {clist} that does not ref {slist}")

return {s.name: s for s in slist}


@lru_cache(None)
def all_classes(self, ordered_by=OrderedBy.PRESERVE, imports=True) -> Dict[ClassDefinitionName, ClassDefinition]:
"""
Expand All @@ -350,15 +404,8 @@ def all_classes(self, ordered_by=OrderedBy.PRESERVE, imports=True) -> Dict[Class
:return: all classes in schema view
"""
classes = copy(self._get_dict(CLASSES, imports))

if ordered_by == OrderedBy.LEXICAL:
ordered_classes = self._order_lexically(elements=classes)
elif ordered_by == OrderedBy.RANK:
ordered_classes = self._order_rank(elements=classes)
else: # else preserve the order in the yaml
ordered_classes = classes

return ordered_classes
classes = self.ordered(classes, ordered_by=ordered_by)
return classes

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

if ordered_by == OrderedBy.LEXICAL:
ordered_slots = self._order_lexically(elements=slots)
elif ordered_by == OrderedBy.RANK:
ordered_slots = self._order_rank(elements=slots)
else:
# preserve order in YAML
ordered_slots = slots
return ordered_slots
slots = self.ordered(slots, ordered_by=ordered_by)
return slots

@deprecated("Use `all_enums` instead")
@lru_cache(None)
Expand Down

0 comments on commit 27990c1

Please sign in to comment.