Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modify induced_class() / induced_slot() so it materializes non-scalar metaslots as well #335

Merged
merged 6 commits into from
Aug 8, 2024
10 changes: 9 additions & 1 deletion linkml_runtime/utils/schemaview.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
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.formatutils import is_empty
from linkml_runtime.utils.pattern import PatternResolver
from linkml_runtime.linkml_model.meta import *
from linkml_runtime.exceptions import OrderingError
Expand Down Expand Up @@ -1369,7 +1370,14 @@ 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:
if v2 is not None:
# can rewrite below as:
# 1. if v2:
# 2. if v2 is not None and
# (
# (isinstance(v2, (dict, list)) and v2) or
# (isinstance(v2, JsonObj) and as_dict(v2))
# )
if not is_empty(v2):
v = v2
logging.debug(f'{v} takes precedence over {v2} for {induced_slot.name}.{metaslot_name}')
if v is None:
Expand Down
59 changes: 59 additions & 0 deletions tests/test_utils/input/DJ_controller_schema.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
id: DJControllerSchema
name: DJControllerSchema
title: LinkML schema for my DJ controller
imports:
- linkml:types
classes:
DJController:
slots:
- jog_wheels
- tempo
- volume_faders
- crossfaders
slot_usage:
tempo:
examples:
- value: 120.0
- value: 144.0
- value: 126.8
- value: 102.6
slots:
jog_wheels:
description: The number of jog wheels on the DJ controller
range: integer
examples:
- value: 2
annotations:
expected_value: an integer between 0 and 4
in_subset: decks
tempo:
description: The tempo of the track (in BPM)
range: float
examples:
- value: 120.0
- value: 144.0
annotations:
expected_value: a number between 0 and 200
preferred_unit: BPM
in_subset: decks
volume_faders:
description: The number of volume faders on the DJ controller
range: integer
examples:
- value: 4
annotations:
expected_value: an integer between 0 and 8
in_subset: mixer
crossfaders:
description: The number of crossfaders on the DJ controller
range: integer
examples:
- value: 1
annotations:
expected_value: an integer between 0 and 2
in_subset: mixer
subsets:
decks:
description: A subset that represents the components in the deck portion of a DJ controller
mixer:
description: A subset that represents the components in the mixer portion of a DJ controller
32 changes: 31 additions & 1 deletion tests/test_utils/test_schemaview.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
from typing import List
from unittest import TestCase

from jsonasobj2 import JsonObj

from linkml_runtime.dumpers import yaml_dumper
from linkml_runtime.linkml_model.meta import SchemaDefinition, ClassDefinition, SlotDefinitionName, SlotDefinition, \
from linkml_runtime.linkml_model.meta import Example, SchemaDefinition, ClassDefinition, SlotDefinitionName, SlotDefinition, \
ClassDefinitionName, Prefix
from linkml_runtime.loaders.yaml_loader import YAMLLoader
from linkml_runtime.utils.introspection import package_schemaview
Expand Down Expand Up @@ -945,6 +947,34 @@ def test_is_inlined(self):
actual_result = sv.is_inlined(slot)
self.assertEqual(actual_result, expected_result)

def test_materialize_nonscalar_slot_usage(self):
sujaypatil96 marked this conversation as resolved.
Show resolved Hide resolved
schema_path = os.path.join(INPUT_DIR, "DJ_controller_schema.yaml")
sv = SchemaView(schema_path)
cls = sv.induced_class("DJController")

# jog_wheels is a slot asserted at the schema level
# check that the range (scalar value) is being materialized properly
assert cls.attributes["jog_wheels"].range == "integer"
# check that the examples (list) is being materialized properly
assert isinstance(cls.attributes["jog_wheels"].examples, list)
for example in cls.attributes["jog_wheels"].examples:
assert example.value == "2"
for example in cls.attributes["volume_faders"].examples:
assert example.value == "4"
for example in cls.attributes["crossfaders"].examples:
assert example.value == "1"
# check that the annotations (dictionary) is being materialized properly
assert isinstance(cls.attributes["jog_wheels"].annotations, JsonObj)
assert cls.attributes["jog_wheels"].annotations.expected_value.value == "an integer between 0 and 4"
assert cls.attributes["volume_faders"].annotations.expected_value.value == "an integer between 0 and 8"

# examples being overridden by slot_usage modification
assert cls.attributes["tempo"].examples == [Example(value='120.0'), Example(value='144.0'), Example(value='126.8'), Example(value='102.6')]
# annotations remain the same / propagated as is from schema-level
# definition of `tempo` slot
assert cls.attributes["tempo"].annotations.expected_value.value == "a number between 0 and 200"
assert cls.attributes["tempo"].annotations.preferred_unit.value == "BPM"


if __name__ == '__main__':
unittest.main()
Loading