dsp-tools 17.0.0.post14__py3-none-any.whl → 17.0.0.post21__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of dsp-tools might be problematic. Click here for more details.

Files changed (27) hide show
  1. dsp_tools/clients/metadata_client.py +24 -0
  2. dsp_tools/clients/metadata_client_live.py +42 -0
  3. dsp_tools/clients/ontology_client.py +21 -0
  4. dsp_tools/clients/ontology_client_live.py +139 -0
  5. dsp_tools/commands/create/communicate_problems.py +19 -0
  6. dsp_tools/commands/create/constants.py +6 -3
  7. dsp_tools/commands/create/create_on_server/cardinalities.py +121 -0
  8. dsp_tools/commands/create/create_on_server/mappers.py +12 -0
  9. dsp_tools/commands/create/models/input_problems.py +11 -2
  10. dsp_tools/commands/create/models/rdf_ontology.py +19 -0
  11. dsp_tools/commands/create/models/server_project_info.py +25 -0
  12. dsp_tools/commands/create/parsing/parse_ontology.py +7 -7
  13. dsp_tools/commands/create/parsing/parse_project.py +1 -1
  14. dsp_tools/commands/create/parsing/parsing_utils.py +2 -2
  15. dsp_tools/commands/create/serialisation/__init__.py +0 -0
  16. dsp_tools/commands/create/serialisation/ontology.py +41 -0
  17. dsp_tools/commands/project/create/project_create_all.py +35 -25
  18. dsp_tools/commands/project/create/project_create_ontologies.py +39 -89
  19. dsp_tools/commands/project/legacy_models/resourceclass.py +0 -33
  20. dsp_tools/error/exceptions.py +4 -0
  21. dsp_tools/utils/data_formats/iri_util.py +7 -0
  22. dsp_tools/utils/rdflib_utils.py +10 -0
  23. {dsp_tools-17.0.0.post14.dist-info → dsp_tools-17.0.0.post21.dist-info}/METADATA +1 -1
  24. {dsp_tools-17.0.0.post14.dist-info → dsp_tools-17.0.0.post21.dist-info}/RECORD +27 -15
  25. /dsp_tools/commands/create/{parsing/parse_lists.py → create_on_server/__init__.py} +0 -0
  26. {dsp_tools-17.0.0.post14.dist-info → dsp_tools-17.0.0.post21.dist-info}/WHEEL +0 -0
  27. {dsp_tools-17.0.0.post14.dist-info → dsp_tools-17.0.0.post21.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,24 @@
1
+ from dataclasses import dataclass
2
+ from enum import Enum
3
+ from enum import auto
4
+ from typing import Protocol
5
+
6
+ from dsp_tools.clients.authentication_client import AuthenticationClient
7
+
8
+
9
+ class MetadataRetrieval(Enum):
10
+ SUCCESS = auto()
11
+ FAILURE = auto()
12
+
13
+
14
+ @dataclass
15
+ class MetadataClient(Protocol):
16
+ """
17
+ Protocol class/interface for the metadata endpoint in the API.
18
+ """
19
+
20
+ server: str
21
+ authentication_client: AuthenticationClient
22
+
23
+ def get_resource_metadata(self, shortcode: str) -> tuple[MetadataRetrieval, list[dict[str, str]]]:
24
+ """Get all resource metadata from one project."""
@@ -0,0 +1,42 @@
1
+ from dataclasses import dataclass
2
+
3
+ import requests
4
+ from loguru import logger
5
+
6
+ from dsp_tools.clients.authentication_client import AuthenticationClient
7
+ from dsp_tools.clients.metadata_client import MetadataClient
8
+ from dsp_tools.clients.metadata_client import MetadataRetrieval
9
+ from dsp_tools.utils.request_utils import RequestParameters
10
+ from dsp_tools.utils.request_utils import log_request
11
+ from dsp_tools.utils.request_utils import log_response
12
+
13
+ TIMEOUT = 120
14
+
15
+
16
+ @dataclass
17
+ class MetadataClientLive(MetadataClient):
18
+ server: str
19
+ authentication_client: AuthenticationClient
20
+
21
+ def get_resource_metadata(self, shortcode: str) -> tuple[MetadataRetrieval, list[dict[str, str]]]:
22
+ url = f"{self.server}/v2/metadata/projects/{shortcode}/resources?format=JSON"
23
+ header = {"Authorization": f"Bearer {self.authentication_client.get_token()}"}
24
+ params = RequestParameters(method="GET", url=url, timeout=TIMEOUT, headers=header)
25
+ logger.debug("GET Resource Metadata")
26
+ log_request(params)
27
+ try:
28
+ response = requests.get(
29
+ url=params.url,
30
+ headers=params.headers,
31
+ timeout=params.timeout,
32
+ )
33
+ if response.ok:
34
+ # we log the response separately because if it was successful it will be too big
35
+ log_response(response, include_response_content=False)
36
+ return MetadataRetrieval.SUCCESS, response.json()
37
+ # here the response text is important
38
+ log_response(response)
39
+ return MetadataRetrieval.FAILURE, []
40
+ except Exception as err: # noqa: BLE001 (blind exception)
41
+ logger.error(err)
42
+ return MetadataRetrieval.FAILURE, []
@@ -0,0 +1,21 @@
1
+ from typing import Any
2
+ from typing import Protocol
3
+
4
+ from rdflib import Literal
5
+
6
+ from dsp_tools.clients.authentication_client import AuthenticationClient
7
+
8
+
9
+ class OntologyClient(Protocol):
10
+ """
11
+ Protocol class/interface for the ontology endpoint in the API.
12
+ """
13
+
14
+ server: str
15
+ authentication_client: AuthenticationClient
16
+
17
+ def get_last_modification_date(self, project_iri: str, onto_iri: str) -> str:
18
+ """Get the last modification date of an ontology"""
19
+
20
+ def post_resource_cardinalities(self, cardinality_graph: dict[str, Any]) -> Literal | None:
21
+ """Add cardinalities to an existing resource class."""
@@ -0,0 +1,139 @@
1
+ from dataclasses import dataclass
2
+ from http import HTTPStatus
3
+ from typing import Any
4
+
5
+ import requests
6
+ from loguru import logger
7
+ from rdflib import Graph
8
+ from rdflib import Literal
9
+ from rdflib import URIRef
10
+ from requests import ReadTimeout
11
+ from requests import Response
12
+
13
+ from dsp_tools.clients.authentication_client import AuthenticationClient
14
+ from dsp_tools.clients.ontology_client import OntologyClient
15
+ from dsp_tools.error.exceptions import BadCredentialsError
16
+ from dsp_tools.error.exceptions import UnexpectedApiResponseError
17
+ from dsp_tools.utils.rdflib_constants import KNORA_API
18
+ from dsp_tools.utils.request_utils import RequestParameters
19
+ from dsp_tools.utils.request_utils import log_and_raise_timeouts
20
+ from dsp_tools.utils.request_utils import log_request
21
+ from dsp_tools.utils.request_utils import log_response
22
+
23
+ TIMEOUT = 60
24
+
25
+
26
+ @dataclass
27
+ class OntologyClientLive(OntologyClient):
28
+ """
29
+ Client for the ontology endpoint in the API.
30
+ """
31
+
32
+ server: str
33
+ authentication_client: AuthenticationClient
34
+
35
+ def get_last_modification_date(self, project_iri: str, onto_iri: str) -> Literal:
36
+ url = f"{self.server}/v2/ontologies/metadata"
37
+ header = {"X-Knora-Accept-Project": project_iri}
38
+ logger.debug("GET ontology metadata")
39
+ try:
40
+ response = self._get_and_log_request(url, header)
41
+ except (TimeoutError, ReadTimeout) as err:
42
+ log_and_raise_timeouts(err)
43
+ if response.ok:
44
+ date = _parse_last_modification_date(response.text, URIRef(onto_iri))
45
+ if not date:
46
+ raise UnexpectedApiResponseError(
47
+ f"Could not find the last modification date of the ontology '{onto_iri}' "
48
+ f"in the response: {response.text}"
49
+ )
50
+ return date
51
+ if response.status_code == HTTPStatus.FORBIDDEN:
52
+ raise BadCredentialsError("You do not have sufficient credentials to retrieve ontology metadata.")
53
+ else:
54
+ raise UnexpectedApiResponseError(
55
+ f"An unexpected response with the status code {response.status_code} was received from the API. "
56
+ f"Please consult 'warnings.log' for details."
57
+ )
58
+
59
+ def post_resource_cardinalities(self, cardinality_graph: dict[str, Any]) -> Literal | None:
60
+ url = f"{self.server}/v2/ontologies/cardinalities"
61
+
62
+ logger.debug("POST resource cardinalities to ontology")
63
+ try:
64
+ response = self._post_and_log_request(url, cardinality_graph)
65
+ except (TimeoutError, ReadTimeout) as err:
66
+ log_and_raise_timeouts(err)
67
+ if response.ok:
68
+ date = _parse_last_modification_date(response.text)
69
+ if not date:
70
+ raise UnexpectedApiResponseError(
71
+ f"Could not find the last modification date in the response: {response.text}"
72
+ )
73
+ return date
74
+ if response.status_code == HTTPStatus.FORBIDDEN:
75
+ raise BadCredentialsError(
76
+ "Only a project or system administrator can add cardinalities to resource classes. "
77
+ "Your permissions are insufficient for this action."
78
+ )
79
+ else:
80
+ logger.error(
81
+ f"During cardinality creation an unexpected response with the status code {response.status_code} "
82
+ f"was received from the API."
83
+ )
84
+ return None
85
+
86
+ def _post_and_log_request(
87
+ self,
88
+ url: str,
89
+ data: dict[str, Any] | None,
90
+ headers: dict[str, str] | None = None,
91
+ ) -> Response:
92
+ data_dict, generic_headers = self._prepare_request(data, headers)
93
+ params = RequestParameters("POST", url, TIMEOUT, data_dict, generic_headers)
94
+ log_request(params)
95
+ response = requests.post(
96
+ url=params.url,
97
+ headers=params.headers,
98
+ data=params.data_serialized,
99
+ timeout=params.timeout,
100
+ )
101
+ log_response(response)
102
+ return response
103
+
104
+ def _get_and_log_request(
105
+ self,
106
+ url: str,
107
+ headers: dict[str, str] | None = None,
108
+ ) -> Response:
109
+ _, generic_headers = self._prepare_request({}, headers)
110
+ params = RequestParameters(method="GET", url=url, timeout=TIMEOUT, headers=generic_headers)
111
+ log_request(params)
112
+ response = requests.get(
113
+ url=params.url,
114
+ headers=params.headers,
115
+ timeout=params.timeout,
116
+ )
117
+ log_response(response)
118
+ return response
119
+
120
+ def _prepare_request(
121
+ self, data: dict[str, Any] | None, headers: dict[str, str] | None
122
+ ) -> tuple[dict[str, Any] | None, dict[str, str]]:
123
+ generic_headers = {
124
+ "Content-Type": "application/json",
125
+ "Authorization": f"Bearer {self.authentication_client.get_token()}",
126
+ }
127
+ data_dict = data if data else None
128
+ if headers:
129
+ generic_headers.update(headers)
130
+ return data_dict, generic_headers
131
+
132
+
133
+ def _parse_last_modification_date(response_text: str, onto_iri: URIRef | None = None) -> Literal | None:
134
+ g = Graph()
135
+ g.parse(data=response_text, format="json-ld")
136
+ result = next(g.objects(subject=onto_iri, predicate=KNORA_API.lastModificationDate), None)
137
+ if isinstance(result, Literal):
138
+ return result
139
+ return None
@@ -0,0 +1,19 @@
1
+ from loguru import logger
2
+
3
+ from dsp_tools.commands.create.models.input_problems import CollectedProblems
4
+ from dsp_tools.commands.create.models.input_problems import CreateProblem
5
+ from dsp_tools.utils.ansi_colors import BOLD_RED
6
+ from dsp_tools.utils.ansi_colors import RED
7
+ from dsp_tools.utils.ansi_colors import RESET_TO_DEFAULT
8
+
9
+
10
+ def print_problem_collection(problem_collection: CollectedProblems) -> None:
11
+ individual_problems = _create_individual_problem_strings(problem_collection.problems)
12
+ logger.error(problem_collection.header, individual_problems)
13
+ print(BOLD_RED, problem_collection.header, RESET_TO_DEFAULT)
14
+ print(RED, individual_problems, RESET_TO_DEFAULT)
15
+
16
+
17
+ def _create_individual_problem_strings(problems: list[CreateProblem]) -> str:
18
+ str_list = [f"{p.problematic_object}: {p.problem!s}" for p in problems]
19
+ return " - " + "\n - ".join(str_list)
@@ -1,4 +1,7 @@
1
- KNORA_API = "http://api.knora.org/ontology/knora-api/v2#"
2
- SALSAH_GUI = "http://api.knora.org/ontology/salsah-gui/v2#"
1
+ from rdflib import Namespace
3
2
 
4
- UNIVERSAL_PREFIXES = {"knora-api": KNORA_API, "salsah-gui": SALSAH_GUI}
3
+ KNORA_API_STR = "http://api.knora.org/ontology/knora-api/v2#"
4
+ SALSAH_GUI_STR = "http://api.knora.org/ontology/salsah-gui/v2#"
5
+ SALSAH_GUI = Namespace(SALSAH_GUI_STR)
6
+
7
+ UNIVERSAL_PREFIXES = {"knora-api": KNORA_API_STR, "salsah-gui": SALSAH_GUI_STR}
@@ -0,0 +1,121 @@
1
+ from typing import Any
2
+
3
+ from loguru import logger
4
+ from rdflib import Literal
5
+ from rdflib import URIRef
6
+
7
+ from dsp_tools.clients.ontology_client import OntologyClient
8
+ from dsp_tools.clients.ontology_client_live import OntologyClientLive
9
+ from dsp_tools.commands.create.models.input_problems import CollectedProblems
10
+ from dsp_tools.commands.create.models.input_problems import CreateProblem
11
+ from dsp_tools.commands.create.models.input_problems import ProblemType
12
+ from dsp_tools.commands.create.models.input_problems import UploadProblem
13
+ from dsp_tools.commands.create.models.parsed_ontology import ParsedClassCardinalities
14
+ from dsp_tools.commands.create.models.parsed_ontology import ParsedOntology
15
+ from dsp_tools.commands.create.models.parsed_ontology import ParsedPropertyCardinality
16
+ from dsp_tools.commands.create.models.server_project_info import CreatedIriCollection
17
+ from dsp_tools.commands.create.models.server_project_info import ProjectIriLookup
18
+ from dsp_tools.commands.create.serialisation.ontology import _make_one_cardinality_graph
19
+ from dsp_tools.commands.create.serialisation.ontology import make_ontology_base_graph
20
+ from dsp_tools.utils.data_formats.iri_util import from_dsp_iri_to_prefixed_iri
21
+ from dsp_tools.utils.rdflib_utils import serialise_json
22
+
23
+
24
+ def add_all_cardinalities(
25
+ ontologies: list[ParsedOntology],
26
+ project_iri_lookup: ProjectIriLookup,
27
+ created_iris: CreatedIriCollection,
28
+ onto_client: OntologyClientLive,
29
+ ) -> CollectedProblems | None:
30
+ all_problems = []
31
+ for onto in ontologies:
32
+ onto_iri = project_iri_lookup.onto_iris.get(onto.name)
33
+ # we do not inform about onto failures here, as it will have been done upstream
34
+ if onto_iri:
35
+ last_mod_date = onto_client.get_last_modification_date(project_iri_lookup.project_iri, onto_iri)
36
+ problems = _add_all_cardinalities_for_one_onto(
37
+ cardinalities=onto.cardinalities,
38
+ onto_iri=URIRef(onto_iri),
39
+ last_modification_date=last_mod_date,
40
+ onto_client=onto_client,
41
+ created_iris=created_iris,
42
+ )
43
+ all_problems.extend(problems)
44
+ if all_problems:
45
+ return CollectedProblems("During the cardinality creation the following problems occurred:", all_problems)
46
+ return None
47
+
48
+
49
+ def _add_all_cardinalities_for_one_onto(
50
+ cardinalities: list[ParsedClassCardinalities],
51
+ onto_iri: URIRef,
52
+ last_modification_date: Literal,
53
+ onto_client: OntologyClient,
54
+ created_iris: CreatedIriCollection,
55
+ ) -> list[CreateProblem]:
56
+ problems: list[CreateProblem] = []
57
+ for c in cardinalities:
58
+ # we do not inform about classes failures here, as it will have been done upstream
59
+ if c.class_iri not in created_iris.classes:
60
+ logger.warning(f"CARDINALITY: Class '{c.class_iri}' not in successes, no cardinalities added.")
61
+ continue
62
+ last_modification_date, creation_problems = _add_cardinalities_for_one_class(
63
+ resource_card=c,
64
+ onto_iri=onto_iri,
65
+ last_modification_date=last_modification_date,
66
+ onto_client=onto_client,
67
+ successful_props=created_iris.properties,
68
+ )
69
+ problems.extend(creation_problems)
70
+ return problems
71
+
72
+
73
+ def _add_cardinalities_for_one_class(
74
+ resource_card: ParsedClassCardinalities,
75
+ onto_iri: URIRef,
76
+ last_modification_date: Literal,
77
+ onto_client: OntologyClient,
78
+ successful_props: set[str],
79
+ ) -> tuple[Literal, list[UploadProblem]]:
80
+ res_iri = URIRef(resource_card.class_iri)
81
+ problems = []
82
+ for one_card in resource_card.cards:
83
+ if one_card.propname not in successful_props:
84
+ logger.warning(f"CARDINALITY: Property '{one_card.propname}' not in successes, no cardinality added.")
85
+ continue
86
+ last_modification_date, problem = _add_one_cardinality(
87
+ one_card, res_iri, onto_iri, last_modification_date, onto_client
88
+ )
89
+ if problem:
90
+ problems.append(problem)
91
+ return last_modification_date, problems
92
+
93
+
94
+ def _add_one_cardinality(
95
+ card: ParsedPropertyCardinality,
96
+ res_iri: URIRef,
97
+ onto_iri: URIRef,
98
+ last_modification_date: Literal,
99
+ onto_client: OntologyClient,
100
+ ) -> tuple[Literal, UploadProblem | None]:
101
+ card_serialised = _serialise_card(card, res_iri, onto_iri, last_modification_date)
102
+ new_mod_date = onto_client.post_resource_cardinalities(card_serialised)
103
+ if not new_mod_date:
104
+ prefixed_cls = from_dsp_iri_to_prefixed_iri(str(res_iri))
105
+ prefixed_prop = from_dsp_iri_to_prefixed_iri(card.propname)
106
+ return last_modification_date, UploadProblem(
107
+ f"{prefixed_cls} / {prefixed_prop}",
108
+ ProblemType.CARDINALITY_COULD_NOT_BE_ADDED,
109
+ )
110
+ return new_mod_date, None
111
+
112
+
113
+ def _serialise_card(
114
+ card: ParsedPropertyCardinality, res_iri: URIRef, onto_iri: URIRef, last_modification_date: Literal
115
+ ) -> dict[str, Any]:
116
+ onto_g = make_ontology_base_graph(onto_iri, last_modification_date)
117
+ onto_serialised = next(iter(serialise_json(onto_g)))
118
+ card_g = _make_one_cardinality_graph(card, res_iri)
119
+ card_serialised = serialise_json(card_g)
120
+ onto_serialised["@graph"] = card_serialised
121
+ return onto_serialised
@@ -0,0 +1,12 @@
1
+ from rdflib import OWL
2
+ from rdflib import Literal
3
+
4
+ from dsp_tools.commands.create.models.parsed_ontology import Cardinality
5
+ from dsp_tools.commands.create.models.rdf_ontology import RdfCardinalityRestriction
6
+
7
+ PARSED_CARDINALITY_TO_RDF = {
8
+ Cardinality.C_1: RdfCardinalityRestriction(OWL.cardinality, Literal(1)),
9
+ Cardinality.C_0_1: RdfCardinalityRestriction(OWL.maxCardinality, Literal(1)),
10
+ Cardinality.C_1_N: RdfCardinalityRestriction(OWL.minCardinality, Literal(1)),
11
+ Cardinality.C_0_N: RdfCardinalityRestriction(OWL.minCardinality, Literal(0)),
12
+ }
@@ -7,17 +7,26 @@ from enum import StrEnum
7
7
  @dataclass
8
8
  class CollectedProblems:
9
9
  header: str
10
- problems: list[InputProblem]
10
+ problems: list[CreateProblem]
11
11
 
12
12
 
13
13
  @dataclass
14
- class InputProblem:
14
+ class CreateProblem:
15
15
  problematic_object: str
16
16
  problem: ProblemType
17
17
 
18
18
 
19
+ @dataclass
20
+ class InputProblem(CreateProblem): ...
21
+
22
+
23
+ @dataclass
24
+ class UploadProblem(CreateProblem): ...
25
+
26
+
19
27
  class ProblemType(StrEnum):
20
28
  PREFIX_COULD_NOT_BE_RESOLVED = (
21
29
  "The prefix used is not defined in the 'prefix' section of the file, "
22
30
  "nor does it belong to one of the project ontologies."
23
31
  )
32
+ CARDINALITY_COULD_NOT_BE_ADDED = "The cardinality could not be added."
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from rdflib import Literal
6
+ from rdflib import URIRef
7
+
8
+
9
+ @dataclass
10
+ class RdfResourceCardinality:
11
+ resource_iri: URIRef
12
+ on_property: URIRef
13
+ cardinality: RdfCardinalityRestriction
14
+
15
+
16
+ @dataclass
17
+ class RdfCardinalityRestriction:
18
+ owl_property: URIRef
19
+ cardinality_value: Literal
@@ -0,0 +1,25 @@
1
+ from dataclasses import dataclass
2
+ from dataclasses import field
3
+
4
+ from dsp_tools.commands.create.constants import KNORA_API_STR
5
+
6
+
7
+ @dataclass
8
+ class ProjectIriLookup:
9
+ project_iri: str
10
+ onto_iris: dict[str, str] = field(default_factory=dict)
11
+
12
+ def add_onto(self, name: str, iri: str) -> None:
13
+ self.onto_iris[name] = iri
14
+
15
+ def get_onto_iri(self, name: str) -> str | None:
16
+ return self.onto_iris.get(name)
17
+
18
+
19
+ @dataclass
20
+ class CreatedIriCollection:
21
+ classes: set[str] = field(default_factory=set)
22
+ properties: set[str] = field(default_factory=set)
23
+
24
+ def __post_init__(self) -> None:
25
+ self.properties.update({f"{KNORA_API_STR}seqnum", f"{KNORA_API_STR}isPartOf"})
@@ -2,7 +2,7 @@ from typing import Any
2
2
  from typing import cast
3
3
 
4
4
  from dsp_tools.commands.create.models.input_problems import CollectedProblems
5
- from dsp_tools.commands.create.models.input_problems import InputProblem
5
+ from dsp_tools.commands.create.models.input_problems import CreateProblem
6
6
  from dsp_tools.commands.create.models.input_problems import ProblemType
7
7
  from dsp_tools.commands.create.models.parsed_ontology import Cardinality
8
8
  from dsp_tools.commands.create.models.parsed_ontology import ParsedClass
@@ -46,7 +46,7 @@ def parse_ontology(ontology_json: dict[str, Any], prefixes: dict[str, str]) -> P
46
46
 
47
47
  def _parse_properties(
48
48
  properties_list: list[dict[str, Any]], current_onto_prefix: str
49
- ) -> tuple[list[ParsedProperty], list[InputProblem]]:
49
+ ) -> tuple[list[ParsedProperty], list[CreateProblem]]:
50
50
  parsed = []
