From b5733f577bd756ab41ed51373ffcc324961e75be Mon Sep 17 00:00:00 2001 From: ialarmedalien Date: Mon, 6 Oct 2025 13:07:01 -0700 Subject: [PATCH 1/2] Reordering functions relating to calculating relationships (ancestors/descendants, etc) --- linkml_runtime/utils/schemaview.py | 446 ++++++++++++++--------------- 1 file changed, 223 insertions(+), 223 deletions(-) diff --git a/linkml_runtime/utils/schemaview.py b/linkml_runtime/utils/schemaview.py index 9e1b2e89..9f3274f1 100644 --- a/linkml_runtime/utils/schemaview.py +++ b/linkml_runtime/utils/schemaview.py @@ -787,6 +787,23 @@ def get_type(self, type_name: TYPE_NAME, imports: bool = True, strict: bool = Fa raise ValueError(msg) return t + @lru_cache(None) + def get_children(self, name: str, mixin: bool = True) -> list[str]: + """Get the children of an element (any class, slot, enum, type). + + :param name: name of the parent element + :param mixin: include mixins + :return: list of child element + """ + children = [] + for el in self.all_elements().values(): + if isinstance(el, (ClassDefinition, SlotDefinition, EnumDefinition)): + if el.is_a and el.is_a == name: + children.append(el.name) + if mixin and el.mixins and name in el.mixins: + children.append(el.name) + return children + def _parents(self, e: Element, imports: bool = True, mixins: bool = True, is_a: bool = True) -> list[ElementName]: parents = copy(e.mixins) if mixins else [] if e.is_a is not None and is_a: @@ -805,103 +822,6 @@ def class_parents( cls = self.get_class(class_name, imports, strict=True) return self._parents(cls, imports, mixins, is_a) - @lru_cache(None) - def enum_parents( - self, enum_name: ENUM_NAME, imports: bool = False, mixins: bool = False, is_a: bool = True - ) -> list[EnumDefinitionName]: - """:param enum_name: child enum name - :param imports: include import closure (False) - :param mixins: include mixins (default is False) - :return: all direct parent enum names (is_a and mixins) - """ - e = self.get_enum(enum_name, strict=True) - return self._parents(e, imports, mixins, is_a=is_a) - - @lru_cache(None) - def permissible_value_parent( - self, permissible_value: str, enum_name: ENUM_NAME - ) -> list[str | PermissibleValueText] | None: - """:param enum_name: child enum name - :param permissible_value: permissible value - :return: all direct parent enum names (is_a) - """ - enum = self.get_enum(enum_name, strict=True) - if enum: - if permissible_value in enum.permissible_values: - pv = enum.permissible_values[permissible_value] - if pv.is_a: - return [pv.is_a] - return None - return [] - - @lru_cache(None) - def permissible_value_children( - self, permissible_value: str, enum_name: ENUM_NAME - ) -> list[str | PermissibleValueText] | None: - """:param enum_name: parent enum name - :param permissible_value: permissible value - :return: all direct child permissible values (is_a) - """ - enum = self.get_enum(enum_name, strict=True) - children = [] - if enum: - if permissible_value in enum.permissible_values: - pv = enum.permissible_values[permissible_value] - for isapv in enum.permissible_values: - isapv_entity = enum.permissible_values[isapv] - if isapv_entity.is_a and pv.text == isapv_entity.is_a: - children.append(isapv) - return children - return None - msg = f'No such enum as "{enum_name}"' - raise ValueError(msg) - - @lru_cache(None) - def slot_parents( - self, slot_name: SLOT_NAME, imports: bool = True, mixins: bool = True, is_a: bool = True - ) -> list[SlotDefinitionName]: - """Return the parent of a slot, if it exists. - - :param slot_name: child slot name - :param imports: include import closure - :param mixins: include mixins (default is True) - :return: all direct parent slot names (is_a and mixins) - """ - s = self.get_slot(slot_name, imports, strict=True) - if s: - return self._parents(s, imports, mixins, is_a) - return [] - - @lru_cache(None) - def type_parents(self, type_name: TYPE_NAME, imports: bool = True) -> list[TypeDefinitionName]: - """Return the parent of a type, if it exists. - - :param type_name: child type name - :param imports: include import closure - :return: all direct parent enum names (is_a and mixins) - """ - typ = self.get_type(type_name, imports, strict=True) - if typ.typeof: - return [typ.typeof] - return [] - - @lru_cache(None) - def get_children(self, name: str, mixin: bool = True) -> list[str]: - """Get the children of an element (any class, slot, enum, type). - - :param name: name of the parent element - :param mixin: include mixins - :return: list of child element - """ - children = [] - for el in self.all_elements().values(): - if isinstance(el, (ClassDefinition, SlotDefinition, EnumDefinition)): - if el.is_a and el.is_a == name: - children.append(el.name) - if mixin and el.mixins and name in el.mixins: - children.append(el.name) - return children - @lru_cache(None) def class_children( self, class_name: CLASS_NAME, imports: bool = True, mixins: bool = True, is_a: bool = True @@ -917,21 +837,6 @@ def class_children( elts = [self.get_class(x) for x in self.all_classes(imports=imports)] return [x.name for x in elts if (x.is_a == class_name and is_a) or (mixins and class_name in x.mixins)] - @lru_cache(None) - def slot_children( - self, slot_name: SLOT_NAME, imports: bool = True, mixins: bool = True, is_a: bool = True - ) -> list[SlotDefinitionName]: - """Return slot children. - - :param slot_name: parent slot name - :param imports: include import closure - :param mixins: include mixins (default is True) - :param is_a: include is_a parents (default is True) - :return: all direct child slot names (is_a and mixins) - """ - elts = [self.get_slot(x) for x in self.all_slots(imports=imports)] - return [x.name for x in elts if (x.is_a == slot_name and is_a) or (mixins and slot_name in x.mixins)] - @lru_cache(None) def class_ancestors( self, @@ -960,86 +865,87 @@ def class_ancestors( ) @lru_cache(None) - def permissible_value_ancestors( - self, permissible_value_text: str, enum_name: ENUM_NAME, reflexive: bool = True, depth_first: bool = True - ) -> list[str]: - """Return the closure of permissible_value_parents method. + def class_descendants( + self, + class_name: CLASS_NAME, + imports: bool = True, + mixins: bool = True, + reflexive: bool = True, + is_a: bool = True, + ) -> list[ClassDefinitionName]: + """Return the closure of class_children method. - :param permissible_value_text: - :type permissible_value_text: str - :param enum_name: - :type enum_name: ENUM_NAME - :param reflexive: ..., defaults to True - :type reflexive: bool, optional - :param depth_first: ..., defaults to True - :type depth_first: bool, optional - :return: closure of permissible_value_parents - :rtype: list[str] + :param class_name: query class + :param imports: include import closure + :param mixins: include mixins (default is True) + :param is_a: include is_a parents (default is True) + :param reflexive: include self in set of descendants + :return: descendants class names """ return _closure( - lambda x: self.permissible_value_parent(x, enum_name), - permissible_value_text, - reflexive=reflexive, - depth_first=depth_first, + lambda x: self.class_children(x, imports=imports, mixins=mixins, is_a=is_a), class_name, reflexive=reflexive ) @lru_cache(None) - def permissible_value_descendants( - self, permissible_value_text: str, enum_name: ENUM_NAME, reflexive: bool = True, depth_first: bool = True - ) -> list[str]: - """Return the closure of permissible_value_children method. + def class_roots(self, imports: bool = True, mixins: bool = True, is_a: bool = True) -> list[ClassDefinitionName]: + """Return all classes that have no parents. - :enum + :param imports: + :param mixins: + :param is_a: include is_a parents (default is True) + :return: """ - return _closure( - lambda x: self.permissible_value_children(x, enum_name), - permissible_value_text, - reflexive=reflexive, - depth_first=depth_first, - ) + return [ + c + for c in self.all_classes(imports=imports) + if self.class_parents(c, mixins=mixins, is_a=is_a, imports=imports) == [] + ] @lru_cache(None) - def enum_ancestors( - self, - enum_name: ENUM_NAME, - imports: bool = True, - mixins: bool = True, - reflexive: bool = True, - is_a: bool = True, - depth_first: bool = True, - ) -> list[EnumDefinitionName]: - """Return the closure of enum_parents method. + def class_leaves(self, imports: bool = True, mixins: bool = True, is_a: bool = True) -> list[ClassDefinitionName]: + """Return all classes that have no children. - :param enum_name: query enum + :param imports: + :param mixins: + :param is_a: include is_a parents (default is True) + :return: + """ + return [ + c + for c in self.all_classes(imports=imports) + if self.class_children(c, mixins=mixins, is_a=is_a, imports=imports) == [] + ] + + @lru_cache(None) + def slot_parents( + self, slot_name: SLOT_NAME, imports: bool = True, mixins: bool = True, is_a: bool = True + ) -> list[SlotDefinitionName]: + """Return the parent of a slot, if it exists. + + :param slot_name: child slot name :param imports: include import closure :param mixins: include mixins (default is True) - :param is_a: include is_a parents (default is True) - :param reflexive: include self in set of ancestors - :param depth_first: - :return: ancestor enum names + :return: all direct parent slot names (is_a and mixins) """ - return _closure( - lambda x: self.enum_parents(x, imports=imports, mixins=mixins, is_a=is_a), - enum_name, - reflexive=reflexive, - depth_first=depth_first, - ) + s = self.get_slot(slot_name, imports, strict=True) + if s: + return self._parents(s, imports, mixins, is_a) + return [] @lru_cache(None) - def type_ancestors( - self, type_name: TYPE_NAME, imports: bool = True, reflexive: bool = True, depth_first: bool = True - ) -> list[TypeDefinitionName]: - """Return all ancestors of a type via typeof. + def slot_children( + self, slot_name: SLOT_NAME, imports: bool = True, mixins: bool = True, is_a: bool = True + ) -> list[SlotDefinitionName]: + """Return slot children. - :param type_name: query type + :param slot_name: parent slot name :param imports: include import closure - :param reflexive: include self in set of ancestors - :param depth_first: - :return: ancestor class names + :param mixins: include mixins (default is True) + :param is_a: include is_a parents (default is True) + :return: all direct child slot names (is_a and mixins) """ - return _closure( - lambda x: self.type_parents(x, imports=imports), type_name, reflexive=reflexive, depth_first=depth_first - ) + elts = [self.get_slot(x) for x in self.all_slots(imports=imports)] + return [x.name for x in elts if (x.is_a == slot_name and is_a) or (mixins and slot_name in x.mixins)] @lru_cache(None) def slot_ancestors( @@ -1058,28 +964,6 @@ def slot_ancestors( lambda x: self.slot_parents(x, imports=imports, mixins=mixins, is_a=is_a), slot_name, reflexive=reflexive ) - @lru_cache(None) - def class_descendants( - self, - class_name: CLASS_NAME, - imports: bool = True, - mixins: bool = True, - reflexive: bool = True, - is_a: bool = True, - ) -> list[ClassDefinitionName]: - """Return the closure of class_children method. - - :param class_name: query class - :param imports: include import closure - :param mixins: include mixins (default is True) - :param is_a: include is_a parents (default is True) - :param reflexive: include self in set of descendants - :return: descendants class names - """ - return _closure( - lambda x: self.class_children(x, imports=imports, mixins=mixins, is_a=is_a), class_name, reflexive=reflexive - ) - @lru_cache(None) def slot_descendants( self, slot_name: SLOT_NAME, imports: bool = True, mixins: bool = True, reflexive: bool = True, is_a: bool = True @@ -1098,58 +982,174 @@ def slot_descendants( ) @lru_cache(None) - def class_roots(self, imports: bool = True, mixins: bool = True, is_a: bool = True) -> list[ClassDefinitionName]: - """Return all classes that have no parents. + def slot_roots(self, imports: bool = True, mixins: bool = True) -> list[SlotDefinitionName]: + """Return all slots that have no parents. :param imports: :param mixins: - :param is_a: include is_a parents (default is True) :return: """ return [ - c - for c in self.all_classes(imports=imports) - if self.class_parents(c, mixins=mixins, is_a=is_a, imports=imports) == [] + c for c in self.all_slots(imports=imports) if self.slot_parents(c, mixins=mixins, imports=imports) == [] ] @lru_cache(None) - def class_leaves(self, imports: bool = True, mixins: bool = True, is_a: bool = True) -> list[ClassDefinitionName]: - """Return all classes that have no children. + def slot_leaves(self, imports: bool = True, mixins: bool = True) -> list[SlotDefinitionName]: + """Return all slots that have no children. :param imports: :param mixins: - :param is_a: include is_a parents (default is True) :return: """ return [ - c - for c in self.all_classes(imports=imports) - if self.class_children(c, mixins=mixins, is_a=is_a, imports=imports) == [] + c for c in self.all_slots(imports=imports) if self.slot_children(c, mixins=mixins, imports=imports) == [] ] @lru_cache(None) - def slot_roots(self, imports: bool = True, mixins: bool = True) -> list[SlotDefinitionName]: - """Return all slots that have no parents. + def type_parents(self, type_name: TYPE_NAME, imports: bool = True) -> list[TypeDefinitionName]: + """Return the parent of a type, if it exists. - :param imports: - :param mixins: - :return: + :param type_name: child type name + :param imports: include import closure + :return: all direct parent enum names (is_a and mixins) """ - return [ - c for c in self.all_slots(imports=imports) if self.slot_parents(c, mixins=mixins, imports=imports) == [] - ] + typ = self.get_type(type_name, imports, strict=True) + if typ.typeof: + return [typ.typeof] + return [] @lru_cache(None) - def slot_leaves(self, imports: bool = True, mixins: bool = True) -> list[SlotDefinitionName]: - """Return all slots that have no children. + def type_ancestors( + self, type_name: TYPE_NAME, imports: bool = True, reflexive: bool = True, depth_first: bool = True + ) -> list[TypeDefinitionName]: + """Return all ancestors of a type via typeof. - :param imports: - :param mixins: - :return: + :param type_name: query type + :param imports: include import closure + :param reflexive: include self in set of ancestors + :param depth_first: + :return: ancestor class names """ - return [ - c for c in self.all_slots(imports=imports) if self.slot_children(c, mixins=mixins, imports=imports) == [] - ] + return _closure( + lambda x: self.type_parents(x, imports=imports), type_name, reflexive=reflexive, depth_first=depth_first + ) + + @lru_cache(None) + def enum_parents( + self, enum_name: ENUM_NAME, imports: bool = False, mixins: bool = False, is_a: bool = True + ) -> list[EnumDefinitionName]: + """:param enum_name: child enum name + :param imports: include import closure (False) + :param mixins: include mixins (default is False) + :return: all direct parent enum names (is_a and mixins) + """ + e = self.get_enum(enum_name, strict=True) + return self._parents(e, imports, mixins, is_a=is_a) + + @lru_cache(None) + def enum_ancestors( + self, + enum_name: ENUM_NAME, + imports: bool = True, + mixins: bool = True, + reflexive: bool = True, + is_a: bool = True, + depth_first: bool = True, + ) -> list[EnumDefinitionName]: + """Return the closure of enum_parents method. + + :param enum_name: query enum + :param imports: include import closure + :param mixins: include mixins (default is True) + :param is_a: include is_a parents (default is True) + :param reflexive: include self in set of ancestors + :param depth_first: + :return: ancestor enum names + """ + return _closure( + lambda x: self.enum_parents(x, imports=imports, mixins=mixins, is_a=is_a), + enum_name, + reflexive=reflexive, + depth_first=depth_first, + ) + + @lru_cache(None) + def permissible_value_parent( + self, permissible_value: str, enum_name: ENUM_NAME + ) -> list[str | PermissibleValueText] | None: + """:param enum_name: child enum name + :param permissible_value: permissible value + :return: all direct parent enum names (is_a) + """ + enum = self.get_enum(enum_name, strict=True) + if enum: + if permissible_value in enum.permissible_values: + pv = enum.permissible_values[permissible_value] + if pv.is_a: + return [pv.is_a] + return None + return [] + + @lru_cache(None) + def permissible_value_children( + self, permissible_value: str, enum_name: ENUM_NAME + ) -> list[str | PermissibleValueText] | None: + """:param enum_name: parent enum name + :param permissible_value: permissible value + :return: all direct child permissible values (is_a) + """ + enum = self.get_enum(enum_name, strict=True) + children = [] + if enum: + if permissible_value in enum.permissible_values: + pv = enum.permissible_values[permissible_value] + for isapv in enum.permissible_values: + isapv_entity = enum.permissible_values[isapv] + if isapv_entity.is_a and pv.text == isapv_entity.is_a: + children.append(isapv) + return children + return None + msg = f'No such enum as "{enum_name}"' + raise ValueError(msg) + + @lru_cache(None) + def permissible_value_ancestors( + self, permissible_value_text: str, enum_name: ENUM_NAME, reflexive: bool = True, depth_first: bool = True + ) -> list[str]: + """Return the closure of permissible_value_parents method. + + :param permissible_value_text: + :type permissible_value_text: str + :param enum_name: + :type enum_name: ENUM_NAME + :param reflexive: ..., defaults to True + :type reflexive: bool, optional + :param depth_first: ..., defaults to True + :type depth_first: bool, optional + :return: closure of permissible_value_parents + :rtype: list[str] + """ + return _closure( + lambda x: self.permissible_value_parent(x, enum_name), + permissible_value_text, + reflexive=reflexive, + depth_first=depth_first, + ) + + @lru_cache(None) + def permissible_value_descendants( + self, permissible_value_text: str, enum_name: ENUM_NAME, reflexive: bool = True, depth_first: bool = True + ) -> list[str]: + """Return the closure of permissible_value_children method. + + :enum + """ + return _closure( + lambda x: self.permissible_value_children(x, enum_name), + permissible_value_text, + reflexive=reflexive, + depth_first=depth_first, + ) @lru_cache(None) def is_multivalued(self, slot_name: SlotDefinition) -> bool: From 2989a5017ded5f998b3425903af9622aeef7e6bd Mon Sep 17 00:00:00 2001 From: ialarmedalien Date: Mon, 6 Oct 2025 13:16:31 -0700 Subject: [PATCH 2/2] Add kwargs input args to all the functions that use _closure(); edit the _closure function to remove some redundancy --- linkml_runtime/utils/schemaview.py | 83 ++++++++++++++++++++++-------- 1 file changed, 62 insertions(+), 21 deletions(-) diff --git a/linkml_runtime/utils/schemaview.py b/linkml_runtime/utils/schemaview.py index 9f3274f1..140c90c4 100644 --- a/linkml_runtime/utils/schemaview.py +++ b/linkml_runtime/utils/schemaview.py @@ -102,19 +102,13 @@ def _closure( rv = [x] if reflexive else [] visited = [] todo = [x] - while len(todo) > 0: - if depth_first: - i = todo.pop() - else: - i = todo[0] - todo = todo[1:] + while todo: + i = todo.pop() if depth_first else todo.pop(0) visited.append(i) - vals = f(i) - if vals is not None: - for v in vals: - if v not in visited and v not in rv: - todo.append(v) - rv.append(v) + for v in f(i) or []: + if v not in visited and v not in rv: + todo.append(v) + rv.append(v) return rv @@ -846,6 +840,7 @@ def class_ancestors( reflexive: bool = True, is_a: bool = True, depth_first: bool = True, + **kwargs: dict[str, Any] | None, ) -> list[ClassDefinitionName]: """Return the closure of class_parents method. @@ -862,6 +857,7 @@ def class_ancestors( class_name, reflexive=reflexive, depth_first=depth_first, + **kwargs, ) @lru_cache(None) @@ -872,6 +868,7 @@ def class_descendants( mixins: bool = True, reflexive: bool = True, is_a: bool = True, + **kwargs: dict[str, Any] | None, ) -> list[ClassDefinitionName]: """Return the closure of class_children method. @@ -883,7 +880,10 @@ def class_descendants( :return: descendants class names """ return _closure( - lambda x: self.class_children(x, imports=imports, mixins=mixins, is_a=is_a), class_name, reflexive=reflexive + lambda x: self.class_children(x, imports=imports, mixins=mixins, is_a=is_a), + class_name, + reflexive=reflexive, + **kwargs, ) @lru_cache(None) @@ -949,7 +949,13 @@ def slot_children( @lru_cache(None) def slot_ancestors( - self, slot_name: SLOT_NAME, imports: bool = True, mixins: bool = True, reflexive: bool = True, is_a: bool = True + self, + slot_name: SLOT_NAME, + imports: bool = True, + mixins: bool = True, + reflexive: bool = True, + is_a: bool = True, + **kwargs: dict[str, Any] | None, ) -> list[SlotDefinitionName]: """Return the closure of slot_parents method. @@ -961,12 +967,21 @@ def slot_ancestors( :return: ancestor slot names """ return _closure( - lambda x: self.slot_parents(x, imports=imports, mixins=mixins, is_a=is_a), slot_name, reflexive=reflexive + lambda x: self.slot_parents(x, imports=imports, mixins=mixins, is_a=is_a), + slot_name, + reflexive=reflexive, + **kwargs, ) @lru_cache(None) def slot_descendants( - self, slot_name: SLOT_NAME, imports: bool = True, mixins: bool = True, reflexive: bool = True, is_a: bool = True + self, + slot_name: SLOT_NAME, + imports: bool = True, + mixins: bool = True, + reflexive: bool = True, + is_a: bool = True, + **kwargs: dict[str, Any] | None, ) -> list[SlotDefinitionName]: """Return the closure of slot_children method. @@ -978,7 +993,10 @@ def slot_descendants( :return: descendants slot names """ return _closure( - lambda x: self.slot_children(x, imports=imports, mixins=mixins, is_a=is_a), slot_name, reflexive=reflexive + lambda x: self.slot_children(x, imports=imports, mixins=mixins, is_a=is_a), + slot_name, + reflexive=reflexive, + **kwargs, ) @lru_cache(None) @@ -1020,7 +1038,12 @@ def type_parents(self, type_name: TYPE_NAME, imports: bool = True) -> list[TypeD @lru_cache(None) def type_ancestors( - self, type_name: TYPE_NAME, imports: bool = True, reflexive: bool = True, depth_first: bool = True + self, + type_name: TYPE_NAME, + imports: bool = True, + reflexive: bool = True, + depth_first: bool = True, + **kwargs: dict[str, Any] | None, ) -> list[TypeDefinitionName]: """Return all ancestors of a type via typeof. @@ -1031,7 +1054,11 @@ def type_ancestors( :return: ancestor class names """ return _closure( - lambda x: self.type_parents(x, imports=imports), type_name, reflexive=reflexive, depth_first=depth_first + lambda x: self.type_parents(x, imports=imports), + type_name, + reflexive=reflexive, + depth_first=depth_first, + **kwargs, ) @lru_cache(None) @@ -1055,6 +1082,7 @@ def enum_ancestors( reflexive: bool = True, is_a: bool = True, depth_first: bool = True, + **kwargs: dict[str, Any] | None, ) -> list[EnumDefinitionName]: """Return the closure of enum_parents method. @@ -1071,6 +1099,7 @@ def enum_ancestors( enum_name, reflexive=reflexive, depth_first=depth_first, + **kwargs, ) @lru_cache(None) @@ -1114,7 +1143,12 @@ def permissible_value_children( @lru_cache(None) def permissible_value_ancestors( - self, permissible_value_text: str, enum_name: ENUM_NAME, reflexive: bool = True, depth_first: bool = True + self, + permissible_value_text: str, + enum_name: ENUM_NAME, + reflexive: bool = True, + depth_first: bool = True, + **kwargs: dict[str, Any] | None, ) -> list[str]: """Return the closure of permissible_value_parents method. @@ -1134,11 +1168,17 @@ def permissible_value_ancestors( permissible_value_text, reflexive=reflexive, depth_first=depth_first, + **kwargs, ) @lru_cache(None) def permissible_value_descendants( - self, permissible_value_text: str, enum_name: ENUM_NAME, reflexive: bool = True, depth_first: bool = True + self, + permissible_value_text: str, + enum_name: ENUM_NAME, + reflexive: bool = True, + depth_first: bool = True, + **kwargs: dict[str, Any] | None, ) -> list[str]: """Return the closure of permissible_value_children method. @@ -1149,6 +1189,7 @@ def permissible_value_descendants( permissible_value_text, reflexive=reflexive, depth_first=depth_first, + **kwargs, ) @lru_cache(None)