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
dsp_tools/cli/args.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
+ from dataclasses import field
4
5
  from enum import Enum
5
6
  from pathlib import Path
6
7
 
@@ -32,3 +33,15 @@ class ValidationSeverity(Enum):
32
33
  ERROR = 3
33
34
  WARNING = 2
34
35
  INFO = 1
36
+
37
+
38
+ @dataclass
39
+ class PathDependencies:
40
+ required_files: list[Path] = field(default_factory=list)
41
+ required_directories: list[Path] = field(default_factory=list)
42
+
43
+
44
+ @dataclass
45
+ class NetworkRequirements:
46
+ api_url: str
47
+ always_requires_docker: bool = False
@@ -1,41 +1,24 @@
1
1
  import argparse
2
- import subprocess
3
- from pathlib import Path
4
2
 
5
- import requests
6
3
  from loguru import logger
7
4
 
8
- from dsp_tools.cli.args import ServerCredentials
9
- from dsp_tools.cli.args import ValidationSeverity
10
- from dsp_tools.commands.excel2json.lists.make_lists import excel2lists
11
- from dsp_tools.commands.excel2json.old_lists import old_excel2lists
12
- from dsp_tools.commands.excel2json.old_lists import validate_lists_section_with_schema
13
- from dsp_tools.commands.excel2json.project import excel2json
14
- from dsp_tools.commands.excel2json.project import old_excel2json
15
- from dsp_tools.commands.excel2json.properties import excel2properties
16
- from dsp_tools.commands.excel2json.resources import excel2resources
17
- from dsp_tools.commands.id2iri import id2iri
18
- from dsp_tools.commands.ingest_xmlupload.create_resources.upload_xml import ingest_xmlupload
19
- from dsp_tools.commands.ingest_xmlupload.ingest_files.ingest_files import ingest_files
20
- from dsp_tools.commands.ingest_xmlupload.upload_files.upload_files import upload_files
21
- from dsp_tools.commands.project.create.project_create_all import create_project
22
- from dsp_tools.commands.project.create.project_create_lists import create_only_lists
23
- from dsp_tools.commands.project.create.project_validate import validate_project
24
- from dsp_tools.commands.project.get.get import get_project
25
- from dsp_tools.commands.resume_xmlupload.resume_xmlupload import resume_xmlupload
26
- from dsp_tools.commands.start_stack import StackConfiguration
27
- from dsp_tools.commands.start_stack import StackHandler
28
- from dsp_tools.commands.validate_data.validate_data import validate_data
29
- from dsp_tools.commands.xmlupload.upload_config import UploadConfig
30
- from dsp_tools.commands.xmlupload.xmlupload import xmlupload
31
- from dsp_tools.error.exceptions import DockerNotReachableError
32
- from dsp_tools.error.exceptions import DspApiNotReachableError
33
- from dsp_tools.error.exceptions import InputError
34
- from dsp_tools.error.exceptions import UserDirectoryNotFoundError
35
- from dsp_tools.error.exceptions import UserFilepathNotFoundError
36
- from dsp_tools.utils.xml_parsing.parse_clean_validate_xml import parse_and_validate_xml_file
37
-
38
- LOCALHOST_API = "http://0.0.0.0:3333"
5
+ from dsp_tools.cli.call_action_files_only import call_excel2json
6
+ from dsp_tools.cli.call_action_files_only import call_excel2lists
7
+ from dsp_tools.cli.call_action_files_only import call_excel2properties
8
+ from dsp_tools.cli.call_action_files_only import call_excel2resources
9
+ from dsp_tools.cli.call_action_files_only import call_id2iri
10
+ from dsp_tools.cli.call_action_files_only import call_old_excel2json
11
+ from dsp_tools.cli.call_action_files_only import call_old_excel2lists
12
+ from dsp_tools.cli.call_action_with_network import call_create
13
+ from dsp_tools.cli.call_action_with_network import call_get
14
+ from dsp_tools.cli.call_action_with_network import call_ingest_files
15
+ from dsp_tools.cli.call_action_with_network import call_ingest_xmlupload
16
+ from dsp_tools.cli.call_action_with_network import call_resume_xmlupload
17
+ from dsp_tools.cli.call_action_with_network import call_start_stack
18
+ from dsp_tools.cli.call_action_with_network import call_stop_stack
19
+ from dsp_tools.cli.call_action_with_network import call_upload_files
20
+ from dsp_tools.cli.call_action_with_network import call_validate_data
21
+ from dsp_tools.cli.call_action_with_network import call_xmlupload
39
22
 
