Skip to content

Commit

Permalink
Merge pull request #216 from linkml/linkml-issue-1023
Browse files Browse the repository at this point in the history
Handle any_of of ranges in rdflib_loader
  • Loading branch information
cmungall authored Oct 20, 2022
2 parents 9937b30 + 5659755 commit 6b9f30f
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 9 deletions.
13 changes: 8 additions & 5 deletions linkml_runtime/loaders/rdflib_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,17 @@ def from_rdf_graph(self, graph: Graph, schemaview: SchemaView, target_class: Typ
logging.debug(f'No CURIE for {p}={o} in {subject} [{subject_class}]')
v = str(o)
elif EnumDefinition.class_name in range_applicable_elements:
range_union_elements = schemaview.slot_range_as_union(slot)
enum_names = [e for e in range_union_elements if e in schemaview.all_enums()]
# if a PV has a meaning URI declared, map this
# back to a text representation
v = namespaces.curie_for(o)
e = schemaview.get_enum(slot.range)
for pv in e.permissible_values.values():
if v == pv.meaning or str(o) == pv.meaning:
v = pv.text
break
for enum_name in enum_names:
e = schemaview.get_enum(enum_name)
for pv in e.permissible_values.values():
if v == pv.meaning or str(o) == pv.meaning:
v = pv.text
break
elif TypeDefinition.class_name in range_applicable_elements:
if cast_literals:
v = namespaces.curie_for(o)
Expand Down
26 changes: 26 additions & 0 deletions tests/test_loaders_dumpers/input/personinfo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ classes:
- current_address
- has_employment_history
- has_familial_relationships
- has_interpersonal_relationships
- has_medical_history
slot_usage:
primary_email:
Expand Down Expand Up @@ -188,6 +189,18 @@ classes:
range: Person
required: true

InterPersonalRelationship:
is_a: Relationship
slot_usage:
type:
any_of:
- range: FamilialRelationshipType
- range: NonFamilialRelationshipType
required: true
related to:
range: Person
required: true

EmploymentEvent:
is_a: Event
slots:
Expand Down Expand Up @@ -253,6 +266,10 @@ slots:
range: FamilialRelationship
multivalued: true
inlined_as_list: true
has_interpersonal_relationships:
range: InterPersonalRelationship
multivalued: true
inlined_as_list: true
in location:
range: Place
current_address:
Expand Down Expand Up @@ -318,6 +335,14 @@ enums:
meaning: famrel:02
CHILD_OF:
meaning: famrel:01
NonFamilialRelationshipType:
permissible_values:
COWORKER_OF:
meaning: famrel:70
ROOMMATE_OF:
meaning: famrel:71
BEST_FRIEND_OF:
MORTAL_ENEMY_OF:
GenderType:
permissible_values:
nonbinary man:
Expand Down Expand Up @@ -345,3 +370,4 @@ enums:
shell company:
loose organization:


58 changes: 56 additions & 2 deletions tests/test_loaders_dumpers/models/personinfo.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Auto generated from personinfo.yaml by pythongen.py version: 0.9.0
# Generation date: 2021-12-26T18:13:04
# Generation date: 2022-10-19T17:57:17
# Schema: personinfo
#
# id: https://w3id.org/linkml/examples/personinfo
Expand All @@ -26,6 +26,7 @@
from linkml_runtime.utils.metamodelcore import Bool, Decimal, URI, URIorCURIE, XSDDate

metamodel_version = "1.7.0"
version = None

# Overwrite dataclasses _init_fn to add **kwargs in __init__
dataclasses._init_fn = dataclasses_init_fn_with_kwargs
Expand Down Expand Up @@ -169,6 +170,7 @@ class Person(NamedThing):
current_address: Optional[Union[dict, "Address"]] = None
has_employment_history: Optional[Union[Union[dict, "EmploymentEvent"], List[Union[dict, "EmploymentEvent"]]]] = empty_list()
has_familial_relationships: Optional[Union[Union[dict, "FamilialRelationship"], List[Union[dict, "FamilialRelationship"]]]] = empty_list()
has_interpersonal_relationships: Optional[Union[Union[dict, "InterPersonalRelationship"], List[Union[dict, "InterPersonalRelationship"]]]] = empty_list()
has_medical_history: Optional[Union[Union[dict, "MedicalEvent"], List[Union[dict, "MedicalEvent"]]]] = empty_list()
aliases: Optional[Union[str, List[str]]] = empty_list()

Expand Down Expand Up @@ -197,7 +199,13 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
self.has_employment_history = [self.has_employment_history] if self.has_employment_history is not None else []
self.has_employment_history = [v if isinstance(v, EmploymentEvent) else EmploymentEvent(**as_dict(v)) for v in self.has_employment_history]

self._normalize_inlined_as_list(slot_name="has_familial_relationships", slot_type=FamilialRelationship, key_name="type", keyed=False)
if not isinstance(self.has_familial_relationships, list):
self.has_familial_relationships = [self.has_familial_relationships] if self.has_familial_relationships is not None else []
self.has_familial_relationships = [v if isinstance(v, FamilialRelationship) else FamilialRelationship(**as_dict(v)) for v in self.has_familial_relationships]

if not isinstance(self.has_interpersonal_relationships, list):
self.has_interpersonal_relationships = [self.has_interpersonal_relationships] if self.has_interpersonal_relationships is not None else []
self.has_interpersonal_relationships = [v if isinstance(v, InterPersonalRelationship) else InterPersonalRelationship(**as_dict(v)) for v in self.has_interpersonal_relationships]

if not isinstance(self.has_medical_history, list):
self.has_medical_history = [self.has_medical_history] if self.has_medical_history is not None else []
Expand Down Expand Up @@ -567,6 +575,32 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
super().__post_init__(**kwargs)


@dataclass
class InterPersonalRelationship(Relationship):
_inherited_slots: ClassVar[List[str]] = []

class_class_uri: ClassVar[URIRef] = PERSONINFO.InterPersonalRelationship
class_class_curie: ClassVar[str] = "personinfo:InterPersonalRelationship"
class_name: ClassVar[str] = "InterPersonalRelationship"
class_model_uri: ClassVar[URIRef] = PERSONINFO.InterPersonalRelationship

type: str = None
related_to: Union[str, PersonId] = None

def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
if self._is_empty(self.type):
self.MissingRequiredField("type")
if not isinstance(self.type, str):
self.type = str(self.type)

if self._is_empty(self.related_to):
self.MissingRequiredField("related_to")
if not isinstance(self.related_to, PersonId):
self.related_to = PersonId(self.related_to)

super().__post_init__(**kwargs)


@dataclass
class EmploymentEvent(Event):
_inherited_slots: ClassVar[List[str]] = []
Expand Down Expand Up @@ -667,6 +701,17 @@ class FamilialRelationshipType(EnumDefinitionImpl):
name="FamilialRelationshipType",
)

