oldaplib 0.3.2__py3-none-any.whl → 0.3.4__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 (41) hide show
  1. oldaplib/ontologies/admin-testing.trig +2 -4
  2. oldaplib/ontologies/admin.trig +2 -4
  3. oldaplib/ontologies/oldap.trig +221 -131
  4. oldaplib/ontologies/shared.trig +0 -3
  5. oldaplib/src/datamodel.py +109 -17
  6. oldaplib/src/dtypes/namespaceiri.py +16 -1
  7. oldaplib/src/enums/externalontologyattr.py +22 -0
  8. oldaplib/src/enums/owlpropertytype.py +10 -0
  9. oldaplib/src/enums/projectattr.py +0 -1
  10. oldaplib/src/enums/propertyclassattr.py +3 -1
  11. oldaplib/src/externalontology.py +554 -0
  12. oldaplib/src/hasproperty.py +5 -0
  13. oldaplib/src/helpers/context.py +4 -4
  14. oldaplib/src/helpers/langstring.py +11 -2
  15. oldaplib/src/helpers/observable_dict.py +4 -1
  16. oldaplib/src/helpers/observable_set.py +135 -80
  17. oldaplib/src/helpers/query_processor.py +3 -0
  18. oldaplib/src/model.py +1 -1
  19. oldaplib/src/oldaplist.py +2 -2
  20. oldaplib/src/oldaplistnode.py +2 -2
  21. oldaplib/src/permissionset.py +3 -3
  22. oldaplib/src/project.py +47 -113
  23. oldaplib/src/propertyclass.py +64 -24
  24. oldaplib/src/resourceclass.py +8 -6
  25. oldaplib/src/version.py +1 -1
  26. oldaplib/src/xsd/iri.py +3 -0
  27. oldaplib/src/xsd/xsd_anyuri.py +5 -5
  28. oldaplib/src/xsd/xsd_qname.py +5 -2
  29. oldaplib/test/test_context.py +0 -4
  30. oldaplib/test/test_datamodel.py +50 -1
  31. oldaplib/test/test_dtypes.py +3 -2
  32. oldaplib/test/test_externalontologies.py +175 -0
  33. oldaplib/test/test_project.py +31 -76
  34. oldaplib/test/test_propertyclass.py +93 -6
  35. oldaplib/test/test_resourceclass.py +10 -10
  36. oldaplib/testdata/connection_test.trig +29 -12
  37. oldaplib/testdata/datamodel_test.trig +1 -1
  38. oldaplib/testdata/objectfactory_test.trig +2 -1
  39. {oldaplib-0.3.2.dist-info → oldaplib-0.3.4.dist-info}/METADATA +1 -1
  40. {oldaplib-0.3.2.dist-info → oldaplib-0.3.4.dist-info}/RECORD +41 -38
  41. {oldaplib-0.3.2.dist-info → oldaplib-0.3.4.dist-info}/WHEEL +0 -0