40
23
 
41
24
  def call_requested_action(args: argparse.Namespace) -> bool: # noqa: PLR0912 (too many branches)
@@ -58,321 +41,42 @@ def call_requested_action(args: argparse.Namespace) -> bool: # noqa: PLR0912 (t
58
41
  match args.action:
59
42
  # commands with Docker / API interactions
60
43
  case "start-stack":
61
- result = _call_start_stack(args)
44
+ result = call_start_stack(args)
62
45
  case "stop-stack":
63
- result = _call_stop_stack()
46
+ result = call_stop_stack()
64
47
  case "create":
65
- result = _call_create(args)
48
+ result = call_create(args)
66
49
  case "get":
67
- result = _call_get(args)
50
+ result = call_get(args)
68
51
  case "validate-data":
69
- result = _call_validate_data(args)
52
+ result = call_validate_data(args)
70
53
  case "xmlupload":
71
- result = _call_xmlupload(args)
54
+ result = call_xmlupload(args)
72
55
  case "resume-xmlupload":
73
- result = _call_resume_xmlupload(args)
56
+ result = call_resume_xmlupload(args)
74
57
  case "upload-files":
75
- result = _call_upload_files(args)
58
+ result = call_upload_files(args)
76
59
  case "ingest-files":
77
- result = _call_ingest_files(args)
60
+ result = call_ingest_files(args)
78
61
  case "ingest-xmlupload":
79
- result = _call_ingest_xmlupload(args)
62
+ result = call_ingest_xmlupload(args)
80
63
  # commands that do not require docker
81
64
  case "excel2json":
82
- result = _call_excel2json(args)
65
+ result = call_excel2json(args)
83
66
  case "old-excel2json":
84
- result = _call_old_excel2json(args)
67
+ result = call_old_excel2json(args)
85
68
  case "excel2lists":
86
- result = _call_excel2lists(args)
69
+ result = call_excel2lists(args)
87
70
  case "old-excel2lists":
88
- result = _call_old_excel2lists(args)
71
+ result = call_old_excel2lists(args)
89
72
  case "excel2resources":
90
- result = _call_excel2resources(args)
73
+ result = call_excel2resources(args)
91
74
  case "excel2properties":
92
- result = _call_excel2properties(args)
75
+ result = call_excel2properties(args)
93
76
  case "id2iri":
94
- result = _call_id2iri(args)
77
+ result = call_id2iri(args)
95
78
  case _:
96
79
  print(f"ERROR: Unknown action '{args.action}'")
97
80
  logger.error(f"Unknown action '{args.action}'")
98
81
  result = False
99
82
  return result
