oldaplib 0.3.3__py3-none-any.whl → 0.3.5__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.
- oldaplib/ontologies/admin-testing.trig +2 -4
- oldaplib/ontologies/admin.trig +2 -4
- oldaplib/ontologies/oldap.trig +221 -131
- oldaplib/ontologies/shared.trig +0 -3
- oldaplib/src/datamodel.py +109 -17
- oldaplib/src/dtypes/namespaceiri.py +16 -1
- oldaplib/src/enums/attributeclass.py +31 -6
- oldaplib/src/enums/externalontologyattr.py +22 -0
- oldaplib/src/enums/owlpropertytype.py +10 -0
- oldaplib/src/enums/projectattr.py +0 -1
- oldaplib/src/enums/propertyclassattr.py +24 -19
- oldaplib/src/externalontology.py +554 -0
- oldaplib/src/hasproperty.py +5 -0
- oldaplib/src/helpers/context.py +4 -4
- oldaplib/src/helpers/langstring.py +11 -2
- oldaplib/src/helpers/observable_dict.py +4 -1
- oldaplib/src/helpers/observable_set.py +135 -80
- oldaplib/src/helpers/query_processor.py +3 -0
- oldaplib/src/model.py +1 -1
- oldaplib/src/oldaplist.py +2 -2
- oldaplib/src/oldaplistnode.py +2 -2
- oldaplib/src/permissionset.py +3 -3
- oldaplib/src/project.py +47 -113
- oldaplib/src/propertyclass.py +98 -36
- oldaplib/src/resourceclass.py +7 -5
- oldaplib/src/version.py +1 -1
- oldaplib/src/xsd/iri.py +3 -0
- oldaplib/src/xsd/xsd_anyuri.py +5 -5
- oldaplib/src/xsd/xsd_qname.py +6 -3
- oldaplib/test/test_context.py +0 -4
- oldaplib/test/test_datamodel.py +50 -1
- oldaplib/test/test_dtypes.py +3 -2
- oldaplib/test/test_externalontologies.py +175 -0
- oldaplib/test/test_project.py +31 -76
- oldaplib/test/test_propertyclass.py +168 -6
- oldaplib/test/test_resourceclass.py +10 -10
- oldaplib/testdata/connection_test.trig +29 -12
- oldaplib/testdata/datamodel_test.trig +1 -1
- oldaplib/testdata/objectfactory_test.trig +2 -1
- {oldaplib-0.3.3.dist-info → oldaplib-0.3.5.dist-info}/METADATA +3 -2
- {oldaplib-0.3.3.dist-info → oldaplib-0.3.5.dist-info}/RECORD +42 -39
- {oldaplib-0.3.3.dist-info → oldaplib-0.3.5.dist-info}/WHEEL +1 -1
oldaplib/src/project.py
CHANGED
|
@@ -110,7 +110,7 @@ class Project(Model):
|
|
|
110
110
|
#_attributes: dict[ProjectAttr, ProjectAttrTypes]
|
|
111
111
|
#__changeset: dict[ProjectAttr, ProjectAttrChange]
|
|
112
112
|
|
|
113
|
-
__slots__ = ('projectIri', 'projectShortName', 'label', 'comment', 'namespaceIri', 'projectStart', 'projectEnd'
|
|
113
|
+
__slots__ = ('projectIri', 'projectShortName', 'label', 'comment', 'namespaceIri', 'projectStart', 'projectEnd')
|
|
114
114
|
|
|
115
115
|
def __init__(self, *,
|
|
116
116
|
con: IConnection,
|
|
@@ -176,8 +176,6 @@ class Project(Model):
|
|
|
176
176
|
partial(Project._get_value, attr=attr),
|
|
177
177
|
partial(Project._set_value, attr=attr),
|
|
178
178
|
partial(Project._del_value, attr=attr)))
|
|
179
|
-
if self._attributes.get(ProjectAttr.USES_EXTERNAL_ONTOLOGY):
|
|
180
|
-
self._attributes[ProjectAttr.USES_EXTERNAL_ONTOLOGY].set_on_change(self.eo_notify)
|
|
181
179
|
self._changeset = {}
|
|
182
180
|
|
|
183
181
|
def update_notifier(self):
|
|
@@ -185,10 +183,6 @@ class Project(Model):
|
|
|
185
183
|
if getattr(value, 'set_notifier', None) is not None:
|
|
186
184
|
value.set_notifier(self.notifier, attr)
|
|
187
185
|
|
|
188
|
-
def eo_notify(self, d: ObservableDict):
|
|
189
|
-
if not self._changeset.get(ProjectAttr.USES_EXTERNAL_ONTOLOGY):
|
|
190
|
-
self._changeset[ProjectAttr.USES_EXTERNAL_ONTOLOGY] = AttributeChange(self._attributes[ProjectAttr.USES_EXTERNAL_ONTOLOGY].copy(), Action.MODIFY)
|
|
191
|
-
|
|
192
186
|
def _as_dict(self):
|
|
193
187
|
return {x.fragment: y for x, y in self._attributes.items()} | super()._as_dict()
|
|
194
188
|
|
|
@@ -239,29 +233,6 @@ class Project(Model):
|
|
|
239
233
|
"""
|
|
240
234
|
self._changeset[attr] = AttributeChange(self._attributes[attr], Action.MODIFY)
|
|
241
235
|
|
|
242
|
-
def add_external_ontology(self, ontos: dict[Xsd_NCName, NamespaceIRI]):
|
|
243
|
-
"""
|
|
244
|
-
Adds external ontologies to the project.
|
|
245
|
-
:param dict[Xsd_NCName, NamespaceIRI]: Dictionary of external ontologies to add
|
|
246
|
-
:return: None
|
|
247
|
-
"""
|
|
248
|
-
if not ontos:
|
|
249
|
-
return
|
|
250
|
-
if not self._attributes.get(ProjectAttr.USES_EXTERNAL_ONTOLOGY):
|
|
251
|
-
self._attributes[ProjectAttr.USES_EXTERNAL_ONTOLOGY] = ObservableDict()
|
|
252
|
-
for prefix, iri in ontos.items():
|
|
253
|
-
if not self._changeset[ProjectAttr.USES_EXTERNAL_ONTOLOGY]:
|
|
254
|
-
self._changeset[ProjectAttr.USES_EXTERNAL_ONTOLOGY] = AttributeChange(self._attributes[ProjectAttr.USES_EXTERNAL_ONTOLOGY].copy(), Action.MODIFY)
|
|
255
|
-
self._attributes[ProjectAttr.USES_EXTERNAL_ONTOLOGY][prefix] = iri
|
|
256
|
-
|
|
257
|
-
def del_external_ontology(self, ontos: list[Xsd_NCName]):
|
|
258
|
-
if not ontos:
|
|
259
|
-
return
|
|
260
|
-
if not self._attributes.get(ProjectAttr.USES_EXTERNAL_ONTOLOGY):
|
|
261
|
-
raise OldapErrorInconsistency(f'Project {self} has no external ontologies.')
|
|
262
|
-
for prefix in ontos:
|
|
263
|
-
if not self._changeset[ProjectAttr.USES_EXTERNAL_ONTOLOGY]:
|
|
264
|
-
self._changeset[ProjectAttr.USES_EXTERNAL_ONTOLOGY] = AttributeChange(self._attributes[ProjectAttr.USES_EXTERNAL_ONTOLOGY].copy(), Action.MODIFY)
|
|
265
236
|
|
|
266
237
|
|
|
267
238
|
@classmethod
|
|
@@ -355,7 +326,6 @@ class Project(Model):
|
|
|
355
326
|
comment = LangString()
|
|
356
327
|
projectStart = None
|
|
357
328
|
projectEnd = None
|
|
358
|
-
usesExternalOntology: dict[Xsd_QName, NamespaceIRI] | None = None
|
|
359
329
|
for r in res:
|
|
360
330
|
if projectIri is None:
|
|
361
331
|
projectIri = r['proj']
|
|
@@ -380,15 +350,11 @@ class Project(Model):
|
|
|
380
350
|
projectStart = r['val']
|
|
381
351
|
case 'oldap:projectEnd':
|
|
382
352
|
projectEnd = r['val']
|
|
383
|
-
case 'oldap:usesExternalOntology':
|
|
384
|
-
if not usesExternalOntology:
|
|
385
|
-
usesExternalOntology = {}
|
|
386
|
-
usesExternalOntology[r['prefix']] = NamespaceIRI(r['iri'])
|
|
387
353
|
if label:
|
|
388
|
-
label.
|
|
354
|
+
label.clear_changeset()
|
|
389
355
|
label.set_notifier(cls.notifier, Xsd_QName(ProjectAttr.LABEL.value))
|
|
390
356
|
if comment:
|
|
391
|
-
comment.
|
|
357
|
+
comment.clear_changeset()
|
|
392
358
|
comment.set_notifier(cls.notifier, Xsd_QName(ProjectAttr.COMMENT.value))
|
|
393
359
|
context[projectShortName] = namespaceIri
|
|
394
360
|
instance = cls(con=con,
|
|
@@ -402,8 +368,7 @@ class Project(Model):
|
|
|
402
368
|
namespaceIri=namespaceIri,
|
|
403
369
|
comment=comment,
|
|
404
370
|
projectStart=projectStart,
|
|
405
|
-
projectEnd=projectEnd
|
|
406
|
-
usesExternalOntology=usesExternalOntology)
|
|
371
|
+
projectEnd=projectEnd)
|
|
407
372
|
cache = CacheSingletonRedis()
|
|
408
373
|
cache.set(instance.projectIri, instance, instance.projectShortName)
|
|
409
374
|
return instance
|
|
@@ -499,6 +464,17 @@ class Project(Model):
|
|
|
499
464
|
|
|
500
465
|
context = Context(name=self._con.context_name)
|
|
501
466
|
|
|
467
|
+
sparql0 = context.sparql_context
|
|
468
|
+
sparql0 += f"""
|
|
469
|
+
ASK {{
|
|
470
|
+
GRAPH oldap:admin {{
|
|
471
|
+
?p a oldap:Project ;
|
|
472
|
+
oldap:projectShortName ?sname .
|
|
473
|
+
FILTER(?sname = {self.projectShortName.toRdf})
|
|
474
|
+
}}
|
|
475
|
+
}}
|
|
476
|
+
"""
|
|
477
|
+
|
|
502
478
|
sparql1 = context.sparql_context
|
|
503
479
|
sparql1 += f"""
|
|
504
480
|
SELECT ?project
|
|
@@ -508,6 +484,23 @@ class Project(Model):
|
|
|
508
484
|
FILTER(?project = {self.projectIri.toRdf})
|
|
509
485
|
}}
|
|
510
486
|
"""
|
|
487
|
+
sparql1a = context.sparql_context
|
|
488
|
+
sparql1a += f"""
|
|
489
|
+
ASK {{
|
|
490
|
+
GRAPH oldap:admin {{
|
|
491
|
+
?project oldap:namespaceIri {self.namespaceIri.toRdf} .
|
|
492
|
+
}}
|
|
493
|
+
}}
|
|
494
|
+
"""
|
|
495
|
+
|
|
496
|
+
sparql1b = context.sparql_context
|
|
497
|
+
sparql1b += f"""
|
|
498
|
+
ASK {{
|
|
499
|
+
GRAPH oldap:admin {{
|
|
500
|
+
{self.projectIri.toRdf} ?p ?o .
|
|
501
|
+
}}
|
|
502
|
+
}}
|
|
503
|
+
"""
|
|
511
504
|
|
|
512
505
|
blank = ''
|
|
513
506
|
sparql2 = context.sparql_context
|
|
@@ -521,21 +514,25 @@ class Project(Model):
|
|
|
521
514
|
for attr, value in self._attributes.items():
|
|
522
515
|
if not value:
|
|
523
516
|
continue
|
|
524
|
-
|
|
525
|
-
for prefix, iri in value.items():
|
|
526
|
-
sparql2 += f' ;\n{blank:{(indent + 3) * indent_inc}}oldap:usesExternalOntology [ oldap:prefix {prefix.toRdf} ; oldap:fullIri {iri.toRdf} ]'
|
|
527
|
-
pass
|
|
528
|
-
else:
|
|
529
|
-
sparql2 += f' ;\n{blank:{(indent + 3) * indent_inc}}{attr.value.toRdf} {value.toRdf}'
|
|
517
|
+
sparql2 += f' ;\n{blank:{(indent + 3) * indent_inc}}{attr.value.toRdf} {value.toRdf}'
|
|
530
518
|
sparql2 += f' .\n{blank:{(indent + 1) * indent_inc}}}}\n'
|
|
531
519
|
sparql2 += f'{blank:{indent * indent_inc}}}}\n'
|
|
532
520
|
|
|
533
521
|
self._con.transaction_start()
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
if len(res) > 0:
|
|
522
|
+
result = self.safe_query(sparql0)
|
|
523
|
+
if result['boolean']:
|
|
537
524
|
self._con.transaction_abort()
|
|
538
|
-
raise OldapErrorAlreadyExists(f'A Project with a
|
|
525
|
+
raise OldapErrorAlreadyExists(f'A Project with a shortname "{self.projectShortName}" already exists.')
|
|
526
|
+
|
|
527
|
+
result = self.safe_query(sparql1a)
|
|
528
|
+
if result['boolean']:
|
|
529
|
+
self._con.transaction_abort()
|
|
530
|
+
raise OldapErrorAlreadyExists(f'A Project with a namespace IRI "{self.namespaceIri}" already exists.')
|
|
531
|
+
|
|
532
|
+
result = self.safe_query(sparql1b)
|
|
533
|
+
if result['boolean']:
|
|
534
|
+
self._con.transaction_abort()
|
|
535
|
+
raise OldapErrorAlreadyExists(f'A Project with a project IRI "{self.projectIri}" already exists.')
|
|
539
536
|
|
|
540
537
|
self.safe_update(sparql2)
|
|
541
538
|
self.safe_commit()
|
|
@@ -607,70 +604,7 @@ class Project(Model):
|
|
|
607
604
|
field=Xsd_QName(field.value))
|
|
608
605
|
sparql_list.append(sparql)
|
|
609
606
|
continue
|
|
610
|
-
|
|
611
|
-
if change.action == Action.MODIFY:
|
|
612
|
-
diff = dict_diff(self._changeset[ProjectAttr.USES_EXTERNAL_ONTOLOGY].old_value, self._attributes[ProjectAttr.USES_EXTERNAL_ONTOLOGY])
|
|
613
|
-
#
|
|
614
|
-
# first we remove the "removed" and "changed"
|
|
615
|
-
#
|
|
616
|
-
to_delete = set(diff['removed']) | set(diff['changed'])
|
|
617
|
-
for key in to_delete:
|
|
618
|
-
sparql = f"""
|
|
619
|
-
DELETE {{
|
|
620
|
-
GRAPH oldap:admin {{
|
|
621
|
-
{self.projectIri.toRdf} oldap:usesExternalOntology ?o .
|
|
622
|
-
?o ?p ?v .
|
|
623
|
-
}}
|
|
624
|
-
}}
|
|
625
|
-
WHERE {{
|
|
626
|
-
GRAPH oldap:admin {{
|
|
627
|
-
?o oldap:prefix {key.toRdf} .
|
|
628
|
-
?o oldap:fullIri ?anyiri .
|
|
629
|
-
}}
|
|
630
|
-
}}
|
|
631
|
-
"""
|
|
632
|
-
sparql_list.append(sparql)
|
|
633
|
-
to_add = set(diff['added']) | set(diff['changed'])
|
|
634
|
-
sparql = f"""
|
|
635
|
-
INSERT DATA {{
|
|
636
|
-
GRAPH oldap:admin {{
|
|
637
|
-
"""
|
|
638
|
-
for key in to_add:
|
|
639
|
-
sparql += f" {self.projectIri.toRdf} oldap:usesExternalOntology [ oldap:prefix {key.toRdf} ; oldap:fullIri {self._attributes[ProjectAttr.USES_EXTERNAL_ONTOLOGY][key].toRdf} ] .\n"
|
|
640
|
-
sparql += f"""
|
|
641
|
-
}}
|
|
642
|
-
}}
|
|
643
|
-
"""
|
|
644
|
-
sparql_list.append(sparql)
|
|
645
|
-
if change.action == Action.DELETE:
|
|
646
|
-
sparql = f"""
|
|
647
|
-
DELETE {{
|
|
648
|
-
GRAPH oldap:admin {{
|
|
649
|
-
{self.projectIri.toRdf} oldap:usesExternalOntology ?o .
|
|
650
|
-
?o ?p ?v .
|
|
651
|
-
}}
|
|
652
|
-
}}
|
|
653
|
-
WHERE {{
|
|
654
|
-
GRAPH oldap:admin {{
|
|
655
|
-
{self.projectIri.toRdf} oldap:usesExternalOntology ?o .
|
|
656
|
-
?o ?p ?v .
|
|
657
|
-
}}
|
|
658
|
-
}}
|
|
659
|
-
"""
|
|
660
|
-
sparql_list.append(sparql)
|
|
661
|
-
if change.action == Action.CREATE:
|
|
662
|
-
sparql = f"""
|
|
663
|
-
INSERT DATA {{
|
|
664
|
-
GRAPH oldap:admin {{
|
|
665
|
-
"""
|
|
666
|
-
for prefix, iri in self._attributes[ProjectAttr.USES_EXTERNAL_ONTOLOGY].items():
|
|
667
|
-
sparql += f" {self.projectIri.toRdf} oldap:usesExternalOntology [ oldap:prefix {prefix.toRdf} ; oldap:fullIri {iri.toRdf} ] .\n"
|
|
668
|
-
sparql += f"""
|
|
669
|
-
}}
|
|
670
|
-
}}
|
|
671
|
-
"""
|
|
672
|
-
sparql_list.append(sparql)
|
|
673
|
-
continue
|
|
607
|
+
|
|
674
608
|
sparql = f'{blank:{indent * indent_inc}}# Project field "{field.value}" with action "{change.action.value}"\n'
|
|
675
609
|
if change.action != Action.CREATE:
|
|
676
610
|
sparql += f'{blank:{indent * indent_inc}}DELETE {{\n'
|
oldaplib/src/propertyclass.py
CHANGED
|
@@ -22,6 +22,7 @@ from pprint import pprint
|
|
|
22
22
|
from typing import Callable, Self, Any
|
|
23
23
|
|
|
24
24
|
from oldaplib.src.helpers.irincname import IriOrNCName
|
|
25
|
+
from oldaplib.src.helpers.observable_set import ObservableSet
|
|
25
26
|
from oldaplib.src.helpers.serializer import serializer
|
|
26
27
|
from oldaplib.src.oldaplogging import get_logger
|
|
27
28
|
from oldaplib.src.cachesingleton import CacheSingletonRedis
|
|
@@ -312,15 +313,16 @@ class PropertyClass(Model, Notify):
|
|
|
312
313
|
raise OldapErrorInconsistency(f'It\'s not possible to use both DATATYPE="{self._attributes[PropClassAttr.DATATYPE]}" and CLASS={self._attributes[PropClassAttr.CLASS]} restrictions.')
|
|
313
314
|
|
|
314
315
|
# setting property type for OWL which distinguished between Data- and Object-properties
|
|
316
|
+
if not self._attributes.get(PropClassAttr.TYPE):
|
|
317
|
+
self._attributes[PropClassAttr.TYPE] = ObservableSet(notifier=self.notifier, notify_data=PropClassAttr.TYPE)
|
|
315
318
|
if self._statementProperty:
|
|
316
|
-
self._attributes[PropClassAttr.TYPE]
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
self._attributes[PropClassAttr.TYPE] = OwlPropertyType.OwlDataProperty
|
|
319
|
+
self._attributes[PropClassAttr.TYPE].add(OwlPropertyType.StatementProperty)
|
|
320
|
+
if self._attributes.get(PropClassAttr.CLASS) is not None:
|
|
321
|
+
self._attributes[PropClassAttr.TYPE].add(OwlPropertyType.OwlObjectProperty)
|
|
322
|
+
if self._attributes.get(PropClassAttr.DATATYPE) is not None:
|
|
323
|
+
raise OldapError(f'Datatype "{self._attributes.get(PropClassAttr.DATATYPE)}" not possible for OwlObjectProperty')
|
|
324
|
+
elif self._attributes.get(PropClassAttr.DATATYPE) is not None:
|
|
325
|
+
self._attributes[PropClassAttr.TYPE].add(OwlPropertyType.OwlDataProperty)
|
|
324
326
|
|
|
325
327
|
#
|
|
326
328
|
# set the class properties
|
|
@@ -344,6 +346,7 @@ class PropertyClass(Model, Notify):
|
|
|
344
346
|
self._force_external = True
|
|
345
347
|
self.__version = SemanticVersion()
|
|
346
348
|
self.__from_triplestore = _from_triplestore
|
|
349
|
+
self.clear_changeset()
|
|
347
350
|
|
|
348
351
|
def __len__(self) -> int:
|
|
349
352
|
return len(self._attributes)
|
|
@@ -755,12 +758,16 @@ class PropertyClass(Model, Notify):
|
|
|
755
758
|
maxCount: Xsd_integer | None = None
|
|
756
759
|
order: Xsd_decimal | None = None
|
|
757
760
|
group: Xsd_QName | None = None
|
|
761
|
+
nodeKind: Xsd_QName | None = None
|
|
758
762
|
propkeys = {Xsd_QName(x.value) for x in PropClassAttr}
|
|
759
763
|
for key, val in attributes.items():
|
|
760
764
|
if key == 'rdf:type':
|
|
761
765
|
if val != 'sh:PropertyShape':
|
|
762
766
|
raise OldapError(f'Inconsistency, expected "sh:PropertyType", got "{val}".')
|
|
763
767
|
continue
|
|
768
|
+
elif key == 'sh:nodeKind':
|
|
769
|
+
nodeKind = val
|
|
770
|
+
continue
|
|
764
771
|
elif key == 'sh:path':
|
|
765
772
|
if isinstance(val, Xsd_QName):
|
|
766
773
|
self._property_class_iri = val
|
|
@@ -816,6 +823,8 @@ class PropertyClass(Model, Notify):
|
|
|
816
823
|
group = val
|
|
817
824
|
elif key in propkeys:
|
|
818
825
|
attr = PropClassAttr.from_value(key)
|
|
826
|
+
if not attr.in_shacl:
|
|
827
|
+
continue
|
|
819
828
|
if attr.datatype == Numeric:
|
|
820
829
|
if not isinstance(val, (Xsd_integer, Xsd_float)):
|
|
821
830
|
raise OldapErrorInconsistency(f'SHACL inconsistency: "{attr.value}" expects a "Xsd:integer" or "Xsd:float", but got "{type(val).__name__}".')
|
|
@@ -831,12 +840,15 @@ class PropertyClass(Model, Notify):
|
|
|
831
840
|
group=group)
|
|
832
841
|
|
|
833
842
|
if self._attributes.get(PropClassAttr.CLASS) is not None:
|
|
834
|
-
self._attributes[PropClassAttr.TYPE]
|
|
843
|
+
self._attributes[PropClassAttr.TYPE].add(OwlPropertyType.OwlObjectProperty)
|
|
835
844
|
dt = self._attributes.get(PropClassAttr.DATATYPE)
|
|
836
845
|
if dt and (dt != XsdDatatypes.anyURI and dt != XsdDatatypes.QName):
|
|
837
846
|
raise OldapError(f'Datatype "{dt}" not valid for OwlObjectProperty')
|
|
838
847
|
else:
|
|
839
|
-
|
|
848
|
+
if nodeKind in {Xsd_QName('sh:IRI'), Xsd_QName('sh:BlankNode'), Xsd_QName('sh:BlankNodeOrIRI')}:
|
|
849
|
+
self._attributes[PropClassAttr.TYPE].add(OwlPropertyType.OwlObjectProperty)
|
|
850
|
+
else:
|
|
851
|
+
self._attributes[PropClassAttr.TYPE].add(OwlPropertyType.OwlDataProperty)
|
|
840
852
|
#
|
|
841
853
|
# update all notifiers of properties
|
|
842
854
|
#
|
|
@@ -848,6 +860,7 @@ class PropertyClass(Model, Notify):
|
|
|
848
860
|
def read_owl(self) -> None:
|
|
849
861
|
if self._externalOntology:
|
|
850
862
|
return
|
|
863
|
+
propkeys = {Xsd_QName(x.value) for x in PropClassAttr}
|
|
851
864
|
context = Context(name=self._con.context_name)
|
|
852
865
|
query1 = context.sparql_context
|
|
853
866
|
query1 += f"""
|
|
@@ -868,12 +881,12 @@ class PropertyClass(Model, Notify):
|
|
|
868
881
|
obj = r['o']
|
|
869
882
|
match attr:
|
|
870
883
|
case 'rdf:type':
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
884
|
+
try:
|
|
885
|
+
# If obj is already a member, keep it; otherwise look it up by value
|
|
886
|
+
prop_type = obj if isinstance(obj, OwlPropertyType) else OwlPropertyType(obj)
|
|
887
|
+
except ValueError as e:
|
|
888
|
+
raise OldapErrorNotFound(f'Unknown owl:Property type "{obj}"') from e
|
|
889
|
+
self._attributes[PropClassAttr.TYPE].add(prop_type)
|
|
877
890
|
case 'owl:subPropertyOf':
|
|
878
891
|
self._attributes[PropClassAttr.SUBPROPERTY_OF] = obj
|
|
879
892
|
case 'rdfs:range':
|
|
@@ -897,22 +910,27 @@ class PropertyClass(Model, Notify):
|
|
|
897
910
|
dt = obj
|
|
898
911
|
if self._modified != dt:
|
|
899
912
|
raise OldapError(f'Inconsistency between SHACL and OWL: created "{self._modified}" vs "{dt}" for property "{self._property_class_iri}".')
|
|
913
|
+
case _:
|
|
914
|
+
if attr in propkeys:
|
|
915
|
+
pcattr = PropClassAttr.from_value(attr)
|
|
916
|
+
if pcattr.in_owl:
|
|
917
|
+
self._attributes[pcattr] = pcattr.datatype(obj)
|
|
900
918
|
#
|
|
901
919
|
# Consistency checks
|
|
902
920
|
#
|
|
903
921
|
if self._statementProperty:
|
|
904
|
-
if self._attributes.get(PropClassAttr.TYPE)
|
|
922
|
+
if OwlPropertyType.StatementProperty not in self._attributes.get(PropClassAttr.TYPE):
|
|
905
923
|
raise OldapErrorInconsistency(f'Property "{self._property_class_iri}" is a statementProperty, but not an StatementProperty.')
|
|
906
924
|
else:
|
|
907
|
-
if self._attributes.get(PropClassAttr.TYPE)
|
|
925
|
+
if OwlPropertyType.StatementProperty in self._attributes.get(PropClassAttr.TYPE):
|
|
908
926
|
raise OldapErrorInconsistency(f'Property "{self._property_class_iri}" is not a statementProperty, but an StatementProperty.')
|
|
909
|
-
if self._attributes[PropClassAttr.TYPE]
|
|
927
|
+
if OwlPropertyType.OwlDataProperty in self._attributes[PropClassAttr.TYPE]:
|
|
910
928
|
if not datatype:
|
|
911
929
|
raise OldapError(f'OwlDataProperty "{self._property_class_iri}" has no rdfs:range datatype defined!')
|
|
912
930
|
if datatype != self._attributes.get(PropClassAttr.DATATYPE).value:
|
|
913
931
|
raise OldapError(
|
|
914
932
|
f'Property "{self._property_class_iri}" has inconsistent datatype definitions: OWL: "{datatype}" vs. SHACL: "{self._attributes[PropClassAttr.DATATYPE].value}"')
|
|
915
|
-
if self._attributes[PropClassAttr.TYPE]
|
|
933
|
+
if OwlPropertyType.OwlObjectProperty in self._attributes[PropClassAttr.TYPE]:
|
|
916
934
|
if not to_node_iri:
|
|
917
935
|
raise OldapError(f'OwlObjectProperty "{self._property_class_iri}" has no rdfs:range resource class defined!')
|
|
918
936
|
if to_node_iri != self._attributes.get(PropClassAttr.CLASS):
|
|
@@ -960,9 +978,10 @@ class PropertyClass(Model, Notify):
|
|
|
960
978
|
attributes = PropertyClass.__query_shacl(con, property._graph, property_class_iri)
|
|
961
979
|
property.parse_shacl(attributes=attributes)
|
|
962
980
|
property.read_owl()
|
|
963
|
-
|
|
981
|
+
property.clear_changeset()
|
|
964
982
|
|
|
965
983
|
property.update_notifier()
|
|
984
|
+
cache.set(property.property_class_iri, property)
|
|
966
985
|
|
|
967
986
|
return property
|
|
968
987
|
|
|
@@ -1029,7 +1048,7 @@ class PropertyClass(Model, Notify):
|
|
|
1029
1048
|
sparql += f' ;\n{blank:{(indent + 1) * indent_inc}}oldap:statementProperty {self._statementProperty.toRdf}'
|
|
1030
1049
|
sparql += f' ;\n{blank:{(indent + 1) * indent_inc}}oldap:externalOntology {self._externalOntology.toRdf}'
|
|
1031
1050
|
for prop, value in self._attributes.items():
|
|
1032
|
-
if prop
|
|
1051
|
+
if not prop.in_shacl:
|
|
1033
1052
|
continue
|
|
1034
1053
|
if not value and not isinstance(value, bool):
|
|
1035
1054
|
#if value is None:
|
|
@@ -1062,24 +1081,47 @@ class PropertyClass(Model, Notify):
|
|
|
1062
1081
|
|
|
1063
1082
|
def create_owl_part1(self, timestamp: Xsd_dateTime, indent: int = 0, indent_inc: int = 4) -> str:
|
|
1064
1083
|
blank = ''
|
|
1065
|
-
sparql = f'{blank:{indent * indent_inc}}{self._property_class_iri.toRdf}
|
|
1066
|
-
if self._attributes.get(PropClassAttr.SUBPROPERTY_OF):
|
|
1067
|
-
sparql += f' ;\n{blank:{(indent + 1) * indent_inc}}rdfs:subPropertyOf {self._attributes[PropClassAttr.SUBPROPERTY_OF].toRdf}'
|
|
1084
|
+
sparql = f'{blank:{indent * indent_inc}}{self._property_class_iri.toRdf} {PropClassAttr.TYPE.toRdf} {self._attributes[PropClassAttr.TYPE].toRdf}'
|
|
1068
1085
|
if self._internal:
|
|
1069
1086
|
sparql += f' ;\n{blank:{(indent + 1) * indent_inc}}rdfs:domain {self._internal.toRdf}'
|
|
1070
|
-
if self._attributes.get(PropClassAttr.TYPE)
|
|
1087
|
+
if self._attributes.get(PropClassAttr.TYPE) and OwlPropertyType.OwlDataProperty in self._attributes[PropClassAttr.TYPE]:
|
|
1071
1088
|
sparql += f' ;\n{blank:{(indent + 1) * indent_inc}}rdfs:range {self._attributes[PropClassAttr.DATATYPE].value}'
|
|
1072
|
-
elif self._attributes.get(PropClassAttr.TYPE)
|
|
1089
|
+
elif self._attributes.get(PropClassAttr.TYPE) and OwlPropertyType.OwlObjectProperty in self._attributes[PropClassAttr.TYPE]:
|
|
1073
1090
|
sparql += f' ;\n{blank:{(indent + 1) * indent_inc}}rdfs:range {self._attributes[PropClassAttr.CLASS].toRdf}'
|
|
1091
|
+
for attr, val in self._attributes.items():
|
|
1092
|
+
#attr = PropClassAttr.from_value(key)
|
|
1093
|
+
if not attr.in_owl or attr == PropClassAttr.TYPE or val is None:
|
|
1094
|
+
continue
|
|
1095
|
+
if attr == PropClassAttr.NAME:
|
|
1096
|
+
sparql += f' ;\n{blank:{(indent + 1) * indent_inc}}rdfs:label {val.toRdf}'
|
|
1097
|
+
elif attr == PropClassAttr.DESCRIPTION:
|
|
1098
|
+
sparql += f' ;\n{blank:{(indent + 1) * indent_inc}}rdfs:comment {val.toRdf}'
|
|
1099
|
+
else:
|
|
1100
|
+
sparql += f' ;\n{blank:{(indent + 1) * indent_inc}}{attr.toRdf} {val.toRdf}'
|
|
1074
1101
|
sparql += f' ;\n{blank:{(indent + 1) * indent_inc}}dcterms:creator {self._con.userIri.toRdf}'
|
|
1075
1102
|
sparql += f' ;\n{blank:{(indent + 1) * indent_inc}}dcterms:created {timestamp.toRdf}'
|
|
1076
1103
|
sparql += f' ;\n{blank:{(indent + 1) * indent_inc}}dcterms:contributor {self._con.userIri.toRdf}'
|
|
1077
1104
|
sparql += f' ;\n{blank:{(indent + 1) * indent_inc}}dcterms:modified {timestamp.toRdf}'
|
|
1078
|
-
if self.name:
|
|
1079
|
-
sparql += f' ;\n{blank:{(indent + 1) * indent_inc}}rdfs:label {self.name.toRdf}'
|
|
1080
|
-
if self.description:
|
|
1081
|
-
sparql += f' ;\n{blank:{(indent + 1) * indent_inc}}rdfs:comment {self.description.toRdf}'
|
|
1082
1105
|
sparql += ' .\n'
|
|
1106
|
+
|
|
1107
|
+
# sparql = f'{blank:{indent * indent_inc}}{self._property_class_iri.toRdf} rdf:type {self._attributes[PropClassAttr.TYPE].toRdf}'
|
|
1108
|
+
# if self._attributes.get(PropClassAttr.SUBPROPERTY_OF):
|
|
1109
|
+
# sparql += f' ;\n{blank:{(indent + 1) * indent_inc}}rdfs:subPropertyOf {self._attributes[PropClassAttr.SUBPROPERTY_OF].toRdf}'
|
|
1110
|
+
# if self._internal:
|
|
1111
|
+
# sparql += f' ;\n{blank:{(indent + 1) * indent_inc}}rdfs:domain {self._internal.toRdf}'
|
|
1112
|
+
# if self._attributes.get(PropClassAttr.TYPE) and OwlPropertyType.OwlDataProperty in self._attributes[PropClassAttr.TYPE]:
|
|
1113
|
+
# sparql += f' ;\n{blank:{(indent + 1) * indent_inc}}rdfs:range {self._attributes[PropClassAttr.DATATYPE].value}'
|
|
1114
|
+
# elif self._attributes.get(PropClassAttr.TYPE) and OwlPropertyType.OwlObjectProperty in self._attributes[PropClassAttr.TYPE]:
|
|
1115
|
+
# sparql += f' ;\n{blank:{(indent + 1) * indent_inc}}rdfs:range {self._attributes[PropClassAttr.CLASS].toRdf}'
|
|
1116
|
+
# sparql += f' ;\n{blank:{(indent + 1) * indent_inc}}dcterms:creator {self._con.userIri.toRdf}'
|
|
1117
|
+
# sparql += f' ;\n{blank:{(indent + 1) * indent_inc}}dcterms:created {timestamp.toRdf}'
|
|
1118
|
+
# sparql += f' ;\n{blank:{(indent + 1) * indent_inc}}dcterms:contributor {self._con.userIri.toRdf}'
|
|
1119
|
+
# sparql += f' ;\n{blank:{(indent + 1) * indent_inc}}dcterms:modified {timestamp.toRdf}'
|
|
1120
|
+
# if self.name:
|
|
1121
|
+
# sparql += f' ;\n{blank:{(indent + 1) * indent_inc}}rdfs:label {self.name.toRdf}'
|
|
1122
|
+
# if self.description:
|
|
1123
|
+
# sparql += f' ;\n{blank:{(indent + 1) * indent_inc}}rdfs:comment {self.description.toRdf}'
|
|
1124
|
+
# sparql += ' .\n'
|
|
1083
1125
|
return sparql
|
|
1084
1126
|
|
|
1085
1127
|
def create_owl_part2(self, *,
|
|
@@ -1104,9 +1146,9 @@ class PropertyClass(Model, Notify):
|
|
|
1104
1146
|
# of the property within the given resource. However, rdfs:range is "global" for all use of this property!
|
|
1105
1147
|
#
|
|
1106
1148
|
if self._attributes.get(PropClassAttr.DATATYPE) or self._attributes.get(PropClassAttr.CLASS):
|
|
1107
|
-
if self._attributes[PropClassAttr.TYPE]
|
|
1149
|
+
if OwlPropertyType.OwlDataProperty in self._attributes[PropClassAttr.TYPE]:
|
|
1108
1150
|
sparql += f' ;\n{blank:{(indent + 1) * indent_inc}}owl:onDataRange {self._attributes[PropClassAttr.DATATYPE].value}'
|
|
1109
|
-
elif self._attributes[PropClassAttr.TYPE]
|
|
1151
|
+
elif OwlPropertyType.OwlObjectProperty in self._attributes[PropClassAttr.TYPE]:
|
|
1110
1152
|
sparql += f' ;\n{blank:{(indent + 1) * indent_inc}}owl:onClass {self._attributes[PropClassAttr.CLASS]}'
|
|
1111
1153
|
sparql += f' ;\n{blank:{indent * indent_inc}}]'
|
|
1112
1154
|
return sparql
|
|
@@ -1195,6 +1237,7 @@ class PropertyClass(Model, Notify):
|
|
|
1195
1237
|
try:
|
|
1196
1238
|
self._con.transaction_update(sparql)
|
|
1197
1239
|
except OldapError as err:
|
|
1240
|
+
print(sparql)
|
|
1198
1241
|
self._con.transaction_abort()
|
|
1199
1242
|
raise
|
|
1200
1243
|
if not self._externalOntology: # it's a project specific property -> OWL has been written -> check consistency!
|
|
@@ -1269,6 +1312,16 @@ class PropertyClass(Model, Notify):
|
|
|
1269
1312
|
attr=prop,
|
|
1270
1313
|
modified=self._modified,
|
|
1271
1314
|
indent=indent, indent_inc=indent_inc)
|
|
1315
|
+
elif prop.datatype == ObservableSet:
|
|
1316
|
+
if prop == PropClassAttr.TYPE:
|
|
1317
|
+
continue
|
|
1318
|
+
# sparql += self._attributes[prop].update_shacl(graph=self._graph,
|
|
1319
|
+
# owlclass_iri=owlclass_iri,
|
|
1320
|
+
# prop_iri=self._property_class_iri,
|
|
1321
|
+
# attr=prop,
|
|
1322
|
+
# modified=self._modified,
|
|
1323
|
+
# indent=indent, indent_inc=indent_inc)
|
|
1324
|
+
# print(gaga)
|
|
1272
1325
|
else:
|
|
1273
1326
|
raise OldapError(f'SHACL property {prop.value} should not have update action "MODIFY" ({prop.datatype}).')
|
|
1274
1327
|
sparql_list.append(sparql)
|
|
@@ -1329,12 +1382,14 @@ class PropertyClass(Model, Notify):
|
|
|
1329
1382
|
owlclass_iri: Xsd_QName | None = None,
|
|
1330
1383
|
timestamp: Xsd_dateTime,
|
|
1331
1384
|
indent: int = 0, indent_inc: int = 4) -> str:
|
|
1385
|
+
tmp = {x for x in PropClassAttr if x.in_owl and x != PropClassAttr.TYPE}
|
|
1332
1386
|
owl_propclass_attributes = {PropClassAttr.SUBPROPERTY_OF, # should be in OWL ontology
|
|
1333
1387
|
PropClassAttr.DATATYPE, # used for rdfs:range in OWL ontology
|
|
1334
|
-
PropClassAttr.CLASS}
|
|
1388
|
+
PropClassAttr.CLASS} | tmp # used for rdfs:range in OWL ontology
|
|
1389
|
+
tmp = {x: x.value.toRdf for x in PropClassAttr if x.in_owl and x != PropClassAttr.TYPE}
|
|
1335
1390
|
owl_prop = {PropClassAttr.SUBPROPERTY_OF: PropClassAttr.SUBPROPERTY_OF.value,
|
|
1336
1391
|
PropClassAttr.DATATYPE: "rdfs:range",
|
|
1337
|
-
PropClassAttr.CLASS: "rdfs:range"}
|
|
1392
|
+
PropClassAttr.CLASS: "rdfs:range"} | tmp
|
|
1338
1393
|
blank = ''
|
|
1339
1394
|
sparql_list = []
|
|
1340
1395
|
for prop, change in self._changeset.items():
|
|
@@ -1367,6 +1422,13 @@ class PropertyClass(Model, Notify):
|
|
|
1367
1422
|
last_modified=self._modified,
|
|
1368
1423
|
indent=indent, indent_inc=indent_inc)
|
|
1369
1424
|
sparql_list.append(sparql)
|
|
1425
|
+
if prop == PropClassAttr.TYPE:
|
|
1426
|
+
|
|
1427
|
+
sparql = self._attributes[prop].update_sparql(graph=Xsd_QName(str(self._graph), 'onto'),
|
|
1428
|
+
subject=self._property_class_iri,
|
|
1429
|
+
ignoreitems={OwlPropertyType.OwlDataProperty,OwlPropertyType.OwlObjectProperty},
|
|
1430
|
+
field=prop)
|
|
1431
|
+
sparql_list.extend(sparql)
|
|
1370
1432
|
if prop == PropClassAttr.NAME:
|
|
1371
1433
|
if change.action == Action.CREATE:
|
|
1372
1434
|
sparql = self.name.create(graph=Xsd_QName(self._graph, 'onto'),
|
|
@@ -1574,7 +1636,7 @@ class PropertyClass(Model, Notify):
|
|
|
1574
1636
|
self._contributor = self._con.userIri
|
|
1575
1637
|
for prop, change in self._changeset.items():
|
|
1576
1638
|
if change.action == Action.MODIFY:
|
|
1577
|
-
self._attributes[prop].
|
|
1639
|
+
self._attributes[prop].clear_changeset()
|
|
1578
1640
|
self._changeset = {}
|
|
1579
1641
|
cache = CacheSingletonRedis()
|
|
1580
1642
|
cache.set(self._property_class_iri, self)
|
oldaplib/src/resourceclass.py
CHANGED
|
@@ -639,7 +639,9 @@ class ResourceClass(Model, Notify):
|
|
|
639
639
|
s += f'{blank:{indent*2}}{qname} = {hasprop.prop} (minCount={hasprop.minCount}, maxCount={hasprop.maxCount}\n'
|
|
640
640
|
return s
|
|
641
641
|
|
|
642
|
-
def
|
|
642
|
+
def clear_changeset(self) -> None:
|
|
643
|
+
for prop in self._properties.values():
|
|
644
|
+
prop.clear_changeset()
|
|
643
645
|
super().clear_changeset()
|
|
644
646
|
|
|
645
647
|
def notifier(self, what: ResClassAttribute | Xsd_QName):
|
|
@@ -797,7 +799,7 @@ class ResourceClass(Model, Notify):
|
|
|
797
799
|
self._attributes[attr].set_notifier(self.notifier, attr)
|
|
798
800
|
|
|
799
801
|
self.__from_triplestore = True
|
|
800
|
-
self.
|
|
802
|
+
self.clear_changeset()
|
|
801
803
|
|
|
802
804
|
@staticmethod
|
|
803
805
|
def __query_resource_props(con: IConnection,
|
|
@@ -1082,7 +1084,7 @@ class ResourceClass(Model, Notify):
|
|
|
1082
1084
|
if not resclass.externalOntology:
|
|
1083
1085
|
resclass.__read_owl()
|
|
1084
1086
|
|
|
1085
|
-
resclass.
|
|
1087
|
+
resclass.clear_changeset()
|
|
1086
1088
|
|
|
1087
1089
|
resclass.update_notifier()
|
|
1088
1090
|
|
|
@@ -1296,7 +1298,7 @@ class ResourceClass(Model, Notify):
|
|
|
1296
1298
|
raise OldapErrorUpdateFailed(f'Creating resource "{self._owlclass_iri}" failed.')
|
|
1297
1299
|
else:
|
|
1298
1300
|
self._con.transaction_commit()
|
|
1299
|
-
self.
|
|
1301
|
+
self.clear_changeset()
|
|
1300
1302
|
cache = CacheSingletonRedis()
|
|
1301
1303
|
cache.set(self._owlclass_iri, self)
|
|
1302
1304
|
|
|
@@ -1859,7 +1861,7 @@ class ResourceClass(Model, Notify):
|
|
|
1859
1861
|
raise OldapErrorUpdateFailed(f'Update of {self._owlclass_iri} failed. {modtime_shacl} {modtime_owl} {timestamp}')
|
|
1860
1862
|
else:
|
|
1861
1863
|
self._con.transaction_commit()
|
|
1862
|
-
self.
|
|
1864
|
+
self.clear_changeset()
|
|
1863
1865
|
self._modified = timestamp
|
|
1864
1866
|
self._contributor = self._con.userIri
|
|
1865
1867
|
self._test_in_use = False
|
oldaplib/src/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.3.
|
|
1
|
+
__version__ = "0.3.5"
|
oldaplib/src/xsd/iri.py
CHANGED
oldaplib/src/xsd/xsd_anyuri.py
CHANGED
|
@@ -65,20 +65,20 @@ class Xsd_anyURI(Xsd):
|
|
|
65
65
|
if isinstance(value, str):
|
|
66
66
|
value = value.replace("<", "").replace(">", "")
|
|
67
67
|
if value.startswith("urn:"):
|
|
68
|
-
if not re.match(r'^urn:[a-z0-9][a-z0-9-]{0,31}:[^\s]+', value):
|
|
68
|
+
if not re.match(r'^urn:[a-z0-9][a-z0-9-]{0,31}:[^\s]+', str(value)):
|
|
69
69
|
raise OldapErrorValue(f'Invalid URN format for "{value}".')
|
|
70
70
|
elif value.startswith("http"):
|
|
71
|
-
if not re.match(self._uri_pattern, value):
|
|
71
|
+
if not re.match(self._uri_pattern, str(value)):
|
|
72
72
|
raise OldapErrorValue(f'Invalid string "{value}" for xsd:anyURI.')
|
|
73
73
|
if validate:
|
|
74
|
-
if not XsdValidator.validate(XsdDatatypes.anyURI, value):
|
|
74
|
+
if not XsdValidator.validate(XsdDatatypes.anyURI, str(value)):
|
|
75
75
|
raise OldapErrorValue(f'Invalid string "{value}" for anyURI')
|
|
76
76
|
else:
|
|
77
|
-
if not url(value):
|
|
77
|
+
if not url(str(value)):
|
|
78
78
|
raise OldapErrorValue(f'Invalid string "{value}" for xsd:anyURI.')
|
|
79
79
|
else:
|
|
80
80
|
raise OldapErrorValue(f'Invalid string "{value}" for anyURI')
|
|
81
|
-
self._value = value
|
|
81
|
+
self._value = str(value)
|
|
82
82
|
self._append_allowed = self._value[-1] == '/' or self._value[-1] == '#'
|
|
83
83
|
|
|
84
84
|
def __repr__(self) -> str:
|
oldaplib/src/xsd/xsd_qname.py
CHANGED
|
@@ -55,11 +55,14 @@ class Xsd_QName(Xsd):
|
|
|
55
55
|
raise OldapErrorValue(f'Invalid string "{value}" for QName. Error: {err}')
|
|
56
56
|
self._value = f'{prefix}:{fragment}'
|
|
57
57
|
else:
|
|
58
|
-
raise OldapErrorValue(f'Invalid value for QName "{value}"')
|
|
58
|
+
raise OldapErrorValue(f'Invalid value for QName "{value}" (type: {type(value).__name__})')
|
|
59
59
|
else:
|
|
60
60
|
prefix = Xsd_NCName(value, validate=validate)
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
if fragment:
|
|
62
|
+
fragment = Xsd_NCName(fragment, validate=validate)
|
|
63
|
+
self._value = f'{prefix}:{fragment}'
|
|
64
|
+
else:
|
|
65
|
+
self._value = f'{prefix}:'
|
|
63
66
|
|
|
64
67
|
def __len__(self) -> int:
|
|
65
68
|
"""
|