51
51
  for prop in properties_list:
52
52
  parsed.append(ParsedProperty(f"{current_onto_prefix}{prop['name']}", prop))
@@ -55,7 +55,7 @@ def _parse_properties(
55
55
 
56
56
  def _parse_classes(
57
57
  classes_list: list[dict[str, Any]], current_onto_prefix: str
58
- ) -> tuple[list[ParsedClass], list[InputProblem]]:
58
+ ) -> tuple[list[ParsedClass], list[CreateProblem]]:
59
59
  parsed = []
60
60
  for cls in classes_list:
61
61
  parsed.append(ParsedClass(f"{current_onto_prefix}{cls['name']}", cls))
@@ -64,7 +64,7 @@ def _parse_classes(
64
64
 
65
65
  def _parse_cardinalities(
66
66
  classes_list: list[dict[str, Any]], current_onto_prefix: str, prefixes: dict[str, str]
67
- ) -> tuple[list[ParsedClassCardinalities], list[InputProblem]]:
67
+ ) -> tuple[list[ParsedClassCardinalities], list[CreateProblem]]:
68
68
  parsed = []
69
69
  failures = []
70
70
  for c in classes_list:
@@ -79,7 +79,7 @@ def _parse_cardinalities(
79
79
 
80
80
  def _parse_one_class_cardinality(
81
81
  cls_json: dict[str, Any], current_onto_prefix: str, prefixes: dict[str, str]
82
- ) -> ParsedClassCardinalities | list[InputProblem]:
82
+ ) -> ParsedClassCardinalities | list[CreateProblem]:
83
83
  failures = []
