Skip to content

Commit

Permalink
Add inheritance ordering from linkml generators, put ordering in a si…
Browse files Browse the repository at this point in the history
…gnle method
  • Loading branch information
sneakers-the-rat committed Mar 21, 2024
1 parent 3b0f817 commit e5b903e
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)

Check warning on line 330 in linkml_runtime/utils/schemaview.py

View check run for this annotation

Codecov / codecov/patch

linkml_runtime/utils/schemaview.py#L330

Added line #L330 was not covered by tests
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}")

Check warning on line 334 in linkml_runtime/utils/schemaview.py

View check run for this annotation

Codecov / codecov/patch

linkml_runtime/utils/schemaview.py#L334

Added line #L334 was not covered by tests

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

Check warning on line 379 in linkml_runtime/utils/schemaview.py

View check run for this annotation

Codecov / codecov/patch

linkml_runtime/utils/schemaview.py#L377-L379

Added lines #L377 - L379 were not covered by tests
while len(clist) > 0:
for i in range(len(clist)):
candidate = clist[i]
can_add = False

Check warning on line 383 in linkml_runtime/utils/schemaview.py

View check run for this annotation

Codecov / codecov/patch

linkml_runtime/utils/schemaview.py#L382-L383

Added lines #L382 - L383 were not covered by tests
if candidate.is_a is None:
can_add = True

Check warning on line 385 in linkml_runtime/utils/schemaview.py

View check run for this annotation

Codecov / codecov/patch

linkml_runtime/utils/schemaview.py#L385

Added line #L385 was not covered by tests
else:
if candidate.is_a in [p.name for p in slist]:
can_add = True

Check warning on line 388 in linkml_runtime/utils/schemaview.py

View check run for this annotation

Codecov / codecov/patch

linkml_runtime/utils/schemaview.py#L388

Added line #L388 was not covered by tests
if can_add:
slist = slist + [candidate]
del clist[i]
break

Check warning on line 392 in linkml_runtime/utils/schemaview.py

View check run for this annotation

Codecov / codecov/patch

linkml_runtime/utils/schemaview.py#L390-L392

Added lines #L390 - L392 were not covered by tests
if not can_add:
raise OrderingError(f"could not find suitable element in {clist} that does not ref {slist}")

Check warning on line 394 in linkml_runtime/utils/schemaview.py

View check run for this annotation

Codecov / codecov/patch

linkml_runtime/utils/schemaview.py#L394

Added line #L394 was not covered by tests

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 e5b903e

Please sign in to comment.