Skip to content
This repository was archived by the owner on Apr 16, 2026. It is now read-only.

Commit 71d4004

Browse files
Merge pull request #453 from linkml/minor_tidying
test_schemaview.py: minor tidying, fixture creation
2 parents f389148 + ff8c543 commit 71d4004

1 file changed

Lines changed: 110 additions & 62 deletions

File tree

‎tests/test_utils/test_schemaview.py‎

Lines changed: 110 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,35 @@
2626
from linkml_runtime.loaders.yaml_loader import YAMLLoader
2727
from linkml_runtime.utils.introspection import package_schemaview
2828
from linkml_runtime.utils.schemaops import roll_down, roll_up
29-
from linkml_runtime.utils.schemaview import SchemaUsage, SchemaView
29+
from linkml_runtime.utils.schemaview import (
30+
CLASSES,
31+
ELEMENTS,
32+
ENUMS,
33+
PREFIXES,
34+
SLOTS,
35+
SUBSETS,
36+
TYPES,
37+
SchemaUsage,
38+
SchemaView,
39+
)
3040
from tests.test_utils import INPUT_DIR
3141

42+
INPUT_DIR_PATH = Path(INPUT_DIR)
43+
3244
logger = logging.getLogger(__name__)
3345

34-
SCHEMA_NO_IMPORTS = Path(INPUT_DIR) / "kitchen_sink_noimports.yaml"
35-
SCHEMA_WITH_IMPORTS = Path(INPUT_DIR) / "kitchen_sink.yaml"
36-
SCHEMA_WITH_STRUCTURED_PATTERNS = Path(INPUT_DIR) / "pattern-example.yaml"
37-
SCHEMA_IMPORT_TREE = Path(INPUT_DIR) / "imports" / "main.yaml"
38-
SCHEMA_RELATIVE_IMPORT_TREE = Path(INPUT_DIR) / "imports_relative" / "L0_0" / "L1_0_0" / "main.yaml"
39-
SCHEMA_RELATIVE_IMPORT_TREE2 = Path(INPUT_DIR) / "imports_relative" / "L0_2" / "main.yaml"
46+
SCHEMA_NO_IMPORTS = INPUT_DIR_PATH / "kitchen_sink_noimports.yaml"
47+
SCHEMA_WITH_IMPORTS = INPUT_DIR_PATH / "kitchen_sink.yaml"
48+
SCHEMA_RELATIVE_IMPORT_TREE = INPUT_DIR_PATH / "imports_relative" / "L0_0" / "L1_0_0" / "main.yaml"
49+
SCHEMA_RELATIVE_IMPORT_TREE2 = INPUT_DIR_PATH / "imports_relative" / "L0_2" / "main.yaml"
4050

4151
CREATURE_SCHEMA = "creature_schema"
4252
CREATURE_SCHEMA_BASE_URL = "https://github.com/linkml/linkml-runtime/tests/test_utils/input/mcc"
43-
CREATURE_SCHEMA_BASE_PATH = Path(INPUT_DIR) / "mcc"
53+
CREATURE_SCHEMA_RAW_URL = (
54+
"https://github.com/linkml/linkml-runtime/raw/main/tests/test_utils/input/mcc/creature_schema.yaml"
55+
)
56+
57+
CREATURE_SCHEMA_BASE_PATH = INPUT_DIR_PATH / "mcc"
4458

4559
yaml_loader = YAMLLoader()
4660
IS_CURRENT = "is current"
@@ -51,19 +65,19 @@
5165
RELATED_TO = "related to"
5266
AGE_IN_YEARS = "age in years"
5367

54-
ELEMENTS = ["prefixes", "classes", "slots", "enums", "types", "subsets"]
68+
ALL_ELEMENTS = [PREFIXES, *ELEMENTS]
5569
EXPECTED = {
56-
"prefixes": {"sc1p1", "sc2p1"},
57-
"classes": {"sc1c1", "sc1c2", "sc2c1", "sc2c2"},
58-
"slots": {"sc1s1", "sc1s2", "sc2s1", "sc2s2"},
59-
"enums": {"sc1e1", "sc2e1"},
60-
"types": {"sc1t1", "sc2t1"},
61-
"subsets": {"sc1ss1", "sc2ss1"},
70+
PREFIXES: {"sc1p1", "sc2p1"},
71+
CLASSES: {"sc1c1", "sc1c2", "sc2c1", "sc2c2"},
72+
SLOTS: {"sc1s1", "sc1s2", "sc2s1", "sc2s2"},
73+
ENUMS: {"sc1e1", "sc2e1"},
74+
TYPES: {"sc1t1", "sc2t1"},
75+
SUBSETS: {"sc1ss1", "sc2ss1"},
6276
}
6377