84
84
  parsed = []
85
85
  for c in cls_json["cardinalities"]:
@@ -96,10 +96,10 @@ def _parse_one_class_cardinality(
96
96
 
97
97
  def _parse_one_cardinality(
98
98
  card_json: dict[str, str | int], current_onto_prefix: str, prefixes: dict[str, str]
99
- ) -> ParsedPropertyCardinality | InputProblem:
99
+ ) -> ParsedPropertyCardinality | CreateProblem:
100
100
  prp_name = cast(str, card_json["propname"])
101
101
  if not (resolved := resolve_to_absolute_iri(prp_name, current_onto_prefix, prefixes)):
102
- return InputProblem(prp_name, ProblemType.PREFIX_COULD_NOT_BE_RESOLVED)
102
+ return CreateProblem(prp_name, ProblemType.PREFIX_COULD_NOT_BE_RESOLVED)
103
103
  gui = cast(int | None, card_json.get("gui_order"))
104
104
  return ParsedPropertyCardinality(
105
105
  propname=resolved,
@@ -56,7 +56,7 @@ def _parse_metadata(project_json: dict[str, Any]) -> ParsedProjectMetadata:
56
56
  longname=project_json["longname"],
57
57
  descriptions=project_json["descriptions"],
58
58
  keywords=project_json["keywords"],
59
- enabled_licenses=project_json["enabled_licenses"],
59
+ enabled_licenses=project_json.get("enabled_licenses", []),
60
60
  )
