Skip to content

Commit 8bec52a

Browse files
committed
fix tests, convert schemaview to pytest
1 parent d858bf6 commit 8bec52a

File tree

2 files changed

+868
-1054
lines changed

2 files changed

+868
-1054
lines changed

linkml_runtime/utils/schemaview.py

Lines changed: 19 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from pathlib import Path
99
from typing import Mapping, Optional, Tuple, TypeVar
1010
import warnings
11+
from pprint import pprint
1112

1213
from linkml_runtime.utils.namespaces import Namespaces
1314
from deprecated.classic import deprecated
@@ -17,6 +18,8 @@
1718
from linkml_runtime.linkml_model.meta import *
1819
from linkml_runtime.exceptions import OrderingError
1920
from enum import Enum
21+
from linkml_runtime.linkml_model.meta import ClassDefinition, SlotDefinition, ClassDefinitionName
22+
from dataclasses import asdict, is_dataclass, fields
2023

2124
logger = logging.getLogger(__name__)
2225

@@ -36,9 +39,9 @@
3639
ENUM_NAME = Union[EnumDefinitionName, str]
3740

3841
ElementType = TypeVar("ElementType", bound=Element)
39-
ElementNameType = TypeVar("ElementNameType", bound=Union[ElementName,str])
42+
ElementNameType = TypeVar("ElementNameType", bound=Union[ElementName, str])
4043
DefinitionType = TypeVar("DefinitionType", bound=Definition)
41-
DefinitionNameType = TypeVar("DefinitionNameType", bound=Union[DefinitionName,str])
44+
DefinitionNameType = TypeVar("DefinitionNameType", bound=Union[DefinitionName, str])
4245
ElementDict = Dict[ElementNameType, ElementType]
4346
DefDict = Dict[DefinitionNameType, DefinitionType]
4447

@@ -53,7 +56,6 @@ class OrderedBy(Enum):
5356
"""
5457

5558

56-
5759
def _closure(f, x, reflexive=True, depth_first=True, **kwargs):
5860
if reflexive:
5961
rv = [x]
@@ -84,7 +86,7 @@ def load_schema_wrap(path: str, **kwargs):
8486
schema: SchemaDefinition
8587
schema = yaml_loader.load(path, target_class=SchemaDefinition, **kwargs)
8688
if "\n" not in path:
87-
# if "\n" not in path and "://" not in path:
89+
# if "\n" not in path and "://" not in path:
8890
# only set path if the input is not a yaml string or URL.
8991
# Setting the source path is necessary for relative imports;
9092
# while initializing a schema with a yaml string is possible, there
@@ -146,6 +148,7 @@ def get_anonymous_class_definition(class_as_dict: ClassDefinition) -> AnonymousC
146148
:return: An AnonymousClassExpression.
147149
"""
148150
an_expr = AnonymousClassExpression()
151+
print(class_as_dict)
149152
valid_fields = {field.name for field in fields(an_expr)}
150153
for k, v in class_as_dict.items():
151154
if k in valid_fields:
@@ -154,6 +157,7 @@ def get_anonymous_class_definition(class_as_dict: ClassDefinition) -> AnonymousC
154157
setattr(an_expr, k, v)
155158
return an_expr
156159

