2626from linkml_runtime .loaders .yaml_loader import YAMLLoader
2727from linkml_runtime .utils .introspection import package_schemaview
2828from 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+ )
3040from tests .test_utils import INPUT_DIR
3141
42+ INPUT_DIR_PATH = Path (INPUT_DIR )
43+
3244logger = 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
4151CREATURE_SCHEMA = "creature_schema"
4252CREATURE_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
4559yaml_loader = YAMLLoader ()
4660IS_CURRENT = "is current"
5165RELATED_TO = "related to"
5266AGE_IN_YEARS = "age in years"
5367
54- ELEMENTS = ["prefixes" , "classes" , "slots" , "enums" , "types" , "subsets" ]
68+ ALL_ELEMENTS = [PREFIXES , * ELEMENTS ]
5569EXPECTED = {
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"
6579PLURAL = {
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" )
83103def 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" )
89121def 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" )
94126def 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" )
100132def 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
105143def 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 ())
778816def 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
929968def 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
14931542def 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:
15391587def 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]:
15691617def 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)
15851633def 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