dsp-tools 17.0.0.post26__py3-none-any.whl → 18.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of dsp-tools might be problematic. Click here for more details.
- dsp_tools/cli/args.py +14 -0
- dsp_tools/cli/call_action.py +34 -327
- dsp_tools/cli/call_action_files_only.py +74 -0
- dsp_tools/cli/call_action_with_network.py +203 -0
- dsp_tools/cli/create_parsers.py +71 -11
- dsp_tools/cli/utils.py +87 -0
- dsp_tools/clients/list_client.py +49 -0
- dsp_tools/clients/list_client_live.py +157 -0
- dsp_tools/clients/metadata_client.py +4 -4
- dsp_tools/clients/metadata_client_live.py +6 -5
- dsp_tools/clients/{ontology_client.py → ontology_clients.py} +17 -2
- dsp_tools/clients/{ontology_client_live.py → ontology_create_client_live.py} +2 -2
- dsp_tools/clients/ontology_get_client_live.py +65 -0
- dsp_tools/clients/project_client.py +10 -0
- dsp_tools/clients/project_client_live.py +30 -0
- dsp_tools/commands/create/create_on_server/cardinalities.py +14 -8
- dsp_tools/commands/create/create_on_server/lists.py +150 -0
- dsp_tools/commands/create/lists_only.py +45 -0
- dsp_tools/commands/create/models/input_problems.py +13 -0
- dsp_tools/commands/create/models/parsed_project.py +14 -1
- dsp_tools/commands/create/models/rdf_ontology.py +0 -7
- dsp_tools/commands/create/models/server_project_info.py +17 -3
- dsp_tools/commands/create/parsing/parse_lists.py +45 -0
- dsp_tools/commands/create/parsing/parse_project.py +23 -4
- dsp_tools/commands/ingest_xmlupload/create_resources/upload_xml.py +4 -0
- dsp_tools/commands/project/create/project_create_all.py +17 -13
- dsp_tools/commands/project/create/project_create_default_permissions.py +8 -6
- dsp_tools/commands/project/create/project_create_ontologies.py +30 -18
- dsp_tools/commands/project/legacy_models/listnode.py +0 -30
- dsp_tools/commands/validate_data/models/api_responses.py +2 -16
- dsp_tools/commands/validate_data/models/input_problems.py +1 -0
- dsp_tools/commands/validate_data/prepare_data/prepare_data.py +24 -16
- dsp_tools/commands/validate_data/process_validation_report/get_user_validation_message.py +53 -23
- dsp_tools/commands/validate_data/sparql/value_shacl.py +1 -1
- dsp_tools/commands/validate_data/validate_data.py +11 -3
- dsp_tools/commands/xmlupload/upload_config.py +1 -0
- dsp_tools/commands/xmlupload/xmlupload.py +1 -0
- dsp_tools/error/exceptions.py +8 -0
- dsp_tools/resources/start-stack/docker-compose.yml +23 -23
- dsp_tools/utils/ansi_colors.py +2 -0
- {dsp_tools-17.0.0.post26.dist-info → dsp_tools-18.0.0.dist-info}/METADATA +1 -1
- {dsp_tools-17.0.0.post26.dist-info → dsp_tools-18.0.0.dist-info}/RECORD +44 -35
- {dsp_tools-17.0.0.post26.dist-info → dsp_tools-18.0.0.dist-info}/WHEEL +1 -1
- dsp_tools/commands/project/create/project_create_lists.py +0 -200
- dsp_tools/commands/validate_data/api_clients.py +0 -124
- {dsp_tools-17.0.0.post26.dist-info → dsp_tools-18.0.0.dist-info}/entry_points.txt +0 -0
|
@@ -15,12 +15,13 @@ from dsp_tools.clients.authentication_client_live import AuthenticationClientLiv
|
|
|
15
15
|
from dsp_tools.clients.connection import Connection
|
|
16
16
|
from dsp_tools.clients.connection_live import ConnectionLive
|
|
17
17
|
from dsp_tools.commands.create.communicate_problems import print_problem_collection
|
|
18
|
+
from dsp_tools.commands.create.create_on_server.lists import create_lists
|
|
19
|
+
from dsp_tools.commands.create.create_on_server.lists import get_existing_lists_on_server
|
|
18
20
|
from dsp_tools.commands.create.models.parsed_project import ParsedProject
|
|
19
21
|
from dsp_tools.commands.create.models.server_project_info import ProjectIriLookup
|
|
20
22
|
from dsp_tools.commands.create.parsing.parse_project import parse_project
|
|
21
23
|
from dsp_tools.commands.project.create.parse_project import parse_project_json
|
|
22
24
|
from dsp_tools.commands.project.create.project_create_default_permissions import create_default_permissions
|
|
23
|
-
from dsp_tools.commands.project.create.project_create_lists import create_lists_on_server
|
|
24
25
|
from dsp_tools.commands.project.create.project_create_ontologies import create_ontologies
|
|
25
26
|
from dsp_tools.commands.project.legacy_models.context import Context
|
|
26
27
|
from dsp_tools.commands.project.legacy_models.group import Group
|
|
@@ -33,6 +34,8 @@ from dsp_tools.error.exceptions import InputError
|
|
|
33
34
|
from dsp_tools.error.exceptions import InvalidInputError
|
|
34
35
|
from dsp_tools.error.exceptions import PermanentConnectionError
|
|
35
36
|
from dsp_tools.legacy_models.langstring import LangString
|
|
37
|
+
from dsp_tools.utils.ansi_colors import BOLD
|
|
38
|
+
from dsp_tools.utils.ansi_colors import RESET_TO_DEFAULT
|
|
36
39
|
from dsp_tools.utils.json_parsing import parse_json_input
|
|
37
40
|
|
|
38
41
|
load_dotenv()
|
|
@@ -89,8 +92,8 @@ def create_project( # noqa: PLR0915,PLR0912 (too many statements & branches)
|
|
|
89
92
|
con = ConnectionLive(creds.server, auth)
|
|
90
93
|
|
|
91
94
|
# create project on DSP server
|
|
92
|
-
info_str = f"Create project '{legacy_project.metadata.shortname}' ({legacy_project.metadata.shortcode})
|
|
93
|
-
print(info_str)
|
|
95
|
+
info_str = f"Create project '{legacy_project.metadata.shortname}' ({legacy_project.metadata.shortcode}):"
|
|
96
|
+
print(BOLD + info_str + RESET_TO_DEFAULT)
|
|
94
97
|
logger.info(info_str)
|
|
95
98
|
project_remote, success = _create_project_on_server(
|
|
96
99
|
project_definition=legacy_project.metadata,
|
|
@@ -102,17 +105,18 @@ def create_project( # noqa: PLR0915,PLR0912 (too many statements & branches)
|
|
|
102
105
|
overall_success = False
|
|
103
106
|
|
|
104
107
|
# create the lists
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
con=con,
|
|
112
|
-
project_remote=project_remote,
|
|
108
|
+
if parsed_project.lists:
|
|
109
|
+
list_name_2_iri, list_problems = create_lists(
|
|
110
|
+
parsed_lists=parsed_project.lists,
|
|
111
|
+
shortcode=parsed_project.project_metadata.shortcode,
|
|
112
|
+
auth=auth,
|
|
113
|
+
project_iri=project_iri,
|
|
113
114
|
)
|
|
114
|
-
if
|
|
115
|
+
if list_problems:
|
|
115
116
|
overall_success = False
|
|
117
|
+
print_problem_collection(list_problems)
|
|
118
|
+
else:
|
|
119
|
+
list_name_2_iri = get_existing_lists_on_server(parsed_project.project_metadata.shortcode, auth)
|
|
116
120
|
|
|
117
121
|
# create the groups
|
|
118
122
|
current_project_groups: dict[str, Group] = {}
|
|
@@ -146,7 +150,7 @@ def create_project( # noqa: PLR0915,PLR0912 (too many statements & branches)
|
|
|
146
150
|
con=con,
|
|
147
151
|
context=context,
|
|
148
152
|
knora_api_prefix=knora_api_prefix,
|
|
149
|
-
|
|
153
|
+
list_name_2_iri=list_name_2_iri,
|
|
150
154
|
ontology_definitions=legacy_project.ontologies,
|
|
151
155
|
project_remote=project_remote,
|
|
152
156
|
verbose=verbose,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from loguru import logger
|
|
2
2
|
|
|
3
3
|
from dsp_tools.commands.project.models.permissions_client import PermissionsClient
|
|
4
|
+
from dsp_tools.utils.ansi_colors import BOLD
|
|
5
|
+
from dsp_tools.utils.ansi_colors import RESET_TO_DEFAULT
|
|
4
6
|
|
|
5
7
|
USER_IRI_PREFIX = "http://www.knora.org/ontology/knora-admin#"
|
|
6
8
|
|
|
@@ -11,23 +13,23 @@ def create_default_permissions(
|
|
|
11
13
|
default_permissions_overrule: dict[str, str | list[str]] | None,
|
|
12
14
|
shortcode: str,
|
|
13
15
|
) -> bool:
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
print(BOLD + "Processing default permissions:" + RESET_TO_DEFAULT)
|
|
17
|
+
logger.info("Processing default permissions:")
|
|
16
18
|
if not _delete_existing_doaps(perm_client):
|
|
17
|
-
print("WARNING: Cannot delete the existing default permissions")
|
|
19
|
+
print(" WARNING: Cannot delete the existing default permissions")
|
|
18
20
|
logger.warning("Cannot delete the existing default permissions")
|
|
19
21
|
return False
|
|
20
22
|
if not _create_new_doap(perm_client, default_permissions):
|
|
21
|
-
print("WARNING: Cannot create default permissions")
|
|
23
|
+
print(" WARNING: Cannot create default permissions")
|
|
22
24
|
logger.warning("Cannot create default permissions")
|
|
23
25
|
return False
|
|
24
26
|
if default_permissions_overrule:
|
|
25
27
|
if not _create_overrules(perm_client, default_permissions_overrule, shortcode):
|
|
26
|
-
print("WARNING: Cannot create default permissions overrules")
|
|
28
|
+
print(" WARNING: Cannot create default permissions overrules")
|
|
27
29
|
logger.warning("Cannot create default permissions overrules")
|
|
28
30
|
return False
|
|
31
|
+
print(" Default permissions have been set")
|
|
29
32
|
logger.info("Default permissions have been set")
|
|
30
|
-
print("Default permissions have been set")
|
|
31
33
|
return True
|
|
32
34
|
|
|
33
35
|
|
|
@@ -7,11 +7,12 @@ from loguru import logger
|
|
|
7
7
|
|
|
8
8
|
from dsp_tools.clients.authentication_client import AuthenticationClient
|
|
9
9
|
from dsp_tools.clients.connection import Connection
|
|
10
|
-
from dsp_tools.clients.
|
|
10
|
+
from dsp_tools.clients.ontology_create_client_live import OntologyCreateClientLive
|
|
11
11
|
from dsp_tools.commands.create.communicate_problems import print_problem_collection
|
|
12
12
|
from dsp_tools.commands.create.create_on_server.cardinalities import add_all_cardinalities
|
|
13
13
|
from dsp_tools.commands.create.models.parsed_ontology import ParsedOntology
|
|
14
14
|
from dsp_tools.commands.create.models.server_project_info import CreatedIriCollection
|
|
15
|
+
from dsp_tools.commands.create.models.server_project_info import ListNameToIriLookup
|
|
15
16
|
from dsp_tools.commands.create.models.server_project_info import ProjectIriLookup
|
|
16
17
|
from dsp_tools.commands.project.legacy_models.context import Context
|
|
17
18
|
from dsp_tools.commands.project.legacy_models.ontology import Ontology
|
|
@@ -22,13 +23,15 @@ from dsp_tools.error.exceptions import BaseError
|
|
|
22
23
|
from dsp_tools.error.exceptions import InputError
|
|
23
24
|
from dsp_tools.legacy_models.datetimestamp import DateTimeStamp
|
|
24
25
|
from dsp_tools.legacy_models.langstring import LangString
|
|
26
|
+
from dsp_tools.utils.ansi_colors import BOLD
|
|
27
|
+
from dsp_tools.utils.ansi_colors import RESET_TO_DEFAULT
|
|
25
28
|
|
|
26
29
|
|
|
27
30
|
def create_ontologies(
|
|
28
31
|
con: Connection,
|
|
29
32
|
context: Context,
|
|
30
33
|
knora_api_prefix: str,
|
|
31
|
-
|
|
34
|
+
list_name_2_iri: ListNameToIriLookup,
|
|
32
35
|
ontology_definitions: list[dict[str, Any]],
|
|
33
36
|
project_remote: Project,
|
|
34
37
|
verbose: bool,
|
|
@@ -45,7 +48,7 @@ def create_ontologies(
|
|
|
45
48
|
con: Connection to the DSP server
|
|
46
49
|
context: prefixes and the ontology IRIs they stand for
|
|
47
50
|
knora_api_prefix: the prefix that stands for the knora-api ontology
|
|
48
|
-
|
|
51
|
+
list_name_2_iri: IRIs of list nodes that were already created and are available on the DSP server
|
|
49
52
|
ontology_definitions: the "ontologies" section of the parsed JSON project file
|
|
50
53
|
project_remote: representation of the project on the DSP server
|
|
51
54
|
verbose: verbose switch
|
|
@@ -61,17 +64,15 @@ def create_ontologies(
|
|
|
61
64
|
True if everything went smoothly, False otherwise
|
|
62
65
|
"""
|
|
63
66
|
success_collection = CreatedIriCollection()
|
|
64
|
-
onto_client =
|
|
67
|
+
onto_client = OntologyCreateClientLive(auth.server, auth)
|
|
65
68
|
|
|
66
69
|
overall_success = True
|
|
67
|
-
|
|
68
|
-
print("Create ontologies...")
|
|
69
|
-
logger.info("Create ontologies...")
|
|
70
|
+
logger.info("Processing Ontology Section")
|
|
70
71
|
try:
|
|
71
72
|
project_ontologies = Ontology.getProjectOntologies(con=con, project_id=str(project_remote.iri))
|
|
72
73
|
except BaseError:
|
|
73
74
|
err_msg = "Unable to retrieve remote ontologies. Cannot check if your ontology already exists."
|
|
74
|
-
print("WARNING: {err_msg}")
|
|
75
|
+
print(f" WARNING: {err_msg}")
|
|
75
76
|
logger.exception(err_msg)
|
|
76
77
|
project_ontologies = []
|
|
77
78
|
|
|
@@ -114,7 +115,7 @@ def create_ontologies(
|
|
|
114
115
|
onto_name=ontology_definition["name"],
|
|
115
116
|
property_definitions=ontology_definition.get("properties", []),
|
|
116
117
|
ontology_remote=ontology_remote,
|
|
117
|
-
|
|
118
|
+
list_name_2_iri=list_name_2_iri,
|
|
118
119
|
con=con,
|
|
119
120
|
last_modification_date=last_modification_date,
|
|
120
121
|
knora_api_prefix=knora_api_prefix,
|
|
@@ -125,7 +126,7 @@ def create_ontologies(
|
|
|
125
126
|
if not success:
|
|
126
127
|
overall_success = False
|
|
127
128
|
|
|
128
|
-
print("
|
|
129
|
+
print(BOLD + "Processing Cardinalities:" + RESET_TO_DEFAULT)
|
|
129
130
|
problems = add_all_cardinalities(
|
|
130
131
|
ontologies=parsed_ontologies,
|
|
131
132
|
project_iri_lookup=project_iri_lookup,
|
|
@@ -173,12 +174,12 @@ def _create_ontology(
|
|
|
173
174
|
# skip if it already exists on the DSP server
|
|
174
175
|
if onto_name in [onto.name for onto in project_ontologies]:
|
|
175
176
|
err_msg = f"Ontology '{onto_name}' already exists on the DSP server. Skipping..."
|
|
176
|
-
print(f"
|
|
177
|
+
print(f"WARNING: {err_msg}")
|
|
177
178
|
logger.warning(err_msg)
|
|
178
179
|
return None
|
|
179
180
|
|
|
180
|
-
print(f"
|
|
181
|
-
logger.info(f"
|
|
181
|
+
print(BOLD + f"Processing ontology '{onto_name}':" + RESET_TO_DEFAULT)
|
|
182
|
+
logger.info(f"Processing ontology '{onto_name}'")
|
|
182
183
|
ontology_local = Ontology(
|
|
183
184
|
con=con,
|
|
184
185
|
project=project_remote,
|
|
@@ -308,11 +309,11 @@ def _sort_resources(
|
|
|
308
309
|
return sorted_resources
|
|
309
310
|
|
|
310
311
|
|
|
311
|
-
def _add_property_classes_to_remote_ontology(
|
|
312
|
+
def _add_property_classes_to_remote_ontology( # noqa: PLR0912
|
|
312
313
|
onto_name: str,
|
|
313
314
|
property_definitions: list[dict[str, Any]],
|
|
314
315
|
ontology_remote: Ontology,
|
|
315
|
-
|
|
316
|
+
list_name_2_iri: ListNameToIriLookup,
|
|
316
317
|
con: Connection,
|
|
317
318
|
last_modification_date: DateTimeStamp,
|
|
318
319
|
knora_api_prefix: str,
|
|
@@ -328,7 +329,7 @@ def _add_property_classes_to_remote_ontology(
|
|
|
328
329
|
onto_name: name of the current ontology
|
|
329
330
|
property_definitions: the part of the parsed JSON project file that contains the properties of the current onto
|
|
330
331
|
ontology_remote: representation of the current ontology on the DSP server
|
|
331
|
-
|
|
332
|
+
list_name_2_iri: IRIs of list nodes that were already created and are available on the DSP server
|
|
332
333
|
con: connection to the DSP server
|
|
333
334
|
last_modification_date: last modification date of the ontology on the DSP server
|
|
334
335
|
knora_api_prefix: the prefix that stands for the knora-api ontology
|
|
@@ -373,7 +374,18 @@ def _add_property_classes_to_remote_ontology(
|
|
|
373
374
|
# get the gui_attributes
|
|
374
375
|
gui_attributes = prop_class.get("gui_attributes")
|
|
375
376
|
if gui_attributes and gui_attributes.get("hlist"):
|
|
376
|
-
|
|
377
|
+
list_name = gui_attributes["hlist"]
|
|
378
|
+
list_iri = list_name_2_iri.get_iri(list_name)
|
|
379
|
+
if not list_iri:
|
|
380
|
+
err_msg = (
|
|
381
|
+
f"Unable to create property class '{prop_class['name']}' "
|
|
382
|
+
f"because the list with the name '{list_name}' does not exist on the server."
|
|
383
|
+
)
|
|
384
|
+
print(f" WARNING: {err_msg}")
|
|
385
|
+
logger.warning(err_msg)
|
|
386
|
+
overall_success = False
|
|
387
|
+
continue
|
|
388
|
+
|
|
377
389
|
gui_attributes["hlist"] = f"<{list_iri}>"
|
|
378
390
|
|
|
379
391
|
# create the property class
|
|
@@ -405,7 +417,7 @@ def _add_property_classes_to_remote_ontology(
|
|
|
405
417
|
err.message,
|
|
406
418
|
):
|
|
407
419
|
err_msg += f", because it refers to a class of another project: '{found.group(1)}'."
|
|
408
|
-
print(f"WARNING: {err_msg}")
|
|
420
|
+
print(f" WARNING: {err_msg}")
|
|
409
421
|
logger.exception(err_msg)
|
|
410
422
|
overall_success = False
|
|
411
423
|
|
|
@@ -301,36 +301,6 @@ class ListNode(Model):
|
|
|
301
301
|
rootNodeIri=rootNodeIri,
|
|
302
302
|
)
|
|
303
303
|
|
|
304
|
-
def create(self) -> ListNode:
|
|
305
|
-
"""
|
|
306
|
-
Create a new List
|
|
307
|
-
|
|
308
|
-
:return: JSON-object from DSP-API
|
|
309
|
-
"""
|
|
310
|
-
jsonobj = self._toJsonObj_create()
|
|
311
|
-
if self._parent:
|
|
312
|
-
result = self._con.post(ListNode.ROUTE_SLASH + quote_plus(self._parent), jsonobj)
|
|
313
|
-
return ListNode.fromJsonObj(self._con, result["nodeinfo"])
|
|
314
|
-
else:
|
|
315
|
-
result = self._con.post(ListNode.ROUTE, jsonobj)
|
|
316
|
-
return ListNode.fromJsonObj(self._con, result["list"]["listinfo"])
|
|
317
|
-
|
|
318
|
-
def _toJsonObj_create(self):
|
|
319
|
-
tmp = {}
|
|
320
|
-
if self._project is None:
|
|
321
|
-
raise BaseError("There must be a project id given!")
|
|
322
|
-
tmp["projectIri"] = self._project
|
|
323
|
-
if self._label.isEmpty():
|
|
324
|
-
raise BaseError("There must be a valid ListNode label!")
|
|
325
|
-
tmp["labels"] = self._label.toJsonObj()
|
|
326
|
-
if self._comments:
|
|
327
|
-
tmp["comments"] = self._comments.toJsonObj()
|
|
328
|
-
if self._name:
|
|
329
|
-
tmp["name"] = self._name
|
|
330
|
-
if self._parent:
|
|
331
|
-
tmp["parentNodeIri"] = self._parent
|
|
332
|
-
return tmp
|
|
333
|
-
|
|
334
304
|
def read(self) -> Any:
|
|
335
305
|
"""
|
|
336
306
|
Read a project from DSP-API
|
|
@@ -5,6 +5,8 @@ from dataclasses import dataclass
|
|
|
5
5
|
from rdflib import Graph
|
|
6
6
|
from rdflib import URIRef
|
|
7
7
|
|
|
8
|
+
from dsp_tools.clients.list_client import OneList
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
@dataclass
|
|
10
12
|
class SHACLValidationReport:
|
|
@@ -24,22 +26,6 @@ class ListLookup:
|
|
|
24
26
|
lists: dict[tuple[str, str], str]
|
|
25
27
|
|
|
26
28
|
|
|
27
|
-
@dataclass
|
|
28
|
-
class OneList:
|
|
29
|
-
list_iri: str
|
|
30
|
-
list_name: str
|
|
31
|
-
nodes: list[OneNode]
|
|
32
|
-
|
|
33
|
-
def hlist(self) -> str:
|
|
34
|
-
return f'"hlist=<{self.list_iri}>"'
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
@dataclass
|
|
38
|
-
class OneNode:
|
|
39
|
-
name: str
|
|
40
|
-
iri: str
|
|
41
|
-
|
|
42
|
-
|
|
43
29
|
@dataclass
|
|
44
30
|
class SHACLListInfo:
|
|
45
31
|
list_iri: URIRef
|
|
@@ -114,5 +114,6 @@ class ProblemType(StrEnum):
|
|
|
114
114
|
LINK_TARGET_TYPE_MISMATCH = "Linked Resource Type Mismatch"
|
|
115
115
|
LINK_TARGET_OF_ANOTHER_PROJECT = "Linked Resource is from another project"
|
|
116
116
|
LINK_TARGET_IS_IRI_OF_PROJECT = "Linked resource is an IRI of the project"
|
|
117
|
+
LINK_TARGET_NOT_FOUND_IN_DB = "Linked resource is an IRI and could not be found in the database."
|
|
117
118
|
INEXISTENT_LINKED_RESOURCE = "Linked Resource does not exist"
|
|
118
119
|
DUPLICATE_VALUE = "Your input is duplicated"
|
|
@@ -8,14 +8,15 @@ from rdflib import URIRef
|
|
|
8
8
|
|
|
9
9
|
from dsp_tools.clients.authentication_client import AuthenticationClient
|
|
10
10
|
from dsp_tools.clients.legal_info_client_live import LegalInfoClientLive
|
|
11
|
-
from dsp_tools.clients.
|
|
11
|
+
from dsp_tools.clients.list_client import OneList
|
|
12
|
+
from dsp_tools.clients.list_client_live import ListGetClientLive
|
|
13
|
+
from dsp_tools.clients.metadata_client import ExistingResourcesRetrieved
|
|
12
14
|
from dsp_tools.clients.metadata_client_live import MetadataClientLive
|
|
13
|
-
from dsp_tools.
|
|
14
|
-
from dsp_tools.
|
|
15
|
+
from dsp_tools.clients.ontology_clients import OntologyGetClient
|
|
16
|
+
from dsp_tools.clients.ontology_get_client_live import OntologyGetClientLive
|
|
15
17
|
from dsp_tools.commands.validate_data.models.api_responses import EnabledLicenseIris
|
|
16
18
|
from dsp_tools.commands.validate_data.models.api_responses import InfoForResourceInDB
|
|
17
19
|
from dsp_tools.commands.validate_data.models.api_responses import ListLookup
|
|
18
|
-
from dsp_tools.commands.validate_data.models.api_responses import OneList
|
|
19
20
|
from dsp_tools.commands.validate_data.models.api_responses import ProjectDataFromApi
|
|
20
21
|
from dsp_tools.commands.validate_data.models.validation import RDFGraphs
|
|
21
22
|
from dsp_tools.commands.validate_data.prepare_data.get_rdf_like_data import get_rdf_like_data
|
|
@@ -47,13 +48,16 @@ def prepare_data_for_validation_from_parsed_resource(
|
|
|
47
48
|
permission_ids: list[str],
|
|
48
49
|
auth: AuthenticationClient,
|
|
49
50
|
shortcode: str,
|
|
50
|
-
|
|
51
|
+
do_not_request_resource_metadata_from_db: bool,
|
|
52
|
+
) -> tuple[RDFGraphs, set[str], ExistingResourcesRetrieved]:
|
|
51
53
|
used_iris = {x.res_type for x in parsed_resources}
|
|
52
|
-
proj_info,
|
|
54
|
+
proj_info, existing_resources_retrieved = _get_project_specific_information_from_api(
|
|
55
|
+
auth, shortcode, do_not_request_resource_metadata_from_db
|
|
56
|
+
)
|
|
53
57
|
list_lookup = _make_list_lookup(proj_info.all_lists)
|
|
54
58
|
data_rdf = _make_data_graph_from_parsed_resources(parsed_resources, authorship_lookup, list_lookup)
|
|
55
59
|
rdf_graphs = _create_graphs(data_rdf, shortcode, auth, proj_info, permission_ids)
|
|
56
|
-
return rdf_graphs, used_iris,
|
|
60
|
+
return rdf_graphs, used_iris, existing_resources_retrieved
|
|
57
61
|
|
|
58
62
|
|
|
59
63
|
def _make_list_lookup(project_lists: list[OneList]) -> ListLookup:
|
|
@@ -66,18 +70,22 @@ def _make_list_lookup(project_lists: list[OneList]) -> ListLookup:
|
|
|
66
70
|
|
|
67
71
|
|
|
68
72
|
def _get_project_specific_information_from_api(
|
|
69
|
-
auth: AuthenticationClient, shortcode: str
|
|
70
|
-
) -> tuple[ProjectDataFromApi,
|
|
71
|
-
list_client =
|
|
72
|
-
all_lists = list_client.
|
|
73
|
+
auth: AuthenticationClient, shortcode: str, do_not_request_resource_metadata_from_db: bool
|
|
74
|
+
) -> tuple[ProjectDataFromApi, ExistingResourcesRetrieved]:
|
|
75
|
+
list_client = ListGetClientLive(auth.server, shortcode)
|
|
76
|
+
all_lists = list_client.get_all_lists_and_nodes()
|
|
73
77
|
enabled_licenses = _get_license_iris(shortcode, auth)
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
if do_not_request_resource_metadata_from_db:
|
|
79
|
+
existing_resources_retrieved = ExistingResourcesRetrieved.FALSE
|
|
80
|
+
formatted_metadata: list[InfoForResourceInDB] = []
|
|
81
|
+
else:
|
|
82
|
+
existing_resources_retrieved, formatted_metadata = _get_metadata_info(auth, shortcode)
|
|
83
|
+
return ProjectDataFromApi(all_lists, enabled_licenses, formatted_metadata), existing_resources_retrieved
|
|
76
84
|
|
|
77
85
|
|
|
78
86
|
def _get_metadata_info(
|
|
79
87
|
auth: AuthenticationClient, shortcode: str
|
|
80
|
-
) -> tuple[
|
|
88
|
+
) -> tuple[ExistingResourcesRetrieved, list[InfoForResourceInDB]]:
|
|
81
89
|
metadata_client = MetadataClientLive(auth.server, auth)
|
|
82
90
|
retrieval_status, metadata = metadata_client.get_resource_metadata(shortcode)
|
|
83
91
|
formatted_metadata = [InfoForResourceInDB(x["resourceIri"], x["resourceClassIri"]) for x in metadata]
|
|
@@ -100,7 +108,7 @@ def _create_graphs(
|
|
|
100
108
|
permission_ids: list[str],
|
|
101
109
|
) -> RDFGraphs:
|
|
102
110
|
logger.debug("Create all graphs.")
|
|
103
|
-
onto_client =
|
|
111
|
+
onto_client = OntologyGetClientLive(auth.server, shortcode)
|
|
104
112
|
ontologies, onto_iris = _get_project_ontos(onto_client)
|
|
105
113
|
knora_ttl = onto_client.get_knora_api()
|
|
106
114
|
knora_api = Graph()
|
|
@@ -147,7 +155,7 @@ def _bind_prefixes_to_graph(g: Graph, project_ontos: list[str]) -> Graph:
|
|
|
147
155
|
return g
|
|
148
156
|
|
|
149
157
|
|
|
150
|
-
def _get_project_ontos(onto_client:
|
|
158
|
+
def _get_project_ontos(onto_client: OntologyGetClient) -> tuple[Graph, list[str]]:
|
|
151
159
|
logger.debug("Get project ontologies from server.")
|
|
152
160
|
all_ontos, onto_iris = onto_client.get_ontologies()
|
|
153
161
|
onto_g = Graph()
|
|
@@ -3,6 +3,7 @@ from collections import defaultdict
|
|
|
3
3
|
import pandas as pd
|
|
4
4
|
|
|
5
5
|
from dsp_tools.cli.args import ValidationSeverity
|
|
6
|
+
from dsp_tools.clients.metadata_client import ExistingResourcesRetrieved
|
|
6
7
|
from dsp_tools.commands.validate_data.models.input_problems import AllProblems
|
|
7
8
|
from dsp_tools.commands.validate_data.models.input_problems import DuplicateFileWarning
|
|
8
9
|
from dsp_tools.commands.validate_data.models.input_problems import InputProblem
|
|
@@ -20,9 +21,14 @@ PROBLEM_TYPES_IGNORE_STR_ENUM_INFO = {ProblemType.GENERIC, ProblemType.FILE_VALU
|
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
def sort_user_problems(
|
|
23
|
-
all_problems: AllProblems,
|
|
24
|
+
all_problems: AllProblems,
|
|
25
|
+
duplicate_file_warnings: DuplicateFileWarning | None,
|
|
26
|
+
shortcode: str,
|
|
27
|
+
existing_resources_retrieved: ExistingResourcesRetrieved,
|
|
24
28
|
) -> SortedProblems:
|
|
25
|
-
iris_removed, links_level_info = _separate_resource_links_to_iris_of_own_project(
|
|
29
|
+
iris_removed, links_level_info = _separate_resource_links_to_iris_of_own_project(
|
|
30
|
+
all_problems.problems, shortcode, existing_resources_retrieved
|
|
31
|
+
)
|
|
26
32
|
filtered_problems = _filter_out_duplicate_problems(iris_removed)
|
|
27
33
|
violations, warnings, info = _separate_according_to_severity(filtered_problems)
|
|
28
34
|
if duplicate_file_warnings:
|
|
@@ -47,37 +53,61 @@ def _separate_according_to_severity(
|
|
|
47
53
|
|
|
48
54
|
|
|
49
55
|
def _separate_resource_links_to_iris_of_own_project(
|
|
50
|
-
problems: list[InputProblem], shortcode: str
|
|
56
|
+
problems: list[InputProblem], shortcode: str, existing_resources_retrieved: ExistingResourcesRetrieved
|
|
51
57
|
) -> tuple[list[InputProblem], list[InputProblem]]:
|
|
52
58
|
link_level_info = []
|
|
53
59
|
all_others = []
|
|
54
|
-
resource_iri_start = "http://rdfh.ch/"
|
|
55
|
-
project_resource_iri = f"{resource_iri_start}{shortcode}/"
|
|
56
60
|
for prblm in problems:
|
|
57
61
|
if prblm.problem_type != ProblemType.INEXISTENT_LINKED_RESOURCE:
|
|
58
62
|
all_others.append(prblm)
|
|
59
|
-
continue
|
|
60
|
-
if not prblm.input_value:
|
|
61
|
-
all_others.append(prblm)
|
|
62
|
-
elif prblm.input_value.startswith(project_resource_iri):
|
|
63
|
-
prblm.message = (
|
|
64
|
-
"You used an absolute IRI to reference an existing resource in the DB. "
|
|
65
|
-
"If this resource does not exist or is not of the correct type, an xmlupload will fail."
|
|
66
|
-
)
|
|
67
|
-
prblm.problem_type = ProblemType.LINK_TARGET_IS_IRI_OF_PROJECT
|
|
68
|
-
link_level_info.append(prblm)
|
|
69
|
-
elif prblm.input_value.startswith(resource_iri_start):
|
|
70
|
-
prblm.message = (
|
|
71
|
-
"You used an absolute IRI to reference an existing resource of another project in the DB. "
|
|
72
|
-
"Cross-Project resource links are not permitted."
|
|
73
|
-
)
|
|
74
|
-
prblm.problem_type = ProblemType.LINK_TARGET_OF_ANOTHER_PROJECT
|
|
75
|
-
all_others.append(prblm)
|
|
76
63
|
else:
|
|
77
|
-
|
|
64
|
+
is_violation, triaged_problem = _determined_link_value_message_and_level(
|
|
65
|
+
prblm, shortcode, existing_resources_retrieved
|
|
66
|
+
)
|
|
67
|
+
if is_violation:
|
|
68
|
+
all_others.append(triaged_problem)
|
|
69
|
+
else:
|
|
70
|
+
link_level_info.append(triaged_problem)
|
|
78
71
|
return all_others, link_level_info
|
|
79
72
|
|
|
80
73
|
|
|
74
|
+
def _determined_link_value_message_and_level(
|
|
75
|
+
problem: InputProblem, shortcode: str, existing_resources_retrieved: ExistingResourcesRetrieved
|
|
76
|
+
) -> tuple[bool, InputProblem]:
|
|
77
|
+
is_violation = True
|
|
78
|
+
resource_iri_start = "http://rdfh.ch/"
|
|
79
|
+
project_resource_iri = f"{resource_iri_start}{shortcode}/"
|
|
80
|
+
if not problem.input_value:
|
|
81
|
+
return is_violation, problem
|
|
82
|
+
if problem.input_value.startswith(project_resource_iri):
|
|
83
|
+
# case IRI and matches those of the projects itself
|
|
84
|
+
if existing_resources_retrieved == ExistingResourcesRetrieved.TRUE:
|
|
85
|
+
# if metadata was sucessfully retrieved, then the IRI is wrong
|
|
86
|
+
problem.problem_type = ProblemType.LINK_TARGET_NOT_FOUND_IN_DB
|
|
87
|
+
problem.message = (
|
|
88
|
+
"You used an absolute IRI to reference an existing resource in the DB. "
|
|
89
|
+
"We could not find a reference to this resource in the database."
|
|
90
|
+
)
|
|
91
|
+
return is_violation, problem
|
|
92
|
+
# if we could not retrieve the metadata, then we cannot verify if it exists or not, so it is only an info
|
|
93
|
+
problem.problem_type = ProblemType.LINK_TARGET_IS_IRI_OF_PROJECT
|
|
94
|
+
problem.message = (
|
|
95
|
+
"You used an absolute IRI to reference an existing resource in the DB. "
|
|
96
|
+
"If this resource does not exist or is not of the correct type, an xmlupload will fail."
|
|
97
|
+
)
|
|
98
|
+
return not is_violation, problem
|
|
99
|
+
if problem.input_value.startswith(resource_iri_start):
|
|
100
|
+
# case IRI, but does not contain the shortcode of the project
|
|
101
|
+
problem.message = (
|
|
102
|
+
"You used an absolute IRI to reference an existing resource of another project in the DB. "
|
|
103
|
+
"Cross-Project resource links are not permitted."
|
|
104
|
+
)
|
|
105
|
+
problem.problem_type = ProblemType.LINK_TARGET_OF_ANOTHER_PROJECT
|
|
106
|
+
return is_violation, problem
|
|
107
|
+
# all other cases, it is not an IRI and must be an internal ID that does not exist in the XML
|
|
108
|
+
return is_violation, problem
|
|
109
|
+
|
|
110
|
+
|
|
81
111
|
def _filter_out_duplicate_problems(problems: list[InputProblem]) -> list[InputProblem]:
|
|
82
112
|
grouped, without_res_id = _group_problems_by_resource(problems)
|
|
83
113
|
filtered = without_res_id
|
|
@@ -8,7 +8,7 @@ from rdflib import Literal
|
|
|
8
8
|
from rdflib import URIRef
|
|
9
9
|
from rdflib.collection import Collection
|
|
10
10
|
|
|
11
|
-
from dsp_tools.
|
|
11
|
+
from dsp_tools.clients.list_client import OneList
|
|
12
12
|
from dsp_tools.commands.validate_data.models.api_responses import SHACLListInfo
|
|
13
13
|
from dsp_tools.utils.rdflib_constants import KNORA_API
|
|
14
14
|
from dsp_tools.utils.rdflib_constants import PropertyTypeAlias
|
|
@@ -10,6 +10,7 @@ from dsp_tools.cli.args import ValidateDataConfig
|
|
|
10
10
|
from dsp_tools.cli.args import ValidationSeverity
|
|
11
11
|
from dsp_tools.clients.authentication_client import AuthenticationClient
|
|
12
12
|
from dsp_tools.clients.authentication_client_live import AuthenticationClientLive
|
|
13
|
+
from dsp_tools.clients.metadata_client import ExistingResourcesRetrieved
|
|
13
14
|
from dsp_tools.commands.validate_data.models.input_problems import OntologyValidationProblem
|
|
14
15
|
from dsp_tools.commands.validate_data.models.input_problems import SortedProblems
|
|
15
16
|
from dsp_tools.commands.validate_data.models.input_problems import UnknownClassesInData
|
|
@@ -51,6 +52,7 @@ def validate_data(
|
|
|
51
52
|
save_graphs: bool,
|
|
52
53
|
skip_ontology_validation: bool,
|
|
53
54
|
id2iri_replacement_file: str | None,
|
|
55
|
+
do_not_request_resource_metadata_from_db: bool,
|
|
54
56
|
) -> bool:
|
|
55
57
|
"""
|
|
56
58
|
Takes a file and project information and validates it against the ontologies on the server.
|
|
@@ -62,6 +64,7 @@ def validate_data(
|
|
|
62
64
|
save_graphs: if this flag is set, all the graphs will be saved in a folder
|
|
63
65
|
skip_ontology_validation: skip the ontology validation
|
|
64
66
|
id2iri_replacement_file: to replace internal IDs of an XML file by IRIs provided in this mapping file
|
|
67
|
+
do_not_request_resource_metadata_from_db: true if no metadata for existing resources should be requested
|
|
65
68
|
|
|
66
69
|
Returns:
|
|
67
70
|
True if no errors that impede an xmlupload were found.
|
|
@@ -78,6 +81,7 @@ def validate_data(
|
|
|
78
81
|
ignore_duplicate_files_warning=ignore_duplicate_files_warning,
|
|
79
82
|
is_on_prod_server=is_prod_like_server(creds.server),
|
|
80
83
|
skip_ontology_validation=skip_ontology_validation,
|
|
84
|
+
do_not_request_resource_metadata_from_db=do_not_request_resource_metadata_from_db,
|
|
81
85
|
)
|
|
82
86
|
auth = AuthenticationClientLive(server=creds.server, email=creds.user, password=creds.password)
|
|
83
87
|
|
|
@@ -104,14 +108,17 @@ def validate_parsed_resources(
|
|
|
104
108
|
config: ValidateDataConfig,
|
|
105
109
|
auth: AuthenticationClient,
|
|
106
110
|
) -> bool:
|
|
107
|
-
rdf_graphs, used_iris,
|
|
111
|
+
rdf_graphs, used_iris, existing_resources_retrieved = prepare_data_for_validation_from_parsed_resource(
|
|
108
112
|
parsed_resources=parsed_resources,
|
|
109
113
|
authorship_lookup=authorship_lookup,
|
|
110
114
|
permission_ids=permission_ids,
|
|
111
115
|
auth=auth,
|
|
112
116
|
shortcode=shortcode,
|
|
117
|
+
do_not_request_resource_metadata_from_db=config.do_not_request_resource_metadata_from_db,
|
|
118
|
+
)
|
|
119
|
+
validation_result = _validate_data(
|
|
120
|
+
rdf_graphs, used_iris, parsed_resources, config, shortcode, existing_resources_retrieved
|
|
113
121
|
)
|
|
114
|
-
validation_result = _validate_data(rdf_graphs, used_iris, parsed_resources, config, shortcode)
|
|
115
122
|
if validation_result.no_problems:
|
|
116
123
|
logger.debug("No validation errors found.")
|
|
117
124
|
print(NO_VALIDATION_ERRORS_FOUND_MSG)
|
|
@@ -143,6 +150,7 @@ def _validate_data(
|
|
|
143
150
|
parsed_resources: list[ParsedResource],
|
|
144
151
|
config: ValidateDataConfig,
|
|
145
152
|
shortcode: str,
|
|
153
|
+
existing_resources_retrieved: ExistingResourcesRetrieved,
|
|
146
154
|
) -> ValidateDataResult:
|
|
147
155
|
logger.debug(f"Validate-data called with the following config: {vars(config)}")
|
|
148
156
|
# Check if unknown classes are used
|
|
@@ -171,7 +179,7 @@ def _validate_data(
|
|
|
171
179
|
)
|
|
172
180
|
return ValidateDataResult(False, sorted_problems, report)
|
|
173
181
|
reformatted = reformat_validation_graph(report)
|
|
174
|
-
sorted_problems = sort_user_problems(reformatted, duplicate_file_warnings, shortcode)
|
|
182
|
+
sorted_problems = sort_user_problems(reformatted, duplicate_file_warnings, shortcode, existing_resources_retrieved)
|
|
175
183
|
return ValidateDataResult(False, sorted_problems, report)
|
|
176
184
|
|
|
177
185
|
|
|
@@ -56,6 +56,7 @@ class UploadConfig:
|
|
|
56
56
|
ignore_duplicate_files_warning: bool = False
|
|
57
57
|
validation_severity: ValidationSeverity = field(default_factory=lambda: ValidationSeverity.INFO)
|
|
58
58
|
id2iri_replacement_file: str | None = None
|
|
59
|
+
do_not_request_resource_metadata_from_db: bool = False
|
|
59
60
|
|
|
60
61
|
def with_server_info(
|
|
61
62
|
self,
|
|
@@ -177,6 +177,7 @@ def _handle_validation(
|
|
|
177
177
|
ignore_duplicate_files_warning=ignore_duplicates,
|
|
178
178
|
is_on_prod_server=is_on_prod_like_server,
|
|
179
179
|
skip_ontology_validation=config.skip_ontology_validation,
|
|
180
|
+
do_not_request_resource_metadata_from_db=config.do_not_request_resource_metadata_from_db,
|
|
180
181
|
),
|
|
181
182
|
auth=auth,
|
|
182
183
|
)
|
dsp_tools/error/exceptions.py
CHANGED
|
@@ -154,3 +154,11 @@ class XmlUploadListNodeNotFoundError(BaseError):
|
|
|
154
154
|
|
|
155
155
|
class UnknownDOAPException(BaseError):
|
|
156
156
|
"""Class for errors that are raised if a DOAP cannot be parsed"""
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class CreateError(BaseError):
|
|
160
|
+
"""Errors for the create command."""
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class ProjectNotFoundError(CreateError):
|
|
164
|
+
"""Class if a project is expected to exist but could not be found."""
|