diff --git a/docs/generators/owl.rst b/docs/generators/owl.rst index a0ed1edeb3..f9ee4b57e5 100644 --- a/docs/generators/owl.rst +++ b/docs/generators/owl.rst @@ -85,9 +85,36 @@ string. :language: yaml The ontology header then carries ``dcterms:creator `` -and ``dcterms:rights "Copyright 2026 The Example Team"``. ``implements: [owl:AnnotationProperty]`` -declares the two predicates as annotation properties; the restrictions that the metaclass's -own slots produce are a matter of the property role, not of the value. +and ``dcterms:rights "Copyright 2026 The Example Team"``, and the two predicates are +declared as annotation properties (see below). + +Annotation properties +^^^^^^^^^^^^^^^^^^^^^ + +A slot that ``implements`` ``owl:AnnotationProperty`` is an annotation property +(`OWL 2 §5.5 `__), and so is +a slot whose property IRI is one of the nine built-in annotation properties, such as a +slot with ``slot_uri: rdfs:label`` emitted with ``--no-use-native-uris``. An annotation +property has no logical meaning (`§10.1 `__), +so the generator emits no class restriction for it: a class that uses the slot gets no +``owl:allValuesFrom`` or cardinality axiom on it. Its domain, a datatype or class range +and its super-properties are emitted as the annotation axioms of +`§10.2 `__; a range that is a node +type such as ``nodeidentifier`` gives no range axiom. Built-in annotation properties are +not declared, as OWL 2 reserves them. + +An IRI is an annotation, an object or a data property, not two of these +(`§5.8.1 `__), so a slot +that implements two roles is an error, as is a logical use of an annotation property: an +inverse or a property characteristic, a unique key, a type designator, or a condition in +a rule or class expression. A super-property (``is_a`` or a mixin) must have the same +role as its sub-property, since a sub-property axiom relates two annotation properties +or two logical properties. Every other predicate the generator writes outside the +reserved vocabulary, such as ``skos:definition`` and ``skos:inScheme`` for element +metadata, is declared as an annotation property, as OWL 2 DL requires a declaration +for each property (§5.8.1). Slots without the role keep their object or data property +projection, also when a metaclass declares them; LinkML then puns the annotated element +as an individual, as it does for all metadata. Enums and PermissibleValues ^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/packages/linkml/src/linkml/generators/common/annotations.py b/packages/linkml/src/linkml/generators/common/annotations.py index 702ace7ef8..bc585500ad 100644 --- a/packages/linkml/src/linkml/generators/common/annotations.py +++ b/packages/linkml/src/linkml/generators/common/annotations.py @@ -35,7 +35,7 @@ True """ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import Any from rdflib import BNode, Literal, URIRef @@ -47,10 +47,11 @@ @dataclass(frozen=True) class DeclaredAnnotation: - """The RDF predicate and value of an annotation that a metaclass slot declares.""" + """The RDF predicate and value of an annotation, and the metaclass slot that declares it.""" predicate: URIRef value: URIRef | BNode | Literal + slot: SlotDefinition = field(compare=False, hash=False, repr=False) def declaring_slots(sv: SchemaView, element: Element | PermissibleValue, tag: str) -> list[SlotDefinition]: @@ -113,7 +114,7 @@ def declared_annotation( for slot in declaring_slots(sv, element, tag): term = annotation_term(sv, slot, value, language) if term is not None: - declared.add(DeclaredAnnotation(URIRef(sv.get_uri(slot, expand=True)), term)) + declared.add(DeclaredAnnotation(URIRef(sv.get_uri(slot, expand=True)), term, slot)) if len(declared) > 1: metaclasses = ", ".join(element.instantiates) raise ValueError(f"The metaclasses {metaclasses} declare annotation {tag!r} with different terms") diff --git a/packages/linkml/src/linkml/generators/owlgen.py b/packages/linkml/src/linkml/generators/owlgen.py index e7a9911bea..522910a5b2 100644 --- a/packages/linkml/src/linkml/generators/owlgen.py +++ b/packages/linkml/src/linkml/generators/owlgen.py @@ -248,6 +248,89 @@ def _present(values: Iterable[_T | None]) -> list[_T]: # is technical and must never be language-tagged. _LANGUAGE_TAGGABLE_RANGES: ClassVar[frozenset[str]] = frozenset({"string", "ncname"}) + # The built-in annotation properties of OWL 2 (Structural Specification §5.5, Table 5). + _BUILTIN_ANNOTATION_PROPERTIES: ClassVar[frozenset[URIRef]] = frozenset( + { + RDFS.label, + RDFS.comment, + RDFS.seeAlso, + RDFS.isDefinedBy, + OWL.deprecated, + OWL.versionInfo, + OWL.priorVersion, + OWL.backwardCompatibleWith, + OWL.incompatibleWith, + } + ) + # The namespaces of the OWL 2 reserved vocabulary (Structural Specification §2.4, Table 2). + _RESERVED_NAMESPACES: ClassVar[tuple[str, ...]] = (str(RDF), str(RDFS), str(XSD), str(OWL)) + # The declarations of a property's role (§5.3 to §5.5). + _PROPERTY_DECLARATIONS: ClassVar[frozenset[URIRef]] = frozenset( + {OWL.ObjectProperty, OWL.DatatypeProperty, OWL.AnnotationProperty} + ) + + def _declare_annotation_properties(self) -> None: + """Declare every predicate of the ontology that has no role as an annotation property. + + OWL 2 DL requires each IRI used as a property to be declared (Structural + Specification §5.8.1). The generator declares the object and data + properties of the slots it emits; every other predicate it writes outside + the reserved vocabulary (§2.4) is an annotation: the metadata of elements + (``skos:definition``, ``skos:inScheme``, ``dcterms:title``, ...), the + mappings, and annotations. A predicate that a restriction uses + (``owl:onProperty``) is a logical property and is left alone. + """ + graph = self.graph + declared = {s for s, role in graph.subject_objects(RDF.type) if role in self._PROPERTY_DECLARATIONS} + restricted = set(graph.objects(None, OWL.onProperty)) + for predicate in set(graph.predicates()): + if str(predicate).startswith(self._RESERVED_NAMESPACES) or predicate in declared | restricted: + continue + graph.add((predicate, RDF.type, OWL.AnnotationProperty)) + + # The property roles a slot can declare with ``implements`` (OWL 2 §5.3 to §5.5). + _PROPERTY_ROLES: ClassVar[frozenset[URIRef]] = frozenset( + {OWL.AnnotationProperty, OWL.ObjectProperty, OWL.DatatypeProperty} + ) + + def _declared_property_role(self, slot: SlotDefinition) -> URIRef | None: + """Return the OWL property role that *slot* declares with ``implements``, if any. + + :raises ValueError: if the slot declares more than one role; an IRI is an + annotation, an object or a data property (OWL 2 §5.8.1) + """ + roles: set[URIRef] = set() + for value in slot.implements: + uri = self.schemaview.expand_curie(value) + if uri.startswith("owl:"): + uri = str(OWL) + uri[len("owl:") :] + if URIRef(uri) in self._PROPERTY_ROLES: + roles.add(URIRef(uri)) + if len(roles) > 1: + raise ValueError(f"Slot {slot.name!r} implements more than one OWL property role: {sorted(roles)}") + return next(iter(roles), None) + + def is_annotation_property(self, slot: SlotDefinition) -> bool: + """Whether *slot* is an OWL annotation property. + + It is one when it implements ``owl:AnnotationProperty`` or when its property + IRI is a built-in annotation property (OWL 2 §5.5), as for a slot mapped to + ``rdfs:label`` and emitted with that IRI. + """ + return ( + self._declared_property_role(slot) == OWL.AnnotationProperty + or self._prop_uri(slot) in self._BUILTIN_ANNOTATION_PROPERTIES + ) + + def _resolved_slot( + self, slot: SlotDefinition, cls: "ClassDefinition | AnonymousClassExpression | None" + ) -> SlotDefinition: + """Return *slot* with its global definition and, within a class, the class's usage applied.""" + sv = self.schemaview + if isinstance(cls, ClassDefinition) and slot.name in sv.class_slots(cls.name): + return sv.induced_slot(slot.name, cls.name) + return sv.get_slot(slot.name, attributes=True) or slot + def __post_init__(self) -> None: # Resolver must be assigned before ``super().__post_init__()`` so that # any hook the parent invokes during initialisation can safely call @@ -334,6 +417,7 @@ def as_graph(self) -> Graph: # Add metadata as annotation properties self.add_metadata(schema, base) + self._declare_annotation_properties() return graph def serialize(self, **kwargs: Any) -> str: @@ -411,6 +495,11 @@ def add_metadata(self, e: Definition | PermissibleValue, uri: URIRef) -> None: continue declared = declared_annotation(this_sv, e, k, v.value, lang) if declared is not None: + if ( + self.is_annotation_property(declared.slot) + and declared.predicate not in self._BUILTIN_ANNOTATION_PROPERTIES + ): + self.graph.add((declared.predicate, RDF.type, OWL.AnnotationProperty)) self.graph.add((uri, declared.predicate, declared.value)) continue if ":" not in k: @@ -537,12 +626,19 @@ def add_class(self, cls: ClassDefinition) -> None: # then create an axiom: (t some C) subClassOf C. type_designator = sv.get_type_designator_slot(cls.name) if type_designator: + if self.is_annotation_property(self._resolved_slot(type_designator, cls)): + raise ValueError( + f"Annotation property {type_designator.name!r} cannot be the type designator of {cls.name!r}" + ) td_prop = self._prop_uri(type_designator.name) restr = self._some_values_from(td_prop, subject_expr) self.graph.add((restr, RDFS.subClassOf, subject_expr)) # unique key constraints are mapped to OWL hasKey if cls.unique_keys: for uk in cls.unique_keys.values(): + for slot_name in uk.unique_key_slots: + if self.is_annotation_property(sv.induced_slot(slot_name, cls.name)): + raise ValueError(f"Annotation property {slot_name!r} cannot be in a unique key of {cls.name!r}") uk_props = [self._prop_uri(slot) for slot in uk.unique_key_slots] uk_props_listnode = BNode() Collection(self.graph, uk_props_listnode, uk_props) @@ -716,6 +812,11 @@ def transform_class_expression( if none_of_expr: owl_exprs.append(none_of_expr) for slot in own_slots: + if self.is_annotation_property(self._resolved_slot(slot, cls)): + if isinstance(cls, AnonymousClassExpression): + raise ValueError(f"Annotation property {slot.name!r} cannot be a condition of a class expression") + # An annotation property has no logical meaning (OWL 2 §10.1), so no restriction. + continue if slot.name: owltypes = self.slot_node_owltypes(sv.get_slot(slot.name), owning_class=cls) else: @@ -1046,7 +1147,9 @@ def add_slot(self, slot: SlotDefinition, attribute: bool = False) -> None: # Slots may be modeled as Object or Datatype Properties # if type_objects is True, then ALL slots are ObjectProperties - self.graph.add((slot_uri, RDF.type, self.slot_owl_type(slot))) + role = self.slot_owl_type(slot) + if slot_uri not in self._BUILTIN_ANNOTATION_PROPERTIES: + self.graph.add((slot_uri, RDF.type, role)) if self.metaclasses: # add metaclass which this property instantiates -- induces punning self.graph.add( @@ -1072,6 +1175,10 @@ def add_slot(self, slot: SlotDefinition, attribute: bool = False) -> None: if attribute: return + if role == OWL.AnnotationProperty: + if slot_uri not in self._BUILTIN_ANNOTATION_PROPERTIES: + self._add_annotation_property_axioms(slot, slot_uri) + return range_expr = self.transform_class_slot_expression(None, slot) if range_expr: @@ -1091,10 +1198,54 @@ def add_slot(self, slot: SlotDefinition, attribute: bool = False) -> None: if getattr(slot, metaslot, False): self.graph.add((slot_uri, RDF.type, uri)) - if slot.is_a: - self.graph.add((slot_uri, RDFS.subPropertyOf, self._prop_uri(slot.is_a))) - for mixin in slot.mixins: - self.graph.add((slot_uri, RDFS.subPropertyOf, self._prop_uri(mixin))) + self._add_super_properties(slot, slot_uri) + + def _add_super_properties(self, slot: SlotDefinition, slot_uri: URIRef) -> None: + """Emit the ``is_a`` parent and the mixins of *slot* as its super-properties. + + A sub-property axiom relates two annotation properties (OWL 2 §10.2.1) or two + properties of a logical role (§9.2.1, §9.3.1), so a super-property must have + the role *slot* has: a slot whose ``is_a`` or mixin is an annotation property + while it is none, or the reverse, is an error. + + :raises ValueError: if *slot* and a super-property differ in the annotation role + """ + is_annotation = self.is_annotation_property(slot) + for parent_name in ([slot.is_a] if slot.is_a else []) + list(slot.mixins): + parent = self.schemaview.get_slot(parent_name, attributes=True) + if parent is not None and self.is_annotation_property(parent) != is_annotation: + kinds = ( + ("an annotation property", "not one") if is_annotation else ("not an annotation property", "one") + ) + raise ValueError( + f"Slot {slot.name!r} is {kinds[0]} but its super-property {parent_name!r} is {kinds[1]}; " + "a sub-property axiom needs both to have the same role (OWL 2 §10.2.1)" + ) + self.graph.add((slot_uri, RDFS.subPropertyOf, self._prop_uri(parent_name))) + + def _add_annotation_property_axioms(self, slot: SlotDefinition, slot_uri: URIRef) -> None: + """Emit the axioms OWL 2 allows for an annotation property (§10.2). + + These are sub-properties and a domain and a range given by a named IRI; a + range that is a type gives its datatype, a node type gives no range. Inverses + and property characteristics are object property axioms (§9.2), so a slot that + declares them with the annotation role is an error. + """ + for metaslot in ("inverse", "symmetric", "asymmetric", "transitive", "reflexive", "irreflexive"): + if getattr(slot, metaslot): + raise ValueError(f"Annotation property {slot.name!r} cannot declare {metaslot}") + sv = self.schemaview + if slot.domain: + self.graph.add((slot_uri, RDFS.domain, self._class_uri(slot.domain))) + if slot.range in sv.all_types(): + term = type_term(sv, slot.range) + if not term.kind.is_node: + self.graph.add((slot_uri, RDFS.range, URIRef(term.datatype))) + elif slot.range in sv.all_classes(): + self.graph.add((slot_uri, RDFS.range, self._class_uri(slot.range))) + elif slot.range in sv.all_enums(): + self.graph.add((slot_uri, RDFS.range, self._enum_uri(slot.range))) + self._add_super_properties(slot, slot_uri) def add_type(self, typ: TypeDefinition) -> None: type_uri = self._type_uri(typ.name) @@ -1688,10 +1839,11 @@ def _permissible_value_uri( def slot_owl_type(self, slot: SlotDefinition) -> URIRef: sv = self.schemaview - if slot.implements: - for t in ["owl:AnnotationProperty", "owl:ObjectProperty", "owl:DatatypeProperty"]: - if t in slot.implements: - return OWL[t.replace("owl:", "")] + role = self._declared_property_role(slot) + if role is not None: + return role + if self._prop_uri(slot) in self._BUILTIN_ANNOTATION_PROPERTIES: + return OWL.AnnotationProperty if slot.range is None: slot_range = self.schemaview.schema.default_range else: diff --git a/tests/linkml/test_base/__snapshots__/meta.owl b/tests/linkml/test_base/__snapshots__/meta.owl index 2f02afafd7..a8726d7570 100644 --- a/tests/linkml/test_base/__snapshots__/meta.owl +++ b/tests/linkml/test_base/__snapshots__/meta.owl @@ -19,6 +19,9 @@ @prefix linkml: . @prefix vann1: . @prefix schema1: . +dcterms:conformsTo a owl:AnnotationProperty . +dcterms:license a owl:AnnotationProperty . +dcterms:title a owl:AnnotationProperty . rdf:object a owl:Class , linkml:RelationalRoleEnum ; rdfs:label "OBJECT" ; rdfs:subClassOf linkml:RelationalRoleEnum ; @@ -34,18 +37,27 @@ rdf:subject a owl:Class , linkml:RelationalRoleEnum ; rdfs:subClassOf linkml:RelationalRoleEnum ; skos:definition "a slot with this role connects a relationship to its subject/source node" ; skos:exactMatch owl:annotatedSource . +skos:altLabel a owl:AnnotationProperty . skos:broaderMatch a owl:Class , linkml:AliasPredicateEnum ; rdfs:label "BROAD_SYNONYM" ; rdfs:subClassOf linkml:AliasPredicateEnum . -skos:exactMatch a owl:Class , linkml:AliasPredicateEnum ; +skos:closeMatch a owl:AnnotationProperty . +skos:definition a owl:AnnotationProperty . +skos:editorialNote a owl:AnnotationProperty . +skos:exactMatch a owl:AnnotationProperty , owl:Class , linkml:AliasPredicateEnum ; rdfs:label "EXACT_SYNONYM" ; rdfs:subClassOf linkml:AliasPredicateEnum . +skos:inScheme a owl:AnnotationProperty . +skos:mappingRelation a owl:AnnotationProperty . skos:narrowerMatch a owl:Class , linkml:AliasPredicateEnum ; rdfs:label "NARROW_SYNONYM" ; rdfs:subClassOf linkml:AliasPredicateEnum . +skos:note a owl:AnnotationProperty . +skos:prefLabel a owl:AnnotationProperty . skos:relatedMatch a owl:Class , linkml:AliasPredicateEnum ; rdfs:label "RELATED_SYNONYM" ; rdfs:subClassOf linkml:AliasPredicateEnum . +sh:order a owl:AnnotationProperty . linkml:AliasPredicateEnum a owl:Class , linkml:EnumDefinition ; rdfs:label "alias_predicate_enum" ; owl:unionOf _:c14n349 ; @@ -1633,6 +1645,7 @@ linkml:shared a owl:ObjectProperty , linkml:SlotDefinition ; skos:altLabel "inverse functional" , "many to one or many" ; skos:definition "If True, then the relationship between the slot domain and range is many to one or many to many" ; skos:inScheme linkml:meta . +linkml:simple_dict_value a owl:AnnotationProperty . linkml:singular_name a owl:ObjectProperty , linkml:SlotDefinition ; rdfs:domain linkml:SlotDefinition ; rdfs:label "singular_name" ; diff --git a/tests/linkml/test_biolink_model/__snapshots__/biolink.owl.ttl b/tests/linkml/test_biolink_model/__snapshots__/biolink.owl.ttl index 364bcd01db..123478b572 100644 --- a/tests/linkml/test_biolink_model/__snapshots__/biolink.owl.ttl +++ b/tests/linkml/test_biolink_model/__snapshots__/biolink.owl.ttl @@ -71,6 +71,19 @@ @prefix CTD: . @prefix schema1: . @prefix doi: . +dct:license a owl:AnnotationProperty . +pav:version a owl:AnnotationProperty . +skos:altLabel a owl:AnnotationProperty . +skos:broadMatch a owl:AnnotationProperty . +skos:closeMatch a owl:AnnotationProperty . +skos:definition a owl:AnnotationProperty . +skos:editorialNote a owl:AnnotationProperty . +skos:exactMatch a owl:AnnotationProperty . +skos:inScheme a owl:AnnotationProperty . +skos:mappingRelation a owl:AnnotationProperty . +skos:narrowMatch a owl:AnnotationProperty . +skos:note a owl:AnnotationProperty . +skos:relatedMatch a owl:AnnotationProperty . os:AllSomeInterpretation a owl:Class , biolink:LogicalInterpretationEnum ; rdfs:label "all_some" ; rdfs:subClassOf biolink:LogicalInterpretationEnum ; @@ -2883,6 +2896,7 @@ biolink:can_be_carried_out_by a owl:ObjectProperty , linkml:SlotDefinition ; rdfs:subPropertyOf biolink:actively_involves ; owl:inverseOf biolink:capable_of ; skos:inScheme . +biolink:canonical_predicate a owl:AnnotationProperty . biolink:capable_of a owl:ObjectProperty , linkml:SlotDefinition ; rdfs:domain biolink:NamedThing ; rdfs:label "capable of" ; @@ -3202,6 +3216,7 @@ biolink:decreases_response_to a owl:ObjectProperty , linkml:SlotDefinition ; skos:narrowMatch CTD:decreases_response_to_substance ; biolink:canonical_predicate true ; biolink:opposite_of "increases response to" . +biolink:denormalized a owl:AnnotationProperty . biolink:deprecated a owl:ObjectProperty , linkml:SlotDefinition ; rdfs:label "deprecated" ; rdfs:range linkml:Boolean ; @@ -5902,6 +5917,7 @@ biolink:xref a owl:ObjectProperty , linkml:SlotDefinition ; skos:definition "A database cross reference or alternative identifier for a NamedThing or edge between two NamedThings. This property should point to a database record or webpage that supports the existence of the edge, or gives more detail about the edge. This property can be used on a node or edge to provide multiple URIs or CURIE cross references." ; skos:inScheme ; skos:narrowMatch gff3:Dbxref , gpi:DB_Xrefs . +linkml:permissible_values a owl:AnnotationProperty . linkml:topValue a owl:DatatypeProperty ; rdfs:label "value" . _:c14n0 a owl:Restriction ; diff --git a/tests/linkml/test_generators/test_owl_annotation_properties.py b/tests/linkml/test_generators/test_owl_annotation_properties.py new file mode 100644 index 0000000000..6d026c64ef --- /dev/null +++ b/tests/linkml/test_generators/test_owl_annotation_properties.py @@ -0,0 +1,228 @@ +"""gen-owl gives annotation properties their OWL role and no logical axioms. + +A slot that implements ``owl:AnnotationProperty``, or whose property IRI is one of +the built-in annotation properties of OWL 2 (Structural Specification §5.5), is +declared as such and produces no class restriction (annotation properties have no +logical meaning, §10.1). Its domain, range and super-properties are emitted as the +annotation axioms of §10.2; logical uses are errors. +""" + +from pathlib import Path + +import pytest +import yaml +from rdflib import DCTERMS, OWL, RDF, RDFS, XSD, Graph, URIRef + +from linkml.generators.owlgen import OwlSchemaGenerator + +EX = "https://example.org/m/" +EXAMPLE = Path(__file__).resolve().parents[3] / "docs/examples/owl-dcmi-agent-annotations.yaml" + +SCHEMA = """ +id: https://example.org/m +name: m +prefixes: + ex: https://example.org/m/ + linkml: https://w3id.org/linkml/ + owl: http://www.w3.org/2002/07/owl# + rdfs: http://www.w3.org/2000/01/rdf-schema# + dcterms: http://purl.org/dc/terms/ +imports: [linkml:types] +default_prefix: ex +default_range: string +slots: + comment: + slot_uri: rdfs:comment + # an annotation property with native URIs too, so that note may refine it + implements: [owl:AnnotationProperty] + note: + slot_uri: dcterms:description + range: string + implements: [owl:AnnotationProperty] + is_a: comment + domain: Thing + ref: + range: nodeidentifier + implements: [owl:AnnotationProperty] +classes: + Thing: + slots: [comment, note, ref] + attributes: + name: + slot_uri: rdfs:label + plain: + range: string +""" + + +def _graph(source: str | Path, **kwargs) -> Graph: + """The generated ontology, parsed back from Turtle.""" + generator = OwlSchemaGenerator(source, type_objects=False, metaclasses=False, **kwargs) + return Graph().parse(data=generator.serialize(), format="turtle") + + +def _restrictions(graph: Graph, prop: URIRef) -> int: + return len(list(graph.subjects(OWL.onProperty, prop))) + + +@pytest.mark.parametrize("prop", ["note", "ref"]) +def test_declared_annotation_property_has_no_restrictions(prop): + """``implements: [owl:AnnotationProperty]`` gives the declaration and suppresses the restrictions.""" + graph = _graph(SCHEMA) + assert set(graph.objects(URIRef(EX + prop), RDF.type)) == {OWL.AnnotationProperty} + assert _restrictions(graph, URIRef(EX + prop)) == 0 + assert set(graph.objects(URIRef(EX + "plain"), RDF.type)) == {OWL.DatatypeProperty} + assert _restrictions(graph, URIRef(EX + "plain")) > 0 + + +@pytest.mark.parametrize( + "native,expected", + [ + pytest.param(True, {URIRef(EX + "name"): OWL.DatatypeProperty}, id="native"), + pytest.param(False, {RDFS.label: None, RDFS.comment: None}, id="mapped"), + ], +) +def test_builtin_annotation_property_iri(native, expected): + """A slot emitted with a built-in annotation property IRI gets no declaration and no restriction. + + With native URIs the local property is an ordinary data property, as before. + """ + graph = _graph(SCHEMA, use_native_uris=native) + for prop, role in expected.items(): + assert set(graph.objects(prop, RDF.type)) == ({role} if role else set()) + assert (_restrictions(graph, prop) > 0) is (role is not None) + if role is None: + assert not set(graph.objects(prop, RDFS.range)) | set(graph.objects(prop, RDFS.domain)) + + +def test_annotation_property_axioms(): + """Domain, a datatype range and super-properties are kept; a node type gives no range (OWL 2 §10.2).""" + graph = _graph(SCHEMA, use_native_uris=False) + note = URIRef("http://purl.org/dc/terms/description") + assert set(graph.objects(note, RDFS.domain)) == {URIRef(EX + "Thing")} + assert set(graph.objects(note, RDFS.range)) == {XSD.string} + assert set(graph.objects(note, RDFS.subPropertyOf)) == {RDFS.comment} + assert set(graph.objects(URIRef(EX + "ref"), RDFS.range)) == set() + + +def test_declared_annotation_predicate_is_declared(): + """A metaclass slot with the annotation role declares the predicate its annotations use.""" + graph = _graph(EXAMPLE) + ontology = URIRef("https://example.org/model") + assert set(graph.objects(ontology, DCTERMS.creator)) == {URIRef("https://orcid.org/0000-0002-1825-0097")} + for predicate in (DCTERMS.creator, DCTERMS.rights): + assert (predicate, RDF.type, OWL.AnnotationProperty) in graph + assert _restrictions(graph, predicate) == 0 + assert not any(obj in {OWL.AnnotationProperty} for obj in graph.objects(None, OWL.onProperty)) + + +def test_contradictory_roles_fail(): + schema = yaml.safe_load(SCHEMA) + schema["slots"]["note"]["implements"] = ["owl:AnnotationProperty", "owl:DatatypeProperty"] + with pytest.raises(ValueError, match="more than one OWL property role"): + _graph(yaml.safe_dump(schema)) + + +@pytest.mark.parametrize( + "change,message", + [ + pytest.param({"slots": {"note": {"symmetric": True}}}, "cannot declare symmetric", id="characteristic"), + pytest.param({"slots": {"note": {"inverse": "comment"}}}, "cannot declare inverse", id="inverse"), + pytest.param( + {"classes": {"Thing": {"unique_keys": {"k": {"unique_key_slots": ["note"]}}}}}, "unique key", id="key" + ), + pytest.param( + {"slots": {"ref": {"designates_type": True, "range": "string"}}}, "type designator", id="designator" + ), + pytest.param( + { + "classes": { + "Thing": {"rules": [{"preconditions": {"slot_conditions": {"note": {"equals_string": "x"}}}}]} + } + }, + "condition of a class expression", + id="rule", + ), + ], +) +def test_logical_use_of_annotation_property_fails(change, message): + """An annotation property cannot carry object property axioms or appear in a class expression.""" + schema = yaml.safe_load(SCHEMA) + for section, elements in change.items(): + for name, fields in elements.items(): + schema[section][name].update(fields) + with pytest.raises(ValueError, match=message): + _graph(yaml.safe_dump(schema)) + + +@pytest.mark.parametrize( + "child,parent,message", + [ + pytest.param( + {"implements": ["owl:AnnotationProperty"]}, + {}, + "is an annotation property but its super-property 'base' is not one", + id="annotation-under-data", + ), + pytest.param( + {}, + {"implements": ["owl:AnnotationProperty"]}, + "is not an annotation property but its super-property 'base' is one", + id="data-under-annotation", + ), + ], +) +@pytest.mark.parametrize("link", ["is_a", "mixins"]) +def test_super_property_with_another_role_fails(child, parent, message, link): + """A sub-property axiom relates properties of one role (OWL 2 §9.3.1, §10.2.1).""" + schema = yaml.safe_load(SCHEMA) + schema["slots"]["base"] = {"range": "string", **parent} + schema["slots"]["derived"] = {"range": "string", **child, link: "base" if link == "is_a" else ["base"]} + schema["classes"]["Thing"]["slots"].append("derived") + with pytest.raises(ValueError, match=message): + _graph(yaml.safe_dump(schema)) + + +@pytest.mark.parametrize( + "native,sub,sup", + [ + pytest.param(True, URIRef(EX + "note"), URIRef(EX + "comment"), id="native"), + pytest.param(False, URIRef("http://purl.org/dc/terms/description"), RDFS.comment, id="mapped"), + ], +) +def test_super_property_with_the_same_role_is_emitted(native, sub, sup): + """An annotation property under an annotation property keeps its super-property, + as ``note`` under ``comment``, which is ``rdfs:comment`` with mapped URIs.""" + graph = _graph(SCHEMA, use_native_uris=native) + assert (sub, RDFS.subPropertyOf, sup) in graph + assert (sub, RDF.type, OWL.AnnotationProperty) in graph + + +@pytest.mark.parametrize("native", [True, False]) +def test_every_predicate_without_a_role_is_an_annotation_property(native): + """OWL 2 DL requires each property IRI to be declared (§5.8.1): the predicates gen-owl writes + for metadata are annotation properties, logical properties keep their role, and the reserved + vocabulary (§2.4) gets no declaration.""" + schema = yaml.safe_load(SCHEMA) + schema["title"] = "A schema" + schema["classes"]["Thing"]["description"] = "A thing." + schema["classes"]["Thing"]["exact_mappings"] = ["dcterms:Agent"] + graph = _graph(yaml.safe_dump(schema), use_native_uris=native) + skos = "http://www.w3.org/2004/02/skos/core#" + for metadata in ( + DCTERMS.title, + URIRef(skos + "definition"), + URIRef(skos + "inScheme"), + URIRef(skos + "exactMatch"), + ): + assert (metadata, RDF.type, OWL.AnnotationProperty) in graph, metadata + restricted = set(graph.objects(None, OWL.onProperty)) + assert restricted, "the schema has class restrictions" + for prop in restricted: + assert (prop, RDF.type, OWL.AnnotationProperty) not in graph + roles = (OWL.AnnotationProperty, OWL.ObjectProperty, OWL.DatatypeProperty) + for predicate in set(graph.predicates()): + if str(predicate).startswith((str(RDF), str(RDFS), str(XSD), str(OWL))): + assert not any((predicate, RDF.type, role) in graph for role in roles), predicate + else: + assert any((predicate, RDF.type, role) in graph for role in roles), predicate diff --git a/tests/linkml/test_issues/__snapshots__/issue_163.owl b/tests/linkml/test_issues/__snapshots__/issue_163.owl index f89ca116ea..36301fbe5f 100644 --- a/tests/linkml/test_issues/__snapshots__/issue_163.owl +++ b/tests/linkml/test_issues/__snapshots__/issue_163.owl @@ -60,6 +60,7 @@ ex:unit a owl:ObjectProperty , linkml:SlotDefinition ; rdfs:label "unit" ; rdfs:range ex:Unitcode ; skos:inScheme ex:sample\/example1 . +skos:inScheme a owl:AnnotationProperty . linkml:topValue a owl:DatatypeProperty ; rdfs:label "value" . _:c14n0 a owl:Restriction ; diff --git a/tests/linkml/test_issues/__snapshots__/issue_163b.owl b/tests/linkml/test_issues/__snapshots__/issue_163b.owl index c76655e4af..66d5433a34 100644 --- a/tests/linkml/test_issues/__snapshots__/issue_163b.owl +++ b/tests/linkml/test_issues/__snapshots__/issue_163b.owl @@ -60,6 +60,7 @@ ex:unit a owl:ObjectProperty , linkml:SlotDefinition ; skos:inScheme . a owl:Ontology ; rdfs:label "example1" . +skos:inScheme a owl:AnnotationProperty . linkml:topValue a owl:DatatypeProperty ; rdfs:label "value" . _:c14n0 a owl:Restriction ; diff --git a/tests/linkml/test_issues/__snapshots__/issue_163c.owl b/tests/linkml/test_issues/__snapshots__/issue_163c.owl index 2e4506b525..71a3272e62 100644 --- a/tests/linkml/test_issues/__snapshots__/issue_163c.owl +++ b/tests/linkml/test_issues/__snapshots__/issue_163c.owl @@ -27,6 +27,9 @@ ex:s2 a owl:ObjectProperty , linkml:SlotDefinition ; skos:prefLabel "source" . ex:sample\/example163c a owl:Ontology ; rdfs:label "example163c" . +skos:definition a owl:AnnotationProperty . +skos:inScheme a owl:AnnotationProperty . +skos:prefLabel a owl:AnnotationProperty . _:c14n0 a owl:Restriction ; owl:minCardinality 0 ; owl:onProperty ex:s1 . diff --git a/tests/linkml/test_issues/__snapshots__/issue_290.owl b/tests/linkml/test_issues/__snapshots__/issue_290.owl index 235810e989..faf89ace5e 100644 --- a/tests/linkml/test_issues/__snapshots__/issue_290.owl +++ b/tests/linkml/test_issues/__snapshots__/issue_290.owl @@ -26,3 +26,5 @@ ex:C a owl:Class, a owl:Ontology ; rdfs:label "example.org" . +skos:inScheme a owl:AnnotationProperty . + diff --git a/tests/linkml/test_issues/__snapshots__/linkml_issue_384.owl.txt b/tests/linkml/test_issues/__snapshots__/linkml_issue_384.owl.txt index 0dfbd8ea82..5060f324a4 100644 --- a/tests/linkml/test_issues/__snapshots__/linkml_issue_384.owl.txt +++ b/tests/linkml/test_issues/__snapshots__/linkml_issue_384.owl.txt @@ -1,4 +1,7 @@ # --- linkml_issue_384-False-False.owl --- + . + . + . . "GeoAge" . _:c14n10 . @@ -177,6 +180,9 @@ _:c14n9 "1"^^ . # --- linkml_issue_384-True-True.owl --- + . + . + . . . "GeoAge" . diff --git a/tests/linkml/test_issues/__snapshots__/linkml_issue_387.owl b/tests/linkml/test_issues/__snapshots__/linkml_issue_387.owl index ebc4c29f9f..681537b7d9 100644 --- a/tests/linkml/test_issues/__snapshots__/linkml_issue_387.owl +++ b/tests/linkml/test_issues/__snapshots__/linkml_issue_387.owl @@ -26,3 +26,5 @@ ex:a a owl:DatatypeProperty ; rdfs:label "a" ; skos:inScheme . +skos:inScheme a owl:AnnotationProperty . + diff --git a/tests/linkml/test_scripts/__snapshots__/genowl/meta_owl.jsonld b/tests/linkml/test_scripts/__snapshots__/genowl/meta_owl.jsonld index c1db60e45a..e8a21fcdbc 100644 --- a/tests/linkml/test_scripts/__snapshots__/genowl/meta_owl.jsonld +++ b/tests/linkml/test_scripts/__snapshots__/genowl/meta_owl.jsonld @@ -7397,5 +7397,89 @@ "@value": "spiteful" } ] + }, + { + "@id": "http://purl.org/dc/terms/title", + "@type": [ + "http://www.w3.org/2002/07/owl#AnnotationProperty" + ] + }, + { + "@id": "http://www.w3.org/2004/02/skos/core#altLabel", + "@type": [ + "http://www.w3.org/2002/07/owl#AnnotationProperty" + ] + }, + { + "@id": "http://www.w3.org/2004/02/skos/core#closeMatch", + "@type": [ + "http://www.w3.org/2002/07/owl#AnnotationProperty" + ] + }, + { + "@id": "http://www.w3.org/2004/02/skos/core#definition", + "@type": [ + "http://www.w3.org/2002/07/owl#AnnotationProperty" + ] + }, + { + "@id": "http://www.w3.org/2004/02/skos/core#exactMatch", + "@type": [ + "http://www.w3.org/2002/07/owl#AnnotationProperty" + ] + }, + { + "@id": "http://www.w3.org/2004/02/skos/core#inScheme", + "@type": [ + "http://www.w3.org/2002/07/owl#AnnotationProperty" + ] + }, + { + "@id": "http://www.w3.org/2004/02/skos/core#mappingRelation", + "@type": [ + "http://www.w3.org/2002/07/owl#AnnotationProperty" + ] + }, + { + "@id": "http://www.w3.org/ns/shacl#order", + "@type": [ + "http://www.w3.org/2002/07/owl#AnnotationProperty" + ] + }, + { + "@id": "https://w3id.org/biolink/opposite", + "@type": [ + "http://www.w3.org/2002/07/owl#AnnotationProperty" + ] + }, + { + "@id": "https://w3id.org/linkml/permissible_values", + "@type": [ + "http://www.w3.org/2002/07/owl#AnnotationProperty" + ] + }, + { + "@id": "https://w3id.org/linkml/tests/kitchen_sink/fallible", + "@type": [ + "http://www.w3.org/2002/07/owl#AnnotationProperty" + ] + }, + { + "@id": "https://w3id.org/linkml/tests/kitchen_sink/opinions", + "@type": [ + "http://www.w3.org/2002/07/owl#AnnotationProperty" + ] + }, + { + "@id": "https://w3id.org/linkml/tests/kitchen_sink/resting", + "@type": [ + "http://www.w3.org/2002/07/owl#AnnotationProperty" + ] + }, + { + "@id": "https://w3id.org/linkml/tests/kitchen_sink/viewer", + "@type": [ + "http://www.w3.org/2002/07/owl#AnnotationProperty" + ] } ] diff --git a/tests/linkml/test_scripts/__snapshots__/genowl/meta_owl.n3 b/tests/linkml/test_scripts/__snapshots__/genowl/meta_owl.n3 index 82096ace4e..d56e354000 100644 --- a/tests/linkml/test_scripts/__snapshots__/genowl/meta_owl.n3 +++ b/tests/linkml/test_scripts/__snapshots__/genowl/meta_owl.n3 @@ -11,9 +11,17 @@ @prefix dcterms: . @prefix linkml: . @prefix schema1: . +dcterms:title a owl:AnnotationProperty . xsd:integer a rdfs:Datatype . xsd:string a rdfs:Datatype ; owl:equivalentClass _:c14n173 . +skos:altLabel a owl:AnnotationProperty . +skos:closeMatch a owl:AnnotationProperty . +skos:definition a owl:AnnotationProperty . +skos:exactMatch a owl:AnnotationProperty . +skos:inScheme a owl:AnnotationProperty . +skos:mappingRelation a owl:AnnotationProperty . +sh:order a owl:AnnotationProperty . bizcodes:001 a owl:Class ; rdfs:label "HIRE" ; rdfs:subClassOf ks:EmploymentEventType ; @@ -30,6 +38,8 @@ bizcodes:004 a owl:Class ; rdfs:label "TRANSFER" ; rdfs:subClassOf ks:EmploymentEventType ; skos:definition "transfer internally" . +biolink:opposite a owl:AnnotationProperty . +linkml:permissible_values a owl:AnnotationProperty . linkml:tests\/core\/Activity a owl:Class ; rdfs:label "activity" ; rdfs:subClassOf _:c14n103 , _:c14n119 , _:c14n132 , _:c14n146 , _:c14n152 , _:c14n155 , _:c14n162 , _:c14n171 , _:c14n191 , _:c14n200 , _:c14n204 , _:c14n210 , _:c14n223 , _:c14n243 , _:c14n246 , _:c14n253 , _:c14n258 , _:c14n59 , _:c14n6 , _:c14n63 , _:c14n68 ; @@ -432,6 +442,7 @@ ks:employed_at a owl:ObjectProperty ; rdfs:label "employed at" ; rdfs:range ks:Company ; skos:inScheme linkml:tests\/kitchen_sink . +ks:fallible a owl:AnnotationProperty . ks:has_birth_event a owl:ObjectProperty ; rdfs:label "has birth event" ; rdfs:range ks:BirthEvent ; @@ -490,6 +501,7 @@ ks:metadata a owl:ObjectProperty ; ks:mixin_slot_I a owl:DatatypeProperty ; rdfs:label "mixin_slot_I" ; skos:inScheme linkml:tests\/kitchen_sink . +ks:opinions a owl:AnnotationProperty . ks:persons a owl:ObjectProperty ; rdfs:label "persons" ; skos:inScheme linkml:tests\/kitchen_sink . @@ -500,6 +512,7 @@ ks:procedure a owl:ObjectProperty ; ks:related_to a owl:DatatypeProperty ; rdfs:label "related to" ; skos:inScheme linkml:tests\/kitchen_sink . +ks:resting a owl:AnnotationProperty . ks:slot_with_space_1 a owl:DatatypeProperty ; rdfs:label "slot with space 1" ; skos:inScheme linkml:tests\/kitchen_sink . @@ -534,6 +547,7 @@ ks:tree_slot_C a owl:DatatypeProperty ; ks:type a owl:DatatypeProperty ; rdfs:label "type" ; skos:inScheme linkml:tests\/kitchen_sink . +ks:viewer a owl:AnnotationProperty . _:c14n0 rdf:first bizcodes:002 ; rdf:rest _:c14n43 . _:c14n1 a owl:Restriction ; diff --git a/tests/linkml/test_scripts/__snapshots__/genowl/meta_owl.ttl b/tests/linkml/test_scripts/__snapshots__/genowl/meta_owl.ttl index 82096ace4e..d56e354000 100644 --- a/tests/linkml/test_scripts/__snapshots__/genowl/meta_owl.ttl +++ b/tests/linkml/test_scripts/__snapshots__/genowl/meta_owl.ttl @@ -11,9 +11,17 @@ @prefix dcterms: . @prefix linkml: . @prefix schema1: . +dcterms:title a owl:AnnotationProperty . xsd:integer a rdfs:Datatype . xsd:string a rdfs:Datatype ; owl:equivalentClass _:c14n173 . +skos:altLabel a owl:AnnotationProperty . +skos:closeMatch a owl:AnnotationProperty . +skos:definition a owl:AnnotationProperty . +skos:exactMatch a owl:AnnotationProperty . +skos:inScheme a owl:AnnotationProperty . +skos:mappingRelation a owl:AnnotationProperty . +sh:order a owl:AnnotationProperty . bizcodes:001 a owl:Class ; rdfs:label "HIRE" ; rdfs:subClassOf ks:EmploymentEventType ; @@ -30,6 +38,8 @@ bizcodes:004 a owl:Class ; rdfs:label "TRANSFER" ; rdfs:subClassOf ks:EmploymentEventType ; skos:definition "transfer internally" . +biolink:opposite a owl:AnnotationProperty . +linkml:permissible_values a owl:AnnotationProperty . linkml:tests\/core\/Activity a owl:Class ; rdfs:label "activity" ; rdfs:subClassOf _:c14n103 , _:c14n119 , _:c14n132 , _:c14n146 , _:c14n152 , _:c14n155 , _:c14n162 , _:c14n171 , _:c14n191 , _:c14n200 , _:c14n204 , _:c14n210 , _:c14n223 , _:c14n243 , _:c14n246 , _:c14n253 , _:c14n258 , _:c14n59 , _:c14n6 , _:c14n63 , _:c14n68 ; @@ -432,6 +442,7 @@ ks:employed_at a owl:ObjectProperty ; rdfs:label "employed at" ; rdfs:range ks:Company ; skos:inScheme linkml:tests\/kitchen_sink . +ks:fallible a owl:AnnotationProperty . ks:has_birth_event a owl:ObjectProperty ; rdfs:label "has birth event" ; rdfs:range ks:BirthEvent ; @@ -490,6 +501,7 @@ ks:metadata a owl:ObjectProperty ; ks:mixin_slot_I a owl:DatatypeProperty ; rdfs:label "mixin_slot_I" ; skos:inScheme linkml:tests\/kitchen_sink . +ks:opinions a owl:AnnotationProperty . ks:persons a owl:ObjectProperty ; rdfs:label "persons" ; skos:inScheme linkml:tests\/kitchen_sink . @@ -500,6 +512,7 @@ ks:procedure a owl:ObjectProperty ; ks:related_to a owl:DatatypeProperty ; rdfs:label "related to" ; skos:inScheme linkml:tests\/kitchen_sink . +ks:resting a owl:AnnotationProperty . ks:slot_with_space_1 a owl:DatatypeProperty ; rdfs:label "slot with space 1" ; skos:inScheme linkml:tests\/kitchen_sink . @@ -534,6 +547,7 @@ ks:tree_slot_C a owl:DatatypeProperty ; ks:type a owl:DatatypeProperty ; rdfs:label "type" ; skos:inScheme linkml:tests\/kitchen_sink . +ks:viewer a owl:AnnotationProperty . _:c14n0 rdf:first bizcodes:002 ; rdf:rest _:c14n43 . _:c14n1 a owl:Restriction ; diff --git a/tests/linkml/test_scripts/__snapshots__/genowl/meta_owl_custom_enum_separator.jsonld b/tests/linkml/test_scripts/__snapshots__/genowl/meta_owl_custom_enum_separator.jsonld index e7ade20fd8..26995c142f 100644 --- a/tests/linkml/test_scripts/__snapshots__/genowl/meta_owl_custom_enum_separator.jsonld +++ b/tests/linkml/test_scripts/__snapshots__/genowl/meta_owl_custom_enum_separator.jsonld @@ -7397,5 +7397,89 @@ "@id": "https://w3id.org/linkml/tests/kitchen_sink/married_to" } ] + }, + { + "@id": "http://purl.org/dc/terms/title", + "@type": [ + "http://www.w3.org/2002/07/owl#AnnotationProperty" + ] + }, + { + "@id": "http://www.w3.org/2004/02/skos/core#altLabel", + "@type": [ + "http://www.w3.org/2002/07/owl#AnnotationProperty" + ] + }, + { + "@id": "http://www.w3.org/2004/02/skos/core#closeMatch", + "@type": [ + "http://www.w3.org/2002/07/owl#AnnotationProperty" + ] + }, + { + "@id": "http://www.w3.org/2004/02/skos/core#definition", + "@type": [ + "http://www.w3.org/2002/07/owl#AnnotationProperty" + ] + }, + { + "@id": "http://www.w3.org/2004/02/skos/core#exactMatch", + "@type": [ + "http://www.w3.org/2002/07/owl#AnnotationProperty" + ] + }, + { + "@id": "http://www.w3.org/2004/02/skos/core#inScheme", + "@type": [ + "http://www.w3.org/2002/07/owl#AnnotationProperty" + ] + }, + { + "@id": "http://www.w3.org/2004/02/skos/core#mappingRelation", + "@type": [ + "http://www.w3.org/2002/07/owl#AnnotationProperty" + ] + }, + { + "@id": "http://www.w3.org/ns/shacl#order", + "@type": [ + "http://www.w3.org/2002/07/owl#AnnotationProperty" + ] + }, + { + "@id": "https://w3id.org/biolink/opposite", + "@type": [ + "http://www.w3.org/2002/07/owl#AnnotationProperty" + ] + }, + { + "@id": "https://w3id.org/linkml/permissible_values", + "@type": [ + "http://www.w3.org/2002/07/owl#AnnotationProperty" + ] + }, + { + "@id": "https://w3id.org/linkml/tests/kitchen_sink/fallible", + "@type": [ + "http://www.w3.org/2002/07/owl#AnnotationProperty" + ] + }, + { + "@id": "https://w3id.org/linkml/tests/kitchen_sink/opinions", + "@type": [ + "http://www.w3.org/2002/07/owl#AnnotationProperty" + ] + }, + { + "@id": "https://w3id.org/linkml/tests/kitchen_sink/resting", + "@type": [ + "http://www.w3.org/2002/07/owl#AnnotationProperty" + ] + }, + { + "@id": "https://w3id.org/linkml/tests/kitchen_sink/viewer", + "@type": [ + "http://www.w3.org/2002/07/owl#AnnotationProperty" + ] } ] diff --git a/tests/linkml/test_scripts/__snapshots__/genowl/meta_owl_custom_enum_separator.n3 b/tests/linkml/test_scripts/__snapshots__/genowl/meta_owl_custom_enum_separator.n3 index 82096ace4e..d56e354000 100644 --- a/tests/linkml/test_scripts/__snapshots__/genowl/meta_owl_custom_enum_separator.n3 +++ b/tests/linkml/test_scripts/__snapshots__/genowl/meta_owl_custom_enum_separator.n3 @@ -11,9 +11,17 @@ @prefix dcterms: . @prefix linkml: . @prefix schema1: . +dcterms:title a owl:AnnotationProperty . xsd:integer a rdfs:Datatype . xsd:string a rdfs:Datatype ; owl:equivalentClass _:c14n173 . +skos:altLabel a owl:AnnotationProperty . +skos:closeMatch a owl:AnnotationProperty . +skos:definition a owl:AnnotationProperty . +skos:exactMatch a owl:AnnotationProperty . +skos:inScheme a owl:AnnotationProperty . +skos:mappingRelation a owl:AnnotationProperty . +sh:order a owl:AnnotationProperty . bizcodes:001 a owl:Class ; rdfs:label "HIRE" ; rdfs:subClassOf ks:EmploymentEventType ; @@ -30,6 +38,8 @@ bizcodes:004 a owl:Class ; rdfs:label "TRANSFER" ; rdfs:subClassOf ks:EmploymentEventType ; skos:definition "transfer internally" . +biolink:opposite a owl:AnnotationProperty . +linkml:permissible_values a owl:AnnotationProperty . linkml:tests\/core\/Activity a owl:Class ; rdfs:label "activity" ; rdfs:subClassOf _:c14n103 , _:c14n119 , _:c14n132 , _:c14n146 , _:c14n152 , _:c14n155 , _:c14n162 , _:c14n171 , _:c14n191 , _:c14n200 , _:c14n204 , _:c14n210 , _:c14n223 , _:c14n243 , _:c14n246 , _:c14n253 , _:c14n258 , _:c14n59 , _:c14n6 , _:c14n63 , _:c14n68 ; @@ -432,6 +442,7 @@ ks:employed_at a owl:ObjectProperty ; rdfs:label "employed at" ; rdfs:range ks:Company ; skos:inScheme linkml:tests\/kitchen_sink . +ks:fallible a owl:AnnotationProperty . ks:has_birth_event a owl:ObjectProperty ; rdfs:label "has birth event" ; rdfs:range ks:BirthEvent ; @@ -490,6 +501,7 @@ ks:metadata a owl:ObjectProperty ; ks:mixin_slot_I a owl:DatatypeProperty ; rdfs:label "mixin_slot_I" ; skos:inScheme linkml:tests\/kitchen_sink . +ks:opinions a owl:AnnotationProperty . ks:persons a owl:ObjectProperty ; rdfs:label "persons" ; skos:inScheme linkml:tests\/kitchen_sink . @@ -500,6 +512,7 @@ ks:procedure a owl:ObjectProperty ; ks:related_to a owl:DatatypeProperty ; rdfs:label "related to" ; skos:inScheme linkml:tests\/kitchen_sink . +ks:resting a owl:AnnotationProperty . ks:slot_with_space_1 a owl:DatatypeProperty ; rdfs:label "slot with space 1" ; skos:inScheme linkml:tests\/kitchen_sink . @@ -534,6 +547,7 @@ ks:tree_slot_C a owl:DatatypeProperty ; ks:type a owl:DatatypeProperty ; rdfs:label "type" ; skos:inScheme linkml:tests\/kitchen_sink . +ks:viewer a owl:AnnotationProperty . _:c14n0 rdf:first bizcodes:002 ; rdf:rest _:c14n43 . _:c14n1 a owl:Restriction ; diff --git a/tests/linkml/test_scripts/__snapshots__/genowl/meta_owl_custom_enum_separator.ttl b/tests/linkml/test_scripts/__snapshots__/genowl/meta_owl_custom_enum_separator.ttl index 82096ace4e..d56e354000 100644 --- a/tests/linkml/test_scripts/__snapshots__/genowl/meta_owl_custom_enum_separator.ttl +++ b/tests/linkml/test_scripts/__snapshots__/genowl/meta_owl_custom_enum_separator.ttl @@ -11,9 +11,17 @@ @prefix dcterms: . @prefix linkml: . @prefix schema1: . +dcterms:title a owl:AnnotationProperty . xsd:integer a rdfs:Datatype . xsd:string a rdfs:Datatype ; owl:equivalentClass _:c14n173 . +skos:altLabel a owl:AnnotationProperty . +skos:closeMatch a owl:AnnotationProperty . +skos:definition a owl:AnnotationProperty . +skos:exactMatch a owl:AnnotationProperty . +skos:inScheme a owl:AnnotationProperty . +skos:mappingRelation a owl:AnnotationProperty . +sh:order a owl:AnnotationProperty . bizcodes:001 a owl:Class ; rdfs:label "HIRE" ; rdfs:subClassOf ks:EmploymentEventType ; @@ -30,6 +38,8 @@ bizcodes:004 a owl:Class ; rdfs:label "TRANSFER" ; rdfs:subClassOf ks:EmploymentEventType ; skos:definition "transfer internally" . +biolink:opposite a owl:AnnotationProperty . +linkml:permissible_values a owl:AnnotationProperty . linkml:tests\/core\/Activity a owl:Class ; rdfs:label "activity" ; rdfs:subClassOf _:c14n103 , _:c14n119 , _:c14n132 , _:c14n146 , _:c14n152 , _:c14n155 , _:c14n162 , _:c14n171 , _:c14n191 , _:c14n200 , _:c14n204 , _:c14n210 , _:c14n223 , _:c14n243 , _:c14n246 , _:c14n253 , _:c14n258 , _:c14n59 , _:c14n6 , _:c14n63 , _:c14n68 ; @@ -432,6 +442,7 @@ ks:employed_at a owl:ObjectProperty ; rdfs:label "employed at" ; rdfs:range ks:Company ; skos:inScheme linkml:tests\/kitchen_sink . +ks:fallible a owl:AnnotationProperty . ks:has_birth_event a owl:ObjectProperty ; rdfs:label "has birth event" ; rdfs:range ks:BirthEvent ; @@ -490,6 +501,7 @@ ks:metadata a owl:ObjectProperty ; ks:mixin_slot_I a owl:DatatypeProperty ; rdfs:label "mixin_slot_I" ; skos:inScheme linkml:tests\/kitchen_sink . +ks:opinions a owl:AnnotationProperty . ks:persons a owl:ObjectProperty ; rdfs:label "persons" ; skos:inScheme linkml:tests\/kitchen_sink . @@ -500,6 +512,7 @@ ks:procedure a owl:ObjectProperty ; ks:related_to a owl:DatatypeProperty ; rdfs:label "related to" ; skos:inScheme linkml:tests\/kitchen_sink . +ks:resting a owl:AnnotationProperty . ks:slot_with_space_1 a owl:DatatypeProperty ; rdfs:label "slot with space 1" ; skos:inScheme linkml:tests\/kitchen_sink . @@ -534,6 +547,7 @@ ks:tree_slot_C a owl:DatatypeProperty ; ks:type a owl:DatatypeProperty ; rdfs:label "type" ; skos:inScheme linkml:tests\/kitchen_sink . +ks:viewer a owl:AnnotationProperty . _:c14n0 rdf:first bizcodes:002 ; rdf:rest _:c14n43 . _:c14n1 a owl:Restriction ; diff --git a/tests/linkml/test_utils/__snapshots__/owl1.owl b/tests/linkml/test_utils/__snapshots__/owl1.owl index e6f9cf30b6..fc7007a731 100644 --- a/tests/linkml/test_utils/__snapshots__/owl1.owl +++ b/tests/linkml/test_utils/__snapshots__/owl1.owl @@ -36,6 +36,7 @@ meta:slotzeromany a owl:ObjectProperty , linkml:SlotDefinition ; skos:inScheme . a owl:Ontology ; rdfs:label "owl1" . +skos:inScheme a owl:AnnotationProperty . linkml:topValue a owl:DatatypeProperty ; rdfs:label "value" . _:c14n0 a owl:Restriction ; diff --git a/tests/linkml/test_utils/__snapshots__/owl2.owl b/tests/linkml/test_utils/__snapshots__/owl2.owl index 767ce208fe..e13fa67a64 100644 --- a/tests/linkml/test_utils/__snapshots__/owl2.owl +++ b/tests/linkml/test_utils/__snapshots__/owl2.owl @@ -36,6 +36,7 @@ meta:objslot a owl:ObjectProperty , linkml:SlotDefinition ; skos:inScheme . a owl:Ontology ; rdfs:label "owl2" . +skos:inScheme a owl:AnnotationProperty . linkml:topValue a owl:DatatypeProperty ; rdfs:label "value" . _:c14n0 a owl:Restriction ;