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
|
@@ -6,9 +6,9 @@ from rdflib import Literal
|
|
|
6
6
|
from dsp_tools.clients.authentication_client import AuthenticationClient
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
class
|
|
9
|
+
class OntologyCreateClient(Protocol):
|
|
10
10
|
"""
|
|
11
|
-
Protocol class/interface
|
|
11
|
+
Protocol class/interface to create / update the ontology through the API.
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
server: str
|
|
@@ -19,3 +19,18 @@ class OntologyClient(Protocol):
|
|
|
19
19
|
|
|
20
20
|
def post_resource_cardinalities(self, cardinality_graph: dict[str, Any]) -> Literal | None:
|
|
21
21
|
"""Add cardinalities to an existing resource class."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class OntologyGetClient(Protocol):
|
|
25
|
+
"""
|
|
26
|
+
Protocol class/interface to get ontologies from the API.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
api_url: str
|
|
30
|
+
shortcode: str
|
|
31
|
+
|
|
32
|
+
def get_knora_api(self) -> str:
|
|
33
|
+
"""Get the knora-api ontology."""
|
|
34
|
+
|
|
35
|
+
def get_ontologies(self) -> tuple[list[str], list[str]]:
|
|
36
|
+
"""Get all project ontologies."""
|
|
@@ -11,7 +11,7 @@ from requests import ReadTimeout
|
|
|
11
11
|
from requests import Response
|
|
12
12
|
|
|
13
13
|
from dsp_tools.clients.authentication_client import AuthenticationClient
|
|
14
|
-
from dsp_tools.clients.
|
|
14
|
+
from dsp_tools.clients.ontology_clients import OntologyCreateClient
|
|
15
15
|
from dsp_tools.error.exceptions import BadCredentialsError
|
|
16
16
|
from dsp_tools.error.exceptions import UnexpectedApiResponseError
|
|
17
17
|
from dsp_tools.utils.rdflib_constants import KNORA_API
|
|
@@ -24,7 +24,7 @@ TIMEOUT = 60
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
@dataclass
|
|
27
|
-
class
|
|
27
|
+
class OntologyCreateClientLive(OntologyCreateClient):
|
|
28
28
|
"""
|
|
29
29
|
Client for the ontology endpoint in the API.
|
|
30
30
|
"""
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Any
|
|
3
|
+
from typing import cast
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from dsp_tools.clients.ontology_clients import OntologyGetClient
|
|
8
|
+
from dsp_tools.error.exceptions import InternalError
|
|
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
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class OntologyGetClientLive(OntologyGetClient):
|
|
16
|
+
api_url: str
|
|
17
|
+
shortcode: str
|
|
18
|
+
|
|
19
|
+
def get_knora_api(self) -> str:
|
|
20
|
+
url = f"{self.api_url}/ontology/knora-api/v2#"
|
|
21
|
+
headers = {"Accept": "text/turtle"}
|
|
22
|
+
timeout = 60
|
|
23
|
+
log_request(RequestParameters("GET", url, timeout=timeout, headers=headers))
|
|
24
|
+
response = requests.get(url=url, headers=headers, timeout=timeout)
|
|
25
|
+
log_response(response, include_response_content=False)
|
|
26
|
+
if not response.ok:
|
|
27
|
+
raise InternalError(f"Failed Request: {response.status_code} {response.text}")
|
|
28
|
+
return response.text
|
|
29
|
+
|
|
30
|
+
def get_ontologies(self) -> tuple[list[str], list[str]]:
|
|
31
|
+
"""
|
|
32
|
+
Returns a list of project ontologies as a string in turtle format.
|
|
33
|
+
And a list of the ontology IRIs
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
list of ontologies and IRIs
|
|
37
|
+
"""
|
|
38
|
+
ontology_iris = self._get_ontology_iris()
|
|
39
|
+
ontologies = [self._get_one_ontology(x) for x in ontology_iris]
|
|
40
|
+
return ontologies, ontology_iris
|
|
41
|
+
|
|
42
|
+
def _get_ontology_iris(self) -> list[str]:
|
|
43
|
+
url = f"{self.api_url}/admin/projects/shortcode/{self.shortcode}"
|
|
44
|
+
timeout = 10
|
|
45
|
+
log_request(RequestParameters("GET", url, timeout=timeout))
|
|
46
|
+
response = requests.get(url=url, timeout=timeout)
|
|
47
|
+
log_response(response)
|
|
48
|
+
if not response.ok:
|
|
49
|
+
raise InternalError(f"Failed Request: {response.status_code} {response.text}")
|
|
50
|
+
response_json = cast(dict[str, Any], response.json())
|
|
51
|
+
if not (ontos := response_json.get("project", {}).get("ontologies")):
|
|
52
|
+
raise InternalError(f"The response from the API does not contain any ontologies.\nResponse:{response.text}")
|
|
53
|
+
output = cast(list[str], ontos)
|
|
54
|
+
return output
|
|
55
|
+
|
|
56
|
+
def _get_one_ontology(self, ontology_iri: str) -> str:
|
|
57
|
+
url = ontology_iri
|
|
58
|
+
headers = {"Accept": "text/turtle"}
|
|
59
|
+
timeout = 30
|
|
60
|
+
log_request(RequestParameters("GET", url, timeout=timeout, headers=headers))
|
|
61
|
+
response = requests.get(url=url, headers=headers, timeout=timeout)
|
|
62
|
+
log_response(response, include_response_content=False)
|
|
63
|
+
if not response.ok:
|
|
64
|
+
raise InternalError(f"Failed Request: {response.status_code} {response.text}")
|
|
65
|
+
return response.text
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from http import HTTPStatus
|
|
3
|
+
from typing import cast
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from dsp_tools.clients.project_client import ProjectInfoClient
|
|
8
|
+
from dsp_tools.error.exceptions import UnexpectedApiResponseError
|
|
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
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class ProjectInfoClientLive(ProjectInfoClient):
|
|
16
|
+
api_url: str
|
|
17
|
+
|
|
18
|
+
def get_project_iri(self, shortcode: str) -> str | None:
|
|
19
|
+
url = f"{self.api_url}/admin/projects/shortcode/{shortcode}"
|
|
20
|
+
timeout = 30
|
|
21
|
+
params = RequestParameters("GET", url, timeout)
|
|
22
|
+
log_request(params)
|
|
23
|
+
response = requests.get(url, timeout=timeout)
|
|
24
|
+
log_response(response)
|
|
25
|
+
if response.ok:
|
|
26
|
+
result = response.json()
|
|
27
|
+
return cast(str, result["project"]["id"])
|
|
28
|
+
if response.status_code == HTTPStatus.NOT_FOUND:
|
|
29
|
+
return None
|
|
30
|
+
raise UnexpectedApiResponseError(f"Encountered unexpected API response with the code {response.status_code}")
|
|
@@ -3,9 +3,10 @@ from typing import Any
|
|
|
3
3
|
from loguru import logger
|
|
4
4
|
from rdflib import Literal
|
|
5
5
|
from rdflib import URIRef
|
|
6
|
+
from tqdm import tqdm
|
|
6
7
|
|
|
7
|
-
from dsp_tools.clients.
|
|
8
|
-
from dsp_tools.clients.
|
|
8
|
+
from dsp_tools.clients.ontology_clients import OntologyCreateClient
|
|
9
|
+
from dsp_tools.clients.ontology_create_client_live import OntologyCreateClientLive
|
|
9
10
|
from dsp_tools.commands.create.models.input_problems import CollectedProblems
|
|
10
11
|
from dsp_tools.commands.create.models.input_problems import CreateProblem
|
|
11
12
|
from dsp_tools.commands.create.models.input_problems import ProblemType
|
|
@@ -25,7 +26,7 @@ def add_all_cardinalities(
|
|
|
25
26
|
ontologies: list[ParsedOntology],
|
|
26
27
|
project_iri_lookup: ProjectIriLookup,
|
|
27
28
|
created_iris: CreatedIriCollection,
|
|
28
|
-
onto_client:
|
|
29
|
+
onto_client: OntologyCreateClientLive,
|
|
29
30
|
) -> CollectedProblems | None:
|
|
30
31
|
all_problems = []
|
|
31
32
|
for onto in ontologies:
|
|
@@ -36,25 +37,30 @@ def add_all_cardinalities(
|
|
|
36
37
|
problems = _add_all_cardinalities_for_one_onto(
|
|
37
38
|
cardinalities=onto.cardinalities,
|
|
38
39
|
onto_iri=URIRef(onto_iri),
|
|
40
|
+
onto_name=onto.name,
|
|
39
41
|
last_modification_date=last_mod_date,
|
|
40
42
|
onto_client=onto_client,
|
|
41
43
|
created_iris=created_iris,
|
|
42
44
|
)
|
|
43
45
|
all_problems.extend(problems)
|
|
44
46
|
if all_problems:
|
|
45
|
-
return CollectedProblems("
|
|
47
|
+
return CollectedProblems(" While adding cardinalities the following problems occurred:", all_problems)
|
|
46
48
|
return None
|
|
47
49
|
|
|
48
50
|
|
|
49
51
|
def _add_all_cardinalities_for_one_onto(
|
|
50
52
|
cardinalities: list[ParsedClassCardinalities],
|
|
51
53
|
onto_iri: URIRef,
|
|
54
|
+
onto_name: str,
|
|
52
55
|
last_modification_date: Literal,
|
|
53
|
-
onto_client:
|
|
56
|
+
onto_client: OntologyCreateClient,
|
|
54
57
|
created_iris: CreatedIriCollection,
|
|
55
58
|
) -> list[CreateProblem]:
|
|
56
59
|
problems: list[CreateProblem] = []
|
|
57
|
-
|
|
60
|
+
progress_bar = tqdm(
|
|
61
|
+
cardinalities, desc=f" Adding cardinalities to the ontology '{onto_name}'", dynamic_ncols=True
|
|
62
|
+
)
|
|
63
|
+
for c in progress_bar:
|
|
58
64
|
# we do not inform about classes failures here, as it will have been done upstream
|
|
59
65
|
if c.class_iri not in created_iris.classes:
|
|
60
66
|
logger.warning(f"CARDINALITY: Class '{c.class_iri}' not in successes, no cardinalities added.")
|
|
@@ -74,7 +80,7 @@ def _add_cardinalities_for_one_class(
|
|
|
74
80
|
resource_card: ParsedClassCardinalities,
|
|
75
81
|
onto_iri: URIRef,
|
|
76
82
|
last_modification_date: Literal,
|
|
77
|
-
onto_client:
|
|
83
|
+
onto_client: OntologyCreateClient,
|
|
78
84
|
successful_props: set[str],
|
|
79
85
|
) -> tuple[Literal, list[UploadProblem]]:
|
|
80
86
|
res_iri = URIRef(resource_card.class_iri)
|
|
@@ -96,7 +102,7 @@ def _add_one_cardinality(
|
|
|
96
102
|
res_iri: URIRef,
|
|
97
103
|
onto_iri: URIRef,
|
|
98
104
|
last_modification_date: Literal,
|
|
99
|
-
onto_client:
|
|
105
|
+
onto_client: OntologyCreateClient,
|
|
100
106
|
) -> tuple[Literal, UploadProblem | None]:
|
|
101
107
|
card_serialised = _serialise_card(card, res_iri, onto_iri, last_modification_date)
|
|
102
108
|
new_mod_date = onto_client.post_resource_cardinalities(card_serialised)
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from loguru import logger
|
|
4
|
+
from tqdm import tqdm
|
|
5
|
+
|
|
6
|
+
from dsp_tools.clients.authentication_client import AuthenticationClient
|
|
7
|
+
from dsp_tools.clients.list_client import ListCreateClient
|
|
8
|
+
from dsp_tools.clients.list_client_live import ListCreateClientLive
|
|
9
|
+
from dsp_tools.clients.list_client_live import ListGetClientLive
|
|
10
|
+
from dsp_tools.commands.create.models.input_problems import CollectedProblems
|
|
11
|
+
from dsp_tools.commands.create.models.input_problems import CreateProblem
|
|
12
|
+
from dsp_tools.commands.create.models.input_problems import ProblemType
|
|
13
|
+
from dsp_tools.commands.create.models.input_problems import UploadProblem
|
|
14
|
+
from dsp_tools.commands.create.models.input_problems import UserInformation
|
|
15
|
+
from dsp_tools.commands.create.models.input_problems import UserInformationMessage
|
|
16
|
+
from dsp_tools.commands.create.models.parsed_project import ParsedList
|
|
17
|
+
from dsp_tools.commands.create.models.parsed_project import ParsedListNode
|
|
18
|
+
from dsp_tools.commands.create.models.parsed_project import ParsedNodeInfo
|
|
19
|
+
from dsp_tools.commands.create.models.server_project_info import ListNameToIriLookup
|
|
20
|
+
from dsp_tools.utils.ansi_colors import BOLD
|
|
21
|
+
from dsp_tools.utils.ansi_colors import BOLD_CYAN
|
|
22
|
+
from dsp_tools.utils.ansi_colors import RESET_TO_DEFAULT
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def create_lists(
|
|
26
|
+
parsed_lists: list[ParsedList], shortcode: str, auth: AuthenticationClient, project_iri: str
|
|
27
|
+
) -> tuple[ListNameToIriLookup, CollectedProblems | None]:
|
|
28
|
+
print(BOLD + "Processing list section:" + RESET_TO_DEFAULT)
|
|
29
|
+
name2iri = get_existing_lists_on_server(shortcode, auth)
|
|
30
|
+
if not parsed_lists:
|
|
31
|
+
return name2iri, None
|
|
32
|
+
lists_to_create, existing_info = _filter_out_existing_lists(parsed_lists, name2iri)
|
|
33
|
+
if existing_info:
|
|
34
|
+
_print_existing_list_info(existing_info)
|
|
35
|
+
if not lists_to_create:
|
|
36
|
+
msg = " All lists defined in the project are already on the server, no list was uploaded."
|
|
37
|
+
logger.warning(msg)
|
|
38
|
+
print(BOLD_CYAN + msg + RESET_TO_DEFAULT)
|
|
39
|
+
return name2iri, None
|
|
40
|
+
|
|
41
|
+
create_client = ListCreateClientLive(auth.server, auth, project_iri)
|
|
42
|
+
|
|
43
|
+
all_problems: list[CreateProblem] = []
|
|
44
|
+
progress_bar = tqdm(lists_to_create, desc=" Creating lists", dynamic_ncols=True)
|
|
45
|
+
for new_lst in progress_bar:
|
|
46
|
+
list_iri, problems = _create_new_list(new_lst, create_client, project_iri)
|
|
47
|
+
if list_iri is None:
|
|
48
|
+
problems.extend(problems)
|
|
49
|
+
else:
|
|
50
|
+
name2iri.add_iri(new_lst.list_info.name, list_iri)
|
|
51
|
+
|
|
52
|
+
create_problems = None
|
|
53
|
+
if all_problems:
|
|
54
|
+
create_problems = CollectedProblems("The following problems occurred during list creation:", all_problems)
|
|
55
|
+
return name2iri, create_problems
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _print_existing_list_info(existing_lists: list[UserInformation]) -> None:
|
|
59
|
+
lists = ", ".join([x.focus_object for x in existing_lists])
|
|
60
|
+
msg = f" The following lists already exist on the server and will be skipped: {lists}"
|
|
61
|
+
logger.info(msg)
|
|
62
|
+
print(BOLD_CYAN + msg + RESET_TO_DEFAULT)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def get_existing_lists_on_server(shortcode: str, auth: AuthenticationClient) -> ListNameToIriLookup:
|
|
66
|
+
client = ListGetClientLive(auth.server, shortcode)
|
|
67
|
+
name2iri_dict = client.get_all_list_iris_and_names()
|
|
68
|
+
return ListNameToIriLookup(name2iri_dict)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _filter_out_existing_lists(
|
|
72
|
+
parsed_lists: list[ParsedList], name2iri: ListNameToIriLookup
|
|
73
|
+
) -> tuple[list[ParsedList], list[UserInformation]]:
|
|
74
|
+
lists_to_create: list[ParsedList] = []
|
|
75
|
+
existing_info: list[UserInformation] = []
|
|
76
|
+
|
|
77
|
+
for parsed_list in parsed_lists:
|
|
78
|
+
if name2iri.check_list_exists(parsed_list.list_info.name):
|
|
79
|
+
existing_info.append(
|
|
80
|
+
UserInformation(parsed_list.list_info.name, UserInformationMessage.LIST_EXISTS_ON_SERVER)
|
|
81
|
+
)
|
|
82
|
+
else:
|
|
83
|
+
lists_to_create.append(parsed_list)
|
|
84
|
+
|
|
85
|
+
return lists_to_create, existing_info
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _create_new_list(
|
|
89
|
+
parsed_list: ParsedList, create_client: ListCreateClient, project_iri: str
|
|
90
|
+
) -> tuple[str | None, list[UploadProblem]]:
|
|
91
|
+
serialised = _serialise_list(parsed_list.list_info, project_iri)
|
|
92
|
+
new_iri = create_client.create_new_list(serialised)
|
|
93
|
+
|
|
94
|
+
if new_iri is None:
|
|
95
|
+
problems: list[UploadProblem] = [
|
|
96
|
+
UploadProblem(parsed_list.list_info.name, ProblemType.LIST_COULD_NOT_BE_CREATED)
|
|
97
|
+
]
|
|
98
|
+
return None, problems
|
|
99
|
+
|
|
100
|
+
node_problems = []
|
|
101
|
+
if parsed_list.children:
|
|
102
|
+
node_problems = _create_node_tree(parsed_list.children, new_iri, create_client, project_iri)
|
|
103
|
+
|
|
104
|
+
return new_iri, node_problems
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _serialise_list(parsed_list_info: ParsedNodeInfo, project_iri: str) -> dict[str, Any]:
|
|
108
|
+
node_dict = {
|
|
109
|
+
"projectIri": project_iri,
|
|
110
|
+
"name": parsed_list_info.name,
|
|
111
|
+
"labels": _convert_to_api_format(parsed_list_info.labels),
|
|
112
|
+
}
|
|
113
|
+
if parsed_list_info.comments:
|
|
114
|
+
node_dict["comments"] = _convert_to_api_format(parsed_list_info.comments)
|
|
115
|
+
return node_dict
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _create_node_tree(
|
|
119
|
+
nodes: list[ParsedListNode], parent_iri: str, create_client: ListCreateClient, project_iri: str
|
|
120
|
+
) -> list[UploadProblem]:
|
|
121
|
+
problems: list[UploadProblem] = []
|
|
122
|
+
|
|
123
|
+
for node in nodes:
|
|
124
|
+
serialised = _serialise_node(node.node_info, parent_iri, project_iri)
|
|
125
|
+
node_iri = create_client.add_list_node(serialised, parent_iri)
|
|
126
|
+
if node_iri is None:
|
|
127
|
+
problems.append(UploadProblem(node.node_info.name, ProblemType.LIST_NODE_COULD_NOT_BE_CREATED))
|
|
128
|
+
continue
|
|
129
|
+
|
|
130
|
+
if node.children:
|
|
131
|
+
child_problems = _create_node_tree(node.children, node_iri, create_client, project_iri)
|
|
132
|
+
problems.extend(child_problems)
|
|
133
|
+
|
|
134
|
+
return problems
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _serialise_node(node_info: ParsedNodeInfo, parent_iri: str, project_iri: str) -> dict[str, Any]:
|
|
138
|
+
node_dict = {
|
|
139
|
+
"parentNodeIri": parent_iri,
|
|
140
|
+
"projectIri": project_iri,
|
|
141
|
+
"name": node_info.name,
|
|
142
|
+
"labels": _convert_to_api_format(node_info.labels),
|
|
143
|
+
}
|
|
144
|
+
if node_info.comments:
|
|
145
|
+
node_dict["comments"] = _convert_to_api_format(node_info.comments)
|
|
146
|
+
return node_dict
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _convert_to_api_format(lang_dict: dict[str, str]) -> list[dict[str, str]]:
|
|
150
|
+
return [{"value": value, "language": lang} for lang, value in lang_dict.items()]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from loguru import logger
|
|
5
|
+
|
|
6
|
+
from dsp_tools.cli.args import ServerCredentials
|
|
7
|
+
from dsp_tools.clients.authentication_client_live import AuthenticationClientLive
|
|
8
|
+
from dsp_tools.clients.project_client_live import ProjectInfoClientLive
|
|
9
|
+
from dsp_tools.commands.create.communicate_problems import print_problem_collection
|
|
10
|
+
from dsp_tools.commands.create.create_on_server.lists import create_lists
|
|
11
|
+
from dsp_tools.commands.create.models.input_problems import CollectedProblems
|
|
12
|
+
from dsp_tools.commands.create.parsing.parse_project import parse_lists_only
|
|
13
|
+
from dsp_tools.error.exceptions import ProjectNotFoundError
|
|
14
|
+
from dsp_tools.utils.ansi_colors import BACKGROUND_BOLD_YELLOW
|
|
15
|
+
from dsp_tools.utils.ansi_colors import RESET_TO_DEFAULT
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def create_lists_only(project_file_as_path_or_parsed: str | Path | dict[str, Any], creds: ServerCredentials) -> bool:
|
|
19
|
+
result = parse_lists_only(project_file_as_path_or_parsed)
|
|
20
|
+
if isinstance(result, CollectedProblems):
|
|
21
|
+
print_problem_collection(result)
|
|
22
|
+
return False
|
|
23
|
+
project_metadata, parsed_lists = result
|
|
24
|
+
|
|
25
|
+
if not parsed_lists:
|
|
26
|
+
msg = "Your file did not contain any lists, therefore no lists were created on the server."
|
|
27
|
+
logger.info(msg)
|
|
28
|
+
print(BACKGROUND_BOLD_YELLOW + msg + RESET_TO_DEFAULT)
|
|
29
|
+
return True
|
|
30
|
+
|
|
31
|
+
project_info = ProjectInfoClientLive(creds.server)
|
|
32
|
+
project_iri = project_info.get_project_iri(project_metadata.shortcode)
|
|
33
|
+
if not project_iri:
|
|
34
|
+
raise ProjectNotFoundError(
|
|
35
|
+
f"This commands adds lists to an existing project. "
|
|
36
|
+
f"The project with the shortcode {project_metadata.shortcode} does not exist on this server. "
|
|
37
|
+
f"If you wish to create an entire project use the `create` command without the flag."
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
auth = AuthenticationClientLive(creds.server, creds.user, creds.password)
|
|
41
|
+
_, problems = create_lists(parsed_lists, project_metadata.shortcode, auth, project_iri)
|
|
42
|
+
if problems:
|
|
43
|
+
print_problem_collection(problems)
|
|
44
|
+
return False
|
|
45
|
+
return True
|
|
@@ -10,6 +10,16 @@ class CollectedProblems:
|
|
|
10
10
|
problems: list[CreateProblem]
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
@dataclass
|
|
14
|
+
class UserInformation:
|
|
15
|
+
focus_object: str
|
|
16
|
+
msg: UserInformationMessage
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class UserInformationMessage(StrEnum):
|
|
20
|
+
LIST_EXISTS_ON_SERVER = "The list already exists on the server, therefore it is skipped entirely."
|
|
21
|
+
|
|
22
|
+
|
|
13
23
|
@dataclass
|
|
14
24
|
class CreateProblem:
|
|
15
25
|
problematic_object: str
|
|
@@ -30,3 +40,6 @@ class ProblemType(StrEnum):
|
|
|
30
40
|
"nor does it belong to one of the project ontologies."
|
|
31
41
|
)
|
|
32
42
|
CARDINALITY_COULD_NOT_BE_ADDED = "The cardinality could not be added."
|
|
43
|
+
DUPLICATE_LIST_NAME = "You have lists in your project with the same name, this is not permitted."
|
|
44
|
+
LIST_COULD_NOT_BE_CREATED = "The list could not be created on the server."
|
|
45
|
+
LIST_NODE_COULD_NOT_BE_CREATED = "The list node could not be created on the server."
|
|
@@ -45,5 +45,18 @@ class ParsedUser:
|
|
|
45
45
|
|
|
46
46
|
@dataclass
|
|
47
47
|
class ParsedList:
|
|
48
|
+
list_info: ParsedNodeInfo
|
|
49
|
+
children: list[ParsedListNode]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class ParsedNodeInfo:
|
|
48
54
|
name: str
|
|
49
|
-
|
|
55
|
+
labels: dict[str, str]
|
|
56
|
+
comments: dict[str, str] | None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class ParsedListNode:
|
|
61
|
+
node_info: ParsedNodeInfo
|
|
62
|
+
children: list[ParsedListNode]
|
|
@@ -6,13 +6,6 @@ from rdflib import Literal
|
|
|
6
6
|
from rdflib import URIRef
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
@dataclass
|
|
10
|
-
class RdfResourceCardinality:
|
|
11
|
-
resource_iri: URIRef
|
|
12
|
-
on_property: URIRef
|
|
13
|
-
cardinality: RdfCardinalityRestriction
|
|
14
|
-
|
|
15
|
-
|
|
16
9
|
@dataclass
|
|
17
10
|
class RdfCardinalityRestriction:
|
|
18
11
|
owl_property: URIRef
|
|
@@ -2,6 +2,7 @@ from dataclasses import dataclass
|
|
|
2
2
|
from dataclasses import field
|
|
3
3
|
|
|
4
4
|
from dsp_tools.commands.create.constants import KNORA_API_STR
|
|
5
|
+
from dsp_tools.error.exceptions import InternalError
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
@dataclass
|
|
@@ -12,9 +13,6 @@ class ProjectIriLookup:
|
|
|
12
13
|
def add_onto(self, name: str, iri: str) -> None:
|
|
13
14
|
self.onto_iris[name] = iri
|
|
14
15
|
|
|
15
|
-
def get_onto_iri(self, name: str) -> str | None:
|
|
16
|
-
return self.onto_iris.get(name)
|
|
17
|
-
|
|
18
16
|
|
|
19
17
|
@dataclass
|
|
20
18
|
class CreatedIriCollection:
|
|
@@ -23,3 +21,19 @@ class CreatedIriCollection:
|
|
|
23
21
|
|
|
24
22
|
def __post_init__(self) -> None:
|
|
25
23
|
self.properties.update({f"{KNORA_API_STR}seqnum", f"{KNORA_API_STR}isPartOf"})
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class ListNameToIriLookup:
|
|
28
|
+
name2iri: dict[str, str]
|
|
29
|
+
|
|
30
|
+
def check_list_exists(self, name: str) -> bool:
|
|
31
|
+
return name in self.name2iri.keys()
|
|
32
|
+
|
|
33
|
+
def add_iri(self, name: str, iri: str) -> None:
|
|
34
|
+
if self.check_list_exists(name):
|
|
35
|
+
raise InternalError(f"List with the name '{name}' already exists in the lookup.")
|
|
36
|
+
self.name2iri[name] = iri
|
|
37
|
+
|
|
38
|
+
def get_iri(self, name: str) -> str | None:
|
|
39
|
+
return self.name2iri.get(name)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from collections import Counter
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from dsp_tools.commands.create.models.input_problems import CollectedProblems
|
|
5
|
+
from dsp_tools.commands.create.models.input_problems import CreateProblem
|
|
6
|
+
from dsp_tools.commands.create.models.input_problems import InputProblem
|
|
7
|
+
from dsp_tools.commands.create.models.input_problems import ProblemType
|
|
8
|
+
from dsp_tools.commands.create.models.parsed_project import ParsedList
|
|
9
|
+
from dsp_tools.commands.create.models.parsed_project import ParsedListNode
|
|
10
|
+
from dsp_tools.commands.create.models.parsed_project import ParsedNodeInfo
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def parse_list_section(lists: list[dict[str, Any]]) -> list[ParsedList] | CollectedProblems:
|
|
14
|
+
list_section = [_parse_one_list(one_list) for one_list in lists]
|
|
15
|
+
list_names = [parsed_list.list_info.name for parsed_list in list_section]
|
|
16
|
+
duplicates = {name for name, count in Counter(list_names).items() if count > 1}
|
|
17
|
+
if duplicates:
|
|
18
|
+
problems: list[CreateProblem] = [
|
|
19
|
+
InputProblem(problematic_object=name, problem=ProblemType.DUPLICATE_LIST_NAME) for name in duplicates
|
|
20
|
+
]
|
|
21
|
+
return CollectedProblems(header="The following problems were found in the list section:", problems=problems)
|
|
22
|
+
return list_section
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _parse_one_list(one_list: dict[str, Any]) -> ParsedList:
|
|
26
|
+
list_info = _parse_node_info(one_list)
|
|
27
|
+
children = _parse_nodes_and_children(one_list["nodes"]) if "nodes" in one_list else []
|
|
28
|
+
return ParsedList(list_info=list_info, children=children)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _parse_node_info(one_node: dict[str, Any]) -> ParsedNodeInfo:
|
|
32
|
+
return ParsedNodeInfo(
|
|
33
|
+
name=one_node["name"],
|
|
34
|
+
labels=one_node["labels"],
|
|
35
|
+
comments=one_node.get("comments"),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _parse_nodes_and_children(list_nodes: list[dict[str, Any]]) -> list[ParsedListNode]:
|
|
40
|
+
parsed_nodes = []
|
|
41
|
+
for node in list_nodes:
|
|
42
|
+
node_info = _parse_node_info(node)
|
|
43
|
+
children = _parse_nodes_and_children(node["nodes"]) if "nodes" in node else []
|
|
44
|
+
parsed_nodes.append(ParsedListNode(node_info=node_info, children=children))
|
|
45
|
+
return parsed_nodes
|
|
@@ -11,12 +11,25 @@ from dsp_tools.commands.create.models.parsed_project import ParsedPermissions
|
|
|
11
11
|
from dsp_tools.commands.create.models.parsed_project import ParsedProject
|
|
12
12
|
from dsp_tools.commands.create.models.parsed_project import ParsedProjectMetadata
|
|
13
13
|
from dsp_tools.commands.create.models.parsed_project import ParsedUser
|
|
14
|
+
from dsp_tools.commands.create.parsing.parse_lists import parse_list_section
|
|
14
15
|
from dsp_tools.commands.create.parsing.parse_ontology import parse_ontology
|
|
15
16
|
from dsp_tools.commands.create.parsing.parsing_utils import create_prefix_lookup
|
|
16
17
|
from dsp_tools.commands.project.create.project_validate import validate_project
|
|
17
18
|
from dsp_tools.utils.json_parsing import parse_json_input
|
|
18
19
|
|
|
19
20
|
|
|
21
|
+
def parse_lists_only(
|
|
22
|
+
project_file_as_path_or_parsed: str | Path | dict[str, Any],
|
|
23
|
+
) -> tuple[ParsedProjectMetadata, list[ParsedList]] | CollectedProblems:
|
|
24
|
+
complete_json = _parse_and_validate(project_file_as_path_or_parsed)
|
|
25
|
+
project_json = complete_json["project"]
|
|
26
|
+
project_metadata = _parse_metadata(project_json)
|
|
27
|
+
parsed_lists, problems = _parse_lists(project_json)
|
|
28
|
+
if isinstance(problems, CollectedProblems):
|
|
29
|
+
return problems
|
|
30
|
+
return project_metadata, parsed_lists
|
|
31
|
+
|
|
32
|
+
|
|
20
33
|
def parse_project(
|
|
21
34
|
project_file_as_path_or_parsed: str | Path | dict[str, Any], api_url: str
|
|
22
35
|
) -> ParsedProject | list[CollectedProblems]:
|
|
@@ -28,6 +41,9 @@ def _parse_project(complete_json: dict[str, Any], api_url: str) -> ParsedProject
|
|
|
28
41
|
prefix_lookup = create_prefix_lookup(complete_json, api_url)
|
|
29
42
|
project_json = complete_json["project"]
|
|
30
43
|
ontologies, failures = _parse_all_ontologies(project_json, prefix_lookup)
|
|
44
|
+
parsed_lists, problems = _parse_lists(project_json)
|
|
45
|
+
if isinstance(problems, CollectedProblems):
|
|
46
|
+
failures.append(problems)
|
|
31
47
|
if failures:
|
|
32
48
|
return failures
|
|
33
49
|
return ParsedProject(
|
|
@@ -36,7 +52,7 @@ def _parse_project(complete_json: dict[str, Any], api_url: str) -> ParsedProject
|
|
|
36
52
|
permissions=_parse_permissions(project_json),
|
|
37
53
|
groups=_parse_groups(project_json),
|
|
38
54
|
users=_parse_users(project_json),
|
|
39
|
-
lists=
|
|
55
|
+
lists=parsed_lists,
|
|
40
56
|
ontologies=ontologies,
|
|
41
57
|
)
|
|
42
58
|
|
|
@@ -79,10 +95,13 @@ def _parse_users(project_json: dict[str, Any]) -> list[ParsedUser]:
|
|
|
79
95
|
return [ParsedUser(x) for x in found]
|
|
80
96
|
|
|
81
97
|
|
|
82
|
-
def _parse_lists(project_json: dict[str, Any]) -> list[ParsedList]:
|
|
98
|
+
def _parse_lists(project_json: dict[str, Any]) -> tuple[list[ParsedList], CollectedProblems | None]:
|
|
83
99
|
if not (found := project_json.get("lists")):
|
|
84
|
-
return []
|
|
85
|
-
|
|
100
|
+
return [], None
|
|
101
|
+
result = parse_list_section(found)
|
|
102
|
+
if isinstance(result, CollectedProblems):
|
|
103
|
+
return [], result
|
|
104
|
+
return result, None
|
|
86
105
|
|
|
87
106
|
|
|
88
107
|
def _parse_all_ontologies(
|
|
@@ -43,6 +43,7 @@ def ingest_xmlupload(
|
|
|
43
43
|
skip_validation: bool = False,
|
|
44
44
|
skip_ontology_validation: bool = False,
|
|
45
45
|
id2iri_replacement_file: str | None = None,
|
|
46
|
+
do_not_request_resource_metadata_from_db: bool = False,
|
|
46
47
|
) -> bool:
|
|
47
48
|
"""
|
|
48
49
|
This function reads an XML file
|
|
@@ -59,6 +60,8 @@ def ingest_xmlupload(
|
|
|
59
60
|
skip_validation: skip the SHACL validation
|
|
60
61
|
skip_ontology_validation: skip the ontology validation
|
|
61
62
|
id2iri_replacement_file: to replace internal IDs of an XML file by IRIs provided in this mapping file
|
|
63
|
+
do_not_request_resource_metadata_from_db: if true do not request metadata information from the api
|
|
64
|
+
for existing resources
|
|
62
65
|
|
|
63
66
|
Returns:
|
|
64
67
|
True if all resources could be uploaded without errors; False if one of the resources could not be
|
|
@@ -115,6 +118,7 @@ def ingest_xmlupload(
|
|
|
115
118
|
ignore_duplicate_files_warning=True,
|
|
116
119
|
is_on_prod_server=is_on_prod_like_server,
|
|
117
120
|
skip_ontology_validation=skip_ontology_validation,
|
|
121
|
+
do_not_request_resource_metadata_from_db=do_not_request_resource_metadata_from_db,
|
|
118
122
|
),
|
|
119
123
|
auth=auth,
|
|
120
124
|
)
|