personal_knowledge_library 3.0.0__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.
Potentially problematic release.
This version of personal_knowledge_library might be problematic. Click here for more details.
- knowledge/__init__.py +91 -0
- knowledge/base/__init__.py +22 -0
- knowledge/base/access.py +167 -0
- knowledge/base/entity.py +267 -0
- knowledge/base/language.py +27 -0
- knowledge/base/ontology.py +2734 -0
- knowledge/base/search.py +473 -0
- knowledge/base/tenant.py +192 -0
- knowledge/nel/__init__.py +11 -0
- knowledge/nel/base.py +495 -0
- knowledge/nel/engine.py +123 -0
- knowledge/ontomapping/__init__.py +667 -0
- knowledge/ontomapping/manager.py +320 -0
- knowledge/public/__init__.py +27 -0
- knowledge/public/cache.py +115 -0
- knowledge/public/helper.py +373 -0
- knowledge/public/relations.py +128 -0
- knowledge/public/wikidata.py +1324 -0
- knowledge/services/__init__.py +128 -0
- knowledge/services/asyncio/__init__.py +7 -0
- knowledge/services/asyncio/base.py +458 -0
- knowledge/services/asyncio/graph.py +1420 -0
- knowledge/services/asyncio/group.py +450 -0
- knowledge/services/asyncio/search.py +439 -0
- knowledge/services/asyncio/users.py +270 -0
- knowledge/services/base.py +533 -0
- knowledge/services/graph.py +1897 -0
- knowledge/services/group.py +819 -0
- knowledge/services/helper.py +142 -0
- knowledge/services/ontology.py +1234 -0
- knowledge/services/search.py +488 -0
- knowledge/services/session.py +444 -0
- knowledge/services/tenant.py +281 -0
- knowledge/services/users.py +445 -0
- knowledge/utils/__init__.py +10 -0
- knowledge/utils/graph.py +417 -0
- knowledge/utils/wikidata.py +197 -0
- knowledge/utils/wikipedia.py +175 -0
- personal_knowledge_library-3.0.0.dist-info/LICENSE +201 -0
- personal_knowledge_library-3.0.0.dist-info/METADATA +1163 -0
- personal_knowledge_library-3.0.0.dist-info/RECORD +42 -0
- personal_knowledge_library-3.0.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Copyright © 2021-present Wacom. All rights reserved.
|
|
3
|
+
import enum
|
|
4
|
+
import json
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Dict, Any, List, Optional, Set
|
|
8
|
+
|
|
9
|
+
import loguru
|
|
10
|
+
from rdflib import Graph, RDFS, URIRef
|
|
11
|
+
|
|
12
|
+
from knowledge.base.ontology import OntologyClassReference, OntologyPropertyReference, DataPropertyType
|
|
13
|
+
from knowledge.public.wikidata import WikidataClass, WikiDataAPIClient
|
|
14
|
+
|
|
15
|
+
# Classes
|
|
16
|
+
TOPIC_CLASS: str = "wacom:core#Topic"
|
|
17
|
+
# Constants
|
|
18
|
+
DBPEDIA_TYPES: str = "dbpedia_types"
|
|
19
|
+
WIKIDATA_TYPES: str = "wikidata_types"
|
|
20
|
+
OBJECT_PROPERTIES: str = "object_properties"
|
|
21
|
+
DATA_PROPERTIES: str = "data_properties"
|
|
22
|
+
DOMAIN_PROPERTIES: str = "domain"
|
|
23
|
+
CLASSES: str = "classes"
|
|
24
|
+
CONTEXT_NAME: str = "core"
|
|
25
|
+
CWD: Path = Path(__file__).parent
|
|
26
|
+
ontology_graph: Graph = Graph()
|
|
27
|
+
logger = loguru.logger
|
|
28
|
+
# Mapping configuration
|
|
29
|
+
superclasses_cache: Optional[Dict[str, WikidataClass]] = None
|
|
30
|
+
subclasses_cache: Optional[Dict[str, WikidataClass]] = None
|
|
31
|
+
superclasses_path: Optional[Path] = None
|
|
32
|
+
subclasses_path: Optional[Path] = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def flatten(hierarchy: WikidataClass) -> Set[str]:
|
|
36
|
+
"""
|
|
37
|
+
Flattens the hierarchy.
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
hierarchy: WikidataClass
|
|
42
|
+
Hierarchy
|
|
43
|
+
|
|
44
|
+
Returns
|
|
45
|
+
-------
|
|
46
|
+
hierarchy: Set[str]
|
|
47
|
+
Hierarchy
|
|
48
|
+
|
|
49
|
+
"""
|
|
50
|
+
hierarchy_set: Set[str] = {hierarchy.qid}
|
|
51
|
+
jobs: List[WikidataClass] = [hierarchy]
|
|
52
|
+
while len(jobs) > 0:
|
|
53
|
+
job: WikidataClass = jobs.pop()
|
|
54
|
+
hierarchy_set.add(job.qid)
|
|
55
|
+
for c in job.subclasses:
|
|
56
|
+
jobs.append(c)
|
|
57
|
+
return hierarchy_set
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def subclasses_of(iri: str) -> List[str]:
|
|
61
|
+
"""
|
|
62
|
+
Returns the subclasses of an ontology class.
|
|
63
|
+
Parameters
|
|
64
|
+
----------
|
|
65
|
+
iri: str
|
|
66
|
+
Ontology class IRI.
|
|
67
|
+
|
|
68
|
+
Returns
|
|
69
|
+
-------
|
|
70
|
+
subclasses: List[str]
|
|
71
|
+
Subclasses of the ontology class.
|
|
72
|
+
"""
|
|
73
|
+
sub_classes: List[str] = [str(s) for s, p, o in list(ontology_graph.triples((None, RDFS.subClassOf, URIRef(iri))))]
|
|
74
|
+
for sub_class in sub_classes:
|
|
75
|
+
sub_classes.extend(subclasses_of(sub_class))
|
|
76
|
+
return sub_classes
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def is_iso_date(date_string: str) -> bool:
|
|
80
|
+
"""
|
|
81
|
+
Checks if a date string is an ISO date.
|
|
82
|
+
Parameters
|
|
83
|
+
----------
|
|
84
|
+
date_string: str
|
|
85
|
+
Date string.
|
|
86
|
+
|
|
87
|
+
Returns
|
|
88
|
+
-------
|
|
89
|
+
is_iso_date: bool
|
|
90
|
+
True if the date string is an ISO date, otherwise False.
|
|
91
|
+
"""
|
|
92
|
+
try:
|
|
93
|
+
datetime.fromisoformat(date_string)
|
|
94
|
+
return True
|
|
95
|
+
except ValueError as _:
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class WikidataClassEncoder(json.JSONEncoder):
|
|
100
|
+
"""
|
|
101
|
+
Wikidata Class encoder
|
|
102
|
+
----------------------
|
|
103
|
+
This class encodes a Wikidata class to JSON.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
def default(self, o):
|
|
107
|
+
if isinstance(o, WikidataClass):
|
|
108
|
+
return o.__dict__()
|
|
109
|
+
return json.JSONEncoder.default(self, o)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class ClassConfiguration:
|
|
113
|
+
"""
|
|
114
|
+
Class configuration
|
|
115
|
+
-------------------
|
|
116
|
+
This class contains the configuration for a class.
|
|
117
|
+
|
|
118
|
+
Parameters
|
|
119
|
+
----------
|
|
120
|
+
ontology_class: str
|
|
121
|
+
Ontology class
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
def __init__(self, ontology_class: str):
|
|
125
|
+
self.__ontology_class: str = ontology_class
|
|
126
|
+
self.__wikidata_classes: List[str] = []
|
|
127
|
+
self.__dbpedia_classes: List[str] = []
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def ontology_class(self) -> str:
|
|
131
|
+
"""Ontology class."""
|
|
132
|
+
return self.__ontology_class
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def wikidata_classes(self) -> List[str]:
|
|
136
|
+
"""Wikidata classes."""
|
|
137
|
+
return self.__wikidata_classes
|
|
138
|
+
|
|
139
|
+
@wikidata_classes.setter
|
|
140
|
+
def wikidata_classes(self, value: List[str]):
|
|
141
|
+
self.__wikidata_classes = value
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def dbpedia_classes(self) -> List[str]:
|
|
145
|
+
"""DBpedia classes."""
|
|
146
|
+
return self.__dbpedia_classes
|
|
147
|
+
|
|
148
|
+
@dbpedia_classes.setter
|
|
149
|
+
def dbpedia_classes(self, value: List[str]):
|
|
150
|
+
self.__dbpedia_classes = value
|
|
151
|
+
|
|
152
|
+
@property
|
|
153
|
+
def concept_type(self) -> OntologyClassReference:
|
|
154
|
+
"""Concept type."""
|
|
155
|
+
return OntologyClassReference.parse(self.__ontology_class)
|
|
156
|
+
|
|
157
|
+
def __str__(self):
|
|
158
|
+
return (
|
|
159
|
+
f"ClassConfiguration(ontology_class={self.__ontology_class}, "
|
|
160
|
+
f"wikidata_classes={self.__wikidata_classes}, dbpedia_classes={self.__dbpedia_classes})"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class PropertyType(enum.Enum):
|
|
165
|
+
"""
|
|
166
|
+
Property type
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
DATA_PROPERTY = 0
|
|
170
|
+
OBJECT_PROPERTY = 1
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class PropertyConfiguration:
|
|
174
|
+
"""
|
|
175
|
+
Property configuration.
|
|
176
|
+
-----------------------
|
|
177
|
+
This class contains the configuration for a property.
|
|
178
|
+
|
|
179
|
+
Parameters
|
|
180
|
+
----------
|
|
181
|
+
iri: str
|
|
182
|
+
The IRI of the property.
|
|
183
|
+
property_type: PropertyType
|
|
184
|
+
The property type.
|
|
185
|
+
pids: Optional[List[str]]
|
|
186
|
+
The list of property PIDs.
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
def __init__(self, iri: str, property_type: PropertyType, pids: Optional[List[str]] = None):
|
|
190
|
+
self.__iri: str = iri
|
|
191
|
+
self.__pids: List[str] = pids if pids else []
|
|
192
|
+
self.__property: PropertyType = property_type
|
|
193
|
+
self.__inverse: Optional[str] = None
|
|
194
|
+
self.__ranges: List[str] = []
|
|
195
|
+
self.__domains: List[str] = []
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def iri(self) -> str:
|
|
199
|
+
"""IRI of the property."""
|
|
200
|
+
return self.__iri
|
|
201
|
+
|
|
202
|
+
@iri.setter
|
|
203
|
+
def iri(self, value: str):
|
|
204
|
+
self.__iri = value
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
def inverse(self) -> Optional[str]:
|
|
208
|
+
"""Inverse property."""
|
|
209
|
+
return self.__inverse
|
|
210
|
+
|
|
211
|
+
@inverse.setter
|
|
212
|
+
def inverse(self, value: str):
|
|
213
|
+
self.__inverse = value
|
|
214
|
+
|
|
215
|
+
@property
|
|
216
|
+
def type(self) -> PropertyType:
|
|
217
|
+
"""Property type."""
|
|
218
|
+
return self.__property
|
|
219
|
+
|
|
220
|
+
@property
|
|
221
|
+
def pids(self) -> List[str]:
|
|
222
|
+
"""List of property PIDs."""
|
|
223
|
+
return self.__pids
|
|
224
|
+
|
|
225
|
+
@property
|
|
226
|
+
def ranges(self) -> List[str]:
|
|
227
|
+
"""List of ranges."""
|
|
228
|
+
return self.__ranges
|
|
229
|
+
|
|
230
|
+
@property
|
|
231
|
+
def domains(self) -> List[str]:
|
|
232
|
+
"""List of domains."""
|
|
233
|
+
return self.__domains
|
|
234
|
+
|
|
235
|
+
def __str__(self):
|
|
236
|
+
return f"PropertyConfiguration(ontology_property={self.iri})"
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class MappingConfiguration:
|
|
240
|
+
"""
|
|
241
|
+
Mapping configuration
|
|
242
|
+
---------------------
|
|
243
|
+
This class contains the configuration for the mapping.
|
|
244
|
+
|
|
245
|
+
"""
|
|
246
|
+
|
|
247
|
+
def __init__(self):
|
|
248
|
+
self.__classes: List[ClassConfiguration] = []
|
|
249
|
+
self.__properties: List[PropertyConfiguration] = []
|
|
250
|
+
self.__index: Dict[str, int] = {}
|
|
251
|
+
self.__index_properties: Dict[str, List[int]] = {}
|
|
252
|
+
self.__index_iri: Dict[str, int] = {}
|
|
253
|
+
self.__direct_index: Dict[str, int] = {}
|
|
254
|
+
|
|
255
|
+
@property
|
|
256
|
+
def classes(self) -> List[ClassConfiguration]:
|
|
257
|
+
"""List of classes."""
|
|
258
|
+
return self.__classes
|
|
259
|
+
|
|
260
|
+
@property
|
|
261
|
+
def properties(self) -> List[PropertyConfiguration]:
|
|
262
|
+
"""List of properties."""
|
|
263
|
+
return self.__properties
|
|
264
|
+
|
|
265
|
+
def guess_classed(self, classes: List[str]) -> Optional[ClassConfiguration]:
|
|
266
|
+
"""
|
|
267
|
+
Guesses the class from the label.
|
|
268
|
+
Parameters
|
|
269
|
+
----------
|
|
270
|
+
classes: List[str]
|
|
271
|
+
The list of classes
|
|
272
|
+
|
|
273
|
+
Returns
|
|
274
|
+
-------
|
|
275
|
+
class: Optional[ClassConfiguration]
|
|
276
|
+
If a mapping exists, the class configuration, otherwise None.
|
|
277
|
+
"""
|
|
278
|
+
for cls_name in classes:
|
|
279
|
+
if cls_name in self.__direct_index:
|
|
280
|
+
return self.__classes[self.__direct_index[cls_name]]
|
|
281
|
+
if cls_name in self.__index:
|
|
282
|
+
return self.__classes[self.__index[cls_name]]
|
|
283
|
+
return None
|
|
284
|
+
|
|
285
|
+
def guess_property(
|
|
286
|
+
self, property_pid: str, concept_type: OntologyClassReference
|
|
287
|
+
) -> Optional[PropertyConfiguration]:
|
|
288
|
+
"""
|
|
289
|
+
Guesses the property from the label.
|
|
290
|
+
Parameters
|
|
291
|
+
----------
|
|
292
|
+
property_pid: str
|
|
293
|
+
PID of the property
|
|
294
|
+
concept_type: OntologyClassReference
|
|
295
|
+
The concept type.
|
|
296
|
+
Returns
|
|
297
|
+
-------
|
|
298
|
+
property_config: Optional[PropertyConfiguration]
|
|
299
|
+
If a mapping exists, the property configuration, otherwise None.
|
|
300
|
+
"""
|
|
301
|
+
if property_pid in self.__index_properties:
|
|
302
|
+
for pid_idx in self.__index_properties[property_pid]:
|
|
303
|
+
prop_conf: PropertyConfiguration = self.__properties[pid_idx]
|
|
304
|
+
if concept_type.iri in prop_conf.domains:
|
|
305
|
+
return prop_conf
|
|
306
|
+
return None
|
|
307
|
+
|
|
308
|
+
def property_for(
|
|
309
|
+
self, class_ref: OntologyClassReference, property_type: Optional[PropertyType]
|
|
310
|
+
) -> List[PropertyConfiguration]:
|
|
311
|
+
"""
|
|
312
|
+
Returns the properties for a class.
|
|
313
|
+
Parameters
|
|
314
|
+
----------
|
|
315
|
+
class_ref: OntologyClassReference
|
|
316
|
+
The class reference.
|
|
317
|
+
property_type: Optional[PropertyType]
|
|
318
|
+
The property type, if None, all properties are returned.
|
|
319
|
+
Returns
|
|
320
|
+
-------
|
|
321
|
+
properties: List[PropertyConfiguration]
|
|
322
|
+
The list of properties.
|
|
323
|
+
"""
|
|
324
|
+
domain_classes: List[str] = [class_ref.iri]
|
|
325
|
+
domain_classes += subclasses_of(class_ref.iri)
|
|
326
|
+
domain_subclasses: Dict[str, List[str]] = {}
|
|
327
|
+
properties: List[PropertyConfiguration] = []
|
|
328
|
+
for prop_conf in self.properties:
|
|
329
|
+
for d in prop_conf.domains:
|
|
330
|
+
if d not in domain_subclasses:
|
|
331
|
+
domain_subclasses[d] = [d] + subclasses_of(d)
|
|
332
|
+
if class_ref.iri in domain_subclasses[d]:
|
|
333
|
+
if property_type is None or prop_conf.type == property_type:
|
|
334
|
+
properties.append(prop_conf)
|
|
335
|
+
return properties
|
|
336
|
+
|
|
337
|
+
def add_class(self, class_configuration: ClassConfiguration, subclasses: Dict[str, List[str]]):
|
|
338
|
+
"""
|
|
339
|
+
Adds a class configuration.
|
|
340
|
+
|
|
341
|
+
Parameters
|
|
342
|
+
----------
|
|
343
|
+
class_configuration: ClassConfiguration
|
|
344
|
+
The class configuration
|
|
345
|
+
subclasses: Dict[str, List[str]]
|
|
346
|
+
The subclasses
|
|
347
|
+
"""
|
|
348
|
+
self.__classes.append(class_configuration)
|
|
349
|
+
class_idx: int = len(self.__classes) - 1
|
|
350
|
+
number_of_classes: int = len(class_configuration.wikidata_classes)
|
|
351
|
+
if number_of_classes > 0:
|
|
352
|
+
logger.info(f"Adding {number_of_classes} classes for {class_configuration.concept_type.iri}")
|
|
353
|
+
for cls_idx, c in enumerate(class_configuration.wikidata_classes):
|
|
354
|
+
if c in subclasses:
|
|
355
|
+
for subclass in subclasses[c]:
|
|
356
|
+
if subclass in self.__index:
|
|
357
|
+
logger.warning(f"Class {subclass} already exists in the index.")
|
|
358
|
+
class_config: ClassConfiguration = self.__classes[self.__index[subclass]]
|
|
359
|
+
logger.warning(f"Class {class_config.concept_type} "
|
|
360
|
+
f"is conflicting with {class_configuration.concept_type}.")
|
|
361
|
+
self.__index[subclass] = class_idx
|
|
362
|
+
self.__direct_index[c] = class_idx
|
|
363
|
+
else:
|
|
364
|
+
w_classes: Dict[str, WikidataClass] = WikiDataAPIClient.subclasses(c)
|
|
365
|
+
for subclass in w_classes.values():
|
|
366
|
+
for cls in flatten(subclass):
|
|
367
|
+
self.__index[cls] = class_idx
|
|
368
|
+
|
|
369
|
+
for c in class_configuration.dbpedia_classes:
|
|
370
|
+
self.__index[c] = len(self.__classes) - 1
|
|
371
|
+
|
|
372
|
+
def add_property(self, property_configuration: PropertyConfiguration):
|
|
373
|
+
"""
|
|
374
|
+
Adds a property configuration.
|
|
375
|
+
|
|
376
|
+
Parameters
|
|
377
|
+
----------
|
|
378
|
+
property_configuration: PropertyConfiguration
|
|
379
|
+
The property configuration
|
|
380
|
+
"""
|
|
381
|
+
self.__properties.append(property_configuration)
|
|
382
|
+
for pid in property_configuration.pids:
|
|
383
|
+
idx: int = len(self.__properties) - 1
|
|
384
|
+
if pid not in self.__index_properties:
|
|
385
|
+
self.__index_properties[pid] = []
|
|
386
|
+
self.__index_properties[pid].append(idx)
|
|
387
|
+
self.__index_iri[property_configuration.iri] = idx
|
|
388
|
+
|
|
389
|
+
def property_for_iri(self, property_iri: str) -> PropertyConfiguration:
|
|
390
|
+
"""
|
|
391
|
+
Returns the property configuration for an IRI.
|
|
392
|
+
|
|
393
|
+
Parameters
|
|
394
|
+
----------
|
|
395
|
+
property_iri: str
|
|
396
|
+
The property IRI
|
|
397
|
+
|
|
398
|
+
Returns
|
|
399
|
+
-------
|
|
400
|
+
property: PropertyConfiguration
|
|
401
|
+
The property configuration
|
|
402
|
+
|
|
403
|
+
Raises
|
|
404
|
+
------
|
|
405
|
+
ValueError
|
|
406
|
+
If the property is not found.
|
|
407
|
+
"""
|
|
408
|
+
if property_iri not in self.__index_iri:
|
|
409
|
+
raise ValueError(f"Property {property_iri} not found.")
|
|
410
|
+
return self.__properties[self.__index_iri[property_iri]]
|
|
411
|
+
|
|
412
|
+
def check_data_property_range(self, property_type: OntologyPropertyReference, content: Optional[Any]) -> bool:
|
|
413
|
+
"""
|
|
414
|
+
Checks if the content is in the range of the property.
|
|
415
|
+
|
|
416
|
+
Parameters
|
|
417
|
+
----------
|
|
418
|
+
property_type: OntologyPropertyReference
|
|
419
|
+
The property type
|
|
420
|
+
content: Optional[Any]
|
|
421
|
+
The content
|
|
422
|
+
|
|
423
|
+
Returns
|
|
424
|
+
-------
|
|
425
|
+
evaluation: bool
|
|
426
|
+
True if the content is in the range, False otherwise.
|
|
427
|
+
"""
|
|
428
|
+
if content is None:
|
|
429
|
+
return False
|
|
430
|
+
prop_config: Optional[PropertyConfiguration] = self.property_for_iri(property_type.iri)
|
|
431
|
+
if prop_config:
|
|
432
|
+
for r in prop_config.ranges:
|
|
433
|
+
if r == DataPropertyType.STRING.value:
|
|
434
|
+
return content is not None and isinstance(content, str)
|
|
435
|
+
if r == DataPropertyType.INTEGER.value:
|
|
436
|
+
return content is not None and isinstance(content, int)
|
|
437
|
+
if r == DataPropertyType.FLOAT.value:
|
|
438
|
+
return content is not None and isinstance(content, float)
|
|
439
|
+
if r == DataPropertyType.BOOLEAN.value:
|
|
440
|
+
return content is not None and isinstance(content, bool)
|
|
441
|
+
if r in {DataPropertyType.DATE.value, DataPropertyType.DATE_TIME.value}:
|
|
442
|
+
return content is not None and isinstance(content, str) and is_iso_date(content)
|
|
443
|
+
return True
|
|
444
|
+
return False
|
|
445
|
+
|
|
446
|
+
def check_object_property_range(
|
|
447
|
+
self,
|
|
448
|
+
property_type: OntologyPropertyReference,
|
|
449
|
+
source_type: OntologyClassReference,
|
|
450
|
+
target_type: OntologyClassReference,
|
|
451
|
+
) -> bool:
|
|
452
|
+
"""
|
|
453
|
+
Checks if the target is in the range of the property.
|
|
454
|
+
Parameters
|
|
455
|
+
----------
|
|
456
|
+
property_type: OntologyPropertyReference
|
|
457
|
+
The property
|
|
458
|
+
source_type: OntologyClassReference
|
|
459
|
+
The concept type
|
|
460
|
+
target_type: OntologyClassReference
|
|
461
|
+
The target type
|
|
462
|
+
|
|
463
|
+
Returns
|
|
464
|
+
-------
|
|
465
|
+
valid: bool
|
|
466
|
+
True if the target is in the range, False otherwise.
|
|
467
|
+
"""
|
|
468
|
+
prop_config: Optional[PropertyConfiguration] = self.property_for_iri(property_type.iri)
|
|
469
|
+
if prop_config:
|
|
470
|
+
if prop_config.type == PropertyType.OBJECT_PROPERTY:
|
|
471
|
+
if source_type.iri in prop_config.domains and target_type.iri in prop_config.ranges:
|
|
472
|
+
return True
|
|
473
|
+
return False
|
|
474
|
+
return False
|
|
475
|
+
|
|
476
|
+
def __str__(self):
|
|
477
|
+
return f"Mapping Configuration(#classes={len(self.__classes)}" f", #properties={len(self.__properties)})"
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
mapping_configuration: Optional[MappingConfiguration] = None
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def build_configuration(mapping: Dict[str, Any], subclasses: Dict[str, List[str]]) -> MappingConfiguration:
|
|
484
|
+
"""
|
|
485
|
+
Builds the configuration from the mapping file.
|
|
486
|
+
Parameters
|
|
487
|
+
----------
|
|
488
|
+
mapping: Dict[str, Any]
|
|
489
|
+
The mapping file
|
|
490
|
+
subclasses: Dict[str, List[str]]
|
|
491
|
+
The subclasses
|
|
492
|
+
|
|
493
|
+
Returns
|
|
494
|
+
-------
|
|
495
|
+
conf: MappingConfiguration
|
|
496
|
+
The mapping configuration
|
|
497
|
+
"""
|
|
498
|
+
conf: MappingConfiguration = MappingConfiguration()
|
|
499
|
+
configuration_classes: int = len(mapping["classes"])
|
|
500
|
+
logger.debug(f"Adding {configuration_classes} classes to the mapping configuration")
|
|
501
|
+
for c, c_conf in mapping["classes"].items():
|
|
502
|
+
class_config: ClassConfiguration = ClassConfiguration(c)
|
|
503
|
+
class_config.dbpedia_classes = c_conf[DBPEDIA_TYPES]
|
|
504
|
+
class_config.wikidata_classes = c_conf[WIKIDATA_TYPES]
|
|
505
|
+
conf.add_class(class_config, subclasses)
|
|
506
|
+
dataproperty_count: int = len(mapping["data_properties"])
|
|
507
|
+
logger.debug(f"Adding {dataproperty_count} data properties to the mapping configuration")
|
|
508
|
+
for p, p_conf in mapping["data_properties"].items():
|
|
509
|
+
property_config: PropertyConfiguration = PropertyConfiguration(
|
|
510
|
+
p, PropertyType.DATA_PROPERTY, p_conf["wikidata_types"]
|
|
511
|
+
)
|
|
512
|
+
if "ranges" in p_conf:
|
|
513
|
+
for ra in p_conf["ranges"]:
|
|
514
|
+
property_config.ranges.append(ra)
|
|
515
|
+
property_config.ranges.extend(subclasses_of(ra))
|
|
516
|
+
if "domains" in p_conf:
|
|
517
|
+
for do in p_conf["domains"]:
|
|
518
|
+
property_config.domains.append(do)
|
|
519
|
+
property_config.domains.extend(subclasses_of(do))
|
|
520
|
+
|
|
521
|
+
conf.add_property(property_config)
|
|
522
|
+
object_property_count: int = len(mapping["object_properties"])
|
|
523
|
+
logger.debug(f"Adding {object_property_count} object properties to the mapping configuration")
|
|
524
|
+
for p, p_conf in mapping["object_properties"].items():
|
|
525
|
+
property_config: PropertyConfiguration = PropertyConfiguration(
|
|
526
|
+
p, PropertyType.OBJECT_PROPERTY, p_conf["wikidata_types"]
|
|
527
|
+
)
|
|
528
|
+
if "ranges" in p_conf:
|
|
529
|
+
for ra in p_conf["ranges"]:
|
|
530
|
+
property_config.ranges.append(ra)
|
|
531
|
+
property_config.ranges.extend(subclasses_of(ra))
|
|
532
|
+
if "domains" in p_conf:
|
|
533
|
+
for do in p_conf["domains"]:
|
|
534
|
+
property_config.domains.append(do)
|
|
535
|
+
property_config.domains.extend(subclasses_of(do))
|
|
536
|
+
if "inverse" in p_conf:
|
|
537
|
+
property_config.inverse = p_conf["inverse"]
|
|
538
|
+
conf.add_property(property_config)
|
|
539
|
+
return conf
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
def update_superclass_cache(path: Path):
|
|
543
|
+
"""
|
|
544
|
+
Updates the taxonomy cache.
|
|
545
|
+
|
|
546
|
+
Parameters
|
|
547
|
+
----------
|
|
548
|
+
path: Path
|
|
549
|
+
The path to the cache file.
|
|
550
|
+
"""
|
|
551
|
+
with open(path, "w", encoding="uft-8") as fp_taxonomy_write:
|
|
552
|
+
fp_taxonomy_write.write(json.dumps(superclasses_cache, indent=2, cls=WikidataClassEncoder))
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
def register_ontology(rdf_str: str):
|
|
556
|
+
"""
|
|
557
|
+
Registers the ontology.
|
|
558
|
+
Parameters
|
|
559
|
+
----------
|
|
560
|
+
rdf_str: str
|
|
561
|
+
The ontology in RDF/XML format.
|
|
562
|
+
"""
|
|
563
|
+
ontology_graph.parse(data=rdf_str, format="xml")
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def superclass_path_from(configuration_path: Path) -> Path:
|
|
567
|
+
"""
|
|
568
|
+
Returns the path to the superclass cache file.
|
|
569
|
+
|
|
570
|
+
Parameters
|
|
571
|
+
----------
|
|
572
|
+
configuration_path: Path
|
|
573
|
+
The path to the configuration file.
|
|
574
|
+
|
|
575
|
+
Returns
|
|
576
|
+
-------
|
|
577
|
+
path: Path
|
|
578
|
+
The path to the superclass cache file.
|
|
579
|
+
"""
|
|
580
|
+
return configuration_path.parent / "superclasses.json"
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
def subclass_path_from(path: Path):
|
|
584
|
+
"""
|
|
585
|
+
Returns the path to the subclass cache file.
|
|
586
|
+
|
|
587
|
+
Parameters
|
|
588
|
+
----------
|
|
589
|
+
path: Path
|
|
590
|
+
The path to the configuration file.
|
|
591
|
+
|
|
592
|
+
Returns
|
|
593
|
+
-------
|
|
594
|
+
subclass_path: Path
|
|
595
|
+
The path to the subclass cache file.
|
|
596
|
+
"""
|
|
597
|
+
return path.parent / "subclasses.json"
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
def load_configuration(configuration: Path):
|
|
601
|
+
"""
|
|
602
|
+
Loads the configuration.
|
|
603
|
+
|
|
604
|
+
Raises
|
|
605
|
+
------
|
|
606
|
+
ValueError
|
|
607
|
+
If the configuration file is not found.
|
|
608
|
+
"""
|
|
609
|
+
global mapping_configuration, superclasses_cache, subclasses_cache, superclasses_path, subclasses_path
|
|
610
|
+
subclasses_path = subclass_path_from(configuration)
|
|
611
|
+
superclasses_path = superclass_path_from(configuration)
|
|
612
|
+
if configuration.exists():
|
|
613
|
+
with configuration.open("r", encoding="utf-8") as fp_configuration:
|
|
614
|
+
configuration = json.loads(fp_configuration.read())
|
|
615
|
+
subclasses: Dict[str, List[str]] = {}
|
|
616
|
+
if subclasses_path.exists():
|
|
617
|
+
with subclasses_path.open("r", encoding="utf-8") as fp_sub:
|
|
618
|
+
subclasses = json.loads(fp_sub.read())
|
|
619
|
+
mapping_configuration = build_configuration(configuration, subclasses)
|
|
620
|
+
else:
|
|
621
|
+
raise ValueError(f"Configuration file {configuration} not found.")
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
def get_mapping_configuration() -> MappingConfiguration:
|
|
625
|
+
"""
|
|
626
|
+
Returns the mapping configuration.
|
|
627
|
+
|
|
628
|
+
Returns
|
|
629
|
+
-------
|
|
630
|
+
mapping_configuration: MappingConfiguration
|
|
631
|
+
The mapping configuration
|
|
632
|
+
"""
|
|
633
|
+
if mapping_configuration is None:
|
|
634
|
+
raise ValueError("Please load configuration")
|
|
635
|
+
return mapping_configuration
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
def save_superclasses_cache(path: Path):
|
|
639
|
+
"""
|
|
640
|
+
Saves the taxonomy cache.
|
|
641
|
+
|
|
642
|
+
Parameters
|
|
643
|
+
----------
|
|
644
|
+
path: Path
|
|
645
|
+
The path to the cache file.
|
|
646
|
+
"""
|
|
647
|
+
global superclasses_cache
|
|
648
|
+
if superclasses_cache is None:
|
|
649
|
+
return
|
|
650
|
+
with open(path, "w", encoding="utf-8") as fp_taxonomy_write:
|
|
651
|
+
fp_taxonomy_write.write(json.dumps(superclasses_cache, indent=2, cls=WikidataClassEncoder))
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
def save_subclasses_cache(path: Path):
|
|
655
|
+
"""
|
|
656
|
+
Saves the taxonomy cache.
|
|
657
|
+
|
|
658
|
+
Parameters
|
|
659
|
+
----------
|
|
660
|
+
path: Path
|
|
661
|
+
The path to the cache file.
|
|
662
|
+
"""
|
|
663
|
+
global subclasses_cache
|
|
664
|
+
if subclasses_cache is None:
|
|
665
|
+
return
|
|
666
|
+
with open(path, "w", encoding="utf-8") as fp_taxonomy_write:
|
|
667
|
+
fp_taxonomy_write.write(json.dumps(subclasses_cache, indent=2, cls=WikidataClassEncoder))
|