@@ -0,0 +1,554 @@
1
+ from copy import deepcopy
2
+ from datetime import datetime
3
+ from functools import partial
4
+ from typing import Any, Self, Callable
5
+
6
+ from elementpath.datatypes import NCName
7
+
8
+ from oldaplib.src.cachesingleton import CacheSingletonRedis
9
+ from oldaplib.src.dtypes.languagein import LanguageIn
10
+ from oldaplib.src.dtypes.namespaceiri import NamespaceIRI
11
+ from oldaplib.src.dtypes.xsdset import XsdSet
12
+ from oldaplib.src.enums.action import Action
13
+ from oldaplib.src.enums.adminpermissions import AdminPermission
14
+ from oldaplib.src.enums.attributeclass import AttributeClass
15
+ from oldaplib.src.enums.externalontologyattr import ExternalOntologyAttr
16
+ from oldaplib.src.helpers.Notify import Notify
17
+ from oldaplib.src.helpers.attributechange import AttributeChange
18
+ from oldaplib.src.helpers.context import Context
19
+ from oldaplib.src.helpers.langstring import LangString
20
+ from oldaplib.src.helpers.oldaperror import OldapError, OldapErrorNoPermission, OldapErrorAlreadyExists, \
21
+ OldapErrorNotFound, OldapErrorUpdateFailed, OldapErrorInUse
22
+ from oldaplib.src.helpers.query_processor import QueryProcessor
23
+ from oldaplib.src.helpers.serializer import serializer
24
+ from oldaplib.src.iconnection import IConnection
25
+ from oldaplib.src.model import Model
26
+ from oldaplib.src.project import Project
27
+ from oldaplib.src.xsd.iri import Iri
28
+ from oldaplib.src.xsd.xsd_datetime import Xsd_dateTime
29
+ from oldaplib.src.xsd.xsd_ncname import Xsd_NCName
30
+ from oldaplib.src.xsd.xsd_qname import Xsd_QName
31
+ from oldaplib.src.xsd.xsd_string import Xsd_string
32
+
33
+
34
+ @serializer
35
+ class ExternalOntology(Model, Notify):
36
+ """Class representing external ontologies.
37
+
38
+ This class provides methods for managing external ontologies.
39
+ """
40
+ __extonto_qname: Xsd_QName | None
41
+ __projectShortName: Xsd_NCName | None
42
+
43
+ def __init__(self, *,
44
+ con: IConnection,
45
+ creator: Iri | str |None = None,
46
+ created: Xsd_dateTime | datetime | str | None = None,
47
+ contributor: Iri | None = None,
48
+ modified: Xsd_dateTime | datetime | str | None = None,
49
+ notifier: Callable[[Xsd_QName], None] | None = None,
50
+ notify_data: Xsd_QName | None = None,
51
+ projectShortName: Xsd_NCName | str,
52
+ validate: bool = False,
53
+ **kwargs):
54
+ Model.__init__(self,
55
+ connection=con,
56
+ creator=creator,
57
+ created=created,
58
+ contributor=contributor,
59
+ modified=modified,
60
+ validate=validate)
61
+ Notify.__init__(self, notifier, notify_data)
62
+ if isinstance(projectShortName, Xsd_NCName):
63
+ self.__projectShortName = projectShortName
64
+ else:
65
+ self.__projectShortName = Xsd_NCName(projectShortName, validate=validate)
66
+ self.set_attributes(kwargs, ExternalOntologyAttr)
67
+ self.__extonto_qname = Xsd_QName(self.__projectShortName, self._attributes[ExternalOntologyAttr.PREFIX])
68
+
69
+ for attr in ExternalOntologyAttr:
70
+ setattr(ExternalOntology, attr.value.fragment, property(
71
+ partial(ExternalOntology._get_value, attr=attr),
72
+ partial(ExternalOntology._set_value, attr=attr),
73
+ partial(ExternalOntology._del_value, attr=attr)))
74
+ self.update_notifier()
75
+ #self._changeset = {}
76
+
77
+ def __len__(self):
78
+ return len(self._attributes)
79
+
80
+
81
+ @property
82
+ def extonto_qname(self) -> Xsd_QName:
83
+ return self.__extonto_qname
84
+
85
+ def update_notifier(self,
86
+ notifier: Callable[[AttributeClass | Xsd_QName], None] | None = None,
87
+ notify_data: AttributeClass | None = None,):
88
+ self.set_notifier(notifier, notify_data)
89
+ for attr, value in self._attributes.items():
90
+ if getattr(value, 'set_notifier', None) is not None:
91
+ value.set_notifier(self.notifier, attr)
92
+
93
+ def _as_dict(self):
94
+ return {x.fragment: y for x, y in self._attributes.items()} | super()._as_dict() | {
95
+ 'projectShortName': self.__projectShortName
96
+ }
97
+
98
+ def __deepcopy__(self, memo: dict[Any, Any]) -> Self:
99
+ if id(self) in memo:
100
+ return memo[id(self)]
101
+ cls = self.__class__
102
+ instance = cls.__new__(cls)
103
+ memo[id(self)] = instance
104
+ Model.__init__(instance,
105
+ connection=deepcopy(self._con, memo),
106
+ creator=deepcopy(self._creator, memo),
107
+ created=deepcopy(self._created, memo),
108
+ contributor=deepcopy(self._contributor, memo),
109
+ modified=deepcopy(self._modified, memo))
110
+ Notify.__init__(instance,
111
+ notifier=self._notifier,
112
+ data=deepcopy(self._notify_data, memo))
113
+ # Copy internals of Model:
114
+ instance._attributes = deepcopy(self._attributes, memo)
115
+ instance._changset = deepcopy(self._changeset, memo)
116
+ instance.__projectShortName = deepcopy(self.__projectShortName, memo)
117
+ instance.__extonto_qname = deepcopy(self.__extonto_qname, memo)
118
+ return instance
119
+
120
+ def check_for_permissions(self) -> (bool, str):
121
+ """
122
+ Internal method to check if a user may modify the permission set.
123
+ :return: a tuple with a boolean (True, False) and the error message (or "OK")
124
+ """
125
+ #
126
+ # First we check if the logged-in user ("actor") has the ADMIN_PERMISSION_SETS permission for
127
+ # the given project!
128
+ #
129
+ actor = self._con.userdata
130
+ sysperms = actor.inProject.get(Iri('oldap:SystemProject'))
131
+ if sysperms and AdminPermission.ADMIN_OLDAP in sysperms:
132
+ #
133
+ # user has root privileges!
134
+ #
135
+ return True, "OK"
136
+ else:
137
+ if actor.inProject.get(self.__project.projectIri) is None:
138
+ return False, f'Actor has no ADMIN_MODEL permission for project {self.__project.projectIri}'
139
+ else:
140
+ if AdminPermission.ADMIN_MODEL not in actor.inProject.get(self.__project.projectIri):
141
+ return False, f'Actor has no ADMIN_MODEL permission for project {self.__project.projectIri}'
142
+ return True, "OK"
143
+
144
+ def notifier(self, attr: ExternalOntologyAttr, value: Any = None) -> None:
145
+ self._changeset[attr] = AttributeChange(None, Action.MODIFY)
146
+ self.notify()
147
+
148
+ def create_shacl(self, *,
149
+ timestamp: Xsd_dateTime,
150
+ indent: int = 0,
151
+ indent_inc: int = 4) -> str:
152
+ blank = ''
153
+ sparql = ''
154
+ sparql += f'{blank:{indent * indent_inc}}INSERT DATA {{\n'
155
+ sparql += f'{blank:{(indent + 1) * indent_inc}}GRAPH {self.__projectShortName}:shacl {{\n'
156
+ sparql += f'{blank:{(indent + 2) * indent_inc}} {self.__extonto_qname.toRdf} a oldap:ExternalOntology'
157
+ sparql += f' ;\n{blank:{(indent + 3) * indent_inc}}dcterms:creator {self._con.userIri.toRdf}'
158
+ sparql += f' ;\n{blank:{(indent + 3) * indent_inc}}dcterms:created {timestamp.toRdf}'
159
+ sparql += f' ;\n{blank:{(indent + 3) * indent_inc}}dcterms:contributor {self._con.userIri.toRdf}'
160
+ sparql += f' ;\n{blank:{(indent + 3) * indent_inc}}dcterms:modified {timestamp.toRdf}'
161
+ for attr, value in self._attributes.items():
162
+ sparql += f' ;\n{blank:{(indent + 3) * indent_inc}}{attr.value.toRdf} {value.toRdf}'
163
+ sparql += f'\n{blank:{(indent + 1) * indent_inc}}}}\n'
164
+ sparql += f'{blank:{indent * indent_inc}}}}\n'
165
+ return sparql
166
+
167
+ def create(self, indent: int = 0, indent_inc: int = 4) -> None:
168
+ if self._con is None:
169
+ raise OldapError("Cannot create: no connection")
170
+
171
+ result, message = self.check_for_permissions()
172
+ if not result:
173
+ raise OldapErrorNoPermission(message)
174
+
175
+ timestamp = Xsd_dateTime()
176
+
177
+ context = Context(name=self._con.context_name)
178
+ blank = ''
179
+ query1 = context.sparql_context
180
+ query1 += f"""
181
+ ASK {{
182
+ GRAPH {self.__projectShortName}:shacl {{
183
+ {self.__extonto_qname.toRdf} a oldap:ExternalOntology .
184
+ }}
185
+ }}
186
+ """
187
+
188
+ sparql = context.sparql_context
189
+ sparql += self.create_shacl(timestamp=timestamp)
190
+
191
+ self._con.transaction_start()
192
+ res1 = self.safe_query(query1)
193
+ if res1['boolean']:
194
+ self._con.transaction_abort()
195
+ raise OldapErrorAlreadyExists(f'An ExternalOntology with a graphIri "{self.__projectShortName}" already exists.')
196
+ self.safe_update(sparql)
197
+ self._con.transaction_commit()
198
+ self._created = timestamp
199
+ self._creator = self._con.userIri
200
+ self._modified = timestamp
201
+ self._contributor = self._con.userIri
202
+ #context[self._attributes[ExternalOntologyAttr.PREFIX]] = NamespaceIRI(str(self._attributes[ExternalOntologyAttr.NAMESPACE_IRI]))
203
+ cache = CacheSingletonRedis()
204
+ cache.set(self.__extonto_qname, self)
205
+
206
+ @classmethod
207
+ def read(cls, *,
208
+ con: IConnection,
209
+ projectShortName: Xsd_NCName | str,
210
+ prefix: NCName | str | None = None,
211
+ validate: bool = False,
212
+ ignore_cache: bool = False):
213
+ if not isinstance(projectShortName, Xsd_NCName):
214
+ projectShortName = Xsd_NCName(projectShortName, validate=validate)
215
+ if isinstance(prefix, NCName):
216
+ extonto_qname = Xsd_QName(projectShortName, prefix, validate=validate)
217
+ else:
218
+ extonto_qname = Xsd_QName(projectShortName, Xsd_NCName(prefix, validate=validate), validate=validate)
219
+ if not ignore_cache:
220
+ cache = CacheSingletonRedis()
221
+ tmp = cache.get(extonto_qname, connection=con)
222
+ if tmp is not None:
223
+ tmp.update_notifier()
224
+ return tmp
225
+ context = Context(name=con.context_name)
226
+ sparql = context.sparql_context
227
+ sparql += f"""
228
+ SELECT ?extonto ?p ?o
229
+ FROM {projectShortName}:shacl
230
+ WHERE {{
231
+ BIND({extonto_qname} as ?extonto)
232
+ ?extonto a oldap:ExternalOntology .
233
+ ?extonto ?p ?o .
234
+ }}
235
+ """
236
+ jsonobj = con.query(sparql)
237
+ res = QueryProcessor(context, jsonobj)
238
+ if len(res) == 0:
239
+ raise OldapErrorNotFound(f'No external ontology "{cls.__extonto_qname}" found.')
240
+ creator: Iri | None = None
241
+ created: Xsd_dateTime | None = None
242
+ contributor: Iri | None = None
243
+ modified: Xsd_dateTime | None = None
244
+ namespace_iri: NamespaceIRI | None = None
245
+ prefix: NCName | None = None
246
+ label: LangString = LangString()
247
+ comment: LangString = LangString()
248
+ for r in res:
249
+ match str(r['p']):
250
+ case 'dcterms:creator':
251
+ creator = r['o']
252
+ case 'dcterms:created':
253
+ created = r['o']
254
+ case 'dcterms:contributor':
255
+ contributor = r['o']
256
+ case 'dcterms:modified':
257
+ modified = r['o']
258
+ case 'oldap:namespaceIri':
259
+ namespace_iri = NamespaceIRI(r['o'])
260
+ case 'oldap:prefix':
261
+ prefix = r['o']
262
+ case 'rdfs:label':
263
+ label.add(r['o'])
264
+ case 'rdfs:comment':
265
+ comment.add(r['o'])
266
+ if comment:
267
+ comment.clear_changeset()
268
+ comment.set_notifier(cls.notifier, Xsd_QName(ExternalOntologyAttr.LABEL.value))
269
+ if label:
270
+ label.clear_changeset()
271
+ label.set_notifier(cls.notifier, Xsd_QName(ExternalOntologyAttr.LABEL.value))
272
+ instance = cls(con=con,
273
+ projectShortName=projectShortName,
274
+ creator=creator,
275
+ created=created,
276
+ contributor=contributor,
277
+ modified=modified,
278
+ prefix=prefix,
279
+ namespaceIri=namespace_iri,
280
+ label=label,
281
+ comment=comment,
282
+ validate=False)
283
+ instance.update_notifier()
284
+ cache = CacheSingletonRedis()
285
+ cache.set(instance.__extonto_qname, instance)
286
+ return instance
287
+
288
+ @staticmethod
289
+ def search(con: IConnection, *, # TODO: finish work here!!! This is just a small sceleton!
290
+ projectShortName: Xsd_NCName | str,
291
+ prefix: NCName | str | None = None,
292
+ namespaceIri: NamespaceIRI | str | None = None,
293
+ label: Xsd_string | str | None = None,
294
+ validate: bool = False) -> list['ExternalOntology']:
295
+ if not isinstance(projectShortName, Xsd_NCName):
296
+ projectShortName = Xsd_NCName(projectShortName, validate=validate)
297
+
298
+ context = Context(name=con.context_name)
299
+ sparql = context.sparql_context
300
+ sparql += f"SELECT DISTINCT ?extonto ?p ?o"
301
+ sparql += f"\nFROM {projectShortName}:shacl"
302
+ sparql += "\nWHERE {"
303
+ sparql += "\n ?extonto a oldap:ExternalOntology ."
304
+ sparql += "\n ?extonto ?p ?o ."
305
+ if prefix:
306
+ sparql += f'\n FILTER( REPLACE(STR(?extonto), ".+[#/]", "") = "{prefix}" )'
307
+ elif namespaceIri:
308
+ sparql += f'\n ?extonto oldap:namespaceIri ?namespaceIri .'
309
+ sparql += f'\n FILTER( ?namespaceIri = "{namespaceIri}" )'
310
+ elif label:
311
+ sparql += f'\n ?extonto rdfs:label ?label .'
312
+ if label.lang:
313
+ sparql += f'?label = {label.toRdf}'
314
+ else:
315
+ sparql += f'CONTAINS(STR(?label), "{Xsd_string.escaping(label.value)}")'
316
+ sparql += "\n}"
317
+ sparql += "\nORDER BY ?extonto"
318
+ jsonobj = con.query(sparql)
319
+ res = QueryProcessor(context, jsonobj)
320
+ result: list[ExternalOntology] = []
321
+ working_on: Xsd_QName | None = None
322
+ data: dict = {}
323
+ cache = CacheSingletonRedis()
324
+ for r in res:
325
+ if working_on is None or working_on != r['extonto']:
326
+ if working_on:
327
+ tmp = ExternalOntology(con=con,
328
+ projectShortName=projectShortName,
329
+ **data)
330
+ result.append(tmp)
331
+ cache.set(tmp.__extonto_qname, tmp)
332
+ data = {}
333
+ data['label'] = LangString()
334
+ data['comment'] = LangString()
335
+ working_on = r['extonto']
336
+ if r['p'] == 'rdf:type':
337
+ continue
338
+ if r['p'] == 'rdfs:label':
339
+ data['label'].add(r['o'])
340
+ elif r['p'] == 'rdfs:comment':
341
+ data['comment'].add(r['o'])
342
+ elif r['p'] == 'oldap:namespaceIri':
343
+ data['namespaceIri'] = NamespaceIRI(r['o'])
344
+ else:
345
+ data[str(r['p'].fragment)] = r['o']
346
+ if working_on:
347
+ tmp = ExternalOntology(con=con,
348
+ projectShortName=projectShortName,
349
+ **data)
350
+ result.append(tmp)
351
+ cache.set(tmp.__extonto_qname, tmp)
352
+
353
+ return result
354
+
355
+ def update(self, indent: int = 0, indent_inc: int = 4) -> None:
356
+ result, message = self.check_for_permissions()
357
+ if not result:
358
+ raise OldapErrorNoPermission(message)
359
+ timestamp = Xsd_dateTime.now()
360
+ context = Context(name=self._con.context_name)
361
+ blank = ''
362
+ sparql_list = []
363
+
364
+ for attr, change in self._changeset.items():
365
+ if attr == ExternalOntologyAttr.LABEL or attr == ExternalOntologyAttr.COMMENT:
366
+ if change.action == Action.MODIFY:
367
+ sparql_list.extend(
368
+ self._attributes[attr].update(graph=Xsd_QName(self.__projectShortName, 'shacl'),
369
+ subject=self.__extonto_qname,
370
+ field=attr.value))
371
+ if change.action == Action.DELETE or change.action == Action.REPLACE:
372
+ sparql = self._changeset[attr].old_value.delete(graph=Xsd_QName(self.__projectShortName, 'shacl'),
373
+ subject=self.__extonto_qname,
374
+ field=attr.value)
375
+ sparql_list.append(sparql)
376
+ if change.action == Action.CREATE or change.action == Action.REPLACE:
377
+ sparql = self._attributes[attr].create(graph=Xsd_QName(self.__projectShortName, 'shacl'),
378
+ subject=self.__extonto_qname,
379
+ field=attr.value)
380
+ sparql_list.append(sparql)
381
+ continue
382
+ sparql = f'{blank:{indent * indent_inc}}WITH {self.__projectShortName}:shacl\n'
383
+ if change.action != Action.CREATE:
384
+ sparql += f'{blank:{indent * indent_inc}}DELETE {{\n'
385
+ sparql += f'{blank:{(indent + 1) * indent_inc}}{self.__extonto_qname.toRdf} {attr.value} {change.old_value.toRdf} .\n'
386
+ sparql += f'{blank:{indent * indent_inc}}}}\n'
387
+ if change.action != Action.DELETE:
388
+ sparql += f'{blank:{indent * indent_inc}}INSERT {{\n'
389
+ sparql += f'{blank:{(indent + 1) * indent_inc}}{self.__extonto_qname.toRdf} {attr.value} {self._attributes[attr].toRdf} .\n'
390
+ sparql += f'{blank:{indent * indent_inc}}}}\n'
391
+ sparql += f'{blank:{indent * indent_inc}}WHERE {{\n'
392
+ sparql += f'{blank:{(indent + 1) * indent_inc}}{self.__extonto_qname.toRdf} {attr.value} {change.old_value.toRdf} .\n'
393
+ sparql += f'{blank:{indent * indent_inc}}}}'
394
+ sparql_list.append(sparql)
395
+ sparql = context.sparql_context
396
+ sparql += " ;\n".join(sparql_list)
397
+
398
+ self._con.transaction_start()
399
+ try:
400
+ self._con.transaction_update(sparql)
401
+ self.set_modified_by_iri(Xsd_QName(self.__projectShortName, 'shacl'), self.__extonto_qname, self._modified, timestamp)
402
+ modtime = self.get_modified_by_iri(Xsd_QName(self.__projectShortName, 'shacl'), self.__extonto_qname)
403
+ except OldapError:
404
+ self._con.transaction_abort()
405
+ raise
406
+ if timestamp != modtime:
407
+ self._con.transaction_abort()
408
+ raise OldapErrorUpdateFailed(f'Update of ExternalOntology "{self.__extonto_qname}" failed! Timestamp does not match"')
409
+ try:
410
+ self._con.transaction_commit()
411
+ except OldapError:
412
+ self._con.transaction_abort()
413
+ raise
414
+ self._modified = timestamp
415
+ self._contributor = self._con.userIri # TODO: move creator, created etc. to Model!
416
+ cache = CacheSingletonRedis()
417
+ cache.set(self.__extonto_qname, self)
418
+
419
+ def in_use_queries(self) -> (str, str):
420
+ """
421
+ Generates two SPARQL ASK queries to check the usage of a permission set in two contexts:
422
+ assigned to a user or used by a data object (resource). The generated queries are used
423
+ to confirm whether the specified permission set is currently in use within the system.
424
+
425
+ The first query checks if the permission set is assigned to any user. The second query
426
+ validates if the permission set is associated with any data object or resource.
427
+
428
+ :return: A tuple containing two SPARQL ASK queries as strings. The first query checks
429
+ if the permission set is assigned to a user, and the second query checks if
430
+ the permission set is associated with a data object or resource.
431
+ :rtype: tuple[str, str]
432
+ """
433
+ context = Context(name=self._con.context_name)
434
+
435
+ #
436
+ # first check if the external ontology is used in the datamodel
437
+ # TODO: Exlucde ExternalOntology definitions itself....
438
+ #
439
+ query1 = context.sparql_context
440
+ query1 += f"""
441
+ ASK {{
442
+ GRAPH {self.__projectShortName}:shacl {{
443
+ ?s ?p ?o .
444
+ FILTER(
445
+ (STRSTARTS(STR(?p), "{self.namespaceIri}") ||
446
+ (isIRI(?o) && STRSTARTS(STR(?o), "{self.namespaceIri}"))) &&
447
+ ?p != oldap:namespaceIri
448
+ )
449
+ }}
450
+ }}
451
+ """
452
+
453
+ #
454
+ # now check if the permission set is used by a data object (resource)
455
+ #
456
+ query2 = context.sparql_context
457
+ query2 += f"""
458
+ ASK {{
459
+ VALUES ?g {{ {self.__projectShortName}:data {self.__projectShortName}:lists }}
460
+ GRAPH ?g {{
461
+ ?s ?p ?o .
462
+ FILTER(
463
+ STRSTARTS(STR(?p), "{self.namespaceIri}") ||
464
+ (isIRI(?o) && STRSTARTS(STR(?o), "{self.namespaceIri}"))
465
+ )
466
+ }}
467
+ }}
468
+ """
469
+ return query1, query2
470
+
471
+ def in_use(self) -> bool:
472
+ result, message = self.check_for_permissions()
473
+ if not result:
474
+ raise OldapErrorNoPermission(message)
475
+
476
+ query1, query2 = self.in_use_queries()
477
+ result1 = self._con.query(query1)
478
+ if result1['boolean']:
479
+ return True
480
+ result2 = self._con.query(query2)
481
+ if result2['boolean']:
482
+ return True
483
+ return False
484
+
485
+ def delete(self, indent: int = 0, indent_inc: int = 4) -> None:
486
+ result, message = self.check_for_permissions()
487
+ if not result:
488
+ raise OldapErrorNoPermission(message)
489
+
490
+ query1, query2 = self.in_use_queries()
491
+ context = Context(name=self._con.context_name)
492
+
493
+ #
494
+ # Now prepare the queries for deleting the permission set
495
+ #
496
+ sparql = context.sparql_context
497
+ sparql += f"""
498
+ DELETE WHERE {{
499
+ GRAPH {self.__projectShortName}:shacl {{
500
+ {self.__extonto_qname.toRdf} ?prop ?val .
501
+ }}
502
+ }}
503
+ """
504
+
505
+ self._con.transaction_start()
506
+ result1 = self.safe_query(query1)
507
+ if result1['boolean']:
508
+ self._con.transaction_abort()
509
+ raise OldapErrorInUse(f"External ontology is used in the data model.")
510
+ result2 = self.safe_query(query2)
511
+ if result2['boolean']:
512
+ self._con.transaction_abort()
513
+ raise OldapErrorInUse("External ontology is used in the data.")
514
+ self.safe_update(sparql)
515
+ self._con.transaction_commit()
516
+ cache = CacheSingletonRedis()
517
+ cache.delete(self.__extonto_qname)
518
+
519
+ @classmethod
520
+ def delete_all(cls, *,
521
+ con: IConnection,
522
+ projectShortName: Xsd_NCName | str,
523
+ validate: bool = False) -> None:
524
+ if not isinstance(projectShortName, Xsd_NCName):
525
+ projectShortName = Xsd_NCName(projectShortName, validate=validate)
526
+ context = Context(name=con.context_name)
527
+
528
+ query0 = context.sparql_context
529
+ query0 += f"""
530
+ SELECT DISTINCT ?extonto
531
+ FROM {projectShortName}:shacl
532
+ WHERE {{
533
+ ?extonto a oldap:ExternalOntology .
534
+ }}
535
+ """
536
+
537
+ sparql = context.sparql_context
538
+ sparql += f"""
539
+ DELETE {{
540
+ GRAPH {projectShortName}:shacl {{
541
+ ?s ?p ?o .
542
+ }}
543
+ }}
544
+ WHERE {{
545
+ GRAPH {projectShortName}:shacl {{
546
+ ?s a oldap:ExternalOntology .
547
+ ?s ?p ?o .
548
+ }}
549
+ }}
550
+ """
551
+ con.update_query(sparql)
552
+
553
+
554
+
@@ -188,6 +188,11 @@ class HasProperty(Model, Notify):
188
188
  order=self._attributes.get(HasPropertyAttr.ORDER, None),
