Domain schemas
This page is for a bundle author whose domain needs types LOKF does not define. It shows how to write a LOKF domain schema, and how to make one from a published LinkML vocabulary.
Following OKF, a type that LOKF does not define is tolerated: the concept
reads as lokf:Concept and the bundle stays valid. The type carries no
meaning until a schema declares it.
A LOKF domain schema declares it. It is a LinkML schema that imports
lokf.yaml and whose classes descend from Concept. Pass it as --schema
to validate, convert, query and serve. The rules are in
specification §6.2.
| You have | Do this |
|---|---|
| Types of your own | Write a schema |
| A published LinkML vocabulary | Adapt it |
| A vocabulary, and types or keys of your own | Adapt it, then extend the copy |
Write a schema
Section titled “Write a schema”id: https://acme.example/schema/analyticsname: analyticsimports: - linkml:types - lokf # lokf.yaml, beside this filedefault_prefix: analyticsprefixes: analytics: https://acme.example/schema/analytics/ linkml: https://w3id.org/linkml/classes: Dashboard: # a concept LOKF has no class for is_a: Concept slots: [shows, refresh_interval]slots: shows: # a typed relation of the domain's own range: Concept multivalued: true slot_uri: analytics:shows refresh_interval: range: string-
Put
lokf.yamlbeside the schema. The- lokfimport reads it from there. Copy it from the installed package, so it matches the toolkit:Terminal window cp "$(python -c "from importlib.resources import files; print(files('lokf') / 'data' / 'lokf.yaml')")" . -
Put each class under
Concept, directly or through a LOKF class such asReference. The validator then accepts it as atype. -
Rename anything that shares a name with
lokf.yaml, and keep its IRI. LinkML merges the two files into one namespace, so aPersonortitleof yours replaces LOKF’s. Rename yours and set itsclass_uriorslot_uri. -
Subclass a LOKF class to add a key to it. Classes are closed, so an undeclared key fails. Declare the key on the subclass and write the subclass’s name in
type. -
Range each relation slot over
Conceptor a subclass of it. Its values then project as IRIs, and--check-refsresolves them. A slot ranged overstringholds a literal. -
Pass the schema to each command:
Terminal window lokf validate knowledge --schema analytics.yaml --check-refslokf convert knowledge --schema analytics.yaml --format ttllokf query knowledge --schema analytics.yaml "SELECT ?s WHERE { ?s a analytics:Dashboard }"lokf serve knowledge --schema analytics.yaml
With the schema, a Dashboard projects as a analytics:Dashboard and
shows as analytics:shows. Without it, the same file reads as
lokf:Concept with additionalType: Dashboard, and shows is a string.
Both readings are conformant, and the bundle’s files are the same in each.
Adapt a published vocabulary
Section titled “Adapt a published vocabulary”lokf adapt copies a published LinkML vocabulary into a LOKF domain schema.
The copy imports lokf.yaml, renames each name the two share and keeps its
IRI, and puts the vocabulary’s root class under Concept.
Biolink-model is the worked case.
-
Run
lokf adapton the vocabulary, withlokf.yamlbeside it:Terminal window lokf adapt biolink_model.yamlbiolink_model.yaml (Biolink-Model 4.4.4) -> biolink_model_lokf.yamlfolded imports: attributesdropped for LOKF's: id, typedetached from a dropped: categorydemoted designators: categoryrenamed, IRIs kept: agent -> BiolinkAgent, dataset -> BiolinkDataset, unit -> biolink_unit, name -> biolink_name, ...folded into LOKF's: nonere-rooted on Concept: Entitytree_root removed: BiolinkKnowledgeGraph, MappingCollectionone canonical form: knowledge graph -> BiolinkKnowledgeGraphcanonical names: 835verified beside lokf.yamlIt is a LOKF domain schema: pass it as --schema, or import it from one of your own beside lokf.yaml:imports: [linkml:types, lokf, biolink_model_lokf]Each line is one of the moves.
re-rooted on Conceptis the one that makes biolink’s classes types:entityand the 264 classes below it can each be named intype. Biolink’s 51 mixins cannot; the classes that use them can.graph BT Gene -- is_a --> BiologicalEntity -- is_a --> NamedThing -- is_a --> Entity Entity -- "is_a (added)" --> Concept["lokf:Concept"] -
Write concepts with the vocabulary’s classes as
type:knowledge/genes/brca1.md ---type: Genecategory: [biolink:Gene]title: BRCA1biolink_name: BRCA1symbol: BRCA1xref: [HGNC:1100]in_taxon: [NCBITaxon:9606]status: stable---# BRCA1typeis LOKF’s designator, and validation matches the class by it.categoryis biolink’s own required list, so a biolink consumer reads the file too.biolink_nameis biolink’sname, renamed in the copy; it still projects asrdfs:label. -
Pass the copy as
--schema:Terminal window lokf validate knowledge --schema biolink_model_lokf.yaml --check-refslokf convert knowledge --schema biolink_model_lokf.yaml --format ttl<https://acme.example/genomics/genes/brca1> a biolink:Gene ;rdfs:label "BRCA1" ;schema:name "BRCA1" ;schema:creativeWorkStatus "stable" ;biolink:category "biolink:Gene"^^xsd:anyURI ;biolink:in_taxon <http://purl.obolibrary.org/obo/NCBITaxon_9606> ;biolink:symbol "BRCA1" ;biolink:xref "HGNC:1100"^^xsd:anyURI .A slot ranged over a class (
in_taxon) projects as an IRI. A slot ranged overuriorcurie(xref) projects as a literal. LOKF’s fields and relations work on the vocabulary’s classes: a LOKFDatasetcan beaboutthe gene, and--check-refsresolves it.
Name the root when the vocabulary has none
Section titled “Name the root when the vocabulary has none”lokf adapt finds the root class by its identifier slot. A vocabulary whose
classes carry no identifier has no root to find. Name its top classes with
--root, one flag for each:
lokf adapt gist_core.yaml --prefix gist --root Aspect --root Category --root Component ...gist-linkml, a LinkML rendering of
Semantic Arts’ gist, is the tested
case, because it fits this pattern. A concept then writes type: Requirement, which
projects as a gist:Requirement.
Fold a slot that LOKF already has
Section titled “Fold a slot that LOKF already has”lokf adapt folds a vocabulary slot into a slot on Concept when the
slot’s slot_uri, or one of its exact_mappings, is that slot’s IRI. The
copy drops the vocabulary’s slot, and the classes that used it use LOKF’s
instead. A slot
mapped to schema:name becomes title, whether the vocabulary spells
schema.org with http or https. A class attribute folds the same way.
The fold copies the slot’s constraints into a slot_usage on every class
that used the slot, so those classes still enforce what the vocabulary did:
including inherited restrictions, class refinements, fixed values, cardinalities,
patterns, and a narrower range such as uriorcurie. When two fields fold
into one, both restrictions are retained using all_of where necessary.
Retained child slots keep the restrictions of a removed parent.
The fold is refused, and the report
says why, when the slot’s shape differs from LOKF’s:
- it is multivalued where LOKF’s slot is not, or the reverse;
- its range is a class, an enum or a number where LOKF’s takes a string;
- it represents a reference where LOKF expects an inlined object, or the reverse.
Use --fold for a slot, or a Class.attr attribute, that means the same as
LOKF’s slot but declares no mapping. Use --keep for one whose mapping
matches but which you want left as it is:
lokf adapt slsa.yaml --root Package --root SourceRepository --fold name=title --fold description=descriptionA concept then writes its display name once, as title, which projects as
schema:name. Without the fold, the copy would carry slsa_name beside it.
lokf adapt warns about an attribute on a re-rooted class that has a
Concept slot’s name but not its IRI. The attribute shadows LOKF’s slot on
that class, and a record of the class writes the attribute. Fold it, or
rename it in the vocabulary.
Extend an adapted vocabulary
Section titled “Extend an adapted vocabulary”The copy is generated, so additions go in a schema of your own. That schema
imports lokf.yaml and the copy, and --schema names it in place of the
copy. The steps in Write a schema apply to what it adds.
gist attaches few slots to its classes, and classes are closed. A concept that needs a gist slot therefore takes a class that lists it:
id: https://acme.example/schema/enterprisename: enterpriseimports: [linkml:types, lokf, gist_core_lokf]default_prefix: enterpriseprefixes: enterprise: https://acme.example/schema/enterprise/ gist: https://w3id.org/semanticarts/ns/ontology/gist/ linkml: https://w3id.org/linkml/classes: GovernedRequirement: # gist's Requirement, with one gist slot listed is_a: Requirement class_uri: gist:Requirement slots: [is_governed_by]---type: GovernedRequirementtitle: Rotate keysis_governed_by: [policies/security]---# Rotate keys<https://acme.example/kb/requirements/rotate-keys> a gist:Requirement ; schema:name "Rotate keys" ; gist:isGovernedBy <https://acme.example/kb/policies/security> .Keep the copy current
Section titled “Keep the copy current”The copy has two inputs: the vocabulary’s release and lokf.yaml. Re-run
lokf adapt when either changes, and commit the result. In CI, --check
exits 1 when the copy is stale or no longer verifies:
lokf adapt biolink_model.yaml -o biolink_model_lokf.yaml --check