Skip to content

Commit 2271044

Browse files
authored
Merge pull request #335 from linkml/issue-2224
Modify `induced_class()` / `induced_slot()` so it materializes non-scalar metaslots as well
2 parents 81b3c62 + ba677c6 commit 2271044

File tree

3 files changed

+112
-2
lines changed

3 files changed

+112
-2
lines changed

linkml_runtime/utils/schemaview.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from linkml_runtime.utils.namespaces import Namespaces
1313
from deprecated.classic import deprecated
1414
from linkml_runtime.utils.context_utils import parse_import_map, map_import
15+
from linkml_runtime.utils.formatutils import is_empty
1516
from linkml_runtime.utils.pattern import PatternResolver
1617
from linkml_runtime.linkml_model.meta import *
1718
from linkml_runtime.exceptions import OrderingError
@@ -1369,7 +1370,14 @@ def induced_slot(self, slot_name: SLOT_NAME, class_name: CLASS_NAME = None, impo
13691370
if v2 is not None:
13701371
v = COMBINE[metaslot_name](v, v2)
13711372
else:
1372-
if v2 is not None:
1373+
# can rewrite below as:
1374+
# 1. if v2:
1375+
# 2. if v2 is not None and
1376+
# (
1377+
# (isinstance(v2, (dict, list)) and v2) or
1378+
# (isinstance(v2, JsonObj) and as_dict(v2))
1379+
# )
1380+
if not is_empty(v2):
13731381
v = v2
13741382
logging.debug(f'{v} takes precedence over {v2} for {induced_slot.name}.{metaslot_name}')
13751383
if v is None:
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
id: DJControllerSchema
2+
name: DJControllerSchema
3+
title: LinkML schema for my DJ controller
4+
imports:
5+
- linkml:types
6+
classes:
7+
DJController:
8+
slots:
9+
- jog_wheels
10+
- tempo
11+
- volume_faders
12+
- crossfaders
13+
slot_usage:
14+
tempo:
15+
examples:
16+
- value: 120.0
17+
- value: 144.0
18+
- value: 126.8
19+
- value: 102.6
20+
slots:
21+
jog_wheels:
22+
description: The number of jog wheels on the DJ controller
23+
range: integer
24+
examples:
25+
- value: 2
26+
annotations:
27+
expected_value: an integer between 0 and 4
28+
in_subset: decks
29+
tempo:
30+
description: The tempo of the track (in BPM)
31+
range: float
32+
examples:
33+
- value: 120.0
34+
- value: 144.0
35+
annotations:
36+
expected_value: a number between 0 and 200
37+
preferred_unit: BPM
38+
in_subset: decks
39+
volume_faders:
40+
description: The number of volume faders on the DJ controller
41+
range: integer
42+
examples:
43+
- value: 4
44+
annotations:
45+
expected_value: an integer between 0 and 8
46+
in_subset: mixer
47+
crossfaders:
48+
description: The number of crossfaders on the DJ controller
49+
range: integer
50+
examples:
51+
- value: 1
52+
annotations:
53+
expected_value: an integer between 0 and 2
54+
in_subset: mixer
55+
subsets:
56+
decks:
57+
description: A subset that represents the components in the deck portion of a DJ controller
58+
mixer:
59+
description: A subset that represents the components in the mixer portion of a DJ controller

tests/test_utils/test_schemaview.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
from typing import List
77
from unittest import TestCase
88

9+
from jsonasobj2 import JsonObj
10+
911
from linkml_runtime.dumpers import yaml_dumper
10-
from linkml_runtime.linkml_model.meta import SchemaDefinition, ClassDefinition, SlotDefinitionName, SlotDefinition, \
12+
from linkml_runtime.linkml_model.meta import Example, SchemaDefinition, ClassDefinition, SlotDefinitionName, SlotDefinition, \
1113
ClassDefinitionName, Prefix
1214
from linkml_runtime.loaders.yaml_loader import YAMLLoader
1315
from linkml_runtime.utils.introspection import package_schemaview
@@ -945,6 +947,47 @@ def test_is_inlined(self):
945947
actual_result = sv.is_inlined(slot)
946948
self.assertEqual(actual_result, expected_result)
947949

950+
def test_materialize_nonscalar_slot_usage(self):
951+
"""
952+
``slot_usage`` overrides values in the base slot definition without
953+
clobbering unrelated, nonscalar values.
954+
955+
See:
956+
- https://github.com/linkml/linkml/issues/2224
957+
- https://github.com/linkml/linkml-runtime/pull/335
958+
"""
959+
schema_path = os.path.join(INPUT_DIR, "DJ_controller_schema.yaml")
960+
sv = SchemaView(schema_path)
961+
cls = sv.induced_class("DJController")
962+
963+
# jog_wheels is a slot asserted at the schema level
964+
# check that the range (scalar value) is being materialized properly
965+
assert cls.attributes["jog_wheels"].range == "integer"
966+
# check that the examples (list) is being materialized properly
967+
assert isinstance(cls.attributes["jog_wheels"].examples, list)
968+
for example in cls.attributes["jog_wheels"].examples:
969+
assert example.value == "2"
970+
for example in cls.attributes["volume_faders"].examples:
971+
assert example.value == "4"
972+
for example in cls.attributes["crossfaders"].examples:
973+
assert example.value == "1"
974+
# check that the annotations (dictionary) is being materialized properly
975+
assert isinstance(cls.attributes["jog_wheels"].annotations, JsonObj)
976+
assert cls.attributes["jog_wheels"].annotations.expected_value.value == "an integer between 0 and 4"
977+
assert cls.attributes["volume_faders"].annotations.expected_value.value == "an integer between 0 and 8"
978+
979+
# examples being overridden by slot_usage modification
980+
assert cls.attributes["tempo"].examples == [Example(value='120.0'), Example(value='144.0'), Example(value='126.8'), Example(value='102.6')]
981+
# annotations remain the same / propagated as is from schema-level
982+
# definition of `tempo` slot
983+
assert cls.attributes["tempo"].annotations.expected_value.value == "a number between 0 and 200"
984+
assert cls.attributes["tempo"].annotations.preferred_unit.value == "BPM"
985+
986+
assert cls.attributes["tempo"].domain_of == ["DJController"]
987+
# ensure that domain_of is not being populated in slot_usage
988+
# test for https://github.com/linkml/linkml/pull/2262 from upstream linkml
989+
assert cls.slot_usage["tempo"].domain_of == []
990+
948991

949992
if __name__ == '__main__':
950993
unittest.main()

0 commit comments

Comments
 (0)