diff --git a/linkml_runtime/utils/schemaview.py b/linkml_runtime/utils/schemaview.py index 78f94240..0471b5f8 100644 --- a/linkml_runtime/utils/schemaview.py +++ b/linkml_runtime/utils/schemaview.py @@ -618,6 +618,40 @@ def permissible_value_parent(self, permissible_value: str, enum_name: ENUM_NAME) else: return [] + @lru_cache() + def permissible_value_children(self, permissible_value: str, enum_name: ENUM_NAME) -> Union[ + str, PermissibleValueText, None, ValueError]: + """ + :param enum_name: parent enum name + :param permissible_value: permissible value + :return: all direct child permissible values (is_a) + + CAT: + LION: + is_a: CAT + ANGRY_LION: + is_a: LION + TABBY: + is_a: CAT + BIRD: + EAGLE: + is_a: BIRD + + """ + + 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 + else: + raise ValueError(f'No such enum as "{enum_name}"') + @lru_cache() def slot_parents(self, slot_name: SLOT_NAME, imports=True, mixins=True, is_a=True) -> List[SlotDefinitionName]: """ @@ -719,6 +753,22 @@ def permissible_value_ancestors(self, permissible_value_text: str, reflexive=reflexive, depth_first=depth_first) + @lru_cache() + def permissible_value_descendants(self, permissible_value_text: str, + enum_name: ENUM_NAME, + reflexive=True, + depth_first=True) -> List[str]: + """ + 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() def enum_ancestors(self, enum_name: ENUM_NAME, imports=True, mixins=True, reflexive=True, is_a=True, depth_first=True) -> List[EnumDefinitionName]: @@ -931,7 +981,6 @@ def get_uri(self, element: Union[ElementName, Element], imports=True, expand=Fal if schema == None: raise ValueError(f'Cannot find {e.from_schema} in schema_map') else: - logging.warning(f'from_schema not populated for element {e.name}') schema = self.schema_map[self.in_schema(e.name)] pfx = schema.default_prefix uri = f'{pfx}:{e_name}' diff --git a/tests/test_utils/input/kitchen_sink_noimports.yaml b/tests/test_utils/input/kitchen_sink_noimports.yaml index 9173ec43..6b3e7ec6 100644 --- a/tests/test_utils/input/kitchen_sink_noimports.yaml +++ b/tests/test_utils/input/kitchen_sink_noimports.yaml @@ -364,6 +364,8 @@ enums: is_a: OtherEnum permissible_values: CAT: + TABBY: + is_a: CAT LION: is_a: CAT ANGRY_LION: diff --git a/tests/test_utils/test_schemaview.py b/tests/test_utils/test_schemaview.py index aca871ff..576a2f15 100644 --- a/tests/test_utils/test_schemaview.py +++ b/tests/test_utils/test_schemaview.py @@ -47,15 +47,26 @@ def test_all_aliases(self): def test_schemaview_enums(self): view = SchemaView(SCHEMA_NO_IMPORTS) + with self.assertRaises(ValueError): + view.permissible_value_parent("not_a_pv", "not_an_enum") for en, e in view.all_enums().items(): if e.name == "Animals": for pv, v in e.permissible_values.items(): if pv == "CAT": self.assertEqual(view.permissible_value_parent(pv, e.name), None) self.assertEqual(view.permissible_value_ancestors(pv, e.name), ['CAT']) + self.assertIn("LION", view.permissible_value_descendants(pv, e.name)) + self.assertIn("ANGRY_LION", view.permissible_value_descendants(pv, e.name)) + self.assertIn("TABBY", view.permissible_value_descendants(pv, e.name)) + self.assertIn("TABBY", view.permissible_value_children(pv, e.name)) + self.assertIn("LION", view.permissible_value_children(pv, e.name)) + self.assertNotIn("EAGLE", view.permissible_value_descendants(pv, e.name)) + if pv == "LION": + self.assertIn("ANGRY_LION", view.permissible_value_children(pv, e.name)) if pv == "ANGRY_LION": self.assertEqual(view.permissible_value_parent(pv, e.name), ['LION']) self.assertEqual(view.permissible_value_ancestors(pv, e.name), ['ANGRY_LION', 'LION', 'CAT']) + self.assertEquals(["ANGRY_LION"], view.permissible_value_descendants(pv, e.name)) for cn, c in view.all_classes().items(): if c.name == "Adult": self.assertEqual(view.class_ancestors(c.name), ['Adult', 'Person', 'HasAliases', 'Thing'])