189
189
  group=self._attributes.get(HasPropertyAttr.GROUP, None))
190
190
 
191
+ def clear_changeset(self) -> None:
192
+ if hasattr(self._prop, 'clear_changeset'):
193
+ self._prop.clear_changeset()
194
+ super().clear_changeset()
195
+
191
196
  def notifier(self, attr: HasPropertyAttr | Iri | Xsd_QName) -> None:
192
197
  #if attr == HasPropertyAttr.PROP:
193
198
  # return
@@ -46,9 +46,9 @@ class ContextSingleton(type):
46
46
  Xsd_NCName('sh'): NamespaceIRI('http://www.w3.org/ns/shacl#'),
47
47
  Xsd_NCName('skos'): NamespaceIRI('http://www.w3.org/2004/02/skos/core#'),
48
48
  Xsd_NCName('schema'): NamespaceIRI('http://schema.org/'),
49
- Xsd_NCName('dc'): NamespaceIRI('http://purl.org/dc/elements/1.1/'),
49
+ #Xsd_NCName('dc'): NamespaceIRI('http://purl.org/dc/elements/1.1/'),
50
50
  Xsd_NCName('dcterms'): NamespaceIRI('http://purl.org/dc/terms/'),
51
- Xsd_NCName('foaf'): NamespaceIRI('http://xmlns.com/foaf/0.1/'),
51
+ #Xsd_NCName('foaf'): NamespaceIRI('http://xmlns.com/foaf/0.1/'),
52
52
  Xsd_NCName('oldap'): NamespaceIRI('http://oldap.org/base#'),