100
-
101
-
102
- def _call_stop_stack() -> bool:
103
- _check_docker_health()
104
- stack_handler = StackHandler(StackConfiguration())
105
- return stack_handler.stop_stack()
106
-
107
-
108
- def _call_start_stack(args: argparse.Namespace) -> bool:
109
- _check_docker_health()
110
- stack_handler = StackHandler(
111
- StackConfiguration(
112
- max_file_size=args.max_file_size,
113
- enforce_docker_system_prune=args.prune,
114
- suppress_docker_system_prune=args.no_prune,
115
- latest_dev_version=args.latest,
116
- upload_test_data=args.with_test_data,
117
- custom_host=args.custom_host,
118
- )
119
- )
120
- return stack_handler.start_stack()
121
-
122
-
123
- def _call_id2iri(args: argparse.Namespace) -> bool:
124
- _check_filepath_exists(Path(args.xmlfile))
125
- _check_filepath_exists(Path(args.mapping))
126
- return id2iri(
127
- xml_file=args.xmlfile,
128
- json_file=args.mapping,
129
- remove_resource_if_id_in_mapping=args.remove_resources,
130
- )
131
-
132
-
133
- def _call_excel2properties(args: argparse.Namespace) -> bool:
134
- _check_filepath_exists(Path(args.excelfile))
135
- _, _, success = excel2properties(
136
- excelfile=args.excelfile,
137
- path_to_output_file=args.properties_section,
138
- )
139
- return success
140
-
141
-
142
- def _call_excel2resources(args: argparse.Namespace) -> bool:
143
- _check_filepath_exists(Path(args.excelfile))
144
- _, _, success = excel2resources(
145
- excelfile=args.excelfile,
146
- path_to_output_file=args.resources_section,
147
- )
148
- return success
149
-
150
-
151
- def _call_old_excel2lists(args: argparse.Namespace) -> bool:
152
- _check_directory_exists(Path(args.excelfolder))
153
- _, success = old_excel2lists(
154
- excelfolder=args.excelfolder,
155
- path_to_output_file=args.lists_section,
156
- verbose=args.verbose,
157
- )
158
- return success
159
-
160
-
161
- def _call_excel2lists(args: argparse.Namespace) -> bool:
162
- _check_directory_exists(Path(args.excelfolder))
163
- _, success = excel2lists(
164
- excelfolder=args.excelfolder,
165
- path_to_output_file=args.lists_section,
166
- )
167
- return success
168
-
169
-
170
- def _call_excel2json(args: argparse.Namespace) -> bool:
171
- _check_directory_exists(Path(args.excelfolder))
172
- return excel2json(
173
- data_model_files=args.excelfolder,
174
- path_to_output_file=args.project_definition,
175
- )
176
-
177
-
178
- def _call_old_excel2json(args: argparse.Namespace) -> bool:
179
- _check_directory_exists(Path(args.excelfolder))
180
- return old_excel2json(
181
- data_model_files=args.excelfolder,
182
- path_to_output_file=args.project_definition,
183
- )
184
-
185
-
186
- def _call_upload_files(args: argparse.Namespace) -> bool:
187
- _check_health_with_docker_on_localhost(args.server)
188
- xml_path = Path(args.xml_file)
189
- _check_filepath_exists(xml_path)
190
- return upload_files(
191
- xml_file=xml_path,
192
- creds=_get_creds(args),
193
- imgdir=Path(args.imgdir),
194
- )
195
-
196
-
197
- def _call_ingest_files(args: argparse.Namespace) -> bool:
198
- _check_health_with_docker_on_localhost(args.server)
199
- return ingest_files(creds=_get_creds(args), shortcode=args.shortcode)
200
-
201
-
202
- def _call_ingest_xmlupload(args: argparse.Namespace) -> bool:
203
- _check_health_with_docker(args.server)
204
- xml_path = Path(args.xml_file)
205
- _check_filepath_exists(xml_path)
206
- interrupt_after = args.interrupt_after if args.interrupt_after > 0 else None
207
- return ingest_xmlupload(
208
- xml_file=xml_path,
209
- creds=_get_creds(args),
210
- interrupt_after=interrupt_after,
211
- skip_validation=args.skip_validation,
212
- skip_ontology_validation=args.skip_ontology_validation,
213
- id2iri_replacement_file=args.id2iri_replacement_with_file,
214
- do_not_request_resource_metadata_from_db=args.do_not_request_resource_metadata_from_db,
215
- )
216
-
217
-
218
- def _call_xmlupload(args: argparse.Namespace) -> bool:
219
- _check_health_with_docker(args.server)
220
- xml_path = Path(args.xmlfile)
221
- _check_filepath_exists(xml_path)
222
- id_2_iri_file = args.id2iri_replacement_with_file
223
- if id_2_iri_file:
224
- _check_filepath_exists(Path(id_2_iri_file))
225
- if args.validate_only:
226
- success = parse_and_validate_xml_file(xml_path)
227
- print("The XML file is syntactically correct.")
228
- return success
229
- else:
230
- interrupt_after = args.interrupt_after if args.interrupt_after > 0 else None
231
- match args.validation_severity:
232
- case "info":
233
- severity = ValidationSeverity.INFO
234
- case "warning":
235
- severity = ValidationSeverity.WARNING
236
- case "error":
237
- severity = ValidationSeverity.ERROR
238
- case _:
239
- raise InputError(
240
- f"The entered validation severity '{args.validation_severity}' "
241
- f"is not part of the allowed values: info, warning, error."
242
- )
243
- return xmlupload(
244
- input_file=xml_path,
245
- creds=_get_creds(args),
246
- imgdir=args.imgdir,
247
- config=UploadConfig(
248
- interrupt_after=interrupt_after,
249
- skip_iiif_validation=args.no_iiif_uri_validation,
250
- skip_validation=args.skip_validation,
251
- ignore_duplicate_files_warning=args.ignore_duplicate_files_warning,
252
- validation_severity=severity,
253
- skip_ontology_validation=args.skip_ontology_validation,
254
- do_not_request_resource_metadata_from_db=args.do_not_request_resource_metadata_from_db,
255
- id2iri_replacement_file=id_2_iri_file,
256
- ),
257
- )
258
-
259
-
260
- def _call_validate_data(args: argparse.Namespace) -> bool:
261
- _check_health_with_docker(args.server)
262
- xml_path = Path(args.xmlfile)
263
- _check_filepath_exists(xml_path)
264
- return validate_data(
265
- filepath=xml_path,
266
- creds=_get_creds(args),
267
- save_graphs=args.save_graphs,
268
- ignore_duplicate_files_warning=args.ignore_duplicate_files_warning,
269
- skip_ontology_validation=args.skip_ontology_validation,
270
- id2iri_replacement_file=args.id2iri_replacement_with_file,
271
- do_not_request_resource_metadata_from_db=args.do_not_request_resource_metadata_from_db,
272
- )
273
-
274
-
275
- def _call_resume_xmlupload(args: argparse.Namespace) -> bool:
276
- # this does not need docker if not on localhost, as does not need to validate
277
- _check_health_with_docker_on_localhost(args.server)
278
- return resume_xmlupload(
279
- creds=_get_creds(args),
280
- skip_first_resource=args.skip_first_resource,
281
- )
282
-
283
-
284
- def _call_get(args: argparse.Namespace) -> bool:
285
- _check_health_with_docker_on_localhost(args.server)
286
- _check_directory_exists(Path(args.project_definition).parent)
287
- return get_project(
288
- project_identifier=args.project,
289
- outfile_path=args.project_definition,
290
- creds=_get_creds(args),
291
- verbose=args.verbose,
292
- )
293
-
294
-
295
- def _call_create(args: argparse.Namespace) -> bool:
296
- _check_health_with_docker_on_localhost(args.server)
297
- _check_filepath_exists(Path(args.project_definition))
298
- success = False
299
- match args.lists_only, args.validate_only:
300
- case True, True:
301
- success = validate_lists_section_with_schema(args.project_definition)
302
- print("'Lists' section of the JSON project file is syntactically correct and passed validation.")
303
- case True, False:
304
- _, success = create_only_lists(
305
- project_file_as_path_or_parsed=args.project_definition,
306
- creds=_get_creds(args),
307
- )
308
- case False, True:
309
- success = validate_project(args.project_definition)
310
- print("JSON project file is syntactically correct and passed validation.")
311
- case False, False:
312
- success = create_project(
313
- project_file_as_path_or_parsed=args.project_definition,
314
- creds=_get_creds(args),
315
- verbose=args.verbose,
316
- )
317
- return success
318
-
319
-
320
- def _get_creds(args: argparse.Namespace) -> ServerCredentials:
321
- return ServerCredentials(
322
- server=args.server,
323
- user=args.user,
324
- password=args.password,
325
- dsp_ingest_url=args.dsp_ingest_url,
326
- )
327
-
328
-
329
- def _check_health_with_docker_on_localhost(api_url: str) -> None:
330
- if api_url == LOCALHOST_API:
331
- _check_docker_health()
332
- _check_api_health(api_url)
333
-
334
-
335
- def _check_health_with_docker(api_url: str) -> None:
336
- # validate always needs docker running
337
- _check_docker_health()
338
- _check_api_health(api_url)
339
-
340
-
341
- def _check_docker_health() -> None:
342
- if subprocess.run("docker stats --no-stream".split(), check=False, capture_output=True).returncode != 0:
343
- raise DockerNotReachableError()
344
-
345
-
346
- def _check_api_health(api_url: str) -> None:
347
- health_url = f"{api_url}/health"
348
- msg = (
349
- "The DSP-API could not be reached. Please check if your stack is healthy "
350
- "or start a stack with 'dsp-tools start-stack' if none is running."
351
- )
352
- try:
353
- response = requests.get(health_url, timeout=2)
354
- if not response.ok:
355
- if api_url != LOCALHOST_API:
356
- msg = (
357
- f"The DSP-API could not be reached (returned status {response.status_code}). "
358
- f"Please contact the DaSCH engineering team for help."
359
- )
360
- logger.error(msg)
361
- raise DspApiNotReachableError(msg)
362
- logger.debug(f"DSP API health check passed: {health_url}")
363
- except requests.exceptions.RequestException as e:
364
- logger.error(e)
365
- if api_url != LOCALHOST_API:
366
- msg = "The DSP-API responded with a request exception. Please contact the DaSCH engineering team for help."
367
- logger.error(msg)
368
- raise DspApiNotReachableError(msg) from None
369
-
370
-
371
- def _check_filepath_exists(file_path: Path) -> None:
372
- if not file_path.exists():
373
- raise UserFilepathNotFoundError(file_path)
374
-
375
-
376
- def _check_directory_exists(dir_path: Path) -> None:
377
- if not dir_path.is_dir():
378
- raise UserDirectoryNotFoundError(dir_path)
@@ -0,0 +1,74 @@
1
+ import argparse
2
+ from pathlib import Path
3
+
4
+ from dsp_tools.cli.args import PathDependencies
5
+ from dsp_tools.cli.utils import check_path_dependencies
6
+ from dsp_tools.commands.excel2json.lists.make_lists import excel2lists
7
+ from dsp_tools.commands.excel2json.old_lists import old_excel2lists
8
+ from dsp_tools.commands.excel2json.project import excel2json
9
+ from dsp_tools.commands.excel2json.project import old_excel2json
10
+ from dsp_tools.commands.excel2json.properties import excel2properties
11
+ from dsp_tools.commands.excel2json.resources import excel2resources
12
+ from dsp_tools.commands.id2iri import id2iri
13
+
14
+
15
+ def call_id2iri(args: argparse.Namespace) -> bool:
16
+ check_path_dependencies(PathDependencies([Path(args.xmlfile), Path(args.mapping)]))
17
+ return id2iri(
18
+ xml_file=args.xmlfile,
19
+ json_file=args.mapping,
20
+ remove_resource_if_id_in_mapping=args.remove_resources,
21
+ )
22
+
23
+
24
+ def call_excel2properties(args: argparse.Namespace) -> bool:
25
+ check_path_dependencies(PathDependencies([Path(args.excelfile)]))
26
+ _, _, success = excel2properties(
27
+ excelfile=args.excelfile,
28
+ path_to_output_file=args.properties_section,
29
+ )
30
+ return success
31
+
32
+
33
+ def call_excel2resources(args: argparse.Namespace) -> bool:
34
+ check_path_dependencies(PathDependencies([Path(args.excelfile)]))
35
+ _, _, success = excel2resources(
36
+ excelfile=args.excelfile,
37
+ path_to_output_file=args.resources_section,
38
+ )
39
+ return success
40
+
41
+
42
+ def call_old_excel2lists(args: argparse.Namespace) -> bool:
43
+ check_path_dependencies(PathDependencies(required_directories=[Path(args.excelfolder)]))
44
+ _, success = old_excel2lists(
45
+ excelfolder=args.excelfolder,
46
+ path_to_output_file=args.lists_section,
47
+ verbose=args.verbose,
48
+ )
49
+ return success
50
+
51
+
52
+ def call_excel2lists(args: argparse.Namespace) -> bool:
53
+ check_path_dependencies(PathDependencies(required_directories=[Path(args.excelfolder)]))
54
+ _, success = excel2lists(
55
+ excelfolder=args.excelfolder,
56
+ path_to_output_file=args.lists_section,
57
+ )
58
+ return success
59
+
60
+
61
+ def call_excel2json(args: argparse.Namespace) -> bool:
62
+ check_path_dependencies(PathDependencies(required_directories=[Path(args.excelfolder)]))
63
+ return excel2json(
64
+ data_model_files=args.excelfolder,
65
+ path_to_output_file=args.project_definition,
66
+ )
67
+
68
+
69
+ def call_old_excel2json(args: argparse.Namespace) -> bool:
70
+ check_path_dependencies(PathDependencies(required_directories=[Path(args.excelfolder)]))
71
+ return old_excel2json(
72
+ data_model_files=args.excelfolder,
73
+ path_to_output_file=args.project_definition,
74
+ )