Skip to content

feat(rdf): give declared annotation values and native metadata the term of their range - #58

Open
jdsika wants to merge 1 commit into
fix/string-derived-anyuri-types-stay-literalsfrom
feat/declared-annotation-terms
Open

jdsika wants to merge 1 commit into
fix/string-derived-anyuri-types-stay-literalsfrom
feat/declared-annotation-terms

Conversation

@jdsika

@jdsika jdsika commented Oct 21, 2026 •

Copy link
Copy Markdown

Mirror of linkml#4157, same head. Review there; this PR tracks the fork stack.

2026-10-23, second push (review): a link value that is neither an IRI nor a CURIE (a bare element name in see_also) was written as a relative IRI, which makes the whole graph non-standard RDF and sends gen-owl's output to the canonicalizer's rdflib fallback (no RDFC-1.0, no diff-stable); main does the same. It is now a plain literal with the warning, which OWL 2 §10.1 admits as an annotation value. The body's claim that the metamodel has 13 such values was wrong: the vendored metamodel has none, and gen-owl on it emits no such warning. OWL, SHACL, compliance, issue and snapshot suites: 9754 passed.

2026-10-23: rebased onto the reviewed linkml#4156 head (88e231b42); no conflict, no code change. test_declared_annotations.py + test_owlgen.py + test_shaclgen.py: 229 passed.

An annotation whose tag a metaclass declares through instantiates now gets the RDF term that the slot's range gives a slot value, by the rule of linkml#4156 (linkml_runtime.utils.rdf_terms). gen-owl and gen-shacl (--include-annotations) share it through linkml.generators.common.annotations, and gen-owl applies the same rule to native metadata such as see_also.

Example

classes:
  Reviewable:
    attributes:
      reviewer: {range: nodeidentifier}
      review: {range: string}
  Person:
    instantiates: [ex:Reviewable]
    annotations: {reviewer: ex:alice, review: well defined}
main this PR
reviewer "ex:alice"@en <https://example.org/alice>
review "well defined"@en unchanged

On main the declared range is ignored: every annotation value is a string, and with --default-language every value is language-tagged, identifiers included. This is the pattern of the annotations guide; the metaclass needs no further declaration.

The rule

  • A type range gives the term of a slot value of that type (fix(rdf): give values of URI-like types one RDF term kind in every generator linkml/linkml#4156): nodeidentifier, uri and uriorcurie give IRIs, or a blank node for a _: label; string gives text, with the language; curie gives an xsd:string literal; a string-derived xsd:anyURI type gives an xsd:anyURI literal.
  • An enum range gives the permissible value's text as a plain literal.
  • A class range, a structured value or an undeclared tag keeps the generator's earlier behaviour.
  • Two metaclasses that declare the tag with different terms are an error.
  • Native metadata in gen-owl follows the metamodel slot's range: see_also, source, created_by and the mappings are IRIs, with CURIEs expanded through the schema's prefixes; license, description and title are strings; created_on is an xsd:dateTime literal.

Standards and LinkML contract

  • OWL 2 §10.1: an annotation value is an IRI, an anonymous individual or a literal, so every declared term is admissible.
  • LinkML specification, part 5: instantiates makes a metaclass's slots the declarations of an element's annotations.
  • specify IRI data type for annotations linkml/linkml#2892: cmungall, "We should implement this … declare the range in your custom annotation model". No new option and no vocabulary table: DCMI's rangeIncludes is a recommendation, and rdfs:Resource includes literals, so a property name cannot decide the term.

Behaviour changes

  • Declared annotation values become nodes or typed literals; identifiers and enum values lose the language tag.
  • Native created_on and last_updated_on become xsd:dateTime literals (were plain strings). Mapped uriorcurie metaslots stay IRIs, now by the shared rule instead of a name check.
  • Fail fast for declared values: a uri annotation value that is a relative reference or a _: label is an error.
  • Native link metadata that is neither an IRI nor a CURIE, such as a bare element name in see_also, is written as a plain literal, with a warning naming the element and metaslot. A relative IRI would make the whole graph non-standard RDF, and main writes one.
  • No snapshot changes.

Not in this PR

Testing

  • test_declared_annotations.py (44): both generators; every range kind; tag as name, CURIE or IRI; annotated classes, slots, enums, permissible values and types; inherited and imported metaclasses; blank nodes; conflicting declarations; wrong-kind negatives; native metadata; the documented DCMI example; a pyshacl check that shape annotations do not constrain data.
  • OWL, SHACL, compliance, issue and snapshot suites: 899 passed; slow suite (metamodel, Biolink) passed.

Draft, stacked on linkml#4156: this PR's own change is its last commit; the first is linkml#4156. It will be rebased when linkml#4156 lands. Fork review: #58. Refs linkml#2892, linkml#3543, linkml#3546.

…rm of their range

An annotation whose tag a metaclass declares through instantiates gets the RDF
term that the slot's range gives a slot value (linkml_runtime.utils.rdf_terms):
nodeidentifier, uri and uriorcurie values are IRIs or blank nodes, string
values are text with the resolved language, curie values are xsd:string
literals, and an enum-ranged slot writes the permissible value's text. gen-owl
and gen-shacl share the rule through linkml.generators.common.annotations;
without a declaration both keep their earlier behaviour, and metaclasses that
declare a tag with different terms are an error.

gen-owl applies the same rule to native metadata, so see_also, source and the
mappings are IRIs by the metamodel's ranges rather than by a name check, and
created_on is an xsd:dateTime literal. A link value that is neither an IRI
nor a CURIE, such as a bare element name in see_also, is written as a plain
literal with a warning: a relative IRI would make the whole graph
non-standard RDF and push it onto the canonicalizer's rdflib fallback. OWL 2 §10.1 admits IRIs, anonymous
individuals and literals as annotation values.

Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant