!pip -q --disable-pip-version-check install yamlmagic
%reload_ext yamlmagic
from IPython.core.display import display, HTML
from types import ModuleType
from json import loads, dumps
from jsonasobj import JsonObj, as_json
from rdflib import Graph
from biolinkml.meta import SchemaDefinition
from biolinkml.utils.schemaloader import SchemaLoader
from biolinkml.utils.yamlutils import DupCheckYamlLoader, as_json_object as yaml_to_json
from biolinkml.generators.shexgen import ShExGenerator
from biolinkml.generators.pythongen import PythonGenerator
from biolinkml.generators.yumlgen import YumlGenerator
from biolinkml.generators.jsonldcontextgen import ContextGenerator
A biolink model consists of:
As an example, the model below defines:
%%yaml --loader DupCheckYamlLoader yaml
id: http://example.org/sample/example1
name: synopsis2
prefixes:
foaf: http://xmlns.com/foaf/0.1/
samp: http://example.org/model/
xsd: http://www.w3.org/2001/XMLSchema#
default_prefix: samp
default_curi_maps:
- semweb_context
default_range: string
types:
string:
base: str
uri: xsd:string
int:
base: int
uri: xsd:integer
boolean:
base: Bool
uri: xsd:boolean
classes:
person:
description: A person, living or dead
slots:
- id
- first name
- last name
- age
- living
- knows
friendly_person:
description: Any person that knows someone
is_a: person
slot_usage:
knows:
required: True
slots:
id:
description: Unique identifier of a person
identifier: true
first name:
description: The first name of a person
slot_uri: foaf:firstName
multivalued: true
last name:
description: The last name of a person
slot_uri: foaf:lastName
required: true
living:
description: Whether the person is alive
range: boolean
comments:
- unspecified means unknown
age:
description: The age of a person if living or age of death if not
range: int
slot_uri: foaf:age
knows:
description: A person known by this person (indicating some level of reciprocated interaction between the parties).
range: person
slot_uri: foaf:knows
multivalued: true
print(PythonGenerator(yaml).serialize())
# Auto generated from None by pythongen.py version: 0.9.0 # Generation date: 2021-01-05 15:24 # Schema: synopsis2 # # id: http://example.org/sample/example1 # description: # license: import dataclasses import sys import re from typing import Optional, List, Union, Dict, ClassVar, Any from dataclasses import dataclass from biolinkml.meta import EnumDefinition, PermissibleValue, PvFormulaOptions from biolinkml.utils.slot import Slot from biolinkml.utils.metamodelcore import empty_list, empty_dict, bnode from biolinkml.utils.yamlutils import YAMLRoot, extended_str, extended_float, extended_int if sys.version_info < (3, 7, 6): from biolinkml.utils.dataclass_extensions_375 import dataclasses_init_fn_with_kwargs else: from biolinkml.utils.dataclass_extensions_376 import dataclasses_init_fn_with_kwargs from biolinkml.utils.formatutils import camelcase, underscore, sfx from biolinkml.utils.enumerations import EnumDefinitionImpl from rdflib import Namespace, URIRef from biolinkml.utils.curienamespace import CurieNamespace from biolinkml.utils.metamodelcore import Bool metamodel_version = "1.7.0" # Overwrite dataclasses _init_fn to add **kwargs in __init__ dataclasses._init_fn = dataclasses_init_fn_with_kwargs # Namespaces FOAF = CurieNamespace('foaf', 'http://xmlns.com/foaf/0.1/') SAMP = CurieNamespace('samp', 'http://example.org/model/') XSD = CurieNamespace('xsd', 'http://www.w3.org/2001/XMLSchema#') DEFAULT_ = SAMP # Types class String(str): type_class_uri = XSD.string type_class_curie = "xsd:string" type_name = "string" type_model_uri = SAMP.String class Int(int): type_class_uri = XSD.integer type_class_curie = "xsd:integer" type_name = "int" type_model_uri = SAMP.Int class Boolean(Bool): type_class_uri = XSD.boolean type_class_curie = "xsd:boolean" type_name = "boolean" type_model_uri = SAMP.Boolean # Class references class PersonId(extended_str): pass class FriendlyPersonId(PersonId): pass @dataclass class Person(YAMLRoot): """ A person, living or dead """ _inherited_slots: ClassVar[List[str]] = [] class_class_uri: ClassVar[URIRef] = SAMP.Person class_class_curie: ClassVar[str] = "samp:Person" class_name: ClassVar[str] = "person" class_model_uri: ClassVar[URIRef] = SAMP.Person id: Union[str, PersonId] = None last_name: str = None first_name: Optional[Union[str, List[str]]] = empty_list() age: Optional[int] = None living: Optional[Bool] = None knows: Optional[Union[Union[str, PersonId], List[Union[str, PersonId]]]] = empty_list() def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): if self.id is None: raise ValueError("id must be supplied") if not isinstance(self.id, PersonId): self.id = PersonId(self.id) if self.last_name is None: raise ValueError("last_name must be supplied") if not isinstance(self.last_name, str): self.last_name = str(self.last_name) if self.first_name is None: self.first_name = [] if not isinstance(self.first_name, list): self.first_name = [self.first_name] self.first_name = [v if isinstance(v, str) else str(v) for v in self.first_name] if self.age is not None and not isinstance(self.age, int): self.age = int(self.age) if self.living is not None and not isinstance(self.living, Bool): self.living = Bool(self.living) if self.knows is None: self.knows = [] if not isinstance(self.knows, list): self.knows = [self.knows] self.knows = [v if isinstance(v, PersonId) else PersonId(v) for v in self.knows] super().__post_init__(**kwargs) @dataclass class FriendlyPerson(Person): """ Any person that knows someone """ _inherited_slots: ClassVar[List[str]] = [] class_class_uri: ClassVar[URIRef] = SAMP.FriendlyPerson class_class_curie: ClassVar[str] = "samp:FriendlyPerson" class_name: ClassVar[str] = "friendly_person" class_model_uri: ClassVar[URIRef] = SAMP.FriendlyPerson id: Union[str, FriendlyPersonId] = None last_name: str = None knows: Union[Union[str, PersonId], List[Union[str, PersonId]]] = None def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]): if self.id is None: raise ValueError("id must be supplied") if not isinstance(self.id, FriendlyPersonId): self.id = FriendlyPersonId(self.id) if self.knows is None: raise ValueError("knows must be supplied") elif not isinstance(self.knows, list): self.knows = [self.knows] elif len(self.knows) == 0: raise ValueError(f"knows must be a non-empty list") self.knows = [v if isinstance(v, PersonId) else PersonId(v) for v in self.knows] super().__post_init__(**kwargs) # Enumerations # Slots class slots: pass slots.id = Slot(uri=SAMP.id, name="id", curie=SAMP.curie('id'), model_uri=SAMP.id, domain=None, range=URIRef) slots.first_name = Slot(uri=FOAF.firstName, name="first name", curie=FOAF.curie('firstName'), model_uri=SAMP.first_name, domain=None, range=Optional[Union[str, List[str]]]) slots.last_name = Slot(uri=FOAF.lastName, name="last name", curie=FOAF.curie('lastName'), model_uri=SAMP.last_name, domain=None, range=str) slots.living = Slot(uri=SAMP.living, name="living", curie=SAMP.curie('living'), model_uri=SAMP.living, domain=None, range=Optional[Bool]) slots.age = Slot(uri=FOAF.age, name="age", curie=FOAF.curie('age'), model_uri=SAMP.age, domain=None, range=Optional[int]) slots.knows = Slot(uri=FOAF.knows, name="knows", curie=FOAF.curie('knows'), model_uri=SAMP.knows, domain=None, range=Optional[Union[Union[str, PersonId], List[Union[str, PersonId]]]]) slots.friendly_person_knows = Slot(uri=SAMP.knows, name="friendly_person_knows", curie=SAMP.curie('knows'), model_uri=SAMP.friendly_person_knows, domain=FriendlyPerson, range=Union[Union[str, PersonId], List[Union[str, PersonId]]])
spec = compile(PythonGenerator(yaml).serialize(), 'test', 'exec')
module = ModuleType('test')
exec(spec, module.__dict__)
display(HTML(f'<img src="{YumlGenerator(yaml).serialize()}"/>'))
cntxt = loads(ContextGenerator(yaml).serialize(base="http://example.org/people/"))
print(dumps(cntxt, indent=" "))
{ "_comments": "Auto generated from None by jsonldcontextgen.py version: 0.1.1\nGeneration date: 2021-01-05 15:24\nSchema: synopsis2\n\nid: http://example.org/sample/example1\ndescription: \nlicense: \n", "@context": { "type": "@type", "foaf": "http://xmlns.com/foaf/0.1/", "samp": "http://example.org/model/", "xsd": "http://www.w3.org/2001/XMLSchema#", "@vocab": "http://example.org/model/", "age": { "@type": "xsd:integer", "@id": "foaf:age" }, "first_name": { "@id": "foaf:firstName" }, "id": "@id", "knows": { "@type": "@id", "@id": "foaf:knows" }, "last_name": { "@id": "foaf:lastName" }, "living": { "@type": "xsd:boolean" }, "@base": "http://example.org/people/" } }
# Generate a person
joe_smith = module.Person(id="42", last_name="smith", first_name=['Joe', 'Bob'], age=43)
print(joe_smith)
Person(id='42', last_name='smith', first_name=['Joe', 'Bob'], age=43, living=None, knows=[])
# Add the context and turn it into RDF
jsonld = as_json(yaml_to_json(joe_smith, cntxt))
print(jsonld)
g = Graph()
g.parse(data=jsonld, format="json-ld")
print(g.serialize(format="turtle").decode())
{ "id": "42", "last_name": "smith", "first_name": [ "Joe", "Bob" ], "age": 43, "living": null, "knows": [], "type": "Person", "@context": { "type": "@type", "foaf": "http://xmlns.com/foaf/0.1/", "samp": "http://example.org/model/", "xsd": "http://www.w3.org/2001/XMLSchema#", "@vocab": "http://example.org/model/", "age": { "@type": "xsd:integer", "@id": "foaf:age" }, "first_name": { "@id": "foaf:firstName" }, "id": "@id", "knows": { "@type": "@id", "@id": "foaf:knows" }, "last_name": { "@id": "foaf:lastName" }, "living": { "@type": "xsd:boolean" }, "@base": "http://example.org/people/" } } @prefix : <http://example.org/model/> . @prefix foaf: <http://xmlns.com/foaf/0.1/> . @prefix samp: <http://example.org/model/> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . <http://example.org/people/42> a samp:Person ; foaf:age 43 ; foaf:firstName "Bob", "Joe" ; foaf:lastName "smith" .
shex = ShExGenerator(yaml).serialize(collections=False)
print(shex)
BASE <http://example.org/model/> PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> PREFIX foaf: <http://xmlns.com/foaf/0.1/> <String> xsd:string <Int> xsd:integer <Boolean> xsd:boolean <FriendlyPerson> CLOSED { ( $<FriendlyPerson_tes> ( &<Person_tes> ; rdf:type [ <Person> ] ? ; <knows> @<Person> + ) ; rdf:type [ <FriendlyPerson> ] ) } <Person> ( CLOSED { ( $<Person_tes> ( foaf:firstName @<String> * ; foaf:lastName @<String> ; foaf:age @<Int> ? ; <living> @<Boolean> ? ; foaf:knows @<Person> * ) ; rdf:type [ <Person> ] ) } OR @<FriendlyPerson> )
from pyshex.evaluate import evaluate
r = evaluate(g, shex,
start="http://example.org/model/Person",
focus="http://example.org/people/42")
print("Conforms" if r[0] else r[1])
Conforms
r = evaluate(g, shex,
start="http://example.org/model/FriendlyPerson",
focus="http://example.org/people/42")
print("Conforms" if r[0] else r[1])
Testing <http://example.org/people/42> against shape http://example.org/model/FriendlyPerson No matching triples found for predicate samp:knows