160+
157161
@dataclass
158162
class SchemaView(object):
159163
"""
@@ -265,7 +269,8 @@ def load_import(self, imp: str, from_schema: SchemaDefinition = None):
265269
return schema
266270

267271
@lru_cache(None)
268-
def imports_closure(self, imports: bool = True, traverse: Optional[bool] = None, inject_metadata=True) -> List[SchemaDefinitionName]:
272+
def imports_closure(self, imports: bool = True, traverse: Optional[bool] = None, inject_metadata=True) -> List[
273+
SchemaDefinitionName]:
269274
"""
270275
Return all imports
271276
@@ -350,7 +355,7 @@ def imports_closure(self, imports: bool = True, traverse: Optional[bool] = None,
350355
visited.add(sn)
351356

352357
# filter duplicates, keeping first entry
353-
closure = list({k:None for k in closure}.keys())
358+
closure = list({k: None for k in closure}.keys())
354359

355360
if inject_metadata:
356361
for s in self.schema_map.values():
@@ -456,7 +461,6 @@ def _order_inheritance(self, elements: DefDict) -> DefDict:
456461

457462
return {s.name: s for s in slist}
458463

459-
460464
@lru_cache(None)
461465
def all_classes(self, ordered_by=OrderedBy.PRESERVE, imports=True) -> Dict[ClassDefinitionName, ClassDefinition]:
462466
"""
@@ -901,15 +905,14 @@ def permissible_value_ancestors(self, permissible_value_text: str,
901905

902906
@lru_cache(None)
903907
def permissible_value_descendants(self, permissible_value_text: str,
904-
enum_name: ENUM_NAME,
905-
reflexive=True,
906-
depth_first=True) -> List[str]:
908+
enum_name: ENUM_NAME,
909+
reflexive=True,
910+
depth_first=True) -> List[str]:
907911
"""
908912
Closure of permissible_value_children method
909913
:enum
910914
"""
911915

912-
913916
return _closure(lambda x: self.permissible_value_children(x, enum_name),
914917
permissible_value_text,
915918
reflexive=reflexive,
@@ -1368,7 +1371,6 @@ def induced_slot(self, slot_name: SLOT_NAME, class_name: CLASS_NAME = None, impo
13681371
:param slot_name: slot to be queries
13691372
:param class_name: class used as context
13701373
:param imports: include imports closure
1371-
:param mangle_name: if True, the slot name will be mangled to include the class name
13721374
:return: dynamic slot constructed by inference
13731375
"""
13741376
if class_name:
@@ -1412,53 +1414,18 @@ def induced_slot(self, slot_name: SLOT_NAME, class_name: CLASS_NAME = None, impo
14121414
}
14131415
# iterate through all metaslots, and potentially populate metaslot value for induced slot
14141416
for metaslot_name in self._metaslots_for_slot():
1415-
14161417
# inheritance of slots; priority order
14171418
# slot-level assignment < ancestor slot_usage < self slot_usage
14181419
v = getattr(induced_slot, metaslot_name, None)
14191420
if cls is None:
14201421
propagated_from = []
14211422
else:
14221423
propagated_from = self.class_ancestors(class_name, reflexive=True, mixins=True)
1423-
14241424
for an in reversed(propagated_from):
14251425
induced_slot.owner = an
1426-
a = self.get_element(an, imports)
1427-
# slot usage of the slot in the ancestor class, last ancestor iterated through here is "self"
1428-
# so that self.slot_usage overrides ancestor slot_usage at the conclusion of the loop.
1426+
a = self.get_class(an, imports)
14291427
anc_slot_usage = a.slot_usage.get(slot_name, {})
1430-
# slot name in the ancestor class
1431-
# getattr(x, 'y') is equivalent to x.y. None here means raise an error if x.y is not found
14321428
v2 = getattr(anc_slot_usage, metaslot_name, None)
1433-
# v2 is the value of the metaslot in slot_usage in the ancestor class, which in the loop, means that
1434-
# the class itself is the last slot_usage to be considered and applied.
1435-
if metaslot_name in ["any_of", "exactly_one_of"]:
1436-
if anc_slot_usage != {}:
1437-
for ao in anc_slot_usage.any_of:
1438-
if ao.range is not None:
1439-
ao_range = self.get_element(ao.range)
1440-
if ao_range:
1441-
print(ao_range)
1442-
acd = get_anonymous_class_definition(to_dict(ao_range))
1443-
if induced_slot.range_expression is None:
1444-
induced_slot.range_expression = AnonymousClassExpression()
1445-
if induced_slot.range_expression.any_of is None:
1446-
induced_slot.range_expression.any_of = []
1447-
# Check for duplicates before appending
1448-
if acd not in induced_slot.range_expression.any_of:
1449-
induced_slot.range_expression.any_of.append(acd)
1450-
for eoo in anc_slot_usage.exactly_one_of:
1451-
if eoo.range is not None:
1452-
eoo_range = self.get_element(eoo.range)
1453-
print(eoo_range)
1454-
acd = get_anonymous_class_definition(as_dict(eoo_range))
1455-
if induced_slot.range_expression is None:
1456-
induced_slot.range_expression = AnonymousClassExpression()
1457-
if induced_slot.range_expression.exactly_one_of is None:
1458-
induced_slot.range_expression.exactly_one_of = []
1459-
# Check for duplicates before appending
1460-
if acd not in induced_slot.range_expression.exactly_one_of:
1461-
induced_slot.range_expression.exactly_one_of.append(acd)
14621429
if v is None:
14631430
v = v2
14641431
else:
@@ -1494,33 +1461,8 @@ def induced_slot(self, slot_name: SLOT_NAME, class_name: CLASS_NAME = None, impo
14941461
if induced_slot.name in c.slots or induced_slot.name in c.attributes:
14951462
if c.name not in induced_slot.domain_of:
14961463
induced_slot.domain_of.append(c.name)
1497-
if induced_slot.range is not None:
1498-
if induced_slot.range_expression is None:
1499-
induced_slot.range_expression = AnonymousClassExpression()
1500-
induced_slot.range_expression.any_of = []
1501-
induced_slot.range_expression.any_of.append(
1502-
get_anonymous_class_definition(to_dict(self.get_element(induced_slot.range)))
1503-
)
1504-
return induced_slot
1505-
else:
1506-
any_of_ancestors = []
1507-
if induced_slot.range_expression.any_of is not None:
1508-
for ao_range in induced_slot.range_expression.any_of:
1509-
ao_range_class = self.get_class(ao_range.name)
1510-
ao_anc = self.class_ancestors(ao_range_class.name)
1511-
for a in ao_anc:
1512-
if a not in any_of_ancestors:
1513-
any_of_ancestors.append(a)
1514-
if induced_slot.range in any_of_ancestors:
1515-
return induced_slot
1516-
else:
1517-
induced_slot.range_expression.any_of.append(
1518-
get_anonymous_class_definition(to_dict(self.get_element(induced_slot.range)))
1519-
)
1520-
return induced_slot
15211464
return induced_slot
15221465

1523-
15241466
@lru_cache(None)
15251467
def _metaslots_for_slot(self):
15261468
fake_slot = SlotDefinition('__FAKE')
@@ -1645,7 +1587,7 @@ def is_inlined(self, slot: SlotDefinition, imports=True) -> bool:
16451587
return True
16461588
elif slot.inlined_as_list:
16471589
return True
1648-
1590+
16491591
id_slot = self.get_identifier_slot(range, imports=imports)
16501592
if id_slot is None:
16511593
# must be inlined as has no identifier
@@ -1689,7 +1631,7 @@ def slot_range_as_union(self, slot: SlotDefinition) -> List[ElementName]:
16891631
"""
16901632
Returns all applicable ranges for a slot
16911633
1692-
Typically any given slot has exactly one range, and one metamodel element type,
1634+
Typically, any given slot has exactly one range, and one metamodel element type,
16931635
but a proposed feature in LinkML 1.2 is range expressions, where ranges can be defined as unions
16941636
16951637
:param slot:
@@ -1701,9 +1643,9 @@ def slot_range_as_union(self, slot: SlotDefinition) -> List[ElementName]:
17011643
if x.range:
17021644
range_union_of.append(x.range)
17031645
return range_union_of
1704-
1646+
17051647
def get_classes_by_slot(
1706-
self, slot: SlotDefinition, include_induced: bool = False
1648+
self, slot: SlotDefinition, include_induced: bool = False
17071649
) -> List[ClassDefinitionName]:
17081650
"""Get all classes that use a given slot, either as a direct or induced slot.
17091651

0 commit comments

Comments
 (0)