pyobo 0.11.2__py3-none-any.whl → 0.12.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. pyobo/.DS_Store +0 -0
  2. pyobo/__init__.py +95 -20
  3. pyobo/__main__.py +0 -0
  4. pyobo/api/__init__.py +81 -10
  5. pyobo/api/alts.py +52 -42
  6. pyobo/api/combine.py +39 -0
  7. pyobo/api/edges.py +68 -0
  8. pyobo/api/hierarchy.py +231 -203
  9. pyobo/api/metadata.py +14 -19
  10. pyobo/api/names.py +207 -127
  11. pyobo/api/properties.py +117 -117
  12. pyobo/api/relations.py +68 -94
  13. pyobo/api/species.py +24 -21
  14. pyobo/api/typedefs.py +11 -11
  15. pyobo/api/utils.py +66 -13
  16. pyobo/api/xrefs.py +107 -114
  17. pyobo/cli/__init__.py +0 -0
  18. pyobo/cli/cli.py +35 -50
  19. pyobo/cli/database.py +210 -160
  20. pyobo/cli/database_utils.py +155 -0
  21. pyobo/cli/lookup.py +163 -195
  22. pyobo/cli/utils.py +19 -6
  23. pyobo/constants.py +102 -3
  24. pyobo/getters.py +209 -191
  25. pyobo/gilda_utils.py +52 -250
  26. pyobo/identifier_utils/__init__.py +33 -0
  27. pyobo/identifier_utils/api.py +305 -0
  28. pyobo/identifier_utils/preprocessing.json +873 -0
  29. pyobo/identifier_utils/preprocessing.py +27 -0
  30. pyobo/identifier_utils/relations/__init__.py +8 -0
  31. pyobo/identifier_utils/relations/api.py +162 -0
  32. pyobo/identifier_utils/relations/data.json +5824 -0
  33. pyobo/identifier_utils/relations/data_owl.json +57 -0
  34. pyobo/identifier_utils/relations/data_rdf.json +1 -0
  35. pyobo/identifier_utils/relations/data_rdfs.json +7 -0
  36. pyobo/mocks.py +9 -6
  37. pyobo/ner/__init__.py +9 -0
  38. pyobo/ner/api.py +72 -0
  39. pyobo/ner/normalizer.py +33 -0
  40. pyobo/obographs.py +48 -40
  41. pyobo/plugins.py +5 -4
  42. pyobo/py.typed +0 -0
  43. pyobo/reader.py +1354 -395
  44. pyobo/reader_utils.py +155 -0
  45. pyobo/resource_utils.py +42 -22
  46. pyobo/resources/__init__.py +0 -0
  47. pyobo/resources/goc.py +75 -0
  48. pyobo/resources/goc.tsv +188 -0
  49. pyobo/resources/ncbitaxon.py +4 -5
  50. pyobo/resources/ncbitaxon.tsv.gz +0 -0
  51. pyobo/resources/ro.py +3 -2
  52. pyobo/resources/ro.tsv +0 -0
  53. pyobo/resources/so.py +0 -0
  54. pyobo/resources/so.tsv +0 -0
  55. pyobo/sources/README.md +12 -8
  56. pyobo/sources/__init__.py +52 -29
  57. pyobo/sources/agrovoc.py +0 -0
  58. pyobo/sources/antibodyregistry.py +11 -12
  59. pyobo/sources/bigg/__init__.py +13 -0
  60. pyobo/sources/bigg/bigg_compartment.py +81 -0
  61. pyobo/sources/bigg/bigg_metabolite.py +229 -0
  62. pyobo/sources/bigg/bigg_model.py +46 -0
  63. pyobo/sources/bigg/bigg_reaction.py +77 -0
  64. pyobo/sources/biogrid.py +1 -2
  65. pyobo/sources/ccle.py +7 -12
  66. pyobo/sources/cgnc.py +9 -6
  67. pyobo/sources/chebi.py +1 -1
  68. pyobo/sources/chembl/__init__.py +9 -0
  69. pyobo/sources/{chembl.py → chembl/chembl_compound.py} +13 -25
  70. pyobo/sources/chembl/chembl_target.py +160 -0
  71. pyobo/sources/civic_gene.py +55 -15
  72. pyobo/sources/clinicaltrials.py +160 -0
  73. pyobo/sources/complexportal.py +24 -24
  74. pyobo/sources/conso.py +14 -22
  75. pyobo/sources/cpt.py +0 -0
  76. pyobo/sources/credit.py +1 -9
  77. pyobo/sources/cvx.py +27 -5
  78. pyobo/sources/depmap.py +9 -12
  79. pyobo/sources/dictybase_gene.py +2 -7
  80. pyobo/sources/drugbank/__init__.py +9 -0
  81. pyobo/sources/{drugbank.py → drugbank/drugbank.py} +11 -16
  82. pyobo/sources/{drugbank_salt.py → drugbank/drugbank_salt.py} +3 -8
  83. pyobo/sources/drugcentral.py +17 -13
  84. pyobo/sources/expasy.py +31 -34
  85. pyobo/sources/famplex.py +13 -18
  86. pyobo/sources/flybase.py +8 -13
  87. pyobo/sources/gard.py +62 -0
  88. pyobo/sources/geonames/__init__.py +9 -0
  89. pyobo/sources/geonames/features.py +28 -0
  90. pyobo/sources/{geonames.py → geonames/geonames.py} +87 -26
  91. pyobo/sources/geonames/utils.py +115 -0
  92. pyobo/sources/gmt_utils.py +6 -7
  93. pyobo/sources/go.py +20 -13
  94. pyobo/sources/gtdb.py +154 -0
  95. pyobo/sources/gwascentral/__init__.py +9 -0
  96. pyobo/sources/{gwascentral_phenotype.py → gwascentral/gwascentral_phenotype.py} +5 -7
  97. pyobo/sources/{gwascentral_study.py → gwascentral/gwascentral_study.py} +1 -7
  98. pyobo/sources/hgnc/__init__.py +9 -0
  99. pyobo/sources/{hgnc.py → hgnc/hgnc.py} +56 -70
  100. pyobo/sources/{hgncgenefamily.py → hgnc/hgncgenefamily.py} +8 -18
  101. pyobo/sources/icd/__init__.py +9 -0
  102. pyobo/sources/{icd10.py → icd/icd10.py} +35 -37
  103. pyobo/sources/icd/icd11.py +148 -0
  104. pyobo/sources/{icd_utils.py → icd/icd_utils.py} +66 -20
  105. pyobo/sources/interpro.py +4 -9
  106. pyobo/sources/itis.py +0 -5
  107. pyobo/sources/kegg/__init__.py +0 -0
  108. pyobo/sources/kegg/api.py +16 -38
  109. pyobo/sources/kegg/genes.py +9 -20
  110. pyobo/sources/kegg/genome.py +1 -7
  111. pyobo/sources/kegg/pathway.py +9 -21
  112. pyobo/sources/mesh.py +58 -24
  113. pyobo/sources/mgi.py +3 -10
  114. pyobo/sources/mirbase/__init__.py +11 -0
  115. pyobo/sources/{mirbase.py → mirbase/mirbase.py} +8 -11
  116. pyobo/sources/{mirbase_constants.py → mirbase/mirbase_constants.py} +0 -0
  117. pyobo/sources/{mirbase_family.py → mirbase/mirbase_family.py} +4 -8
  118. pyobo/sources/{mirbase_mature.py → mirbase/mirbase_mature.py} +3 -7
  119. pyobo/sources/msigdb.py +74 -39
  120. pyobo/sources/ncbi/__init__.py +9 -0
  121. pyobo/sources/ncbi/ncbi_gc.py +162 -0
  122. pyobo/sources/{ncbigene.py → ncbi/ncbigene.py} +18 -19
  123. pyobo/sources/nih_reporter.py +60 -0
  124. pyobo/sources/nlm/__init__.py +9 -0
  125. pyobo/sources/nlm/nlm_catalog.py +48 -0
  126. pyobo/sources/nlm/nlm_publisher.py +36 -0
  127. pyobo/sources/nlm/utils.py +116 -0
  128. pyobo/sources/npass.py +6 -8
  129. pyobo/sources/omim_ps.py +11 -4
  130. pyobo/sources/pathbank.py +4 -8
  131. pyobo/sources/pfam/__init__.py +9 -0
  132. pyobo/sources/{pfam.py → pfam/pfam.py} +3 -8
  133. pyobo/sources/{pfam_clan.py → pfam/pfam_clan.py} +2 -7
  134. pyobo/sources/pharmgkb/__init__.py +15 -0
  135. pyobo/sources/pharmgkb/pharmgkb_chemical.py +89 -0
  136. pyobo/sources/pharmgkb/pharmgkb_disease.py +77 -0
  137. pyobo/sources/pharmgkb/pharmgkb_gene.py +108 -0
  138. pyobo/sources/pharmgkb/pharmgkb_pathway.py +63 -0
  139. pyobo/sources/pharmgkb/pharmgkb_variant.py +84 -0
  140. pyobo/sources/pharmgkb/utils.py +86 -0
  141. pyobo/sources/pid.py +1 -6
  142. pyobo/sources/pombase.py +6 -10
  143. pyobo/sources/pubchem.py +4 -9
  144. pyobo/sources/reactome.py +5 -11
  145. pyobo/sources/rgd.py +11 -16
  146. pyobo/sources/rhea.py +37 -36
  147. pyobo/sources/ror.py +69 -42
  148. pyobo/sources/selventa/__init__.py +0 -0
  149. pyobo/sources/selventa/schem.py +4 -7
  150. pyobo/sources/selventa/scomp.py +1 -6
  151. pyobo/sources/selventa/sdis.py +4 -7
  152. pyobo/sources/selventa/sfam.py +1 -6
  153. pyobo/sources/sgd.py +6 -11
  154. pyobo/sources/signor/__init__.py +7 -0
  155. pyobo/sources/signor/download.py +41 -0
  156. pyobo/sources/signor/signor_complexes.py +105 -0
  157. pyobo/sources/slm.py +12 -15
  158. pyobo/sources/umls/__init__.py +7 -1
  159. pyobo/sources/umls/__main__.py +0 -0
  160. pyobo/sources/umls/get_synonym_types.py +20 -4
  161. pyobo/sources/umls/sty.py +57 -0
  162. pyobo/sources/umls/synonym_types.tsv +1 -1
  163. pyobo/sources/umls/umls.py +18 -22
  164. pyobo/sources/unimod.py +46 -0
  165. pyobo/sources/uniprot/__init__.py +1 -1
  166. pyobo/sources/uniprot/uniprot.py +40 -32
  167. pyobo/sources/uniprot/uniprot_ptm.py +4 -34
  168. pyobo/sources/utils.py +3 -2
  169. pyobo/sources/wikipathways.py +7 -10
  170. pyobo/sources/zfin.py +5 -10
  171. pyobo/ssg/__init__.py +12 -16
  172. pyobo/ssg/base.html +0 -0
  173. pyobo/ssg/index.html +26 -13
  174. pyobo/ssg/term.html +12 -2
  175. pyobo/ssg/typedef.html +0 -0
  176. pyobo/struct/__init__.py +54 -8
  177. pyobo/struct/functional/__init__.py +1 -0
  178. pyobo/struct/functional/dsl.py +2572 -0
  179. pyobo/struct/functional/macros.py +423 -0
  180. pyobo/struct/functional/obo_to_functional.py +385 -0
  181. pyobo/struct/functional/ontology.py +272 -0
  182. pyobo/struct/functional/utils.py +112 -0
  183. pyobo/struct/reference.py +331 -136
  184. pyobo/struct/struct.py +1484 -657
  185. pyobo/struct/struct_utils.py +1078 -0
  186. pyobo/struct/typedef.py +162 -210
  187. pyobo/struct/utils.py +12 -5
  188. pyobo/struct/vocabulary.py +138 -0
  189. pyobo/utils/__init__.py +0 -0
  190. pyobo/utils/cache.py +16 -15
  191. pyobo/utils/io.py +51 -41
  192. pyobo/utils/iter.py +5 -5
  193. pyobo/utils/misc.py +41 -53
  194. pyobo/utils/ndex_utils.py +0 -0
  195. pyobo/utils/path.py +73 -70
  196. pyobo/version.py +3 -3
  197. pyobo-0.12.1.dist-info/METADATA +671 -0
  198. pyobo-0.12.1.dist-info/RECORD +201 -0
  199. pyobo-0.12.1.dist-info/WHEEL +4 -0
  200. {pyobo-0.11.2.dist-info → pyobo-0.12.1.dist-info}/entry_points.txt +1 -0
  201. pyobo-0.12.1.dist-info/licenses/LICENSE +21 -0
  202. pyobo/aws.py +0 -162
  203. pyobo/cli/aws.py +0 -47
  204. pyobo/identifier_utils.py +0 -142
  205. pyobo/normalizer.py +0 -232
  206. pyobo/registries/__init__.py +0 -16
  207. pyobo/registries/metaregistry.json +0 -507
  208. pyobo/registries/metaregistry.py +0 -135
  209. pyobo/sources/icd11.py +0 -105
  210. pyobo/xrefdb/__init__.py +0 -1
  211. pyobo/xrefdb/canonicalizer.py +0 -214
  212. pyobo/xrefdb/priority.py +0 -59
  213. pyobo/xrefdb/sources/__init__.py +0 -60
  214. pyobo/xrefdb/sources/biomappings.py +0 -36
  215. pyobo/xrefdb/sources/cbms2019.py +0 -91
  216. pyobo/xrefdb/sources/chembl.py +0 -83
  217. pyobo/xrefdb/sources/compath.py +0 -82
  218. pyobo/xrefdb/sources/famplex.py +0 -64
  219. pyobo/xrefdb/sources/gilda.py +0 -50
  220. pyobo/xrefdb/sources/intact.py +0 -113
  221. pyobo/xrefdb/sources/ncit.py +0 -133
  222. pyobo/xrefdb/sources/pubchem.py +0 -27
  223. pyobo/xrefdb/sources/wikidata.py +0 -116
  224. pyobo/xrefdb/xrefs_pipeline.py +0 -180
  225. pyobo-0.11.2.dist-info/METADATA +0 -711
  226. pyobo-0.11.2.dist-info/RECORD +0 -157
  227. pyobo-0.11.2.dist-info/WHEEL +0 -5
  228. pyobo-0.11.2.dist-info/top_level.txt +0 -1