6478
# workaround for the annoying plural version of "class"
6579
PLURAL = {
66-
"class": "classes",
80+
"class": CLASSES,
6781
}
6882

6983

@@ -79,27 +93,51 @@ def schema_view_with_imports() -> SchemaView:
7993
return SchemaView(SCHEMA_WITH_IMPORTS)
8094

8195

96+
@pytest.fixture(scope="session")
97+
def sv_import_tree() -> SchemaView:
98+
"""Fixture for a SchemaView for testing imports and ordering."""
99+
return SchemaView(INPUT_DIR_PATH / "imports" / "main.yaml")
100+
101+
82102
@pytest.fixture(scope="session")
83103
def sv_attributes() -> SchemaView:
84104
"""Fixture for a SchemaView for testing attribute edge cases."""
85-
return SchemaView(os.path.join(INPUT_DIR, "attribute_edge_cases.yaml"))
105+
return SchemaView(INPUT_DIR_PATH / "attribute_edge_cases.yaml")
106+
107+
108+
@pytest.fixture(scope="session")
109+
def sv_inlined() -> SchemaView:
110+
"""Fixture for a SchemaView for testing inlined slots."""
111+
return SchemaView(INPUT_DIR_PATH / "schemaview_is_inlined.yaml")
112+
113+
114+
@pytest.fixture(scope="session")
115+
def sv_structured_patterns() -> SchemaView:
116+
"""Fixture for a SchemaView for testing structured patterns."""
117+
return SchemaView(INPUT_DIR_PATH / "pattern-example.yaml")
86118

87119

88120
@pytest.fixture(scope="session")
89121
def creature_view() -> SchemaView:
90-
return SchemaView(str(CREATURE_SCHEMA_BASE_PATH / "creature_schema.yaml"))
122+
return SchemaView(CREATURE_SCHEMA_BASE_PATH / "creature_schema.yaml")
91123

92124

93125
@pytest.fixture(scope="session")
94126
def creature_view_remote() -> SchemaView:
95127
"""Fixture for a SchemaView for testing remote imports."""
96-
return SchemaView(str(CREATURE_SCHEMA_BASE_PATH / "creature_schema_remote.yaml"))
128+
return SchemaView(CREATURE_SCHEMA_BASE_PATH / "creature_schema_remote.yaml")
97129

98130

99131
@pytest.fixture(scope="session")
100132
def creature_view_local() -> SchemaView:
101133
"""Fixture for a SchemaView for testing local relative file path imports."""
102-
return SchemaView(str(CREATURE_SCHEMA_BASE_PATH / "creature_schema_local.yaml"))
134+
return SchemaView(CREATURE_SCHEMA_BASE_PATH / "creature_schema_local.yaml")
135+
136+
137+
@pytest.fixture(scope="session")
138+
def creature_view_direct_url() -> SchemaView:
139+
"""Fixture for a SchemaView called directly by using the URL."""
140+
return SchemaView(CREATURE_SCHEMA_RAW_URL)
103141

104142

