dsp-tools 17.0.0.post29__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.

Files changed (38) hide show
  1. dsp_tools/cli/args.py +13 -0
  2. dsp_tools/cli/call_action.py +34 -330
  3. dsp_tools/cli/call_action_files_only.py +74 -0
  4. dsp_tools/cli/call_action_with_network.py +203 -0
  5. dsp_tools/cli/create_parsers.py +50 -11
  6. dsp_tools/cli/utils.py +87 -0
  7. dsp_tools/clients/list_client.py +49 -0
  8. dsp_tools/clients/list_client_live.py +157 -0
  9. dsp_tools/clients/{ontology_client.py → ontology_clients.py} +17 -2
  10. dsp_tools/clients/{ontology_client_live.py → ontology_create_client_live.py} +2 -2
  11. dsp_tools/clients/ontology_get_client_live.py +65 -0
  12. dsp_tools/clients/project_client.py +10 -0
  13. dsp_tools/clients/project_client_live.py +30 -0
  14. dsp_tools/commands/create/create_on_server/cardinalities.py +14 -8
  15. dsp_tools/commands/create/create_on_server/lists.py +150 -0
  16. dsp_tools/commands/create/lists_only.py +45 -0
  17. dsp_tools/commands/create/models/input_problems.py +13 -0
  18. dsp_tools/commands/create/models/parsed_project.py +14 -1
  19. dsp_tools/commands/create/models/rdf_ontology.py +0 -7
  20. dsp_tools/commands/create/models/server_project_info.py +17 -3
  21. dsp_tools/commands/create/parsing/parse_lists.py +45 -0
  22. dsp_tools/commands/create/parsing/parse_project.py +23 -4
  23. dsp_tools/commands/project/create/project_create_all.py +17 -13
  24. dsp_tools/commands/project/create/project_create_default_permissions.py +8 -6
  25. dsp_tools/commands/project/create/project_create_ontologies.py +30 -18
  26. dsp_tools/commands/project/legacy_models/listnode.py +0 -30
  27. dsp_tools/commands/validate_data/models/api_responses.py +2 -16
  28. dsp_tools/commands/validate_data/prepare_data/prepare_data.py +8 -7
  29. dsp_tools/commands/validate_data/sparql/value_shacl.py +1 -1
  30. dsp_tools/error/exceptions.py +8 -0
  31. dsp_tools/resources/start-stack/docker-compose.yml +23 -23
  32. dsp_tools/utils/ansi_colors.py +2 -0
  33. {dsp_tools-17.0.0.post29.dist-info → dsp_tools-18.0.0.dist-info}/METADATA +1 -1
  34. {dsp_tools-17.0.0.post29.dist-info → dsp_tools-18.0.0.dist-info}/RECORD +36 -27
  35. {dsp_tools-17.0.0.post29.dist-info → dsp_tools-18.0.0.dist-info}/WHEEL +1 -1
  36. dsp_tools/commands/project/create/project_create_lists.py +0 -200
  37. dsp_tools/commands/validate_data/api_clients.py +0 -124
  38. {dsp_tools-17.0.0.post29.dist-info → dsp_tools-18.0.0.dist-info}/entry_points.txt +0 -0
