Skip to content

Commit

Permalink
Merge pull request #66 from linkml/linkml-issue-450
Browse files Browse the repository at this point in the history
linkml issue 450
  • Loading branch information
cmungall authored Nov 4, 2021
2 parents 6c222b2 + ba6f709 commit ab780db
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 12 deletions.
6 changes: 4 additions & 2 deletions linkml_runtime/dumpers/rdflib_dumper.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,14 @@ def inject_triples(self, element: Any, schemaview: SchemaView, graph: Graph, tar
t = schemaview.get_type(target_type)
dt_uri = t.uri
if dt_uri:
if dt_uri == 'xsd:string':
if dt_uri == 'rdfs:Resource':
return URIRef(schemaview.expand_curie(element))
elif dt_uri == 'xsd:string':
return Literal(element)
else:
return Literal(element, datatype=namespaces.uri_for(dt_uri))
else:
logging.error(f'NO DT: {t}')
logging.warning(f'No datatype specified for : {t.name}, using plain Literal')
return Literal(element)
element_vars = {k: v for k, v in vars(element).items() if not k.startswith('_')}
if len(element_vars) == 0:
Expand Down
19 changes: 19 additions & 0 deletions tests/test_issues/input/linkml_issue_450.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
id: https://w3id.org/linkml/examples/test
prefixes:
linkml: https://w3id.org/linkml/
default_range: my_str
default_curi_maps:
- semweb_context

types:
entity:
base: str
uri: rdfs:Resource

classes:
C1:
attributes:
a:
range: entity


Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ P:002 a ns3:Person ;
ns2:employed_at ROR:1 ;
ns2:is_current true ] ;
ns2:has_familial_relationships [ a ns2:FamilialRelationship ;
ns2:related_to "P:001" ;
ns2:related_to P:001 ;
ns2:type <https://example.org/FamilialRelations#01> ] ;
ns2:has_medical_history [ a ns2:MedicalEvent ;
ns1:startedAtTime "2019-01-01"^^xsd:date ;
Expand Down
5 changes: 5 additions & 0 deletions tests/test_loaders_dumpers/input/example_personinfo_data.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ persons:
primary_email: [email protected]
age_in_years: 33
gender: cisgender man
depicted_by: https://example.org/pictures/fred.jpg
- id: P:002
name: joe schmoe
primary_email: [email protected]
Expand All @@ -21,6 +22,10 @@ persons:
id: CODE:D0001
name: headache
code_system: CODE:D
mappings:
- HP:0002315
- http://purl.obolibrary.org/obo/SYMP_0000504
- http://www.wikidata.org/entity/Q86
procedure:
id: CODE:P0001
name: trepanation
Expand Down
24 changes: 24 additions & 0 deletions tests/test_loaders_dumpers/input/personinfo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ prefixes:
GSSO: http://purl.obolibrary.org/obo/GSSO_
famrel: https://example.org/FamilialRelations#
bizcodes: https://example.org/bizcodes/
HP: http://purl.obolibrary.org/obo/HP_
skos: http://www.w3.org/2004/02/skos/core#
default_prefix: personinfo
default_range: string

Expand All @@ -25,6 +27,19 @@ emit_prefixes:
- xsd
- skos

types:

CrossReference:
typeof: uriorcurie
description: A string URI or CURIE representation of an external identifier, modeled as a Resource in RDF
base: str
uri: rdfs:Resource

ImageURL:
typeof: uri
base: str
uri: xsd:anyURI

classes:

NamedThing:
Expand All @@ -35,6 +50,7 @@ classes:
- name
- description
- image
- depicted_by
close_mappings:
- schema:Thing

Expand Down Expand Up @@ -91,6 +107,7 @@ classes:
slots:
- id
- name
- depicted_by

Address:
class_uri: schema:PostalAddress
Expand All @@ -113,6 +130,10 @@ classes:
attributes:
code system:
range: code system
mappings:
slot_uri: skos:exactMatch
multivalued: true
range: CrossReference

DiagnosisConcept:
is_a: Concept
Expand Down Expand Up @@ -217,6 +238,9 @@ slots:
minimum_value: 0
maximum_value: 999
related_to:
range: NamedThing
depicted_by:
range: ImageURL
type:
street:
city:
Expand Down
50 changes: 42 additions & 8 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-10-20 18:50
# Generation date: 2021-11-03 18:00
# Schema: personinfo
#
# id: https://w3id.org/linkml/examples/personinfo
Expand All @@ -22,8 +22,8 @@
from linkml_runtime.utils.enumerations import EnumDefinitionImpl
from rdflib import Namespace, URIRef
from linkml_runtime.utils.curienamespace import CurieNamespace
from linkml_runtime.linkml_model.types import Boolean, Date, Float, Integer, String
from linkml_runtime.utils.metamodelcore import Bool, XSDDate
from linkml_runtime.linkml_model.types import Boolean, Date, Float, Integer, String, Uri, Uriorcurie
from linkml_runtime.utils.metamodelcore import Bool, URI, URIorCURIE, XSDDate

metamodel_version = "1.7.0"

Expand All @@ -32,6 +32,7 @@

# Namespaces
GSSO = CurieNamespace('GSSO', 'http://purl.obolibrary.org/obo/GSSO_')
HP = CurieNamespace('HP', 'http://purl.obolibrary.org/obo/HP_')
BIZCODES = CurieNamespace('bizcodes', 'https://example.org/bizcodes/')
FAMREL = CurieNamespace('famrel', 'https://example.org/FamilialRelations#')
LINKML = CurieNamespace('linkml', 'https://w3id.org/linkml/')
Expand All @@ -40,12 +41,26 @@
RDF = CurieNamespace('rdf', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#')
RDFS = CurieNamespace('rdfs', 'http://www.w3.org/2000/01/rdf-schema#')
SCHEMA = CurieNamespace('schema', 'http://schema.org/')
SKOS = CurieNamespace('skos', 'http://example.org/UNKNOWN/skos/')
SKOS = CurieNamespace('skos', 'http://www.w3.org/2004/02/skos/core#')
XSD = CurieNamespace('xsd', 'http://www.w3.org/2001/XMLSchema#')
DEFAULT_ = PERSONINFO


# Types
class CrossReference(Uriorcurie):
""" A string URI or CURIE representation of an external identifier, modeled as a Resource in RDF """
type_class_uri = RDFS.Resource
type_class_curie = "rdfs:Resource"
type_name = "CrossReference"
type_model_uri = PERSONINFO.CrossReference


class ImageURL(Uri):
type_class_uri = XSD.anyURI
type_class_curie = "xsd:anyURI"
type_name = "ImageURL"
type_model_uri = PERSONINFO.ImageURL


# Class references
class NamedThingId(extended_str):
Expand Down Expand Up @@ -96,6 +111,7 @@ class NamedThing(YAMLRoot):
name: Optional[str] = None
description: Optional[str] = None
image: Optional[str] = None
depicted_by: Optional[Union[str, ImageURL]] = None

def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
if self._is_empty(self.id):
Expand All @@ -112,6 +128,9 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
if self.image is not None and not isinstance(self.image, str):
self.image = str(self.image)

if self.depicted_by is not None and not isinstance(self.depicted_by, ImageURL):
self.depicted_by = ImageURL(self.depicted_by)

super().__post_init__(**kwargs)


Expand Down Expand Up @@ -254,6 +273,7 @@ class Place(YAMLRoot):

id: Union[str, PlaceId] = None
name: Optional[str] = None
depicted_by: Optional[Union[str, ImageURL]] = None
aliases: Optional[Union[str, List[str]]] = empty_list()

def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
Expand All @@ -265,6 +285,9 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
if self.name is not None and not isinstance(self.name, str):
self.name = str(self.name)

if self.depicted_by is not None and not isinstance(self.depicted_by, ImageURL):
self.depicted_by = ImageURL(self.depicted_by)

if not isinstance(self.aliases, list):
self.aliases = [self.aliases] if self.aliases is not None else []
self.aliases = [v if isinstance(v, str) else str(v) for v in self.aliases]
Expand Down Expand Up @@ -339,6 +362,7 @@ class Concept(NamedThing):

id: Union[str, ConceptId] = None
code_system: Optional[Union[str, CodeSystemId]] = None
mappings: Optional[Union[Union[str, CrossReference], List[Union[str, CrossReference]]]] = empty_list()

def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
if self._is_empty(self.id):
Expand All @@ -349,6 +373,10 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
if self.code_system is not None and not isinstance(self.code_system, CodeSystemId):
self.code_system = CodeSystemId(self.code_system)

if not isinstance(self.mappings, list):
self.mappings = [self.mappings] if self.mappings is not None else []
self.mappings = [v if isinstance(v, CrossReference) else CrossReference(v) for v in self.mappings]

super().__post_init__(**kwargs)


Expand Down Expand Up @@ -427,7 +455,7 @@ class Relationship(YAMLRoot):

started_at_time: Optional[Union[str, XSDDate]] = None
ended_at_time: Optional[Union[str, XSDDate]] = None
related_to: Optional[str] = None
related_to: Optional[Union[str, NamedThingId]] = None
type: Optional[str] = None

def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
Expand All @@ -437,8 +465,8 @@ def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
if self.ended_at_time is not None and not isinstance(self.ended_at_time, XSDDate):
self.ended_at_time = XSDDate(self.ended_at_time)

if self.related_to is not None and not isinstance(self.related_to, str):
self.related_to = str(self.related_to)
if self.related_to is not None and not isinstance(self.related_to, NamedThingId):
self.related_to = NamedThingId(self.related_to)

if self.type is not None and not isinstance(self.type, str):
self.type = str(self.type)
Expand Down Expand Up @@ -674,7 +702,10 @@ class slots:
model_uri=PERSONINFO.age_in_years, domain=None, range=Optional[int])

slots.related_to = Slot(uri=PERSONINFO.related_to, name="related_to", curie=PERSONINFO.curie('related_to'),
model_uri=PERSONINFO.related_to, domain=None, range=Optional[str])
model_uri=PERSONINFO.related_to, domain=None, range=Optional[Union[str, NamedThingId]])

slots.depicted_by = Slot(uri=PERSONINFO.depicted_by, name="depicted_by", curie=PERSONINFO.curie('depicted_by'),
model_uri=PERSONINFO.depicted_by, domain=None, range=Optional[Union[str, ImageURL]])

slots.type = Slot(uri=PERSONINFO.type, name="type", curie=PERSONINFO.curie('type'),
model_uri=PERSONINFO.type, domain=None, range=Optional[str])
Expand Down Expand Up @@ -721,6 +752,9 @@ class slots:
slots.concept__code_system = Slot(uri=PERSONINFO.code_system, name="concept__code_system", curie=PERSONINFO.curie('code_system'),
model_uri=PERSONINFO.concept__code_system, domain=None, range=Optional[Union[str, CodeSystemId]])

slots.concept__mappings = Slot(uri=SKOS.exactMatch, name="concept__mappings", curie=SKOS.curie('exactMatch'),
model_uri=PERSONINFO.concept__mappings, domain=None, range=Optional[Union[Union[str, CrossReference], List[Union[str, CrossReference]]]])

slots.container__persons = Slot(uri=PERSONINFO.persons, name="container__persons", curie=PERSONINFO.curie('persons'),
model_uri=PERSONINFO.container__persons, domain=None, range=Optional[Union[Dict[Union[str, PersonId], Union[dict, Person]], List[Union[dict, Person]]]])

Expand Down
12 changes: 11 additions & 1 deletion tests/test_loaders_dumpers/test_rdflib_dumper.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import logging

from rdflib import Graph, Literal
from rdflib.namespace import RDF
from rdflib.namespace import RDF, SKOS, XSD
from rdflib import Namespace

from linkml_runtime.loaders import json_loader
Expand Down Expand Up @@ -44,14 +44,24 @@ def test_rdflib_dumper(self):
INFO = Namespace('https://w3id.org/linkml/examples/personinfo/')
SDO = Namespace('http://schema.org/')
GSSO = Namespace('http://purl.obolibrary.org/obo/GSSO_')
HP = Namespace('http://purl.obolibrary.org/obo/HP_')
SYMP = Namespace('http://purl.obolibrary.org/obo/SYMP_')
WD = Namespace('http://www.wikidata.org/entity/')
self.assertIn((P['001'], RDF.type, SDO.Person), g)
self.assertIn((P['001'], SDO.name, Literal("fred bloggs")), g)
self.assertIn((P['001'], SDO.email, Literal("[email protected]")), g)
self.assertIn((P['001'], INFO.age_in_years, Literal(33)), g)
self.assertIn((P['001'], SDO.gender, GSSO['000371']), g)
self.assertIn((P['001'], INFO.depicted_by, Literal('https://example.org/pictures/fred.jpg', datatype=XSD.anyURI)), g)
self.assertNotIn((P['001'], INFO.depicted_by, Literal('https://example.org/pictures/fred.jpg', datatype=XSD.string)), g)
#for (s,p,o) in g.triples((None, None, None)):
# print(f'{s} {p} {o}')
self.assertIn((CODE['D0001'], RDF.type, INFO.DiagnosisConcept), g)
self.assertIn((CODE['D0001'], RDF.type, INFO.DiagnosisConcept), g)
self.assertIn((CODE['D0001'], SKOS.exactMatch, HP['0002315']), g)
self.assertIn((CODE['D0001'], SKOS.exactMatch, WD.Q86), g)
self.assertIn((CODE['D0001'], SKOS.exactMatch, SYMP['0000504']), g)
self.assertNotIn((CODE['D0001'], SKOS.exactMatch, Literal(HP['0002315'])), g)
[container] = g.subjects(RDF.type, INFO.Container)
self.assertIn((container, INFO.organizations, ROR['1']), g)
self.assertIn((container, INFO.organizations, ROR['2']), g)
Expand Down

0 comments on commit ab780db

Please sign in to comment.