@@ -0,0 +1,2572 @@
1
+ """A DSL for functional OWL."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import datetime
6
+ import itertools as itt
7
+ import typing
8
+ from abc import abstractmethod
9
+ from collections.abc import Iterable, Sequence
10
+ from typing import ClassVar, TypeAlias
11
+
12
+ import rdflib.namespace
13
+ from curies import Converter, Reference
14
+ from rdflib import OWL, RDF, RDFS, XSD, Graph, collection, term
15
+
16
+ from pyobo.struct.functional.utils import list_to_funowl
17
+ from pyobo.struct.reference import Reference as PyOBOReference
18
+ from pyobo.struct.reference import Referenced, get_preferred_prefix
19
+ from pyobo.struct.struct_utils import OBOLiteral
20
+
21
+ from .utils import FunctionalOWLSerializable, RDFNodeSerializable
22
+
23
+ __all__ = [
24
+ "Annotation",
25
+ "AnnotationAssertion",
26
+ "AnnotationAxiom",
27
+ "AnnotationProperty",
28
+ "AnnotationPropertyDomain",
29
+ "AnnotationPropertyRange",
30
+ "Assertion",
31
+ "AsymmetricObjectProperty",
32
+ "Axiom",
33
+ "Box",
34
+ "ClassAssertion",
35
+ "ClassAxiom",
36
+ "ClassExpression",
37
+ "DataAllValuesFrom",
38
+ "DataComplementOf",
39
+ "DataExactCardinality",
40
+ "DataHasValue",
41
+ "DataIntersectionOf",
42
+ "DataMaxCardinality",
43
+ "DataMinCardinality",
44
+ "DataOneOf",
45
+ "DataPropertyAssertion",
46
+ "DataPropertyAxiom",
47
+ "DataPropertyDomain",
48
+ "DataPropertyExpression",
49
+ "DataPropertyRange",
50
+ "DataRange",
51
+ "DataSomeValuesFrom",
52
+ "DataUnionOf",
53
+ "DatatypeDefinition",
54
+ "DatatypeRestriction",
55
+ "Declaration",
56
+ "DeclarationType",
57
+ "DifferentIndividuals",
58
+ "DisjointClasses",
59
+ "DisjointDataProperties",
60
+ "DisjointObjectProperties",
61
+ "DisjointUnion",
62
+ "EquivalentClasses",
63
+ "EquivalentDataProperties",
64
+ "EquivalentObjectProperties",
65
+ "FunctionalDataProperty",
66
+ "FunctionalObjectProperty",
67
+ "HasKey",
68
+ "InverseFunctionalObjectProperty",
69
+ "InverseObjectProperties",
70
+ "IrreflexiveObjectProperty",
71
+ "NegativeDataPropertyAssertion",
72
+ "NegativeObjectPropertyAssertion",
73
+ "ObjectAllValuesFrom",
74
+ "ObjectComplementOf",
75
+ "ObjectExactCardinality",
76
+ "ObjectHasSelf",
77
+ "ObjectHasValue",
78
+ "ObjectIntersectionOf",
79
+ "ObjectInverseOf",
80
+ "ObjectMaxCardinality",
81
+ "ObjectMinCardinality",
82
+ "ObjectOneOf",
83
+ "ObjectPropertyAssertion",
84
+ "ObjectPropertyAxiom",
85
+ "ObjectPropertyChain",
86
+ "ObjectPropertyDomain",
87
+ "ObjectPropertyExpression",
88
+ "ObjectPropertyRange",
89
+ "ObjectSomeValuesFrom",
90
+ "ObjectUnionOf",
91
+ "ReflexiveObjectProperty",
92
+ "SameIndividual",
93
+ "SubAnnotationPropertyOf",
94
+ "SubClassOf",
95
+ "SubDataPropertyOf",
96
+ "SubObjectPropertyExpression",
97
+ "SubObjectPropertyOf",
98
+ "SymmetricObjectProperty",
99
+ "TransitiveObjectProperty",
100
+ "l",
101
+ ]
102
+
103
+
104
+ def l(value) -> term.Literal: # noqa:E743
105
+ """Get a literal."""
106
+ return term.Literal(value)
107
+
108
+
109
+ #: These are the literals that can be automatically converted to and from RDFLib
110
+ SupportedLiterals: TypeAlias = int | float | bool | str | datetime.date | datetime.datetime
111
+
112
+ #: A partial hint for something that can be turned into an :class:`IdentifierBox`.
113
+ #: Here, a string gets interpreted into a CURIE using :meth:`curies.Reference.from_curie`
114
+ IdentifierHint = term.URIRef | Reference | Referenced | str
115
+
116
+
117
+ class Box(FunctionalOWLSerializable, RDFNodeSerializable):
118
+ """A model for objects that can be represented as nodes in RDF and Functional OWL."""
119
+
120
+
121
+ def obo_literal_to_rdflib(obo_literal: OBOLiteral, converter: Converter) -> rdflib.Literal:
122
+ """Expand the OBO literal."""
123
+ iri = converter.expand(obo_literal.datatype.curie, strict=True)
124
+ return rdflib.Literal(obo_literal.value, datatype=rdflib.URIRef(iri))
125
+
126
+
127
+ class IdentifierBox(Box):
128
+ """A simple wrapper around CURIEs and IRIs."""
129
+
130
+ identifier: term.URIRef | Reference
131
+
132
+ def __init__(self, identifier: IdentifierBoxOrHint) -> None:
133
+ """Initialize the identifier box with a URIRef, Reference, or string representing a CURIE."""
134
+ if isinstance(identifier, Referenced):
135
+ identifier = identifier.reference
136
+ if isinstance(identifier, IdentifierBox):
137
+ self.identifier = identifier.identifier
138
+ # make sure to check for URIRef first,
139
+ # since it's also a subclass of str
140
+ elif isinstance(identifier, term.URIRef):
141
+ self.identifier = identifier
142
+ elif isinstance(identifier, str):
143
+ self.identifier = Reference.from_curie(identifier)
144
+ elif isinstance(identifier, PyOBOReference):
145
+ # it doesn't matter we're potentially throwing away the name,
146
+ # since this annotation gets put in OFN in a different place
147
+ self.identifier = Reference(
148
+ prefix=get_preferred_prefix(identifier), identifier=identifier.identifier
149
+ )
150
+ elif isinstance(identifier, Reference):
151
+ self.identifier = identifier
152
+ else:
153
+ raise TypeError(f"can not make an identifier box from: {identifier}")
154
+
155
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
156
+ """Represent this identifier for RDF, using the converter to convert a CURIE if appropriate."""
157
+ if isinstance(self.identifier, term.URIRef):
158
+ return self.identifier
159
+ # TODO make more efficient
160
+ iri = converter.expand(self.identifier.curie, strict=True)
161
+ return term.URIRef(iri)
162
+
163
+ def to_funowl(self) -> str:
164
+ """Represent this identifier for functional OWL."""
165
+ if isinstance(self.identifier, term.URIRef):
166
+ return f"<{self.identifier}>"
167
+ if any(c in self.identifier.identifier for c in "()"):
168
+ raise ValueError(f"Can't encode CURIE with parentheses to OFN: {self.identifier}")
169
+ return self.identifier.curie
170
+
171
+ def to_funowl_args(self) -> str: # pragma: no cover
172
+ """Get the inside of the functional OWL tag representing the identifier (unused)."""
173
+ raise RuntimeError
174
+
175
+
176
+ class LiteralBox(Box):
177
+ """A simple wrapper around a literal."""
178
+
179
+ literal: term.Literal
180
+ _namespace_manager: ClassVar[rdflib.namespace.NamespaceManager] = Graph().namespace_manager
181
+ _converter: ClassVar[Converter] = Converter.from_rdflib(_namespace_manager)
182
+
183
+ def __init__(self, literal: LiteralBoxOrHint, language: str | None = None) -> None:
184
+ """Initialize the literal box with a RDFlib literal or Python primitive.."""
185
+ if literal is None:
186
+ raise ValueError
187
+ if isinstance(literal, LiteralBox):
188
+ self.literal = literal.literal
189
+ elif isinstance(literal, term.Literal):
190
+ self.literal = literal
191
+ elif isinstance(literal, bool):
192
+ self.literal = term.Literal(str(literal).lower(), datatype=XSD.boolean)
193
+ elif isinstance(literal, int):
194
+ self.literal = term.Literal(literal, datatype=XSD.integer)
195
+ elif isinstance(literal, float):
196
+ self.literal = term.Literal(literal, datatype=XSD.decimal)
197
+ elif isinstance(literal, str):
198
+ self.literal = term.Literal(literal, lang=language)
199
+ elif isinstance(literal, datetime.date):
200
+ self.literal = term.Literal(literal, datatype=XSD.date)
201
+ elif isinstance(literal, datetime.datetime):
202
+ self.literal = term.Literal(literal, datatype=XSD.dateTime)
203
+ elif isinstance(literal, OBOLiteral):
204
+ self.literal = obo_literal_to_rdflib(literal, self._converter)
205
+ else:
206
+ raise TypeError(f"Unhandled type for literal: {literal}")
207
+
208
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.Literal:
209
+ """Represent this literal for RDF."""
210
+ return self.literal
211
+
212
+ def to_funowl(self) -> str:
213
+ """Represent this literal for functional OWL."""
214
+ return self.literal.n3(self._namespace_manager)
215
+
216
+ def to_funowl_args(self) -> str: # pragma: no cover
217
+ """Get the inside of the functional OWL tag representing the literal (unused)."""
218
+ raise RuntimeError
219
+
220
+
221
+ IdentifierBoxOrHint: TypeAlias = IdentifierHint | IdentifierBox
222
+ LiteralBoxOrHint: TypeAlias = LiteralBox | term.Literal | SupportedLiterals | OBOLiteral
223
+ PrimitiveHint: TypeAlias = IdentifierBoxOrHint | LiteralBoxOrHint
224
+ PrimitiveBox: TypeAlias = LiteralBox | IdentifierBox
225
+
226
+
227
+ def _safe_primitive_box(value: PrimitiveHint) -> PrimitiveBox:
228
+ # if it's already boxed, then pass it through
229
+ if isinstance(value, PrimitiveBox):
230
+ return value
231
+ # note that literal is also a subclass of str,
232
+ # so it needs to be cheked first
233
+ if isinstance(value, term.Literal):
234
+ return LiteralBox(value)
235
+ # note that we decided that strings should be parsed
236
+ # by default as a CURIE. If you want to pass a literal
237
+ # through, wrap it with rdflib.Literal
238
+ if isinstance(value, str):
239
+ return IdentifierBox(value)
240
+ if isinstance(value, SupportedLiterals | OBOLiteral):
241
+ return LiteralBox(value)
242
+ # everything else (e.g., URIRef, Reference) are for identifier boxes
243
+ return IdentifierBox(value)
244
+
245
+
246
+ def _make_sequence(
247
+ graph: Graph,
248
+ members: Iterable[Box],
249
+ converter: Converter,
250
+ *,
251
+ type_connector_nodes: bool = False,
252
+ ) -> term.IdentifiedNode:
253
+ """Make a sequence."""
254
+ return _make_sequence_nodes(
255
+ graph,
256
+ [m.to_rdflib_node(graph, converter) for m in members],
257
+ type_connector_nodes=type_connector_nodes,
258
+ )
259
+
260
+
261
+ def _make_sequence_nodes(
262
+ graph: Graph,
263
+ members: Sequence[term.IdentifiedNode | term.Literal],
264
+ *,
265
+ type_connector_nodes: bool = False,
266
+ ) -> term.IdentifiedNode:
267
+ """Make a sequence."""
268
+ if not members:
269
+ return RDF.nil
270
+ node = term.BNode()
271
+ collection.Collection(graph, node, list(members))
272
+ if type_connector_nodes:
273
+ # This is a weird quirk required for DataOneOf, which for
274
+ # some reason emits `rdf:type rdfs:List` for each element
275
+ for connector_node in _yield_connector_nodes(graph, node):
276
+ graph.add((connector_node, RDF.type, RDF.List))
277
+ return node
278
+
279
+
280
+ def _yield_connector_nodes(
281
+ graph: Graph, start: term.IdentifiedNode
282
+ ) -> Iterable[term.IdentifiedNode]:
283
+ """Yield all of the nodes representing parts of a collection.
284
+
285
+ This is different than simply doing :meth:`rdflib.graph.items`,
286
+ because that function gets the "first" parts - this function
287
+ gets the blank nodes that are representing the prongs in the
288
+ list itself.
289
+
290
+ We have to do this because ROBOT implemenents RDF conversion for
291
+ :class:`DataOneOf` strangely, where each blank node in the collection
292
+ gets a triple typing it as a list ``<bnode> rdf:type rdfs:List``
293
+ """
294
+ yield start
295
+ item: term.IdentifiedNode | None = start
296
+ while item := graph.value(item, RDF.rest): # type:ignore
297
+ if item == RDF.nil:
298
+ break
299
+ yield item
300
+
301
+
302
+ """Section 5"""
303
+
304
+ DeclarationType: TypeAlias = typing.Literal[
305
+ "Class",
306
+ "ObjectProperty",
307
+ "DataProperty",
308
+ "Datatype",
309
+ "AnnotationProperty",
310
+ "NamedIndividual",
311
+ ]
312
+ type_to_uri: dict[DeclarationType, term.URIRef] = {
313
+ "Class": OWL.Class,
314
+ "ObjectProperty": OWL.ObjectProperty,
315
+ "DataProperty": OWL.DatatypeProperty,
316
+ "AnnotationProperty": OWL.AnnotationProperty,
317
+ "Datatype": RDFS.Datatype,
318
+ "NamedIndividual": OWL.NamedIndividual,
319
+ }
320
+
321
+
322
+ class Declaration(Box):
323
+ """Declarations."""
324
+
325
+ def __init__(self, node: IdentifierBoxOrHint, type: DeclarationType) -> None:
326
+ """Initialize the declaration with a given identifier and type."""
327
+ self.node = IdentifierBox(node)
328
+ self.type = type
329
+
330
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
331
+ """Represent this declaration for RDF."""
332
+ node = self.node.to_rdflib_node(graph, converter)
333
+ graph.add((node, RDF.type, type_to_uri[self.type]))
334
+ return node
335
+
336
+ def to_funowl_args(self) -> str:
337
+ """Get the inside of the functional OWL tag representing the declaration."""
338
+ return f"{self.type}({self.node.to_funowl()})"
339
+
340
+
341
+ """
342
+ Section 6: Property Expressions
343
+ """
344
+
345
+
346
+ class ObjectPropertyExpression(Box):
347
+ """A model representing `6.1 "Object Property Expressions" <https://www.w3.org/TR/owl2-syntax/#Object_Property_Expressions>`_.
348
+
349
+ .. image:: https://www.w3.org/TR/owl2-syntax/C_objectproperty.gif
350
+ """
351
+
352
+ @classmethod
353
+ def safe(cls, ope: ObjectPropertyExpression | IdentifierBoxOrHint) -> ObjectPropertyExpression:
354
+ """Pass through a pre-instantiated object property expression, or create a simple one for an identifier."""
355
+ if isinstance(ope, IdentifierBoxOrHint):
356
+ return SimpleObjectPropertyExpression(ope)
357
+ return ope
358
+
359
+
360
+ class SimpleObjectPropertyExpression(IdentifierBox, ObjectPropertyExpression):
361
+ """A simple object property expression represented by an IRI/CURIE."""
362
+
363
+ #: A set of built-in object properties that shouldn't be re-defined, since they
364
+ #: appear in Table 3 of https://www.w3.org/TR/owl2-syntax/#IRIs.
365
+ _SKIP: ClassVar[set[term.Node]] = {OWL.topObjectProperty, OWL.bottomObjectProperty}
366
+
367
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
368
+ """Represent this object property identifier for RDF."""
369
+ node = super().to_rdflib_node(graph, converter)
370
+ if node in self._SKIP:
371
+ return node
372
+ graph.add((node, RDF.type, OWL.ObjectProperty))
373
+ return node
374
+
375
+
376
+ class ObjectInverseOf(ObjectPropertyExpression):
377
+ """A property expression defined in `6.1.1 "Inverse Object Properties" <https://www.w3.org/TR/owl2-syntax/#Inverse_Object_Properties>`_.
378
+
379
+ For example, ``ObjectPropertyAssertion( a:fatherOf a:Peter a:Stewie )`` implies
380
+ ``ObjectPropertyAssertion( ObjectInverseOf(a:fatherOf) a:Stewie a:Peter )``.
381
+
382
+ >>> ObjectInverseOf("a:fatherOf")
383
+
384
+ .. warning::
385
+
386
+ This is the only instance in the specification where the
387
+ name of the tag is not the same as the name of the element
388
+ in the spec, which is ``InverseObjectProperty``.
389
+ """
390
+
391
+ object_property: IdentifierBox
392
+
393
+ def __init__(self, object_property: IdentifierBoxOrHint) -> None:
394
+ """Instantiate an inverse object property."""
395
+ # note that this can't be an expression - it has to be a defined thing.
396
+ # further, we can't use SimpleObjectPropertyExpression because
397
+ # we're trying to stay consistent with OWLAPI, and it sometimes doesn't
398
+ # automatically assert the enclosed property as a owl:ObjectProperty,
399
+ # e.g., inside ObjectMaxCardinality (not) and inside SubjectPropertyOf (does)
400
+ # see https://github.com/owlcs/owlapi/issues/1161
401
+ self.object_property = IdentifierBox(object_property)
402
+
403
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
404
+ """Get a node representing the inverse object property."""
405
+ node = term.BNode()
406
+ graph.add((node, OWL.inverseOf, self.object_property.to_rdflib_node(graph, converter)))
407
+ return node
408
+
409
+ def declare_wrapped_ope(self, graph: Graph, converter: Converter) -> None:
410
+ """Declare the wrapped object property."""
411
+ graph.add(
412
+ (self.object_property.to_rdflib_node(graph, converter), RDF.type, OWL.ObjectProperty)
413
+ )
414
+
415
+ def to_funowl_args(self) -> str:
416
+ """Get the inside of the functional OWL tag representing the inverse object property."""
417
+ return self.object_property.to_funowl()
418
+
419
+
420
+ class DataPropertyExpression(Box):
421
+ """A model representing `6.2 "Data Property Expressions" <https://www.w3.org/TR/owl2-syntax/#Data_Property_Expressions>`_.
422
+
423
+ .. image:: https://www.w3.org/TR/owl2-syntax/C_dataproperty.gif
424
+ """
425
+
426
+ @classmethod
427
+ def safe(cls, dpe: DataPropertyExpression | IdentifierBoxOrHint) -> DataPropertyExpression:
428
+ """Pass through a pre-instantiated data property expression, or create a simple one for an identifier."""
429
+ if isinstance(dpe, IdentifierBoxOrHint):
430
+ return SimpleDataPropertyExpression(dpe)
431
+ return dpe
432
+
433
+
434
+ class SimpleDataPropertyExpression(IdentifierBox, DataPropertyExpression):
435
+ """A simple data property expression represented by an IRI/CURIE."""
436
+
437
+ #: A set of built-in data properties that shouldn't be re-defined, since they
438
+ #: appear in Table 3 of https://www.w3.org/TR/owl2-syntax/#IRIs.
439
+ _SKIP: ClassVar[set[term.URIRef]] = {OWL.topDataProperty, OWL.bottomDataProperty}
440
+
441
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
442
+ """Represent this data property identifier for RDF."""
443
+ node = super().to_rdflib_node(graph, converter)
444
+ if node in self._SKIP:
445
+ return node
446
+ graph.add((node, RDF.type, OWL.DatatypeProperty))
447
+ return node
448
+
449
+
450
+ """
451
+ Section 7: Data Ranges
452
+ """
453
+
454
+
455
+ class DataRange(Box):
456
+ """A model representing `7 "Data Ranges" <https://www.w3.org/TR/owl2-syntax/#Datatypes>`_.
457
+
458
+ .. image:: https://www.w3.org/TR/owl2-syntax/C_datarange.gif
459
+ """
460
+
461
+ @classmethod
462
+ def safe(cls, data_range: DataRange | IdentifierBoxOrHint) -> DataRange:
463
+ """Pass through a pre-instantiated data range, or create a simple one for an identifier."""
464
+ if isinstance(data_range, IdentifierBoxOrHint):
465
+ return SimpleDateRange(data_range)
466
+ return data_range
467
+
468
+
469
+ class SimpleDateRange(IdentifierBox, DataRange):
470
+ """A simple data range represented by an IRI/CURIE."""
471
+
472
+ # TODO add skip to RDF node output for builtin data types?
473
+
474
+
475
+ class _ListDataRange(DataRange):
476
+ """An abstract model for data intersection and union expressions."""
477
+
478
+ property_type: ClassVar[term.URIRef]
479
+ data_ranges: Sequence[DataRange]
480
+
481
+ def __init__(self, data_ranges: Sequence[DataRange | IdentifierBoxOrHint]) -> None:
482
+ """Initialize this list of data ranges."""
483
+ self.data_ranges = [DataRange.safe(dr) for dr in data_ranges]
484
+
485
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
486
+ """Represent this list of data range for RDF."""
487
+ node = term.BNode()
488
+ graph.add((node, RDF.type, RDFS.Datatype))
489
+ graph.add((node, self.property_type, _make_sequence(graph, self.data_ranges, converter)))
490
+ return node
491
+
492
+ def to_funowl_args(self) -> str:
493
+ """Get the inside of the functional OWL tag representing the data range."""
494
+ return list_to_funowl(self.data_ranges)
495
+
496
+
497
+ class DataIntersectionOf(_ListDataRange):
498
+ """A data range defined in `7.1 "Intersection of Data Ranges" <https://www.w3.org/TR/owl2-syntax/#Intersection_of_Data_Ranges>`_.
499
+
500
+ The following data range contains exactly the integer 0:
501
+
502
+ >>> DataIntersectionOf(["xsd:nonNegativeInteger", "xsd:nonPositiveInteger"])
503
+ """
504
+
505
+ property_type: ClassVar[term.URIRef] = OWL.intersectionOf
506
+
507
+
508
+ class DataUnionOf(_ListDataRange):
509
+ """A data range defined in `7.2 "Union of Data Ranges" <https://www.w3.org/TR/owl2-syntax/#Union_of_Data_Ranges>`_.
510
+
511
+ The following data range contains all strings and all integers:
512
+
513
+ >>> DataUnionOf(["xsd:string", "xsd:integer"])
514
+ """
515
+
516
+ property_type: ClassVar[term.URIRef] = OWL.unionOf
517
+
518
+
519
+ class DataComplementOf(DataRange):
520
+ """A data range defined in `7.3 Complement of Data Ranges" <https://www.w3.org/TR/owl2-syntax/#Complement_of_Data_Ranges>`_.
521
+
522
+ The following complement data range contains literals that are not positive integers:
523
+
524
+ >>> DataComplementOf("xsd:positiveInteger")
525
+
526
+ The following contains all non-zero integers:
527
+
528
+ >>> DataComplementOf(DataIntersectionOf(["xsd:nonNegativeInteger", "xsd:nonPositiveInteger"]))
529
+ """
530
+
531
+ data_range: DataRange
532
+
533
+ def __init__(self, data_range: DataRange | IdentifierBoxOrHint):
534
+ """Initialize a complement of a data range using another data range."""
535
+ self.data_range = DataRange.safe(data_range)
536
+
537
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
538
+ """Represent this complement of a data range for RDF."""
539
+ node = term.BNode()
540
+ graph.add((node, RDF.type, RDFS.Datatype))
541
+ graph.add(
542
+ (node, OWL.datatypeComplementOf, self.data_range.to_rdflib_node(graph, converter))
543
+ )
544
+ return node
545
+
546
+ def to_funowl_args(self) -> str:
547
+ """Get the inside of the functional OWL tag representing the complement of data range."""
548
+ return self.data_range.to_funowl()
549
+
550
+
551
+ class DataOneOf(DataRange):
552
+ """A data range defined in `7.4 Enumeration of Literals" <https://www.w3.org/TR/owl2-syntax/#Enumeration_of_Literals>`_.
553
+
554
+ The following data range contains exactly two literals: the string "Peter" and the integer one.
555
+
556
+ >>> DataOneOf(["Peter", 1])
557
+
558
+ This can be specified more explicitly with :class:`rdflib.Literal`:
559
+
560
+ >>> import rdflib
561
+ >>> DataOneOf(["Peter", rdflib.Literal(1, datatype=XSD.nonNegativeInteger)])
562
+ """
563
+
564
+ literals: Sequence[LiteralBox]
565
+
566
+ def __init__(self, literals: Sequence[LiteralBoxOrHint]):
567
+ """Initialize an enumeration of literals."""
568
+ self.literals = [LiteralBox(literal) for literal in literals]
569
+
570
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
571
+ """Represent this enumeration of literals for RDF."""
572
+ node = term.BNode()
573
+ literal_nodes = [literal.to_rdflib_node(graph, converter) for literal in self.literals]
574
+ graph.add((node, RDF.type, RDFS.Datatype))
575
+ graph.add(
576
+ (node, OWL.oneOf, _make_sequence_nodes(graph, literal_nodes, type_connector_nodes=True))
577
+ )
578
+ return node
579
+
580
+ def to_funowl_args(self) -> str:
581
+ """Get the inside of the functional OWL tag representing the enumeration of literals."""
582
+ return list_to_funowl(self.literals)
583
+
584
+
585
+ class DatatypeRestriction(DataRange):
586
+ """A data range defined in `7.5 Datatype Restrictions " <https://www.w3.org/TR/owl2-syntax/#Datatype_Restrictions>`_.
587
+
588
+ The following data range contains exactly the integers 5, 6, 7, 8, and 9:
589
+
590
+ >>> DatatypeRestriction("xsd:integer", [("xsd:minInclusive", 5), ("xsd:maxExclusive", 10)])
591
+ """
592
+
593
+ datatype: IdentifierBox
594
+ pairs: list[tuple[IdentifierBox, LiteralBox]]
595
+
596
+ def __init__(
597
+ self,
598
+ datatype: IdentifierBoxOrHint,
599
+ pairs: list[tuple[IdentifierBoxOrHint, LiteralBoxOrHint]],
600
+ ) -> None:
601
+ """Initialize a datatype restriction.
602
+
603
+ :param datatype: The base datatype
604
+ :param pairs: A list of pairs of restrictions (e.g., ``xsd:minInclusive``) and literal values
605
+ """
606
+ self.datatype = IdentifierBox(datatype)
607
+ self.pairs = [(IdentifierBox(facet), LiteralBox(value)) for facet, value in pairs]
608
+
609
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
610
+ """Represent this datatype restriction for RDF."""
611
+ node = term.BNode()
612
+
613
+ restrictions: list[term.BNode] = []
614
+ for facet, value in self.pairs:
615
+ restriction = term.BNode()
616
+ graph.add(
617
+ (
618
+ restriction,
619
+ facet.to_rdflib_node(graph, converter),
620
+ value.to_rdflib_node(graph, converter),
621
+ )
622
+ )
623
+ restrictions.append(restriction)
624
+
625
+ graph.add((node, RDF.type, RDFS.Datatype))
626
+ graph.add((node, OWL.onDatatype, self.datatype.to_rdflib_node(graph, converter)))
627
+ graph.add((node, OWL.withRestrictions, _make_sequence_nodes(graph, restrictions)))
628
+ return node
629
+
630
+ def to_funowl_args(self) -> str:
631
+ """Get the inside of the functional OWL tag representing the datatype restriction."""
632
+ pairs_funowl = " ".join(
633
+ f"{facet.to_funowl()} {value.to_funowl()}" for facet, value in self.pairs
634
+ )
635
+ return f"{self.datatype.to_funowl()} {pairs_funowl}"
636
+
637
+
638
+ """
639
+ `Section 8: Class Expressions <https://www.w3.org/TR/owl2-syntax/#Class_Expressions>`_
640
+ """
641
+
642
+
643
+ class ClassExpression(Box):
644
+ """An abstract model representing `class expressions <https://www.w3.org/TR/owl2-syntax/#Class_Expressions>`_."""
645
+
646
+ @classmethod
647
+ def safe(cls, class_expresion: ClassExpression | IdentifierBoxOrHint) -> ClassExpression:
648
+ """Pass through a pre-instantiated class expression, or create a simple one for an identifier."""
649
+ if isinstance(class_expresion, IdentifierBoxOrHint):
650
+ return SimpleClassExpression(class_expresion)
651
+ return class_expresion
652
+
653
+
654
+ class SimpleClassExpression(IdentifierBox, ClassExpression):
655
+ """A simple class expression represented by an IRI/CURIE."""
656
+
657
+ #: A set of built-in classes that shouldn't be re-defined, since they
658
+ #: appear in Table 3 of https://www.w3.org/TR/owl2-syntax/#IRIs.
659
+ _SKIP: ClassVar[set[term.Node]] = {OWL.Thing, OWL.Nothing}
660
+
661
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
662
+ """Represent this class identifier for RDF."""
663
+ node = super().to_rdflib_node(graph, converter)
664
+ if node in self._SKIP:
665
+ # i.e., don't add extra annotations for these
666
+ return node
667
+ graph.add((node, RDF.type, OWL.Class))
668
+ return node
669
+
670
+
671
+ class _ObjectList(ClassExpression):
672
+ """An abstract model for class expressions defined by lists.
673
+
674
+ Defined in `8.1 Propositional Connectives and Enumeration of
675
+ Individuals <Propositional_Connectives_and_Enumeration_of_Individuals>`_
676
+
677
+ .. image:: https://www.w3.org/TR/owl2-syntax/C_propositional.gif
678
+ """
679
+
680
+ property_type: ClassVar[term.URIRef]
681
+ class_expressions: Sequence[ClassExpression]
682
+
683
+ def __init__(self, class_expressions: Sequence[ClassExpression | IdentifierBoxOrHint]) -> None:
684
+ """Initialize the model with a list of class expressions."""
685
+ if len(class_expressions) < 2:
686
+ raise ValueError("must have at least two class expressions")
687
+ self.class_expressions = [ClassExpression.safe(ce) for ce in class_expressions]
688
+
689
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
690
+ """Represent this object list identifier for RDF."""
691
+ node = term.BNode()
692
+ graph.add((node, RDF.type, OWL.Class))
693
+ graph.add(
694
+ (node, self.property_type, _make_sequence(graph, self.class_expressions, converter))
695
+ )
696
+ return node
697
+
698
+ def to_funowl_args(self) -> str:
699
+ """Get the inside of the functional OWL tag representing the object list."""
700
+ return list_to_funowl(self.class_expressions)
701
+
702
+
703
+ class ObjectIntersectionOf(_ObjectList):
704
+ """A class expression defined in `8.1.1 Intersection of Class Expressions <https://www.w3.org/TR/owl2-syntax/#Intersection_of_Class_Expressions>`_.
705
+
706
+ Consider the ontology consisting of the following axioms.
707
+
708
+ >>> ClassAssertion("a:Dog", "a:Brian") # Brian is a dog.
709
+ >>> ClassAssertion("a:CanTalk", "a:Brian") # Brian can talk.
710
+
711
+ The following class expression describes all dogs that can talk;
712
+ furthermore, ``a:Brian`` is classified as its instance.
713
+
714
+ >>> ObjectIntersectionOf(["a:Dog", "a:CanTalk"])
715
+ """
716
+
717
+ property_type: ClassVar[term.URIRef] = OWL.intersectionOf
718
+
719
+
720
+ class ObjectUnionOf(_ObjectList):
721
+ """A class expression defined in `8.1.2 Union of Class Expressions <https://www.w3.org/TR/owl2-syntax/#Union_of_Class_Expressions>`_.
722
+
723
+ Consider the ontology consisting of the following axioms.
724
+
725
+ >>> ClassAssertion("a:Man", "a:Peter") # Peter is a man.
726
+ >>> ClassAssertion("a:Woman", "a:Lois") # Lois is a woman.
727
+
728
+ The following class expression describes all individuals that are instances of either
729
+ ``a:Man`` or ``a:Woman``; furthermore, both ``a:Peter`` and ``a:Lois`` are classified
730
+ as its instances:
731
+
732
+ >>> ObjectUnionOf(["a:Man", "a:Woman"])
733
+ """
734
+
735
+ property_type: ClassVar[term.URIRef] = OWL.unionOf
736
+
737
+
738
+ class ObjectComplementOf(ClassExpression):
739
+ """A class expression defined in `8.1.3 Complement of Class Expressions <https://www.w3.org/TR/owl2-syntax/#Complement_of_Class_Expressions>`_.
740
+
741
+ Example 1
742
+ ---------
743
+ Consider the ontology consisting of the following axioms.
744
+
745
+ >>> DisjointClasses(["a:Man", "a:Woman"]) # Nothing can be both a man and a woman.
746
+ >>> ClassAssertion("a:Woman", "a:Lois") # Lois is a woman.
747
+
748
+ The following class expression describes all things that are not instances of a:Man:
749
+
750
+ >>> ObjectComplementOf("a:Man")
751
+
752
+ Example 2
753
+ ---------
754
+ OWL 2 has open-world semantics, so negation in OWL 2 is the same as in classical
755
+ (first-order) logic. To understand open-world semantics, consider the ontology
756
+ consisting of the following assertion.
757
+
758
+ >>> ClassAssertion("a:Dog", "a:Brian") # Brian is a dog.
759
+
760
+ One might expect ``a:Brian`` to be classified as an instance of
761
+ the following class expression:
762
+
763
+ >>> ObjectComplementOf("a:Bird")
764
+
765
+ However, because of the OWL reasoning, this can't be concluded
766
+ """
767
+
768
+ class_expression: ClassExpression
769
+
770
+ def __init__(self, class_expression: ClassExpression | IdentifierBoxOrHint) -> None:
771
+ """Initialize the model with a single class expression."""
772
+ self.class_expression = ClassExpression.safe(class_expression)
773
+
774
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
775
+ """Represent this object complement as RDF."""
776
+ node = term.BNode()
777
+ graph.add((node, RDF.type, OWL.Class))
778
+ graph.add((node, OWL.complementOf, self.class_expression.to_rdflib_node(graph, converter)))
779
+ return node
780
+
781
+ def to_funowl_args(self) -> str:
782
+ """Get the inside of the functional OWL tag representing the complement of class expression."""
783
+ return self.class_expression.to_funowl()
784
+
785
+
786
+ class ObjectOneOf(ClassExpression):
787
+ """A class expression defined in `8.1.4 Enumeration of Individuals <https://www.w3.org/TR/owl2-syntax/#Enumeration_of_Individuals>`_."""
788
+
789
+ property_type: ClassVar[term.URIRef] = OWL.oneOf
790
+ individuals: Sequence[IdentifierBox]
791
+
792
+ def __init__(self, individuals: Sequence[IdentifierBoxOrHint]) -> None:
793
+ """Initialize the model with a list of class expressions."""
794
+ if len(individuals) < 2:
795
+ raise ValueError("must have at least two class expressions")
796
+ self.individuals = [IdentifierBox(i) for i in individuals]
797
+
798
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
799
+ """Represent this enumeration of individuals for RDF."""
800
+ node = term.BNode()
801
+ graph.add((node, RDF.type, OWL.Class))
802
+ nodes = [i.to_rdflib_node(graph, converter) for i in self.individuals]
803
+ nodes = sorted(nodes, key=str) # sort is very important to be consistent with OWLAPI!
804
+ for i in nodes:
805
+ graph.add((i, RDF.type, OWL.NamedIndividual))
806
+ graph.add((node, self.property_type, _make_sequence_nodes(graph, nodes)))
807
+ return node
808
+
809
+ def to_funowl_args(self) -> str:
810
+ """Get the inside of the functional OWL tag representing the enumeration of individuals."""
811
+ return list_to_funowl(self.individuals)
812
+
813
+
814
+ def get_owl_restriction(
815
+ graph: Graph,
816
+ object_property_expression: ObjectPropertyExpression,
817
+ restriction_predicate: term.URIRef,
818
+ restriction_target: Box | term.Literal | term.IdentifiedNode,
819
+ converter: Converter,
820
+ ) -> term.BNode:
821
+ """Generate a blank node representing an OWL restriction.
822
+
823
+ :param graph: An RDFlib graph
824
+ :param object_property_expression: The object property expression that goes with ``owl:onProperty``
825
+ :param restriction_predicate: The predicate that connects the restriction to the target.
826
+ Can be one of ``owl:someValuesFrom``, ``owl:allValuesFrom``,
827
+ ``owl:hasValue``, ``owl:hasSelf``, or something more exotic
828
+ :param restriction_target: The target reference or literal
829
+ :param converter: The a converter for CURIEs to URIs
830
+ :return: A blank node representing an OWL restriction
831
+ """
832
+ node = term.BNode()
833
+ graph.add((node, RDF.type, OWL.Restriction))
834
+ graph.add((node, OWL.onProperty, object_property_expression.to_rdflib_node(graph, converter)))
835
+ if isinstance(restriction_target, Box):
836
+ graph.add(
837
+ (node, restriction_predicate, restriction_target.to_rdflib_node(graph, converter))
838
+ )
839
+ else:
840
+ graph.add((node, restriction_predicate, restriction_target))
841
+ return node
842
+
843
+
844
+ class _ObjectValuesFrom(ClassExpression):
845
+ #: The predicate used in the OWL restriction, either ``owl:someValuesFrom`` or ``owl:allValuesFrom``
846
+ restriction_predicate: ClassVar[term.URIRef]
847
+ object_property_expression: ObjectPropertyExpression
848
+ class_expression: ClassExpression
849
+
850
+ def __init__(
851
+ self,
852
+ object_property_expression: ObjectPropertyExpression | IdentifierBoxOrHint,
853
+ class_expression: ClassExpression | IdentifierBoxOrHint,
854
+ ) -> None:
855
+ """Instantiate a quantification."""
856
+ self.object_property_expression = ObjectPropertyExpression.safe(object_property_expression)
857
+ self.object_expression = ClassExpression.safe(class_expression)
858
+
859
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
860
+ """Represent this quantification for RDF."""
861
+ return get_owl_restriction(
862
+ graph,
863
+ object_property_expression=self.object_property_expression,
864
+ restriction_predicate=self.restriction_predicate,
865
+ restriction_target=self.object_expression,
866
+ converter=converter,
867
+ )
868
+
869
+ def to_funowl_args(self) -> str:
870
+ """Get the inside of the functional OWL tag representing the quantification."""
871
+ return f"{self.object_property_expression.to_funowl()} {self.object_expression.to_funowl()}"
872
+
873
+
874
+ class ObjectSomeValuesFrom(_ObjectValuesFrom):
875
+ """A class expression defined in `8.2.1 Existential Quantification <https://www.w3.org/TR/owl2-syntax/#Existential_Quantification>`_."""
876
+
877
+ restriction_predicate: ClassVar[term.URIRef] = OWL.someValuesFrom
878
+
879
+
880
+ class ObjectAllValuesFrom(_ObjectValuesFrom):
881
+ """A class expression defined in `8.2.2 Universal Quantification <https://www.w3.org/TR/owl2-syntax/# Universal_Quantification>`_."""
882
+
883
+ restriction_predicate: ClassVar[term.URIRef] = OWL.allValuesFrom
884
+
885
+
886
+ class ObjectHasValue(ClassExpression):
887
+ """A class expression defined in `8.2.3 Individual Value Restriction <https://www.w3.org/TR/owl2-syntax/#Individual_Value_Restriction>`_."""
888
+
889
+ object_property_expression: ObjectPropertyExpression
890
+ individual: IdentifierBox
891
+
892
+ def __init__(
893
+ self,
894
+ object_property_expression: ObjectPropertyExpression | IdentifierBoxOrHint,
895
+ individual: IdentifierBoxOrHint,
896
+ ) -> None:
897
+ """Instantiate an individual value restriction."""
898
+ self.object_property_expression = ObjectPropertyExpression.safe(object_property_expression)
899
+ self.individual = IdentifierBox(individual)
900
+
901
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
902
+ """Represent the individual value restriction for RDF."""
903
+ individual = self.individual.to_rdflib_node(graph, converter)
904
+ graph.add((individual, RDF.type, OWL.NamedIndividual))
905
+ return get_owl_restriction(
906
+ graph,
907
+ object_property_expression=self.object_property_expression,
908
+ restriction_predicate=OWL.hasValue,
909
+ restriction_target=individual,
910
+ converter=converter,
911
+ )
912
+
913
+ def to_funowl_args(self) -> str:
914
+ """Get the inside of the functional OWL tag representing the individual value restriction."""
915
+ return f"{self.object_property_expression.to_funowl()} {self.individual.to_funowl()}"
916
+
917
+
918
+ class ObjectHasSelf(ClassExpression):
919
+ """A class expression defined in `8.2.4 Self-Restriction <https://www.w3.org/TR/owl2-syntax/#Self-Restriction>`_."""
920
+
921
+ object_property_expression: ObjectPropertyExpression
922
+
923
+ def __init__(
924
+ self, object_property_expression: ObjectPropertyExpression | IdentifierBoxOrHint
925
+ ) -> None:
926
+ """Initialize the model with a property expression."""
927
+ self.object_property_expression = ObjectPropertyExpression.safe(object_property_expression)
928
+
929
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
930
+ """Represent the self restriction for RDF."""
931
+ return get_owl_restriction(
932
+ graph,
933
+ object_property_expression=self.object_property_expression,
934
+ restriction_predicate=OWL.hasSelf,
935
+ restriction_target=term.Literal(True),
936
+ converter=converter,
937
+ )
938
+
939
+ def to_funowl_args(self) -> str:
940
+ """Get the inside of the functional OWL tag representing the self-restriction."""
941
+ return self.object_property_expression.to_funowl()
942
+
943
+
944
+ class _Cardinality(ClassExpression):
945
+ """A helper class for object and data cardinality constraints."""
946
+
947
+ property_qualified: ClassVar[term.URIRef]
948
+ property_unqualified: ClassVar[term.URIRef]
949
+ property_type: ClassVar[term.URIRef]
950
+ property_expression_type: ClassVar[term.URIRef] # the datatype for the target_expression
951
+
952
+ cardinality: int
953
+ property_expression: Box
954
+ target_expression: Box | None
955
+
956
+ def __init__(
957
+ self, cardinality: int, property_expression: Box, target_expression: Box | None = None
958
+ ) -> None:
959
+ """Instantiate the cardinality restriction."""
960
+ self.cardinality = cardinality
961
+ self.property_expression = property_expression
962
+ self.target_expression = target_expression
963
+
964
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
965
+ """Get a node representing the object or data cardinality constraint."""
966
+ node = term.BNode()
967
+ graph.add((node, RDF.type, OWL.Restriction))
968
+ pe_node = self.property_expression.to_rdflib_node(graph, converter)
969
+ # don't annotate the expression type if it's a blank node
970
+ # (i.e., if the property expression is an ObjectInverseOf)
971
+ if not isinstance(pe_node, term.BNode):
972
+ graph.add((pe_node, RDF.type, self.property_expression_type))
973
+ graph.add((node, OWL.onProperty, pe_node))
974
+ literal = term.Literal(str(self.cardinality), datatype=XSD.nonNegativeInteger)
975
+ if self.target_expression is not None:
976
+ graph.add((node, self.property_qualified, literal))
977
+ graph.add(
978
+ (node, self.property_type, self.target_expression.to_rdflib_node(graph, converter))
979
+ )
980
+ else:
981
+ graph.add((node, self.property_unqualified, literal))
982
+ return node
983
+
984
+ def to_funowl_args(self) -> str:
985
+ """Get the inside of the functional OWL tag representing the cardinality constraint."""
986
+ inside = f"{self.cardinality} {self.property_expression.to_funowl()}"
987
+ if self.target_expression is not None:
988
+ inside += f" {self.target_expression.to_funowl()}"
989
+ return inside
990
+
991
+
992
+ class _ObjectCardinality(_Cardinality):
993
+ """A grouping class for object cardinality models.
994
+
995
+ The three subclasses only differ by the qualified and unqualified
996
+ ranges used.
997
+
998
+ .. image:: https://www.w3.org/TR/owl2-syntax/C_objectcardinality.gif
999
+ """
1000
+
1001
+ property_type: ClassVar[term.URIRef] = OWL.onClass
1002
+ property_expression_type: ClassVar[term.URIRef] = OWL.ObjectProperty
1003
+ property_expression: ObjectPropertyExpression
1004
+ target_expression: ClassExpression | None
1005
+
1006
+ def __init__(
1007
+ self,
1008
+ cardinality: int,
1009
+ object_property_expression: ObjectPropertyExpression | IdentifierBoxOrHint,
1010
+ class_expression: ClassExpression | IdentifierBoxOrHint | None = None,
1011
+ ) -> None:
1012
+ """Instantiate an object cardinality restriction."""
1013
+ super().__init__(
1014
+ cardinality=cardinality,
1015
+ property_expression=ObjectPropertyExpression.safe(object_property_expression),
1016
+ target_expression=ClassExpression.safe(class_expression)
1017
+ if class_expression is not None
1018
+ else None,
1019
+ )
1020
+
1021
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
1022
+ """Represent the object cardinality restriction for RDF."""
1023
+ node = super().to_rdflib_node(graph, converter)
1024
+ # we're special-casing this because of the inconsistent way that OWLAPI
1025
+ # adds type assertions when using ObjectInverseOf (for some reason,
1026
+ # it doesn't generate them inside EquivalentClasses)
1027
+ if isinstance(self.property_expression, ObjectInverseOf):
1028
+ self.property_expression.declare_wrapped_ope(graph, converter)
1029
+ return node
1030
+
1031
+
1032
+ class ObjectMinCardinality(_ObjectCardinality):
1033
+ """A class expression defined in `8.3.1 Minimum Cardinality <https://www.w3.org/TR/owl2-syntax/#Minimum_Cardinality>`_."""
1034
+
1035
+ property_qualified: ClassVar[term.URIRef] = OWL.minQualifiedCardinality
1036
+ property_unqualified: ClassVar[term.URIRef] = OWL.minCardinality
1037
+
1038
+
1039
+ class ObjectMaxCardinality(_ObjectCardinality):
1040
+ """A class expression defined in `8.3.2 Maximum Cardinality <https://www.w3.org/TR/owl2-syntax/#Maximum_Cardinality>`_."""
1041
+
1042
+ property_qualified: ClassVar[term.URIRef] = OWL.maxQualifiedCardinality
1043
+ property_unqualified: ClassVar[term.URIRef] = OWL.maxCardinality
1044
+
1045
+
1046
+ class ObjectExactCardinality(_ObjectCardinality):
1047
+ """A class expression defined in `8.3.2 Exact Cardinality <https://www.w3.org/TR/owl2-syntax/#Exact_Cardinality>`_."""
1048
+
1049
+ property_qualified: ClassVar[term.URIRef] = OWL.qualifiedCardinality
1050
+ property_unqualified: ClassVar[term.URIRef] = OWL.cardinality
1051
+
1052
+
1053
+ class _DataValuesFrom(ClassExpression):
1054
+ """A class expression defined in https://www.w3.org/TR/owl2-syntax/#Existential_Quantification_2."""
1055
+
1056
+ property_type: ClassVar[term.URIRef]
1057
+ data_property_expressions: Sequence[DataPropertyExpression]
1058
+ data_range: DataRange
1059
+
1060
+ def __init__(
1061
+ self,
1062
+ data_property_expressions: list[DataPropertyExpression | IdentifierBoxOrHint],
1063
+ data_range: DataRange | IdentifierBoxOrHint,
1064
+ ) -> None:
1065
+ """Instantiate a data values existential quantification."""
1066
+ if not data_property_expressions:
1067
+ raise ValueError
1068
+ if len(data_property_expressions) >= 2:
1069
+ raise NotImplementedError(
1070
+ "while the OWL 2 spec states that there can be multiple data property "
1071
+ "expressions, there is no explanation on what should be done if many "
1072
+ "appear. Therefore, it's left as unimplemented for now."
1073
+ )
1074
+ self.data_property_expressions = [
1075
+ DataPropertyExpression.safe(dpe) for dpe in data_property_expressions
1076
+ ]
1077
+ self.data_range = DataRange.safe(data_range)
1078
+
1079
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.BNode:
1080
+ """Represent the data values existential quantification for RDF."""
1081
+ node = term.BNode()
1082
+ graph.add((node, RDF.type, OWL.Restriction))
1083
+ p_o = _get_data_value_po(graph, converter, self.data_property_expressions)
1084
+ graph.add((node, self.property_type, self.data_range.to_rdflib_node(graph, converter)))
1085
+ graph.add((node, *p_o))
1086
+ return node
1087
+
1088
+ def to_funowl_args(self) -> str:
1089
+ """Get the inside of the functional OWL tag representing the existential quantification."""
1090
+ return list_to_funowl((*self.data_property_expressions, self.data_range))
1091
+
1092
+
1093
+ def _get_data_value_po(
1094
+ graph, converter, dpes: Sequence[DataPropertyExpression]
1095
+ ) -> tuple[term.URIRef, term.IdentifiedNode]:
1096
+ if len(dpes) >= 2:
1097
+ # Note that this is currently not possible to get to with
1098
+ return OWL.onProperties, _make_sequence(graph, dpes, converter=converter)
1099
+ else:
1100
+ return OWL.onProperty, dpes[0].to_rdflib_node(graph, converter)
1101
+
1102
+
1103
+ class DataSomeValuesFrom(_DataValuesFrom):
1104
+ """A class expression defined in `8.4.1 Existential Qualifications <https://www.w3.org/TR/owl2-syntax/#Existential_Quantification_2>`_."""
1105
+
1106
+ property_type: ClassVar[term.URIRef] = OWL.someValuesFrom
1107
+
1108
+
1109
+ class DataAllValuesFrom(_DataValuesFrom):
1110
+ """A class expression defined in `8.4.2 Universal Qualifications <https://www.w3.org/TR/owl2-syntax/#Universal_Quantification_2>`_."""
1111
+
1112
+ property_type: ClassVar[term.URIRef] = OWL.allValuesFrom
1113
+
1114
+
1115
+ class DataHasValue(_DataValuesFrom):
1116
+ """A class expression defined in `8.4.3 Literal Value Restriction <https://www.w3.org/TR/owl2-syntax/#Literal_Value_Restriction>`_."""
1117
+
1118
+ property_type: ClassVar[term.URIRef] = OWL.hasValue
1119
+ data_property_expression: DataPropertyExpression
1120
+ literal: LiteralBox
1121
+
1122
+ def __init__(
1123
+ self,
1124
+ data_property_expression: DataPropertyExpression | IdentifierBoxOrHint,
1125
+ literal: LiteralBoxOrHint,
1126
+ ) -> None:
1127
+ """Instantiate a literal value restriction."""
1128
+ self.data_property_expression = DataPropertyExpression.safe(data_property_expression)
1129
+ self.literal = LiteralBox(literal)
1130
+
1131
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.BNode:
1132
+ """Represent this literal value restriction for RDF."""
1133
+ node = term.BNode()
1134
+ graph.add((node, RDF.type, OWL.Restriction))
1135
+ graph.add((node, OWL.hasValue, self.literal.to_rdflib_node(graph, converter)))
1136
+ graph.add(
1137
+ (node, OWL.onProperty, self.data_property_expression.to_rdflib_node(graph, converter))
1138
+ )
1139
+ return node
1140
+
1141
+ def to_funowl_args(self) -> str:
1142
+ """Get the inside of the functional OWL tag representing the literal value restriction."""
1143
+ return f"{self.data_property_expression.to_funowl()} {self.literal.to_funowl()}"
1144
+
1145
+
1146
+ class _DataCardinality(_Cardinality):
1147
+ """A grouping class for data cardinality models.
1148
+
1149
+ The three subclasses only differ by the qualified and unqualified
1150
+ ranges used.
1151
+ """
1152
+
1153
+ property_type: ClassVar[term.URIRef] = OWL.onDataRange
1154
+ property_expression_type: ClassVar[term.URIRef] = OWL.DatatypeProperty
1155
+ property_expression: DataPropertyExpression
1156
+ target_expression: DataRange | None
1157
+
1158
+ def __init__(
1159
+ self,
1160
+ cardinality: int,
1161
+ data_property_expression: DataPropertyExpression | IdentifierBoxOrHint,
1162
+ data_range: DataRange | IdentifierBoxOrHint | None = None,
1163
+ ) -> None:
1164
+ """Instantiate a data cardinality restriction."""
1165
+ super().__init__(
1166
+ cardinality,
1167
+ DataPropertyExpression.safe(data_property_expression),
1168
+ DataRange.safe(data_range) if data_range is not None else None,
1169
+ )
1170
+
1171
+
1172
+ class DataMinCardinality(_DataCardinality):
1173
+ """A class expression defined in `8.5.1 Minimum Cardinality <https://www.w3.org/TR/owl2-syntax/#Minimum_Cardinality_2>`_."""
1174
+
1175
+ property_qualified: ClassVar[term.URIRef] = OWL.minQualifiedCardinality
1176
+ property_unqualified: ClassVar[term.URIRef] = OWL.minCardinality
1177
+
1178
+
1179
+ class DataMaxCardinality(_DataCardinality):
1180
+ """A class expression defined in `8.5.2 Maximum Cardinality <https://www.w3.org/TR/owl2-syntax/#Maximum_Cardinality_2>`_."""
1181
+
1182
+ property_qualified: ClassVar[term.URIRef] = OWL.maxQualifiedCardinality
1183
+ property_unqualified: ClassVar[term.URIRef] = OWL.maxCardinality
1184
+
1185
+
1186
+ class DataExactCardinality(_DataCardinality):
1187
+ """A class expression defined in `8.5.3 Exact Cardinality <https://www.w3.org/TR/owl2-syntax/#Exact_Cardinality_2>`_."""
1188
+
1189
+ property_qualified: ClassVar[term.URIRef] = OWL.qualifiedCardinality
1190
+ property_unqualified: ClassVar[term.URIRef] = OWL.cardinality
1191
+
1192
+
1193
+ """
1194
+ `Section 9: Axioms <https://www.w3.org/TR/owl2-syntax/#Axioms>`_
1195
+ """
1196
+
1197
+
1198
+ class Axiom(Box):
1199
+ """A model for an `axiom <https://www.w3.org/TR/owl2-syntax/#Axioms>`_."""
1200
+
1201
+ annotations: list[Annotation]
1202
+
1203
+ def __init__(self, annotations: list[Annotation] | None = None) -> None:
1204
+ """Instantiate an axiom, with an optional list of annotations."""
1205
+ self.annotations = annotations or []
1206
+
1207
+ def to_funowl_args(self) -> str:
1208
+ """Get the functional OWL tag representing the axiom."""
1209
+ if self.annotations:
1210
+ return list_to_funowl(self.annotations) + " " + self._funowl_inside_2()
1211
+ return self._funowl_inside_2()
1212
+
1213
+ @abstractmethod
1214
+ def _funowl_inside_2(self) -> str:
1215
+ """Get the inside of the functional OWL tag representing the axiom."""
1216
+
1217
+
1218
+ class ClassAxiom(Axiom):
1219
+ """A model for a class axiom."""
1220
+
1221
+
1222
+ def _add_triple(
1223
+ graph: Graph,
1224
+ s: term.IdentifiedNode,
1225
+ p: term.IdentifiedNode,
1226
+ o: term.IdentifiedNode | term.Literal,
1227
+ annotations: Annotations | None = None,
1228
+ *,
1229
+ converter: Converter,
1230
+ ) -> term.BNode:
1231
+ graph.add((s, p, o))
1232
+ return _add_triple_annotations(graph, s, p, o, annotations=annotations, converter=converter)
1233
+
1234
+
1235
+ def _add_triple_annotations(
1236
+ graph: Graph,
1237
+ s: term.IdentifiedNode,
1238
+ p: term.IdentifiedNode,
1239
+ o: term.IdentifiedNode | term.Literal,
1240
+ *,
1241
+ annotations: Annotations | None = None,
1242
+ type: term.URIRef | None = None,
1243
+ converter: Converter,
1244
+ force_for_negative_assertion: bool = False,
1245
+ reified_s=OWL.annotatedSource,
1246
+ reified_p=OWL.annotatedProperty,
1247
+ reified_o=OWL.annotatedTarget,
1248
+ ) -> term.BNode:
1249
+ # in order to represent annotations on a triple,
1250
+ # we need to "reify" the triple, which means to
1251
+ # represent it with a blank node
1252
+ reified_triple = term.BNode()
1253
+ if not annotations and not force_for_negative_assertion:
1254
+ return reified_triple
1255
+ if type is None:
1256
+ type = OWL.Axiom
1257
+ graph.add((reified_triple, RDF.type, type))
1258
+ graph.add((reified_triple, reified_s, s))
1259
+ graph.add((reified_triple, reified_p, p))
1260
+ graph.add((reified_triple, reified_o, o))
1261
+ for annotation in annotations or []:
1262
+ annotation._add_to_triple(graph, reified_triple, converter)
1263
+ return reified_triple
1264
+
1265
+
1266
+ class SubClassOf(ClassAxiom):
1267
+ r"""A class axiom defined in `9.1.1 "Subclass Axioms" <https://www.w3.org/TR/owl2-syntax/#Subclass_Axioms>`_.
1268
+
1269
+ Example:
1270
+ >>> SubClassOf("a:Baby", "a:Child") # Each baby is a child.
1271
+ >>> SubClassOf("a:Child", "a:Person") # Each child is a person.
1272
+ >>> ClassAssertion("a:Baby", "a:Stewie") # Stewie is a baby.
1273
+
1274
+ This axiom can be applied to more complicated expressions. For example,
1275
+ here's the long form for a :class:`FunctionalDataProperty`:
1276
+
1277
+ >>> axiom = SubClassOf("owl:Thing", DataMaxCardinality(1, "a:hasAge"))
1278
+
1279
+ which itself is eqivalent to:
1280
+
1281
+ >>> FunctionalDataProperty("a:hasAge")
1282
+ """
1283
+
1284
+ child: ClassExpression
1285
+ parent: ClassExpression
1286
+
1287
+ def __init__(
1288
+ self,
1289
+ child: ClassExpression | IdentifierBoxOrHint,
1290
+ parent: ClassExpression | IdentifierBoxOrHint,
1291
+ *,
1292
+ annotations: Annotations | None = None,
1293
+ ) -> None:
1294
+ """Initialize a subclass axiom."""
1295
+ self.child = ClassExpression.safe(child)
1296
+ self.parent = ClassExpression.safe(parent)
1297
+ super().__init__(annotations)
1298
+
1299
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
1300
+ """Represent the subclass axiom for RDF."""
1301
+ s = self.child.to_rdflib_node(graph, converter)
1302
+ o = self.parent.to_rdflib_node(graph, converter)
1303
+ return _add_triple(graph, s, RDFS.subClassOf, o, self.annotations, converter=converter)
1304
+
1305
+ def _funowl_inside_2(self) -> str:
1306
+ return f"{self.child.to_funowl()} {self.parent.to_funowl()}"
1307
+
1308
+
1309
+ class EquivalentClasses(ClassAxiom):
1310
+ """A class axiom defined in `9.1.2 "Subclass Axioms" <https://www.w3.org/TR/owl2-syntax/#Equivalent_Classes>`_.
1311
+
1312
+ >>> EquivalentClasses(
1313
+ ... [
1314
+ ... "a:GriffinFamilyMember",
1315
+ ... ObjectOneOf(["a:Peter", "a:Lois", "a:Stewie", "a:Meg", "a:Chris", "a:Brian"]),
1316
+ ... ]
1317
+ ... )
1318
+ """
1319
+
1320
+ class_expression: Sequence[ClassExpression]
1321
+
1322
+ def __init__(
1323
+ self,
1324
+ class_expressions: Sequence[ClassExpression | IdentifierBoxOrHint],
1325
+ *,
1326
+ annotations: Annotations | None = None,
1327
+ ) -> None:
1328
+ """Initialize a equivalent class axiom."""
1329
+ if len(class_expressions) < 2:
1330
+ raise ValueError
1331
+ self.class_expressions = [ClassExpression.safe(ce) for ce in class_expressions]
1332
+ super().__init__(annotations)
1333
+
1334
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
1335
+ """Represent the equivalent class axiom for RDF."""
1336
+ rv = term.BNode()
1337
+ nodes = [ce.to_rdflib_node(graph, converter) for ce in self.class_expressions]
1338
+ for s, o in itt.pairwise(nodes):
1339
+ _add_triple(graph, s, OWL.equivalentClass, o, self.annotations, converter=converter)
1340
+ # TODO connect all triples to this BNode?
1341
+ return rv
1342
+
1343
+ def _funowl_inside_2(self) -> str:
1344
+ return list_to_funowl(self.class_expressions)
1345
+
1346
+
1347
+ class DisjointClasses(ClassAxiom):
1348
+ """A class axiom defined in `9.1.3 "Disjoint Classes" <https://www.w3.org/TR/owl2-syntax/#Disjoint_Classes>`_.
1349
+
1350
+ >>> DisjointClasses("a:Boy a:Girl".split())
1351
+ """
1352
+
1353
+ class_expression: Sequence[ClassExpression]
1354
+
1355
+ def __init__(
1356
+ self,
1357
+ class_expressions: Sequence[ClassExpression | IdentifierBoxOrHint],
1358
+ *,
1359
+ annotations: Annotations | None = None,
1360
+ ) -> None:
1361
+ """Initialize a disjoint classes axiom."""
1362
+ if len(class_expressions) < 2:
1363
+ raise ValueError
1364
+ self.class_expressions = [ClassExpression.safe(ce) for ce in class_expressions]
1365
+ super().__init__(annotations)
1366
+
1367
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
1368
+ """Represent the disjoint classes axiom for RDF."""
1369
+ nodes = [ce.to_rdflib_node(graph, converter) for ce in self.class_expressions]
1370
+ if len(nodes) == 2:
1371
+ return _add_triple(
1372
+ graph, nodes[0], OWL.disjointWith, nodes[1], self.annotations, converter=converter
1373
+ )
1374
+ else:
1375
+ node = term.BNode()
1376
+ graph.add((node, RDF.type, OWL.AllDisjointClasses))
1377
+ _add_triple(
1378
+ graph,
1379
+ node,
1380
+ OWL.members,
1381
+ _make_sequence_nodes(graph, nodes),
1382
+ self.annotations,
1383
+ converter=converter,
1384
+ )
1385
+ return node
1386
+
1387
+ def _funowl_inside_2(self) -> str:
1388
+ return list_to_funowl(self.class_expressions)
1389
+
1390
+
1391
+ class DisjointUnion(ClassAxiom):
1392
+ """A class axiom defined in `9.1.4 "Disjoint Union of Class Expressions" <https://www.w3.org/TR/owl2-syntax/#Disjoint_Union_of_Class_Expressions>`_."""
1393
+
1394
+ parent: SimpleClassExpression
1395
+ class_expression: Sequence[ClassExpression]
1396
+
1397
+ def __init__(
1398
+ self,
1399
+ parent: IdentifierBoxOrHint,
1400
+ class_expressions: Sequence[ClassExpression | IdentifierBoxOrHint],
1401
+ *,
1402
+ annotations: Annotations | None = None,
1403
+ ) -> None:
1404
+ """Initialize a disjoint union of class expressions axiom."""
1405
+ if len(class_expressions) < 2:
1406
+ raise ValueError
1407
+ self.parent = SimpleClassExpression(parent)
1408
+ self.class_expressions = [ClassExpression.safe(ce) for ce in class_expressions]
1409
+ super().__init__(annotations)
1410
+
1411
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
1412
+ """Represent the disjoint union of class expressions axiom as RDF."""
1413
+ return _add_triple(
1414
+ graph,
1415
+ self.parent.to_rdflib_node(graph, converter),
1416
+ OWL.disjointUnionOf,
1417
+ _make_sequence(graph, self.class_expressions, converter),
1418
+ self.annotations,
1419
+ converter=converter,
1420
+ )
1421
+
1422
+ def _funowl_inside_2(self) -> str:
1423
+ return list_to_funowl((self.parent, *self.class_expressions))
1424
+
1425
+
1426
+ """Section 9.2: Object Property Axioms"""
1427
+
1428
+
1429
+ class ObjectPropertyAxiom(Axiom):
1430
+ """A grouping class for `9.2 "Object Property Axioms" <https://www.w3.org/TR/owl2-syntax/#Object_Property_Axioms>`_.
1431
+
1432
+ .. image:: https://www.w3.org/TR/owl2-syntax/A_objectproperty2.gif
1433
+ """
1434
+
1435
+
1436
+ class ObjectPropertyChain(Box):
1437
+ """Represents a list of object properties."""
1438
+
1439
+ object_property_expressions: Sequence[ObjectPropertyExpression]
1440
+
1441
+ def __init__(
1442
+ self, object_property_expressions: Sequence[ObjectPropertyExpression | IdentifierBoxOrHint]
1443
+ ):
1444
+ """Instantiate a list of object property expressions."""
1445
+ self.object_property_expressions = [
1446
+ ObjectPropertyExpression.safe(ope) for ope in object_property_expressions
1447
+ ]
1448
+
1449
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
1450
+ """Represent the list of object property expressions for RDF."""
1451
+ nodes = [ope.to_rdflib_node(graph, converter) for ope in self.object_property_expressions]
1452
+ return _make_sequence_nodes(graph, nodes)
1453
+
1454
+ def to_funowl_args(self) -> str:
1455
+ """Get the inside of the functional OWL tag representing the object property chain."""
1456
+ return list_to_funowl(self.object_property_expressions)
1457
+
1458
+
1459
+ SubObjectPropertyExpression: TypeAlias = ObjectPropertyExpression | ObjectPropertyChain
1460
+
1461
+
1462
+ class SubObjectPropertyOf(ObjectPropertyAxiom): # 9.2.1
1463
+ """An object property axiom defined in `9.2.1 "Object Subproperties" <https://www.w3.org/TR/owl2-syntax/#Object_Subproperties>`_."""
1464
+
1465
+ child: SubObjectPropertyExpression
1466
+ parent: ObjectPropertyExpression
1467
+
1468
+ def __init__(
1469
+ self,
1470
+ child: SubObjectPropertyExpression | IdentifierBoxOrHint,
1471
+ parent: ObjectPropertyExpression | IdentifierBoxOrHint,
1472
+ *,
1473
+ annotations: Annotations | None = None,
1474
+ ) -> None:
1475
+ """Instantiate an object subproperty axiom."""
1476
+ if isinstance(child, ObjectPropertyChain):
1477
+ self.child = child
1478
+ else:
1479
+ self.child = ObjectPropertyExpression.safe(child)
1480
+ self.parent = ObjectPropertyExpression.safe(parent)
1481
+ super().__init__(annotations)
1482
+
1483
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
1484
+ """Represent the object subproperty axiom for RDF."""
1485
+ s = self.child.to_rdflib_node(graph, converter)
1486
+ o = self.parent.to_rdflib_node(graph, converter)
1487
+ if isinstance(self.child, ObjectInverseOf):
1488
+ self.child.declare_wrapped_ope(graph, converter)
1489
+ if isinstance(self.parent, ObjectInverseOf):
1490
+ self.parent.declare_wrapped_ope(graph, converter)
1491
+ if isinstance(self.child, ObjectPropertyChain):
1492
+ return _add_triple(
1493
+ graph, o, OWL.propertyChainAxiom, s, self.annotations, converter=converter
1494
+ )
1495
+ else:
1496
+ return _add_triple(
1497
+ graph, s, RDFS.subPropertyOf, o, self.annotations, converter=converter
1498
+ )
1499
+
1500
+ def _funowl_inside_2(self) -> str:
1501
+ return f"{self.child.to_funowl()} {self.parent.to_funowl()}"
1502
+
1503
+
1504
+ class _ObjectPropertyList(ObjectPropertyAxiom):
1505
+ """A model for an object property axiom that accepts a list of object property expressions."""
1506
+
1507
+ object_property_expressions: Sequence[ObjectPropertyExpression]
1508
+
1509
+ def __init__(
1510
+ self,
1511
+ object_property_expressions: Sequence[ObjectPropertyExpression | IdentifierBoxOrHint],
1512
+ *,
1513
+ annotations: Annotations | None = None,
1514
+ ) -> None:
1515
+ """Instntiate an object property list."""
1516
+ if len(object_property_expressions) < 2:
1517
+ raise ValueError
1518
+ self.object_property_expressions = [
1519
+ ObjectPropertyExpression.safe(ope) for ope in object_property_expressions
1520
+ ]
1521
+ super().__init__(annotations)
1522
+
1523
+ def _funowl_inside_2(self) -> str:
1524
+ return list_to_funowl(self.object_property_expressions)
1525
+
1526
+
1527
+ def _equivalent_xxx(
1528
+ graph: Graph,
1529
+ expressions: Iterable[Box],
1530
+ *,
1531
+ annotations: Annotations | None = None,
1532
+ converter: Converter,
1533
+ ) -> term.BNode:
1534
+ nodes = [expression.to_rdflib_node(graph, converter) for expression in expressions]
1535
+ for s, o in itt.combinations(nodes, 2):
1536
+ _add_triple(graph, s, OWL.equivalentProperty, o, annotations, converter=converter)
1537
+ # an unused blank node is returned here, since adding
1538
+ # these relationship doesn't correspond to a node itself
1539
+ return term.BNode()
1540
+
1541
+
1542
+ class EquivalentObjectProperties(_ObjectPropertyList):
1543
+ """An object property axiom defined in `9.2.2 "Equivalent Object Subproperties" <https://www.w3.org/TR/owl2-syntax/#Equivalent_Object_Properties>`_."""
1544
+
1545
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
1546
+ """Represent the equivalent object subproperties axiom for RDF."""
1547
+ return _equivalent_xxx(
1548
+ graph,
1549
+ self.object_property_expressions,
1550
+ annotations=self.annotations,
1551
+ converter=converter,
1552
+ )
1553
+
1554
+
1555
+ def _disjoint_xxx(
1556
+ graph: Graph,
1557
+ expressions: Iterable[Box],
1558
+ *,
1559
+ annotations: Annotations | None = None,
1560
+ converter: Converter,
1561
+ ) -> term.BNode:
1562
+ nodes = [expression.to_rdflib_node(graph, converter) for expression in expressions]
1563
+ nodes = sorted(nodes, key=str)
1564
+ if len(nodes) == 2:
1565
+ return _add_triple(
1566
+ graph, nodes[0], OWL.propertyDisjointWith, nodes[1], annotations, converter=converter
1567
+ )
1568
+ else:
1569
+ node = term.BNode()
1570
+ graph.add((node, RDF.type, OWL.AllDisjointProperties))
1571
+ _add_triple(
1572
+ graph,
1573
+ node,
1574
+ OWL.members,
1575
+ _make_sequence_nodes(graph, nodes),
1576
+ annotations,
1577
+ converter=converter,
1578
+ )
1579
+ return node
1580
+
1581
+
1582
+ class DisjointObjectProperties(_ObjectPropertyList): # 9.2.3
1583
+ """An object property axiom defined in `9.2.3 "Disjoint Object Properties" <https://www.w3.org/TR/owl2-syntax/#Disjoint_Object_Properties>`_."""
1584
+
1585
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
1586
+ """Represent the disjoint object properties axiom for RDF."""
1587
+ return _disjoint_xxx(
1588
+ graph,
1589
+ self.object_property_expressions,
1590
+ annotations=self.annotations,
1591
+ converter=converter,
1592
+ )
1593
+
1594
+
1595
+ class InverseObjectProperties(ObjectPropertyAxiom): # 9.2.4
1596
+ """An object property axiom defined in `9.2.4 "Inverse Object Properties" <https://www.w3.org/TR/owl2-syntax/#Inverse_Object_Properties_2>`_.
1597
+
1598
+ For example, having a father is the opposite of being a father of someone:
1599
+
1600
+ >>> InverseObjectProperties("a:hasFather", "a:fatherOf")
1601
+ """
1602
+
1603
+ left: ObjectPropertyExpression
1604
+ right: ObjectPropertyExpression
1605
+
1606
+ def __init__(
1607
+ self,
1608
+ left: ObjectPropertyExpression | IdentifierBoxOrHint,
1609
+ right: ObjectPropertyExpression | IdentifierBoxOrHint,
1610
+ *,
1611
+ annotations: Annotations | None = None,
1612
+ ) -> None:
1613
+ """Initialize an inverse object property axiom."""
1614
+ self.left = ObjectPropertyExpression.safe(left)
1615
+ self.right = ObjectPropertyExpression.safe(right)
1616
+ super().__init__(annotations)
1617
+
1618
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
1619
+ """Represent the inverse object property axiom for RDF."""
1620
+ # note these are backwards, since everything is backwards in OFN :shrug:
1621
+ s = self.right.to_rdflib_node(graph, converter)
1622
+ o = self.left.to_rdflib_node(graph, converter)
1623
+ return _add_triple(graph, s, OWL.inverseOf, o, self.annotations, converter=converter)
1624
+
1625
+ def _funowl_inside_2(self) -> str:
1626
+ return f"{self.left.to_funowl()} {self.right.to_funowl()}"
1627
+
1628
+
1629
+ class _ObjectPropertyTyping(ObjectPropertyAxiom): # 9.2.4
1630
+ """An object property axiom for a range or domain."""
1631
+
1632
+ property_type: ClassVar[term.URIRef]
1633
+ object_property_expression: ObjectPropertyExpression
1634
+ value: ClassExpression
1635
+
1636
+ def __init__(
1637
+ self,
1638
+ left: ObjectPropertyExpression | IdentifierBoxOrHint,
1639
+ right: ClassExpression | IdentifierBoxOrHint,
1640
+ *,
1641
+ annotations: Annotations | None = None,
1642
+ ) -> None:
1643
+ """Initialize a object property domain or range."""
1644
+ self.object_property_expression = ObjectPropertyExpression.safe(left)
1645
+ self.value = ClassExpression.safe(right)
1646
+ super().__init__(annotations)
1647
+
1648
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
1649
+ """Represent the object property domain or range for RDF."""
1650
+ s = self.object_property_expression.to_rdflib_node(graph, converter)
1651
+ o = self.value.to_rdflib_node(graph, converter)
1652
+ return _add_triple(graph, s, self.property_type, o, self.annotations, converter=converter)
1653
+
1654
+ def _funowl_inside_2(self) -> str:
1655
+ return f"{self.object_property_expression.to_funowl()} {self.value.to_funowl()}"
1656
+
1657
+
1658
+ class ObjectPropertyDomain(_ObjectPropertyTyping): # 9.2.5
1659
+ """An object property axiom defined in `9.2.5 "Object Property Domain" <https://www.w3.org/TR/owl2-syntax/#Object_Property_Domain>`_.
1660
+
1661
+ Consider the ontology consisting of the following axioms.
1662
+
1663
+ >>> ObjectPropertyDomain("a:hasDog", "a:Person") # Only people can own dogs.
1664
+ >>> ObjectPropertyAssertion("a:hasDog", "a:Peter", "a:Brian") # Brian is a dog of Peter.
1665
+
1666
+ This ontology therefore entails:
1667
+
1668
+ >>> ClassAssertion("a:Person", "a:Peter") # Peter is a person
1669
+ """
1670
+
1671
+ property_type: ClassVar[term.URIRef] = RDFS.domain
1672
+
1673
+
1674
+ class ObjectPropertyRange(_ObjectPropertyTyping): # 9.2.6
1675
+ """An object property axiom defined in `9.2.5 "Object Property Range" <https://www.w3.org/TR/owl2-syntax/#Object_Property_Range>`_.
1676
+
1677
+ Consider the ontology consisting of the following axioms.
1678
+
1679
+ >>> # The range of the a:hasDog property is the class a:Dog.
1680
+ >>> ObjectPropertyRange("a:hasDog", "a:Dog")
1681
+ >>> ObjectPropertyAssertion("a:hasDog", "a:Peter", "a:Brian") # Brian is a dog of Peter.
1682
+
1683
+ By the first axiom, each individual that has an incoming
1684
+ ``a:hasDog`` connection must be an instance of ``a:Dog``.
1685
+ Therefore, ``a:Brian`` can be classified as an instance of
1686
+ ``a:Dog``; that is, this ontology entails the following assertion:
1687
+
1688
+ >>> ClassAssertion("a:Dog", "a:Brian")
1689
+ """
1690
+
1691
+ property_type: ClassVar[term.URIRef] = RDFS.range
1692
+
1693
+
1694
+ class _UnaryObjectProperty(ObjectPropertyAxiom):
1695
+ """A grouping class for object property axioms with a single argument."""
1696
+
1697
+ property_type: ClassVar[term.URIRef]
1698
+ object_property_expression: ObjectPropertyExpression
1699
+
1700
+ def __init__(
1701
+ self,
1702
+ object_property_expression: ObjectPropertyExpression | IdentifierBoxOrHint,
1703
+ *,
1704
+ annotations: Annotations | None = None,
1705
+ ) -> None:
1706
+ """Initialize a unary object property axiom."""
1707
+ self.object_property_expression = ObjectPropertyExpression.safe(object_property_expression)
1708
+ super().__init__(annotations)
1709
+
1710
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
1711
+ """Represent the unary object property axiom for RDF."""
1712
+ return _add_triple(
1713
+ graph,
1714
+ self.object_property_expression.to_rdflib_node(graph, converter),
1715
+ RDF.type,
1716
+ self.property_type,
1717
+ self.annotations,
1718
+ converter=converter,
1719
+ )
1720
+
1721
+ def _funowl_inside_2(self) -> str:
1722
+ return self.object_property_expression.to_funowl()
1723
+
1724
+
1725
+ class FunctionalObjectProperty(_UnaryObjectProperty): # 9.2.7
1726
+ """An object property axiom defined in `9.2.7 "Functional Object Properties" <https://www.w3.org/TR/owl2-syntax/#Functional_Object_Properties>`_.
1727
+
1728
+ Consider the ontology consisting of the following axioms.
1729
+
1730
+ >>> FunctionalObjectProperty("a:hasFather") # Each object can have at most one father.
1731
+ >>> ObjectPropertyAssertion("a:hasFather", "a:Stewie", "a:Peter") # Peter is Stewie's father.
1732
+ >>> ObjectPropertyAssertion(
1733
+ ... "a:hasFather", "a:Stewie", "a:Peter_Griffin"
1734
+ ... ) # Peter Griffin is Stewie's father.
1735
+
1736
+ By the first axiom, ``a:hasFather`` can point from a:Stewie to
1737
+ at most one distinct individual, so ``a:Peter`` and ``a:Peter_Griffin``
1738
+ must be equal; that is, this ontology entails the following assertion:
1739
+
1740
+ >>> SameIndividual(["a:Peter", "a:Peter_Griffin"])
1741
+
1742
+ One might expect the previous ontology to be inconsistent, since
1743
+ the a:hasFather property points to two different values for
1744
+ ``a:Stewie``. OWL 2, however, does not make the unique name assumption,
1745
+ so ``a:Peter`` and ``a:Peter_Griffin`` are not necessarily distinct individuals.
1746
+ If the ontology were extended with the following assertion, then it
1747
+ would indeed become inconsistent:
1748
+
1749
+ >>> DifferentIndividuals(["a:Peter", "a:Peter_Griffin"])
1750
+ """
1751
+
1752
+ property_type = OWL.FunctionalProperty
1753
+
1754
+
1755
+ class InverseFunctionalObjectProperty(_UnaryObjectProperty):
1756
+ """An object property axiom defined in `9.2.8 "Inverse-Functional Object Properties" <https://www.w3.org/TR/owl2-syntax/#Inverse-Functional_Object_Properties>`_."""
1757
+
1758
+ property_type: ClassVar[term.URIRef] = OWL.InverseFunctionalProperty
1759
+
1760
+
1761
+ class ReflexiveObjectProperty(_UnaryObjectProperty):
1762
+ """An object property axiom defined in `9.2.9 "Irreflexive Object Properties" <https://www.w3.org/TR/owl2-syntax/#Reflexive_Object_Properties>`_."""
1763
+
1764
+ property_type: ClassVar[term.URIRef] = OWL.ReflexiveProperty
1765
+
1766
+
1767
+ class IrreflexiveObjectProperty(_UnaryObjectProperty):
1768
+ """An object property axiom defined in `9.2.10 "Reflexive Object Properties" <https://www.w3.org/TR/owl2-syntax/#Ireflexive_Object_Properties>`_."""
1769
+
1770
+ property_type: ClassVar[term.URIRef] = OWL.IrreflexiveProperty
1771
+
1772
+
1773
+ class SymmetricObjectProperty(_UnaryObjectProperty):
1774
+ """An object property axiom defined in `9.2.11 "Symmetric Object Properties" <https://www.w3.org/TR/owl2-syntax/#Symmetric_Object_Properties>`_."""
1775
+
1776
+ property_type: ClassVar[term.URIRef] = OWL.SymmetricProperty
1777
+
1778
+
1779
+ class AsymmetricObjectProperty(_UnaryObjectProperty): # 9.2.12
1780
+ """An object property axiom defined in `9.2.12 "Asymmetric Object Properties" <https://www.w3.org/TR/owl2-syntax/#Asymmetric_Object_Properties>`_."""
1781
+
1782
+ property_type: ClassVar[term.URIRef] = OWL.AsymmetricProperty
1783
+
1784
+
1785
+ class TransitiveObjectProperty(_UnaryObjectProperty): # 9.2.13
1786
+ """An object property axiom defined in `9.2.13 "Transitive Object Properties" <https://www.w3.org/TR/owl2-syntax/#Transitive_Object_Properties>`_."""
1787
+
1788
+ property_type: ClassVar[term.URIRef] = OWL.TransitiveProperty
1789
+
1790
+
1791
+ """9.3: Data Property Axioms"""
1792
+
1793
+
1794
+ class DataPropertyAxiom(Axiom):
1795
+ """A model for `9.3 "Data Property Axioms" <https://www.w3.org/TR/owl2-syntax/#Data_Property_Axioms>`_."""
1796
+
1797
+
1798
+ class SubDataPropertyOf(DataPropertyAxiom):
1799
+ """A data property axiom for `9.3.1 "Data Subproperties" <https://www.w3.org/TR/owl2-syntax/#Data_Subproperties>`_."""
1800
+
1801
+ child: DataPropertyExpression
1802
+ parent: DataPropertyExpression
1803
+
1804
+ def __init__(
1805
+ self,
1806
+ child: DataPropertyExpression | IdentifierBoxOrHint,
1807
+ parent: DataPropertyExpression | IdentifierBoxOrHint,
1808
+ *,
1809
+ annotations: Annotations | None = None,
1810
+ ) -> None:
1811
+ """Instantiate a data subproperties axiom."""
1812
+ self.child = DataPropertyExpression.safe(child)
1813
+ self.parent = DataPropertyExpression.safe(parent)
1814
+ super().__init__(annotations)
1815
+
1816
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
1817
+ """Represent the data subproperties axiom for RDF."""
1818
+ s = self.child.to_rdflib_node(graph, converter)
1819
+ o = self.parent.to_rdflib_node(graph, converter)
1820
+ return _add_triple(graph, s, RDFS.subPropertyOf, o, self.annotations, converter=converter)
1821
+
1822
+ def _funowl_inside_2(self) -> str:
1823
+ return f"{self.child.to_funowl()} {self.parent.to_funowl()}"
1824
+
1825
+
1826
+ class _DataPropertyList(DataPropertyAxiom):
1827
+ """A model for a data property axiom that takes a list of data property expressions."""
1828
+
1829
+ data_property_expressions: Sequence[DataPropertyExpression]
1830
+
1831
+ def __init__(
1832
+ self,
1833
+ data_property_expressions: Sequence[DataPropertyExpression | IdentifierBoxOrHint],
1834
+ *,
1835
+ annotations: Annotations | None = None,
1836
+ ) -> None:
1837
+ """Instantiate a data property list axiom."""
1838
+ if len(data_property_expressions) < 2:
1839
+ raise ValueError
1840
+ self.data_property_expressions = [
1841
+ DataPropertyExpression.safe(dpe) for dpe in data_property_expressions
1842
+ ]
1843
+ super().__init__(annotations)
1844
+
1845
+ def _funowl_inside_2(self) -> str:
1846
+ return list_to_funowl(self.data_property_expressions)
1847
+
1848
+
1849
+ class EquivalentDataProperties(_DataPropertyList):
1850
+ """A data property axiom for `9.3.2 "Equivalent Data Properties" <https://www.w3.org/TR/owl2-syntax/#Equivalent_Data_Properties>`_."""
1851
+
1852
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
1853
+ """Represent the equivalent data properties axiom for RDF."""
1854
+ for dpe in self.data_property_expressions:
1855
+ graph.add((dpe.to_rdflib_node(graph, converter), RDF.type, OWL.DatatypeProperty))
1856
+ return _equivalent_xxx(
1857
+ graph,
1858
+ self.data_property_expressions,
1859
+ annotations=self.annotations,
1860
+ converter=converter,
1861
+ )
1862
+
1863
+
1864
+ class DisjointDataProperties(_DataPropertyList):
1865
+ """A data property axiom for `9.3.3 "Disjoint Data Properties" <https://www.w3.org/TR/owl2-syntax/#Disjoint_Data_Properties>`_."""
1866
+
1867
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
1868
+ """Represent the disjoint data properties axiom for RDF."""
1869
+ for dpe in self.data_property_expressions:
1870
+ graph.add((dpe.to_rdflib_node(graph, converter), RDF.type, OWL.DatatypeProperty))
1871
+ return _disjoint_xxx(
1872
+ graph, self.data_property_expressions, annotations=self.annotations, converter=converter
1873
+ )
1874
+
1875
+
1876
+ class _DataPropertyTyping(DataPropertyAxiom): # 9.2.4
1877
+ """An axiom that represents the range or domain of a data property."""
1878
+
1879
+ property_type: ClassVar[term.URIRef]
1880
+ data_property_expression: DataPropertyExpression
1881
+ target: Box
1882
+
1883
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
1884
+ """Represent the equivalent data properties axiom for RDF."""
1885
+ s = self.data_property_expression.to_rdflib_node(graph, converter)
1886
+ graph.add((s, RDF.type, OWL.DatatypeProperty))
1887
+ o = self.target.to_rdflib_node(graph, converter)
1888
+ return _add_triple(graph, s, self.property_type, o, self.annotations, converter=converter)
1889
+
1890
+ def _funowl_inside_2(self) -> str:
1891
+ return f"{self.data_property_expression.to_funowl()} {self.target.to_funowl()}"
1892
+
1893
+
1894
+ class DataPropertyDomain(_DataPropertyTyping): # 9.3.4
1895
+ """A data property axiom for `9.3.4 "Data Property Domain" <https://www.w3.org/TR/owl2-syntax/#Data_Property_Domain>`_."""
1896
+
1897
+ property_type: ClassVar[term.URIRef] = RDFS.domain
1898
+
1899
+ def __init__(
1900
+ self,
1901
+ data_property_expression: DataPropertyExpression | IdentifierBoxOrHint,
1902
+ class_expression: ClassExpression | IdentifierBoxOrHint,
1903
+ *,
1904
+ annotations: Annotations | None = None,
1905
+ ) -> None:
1906
+ """Instantiate a data property domain axiom."""
1907
+ self.data_property_expression = DataPropertyExpression.safe(data_property_expression)
1908
+ self.target = ClassExpression.safe(class_expression)
1909
+ super().__init__(annotations)
1910
+
1911
+
1912
+ class DataPropertyRange(_DataPropertyTyping):
1913
+ """A data property axiom for `9.3.5 "Data Property Range" <https://www.w3.org/TR/owl2-syntax/#Data_Property_Range>`_."""
1914
+
1915
+ property_type: ClassVar[term.URIRef] = RDFS.range
1916
+
1917
+ def __init__(
1918
+ self,
1919
+ data_property_expression: DataPropertyExpression | IdentifierBoxOrHint,
1920
+ data_range: DataRange | IdentifierBoxOrHint,
1921
+ *,
1922
+ annotations: Annotations | None = None,
1923
+ ) -> None:
1924
+ """Instantiate a data property range axiom."""
1925
+ self.data_property_expression = DataPropertyExpression.safe(data_property_expression)
1926
+ self.target = DataRange.safe(data_range)
1927
+ super().__init__(annotations)
1928
+
1929
+
1930
+ class FunctionalDataProperty(DataPropertyAxiom):
1931
+ """A data property axiom for `9.3.6 "Functional Data Properties" <https://www.w3.org/TR/owl2-syntax/#Functional_Data_Properties>`_.
1932
+
1933
+ Consider the ontology consisting of the following axioms.
1934
+
1935
+ >>> FunctionalDataProperty("a:hasAge") # Each object can have at most one age.
1936
+ >>> DataPropertyAssertion("a:hasAge", "a:Meg", 17) # Meg is seventeen years old.
1937
+
1938
+ By the first axiom, ``a:hasAge`` can point from ``a:Meg`` to at most one
1939
+ distinct literal. In this example ontology, this axiom is satisfied. If,
1940
+ however, the ontology were extended with the following assertion, the
1941
+ semantics of functionality axioms would imply that ``"15"^^xsd:integer`` is
1942
+ equal to ``"17"^^xsd:integer``, which is a contradiction and the ontology
1943
+ would become inconsistent:
1944
+
1945
+ >>> DataPropertyAssertion("a:hasAge", "a:Meg", 15)
1946
+ """
1947
+
1948
+ data_property_expression: DataPropertyExpression
1949
+
1950
+ def __init__(
1951
+ self,
1952
+ data_property_expression: DataPropertyExpression | IdentifierBoxOrHint,
1953
+ *,
1954
+ annotations: Annotations | None = None,
1955
+ ):
1956
+ """Instantiate a functional data property axiom."""
1957
+ self.data_property_expression = DataPropertyExpression.safe(data_property_expression)
1958
+ super().__init__(annotations)
1959
+
1960
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
1961
+ """Represent the functional data property for RDF."""
1962
+ return _add_triple(
1963
+ graph,
1964
+ self.data_property_expression.to_rdflib_node(graph, converter),
1965
+ RDF.type,
1966
+ OWL.FunctionalProperty,
1967
+ self.annotations,
1968
+ converter=converter,
1969
+ )
1970
+
1971
+ def _funowl_inside_2(self) -> str:
1972
+ return self.data_property_expression.to_funowl()
1973
+
1974
+
1975
+ """Section 9.4: Datatype Definitions"""
1976
+
1977
+
1978
+ class DatatypeDefinition(Axiom):
1979
+ """A model for `9.4 "Datatype Definitions" <https://www.w3.org/TR/owl2-syntax/#Datatype_Definitions>`_."""
1980
+
1981
+ datatype: IdentifierBox
1982
+ data_range: DataRange
1983
+
1984
+ def __init__(
1985
+ self,
1986
+ datatype: IdentifierBoxOrHint,
1987
+ data_range: DataRange | IdentifierBoxOrHint,
1988
+ *,
1989
+ annotations: Annotations | None = None,
1990
+ ) -> None:
1991
+ """Instantiate a datatype definition axiom."""
1992
+ self.datatype = IdentifierBox(datatype)
1993
+ self.data_range = DataRange.safe(data_range)
1994
+ super().__init__(annotations)
1995
+
1996
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
1997
+ """Represent the datatype definition axiom for RDF."""
1998
+ s = self.datatype.to_rdflib_node(graph, converter)
1999
+ graph.add((s, RDF.type, RDFS.Datatype))
2000
+ return _add_triple(
2001
+ graph,
2002
+ s,
2003
+ OWL.equivalentClass,
2004
+ self.data_range.to_rdflib_node(graph, converter),
2005
+ annotations=self.annotations,
2006
+ converter=converter,
2007
+ )
2008
+
2009
+ def _funowl_inside_2(self) -> str:
2010
+ return f"{self.datatype.to_funowl()} {self.data_range.to_funowl()}"
2011
+
2012
+
2013
+ """Section 9.5: Keys"""
2014
+
2015
+
2016
+ class HasKey(Axiom):
2017
+ """An axiom for `9.5 "Keys" <https://www.w3.org/TR/owl2-syntax/#Keys>`_."""
2018
+
2019
+ class_expression: ClassExpression
2020
+ object_property_expressions: list[ObjectPropertyExpression]
2021
+ data_property_expressions: list[DataPropertyExpression]
2022
+
2023
+ def __init__(
2024
+ self,
2025
+ class_expression: ClassExpression | IdentifierBoxOrHint,
2026
+ object_property_expressions: Sequence[ObjectPropertyExpression | IdentifierBoxOrHint],
2027
+ data_property_expressions: Sequence[DataPropertyExpression | IdentifierBoxOrHint],
2028
+ *,
2029
+ annotations: Annotations | None = None,
2030
+ ) -> None:
2031
+ """Instantiate a "has key" axiom."""
2032
+ self.class_expression = ClassExpression.safe(class_expression)
2033
+ self.object_property_expressions = [
2034
+ ObjectPropertyExpression.safe(ope) for ope in object_property_expressions
2035
+ ]
2036
+ self.data_property_expressions = [
2037
+ DataPropertyExpression.safe(dpe) for dpe in data_property_expressions
2038
+ ]
2039
+ super().__init__(annotations)
2040
+
2041
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
2042
+ """Represent the "has key" axiom for RDF."""
2043
+ object_and_data_property_expressions: list[
2044
+ ObjectPropertyExpression | DataPropertyExpression
2045
+ ] = []
2046
+ object_and_data_property_expressions.extend(self.object_property_expressions)
2047
+ object_and_data_property_expressions.extend(self.data_property_expressions)
2048
+
2049
+ return _add_triple(
2050
+ graph,
2051
+ self.class_expression.to_rdflib_node(graph, converter),
2052
+ OWL.hasKey,
2053
+ _make_sequence(graph, object_and_data_property_expressions, converter),
2054
+ annotations=self.annotations,
2055
+ converter=converter,
2056
+ )
2057
+
2058
+ def _funowl_inside_2(self) -> str:
2059
+ aa = f"{self.class_expression.to_funowl()}"
2060
+ if self.object_property_expressions:
2061
+ aa += f" ( {list_to_funowl(self.object_property_expressions)} )"
2062
+ else:
2063
+ aa += " ()"
2064
+ if self.data_property_expressions:
2065
+ aa += f" ( {list_to_funowl(self.data_property_expressions)} )"
2066
+ else:
2067
+ aa += " ()"
2068
+ return aa
2069
+
2070
+
2071
+ """Section 9.6: Assertions"""
2072
+
2073
+
2074
+ class Assertion(Axiom):
2075
+ """Axioms for `9.6 "Assertions" <https://www.w3.org/TR/owl2-syntax/#Assertions>`_."""
2076
+
2077
+
2078
+ class _IndividualListAssertion(Assertion):
2079
+ """A grouping class for individual equality and inequality axioms."""
2080
+
2081
+ individuals: Sequence[IdentifierBox]
2082
+
2083
+ def __init__(
2084
+ self,
2085
+ individuals: Sequence[IdentifierBoxOrHint],
2086
+ *,
2087
+ annotations: Annotations | None = None,
2088
+ ) -> None:
2089
+ """Instantiate an individual list axiom."""
2090
+ self.individuals = [IdentifierBox(i) for i in individuals]
2091
+ super().__init__(annotations)
2092
+
2093
+ def _funowl_inside_2(self) -> str:
2094
+ return list_to_funowl(self.individuals)
2095
+
2096
+
2097
+ class SameIndividual(_IndividualListAssertion):
2098
+ """An axiom for `9.6.1 "Individual Equality" <https://www.w3.org/TR/owl2-syntax/#Individual_Equality>`_."""
2099
+
2100
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
2101
+ """Represent the individual equality axiom for RDF."""
2102
+ nodes = [i.to_rdflib_node(graph, converter) for i in self.individuals]
2103
+ for node in nodes:
2104
+ graph.add((node, RDF.type, OWL.NamedIndividual))
2105
+ for s, o in itt.combinations(nodes, 2):
2106
+ _add_triple(graph, s, OWL.sameAs, o, annotations=self.annotations, converter=converter)
2107
+ # TODO connect this node to triples?
2108
+ return term.BNode()
2109
+
2110
+
2111
+ class DifferentIndividuals(_IndividualListAssertion):
2112
+ """An axiom for `9.6.2 "Individual Inequality" <https://www.w3.org/TR/owl2-syntax/#Individual_Inequality>`_."""
2113
+
2114
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
2115
+ """Represent the individual inequality axiom for RDF."""
2116
+ nodes = [i.to_rdflib_node(graph, converter) for i in self.individuals]
2117
+ for node in nodes:
2118
+ graph.add((node, RDF.type, OWL.NamedIndividual))
2119
+ if len(nodes) == 2:
2120
+ return _add_triple(
2121
+ graph,
2122
+ nodes[0],
2123
+ OWL.differentFrom,
2124
+ nodes[1],
2125
+ annotations=self.annotations,
2126
+ converter=converter,
2127
+ )
2128
+ else:
2129
+ node = term.BNode()
2130
+ graph.add((node, RDF.type, OWL.AllDifferent))
2131
+ graph.add((node, OWL.distinctMembers, _make_sequence_nodes(graph, nodes)))
2132
+ # FIXME add annotations
2133
+ return node
2134
+
2135
+
2136
+ class ClassAssertion(Assertion):
2137
+ """An axiom for `9.6.3 "Class Assertions" <https://www.w3.org/TR/owl2-syntax/#Class_Assertions>`_."""
2138
+
2139
+ class_expression: ClassExpression
2140
+ individual: IdentifierBox
2141
+
2142
+ def __init__(
2143
+ self,
2144
+ class_expression: ClassExpression | IdentifierBoxOrHint,
2145
+ individual: IdentifierBoxOrHint,
2146
+ *,
2147
+ annotations: Annotations | None = None,
2148
+ ) -> None:
2149
+ """Instantiate a class assertion axiom."""
2150
+ self.class_expression = ClassExpression.safe(class_expression)
2151
+ self.individual = IdentifierBox(individual)
2152
+ super().__init__(annotations)
2153
+
2154
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
2155
+ """Represent the class assertion axiom for RDF."""
2156
+ s = self.individual.to_rdflib_node(graph, converter)
2157
+ graph.add((s, RDF.type, OWL.NamedIndividual))
2158
+ return _add_triple(
2159
+ graph,
2160
+ s,
2161
+ RDF.type,
2162
+ self.class_expression.to_rdflib_node(graph, converter),
2163
+ annotations=self.annotations,
2164
+ converter=converter,
2165
+ )
2166
+
2167
+ def _funowl_inside_2(self) -> str:
2168
+ return f"{self.class_expression.to_funowl()} {self.individual.to_funowl()}"
2169
+
2170
+
2171
+ class _BaseObjectPropertyAssertion(Assertion):
2172
+ """A grouping class for positive and negative object property assertion axioms."""
2173
+
2174
+ object_property_expression: ObjectPropertyExpression
2175
+ source_individual: IdentifierBox
2176
+ target_individual: IdentifierBox
2177
+
2178
+ def __init__(
2179
+ self,
2180
+ object_property_expression: ObjectPropertyExpression | IdentifierBoxOrHint,
2181
+ source_individual: IdentifierBoxOrHint,
2182
+ target_individual: IdentifierBoxOrHint,
2183
+ *,
2184
+ annotations: Annotations | None = None,
2185
+ ) -> None:
2186
+ """Initialize an object property assertion axiom."""
2187
+ self.object_property_expression = ObjectPropertyExpression.safe(object_property_expression)
2188
+ self.source_individual = IdentifierBox(source_individual)
2189
+ self.target_individual = IdentifierBox(target_individual)
2190
+ super().__init__(annotations)
2191
+
2192
+ def _funowl_inside_2(self) -> str:
2193
+ return f"{self.object_property_expression.to_funowl()} {self.source_individual.to_funowl()} {self.target_individual.to_funowl()}"
2194
+
2195
+
2196
+ class ObjectPropertyAssertion(_BaseObjectPropertyAssertion):
2197
+ """An axiom for `9.6.4 "Positive Object Property Assertions" <https://www.w3.org/TR/owl2-syntax/#Positive_Object_Property_Assertions>`_."""
2198
+
2199
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
2200
+ """Represent the positive object property assertion axiom for RDF."""
2201
+ s = self.source_individual.to_rdflib_node(graph, converter)
2202
+ o = self.target_individual.to_rdflib_node(graph, converter)
2203
+ graph.add((s, RDF.type, OWL.NamedIndividual))
2204
+ graph.add((o, RDF.type, OWL.NamedIndividual))
2205
+ if isinstance(self.object_property_expression, ObjectInverseOf):
2206
+ # flip them around
2207
+ s, o = o, s
2208
+ # unpack the inverse property
2209
+ p = self.object_property_expression.object_property.to_rdflib_node(graph, converter)
2210
+ # make sure the inverse property is declared
2211
+ graph.add((p, RDF.type, OWL.ObjectProperty))
2212
+ else:
2213
+ p = self.object_property_expression.to_rdflib_node(graph, converter)
2214
+
2215
+ return _add_triple(graph, s, p, o, annotations=self.annotations, converter=converter)
2216
+
2217
+
2218
+ class NegativeObjectPropertyAssertion(_BaseObjectPropertyAssertion):
2219
+ """An axiom for `9.6.5 "Negative Object Property Assertions" <https://www.w3.org/TR/owl2-syntax/#Negative_Object_Property_Assertions>`_."""
2220
+
2221
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
2222
+ """Represent the negative object property assertion axiom for RDF."""
2223
+ s = self.source_individual.to_rdflib_node(graph, converter)
2224
+ o = self.target_individual.to_rdflib_node(graph, converter)
2225
+ graph.add((s, RDF.type, OWL.NamedIndividual))
2226
+ graph.add((o, RDF.type, OWL.NamedIndividual))
2227
+ if isinstance(self.object_property_expression, ObjectInverseOf):
2228
+ # TODO OWLAPI is not consistent with the way ObjectPropertyAssertion and
2229
+ # NegativeObjectPropertyAssertion work. For some reason, it doesn't
2230
+ # flip the subject and object / unpack the inverse OPE for
2231
+ # NegativeObjectPropertyAssertion. Therefore, it's implemented here
2232
+ # to reflect that. Need to make an issue on OWLAPI about this
2233
+ self.object_property_expression.declare_wrapped_ope(graph, converter)
2234
+ return _add_triple_annotations(
2235
+ graph,
2236
+ s,
2237
+ self.object_property_expression.to_rdflib_node(graph, converter),
2238
+ o,
2239
+ annotations=self.annotations,
2240
+ type=OWL.NegativePropertyAssertion,
2241
+ converter=converter,
2242
+ force_for_negative_assertion=True,
2243
+ reified_p=OWL.assertionProperty,
2244
+ reified_s=OWL.sourceIndividual,
2245
+ reified_o=OWL.targetIndividual,
2246
+ )
2247
+
2248
+
2249
+ class _BaseDataPropertyAssertion(Assertion):
2250
+ """A grouping class for positive and negative data property assertion axioms."""
2251
+
2252
+ source_individual: IdentifierBox
2253
+ target: PrimitiveBox
2254
+
2255
+ def __init__(
2256
+ self,
2257
+ data_property_expression: DataPropertyExpression | IdentifierBoxOrHint,
2258
+ source_individual: IdentifierBoxOrHint,
2259
+ target: PrimitiveHint,
2260
+ *,
2261
+ annotations: Annotations | None = None,
2262
+ ) -> None:
2263
+ """Initialize a data property assertion axiom."""
2264
+ self.data_property_expression = DataPropertyExpression.safe(data_property_expression)
2265
+ self.source_individual = IdentifierBox(source_individual)
2266
+ self.target = _safe_primitive_box(target)
2267
+ super().__init__(annotations)
2268
+
2269
+ def _funowl_inside_2(self) -> str:
2270
+ return f"{self.data_property_expression.to_funowl()} {self.source_individual.to_funowl()} {self.target.to_funowl()}"
2271
+
2272
+
2273
+ class DataPropertyAssertion(_BaseDataPropertyAssertion):
2274
+ """An axiom for `9.6.6 "Positive Data Property Assertions" <https://www.w3.org/TR/owl2-syntax/#Positive_Data_Property_Assertions>`_.
2275
+
2276
+ >>> DataPropertyAssertion("a:hasAge", "a:Meg", 17)
2277
+ """
2278
+
2279
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
2280
+ """Represent the positive data property assertion axiom for RDF."""
2281
+ s = self.source_individual.to_rdflib_node(graph, converter)
2282
+ graph.add((s, RDF.type, OWL.NamedIndividual))
2283
+ return _add_triple(
2284
+ graph,
2285
+ s,
2286
+ self.data_property_expression.to_rdflib_node(graph, converter),
2287
+ self.target.to_rdflib_node(graph, converter),
2288
+ annotations=self.annotations,
2289
+ converter=converter,
2290
+ )
2291
+
2292
+
2293
+ class NegativeDataPropertyAssertion(_BaseDataPropertyAssertion):
2294
+ """An axiom for `9.6.7 "Negative Data Property Assertions" <https://www.w3.org/TR/owl2-syntax/#Negative_Data_Property_Assertions>`_.
2295
+
2296
+ >>> NegativeDataPropertyAssertion("a:hasAge", "a:Meg", 5)
2297
+ """
2298
+
2299
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
2300
+ """Represent the negative data property assertion axiom for RDF."""
2301
+ s = self.source_individual.to_rdflib_node(graph, converter)
2302
+ graph.add((s, RDF.type, OWL.NamedIndividual))
2303
+ return _add_triple_annotations(
2304
+ graph,
2305
+ s,
2306
+ self.data_property_expression.to_rdflib_node(graph, converter),
2307
+ self.target.to_rdflib_node(graph, converter),
2308
+ annotations=self.annotations,
2309
+ type=OWL.NegativePropertyAssertion,
2310
+ converter=converter,
2311
+ force_for_negative_assertion=True,
2312
+ reified_p=OWL.assertionProperty,
2313
+ reified_s=OWL.sourceIndividual,
2314
+ reified_o=OWL.targetValue,
2315
+ )
2316
+
2317
+
2318
+ """Section 10: Annotations"""
2319
+
2320
+
2321
+ class Annotation(Box): # 10.1
2322
+ """An element defined in `10.1 "Annotations of Ontologies, Axioms, and other Annotations" <https://www.w3.org/TR/owl2-syntax/#Annotations_of_Ontologies.2C_Axioms.2C_and_other_Annotations>`_.
2323
+
2324
+ .. image:: https://www.w3.org/TR/owl2-syntax/Annotations.gif
2325
+
2326
+ Annotations can be used to add additional context, like curation provenance, to
2327
+ assertions
2328
+
2329
+ >>> AnnotationAssertion(
2330
+ ... "skos:exactMatch",
2331
+ ... "agrovoc:0619dd9e",
2332
+ ... "agro:00000137",
2333
+ ... annotations=[
2334
+ ... Annotation("dcterms:contributor", "orcid:0000-0003-4423-4370"),
2335
+ ... Annotation("sssom:mapping_justification", "semapv:ManualMappingCuration"),
2336
+ ... ],
2337
+ ... )
2338
+
2339
+ Annotations can even be used on themselves, adding arbitrary levels of detail.
2340
+ In the following example, we annotate the affiliation of the contributor
2341
+ via the `wd:P1416 (affiliation) <https://www.wikidata.org/wiki/Property:P1416>`_
2342
+ predicate.
2343
+
2344
+ >>> AnnotationAssertion(
2345
+ ... "skos:exactMatch",
2346
+ ... "agrovoc:0619dd9e",
2347
+ ... "agro:00000137",
2348
+ ... annotations=[
2349
+ ... Annotation(
2350
+ ... "dcterms:contributor",
2351
+ ... "orcid:0000-0003-4423-4370",
2352
+ ... annotations=[
2353
+ ... Annotation("wd:P1416", "wd:Q126066280"),
2354
+ ... ],
2355
+ ... ),
2356
+ ... Annotation("sssom:mapping_justification", "semapv:ManualMappingCuration"),
2357
+ ... ],
2358
+ ... )
2359
+ """
2360
+
2361
+ annotation_property: IdentifierBox
2362
+ value: PrimitiveBox
2363
+ annotations: list[Annotation]
2364
+
2365
+ def __init__(
2366
+ self,
2367
+ annotation_property: IdentifierBoxOrHint,
2368
+ value: PrimitiveHint,
2369
+ *,
2370
+ annotations: list[Annotation] | None = None,
2371
+ ) -> None:
2372
+ """Initialize an annotation."""
2373
+ self.annotation_property = IdentifierBox(annotation_property)
2374
+ self.value = _safe_primitive_box(value)
2375
+ self.annotations = annotations or []
2376
+
2377
+ def to_rdflib_node(
2378
+ self, graph: Graph, converter: Converter
2379
+ ) -> term.IdentifiedNode: # pragma: no cover
2380
+ """Represent the annotation as an RDF node (unused)."""
2381
+ raise RuntimeError
2382
+
2383
+ def _add_to_triple(
2384
+ self,
2385
+ graph: Graph,
2386
+ subject: term.IdentifiedNode,
2387
+ converter: Converter,
2388
+ ) -> None:
2389
+ annotation_property = self.annotation_property.to_rdflib_node(graph, converter)
2390
+ annotation_object = self.value.to_rdflib_node(graph, converter)
2391
+ graph.add((annotation_property, RDF.type, OWL.AnnotationProperty))
2392
+ graph.add((subject, annotation_property, annotation_object))
2393
+ if self.annotations:
2394
+ _add_triple_annotations(
2395
+ graph,
2396
+ subject,
2397
+ annotation_property,
2398
+ annotation_object,
2399
+ converter=converter,
2400
+ annotations=self.annotations,
2401
+ type=OWL.Annotation,
2402
+ )
2403
+
2404
+ def to_funowl_args(self) -> str:
2405
+ """Get the inside of the functional OWL tag representing the annotation."""
2406
+ end = f"{self.annotation_property.to_funowl()} {self.value.to_funowl()}"
2407
+ if self.annotations:
2408
+ return list_to_funowl(self.annotations) + " " + end
2409
+ return end
2410
+
2411
+
2412
+ Annotations: TypeAlias = list[Annotation]
2413
+
2414
+
2415
+ class AnnotationAxiom(Axiom): # 10.2
2416
+ """A grouping class for annotation axioms defined in `10.2 "Axiom Annotations" <https://www.w3.org/TR/owl2-syntax/#Annotation_Axioms>`_.
2417
+
2418
+ .. image:: https://www.w3.org/TR/owl2-syntax/A_annotation.gif
2419
+ """
2420
+
2421
+
2422
+ class AnnotationProperty(IdentifierBox):
2423
+ """A wrapper around an identifier box with custom functionality."""
2424
+
2425
+ #: A set of built-in annotation properties that shouldn't be re-defined, since they
2426
+ #: appear in Table 3 of https://www.w3.org/TR/owl2-syntax/#IRIs.
2427
+ _SKIP: ClassVar[set[term.Node]] = {
2428
+ OWL.backwardCompatibleWith,
2429
+ OWL.deprecated,
2430
+ OWL.incompatibleWith,
2431
+ OWL.priorVersion,
2432
+ OWL.versionInfo,
2433
+ RDFS.comment,
2434
+ RDFS.isDefinedBy,
2435
+ RDFS.label,
2436
+ RDFS.seeAlso,
2437
+ }
2438
+
2439
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
2440
+ """Represent the annotation property for RDF."""
2441
+ node = super().to_rdflib_node(graph, converter)
2442
+ if node in self._SKIP:
2443
+ return node
2444
+ graph.add((node, RDF.type, OWL.AnnotationProperty))
2445
+ return node
2446
+
2447
+
2448
+ class AnnotationAssertion(AnnotationAxiom): # 10.2.1
2449
+ """An annotation axiom defined in `10.2.1 Annotation Assertion <https://www.w3.org/TR/owl2-syntax/#Annotation_Assertion>`_."""
2450
+
2451
+ annotation_property: AnnotationProperty
2452
+ subject: IdentifierBox
2453
+ value: PrimitiveBox
2454
+
2455
+ def __init__(
2456
+ self,
2457
+ annotation_property: IdentifierBoxOrHint,
2458
+ subject: IdentifierBoxOrHint,
2459
+ value: PrimitiveHint,
2460
+ *,
2461
+ annotations: list[Annotation] | None = None,
2462
+ ) -> None:
2463
+ """Initialize an annotation assertion axiom."""
2464
+ self.annotation_property = AnnotationProperty(annotation_property)
2465
+ self.subject = IdentifierBox(subject)
2466
+ self.value = _safe_primitive_box(value)
2467
+ super().__init__(annotations)
2468
+
2469
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
2470
+ """Represent the annotation assertion axiom for RDF."""
2471
+ return _add_triple(
2472
+ graph,
2473
+ self.subject.to_rdflib_node(graph, converter),
2474
+ self.annotation_property.to_rdflib_node(graph, converter),
2475
+ self.value.to_rdflib_node(graph, converter),
2476
+ annotations=self.annotations,
2477
+ converter=converter,
2478
+ )
2479
+
2480
+ def _funowl_inside_2(self) -> str:
2481
+ return " ".join(
2482
+ (
2483
+ self.annotation_property.to_funowl(),
2484
+ self.subject.to_funowl(),
2485
+ self.value.to_funowl(),
2486
+ )
2487
+ )
2488
+
2489
+
2490
+ class SubAnnotationPropertyOf(AnnotationAxiom): # 10.2.2
2491
+ """An annotation axiom defined in `10.2.2 Annotation Subproperties <https://www.w3.org/TR/owl2-syntax/#Annotation_Subproperties>`_."""
2492
+
2493
+ child: AnnotationProperty
2494
+ parent: AnnotationProperty
2495
+
2496
+ def __init__(
2497
+ self,
2498
+ child: IdentifierBoxOrHint,
2499
+ parent: IdentifierBoxOrHint,
2500
+ *,
2501
+ annotations: Annotations | None = None,
2502
+ ) -> None:
2503
+ """Initialize an annotation subproperty axiom."""
2504
+ self.child = AnnotationProperty(child)
2505
+ self.parent = AnnotationProperty(parent)
2506
+ super().__init__(annotations)
2507
+
2508
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
2509
+ """Represent the annotation subproperty axiom for RDF."""
2510
+ s = self.child.to_rdflib_node(graph, converter)
2511
+ o = self.parent.to_rdflib_node(graph, converter)
2512
+ return _add_triple(graph, s, RDFS.subPropertyOf, o, self.annotations, converter=converter)
2513
+
2514
+ def _funowl_inside_2(self) -> str:
2515
+ return f"{self.child.to_funowl()} {self.parent.to_funowl()}"
2516
+
2517
+
2518
+ class AnnotationPropertyTypingAxiom(AnnotationAxiom):
2519
+ """A helper class that defines shared functionality between annotation property domains and ranges."""
2520
+
2521
+ property_type: ClassVar[term.URIRef]
2522
+ annotation_property: AnnotationProperty
2523
+ value: PrimitiveBox
2524
+
2525
+ def __init__(
2526
+ self,
2527
+ annotation_property: IdentifierBoxOrHint,
2528
+ value: PrimitiveHint,
2529
+ *,
2530
+ annotations: Annotations | None = None,
2531
+ ) -> None:
2532
+ """Initialize an annotation property range or domain axiom."""
2533
+ self.annotation_property = AnnotationProperty(annotation_property)
2534
+ self.value = _safe_primitive_box(value)
2535
+ super().__init__(annotations)
2536
+
2537
+ def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
2538
+ """Represent the annotation property range or domain axiom for RDF."""
2539
+ s = self.annotation_property.to_rdflib_node(graph, converter)
2540
+ o = self.value.to_rdflib_node(graph, converter)
2541
+ graph.add((s, RDF.type, OWL.AnnotationProperty))
2542
+ return _add_triple(graph, s, self.property_type, o, self.annotations, converter=converter)
2543
+
2544
+ def _funowl_inside_2(self) -> str:
2545
+ return f"{self.annotation_property.to_funowl()} {self.value.to_funowl()}"
2546
+
2547
+
2548
+ class AnnotationPropertyDomain(AnnotationPropertyTypingAxiom): # 10.2.3
2549
+ """An annotation axiom defined in `10.2.3 Annotation Property Domain <https://www.w3.org/TR/owl2-syntax/#Annotation_Property_Domain>`_."""
2550
+
2551
+ property_type: ClassVar[term.URIRef] = RDFS.domain
2552
+
2553
+
2554
+ class AnnotationPropertyRange(AnnotationPropertyTypingAxiom): # 10.2.4
2555
+ """An annotation axiom defined in `10.2.4 Annotation Property Range <https://www.w3.org/TR/owl2-syntax/#Annotation_Property_Range>`_.
2556
+
2557
+ For example, the range of all ``rdfs:label`` should be a string.
2558
+ This can be represented as with the functional OWL
2559
+ ``AnnotationPropertyRange( rdfs:label xsd:string )``, or in
2560
+ Python like the following:
2561
+
2562
+ Using :mod:`rdflib` namespaces:
2563
+
2564
+ >>> from rdflib import RDFS, XSD
2565
+ >>> AnnotationPropertyRange(RDFS.label, XSD.string)
2566
+
2567
+ Using a string:
2568
+
2569
+ >>> AnnotationPropertyRange("rdfs:label", "xsd:string")
2570
+ """
2571
+
2572
+ property_type: ClassVar[term.URIRef] = RDFS.range