class NonFamilialRelationshipType(EnumDefinitionImpl):

COWORKER_OF = PermissibleValue(text="COWORKER_OF",
meaning=FAMREL["70"])
ROOMMATE_OF = PermissibleValue(text="ROOMMATE_OF",
meaning=FAMREL["70"])

_defn = EnumDefinition(
name="NonFamilialRelationshipType",
)

class GenderType(EnumDefinitionImpl):

_defn = EnumDefinition(
Expand Down Expand Up @@ -763,6 +808,9 @@ class slots:
slots.has_familial_relationships = Slot(uri=PERSONINFO.has_familial_relationships, name="has_familial_relationships", curie=PERSONINFO.curie('has_familial_relationships'),
model_uri=PERSONINFO.has_familial_relationships, domain=None, range=Optional[Union[Union[dict, FamilialRelationship], List[Union[dict, FamilialRelationship]]]])

slots.has_interpersonal_relationships = Slot(uri=PERSONINFO.has_interpersonal_relationships, name="has_interpersonal_relationships", curie=PERSONINFO.curie('has_interpersonal_relationships'),
model_uri=PERSONINFO.has_interpersonal_relationships, domain=None, range=Optional[Union[Union[dict, InterPersonalRelationship], List[Union[dict, InterPersonalRelationship]]]])

slots.in_location = Slot(uri=PERSONINFO.in_location, name="in location", curie=PERSONINFO.curie('in_location'),
model_uri=PERSONINFO.in_location, domain=None, range=Optional[Union[str, PlaceId]])

Expand Down Expand Up @@ -859,3 +907,9 @@ class slots:

slots.FamilialRelationship_related_to = Slot(uri=PERSONINFO.related_to, name="FamilialRelationship_related to", curie=PERSONINFO.curie('related_to'),
model_uri=PERSONINFO.FamilialRelationship_related_to, domain=FamilialRelationship, range=Union[str, PersonId])

slots.InterPersonalRelationship_type = Slot(uri=PERSONINFO.type, name="InterPersonalRelationship_type", curie=PERSONINFO.curie('type'),
model_uri=PERSONINFO.InterPersonalRelationship_type, domain=InterPersonalRelationship, range=str)

slots.InterPersonalRelationship_related_to = Slot(uri=PERSONINFO.related_to, name="InterPersonalRelationship_related to", curie=PERSONINFO.curie('related_to'),
model_uri=PERSONINFO.InterPersonalRelationship_related_to, domain=InterPersonalRelationship, range=Union[str, PersonId])
46 changes: 44 additions & 2 deletions tests/test_loaders_dumpers/test_rdflib_dumper.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@
from rdflib import Namespace

from linkml_runtime import MappingError, DataNotFoundError
from linkml_runtime.loaders import json_loader
from linkml_runtime.dumpers import rdflib_dumper, yaml_dumper
from linkml_runtime.loaders import yaml_loader
from linkml_runtime.loaders import rdflib_loader
from linkml_runtime.utils.schemaview import SchemaView
from linkml_runtime.utils.schemaops import roll_up, roll_down
from tests.test_loaders_dumpers import INPUT_DIR, OUTPUT_DIR
from tests.test_loaders_dumpers.models.personinfo import Container, Person, Address, Organization, OrganizationType
from tests.test_loaders_dumpers.models.node_object import NodeObject, Triple
Expand Down Expand Up @@ -61,6 +59,29 @@
personinfo:age_in_years 33 .
"""

enum_union_type_test_ttl = """
@prefix P: <http://example.org/P/> .
@prefix personinfo: <https://w3id.org/linkml/examples/personinfo/> .
@prefix sdo: <http://schema.org/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix famrel: <https://example.org/FamilialRelations#> .
P:001 a sdo:Person ;
sdo:email "[email protected]" ;
sdo:name "fred bloggs" ;
personinfo:age_in_years 33 ;
personinfo:has_interpersonal_relationships [
a personinfo:InterpersonalRelationship ;
personinfo:type famrel:70 ;
personinfo:related_to P:002
] ,
[
a personinfo:InterpersonalRelationship ;
personinfo:type "BEST_FRIEND_OF" ;
personinfo:related_to P:003
] .
"""

blank_node_test_ttl = """
@prefix personinfo: <https://w3id.org/linkml/examples/personinfo/> .
@prefix sdo: <http://schema.org/> .
Expand Down Expand Up @@ -174,6 +195,27 @@ def test_unmapped_predicates(self):
self.assertEqual(str(person.gender), "cisgender man")
yaml_dumper.dump(person, to_file=UNMAPPED_ROUNDTRIP)


def test_any_of_enum(self):
"""
Tests https://github.com/linkml/linkml/issues/1023
"""
view = SchemaView(SCHEMA)
# default behavior is to raise error on unmapped predicates
person = rdflib_loader.loads(enum_union_type_test_ttl, target_class=Person,
schemaview=view, prefix_map=prefix_map)
self.assertEqual(person.id, 'P:001')
self.assertEqual(person.age_in_years, 33)
yaml_dumper.dump(person, to_file=UNMAPPED_ROUNDTRIP)
cases = [
("P:002", "COWORKER_OF"),
("P:003", "BEST_FRIEND_OF"),
]
tups = []
for r in person.has_interpersonal_relationships:
tups.append((r.related_to, r.type))
self.assertCountEqual(cases, tups)

def test_unmapped_type(self):
"""
If a type cannot be mapped then no objects will be returned by load/from_rdf_graph
Expand Down

0 comments on commit 6b9f30f

Please sign in to comment.