61
61
 
62
62
 
@@ -2,7 +2,7 @@ from typing import Any
2
2
 
3
3
  import regex
4
4
 
5
- from dsp_tools.commands.create.constants import KNORA_API
5
+ from dsp_tools.commands.create.constants import KNORA_API_STR
6
6
  from dsp_tools.commands.create.constants import UNIVERSAL_PREFIXES
7
7
  from dsp_tools.utils.data_formats.uri_util import is_uri
8
8
 
@@ -14,7 +14,7 @@ def resolve_to_absolute_iri(prefixed: str, current_onto: str, prefix_lookup: dic
14
14
  return f"{current_onto}{prefixed.lstrip(':')}"
15
15
  segments = prefixed.split(":", maxsplit=1)
16
16
  if len(segments) == 1:
17
- return f"{KNORA_API}{segments[0]}"
17
+ return f"{KNORA_API_STR}{segments[0]}"
18
18
  if not (found := prefix_lookup.get(segments[0])):
19
19
  return None
20
20
  return f"{found}{segments[1]}"
File without changes
@@ -0,0 +1,41 @@
1
+ from rdflib import OWL
2
+ from rdflib import RDF
3
+ from rdflib import RDFS
4
+ from rdflib import BNode
5
+ from rdflib import Graph
6
+ from rdflib import Literal
7
+ from rdflib import URIRef
8
+
9
+ from dsp_tools.commands.create.constants import SALSAH_GUI
10
+ from dsp_tools.commands.create.create_on_server.mappers import PARSED_CARDINALITY_TO_RDF
11
+ from dsp_tools.commands.create.models.parsed_ontology import ParsedPropertyCardinality
12
+ from dsp_tools.utils.rdflib_constants import KNORA_API
13
+
14
+
15
+ def make_ontology_base_graph(onto_iri: URIRef, last_modification_date: Literal) -> Graph:
16
+ g = Graph()
17
+ g.add((onto_iri, RDF.type, OWL.Ontology))
18
+ g.add((onto_iri, KNORA_API.lastModificationDate, last_modification_date))
19
+ return g
20
+
21
+
22
+ def make_cardinality_graph_for_request(
23
+ card: ParsedPropertyCardinality, res_iri: URIRef, onto_iri: URIRef, last_modification_date: Literal
24
+ ) -> Graph:
25
+ g = make_ontology_base_graph(onto_iri, last_modification_date)
26
+ g += _make_one_cardinality_graph(card, res_iri)
27
+ return g
28
+
29
+
30
+ def _make_one_cardinality_graph(card: ParsedPropertyCardinality, res_iri: URIRef) -> Graph:
31
+ card_info = PARSED_CARDINALITY_TO_RDF[card.cardinality]
32
+ g = Graph()
33
+ bn_card = BNode()
34
+ g.add((res_iri, RDF.type, OWL.Class))
35
+ g.add((res_iri, RDFS.subClassOf, bn_card))
36
+ g.add((bn_card, RDF.type, OWL.Restriction))
37
+ g.add((bn_card, card_info.owl_property, card_info.cardinality_value))
38
+ g.add((bn_card, OWL.onProperty, URIRef(card.propname)))
39
+ if card.gui_order is not None:
40
+ g.add((bn_card, SALSAH_GUI.guiOrder, Literal(card.gui_order)))
41
+ return g
@@ -4,6 +4,7 @@ of the project, the creation of groups, users, lists, resource classes, properti
4
4
  import os
5
5
  from pathlib import Path
6
6
  from typing import Any
7
+ from typing import cast
7
8
  from urllib.parse import quote_plus
8
9
 
9
10
  from dotenv import load_dotenv
@@ -13,11 +14,14 @@ from dsp_tools.cli.args import ServerCredentials
13
14
  from dsp_tools.clients.authentication_client_live import AuthenticationClientLive
14
15
  from dsp_tools.clients.connection import Connection
15
16
  from dsp_tools.clients.connection_live import ConnectionLive
17
+ from dsp_tools.commands.create.communicate_problems import print_problem_collection
18
+ from dsp_tools.commands.create.models.parsed_project import ParsedProject
19
+ from dsp_tools.commands.create.models.server_project_info import ProjectIriLookup
20
+ from dsp_tools.commands.create.parsing.parse_project import parse_project
16
21
  from dsp_tools.commands.project.create.parse_project import parse_project_json
17
22
  from dsp_tools.commands.project.create.project_create_default_permissions import create_default_permissions
18
23
  from dsp_tools.commands.project.create.project_create_lists import create_lists_on_server
19
24
  from dsp_tools.commands.project.create.project_create_ontologies import create_ontologies
20
- from dsp_tools.commands.project.create.project_validate import validate_project
21
25
  from dsp_tools.commands.project.legacy_models.context import Context
22
26
  from dsp_tools.commands.project.legacy_models.group import Group
23
27
  from dsp_tools.commands.project.legacy_models.project import Project
@@ -34,7 +38,7 @@ from dsp_tools.utils.json_parsing import parse_json_input
34
38
  load_dotenv()
35
39
 
36
40
 
37
- def create_project( # noqa: PLR0915 (too many statements)
41
+ def create_project( # noqa: PLR0915,PLR0912 (too many statements & branches)
38
42
  project_file_as_path_or_parsed: str | Path | dict[str, Any],
39
43
  creds: ServerCredentials,
40
44
  verbose: bool = False,
@@ -69,38 +73,41 @@ def create_project( # noqa: PLR0915 (too many statements)
69
73
  knora_api_prefix = "knora-api:"
70
74
  overall_success = True
71
75
 
72
- project_json = parse_json_input(project_file_as_path_or_parsed=project_file_as_path_or_parsed)
76
+ # includes validation
77
+ parsed_project = parse_project(project_file_as_path_or_parsed, creds.server)
78
+ if not isinstance(parsed_project, ParsedProject):
79
+ for problem in parsed_project:
80
+ print_problem_collection(problem)
81
+ return False
73
82
 
83
+ # required for the legacy code
84
+ project_json = parse_json_input(project_file_as_path_or_parsed=project_file_as_path_or_parsed)
74
85
  context = Context(project_json.get("prefixes", {}))
75
-
76
- # validate against JSON schema
77
- validate_project(project_json)
78
- print(" JSON project file is syntactically correct and passed validation.")
79
- logger.info("JSON project file is syntactically correct and passed validation.")
80
-
81
- project = parse_project_json(project_json)
86
+ legacy_project = parse_project_json(project_json)
82
87
 
83
88
  auth = AuthenticationClientLive(creds.server, creds.user, creds.password)
84
89
  con = ConnectionLive(creds.server, auth)
85
90
 
86
91
  # create project on DSP server
87
- info_str = f"Create project '{project.metadata.shortname}' ({project.metadata.shortcode})..."
92
+ info_str = f"Create project '{legacy_project.metadata.shortname}' ({legacy_project.metadata.shortcode})..."
88
93
  print(info_str)
89
94
  logger.info(info_str)
90
95
  project_remote, success = _create_project_on_server(
91
- project_definition=project.metadata,
96
+ project_definition=legacy_project.metadata,
92
97
  con=con,
93
98
  )
99
+ project_iri = cast(str, project_remote.iri)
100
+ project_iri_lookup = ProjectIriLookup(project_iri)
94
101
  if not success:
95
102
  overall_success = False
96
103
 
97
104
  # create the lists
98
105
  names_and_iris_of_list_nodes: dict[str, Any] = {}
99
- if project.lists:
106
+ if legacy_project.lists:
100
107
  print("Create lists...")
101
108
  logger.info("Create lists...")
102
109
  names_and_iris_of_list_nodes, success = create_lists_on_server(
103
- lists_to_create=project.lists,
110
+ lists_to_create=legacy_project.lists,
104
111
  con=con,
105
112
  project_remote=project_remote,
106
113
  )
@@ -109,24 +116,24 @@ def create_project( # noqa: PLR0915 (too many statements)
109
116
 
110
117
  # create the groups
111
118
  current_project_groups: dict[str, Group] = {}
112
- if project.groups:
119
+ if legacy_project.groups:
113
120
  print("Create groups...")
114
121
  logger.info("Create groups...")
115
122
  current_project_groups, success = _create_groups(
116
123
  con=con,
117
- groups=project.groups,
124
+ groups=legacy_project.groups,
118
125
  project=project_remote,
119
126
  )
120
127
  if not success:
121
128
  overall_success = False
122
129
 
123
130
  # create or update the users
124
- if project.users:
131
+ if legacy_project.users:
125
132
  print("Create users...")
126
133
  logger.info("Create users...")
127
134
  success = _create_users(
128
135
  con=con,
129
- users_section=project.users,
136
+ users_section=legacy_project.users,
130
137
  current_project_groups=current_project_groups,
131
138
  current_project=project_remote,
132
139
  verbose=verbose,
@@ -140,9 +147,12 @@ def create_project( # noqa: PLR0915 (too many statements)
140
147
  context=context,
141
148
  knora_api_prefix=knora_api_prefix,
142
149
  names_and_iris_of_list_nodes=names_and_iris_of_list_nodes,
143
- ontology_definitions=project.ontologies,
150
+ ontology_definitions=legacy_project.ontologies,
144
151
  project_remote=project_remote,
145
152
  verbose=verbose,
153
+ parsed_ontologies=parsed_project.ontologies,
154
+ project_iri_lookup=project_iri_lookup,
155
+ auth=auth,
146
156
  )
147
157
  if not success:
148
158
  overall_success = False
@@ -151,9 +161,9 @@ def create_project( # noqa: PLR0915 (too many statements)
151
161
  perm_client = PermissionsClient(auth, str(project_remote.iri))
152
162
  success = create_default_permissions(
153
163
  perm_client,
154
- project.metadata.default_permissions,
155
- project.metadata.default_permissions_overrule,
156
- project.metadata.shortcode,
164
+ legacy_project.metadata.default_permissions,
165
+ legacy_project.metadata.default_permissions_overrule,
166
+ legacy_project.metadata.shortcode,
157
167
  )
158
168
  if not success:
159
169
  overall_success = False
@@ -161,15 +171,15 @@ def create_project( # noqa: PLR0915 (too many statements)
161
171
  # final steps
162
172
  if overall_success:
163
173
  msg = (
164
- f"Successfully created project '{project.metadata.shortname}' "
165
- f"({project.metadata.shortcode}) with all its ontologies. "
174
+ f"Successfully created project '{legacy_project.metadata.shortname}' "
175
+ f"({legacy_project.metadata.shortcode}) with all its ontologies. "
166
176
  f"There were no problems during the creation process."
167
177
  )
168
178
  print(f"========================================================\n{msg}")
169
179
  logger.info(msg)
170
180
  else:
171
181
  msg = (
172
- f"The project '{project.metadata.shortname}' ({project.metadata.shortcode}) "
182
+ f"The project '{legacy_project.metadata.shortname}' ({legacy_project.metadata.shortcode}) "
173
183
  f"with its ontologies could be created, "
174
184
  f"but during the creation process, some problems occurred. Please carefully check the console output."
175
185
  )
@@ -1,12 +1,19 @@
1
1
  from typing import Any
2
2
  from typing import Optional
3
+ from typing import cast
3
4
 
4
5
  import regex
5
6
  from loguru import logger
6
7
 
8
+ from dsp_tools.clients.authentication_client import AuthenticationClient
7
9
  from dsp_tools.clients.connection import Connection
10
+ from dsp_tools.clients.ontology_client_live import OntologyClientLive
11
+ from dsp_tools.commands.create.communicate_problems import print_problem_collection
12
+ from dsp_tools.commands.create.create_on_server.cardinalities import add_all_cardinalities
13
+ from dsp_tools.commands.create.models.parsed_ontology import ParsedOntology
14
+ from dsp_tools.commands.create.models.server_project_info import CreatedIriCollection
15
+ from dsp_tools.commands.create.models.server_project_info import ProjectIriLookup
8
16
  from dsp_tools.commands.project.legacy_models.context import Context
9
- from dsp_tools.commands.project.legacy_models.helpers import Cardinality
10
17
  from dsp_tools.commands.project.legacy_models.ontology import Ontology
11
18
  from dsp_tools.commands.project.legacy_models.project import Project
12
19
  from dsp_tools.commands.project.legacy_models.propertyclass import PropertyClass
@@ -25,6 +32,9 @@ def create_ontologies(
25
32
  ontology_definitions: list[dict[str, Any]],
26
33
  project_remote: Project,
27
34
  verbose: bool,
35
+ parsed_ontologies: list[ParsedOntology],
36
+ project_iri_lookup: ProjectIriLookup,
37
+ auth: AuthenticationClient,
28
38
  ) -> bool:
29
39
  """
30
40
  Iterates over the ontologies in a JSON project file and creates the ontologies that don't exist on the DSP server
@@ -39,6 +49,9 @@ def create_ontologies(
39
49
  ontology_definitions: the "ontologies" section of the parsed JSON project file
40
50
  project_remote: representation of the project on the DSP server
41
51
  verbose: verbose switch
52
+ parsed_ontologies: parsed ontologies
53
+ project_iri_lookup: lookup for IRIs
54
+ auth: Authentication Client
42
55
 
43
56
  Raises:
44
57
  InputError: if an error occurs during the creation of an ontology.
@@ -47,6 +60,8 @@ def create_ontologies(
47
60
  Returns:
48
61
  True if everything went smoothly, False otherwise
49
62
  """
63
+ success_collection = CreatedIriCollection()
64
+ onto_client = OntologyClientLive(auth.server, auth)
50
65
 
51
66
  overall_success = True
52
67
 
@@ -60,6 +75,9 @@ def create_ontologies(
60
75
  logger.exception(err_msg)
61
76
  project_ontologies = []
62
77
 
78
+ for existing_onto in project_ontologies:
79
+ project_iri_lookup.add_onto(existing_onto.name, existing_onto.iri)
80
+
63
81
  created_ontos: list[tuple[dict[str, Any], Ontology, dict[str, ResourceClass]]] = []
64
82
  for ontology_definition in ontology_definitions:
65
83
  ontology_remote = _create_ontology(
@@ -75,6 +93,8 @@ def create_ontologies(
75
93
  if not ontology_remote:
76
94
  overall_success = False
77
95
  continue
96
+ else:
97
+ project_iri_lookup.add_onto(ontology_remote.name, ontology_remote.iri)
78
98
 
79
99
  # add the empty resource classes to the remote ontology
80
100
  last_modification_date, remote_res_classes, success = _add_resource_classes_to_remote_ontology(
@@ -85,11 +105,12 @@ def create_ontologies(
85
105
  last_modification_date=ontology_remote.lastModificationDate,
86
106
  verbose=verbose,
87
107
  )
108
+ success_collection.classes.update(set(remote_res_classes.keys()))
88
109
  if not success:
89
110
  overall_success = False
90
111
 
91
112
  # add the property classes to the remote ontology
92
- last_modification_date, success = _add_property_classes_to_remote_ontology(
113
+ last_modification_date, success, property_successes = _add_property_classes_to_remote_ontology(
93
114
  onto_name=ontology_definition["name"],
94
115
  property_definitions=ontology_definition.get("properties", []),
95
116
  ontology_remote=ontology_remote,
@@ -99,22 +120,21 @@ def create_ontologies(
99
120
  knora_api_prefix=knora_api_prefix,
100
121
  verbose=verbose,
101
122
  )
123
+ success_collection.properties.update(property_successes)
102
124
  created_ontos.append((ontology_definition, ontology_remote, remote_res_classes))
103
125
  if not success:
104
126
  overall_success = False
105
127
 
106
128
  print("Add cardinalities to resource classes...")
107
- for ontology_definition, ontology_remote, remote_res_classes in created_ontos:
108
- success = _add_cardinalities_to_resource_classes(
109
- resclass_definitions=ontology_definition.get("resources", []),
110
- ontology_remote=ontology_remote,
111
- remote_res_classes=remote_res_classes,
112
- knora_api_prefix=knora_api_prefix,
113
- context=context,
114
- verbose=verbose,
115
- )
116
- if not success:
117
- overall_success = False
129
+ problems = add_all_cardinalities(
130
+ ontologies=parsed_ontologies,
131
+ project_iri_lookup=project_iri_lookup,
132
+ created_iris=success_collection,
133
+ onto_client=onto_client,
134
+ )
135
+ if problems:
136
+ overall_success = False
137
+ print_problem_collection(problems)
118
138
 
119
139
  return overall_success
120
140
 
@@ -297,7 +317,7 @@ def _add_property_classes_to_remote_ontology(
297
317
  last_modification_date: DateTimeStamp,
298
318
  knora_api_prefix: str,
299
319
  verbose: bool,
300
- ) -> tuple[DateTimeStamp, bool]:
320
+ ) -> tuple[DateTimeStamp, bool, set[str]]:
301
321
  """
302
322
  Creates the property classes defined in the "properties" section of an ontology. The
303
323
  containing project and the containing ontology must already be existing on the DSP server.
@@ -317,6 +337,7 @@ def _add_property_classes_to_remote_ontology(
317
337
  Returns:
318
338
  a tuple consisting of the last modification date of the ontology, and the success status
319
339
  """
340
+ property_successes = set()
320
341
  overall_success = True
321
342
  print(" Create property classes...")
322
343
  logger.info("Create property classes...")
@@ -370,11 +391,13 @@ def _add_property_classes_to_remote_ontology(
370
391
  comment=LangString(prop_class["comments"]) if prop_class.get("comments") else None,
371
392
  )
372
393
  try:
373
- last_modification_date, _ = prop_class_local.create(last_modification_date)
394
+ last_modification_date, prop_class_created = prop_class_local.create(last_modification_date)
374
395
  ontology_remote.lastModificationDate = last_modification_date
375
396
  if verbose:
376
397
  print(f" Created property class '{prop_class['name']}'")
377
398
  logger.info(f"Created property class '{prop_class['name']}'")
399
+ prop_iri = cast(str, prop_class_created.iri)
400
+ property_successes.add(prop_iri)
378
401
  except BaseError as err:
379
402
  err_msg = f"Unable to create property class '{prop_class['name']}'"
380
403
  if found := regex.search(
@@ -386,7 +409,7 @@ def _add_property_classes_to_remote_ontology(
386
409
  logger.exception(err_msg)
387
410
  overall_success = False
388
411
 
389
- return last_modification_date, overall_success
412
+ return last_modification_date, overall_success, property_successes
390
413
 
391
414
 
392
415
  def _sort_prop_classes(
@@ -423,76 +446,3 @@ def _sort_prop_classes(
423
446
  ok_propclass_names.append(prop_name)
424
447
  prop_classes_to_sort.remove(prop)
425
448
  return sorted_prop_classes
426
-
427
-
428
- def _add_cardinalities_to_resource_classes(
429
- resclass_definitions: list[dict[str, Any]],
430
- ontology_remote: Ontology,
431
- remote_res_classes: dict[str, ResourceClass],
432
- knora_api_prefix: str,
433
- context: Context,
434
- verbose: bool,
435
- ) -> bool:
436
- """
437
- Iterates over the resource classes of an ontology of a JSON project definition, and adds the cardinalities to each
438
- resource class. The resource classes and the properties must already be existing on the DSP server.
439
- If an error occurs during creation of a cardinality, it is printed out, the process continues, but the success
440
- status will be false.
441
-
442
- Args:
443
- resclass_definitions: the part of the parsed JSON project file that contains the resources of the current onto
444
- ontology_remote: representation of the current ontology on the DSP server
445
- remote_res_classes: representations of the resource classes on the DSP server
446
- knora_api_prefix: the prefix that stands for the knora-api ontology
447
- context: the context of the current project
448
- verbose: verbose switch
449
-
450
- Returns:
451
- success status
452
- """
453
- overall_success = True
454
- print(f" Add cardinalities to resource classes of ontology '{ontology_remote.iri}'...")
455
- logger.info(f"Add cardinalities to resource classes of ontology '{ontology_remote.iri}'...")
456
- switcher = {
457
- "1": Cardinality.C_1,
458
- "0-1": Cardinality.C_0_1,
459
- "0-n": Cardinality.C_0_n,
460
- "1-n": Cardinality.C_1_n,
461
- }
462
- for res_class in resclass_definitions:
463
- res_class_remote = remote_res_classes.get(f"{ontology_remote.iri}#{res_class['name']}")
464
- if not res_class_remote:
465
- msg = (
466
- f"Unable to add cardinalities to resource class '{res_class['name']}': "
467
- f"This class doesn't exist on the DSP server."
468
- )
469
- print(f"WARNINIG: {msg}")
470
- logger.exception(msg)
471
- overall_success = False
472
- continue
473
- for card_info in res_class.get("cardinalities", []):
474
- if ":" in card_info["propname"]:
475
- prefix, prop = card_info["propname"].split(":")
476
- qualified_propname = card_info["propname"] if prefix else f"{ontology_remote.name}:{prop}"
477
- else:
478
- qualified_propname = knora_api_prefix + card_info["propname"]
479
-
480
- try:
481
- last_modification_date = res_class_remote.addProperty(
482
- property_id=qualified_propname,
483
- cardinality=switcher[card_info["cardinality"]],
484
- gui_order=card_info.get("gui_order"),
485
- last_modification_date=ontology_remote.lastModificationDate,
486
- context=context,
487
- )
488
- ontology_remote.lastModificationDate = last_modification_date
489
- if verbose:
490
- print(f" Added cardinality '{card_info['propname']}' to resource class '{res_class['name']}'")
491
- logger.info(f"Added cardinality '{card_info['propname']}' to resource class '{res_class['name']}'")
492
- except BaseError:
493
- err_msg = f"Unable to add cardinality '{qualified_propname}' to resource class {res_class['name']}."
494
- print(f"WARNING: {err_msg}")
495
- logger.exception(err_msg)
496
- overall_success = False
497
-
498
- return overall_success
@@ -477,39 +477,6 @@ class ResourceClass(Model):
477
477
  def has_properties(self) -> dict[str, HasProperty]:
478
478
  return self._has_properties
479
479
 
480
- def getProperty(self, property_id: str) -> Optional[HasProperty]:
481
- if self._has_properties is None:
482
- return None
483
- else:
484
- return self._has_properties.get(self._context.get_prefixed_iri(property_id))
485
-
486
- def addProperty(
487
- self,
488
- last_modification_date: DateTimeStamp,
489
- property_id: str,
490
- cardinality: Cardinality,
491
- context: Context,
492
- gui_order: Optional[int] = None,
493
- ) -> DateTimeStamp:
494
- self._context.context.update(context.context)
495
- if self._has_properties.get(property_id) is None:
496
- latest_modification_date, resclass = HasProperty(
497
- con=self._con,
498
- context=self._context,
499
- ontology_id=self._ontology_id,
500
- property_id=property_id,
501
- resclass_id=self.iri,
502
- cardinality=cardinality,
503
- gui_order=gui_order,
504
- ).create(last_modification_date)
505
- hp = resclass.getProperty(property_id)
506
- hp.ontology_id = self._context.iri_from_prefix(self._ontology_id)
507
- hp.resclass_id = self._iri
508
- self._has_properties[hp.property_id] = hp
509
- return latest_modification_date
510
- else:
511
- raise BaseError("Property already has cardinality in this class! " + property_id)
512
-
513
480
  @classmethod
514
481
  def fromJsonObj(cls, con: Connection, context: Context, json_obj: Any) -> ResourceClass:
515
482
  if isinstance(json_obj, list):
@@ -65,6 +65,10 @@ class InvalidGuiAttributeError(BaseError):
65
65
  """This error is raised when a invalid gui-attribute is used."""
66
66
 
67
67
 
68
+ class UnexpectedApiResponseError(BaseError):
69
+ """This error is raised when the API gives an unexpected response, that we cannot anticipate and handle cleanly."""
70
+
71
+
68
72
  class UserFilepathNotFoundError(InputError):
69
73
  """This error is raised if a filepath from the user does not exist."""
70
74
 
@@ -12,3 +12,10 @@ def is_iri(s: str) -> bool:
12
12
  def is_resource_iri(s: str) -> bool:
13
13
  """Checks whether a string is a valid resource IRI."""
14
14
  return regex.fullmatch(_resource_iri_pattern, s) is not None
15
+
16
+
17
+ def from_dsp_iri_to_prefixed_iri(iri: str) -> str:
18
+ dsp_iri_re = r".+\/(.+?)\/v2#(.+)$"
19
+ if not (found := regex.search(dsp_iri_re, iri)):
20
+ return iri
21
+ return f"{found.group(1)}:{found.group(2)}"
@@ -0,0 +1,10 @@
1
+ import json
2
+ from typing import Any
3
+
4
+ from rdflib import Graph
5
+
6
+
7
+ def serialise_json(rdf_graph: Graph) -> list[dict[str, Any]]:
8
+ graph_bytes = rdf_graph.serialize(format="json-ld", encoding="utf-8")
9
+ json_graph: list[dict[str, Any]] = json.loads(graph_bytes)
10
+ return json_graph
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: dsp-tools
3
- Version: 17.0.0.post14
3
+ Version: 17.0.0.post21
4
4
  Summary: DSP-TOOLS is a Python package with a command line interface that helps you interact with a DaSCH service platform (DSP) server.
5
5
  Author: DaSCH - Swiss National Data and Service Center for the Humanities
6
6
  Author-email: DaSCH - Swiss National Data and Service Center for the Humanities <info@dasch.swiss>
@@ -12,18 +12,29 @@ dsp_tools/clients/connection_live.py,sha256=Y0T-F93FFGnY2Z7qHhG56v3Ajg7U7ATq4QHI
12
12
  dsp_tools/clients/fuseki_metrics.py,sha256=Vy_aWOusnzlD0EnbyHZcTtPIpMEfRA_ihtYbysTq7sQ,2059
13
13
  dsp_tools/clients/legal_info_client.py,sha256=itDvGQf1VV1WiH2oHVcH1epSUSdJPRDwdRUvmCw8P4s,742
14
14
  dsp_tools/clients/legal_info_client_live.py,sha256=9nOe8Y-oQRHh4TkD2-tjdxLNNTonQ0i-P6UjCGgIBGA,5987
15
+ dsp_tools/clients/metadata_client.py,sha256=Ozlnz8-_lgPHHKPXsRyLDx1ccvBPoIYLbAJcfYnKvXY,610
16
+ dsp_tools/clients/metadata_client_live.py,sha256=LKaGCBZTAyJg0JHxrDOkcrb_Z3csNKXHEhKiWlVylis,1739
17
+ dsp_tools/clients/ontology_client.py,sha256=bATGyI8YFFCl0yis9beJjGB1ZlJuO_GqYnlB-2ulv94,638
18
+ dsp_tools/clients/ontology_client_live.py,sha256=8uWoRxwlCvUHQZYQsQ5XojAlpEAqzIhFNYROQfsNWf4,5367
15
19
  dsp_tools/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
20
  dsp_tools/commands/create/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
- dsp_tools/commands/create/constants.py,sha256=UHbYEERF8Rss7JINKBaR6z85LpwaH8w892airFKYHcw,191
21
+ dsp_tools/commands/create/communicate_problems.py,sha256=GdAOiUZevrYFhRINyDMhcbXdUqtGDKb2nEelhdfSFR0,885
22
+ dsp_tools/commands/create/constants.py,sha256=ZzWNn_zh-0Oy0SJmGivQbcAnQTmwT0akNchZUppXgh0,276
23
+ dsp_tools/commands/create/create_on_server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
+ dsp_tools/commands/create/create_on_server/cardinalities.py,sha256=QW0i6TSNmP-dGS2WSad1np9QHz41-A51ArspuAQZGJg,5260
25
+ dsp_tools/commands/create/create_on_server/mappers.py,sha256=Lu2DJAICOZu7WP_1UDvyvRAi0T9ZGO88kvx0UfI6v5I,564
18
26
  dsp_tools/commands/create/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
- dsp_tools/commands/create/models/input_problems.py,sha256=xU4nzl-DEghXWg7eT5V6Z3V_srzUlM-WmzeitDY3fBE,483
27
+ dsp_tools/commands/create/models/input_problems.py,sha256=Oa8-fQy2FTtmfXzMVdhBE9Cs3PlOgTWga4XmK5QkM7s,665
20
28
  dsp_tools/commands/create/models/parsed_ontology.py,sha256=EUuD47SmWXyB74vGdxGwuvRzq1f4_YdciwYmqcEwqWc,815
21
29
  dsp_tools/commands/create/models/parsed_project.py,sha256=Gnl9gJEm5sNbccYG24cwT4doS-oqh_cywjbqk1V4wZQ,928
30
+ dsp_tools/commands/create/models/rdf_ontology.py,sha256=TyXHmblr9j5XZFVqjLT516MrVYPgzjXWvTX4TckEPPk,361
31
+ dsp_tools/commands/create/models/server_project_info.py,sha256=k5EVYo8ZJhMMB5XKja9Xl6CFQ_oLRFdRZPjMyj4XmO4,699
22
32
  dsp_tools/commands/create/parsing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
- dsp_tools/commands/create/parsing/parse_lists.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
- dsp_tools/commands/create/parsing/parse_ontology.py,sha256=4G_IN7lUdfTmZRTpcsE644MjP9HXe3NQkQ8c48lCiGQ,4261
25
- dsp_tools/commands/create/parsing/parse_project.py,sha256=yWcBSAmlcML5GsSTFVL69XFuzqyntrANq6XO9Nfh3n8,3997
26
- dsp_tools/commands/create/parsing/parsing_utils.py,sha256=u_PG8EWnhT1aV5GQSb5eCUbGY0Bd7mo4W49ciIhFzVo,1628
33
+ dsp_tools/commands/create/parsing/parse_ontology.py,sha256=4cqo2wa9wcLBszWXgPrixQgaHYPHfENQbF6dkeBVNiU,4268
34
+ dsp_tools/commands/create/parsing/parse_project.py,sha256=CghB-pJ6RcllOP0jn8Hs50kRcxS2Q0iS8A_QNGbjzZU,4005
35
+ dsp_tools/commands/create/parsing/parsing_utils.py,sha256=-kr9ulha4T6reaQsQ2lImuqKEp2w4tQi66tjm2lDa9w,1636
36
+ dsp_tools/commands/create/serialisation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
+ dsp_tools/commands/create/serialisation/ontology.py,sha256=NK4kjriHs7dt5fXNiIBc1z_mE9szI4YemJ--hkYYeCk,1581
27
38
  dsp_tools/commands/excel2json/CLAUDE.md,sha256=y7ZcmrHbewN48sV0wyZhNfXDXdERG4PRvdz02Mqh0gg,3399
28
39
  dsp_tools/commands/excel2json/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
40
  dsp_tools/commands/excel2json/json_header.py,sha256=3PkhYHloGhuK9_zl7XrqboRksquWzGsdNNNo7-LC2c8,13543
@@ -66,10 +77,10 @@ dsp_tools/commands/ingest_xmlupload/upload_files/upload_files.py,sha256=QXj58UiT
66
77
  dsp_tools/commands/project/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
67
78
  dsp_tools/commands/project/create/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
68
79
  dsp_tools/commands/project/create/parse_project.py,sha256=xYcFaUtKLZigxDZpD7O2JNueHmy_-Ar1iE2LJe97gos,4081
69
- dsp_tools/commands/project/create/project_create_all.py,sha256=aOPk_Bwg6-0VXTDaG3VnhbBRtqZtzcGS87uGcEdqjMg,24201
80
+ dsp_tools/commands/project/create/project_create_all.py,sha256=GM1XjpRvGoT79wGO9VF27c8CBdV42dIOhcT3AdKSwzg,24888
70
81
  dsp_tools/commands/project/create/project_create_default_permissions.py,sha256=CygzQ4rFF6SYUCkSvrO6gkjpINWZh4wjq_2V0PExwso,5990
71
82
  dsp_tools/commands/project/create/project_create_lists.py,sha256=A9kKe4gVP1Ug869102bRXK5RraZUtGzv5cWyFfK_DEA,7963
72
- dsp_tools/commands/project/create/project_create_ontologies.py,sha256=Iz38-3ubInEPpvCHpQDiJ9qR7bF4ZFwXJ121E5xEdC8,21647
83
+ dsp_tools/commands/project/create/project_create_ontologies.py,sha256=Ie5WHTKg5S1bRCg3SmmbB1tjem3NwRrX3N32TeAWzyI,19465
73
84
  dsp_tools/commands/project/create/project_validate.py,sha256=H5TVvqO5V63roGEde1J0n21twkajy4AkVSDGmZb4Jp8,27443
74
85
  dsp_tools/commands/project/get/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
75
86
  dsp_tools/commands/project/get/get.py,sha256=_5LxbSP5Fi0PyXODjvND3ZC9E_x91yk6Xb6tcldG5j0,6447
@@ -84,7 +95,7 @@ dsp_tools/commands/project/legacy_models/model.py,sha256=AzxebBc5Y19kPFvsr4cPLWr
84
95
  dsp_tools/commands/project/legacy_models/ontology.py,sha256=uXX0U4FVzx53GIjTyZhCN96oxdSc1Jaq-O818XrsSEc,12557
85
96
  dsp_tools/commands/project/legacy_models/project.py,sha256=TlC-DPvaIxCdhnO82s_92lcC8--JzO840cLzrmrG4Fo,12012
86
97
  dsp_tools/commands/project/legacy_models/propertyclass.py,sha256=tDr23zwcZptZzBIAQSW27aRtRuSbM6PI_hk2ZXnuLm8,16954
87
- dsp_tools/commands/project/legacy_models/resourceclass.py,sha256=zE02MvJ6kGI5GA5n71oTFnKJaWHtX94yrsBsTQlS71s,27939
98
+ dsp_tools/commands/project/legacy_models/resourceclass.py,sha256=eMorLi4x3zaeq-zBgf3lniKRpcJnQNpGLkJUPPTPsYY,26599
88
99
  dsp_tools/commands/project/legacy_models/user.py,sha256=qJ1vxK_yQbLb7z1i7KmJA3j5wYZKAUHPBGIhIXHhfq4,16057
89
100
  dsp_tools/commands/project/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
90
101
  dsp_tools/commands/project/models/permissions_client.py,sha256=v_Y02kTWFi7l9JE18lUxkyjc-vyt8KK7h8MN8MtsgXw,2494
@@ -175,7 +186,7 @@ dsp_tools/config/logger_config.py,sha256=Bw2Gu5F2d8un_KNk0hvNtV7fvN2TlThqo6gSwqe
175
186
  dsp_tools/config/warnings_config.py,sha256=15_Lt227HLqhdn6v-zJbi1KG9Fo6Zi1_4fp_a-iY72w,1142
176
187
  dsp_tools/error/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
177
188
  dsp_tools/error/custom_warnings.py,sha256=7C2DscIz9k7IfM8uebIsKWPcWcSjwpDqbIRDaPw7bI8,1053
178
- dsp_tools/error/exceptions.py,sha256=cBhH1bpFnOba6rJAEKvgVRwBg6Ni2gN1MPQLDdElPaQ,4686
189
+ dsp_tools/error/exceptions.py,sha256=giLM6Hx17lMdCjNdlwMr4zqdGcqUHypAxESSAtkvvnU,4853
179
190
  dsp_tools/error/problems.py,sha256=DotzVg3MYvMJmernd9tTBmDHoT1MOkHdiWVv8iMoFSk,265
180
191
  dsp_tools/error/xmllib_errors.py,sha256=DpYCsBIx_GmsBAUlfk2VMqtzD5IGMRbd2yXTcrJFHR4,549
181
192
  dsp_tools/error/xmllib_warnings.py,sha256=sS9jJXGJtQqCiJ9P2zCM5gpIhTpChbujQz_fPvxLm8g,1557
@@ -204,12 +215,13 @@ dsp_tools/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
204
215
  dsp_tools/utils/ansi_colors.py,sha256=p2vq-wzfmE-wdDddvC26UcOA6Z1qhFTdzP0dPoW4sy0,1691
205
216
  dsp_tools/utils/data_formats/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
206
217
  dsp_tools/utils/data_formats/date_util.py,sha256=VLNQnSsUX6NB1IY3Ry5KCxCLgHHYN0TSSBRn8hyXN-4,4714
207
- dsp_tools/utils/data_formats/iri_util.py,sha256=_PXOdfwIDDe2fT1f8k1FBoKdaw1RNnL2Sw8UJrfO-wE,457
218
+ dsp_tools/utils/data_formats/iri_util.py,sha256=O_TQb104ukjVsaqgkstz54QvjgqFPLSxVvqH6HVNjQc,670
208
219
  dsp_tools/utils/data_formats/shared.py,sha256=9AybXCAboojvRULZPud5e6B7UkjQOuN6f5fiVxwuZe8,2618
209
220
  dsp_tools/utils/data_formats/uri_util.py,sha256=9UGrbtxHVI0Ka0ttxd39KDhGeeP0xOdVLjt6HyV-Ic8,3257
210
221
  dsp_tools/utils/fuseki_bloating.py,sha256=yUCSijVf_Ne9iWUK_IzbPAkOMkN8Xt9ttPcWWRSIgMI,2381
211
222
  dsp_tools/utils/json_parsing.py,sha256=KlNvwnZoq-gJqnOVH7tiZMzgdld_3IunuOVvlEVXGXc,1684
212
223
  dsp_tools/utils/rdflib_constants.py,sha256=D5HO7oILBRiQI-bJj14pbCx6zhKY4RGqjIjq6S_IawU,652
224
+ dsp_tools/utils/rdflib_utils.py,sha256=M9UCXMe8W3SDQ0DYh4i6lRwYkyWozrWLl19NP5A-RlY,284
213
225
  dsp_tools/utils/replace_id_with_iri.py,sha256=5M5vXWJIQ0oO4ELwIt_5QeYBvyGESBc2smuNThHiQUY,2928
214
226
  dsp_tools/utils/request_utils.py,sha256=KxiFpQ_IH1ZCIF-SknPjRMP5-ZtLP3wWzwSrEZDB2fk,6902
215
227
  dsp_tools/utils/xml_parsing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -248,7 +260,7 @@ dsp_tools/xmllib/models/res.py,sha256=c3edvilYZVDmv2O6Z36sSkHXcuKPAJLfWVpStDTMuJ
248
260
  dsp_tools/xmllib/models/root.py,sha256=x8_vrDSJ1pZUJUL8LR460dZe4Cg57G_Hy-Zfr2S29dw,13562
249
261
  dsp_tools/xmllib/value_checkers.py,sha256=Yx3r6_WoZ5Lev8Orp8yDzd03JvP2GBmFNSFT2dzrycM,10712
250
262
  dsp_tools/xmllib/value_converters.py,sha256=WMYS5hd1VlrLLBXnf6pv9yYoPBsv_2MxOO6xv-QsRW4,29218
251
- dsp_tools-17.0.0.post14.dist-info/WHEEL,sha256=M6du7VZflc4UPsGphmOXHANdgk8zessdJG0DBUuoA-U,78
252
- dsp_tools-17.0.0.post14.dist-info/entry_points.txt,sha256=qjRfEbkeAwLU_AE2Q-l4Y9irPNmu4Wna-3bfRp1bqV4,62
253
- dsp_tools-17.0.0.post14.dist-info/METADATA,sha256=tekPavfqdVzjQmW5uibBjMcpbJe8Ty1URY8MhmFCv_4,4285
254
- dsp_tools-17.0.0.post14.dist-info/RECORD,,
263
+ dsp_tools-17.0.0.post21.dist-info/WHEEL,sha256=M6du7VZflc4UPsGphmOXHANdgk8zessdJG0DBUuoA-U,78
264
+ dsp_tools-17.0.0.post21.dist-info/entry_points.txt,sha256=qjRfEbkeAwLU_AE2Q-l4Y9irPNmu4Wna-3bfRp1bqV4,62
265
+ dsp_tools-17.0.0.post21.dist-info/METADATA,sha256=c-CiHkhfevQLuSoAyhtEYoAA1CF4nWN8HoR5o7H6e_8,4285
266
+ dsp_tools-17.0.0.post21.dist-info/RECORD,,