diff --git a/linkml_runtime/utils/schemaview.py b/linkml_runtime/utils/schemaview.py index 1e568877..ab1346dc 100644 --- a/linkml_runtime/utils/schemaview.py +++ b/linkml_runtime/utils/schemaview.py @@ -132,7 +132,7 @@ def load_import(self, imp: str, from_schema: SchemaDefinition = None): sname = self.importmap.get(str(imp), imp) # Import map may use CURIE sname = self.namespaces().uri_for(sname) if ':' in sname else sname sname = self.importmap.get(str(sname), sname) # It may also use URI or other forms - print(f'Loading schema {sname} from {from_schema.source_file}') + logging.info(f'Loading schema {sname} from {from_schema.source_file}') schema = load_schema_wrap(sname + '.yaml', base_dir=os.path.dirname(from_schema.source_file) if from_schema.source_file else None) return schema @@ -667,8 +667,10 @@ def induced_slot(self, slot_name: SLOT_NAME, class_name: CLASS_NAME = None, impo 'minimum_value': lambda x, y: max(x, y), } for metaslot_name in SlotDefinition._inherited_slots: + # inheritance of slots; priority order + # slot-level assignment < ancestor slot_usage < self slot_usage v = getattr(islot, metaslot_name, None) - for an in self.class_ancestors(class_name): + for an in reversed(self.class_ancestors(class_name)): a = self.get_class(an, imports) anc_slot_usage = a.slot_usage.get(slot_name, {}) v2 = getattr(anc_slot_usage, metaslot_name, None) @@ -679,7 +681,9 @@ def induced_slot(self, slot_name: SLOT_NAME, class_name: CLASS_NAME = None, impo if v2 is not None: v = COMBINE[metaslot_name](v, v2) else: - break + if v2 is not None: + v = v2 + logging.debug(f'{v} takes precedence over {v2} for {islot.name}.{metaslot_name}') if v is None: if metaslot_name == 'range': v = self.schema.default_range diff --git a/tests/test_utils/input/kitchen_sink.yaml b/tests/test_utils/input/kitchen_sink.yaml index 12bfd87e..eaa79e18 100644 --- a/tests/test_utils/input/kitchen_sink.yaml +++ b/tests/test_utils/input/kitchen_sink.yaml @@ -223,7 +223,9 @@ slots: - subset A - subset B related to: + range: Thing type: + range: string street: city: has birth event: diff --git a/tests/test_utils/input/kitchen_sink_noimports.yaml b/tests/test_utils/input/kitchen_sink_noimports.yaml index 299f3449..ee943818 100644 --- a/tests/test_utils/input/kitchen_sink_noimports.yaml +++ b/tests/test_utils/input/kitchen_sink_noimports.yaml @@ -250,7 +250,9 @@ slots: - subset A - subset B related to: + range: Thing type: + range: string street: city: has birth event: diff --git a/tests/test_utils/test_schemaview.py b/tests/test_utils/test_schemaview.py index 9a3a974a..6166e77e 100644 --- a/tests/test_utils/test_schemaview.py +++ b/tests/test_utils/test_schemaview.py @@ -36,6 +36,8 @@ def test_schemaview(self): #assert list(view.annotation_dict('employed at')[] if True: + for sn, s in view.all_slot().items(): + logging.info(f'SN = {sn} RANGE={s.range}') # this section is mostly for debugging for cn in all_cls.keys(): logging.debug(f'{cn} PARENTS = {view.class_parents(cn)}') @@ -86,9 +88,14 @@ def test_schemaview(self): #assert view.get_class('Company').class_uri == 'prov:Agent' assert view.get_uri('Company') == 'ks:Company' + # test induced slots + for c in ['Company', 'Person', 'Organization',]: assert view.induced_slot('aliases', c).multivalued is True + assert view.get_identifier_slot('Company').name == 'id' + assert view.get_identifier_slot('Thing').name == 'id' + assert view.get_identifier_slot('FamilialRelationship') is None for c in ['Company', 'Person', 'Organization', 'Thing']: assert view.induced_slot('id', c).identifier is True assert view.induced_slot('name', c).identifier is not True @@ -99,10 +106,14 @@ def test_schemaview(self): logging.debug(f's={s.range} // c = {c}') assert s.range == 'date' assert s.slot_uri == 'prov:startedAtTime' + # test slot_usage assert view.induced_slot('age in years', 'Person').minimum_value == 0 assert view.induced_slot('age in years', 'Adult').minimum_value == 16 + assert view.induced_slot('name', 'Person').pattern is not None assert view.induced_slot('type', 'FamilialRelationship').range == 'FamilialRelationshipType' assert view.induced_slot('related to', 'FamilialRelationship').range == 'Person' + assert view.get_slot('related to').range == 'Thing' + assert view.induced_slot('related to', 'Relationship').range == 'Thing' a = view.get_class('activity') self.assertCountEqual(a.exact_mappings, ['prov:Activity']) @@ -213,7 +224,7 @@ def test_imports(self): assert view.induced_slot('name', c).range == 'string' for c in ['Event', 'EmploymentEvent', 'MedicalEvent']: s = view.induced_slot('started at time', c) - logging.debug(f's={s.range} // c = {c}') + print(f's={s.range} // c = {c}') assert s.range == 'date' assert s.slot_uri == 'prov:startedAtTime' assert view.induced_slot('age in years', 'Person').minimum_value == 0