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.
Files changed (42) 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/attributeclass.py +31 -6
  8. oldaplib/src/enums/externalontologyattr.py +22 -0
  9. oldaplib/src/enums/owlpropertytype.py +10 -0
  10. oldaplib/src/enums/projectattr.py +0 -1
  11. oldaplib/src/enums/propertyclassattr.py +24 -19
  12. oldaplib/src/externalontology.py +554 -0
  13. oldaplib/src/hasproperty.py +5 -0
  14. oldaplib/src/helpers/context.py +4 -4
  15. oldaplib/src/helpers/langstring.py +11 -2
  16. oldaplib/src/helpers/observable_dict.py +4 -1
  17. oldaplib/src/helpers/observable_set.py +135 -80
  18. oldaplib/src/helpers/query_processor.py +3 -0
  19. oldaplib/src/model.py +1 -1
  20. oldaplib/src/oldaplist.py +2 -2
  21. oldaplib/src/oldaplistnode.py +2 -2
  22. oldaplib/src/permissionset.py +3 -3
  23. oldaplib/src/project.py +47 -113
  24. oldaplib/src/propertyclass.py +98 -36
  25. oldaplib/src/resourceclass.py +7 -5
  26. oldaplib/src/version.py +1 -1
  27. oldaplib/src/xsd/iri.py +3 -0
  28. oldaplib/src/xsd/xsd_anyuri.py +5 -5
  29. oldaplib/src/xsd/xsd_qname.py +6 -3
  30. oldaplib/test/test_context.py +0 -4
  31. oldaplib/test/test_datamodel.py +50 -1
  32. oldaplib/test/test_dtypes.py +3 -2
  33. oldaplib/test/test_externalontologies.py +175 -0
  34. oldaplib/test/test_project.py +31 -76
  35. oldaplib/test/test_propertyclass.py +168 -6
  36. oldaplib/test/test_resourceclass.py +10 -10
  37. oldaplib/testdata/connection_test.trig +29 -12
  38. oldaplib/testdata/datamodel_test.trig +1 -1
  39. oldaplib/testdata/objectfactory_test.trig +2 -1
  40. {oldaplib-0.3.3.dist-info → oldaplib-0.3.5.dist-info}/METADATA +3 -2
  41. {oldaplib-0.3.3.dist-info → oldaplib-0.3.5.dist-info}/RECORD +42 -39
  42. {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', 'usesExternalOntology')
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.changeset_clear()
354
+ label.clear_changeset()
389
355
  label.set_notifier(cls.notifier, Xsd_QName(ProjectAttr.LABEL.value))
390
356
  if comment:
391
- comment.changeset_clear()
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
- if attr == ProjectAttr.USES_EXTERNAL_ONTOLOGY:
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
- jsonobj = self.safe_query(sparql1)
535
- res = QueryProcessor(context, jsonobj)
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 projectIri "{self.projectIri}" already exists')
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
- if field == ProjectAttr.USES_EXTERNAL_ONTOLOGY:
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'
@@ -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] = OwlPropertyType.StatementProperty
317
- else:
318
- if self._attributes.get(PropClassAttr.CLASS) is not None:
319
- self._attributes[PropClassAttr.TYPE] = OwlPropertyType.OwlObjectProperty
320
- if self._attributes.get(PropClassAttr.DATATYPE) is not None:
321
- raise OldapError(f'Datatype "{self._attributes.get(PropClassAttr.DATATYPE)}" not possible for OwlObjectProperty')
322
- elif self._attributes.get(PropClassAttr.DATATYPE) is not None:
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] = OwlPropertyType.OwlObjectProperty
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
- self._attributes[PropClassAttr.TYPE] = OwlPropertyType.OwlDataProperty
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
- if obj == 'owl:DatatypeProperty':
872
- self._attributes[PropClassAttr.TYPE] = OwlPropertyType.OwlDataProperty
873
- elif obj == 'owl:ObjectProperty':
874
- self._attributes[PropClassAttr.TYPE] = OwlPropertyType.OwlObjectProperty
875
- elif obj == 'rdf:Property':
876
- self._attributes[PropClassAttr.TYPE] = OwlPropertyType.StatementProperty
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) != OwlPropertyType.StatementProperty:
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) == OwlPropertyType.StatementProperty:
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] == OwlPropertyType.OwlDataProperty:
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] == OwlPropertyType.OwlObjectProperty:
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
- cache.set(property.property_class_iri, property)
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 == PropClassAttr.TYPE:
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} rdf:type {self._attributes[PropClassAttr.TYPE].value}'
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) == OwlPropertyType.OwlDataProperty:
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) == OwlPropertyType.OwlObjectProperty:
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] == OwlPropertyType.OwlDataProperty:
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] == OwlPropertyType.OwlObjectProperty:
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} # used for rdfs:range in OWL ontology
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].changeset_clear()
1639
+ self._attributes[prop].clear_changeset()
1578
1640
  self._changeset = {}
1579
1641
  cache = CacheSingletonRedis()
1580
1642
  cache.set(self._property_class_iri, self)
@@ -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 changeset_clear(self) -> None:
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.changeset_clear()
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.changeset_clear()
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.changeset_clear()
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.changeset_clear()
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.3"
1
+ __version__ = "0.3.5"
oldaplib/src/xsd/iri.py CHANGED
@@ -169,6 +169,9 @@ class Iri(Xsd):
169
169
  """
170
170
  return hash(self.__value)
171
171
 
172
+ def startswith(self, value, start=None, end=None):
173
+ return self.__value.startswith(value, start, end)
174
+
172
175
  @property
173
176
  def toRdf(self) -> str:
174
177
  """
@@ -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:
@@ -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
- fragment = Xsd_NCName(fragment, validate=validate)
62
- self._value = f'{prefix}:{fragment}'
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
  """