53
53
  Xsd_NCName('shared'): NamespaceIRI('http://oldap.org/shared#')
54
54
  }
@@ -61,9 +61,9 @@ class ContextSingleton(type):
61
61
  NamespaceIRI('http://www.w3.org/ns/shacl#'): Xsd_NCName('sh'),
62
62
  NamespaceIRI('http://www.w3.org/2004/02/skos/core#'): Xsd_NCName('skos'),
63
63
  NamespaceIRI('http://schema.org/'): Xsd_NCName('schema'),
64
- NamespaceIRI('http://purl.org/dc/elements/1.1/'): Xsd_NCName('dc'),
64
+ #NamespaceIRI('http://purl.org/dc/elements/1.1/'): Xsd_NCName('dc'),
65
65
  NamespaceIRI('http://purl.org/dc/terms/'): Xsd_NCName('dcterms'),
66
- NamespaceIRI('http://xmlns.com/foaf/0.1/'): Xsd_NCName('foaf'),
66
+ #NamespaceIRI('http://xmlns.com/foaf/0.1/'): Xsd_NCName('foaf'),
67
67
  NamespaceIRI('http://oldap.org/base#'): Xsd_NCName('oldap'),
68
68
  NamespaceIRI('http://oldap.org/shared#'): Xsd_NCName('shared'),