@@ -1,200 +0,0 @@
1
- from typing import Any
2
- from typing import Optional
3
- from typing import Union
4
-
5
- from loguru import logger
6
-
7
- from dsp_tools.cli.args import ServerCredentials
8
- from dsp_tools.clients.authentication_client_live import AuthenticationClientLive
9
- from dsp_tools.clients.connection import Connection
10
- from dsp_tools.clients.connection_live import ConnectionLive
11
- from dsp_tools.commands.project.create.project_validate import validate_project
12
- from dsp_tools.commands.project.legacy_models.listnode import ListNode
13
- from dsp_tools.commands.project.legacy_models.project import Project
14
- from dsp_tools.error.exceptions import BaseError
15
- from dsp_tools.error.exceptions import InputError
16
- from dsp_tools.utils.json_parsing import parse_json_input
17
-
18
-
19
- def create_lists_on_server(
20
- lists_to_create: list[dict[str, Any]],
21
- con: Connection,
22
- project_remote: Project,
23
- ) -> tuple[dict[str, Any], bool]:
24
- """
25
- Creates the "lists" section of a JSON project definition on a DSP server.
26
- If a list with the same name is already existing in this project on the DSP server, this list is skipped.
27
- If a node or an entire list cannot be created, an error message is printed, but the process continues.
28
-
29
- Args:
30
- lists_to_create: "lists" section of a JSON project definition
31
- con: connection to the DSP server
32
- project_remote: representation of the project on the DSP server
33
-
34
- Raises:
35
- BaseError: if one of the lists to be created already exists on the DSP server, but it has no name
36
-
37
- Returns:
38
- tuple consisting of the IRIs of the list nodes and the success status (True if everything went well)
39
- """
40
-
41
- overall_success = True
42
-
43
- # retrieve existing lists
44
- try:
45
- existing_lists = ListNode.getAllLists(con=con, project_iri=project_remote.iri)
46
- except BaseError:
47
- err_msg = "Unable to retrieve existing lists on DSP server. Cannot check if your lists are already existing."
48
- print(f"WARNING: {err_msg}")
49
- logger.exception(err_msg)
50
- existing_lists = []
51
- overall_success = False
52
-
53
- current_project_lists: dict[str, Any] = {}
54
- for new_lst in lists_to_create:
55
- if existing_lst := [x for x in existing_lists if x.project == project_remote.iri and x.name == new_lst["name"]]:
56
- existing_list_name = existing_lst[0].name
57
- if not existing_list_name:
58
- raise BaseError(f"Node {existing_lst[0]} has no name.")
59
- current_project_lists[existing_list_name] = {
60
- "id": existing_lst[0].iri,
61
- "nodes": new_lst["nodes"],
62
- }
63
- print(f" WARNING: List '{new_lst['name']}' already exists on the DSP server. Skipping...")
64
- overall_success = False
65
- continue
66
-
67
- created_list, success = _create_list_node(con=con, project=project_remote, node=new_lst)
68
- current_project_lists.update(created_list)
69
- if not success:
70
- overall_success = False
71
- print(f" Created list '{new_lst['name']}'.")
72
-
73
- return current_project_lists, overall_success
74
-
75
-
76
- def _create_list_node(
77
- con: Connection,
78
- project: Project,
79
- node: dict[str, Any],
80
- parent_node: Optional[ListNode] = None,
81
- ) -> tuple[dict[str, Any], bool]:
82
- """
83
- Creates a list node on the DSP server, recursively scanning through all its subnodes, creating them as well.
84
- If a node cannot be created, an error message is printed, but the process continues.
85
-
86
- Args:
87
- con: connection to the DSP server
88
- project: project that holds the list where this node should be added to
89
- node: the node to be created
90
- parent_node: parent node of the node to be created (optional)
91
-
92
- Returns:
93
- Returns a tuple consisting of a dict and a bool.
94
- The dict contains the IRIs of the created list nodes,
95
- nested according to their hierarchy structure,
96
- i.e. ``{nodename: {"id": IRI, "nodes": {...}}}``.
97
- The bool is True if all nodes could be created,
98
- False if any node could not be created.
99
-
100
- Raises:
101
- BaseError: if the created node has no name
102
- """
103
- new_node: ListNode = ListNode(
104
- con=con,
105
- project=project,
106
- label=node["labels"],
107
- comments=node.get("comments"),
108
- name=node["name"],
109
- parent=parent_node,
110
- )
111
- try:
112
- new_node = new_node.create()
113
- except BaseError:
114
- print(f"WARNING: Cannot create list node '{node['name']}'.")
115
- logger.exception(f"Cannot create list node '{node['name']}'.")
116
- return {}, False
117
-
118
- # if node has child nodes, call the method recursively
119
- if node.get("nodes"):
120
- overall_success = True
121
- subnode_list = []
122
- for subnode in node["nodes"]:
123
- created_subnode, success = _create_list_node(con=con, project=project, node=subnode, parent_node=new_node)
124
- subnode_list.append(created_subnode)
125
- if not success:
126
- overall_success = False
127
- if not new_node.name:
128
- raise BaseError(f"Node {new_node} has no name.")
129
- return {new_node.name: {"id": new_node.iri, "nodes": subnode_list}}, overall_success
130
- else:
131
- if not new_node.name:
132
- raise BaseError(f"Node {new_node} has no name.")
133
- return {new_node.name: {"id": new_node.iri}}, True
134
-
135
-
136
- def create_only_lists(
137
- project_file_as_path_or_parsed: Union[str, dict[str, Any]],
138
- creds: ServerCredentials,
139
- ) -> tuple[dict[str, Any], bool]:
140
- """
141
- This function accepts a JSON project definition,
142
- connects to a DSP server,
143
- and uploads the "lists" section to the server.
144
-
145
- The project must already exist on the DSP server.
146
- If a list with the same name is already existing in this project on the DSP server, this list is skipped.
147
-
148
- Args:
149
- project_file_as_path_or_parsed: path to the JSON project definition, or parsed JSON object
150
- creds: credentials to connect to the DSP server
151
-
152
- Raises:
153
- InputError:
154
- - if the project cannot be read from the server
155
- - if the connection to the DSP server cannot be established
156
-
157
- BaseError:
158
- - if the input is invalid
159
- - if a problem occurred while trying to expand the Excel files
160
- - if the JSON file is invalid according to the schema
161
-
162
- Returns:
163
- Returns a tuple consisting of a dict and a bool.
164
- The dict contains the IRIs of the created list nodes,
165
- nested according to their hierarchy structure,
166
- i.e. ``{nodename: {"id": IRI, "nodes": {...}}}``.
167
- If there are no lists in the project definition,
168
- an empty dictionary is returned.
169
- The bool indicates if everything went smoothly during the process.
170
- If a warning or error occurred (e.g. one of the lists already exists,
171
- or one of the nodes could not be created),
172
- it is False.
173
- """
174
- project_definition = parse_json_input(project_file_as_path_or_parsed=project_file_as_path_or_parsed)
175
- if not project_definition.get("project", {}).get("lists"):
176
- return {}, True
177
- validate_project(project_definition)
178
- print("JSON project file is syntactically correct and passed validation.")
179
-
180
- auth = AuthenticationClientLive(creds.server, creds.user, creds.password)
181
- con = ConnectionLive(creds.server, auth)
182
-
183
- # retrieve the project
184
- shortcode = project_definition["project"]["shortcode"]
185
- project_local = Project(con=con, shortcode=shortcode)
186
- try:
187
- project_remote = project_local.read()
188
- except BaseError:
189
- err_msg = f"Unable to create the lists: The project {shortcode} cannot be found on the DSP server."
190
- logger.exception(err_msg)
191
- raise InputError(err_msg) from None
192
-
193
- # create new lists
194
- current_project_lists, success = create_lists_on_server(
195
- lists_to_create=project_definition["project"]["lists"],
196
- con=con,
197
- project_remote=project_remote,
198
- )
199
-
200
- return current_project_lists, success
@@ -1,124 +0,0 @@
1
- from dataclasses import dataclass
2
- from typing import Any
3
- from typing import cast
4
- from urllib.parse import quote_plus
5
-
6
- import requests
7
-
8
- from dsp_tools.commands.validate_data.models.api_responses import OneList
9
- from dsp_tools.commands.validate_data.models.api_responses import OneNode
10
- from dsp_tools.error.exceptions import InternalError
11
- from dsp_tools.utils.request_utils import RequestParameters
12
- from dsp_tools.utils.request_utils import log_request
13
- from dsp_tools.utils.request_utils import log_response
14
-
15
-
16
- @dataclass
17
- class OntologyClient:
18
- api_url: str
19
- shortcode: str
20
-
21
- def get_knora_api(self) -> str:
22
- url = f"{self.api_url}/ontology/knora-api/v2#"
23
- headers = {"Accept": "text/turtle"}
24
- timeout = 60
25
- log_request(RequestParameters("GET", url, timeout=timeout, headers=headers))
26
- response = requests.get(url=url, headers=headers, timeout=timeout)
27
- log_response(response, include_response_content=False)
28
- if not response.ok:
29
- raise InternalError(f"Failed Request: {response.status_code} {response.text}")
30
- return response.text
31
-
32
- def get_ontologies(self) -> tuple[list[str], list[str]]:
33
- """
34
- Returns a list of project ontologies as a string in turtle format.
35
- And a list of the ontology IRIs
36
-
37
- Returns:
38
- list of ontologies and IRIs
39
- """
40
- ontology_iris = self._get_ontology_iris()
41
- ontologies = [self._get_one_ontology(x) for x in ontology_iris]
42
- return ontologies, ontology_iris
43
-
44
- def _get_ontology_iris(self) -> list[str]:
45
- url = f"{self.api_url}/admin/projects/shortcode/{self.shortcode}"
46
- timeout = 10
47
- log_request(RequestParameters("GET", url, timeout=timeout))
48
- response = requests.get(url=url, timeout=timeout)
49
- log_response(response)
50
- if not response.ok:
51
- raise InternalError(f"Failed Request: {response.status_code} {response.text}")
52
- response_json = cast(dict[str, Any], response.json())
53
- if not (ontos := response_json.get("project", {}).get("ontologies")):
54
- raise InternalError(f"The response from the API does not contain any ontologies.\nResponse:{response.text}")
55
- output = cast(list[str], ontos)
56
- return output
57
-
58
- def _get_one_ontology(self, ontology_iri: str) -> str:
59
- url = ontology_iri
60
- headers = {"Accept": "text/turtle"}
61
- timeout = 30
62
- log_request(RequestParameters("GET", url, timeout=timeout, headers=headers))
63
- response = requests.get(url=url, headers=headers, timeout=timeout)
64
- log_response(response, include_response_content=False)
65
- if not response.ok:
66
- raise InternalError(f"Failed Request: {response.status_code} {response.text}")
67
- return response.text
68
-
69
-
70
- @dataclass
71
- class ListClient:
72
- """Client to request and reformat the lists of a project."""
73
-
74
- api_url: str
75
- shortcode: str
76
-
77
- def get_lists(self) -> list[OneList]:
78
- list_json = self._get_all_list_iris()
79
- all_iris = self._extract_list_iris(list_json)
80
- all_lists = [self._get_one_list(iri) for iri in all_iris]
81
- return [self._reformat_one_list(lst) for lst in all_lists]
82
-
83
- def _get_all_list_iris(self) -> dict[str, Any]:
84
- url = f"{self.api_url}/admin/lists?projectShortcode={self.shortcode}"
85
- timeout = 10
86
- log_request(RequestParameters("GET", url, timeout))
87
- response = requests.get(url=url, timeout=timeout)
88
- log_response(response)
89
- if not response.ok:
90
- raise InternalError(f"Failed Request: {response.status_code} {response.text}")
91
- json_response = cast(dict[str, Any], response.json())
92
- return json_response
93
-
94
- def _extract_list_iris(self, response_json: dict[str, Any]) -> list[str]:
95
- return [x["id"] for x in response_json["lists"]]
96
-
97
- def _get_one_list(self, list_iri: str) -> dict[str, Any]:
98
- encoded_list_iri = quote_plus(list_iri)
99
- url = f"{self.api_url}/admin/lists/{encoded_list_iri}"
100
- timeout = 30
101
- log_request(RequestParameters("GET", url, timeout))
102
- response = requests.get(url=url, timeout=timeout)
103
- log_response(response, include_response_content=False)
104
- if not response.ok:
105
- raise InternalError(f"Failed Request: {response.status_code} {response.text}")
106
- response_json = cast(dict[str, Any], response.json())
107
- return response_json
108
-
109
- def _reformat_one_list(self, response_json: dict[str, Any]) -> OneList:
110
- list_name = response_json["list"]["listinfo"]["name"]
111
- list_id = response_json["list"]["listinfo"]["id"]
112
- nodes = response_json["list"]["children"]
113
- all_nodes = []
114
- for child in nodes:
115
- all_nodes.append(OneNode(child["name"], child["id"]))
116
- if node_child := child.get("children"):
117
- self._reformat_children(node_child, all_nodes)
118
- return OneList(list_iri=list_id, list_name=list_name, nodes=all_nodes)
119
-
120
- def _reformat_children(self, list_child: list[dict[str, Any]], current_nodes: list[OneNode]) -> None:
121
- for child in list_child:
122
- current_nodes.append(OneNode(child["name"], child["id"]))
123
- if grand_child := child.get("children"):
124
- self._reformat_children(grand_child, current_nodes)