105143
def make_schema(
@@ -364,7 +402,7 @@ def test_imports(schema_view_with_imports: SchemaView) -> None:
364402
logger.debug(view.imports_closure())
365403
assert set(view.imports_closure()) == {"kitchen_sink", "core", "linkml:types"}
366404

367-
for t in view.all_types().keys():
405+
for t in view.all_types():
368406
logger.debug(f"T={t} in={view.in_schema(t)}")
369407
assert view.in_schema(ClassDefinitionName("Person")) == "kitchen_sink"
370408
assert view.in_schema(SlotDefinitionName("id")) == "core"
@@ -465,10 +503,9 @@ def test_imports_from_schemaview(schema_view_with_imports: SchemaView) -> None:
465503
assert len(view.all_classes(imports=False)) == len(view2.all_classes(imports=False))
466504

467505

468-
def test_imports_closure_order() -> None:
506+
def test_imports_closure_order(sv_import_tree: SchemaView) -> None:
469507
"""Imports should override in a python-like order."""
470-
sv = SchemaView(SCHEMA_IMPORT_TREE)
471-
closure = sv.imports_closure(imports=True)
508+
closure = sv_import_tree.imports_closure(imports=True)
472509
target = [
473510
"linkml:types",
474511
"s1_1",
@@ -489,12 +526,11 @@ def test_imports_closure_order() -> None:
489526
assert closure == target
490527

491528

492-
def test_imports_overrides() -> None:
529+
def test_imports_overrides(sv_import_tree: SchemaView) -> None:
493530
"""Classes defined in the importing module should override same-named classes in imported modules."""
494-
sv = SchemaView(SCHEMA_IMPORT_TREE)
495531
defaults = {}
496532
target = {}
497-
for name, cls in sv.all_classes(imports=True).items():
533+
for name, cls in sv_import_tree.all_classes(imports=True).items():
498534
target[name] = name
499535
defaults[name] = cls.attributes["value"].ifabsent
500536

@@ -773,7 +809,9 @@ def test_uris_without_default_prefix() -> None:
773809
CREATURE_EXPECTED["element"][s_name].update(el)
774810

775811

776-
@pytest.mark.parametrize("schema", ["creature_view", "creature_view_remote", "creature_view_local"])
812+
@pytest.mark.parametrize(
813+
"schema", ["creature_view", "creature_view_remote", "creature_view_local", "creature_view_direct_url"]
814+
)
777815
@pytest.mark.parametrize("entity", CREATURE_EXPECTED.keys())
778816
def test_creature_schema_entities_with_without_imports(
779817
schema: str, entity: str, request: pytest.FixtureRequest
@@ -789,12 +827,13 @@ def test_creature_schema_entities_with_without_imports(
789827
- creature_view: the main schema with all entities
790828
- creature_view_remote: imports the creature schema using a curie and a remote URL
791829
- creature_view_local: imports the creature schema using a local relative file path
830+
- creature_view_direct_url: imports the creature schema using a direct URL
792831
"""
793832
creature_view = request.getfixturevalue(schema)
794833

795834
# use the PLURAL mapping to get the correct method name to retrieve all entities of the given type
796835
get_all_fn = "all_" + PLURAL.get(entity, f"{entity}s")
797-
if schema == "creature_view":
836+
if schema in ("creature_view", "creature_view_direct_url"):
798837
assert set(getattr(creature_view, get_all_fn)(imports=False)) == CREATURE_EXPECTED[entity][CREATURE_SCHEMA]
799838
else:
800839
assert set(getattr(creature_view, get_all_fn)(imports=False)) == set()
@@ -927,7 +966,20 @@ def test_alias_slot(schema_view_no_imports: SchemaView) -> None:
927966

928967

929968
def test_schemaview_enums(schema_view_no_imports: SchemaView) -> None:
930-
"""Test various aspects of Enum representation."""
969+
"""Test various aspects of Enum representation.
970+
971+
CAT:
972+
LION:
973+
is_a: CAT
974+
ANGRY_LION:
975+
is_a: LION
976+
TABBY:
977+
is_a: CAT
978+
BIRD:
979+
EAGLE:
980+
is_a: BIRD
981+
"""
982+
931983
view = schema_view_no_imports
932984

933985
# Test for ValueError when passing incorrect parameters
@@ -1191,7 +1243,7 @@ def test_get_classes_modifying_slot() -> None:
11911243
11921244
https://github.com/linkml/linkml/issues/1126
11931245
"""
1194-
classes_mod_slot_file = Path(INPUT_DIR) / "issue_1126.yaml"
1246+
classes_mod_slot_file = INPUT_DIR_PATH / "issue_1126.yaml"
11951247
view = SchemaView(str(classes_mod_slot_file))
11961248
slot_definition = view.get_slot("type")
11971249
slot_classes = view.get_classes_modifying_slot(slot_definition)
@@ -1440,33 +1492,30 @@ def test_get_classes_by_slot(schema_view_with_imports: SchemaView) -> None:
14401492
assert sorted(actual_result) == sorted(expected_result)
14411493

14421494

1443-
def test_materialize_patterns() -> None:
1495+
def test_materialize_patterns(sv_structured_patterns: SchemaView) -> None:
14441496
"""Test pattern materialization."""
1445-
sv = SchemaView(SCHEMA_WITH_STRUCTURED_PATTERNS)
1446-
sv.materialize_patterns()
1497+
sv_structured_patterns.materialize_patterns()
14471498

1448-
height_slot = sv.get_slot("height")
1449-
weight_slot = sv.get_slot("weight")
1499+
height_slot = sv_structured_patterns.get_slot("height")
1500+
weight_slot = sv_structured_patterns.get_slot("weight")
14501501

14511502
assert height_slot.pattern == r"\d+[\.\d+] (centimeter|meter|inch)"
14521503
assert weight_slot.pattern == r"\d+[\.\d+] (kg|g|lbs|stone)"
14531504

14541505

1455-
def test_materialize_patterns_slot_usage() -> None:
1506+
def test_materialize_patterns_slot_usage(sv_structured_patterns: SchemaView) -> None:
14561507
"""Test pattern materialization with slot_usage."""
1457-
sv = SchemaView(SCHEMA_WITH_STRUCTURED_PATTERNS)
1458-
sv.materialize_patterns()
1508+
sv_structured_patterns.materialize_patterns()
14591509

1460-
name_slot_usage = sv.get_class("FancyPersonInfo").slot_usage["name"]
1510+
name_slot_usage = sv_structured_patterns.get_class("FancyPersonInfo").slot_usage["name"]
14611511
assert name_slot_usage.pattern == r"\S+ \S+-\S+"
14621512

14631513

1464-
def test_materialize_patterns_attribute() -> None:
1514+
def test_materialize_patterns_attribute(sv_structured_patterns: SchemaView) -> None:
14651515
"""Test pattern materialization with attributes."""
1466-
sv = SchemaView(SCHEMA_WITH_STRUCTURED_PATTERNS)
1467-
sv.materialize_patterns()
1516+
sv_structured_patterns.materialize_patterns()
14681517

1469-
weight_attribute = sv.get_class("ClassWithAttributes").attributes["weight"]
1518+
weight_attribute = sv_structured_patterns.get_class("ClassWithAttributes").attributes["weight"]
14701519
assert weight_attribute.pattern == r"\d+[\.\d+] (kg|g|lbs|stone)"
14711520

14721521

@@ -1484,15 +1533,14 @@ def test_materialize_patterns_attribute() -> None:
14841533
("inlined_as_list_integer", False),
14851534
],
14861535
)
1487-
def test_is_inlined(schema_view_inlined: SchemaView, slot_name: str, expected_result: bool) -> None:
1536+
def test_is_inlined(sv_inlined: SchemaView, slot_name: str, expected_result: bool) -> None:
14881537
"""Tests for slots being inlined or not."""
1489-
slot = schema_view_inlined.get_slot(slot_name)
1490-
assert schema_view_inlined.is_inlined(slot) == expected_result
1538+
slot = sv_inlined.get_slot(slot_name)
1539+
assert sv_inlined.is_inlined(slot) == expected_result
14911540

14921541

14931542
def test_materialize_nonscalar_slot_usage() -> None:
1494-
schema_path = os.path.join(INPUT_DIR, "DJ_controller_schema.yaml")
1495-
sv = SchemaView(schema_path)
1543+
sv = SchemaView(str(INPUT_DIR_PATH / "DJ_controller_schema.yaml"))
14961544
cls = sv.induced_class("DJController")
14971545

14981546
assert cls.attributes["jog_wheels"].range == "integer"
@@ -1539,7 +1587,7 @@ def test_type_and_slot_with_same_name() -> None:
15391587
def test_merge_schema_merge_into_empty(sv_merge_1: SchemaView, sv_empty: SchemaView) -> None:
15401588
"""Trivial case: merge a schema into an empty schema."""
15411589
sv_empty.merge_schema(sv_merge_1.schema)
1542-
for k in ELEMENTS:
1590+
for k in ALL_ELEMENTS:
15431591
assert getattr(sv_empty.schema, k) == getattr(sv_merge_1.schema, k)
15441592

15451593

@@ -1548,7 +1596,7 @@ def test_merge_schema_merge_empty(sv_merge_1: SchemaView, sv_empty: SchemaView)
15481596
sv_merge_1_orig = deepcopy(sv_merge_1)
15491597
sv_merge_1.merge_schema(sv_empty.schema)
15501598

1551-
for k in ELEMENTS:
1599+
for k in ALL_ELEMENTS:
15521600
assert getattr(sv_merge_1_orig.schema, k) == getattr(sv_merge_1.schema, k)
15531601

15541602

@@ -1569,13 +1617,13 @@ def _get_clobbered_field_val(element: str) -> tuple[str, str]:
15691617
def test_merge_schema_no_clobber(sv_merge_1: SchemaView, sv_merge_2: SchemaView) -> None:
15701618
"""Merge non-disjoint schemas, ensuring that elements in the source schema are not clobbered."""
15711619
sv_merge_2.merge_schema(sv_merge_1.schema)
1572-
for element in ELEMENTS:
1620+
for element in ALL_ELEMENTS:
15731621
(field, val) = _get_clobbered_field_val(element)
15741622
for v in getattr(sv_merge_1.schema, element).values():
15751623
setattr(v, field, val)
15761624

15771625
sv_merge_2.merge_schema(sv_merge_1.schema, clobber=False)
1578-
for element in ELEMENTS:
1626+
for element in ALL_ELEMENTS:
15791627
(field, val) = _get_clobbered_field_val(element)
15801628
for k, v in getattr(sv_merge_2.schema, element).items():
15811629
if k in getattr(sv_merge_1.schema, element):
@@ -1585,44 +1633,44 @@ def test_merge_schema_no_clobber(sv_merge_1: SchemaView, sv_merge_2: SchemaView)
15851633
def test_merge_schema_clobber(sv_merge_1: SchemaView, sv_merge_2: SchemaView) -> None:
15861634
"""Merge non-disjoint schemas, ensuring that elements in source schema are clobbered."""
15871635
sv_merge_2.merge_schema(sv_merge_1.schema)
1588-
for element in ELEMENTS:
1636+
for element in ALL_ELEMENTS:
15891637
(field, val) = _get_clobbered_field_val(element)
15901638
for v in getattr(sv_merge_1.schema, element).values():
15911639
setattr(v, field, val)
15921640

15931641
sv_merge_2.merge_schema(sv_merge_1.schema, clobber=True)
1594-
for element in ELEMENTS:
1642+
for element in ALL_ELEMENTS:
15951643
(field, val) = _get_clobbered_field_val(element)
15961644
for k, v in getattr(sv_merge_2.schema, element).items():
15971645
if k in getattr(sv_merge_1.schema, element):
15981646
assert getattr(v, field) == val
15991647

16001648

1601-
def test_get_slots_by_enum_schema_slot(sv_issue_998) -> None:
1649+
def test_get_slots_by_enum_schema_slot(sv_issue_998: SchemaView) -> None:
16021650
enum_slots = sv_issue_998.get_slots_by_enum("EmploymentStatusEnum")
16031651
assert len(enum_slots) == 2
16041652

16051653

1606-
def test_get_slots_by_enum_no_duplicates(sv_issue_998) -> None:
1654+
def test_get_slots_by_enum_no_duplicates(sv_issue_998: SchemaView) -> None:
16071655
enum_slots = sv_issue_998.get_slots_by_enum("PersonStatusEnum")
16081656
assert len(enum_slots) == 1
16091657
assert enum_slots[0].name == "status"
16101658

16111659

1612-
def test_get_slots_by_enum_attribute_slot(sv_issue_998) -> None:
1660+
def test_get_slots_by_enum_attribute_slot(sv_issue_998: SchemaView) -> None:
16131661
enum_slots = sv_issue_998.get_slots_by_enum("EmploymentStatusEnum")
16141662
assert len(enum_slots) == 2
16151663
assert sorted([slot.name for slot in enum_slots]) == ["employed", "past_employer"]
16161664

16171665

1618-
def test_get_slots_by_enum_schema_and_attribute_slots(sv_issue_998) -> None:
1666+
def test_get_slots_by_enum_schema_and_attribute_slots(sv_issue_998: SchemaView) -> None:
16191667
enum_slots = sv_issue_998.get_slots_by_enum("RelationshipStatusEnum")
16201668
assert len(enum_slots) == 2
16211669
assert enum_slots[0].name == "relationship"
16221670
assert enum_slots[1].name == "past_relationship"
16231671

16241672

1625-
def test_get_slots_by_enum_slot_usage_range(sv_issue_998) -> None:
1673+
def test_get_slots_by_enum_slot_usage_range(sv_issue_998: SchemaView) -> None:
16261674
enum_slots = sv_issue_998.get_slots_by_enum("TypeEnum")
16271675
assert len(enum_slots) == 1
16281676
assert enum_slots[0].name == "type"
@@ -1633,7 +1681,7 @@ def test_get_slot_gets_attr_not_slot() -> None:
16331681
16341682
See https://github.com/linkml/linkml/issues/998.
16351683
"""
1636-
schema_file = Path(INPUT_DIR) / "issue_590.yaml"
1684+
schema_file = INPUT_DIR_PATH / "issue_590.yaml"
16371685

16381686
sv = SchemaView(str(schema_file))
16391687
# check that multivalued is set to False as in schema
@@ -1645,7 +1693,7 @@ def test_class_name_mappings() -> None:
16451693
16461694
See https://github.com/linkml/linkml/issues/478
16471695
"""
1648-
schema_file = Path(INPUT_DIR) / "issue_478.yaml"
1696+
schema_file = INPUT_DIR_PATH / "issue_478.yaml"
16491697
view = SchemaView(str(schema_file))
16501698

16511699
# class names => normalised class names:

0 commit comments

Comments
 (0)