69
69
  }
@@ -66,7 +66,7 @@ class LangString(Notify):
66
66
  - _add()_: Add a string
67
67
  - _undo_(): Forget all changes
68
68
  - _changeset()_: Return the changeset dict (Note: this is not for generic use)
69
- - _changeset_clear()_: Clear changeset to an empty dict
69
+ - _clear_changeset()_: Clear changeset to an empty dict
70
70
  - _update_shacl_(): Return the SPARQL code piece that updates a Language string SHACL part of the triple store.
71
71
  - _delete_shacl_(): Return the SPARQL code piece that deletes an LanguageString
72
72
  """
@@ -440,7 +440,7 @@ class LangString(Notify):
440
440
  """
441
441
  return self._changeset
442
442
 
443
- def changeset_clear(self) -> None:
443
+ def clear_changeset(self) -> None:
444
444
  """
445
445
  Clear changeset to an empty dict
446
446
  :return: Nothing
@@ -448,6 +448,15 @@ class LangString(Notify):
448
448
  """
449
449
  self._changeset = {}
450
450
 
451
+ def clear_changset(self) -> None:
452
+ """
453
+ Clear changeset to an empty dict
454
+ :return: Nothing
455
+ :rtype: None
456
+ """
457
+ self._changeset = {}
458
+
459
+
451
460
  def create(self, *,
452
461
  graph: Xsd_QName,
453
462
  subject: Iri,
@@ -77,6 +77,9 @@ class ObservableDict(UserDict):
77
77
  def _as_dict(self):
78
78
  return {'obsdict': [{'key': key, 'val': val} for key, val in self.data.items()]}
79
79
 
80
- def clear_changeset(self):
80
+ def clear_changeset(self) -> None:
81
+ for item in self.data.values():
82
+ if hasattr(item, 'clear_changeset'):
83
+ item.clear_changeset()
81
84
  self._changeset = {}
82
85