dsp-tools 17.0.0.post9__py3-none-any.whl → 17.0.0.post14__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/call_action.py +68 -25
- dsp_tools/commands/create/__init__.py +0 -0
- dsp_tools/commands/create/constants.py +4 -0
- dsp_tools/commands/create/models/__init__.py +0 -0
- dsp_tools/commands/create/models/input_problems.py +23 -0
- dsp_tools/commands/create/models/parsed_ontology.py +48 -0
- dsp_tools/commands/create/models/parsed_project.py +49 -0
- dsp_tools/commands/create/parsing/__init__.py +0 -0
- dsp_tools/commands/create/parsing/parse_lists.py +0 -0
- dsp_tools/commands/create/parsing/parse_ontology.py +108 -0
- dsp_tools/commands/create/parsing/parse_project.py +99 -0
- dsp_tools/commands/create/parsing/parsing_utils.py +43 -0
- dsp_tools/commands/excel2json/json_header.py +7 -4
- dsp_tools/commands/excel2json/models/json_header.py +2 -2
- dsp_tools/commands/excel2json/project.py +2 -1
- dsp_tools/error/exceptions.py +12 -0
- {dsp_tools-17.0.0.post9.dist-info → dsp_tools-17.0.0.post14.dist-info}/METADATA +1 -1
- {dsp_tools-17.0.0.post9.dist-info → dsp_tools-17.0.0.post14.dist-info}/RECORD +20 -9
- {dsp_tools-17.0.0.post9.dist-info → dsp_tools-17.0.0.post14.dist-info}/WHEEL +1 -1
- {dsp_tools-17.0.0.post9.dist-info → dsp_tools-17.0.0.post14.dist-info}/entry_points.txt +0 -0
dsp_tools/cli/call_action.py
CHANGED
|
@@ -2,6 +2,7 @@ import argparse
|
|
|
2
2
|
import subprocess
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
|
+
import requests
|
|
5
6
|
from loguru import logger
|
|
6
7
|
|
|
7
8
|
from dsp_tools.cli.args import ServerCredentials
|
|
@@ -27,9 +28,13 @@ from dsp_tools.commands.start_stack import StackHandler
|
|
|
27
28
|
from dsp_tools.commands.validate_data.validate_data import validate_data
|
|
28
29
|
from dsp_tools.commands.xmlupload.upload_config import UploadConfig
|
|
29
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
|
|
30
33
|
from dsp_tools.error.exceptions import InputError
|
|
31
34
|
from dsp_tools.utils.xml_parsing.parse_clean_validate_xml import parse_and_validate_xml_file
|
|
32
35
|
|
|
36
|
+
LOCALHOST_API = "http://0.0.0.0:3333"
|
|
37
|
+
|
|
33
38
|
|
|
34
39
|
def call_requested_action(args: argparse.Namespace) -> bool: # noqa: PLR0912 (too many branches)
|
|
35
40
|
"""
|
|
@@ -41,20 +46,36 @@ def call_requested_action(args: argparse.Namespace) -> bool: # noqa: PLR0912 (t
|
|
|
41
46
|
Raises:
|
|
42
47
|
BaseError: from the called function
|
|
43
48
|
InputError: from the called function
|
|
49
|
+
DockerNotReachableError: from the called function
|
|
50
|
+
LocalDspApiNotReachableError: from the called function
|
|
44
51
|
unexpected errors from the called methods and underlying libraries
|
|
45
52
|
|
|
46
53
|
Returns:
|
|
47
54
|
success status
|
|
48
55
|
"""
|
|
49
56
|
match args.action:
|
|
57
|
+
# commands with Docker / API interactions
|
|
58
|
+
case "start-stack":
|
|
59
|
+
result = _call_start_stack(args)
|
|
60
|
+
case "stop-stack":
|
|
61
|
+
result = _call_stop_stack()
|
|
50
62
|
case "create":
|
|
51
63
|
result = _call_create(args)
|
|
52
|
-
case "
|
|
53
|
-
result =
|
|
64
|
+
case "get":
|
|
65
|
+
result = _call_get(args)
|
|
54
66
|
case "validate-data":
|
|
55
67
|
result = _call_validate_data(args)
|
|
68
|
+
case "xmlupload":
|
|
69
|
+
result = _call_xmlupload(args)
|
|
56
70
|
case "resume-xmlupload":
|
|
57
71
|
result = _call_resume_xmlupload(args)
|
|
72
|
+
case "upload-files":
|
|
73
|
+
result = _call_upload_files(args)
|
|
74
|
+
case "ingest-files":
|
|
75
|
+
result = _call_ingest_files(args)
|
|
76
|
+
case "ingest-xmlupload":
|
|
77
|
+
result = _call_ingest_xmlupload(args)
|
|
78
|
+
# commands that do not require docker
|
|
58
79
|
case "excel2json":
|
|
59
80
|
result = _call_excel2json(args)
|
|
60
81
|
case "old-excel2json":
|
|
@@ -69,18 +90,6 @@ def call_requested_action(args: argparse.Namespace) -> bool: # noqa: PLR0912 (t
|
|
|
69
90
|
result = _call_excel2properties(args)
|
|
70
91
|
case "id2iri":
|
|
71
92
|
result = _call_id2iri(args)
|
|
72
|
-
case "start-stack":
|
|
73
|
-
result = _call_start_stack(args)
|
|
74
|
-
case "stop-stack":
|
|
75
|
-
result = _call_stop_stack()
|
|
76
|
-
case "get":
|
|
77
|
-
result = _call_get(args)
|
|
78
|
-
case "upload-files":
|
|
79
|
-
result = _call_upload_files(args)
|
|
80
|
-
case "ingest-files":
|
|
81
|
-
result = _call_ingest_files(args)
|
|
82
|
-
case "ingest-xmlupload":
|
|
83
|
-
result = _call_ingest_xmlupload(args)
|
|
84
93
|
case _:
|
|
85
94
|
print(f"ERROR: Unknown action '{args.action}'")
|
|
86
95
|
logger.error(f"Unknown action '{args.action}'")
|
|
@@ -89,6 +98,7 @@ def call_requested_action(args: argparse.Namespace) -> bool: # noqa: PLR0912 (t
|
|
|
89
98
|
|
|
90
99
|
|
|
91
100
|
def _call_stop_stack() -> bool:
|
|
101
|
+
_check_docker_health()
|
|
92
102
|
stack_handler = StackHandler(StackConfiguration())
|
|
93
103
|
return stack_handler.stop_stack()
|
|
94
104
|
|
|
@@ -164,7 +174,7 @@ def _call_old_excel2json(args: argparse.Namespace) -> bool:
|
|
|
164
174
|
|
|
165
175
|
|
|
166
176
|
def _call_upload_files(args: argparse.Namespace) -> bool:
|
|
167
|
-
|
|
177
|
+
_check_health_with_docker_on_localhost(args.server)
|
|
168
178
|
return upload_files(
|
|
169
179
|
xml_file=Path(args.xml_file),
|
|
170
180
|
creds=_get_creds(args),
|
|
@@ -173,12 +183,12 @@ def _call_upload_files(args: argparse.Namespace) -> bool:
|
|
|
173
183
|
|
|
174
184
|
|
|
175
185
|
def _call_ingest_files(args: argparse.Namespace) -> bool:
|
|
176
|
-
|
|
186
|
+
_check_health_with_docker_on_localhost(args.server)
|
|
177
187
|
return ingest_files(creds=_get_creds(args), shortcode=args.shortcode)
|
|
178
188
|
|
|
179
189
|
|
|
180
190
|
def _call_ingest_xmlupload(args: argparse.Namespace) -> bool:
|
|
181
|
-
|
|
191
|
+
_check_health_with_docker(args.server)
|
|
182
192
|
interrupt_after = args.interrupt_after if args.interrupt_after > 0 else None
|
|
183
193
|
return ingest_xmlupload(
|
|
184
194
|
xml_file=Path(args.xml_file),
|
|
@@ -191,7 +201,7 @@ def _call_ingest_xmlupload(args: argparse.Namespace) -> bool:
|
|
|
191
201
|
|
|
192
202
|
|
|
193
203
|
def _call_xmlupload(args: argparse.Namespace) -> bool:
|
|
194
|
-
|
|
204
|
+
_check_health_with_docker(args.server)
|
|
195
205
|
if args.validate_only:
|
|
196
206
|
success = parse_and_validate_xml_file(Path(args.xmlfile))
|
|
197
207
|
print("The XML file is syntactically correct.")
|
|
@@ -227,7 +237,7 @@ def _call_xmlupload(args: argparse.Namespace) -> bool:
|
|
|
227
237
|
|
|
228
238
|
|
|
229
239
|
def _call_validate_data(args: argparse.Namespace) -> bool:
|
|
230
|
-
|
|
240
|
+
_check_health_with_docker(args.server)
|
|
231
241
|
return validate_data(
|
|
232
242
|
filepath=Path(args.xmlfile),
|
|
233
243
|
creds=_get_creds(args),
|
|
@@ -239,7 +249,8 @@ def _call_validate_data(args: argparse.Namespace) -> bool:
|
|
|
239
249
|
|
|
240
250
|
|
|
241
251
|
def _call_resume_xmlupload(args: argparse.Namespace) -> bool:
|
|
242
|
-
|
|
252
|
+
# this does not need docker if not on localhost, as does not need to validate
|
|
253
|
+
_check_health_with_docker_on_localhost(args.server)
|
|
243
254
|
return resume_xmlupload(
|
|
244
255
|
creds=_get_creds(args),
|
|
245
256
|
skip_first_resource=args.skip_first_resource,
|
|
@@ -247,7 +258,7 @@ def _call_resume_xmlupload(args: argparse.Namespace) -> bool:
|
|
|
247
258
|
|
|
248
259
|
|
|
249
260
|
def _call_get(args: argparse.Namespace) -> bool:
|
|
250
|
-
|
|
261
|
+
_check_health_with_docker_on_localhost(args.server)
|
|
251
262
|
return get_project(
|
|
252
263
|
project_identifier=args.project,
|
|
253
264
|
outfile_path=args.project_definition,
|
|
@@ -257,7 +268,7 @@ def _call_get(args: argparse.Namespace) -> bool:
|
|
|
257
268
|
|
|
258
269
|
|
|
259
270
|
def _call_create(args: argparse.Namespace) -> bool:
|
|
260
|
-
|
|
271
|
+
_check_health_with_docker_on_localhost(args.server)
|
|
261
272
|
success = False
|
|
262
273
|
match args.lists_only, args.validate_only:
|
|
263
274
|
case True, True:
|
|
@@ -289,11 +300,43 @@ def _get_creds(args: argparse.Namespace) -> ServerCredentials:
|
|
|
289
300
|
)
|
|
290
301
|
|
|
291
302
|
|
|
292
|
-
def
|
|
293
|
-
if api_url ==
|
|
303
|
+
def _check_health_with_docker_on_localhost(api_url: str) -> None:
|
|
304
|
+
if api_url == LOCALHOST_API:
|
|
294
305
|
_check_docker_health()
|
|
306
|
+
_check_api_health(api_url)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _check_health_with_docker(api_url: str) -> None:
|
|
310
|
+
# validate always needs docker running
|
|
311
|
+
_check_docker_health()
|
|
312
|
+
_check_api_health(api_url)
|
|
295
313
|
|
|
296
314
|
|
|
297
315
|
def _check_docker_health() -> None:
|
|
298
316
|
if subprocess.run("docker stats --no-stream".split(), check=False, capture_output=True).returncode != 0:
|
|
299
|
-
raise
|
|
317
|
+
raise DockerNotReachableError()
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _check_api_health(api_url: str) -> None:
|
|
321
|
+
health_url = f"{api_url}/health"
|
|
322
|
+
msg = (
|
|
323
|
+
"The DSP-API could not be reached. Please check if your stack is healthy "
|
|
324
|
+
"or start a stack with 'dsp-tools start-stack' if none is running."
|
|
325
|
+
)
|
|
326
|
+
try:
|
|
327
|
+
response = requests.get(health_url, timeout=2)
|
|
328
|
+
if not response.ok:
|
|
329
|
+
if api_url != LOCALHOST_API:
|
|
330
|
+
msg = (
|
|
331
|
+
f"The DSP-API could not be reached (returned status {response.status_code}). "
|
|
332
|
+
f"Please contact the DaSCH engineering team for help."
|
|
333
|
+
)
|
|
334
|
+
logger.error(msg)
|
|
335
|
+
raise DspApiNotReachableError(msg)
|
|
336
|
+
logger.debug(f"DSP API health check passed: {health_url}")
|
|
337
|
+
except requests.exceptions.RequestException as e:
|
|
338
|
+
logger.error(e)
|
|
339
|
+
if api_url != LOCALHOST_API:
|
|
340
|
+
msg = "The DSP-API responded with a request exception. Please contact the DaSCH engineering team for help."
|
|
341
|
+
logger.error(msg)
|
|
342
|
+
raise DspApiNotReachableError(msg) from None
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from enum import StrEnum
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class CollectedProblems:
|
|
9
|
+
header: str
|
|
10
|
+
problems: list[InputProblem]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class InputProblem:
|
|
15
|
+
problematic_object: str
|
|
16
|
+
problem: ProblemType
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ProblemType(StrEnum):
|
|
20
|
+
PREFIX_COULD_NOT_BE_RESOLVED = (
|
|
21
|
+
"The prefix used is not defined in the 'prefix' section of the file, "
|
|
22
|
+
"nor does it belong to one of the project ontologies."
|
|
23
|
+
)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from enum import auto
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class ParsedOntology:
|
|
11
|
+
name: str
|
|
12
|
+
label: str
|
|
13
|
+
comment: str | None
|
|
14
|
+
classes: list[ParsedClass]
|
|
15
|
+
properties: list[ParsedProperty]
|
|
16
|
+
cardinalities: list[ParsedClassCardinalities]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class ParsedClass:
|
|
21
|
+
name: str
|
|
22
|
+
info: dict[str, Any]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class ParsedProperty:
|
|
27
|
+
name: str
|
|
28
|
+
info: dict[str, Any]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class ParsedClassCardinalities:
|
|
33
|
+
class_iri: str
|
|
34
|
+
cards: list[ParsedPropertyCardinality]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class ParsedPropertyCardinality:
|
|
39
|
+
propname: str
|
|
40
|
+
cardinality: Cardinality
|
|
41
|
+
gui_order: int | None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Cardinality(Enum):
|
|
45
|
+
C_0_1 = auto()
|
|
46
|
+
C_1 = auto()
|
|
47
|
+
C_0_N = auto()
|
|
48
|
+
C_1_N = auto()
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from dsp_tools.commands.create.models.parsed_ontology import ParsedOntology
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class ParsedProject:
|
|
11
|
+
prefixes: dict[str, str]
|
|
12
|
+
project_metadata: ParsedProjectMetadata
|
|
13
|
+
permissions: ParsedPermissions
|
|
14
|
+
groups: list[ParsedGroup]
|
|
15
|
+
users: list[ParsedUser]
|
|
16
|
+
lists: list[ParsedList]
|
|
17
|
+
ontologies: list[ParsedOntology]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class ParsedProjectMetadata:
|
|
22
|
+
shortcode: str
|
|
23
|
+
shortname: str
|
|
24
|
+
longname: str
|
|
25
|
+
descriptions: dict[str, str]
|
|
26
|
+
keywords: list[str]
|
|
27
|
+
enabled_licenses: list[str]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class ParsedPermissions:
|
|
32
|
+
default_permissions: str
|
|
33
|
+
default_permissions_overrule: dict[str, Any] | None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class ParsedGroup:
|
|
38
|
+
info: dict[str, Any]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class ParsedUser:
|
|
43
|
+
info: dict[str, Any]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class ParsedList:
|
|
48
|
+
name: str
|
|
49
|
+
info: dict[str, Any]
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from typing import cast
|
|
3
|
+
|
|
4
|
+
from dsp_tools.commands.create.models.input_problems import CollectedProblems
|
|
5
|
+
from dsp_tools.commands.create.models.input_problems import InputProblem
|
|
6
|
+
from dsp_tools.commands.create.models.input_problems import ProblemType
|
|
7
|
+
from dsp_tools.commands.create.models.parsed_ontology import Cardinality
|
|
8
|
+
from dsp_tools.commands.create.models.parsed_ontology import ParsedClass
|
|
9
|
+
from dsp_tools.commands.create.models.parsed_ontology import ParsedClassCardinalities
|
|
10
|
+
from dsp_tools.commands.create.models.parsed_ontology import ParsedOntology
|
|
11
|
+
from dsp_tools.commands.create.models.parsed_ontology import ParsedProperty
|
|
12
|
+
from dsp_tools.commands.create.models.parsed_ontology import ParsedPropertyCardinality
|
|
13
|
+
from dsp_tools.commands.create.parsing.parsing_utils import resolve_to_absolute_iri
|
|
14
|
+
|
|
15
|
+
CARDINALITY_MAPPER = {
|
|
16
|
+
"0-1": Cardinality.C_0_1,
|
|
17
|
+
"1": Cardinality.C_1,
|
|
18
|
+
"0-n": Cardinality.C_0_N,
|
|
19
|
+
"1-n": Cardinality.C_1_N,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def parse_ontology(ontology_json: dict[str, Any], prefixes: dict[str, str]) -> ParsedOntology | CollectedProblems:
|
|
24
|
+
onto_name = ontology_json["name"]
|
|
25
|
+
current_onto = prefixes[onto_name]
|
|
26
|
+
fails = []
|
|
27
|
+
props, prop_fails = _parse_properties(ontology_json["properties"], current_onto)
|
|
28
|
+
fails.extend(prop_fails)
|
|
29
|
+
classes, cls_fails = _parse_classes(ontology_json["resources"], current_onto)
|
|
30
|
+
fails.extend(cls_fails)
|
|
31
|
+
cards, card_fails = _parse_cardinalities(ontology_json["resources"], current_onto, prefixes)
|
|
32
|
+
fails.extend(card_fails)
|
|
33
|
+
if fails:
|
|
34
|
+
return CollectedProblems(
|
|
35
|
+
f"During the parsing of the ontology '{onto_name}' the following errors occurred", fails
|
|
36
|
+
)
|
|
37
|
+
return ParsedOntology(
|
|
38
|
+
name=onto_name,
|
|
39
|
+
label=ontology_json["label"],
|
|
40
|
+
comment=ontology_json.get("comment"),
|
|
41
|
+
classes=classes,
|
|
42
|
+
properties=props,
|
|
43
|
+
cardinalities=cards,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _parse_properties(
|
|
48
|
+
properties_list: list[dict[str, Any]], current_onto_prefix: str
|
|
49
|
+
) -> tuple[list[ParsedProperty], list[InputProblem]]:
|
|
50
|
+
parsed = []
|
|
51
|
+
for prop in properties_list:
|
|
52
|
+
parsed.append(ParsedProperty(f"{current_onto_prefix}{prop['name']}", prop))
|
|
53
|
+
return parsed, []
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _parse_classes(
|
|
57
|
+
classes_list: list[dict[str, Any]], current_onto_prefix: str
|
|
58
|
+
) -> tuple[list[ParsedClass], list[InputProblem]]:
|
|
59
|
+
parsed = []
|
|
60
|
+
for cls in classes_list:
|
|
61
|
+
parsed.append(ParsedClass(f"{current_onto_prefix}{cls['name']}", cls))
|
|
62
|
+
return parsed, []
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _parse_cardinalities(
|
|
66
|
+
classes_list: list[dict[str, Any]], current_onto_prefix: str, prefixes: dict[str, str]
|
|
67
|
+
) -> tuple[list[ParsedClassCardinalities], list[InputProblem]]:
|
|
68
|
+
parsed = []
|
|
69
|
+
failures = []
|
|
70
|
+
for c in classes_list:
|
|
71
|
+
if c.get("cardinalities"):
|
|
72
|
+
result = _parse_one_class_cardinality(c, current_onto_prefix, prefixes)
|
|
73
|
+
if isinstance(result, ParsedClassCardinalities):
|
|
74
|
+
parsed.append(result)
|
|
75
|
+
else:
|
|
76
|
+
failures.extend(result)
|
|
77
|
+
return parsed, failures
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _parse_one_class_cardinality(
|
|
81
|
+
cls_json: dict[str, Any], current_onto_prefix: str, prefixes: dict[str, str]
|
|
82
|
+
) -> ParsedClassCardinalities | list[InputProblem]:
|
|
83
|
+
failures = []
|
|
84
|
+
parsed = []
|
|
85
|
+
for c in cls_json["cardinalities"]:
|
|
86
|
+
result = _parse_one_cardinality(c, current_onto_prefix, prefixes)
|
|
87
|
+
if isinstance(result, ParsedPropertyCardinality):
|
|
88
|
+
parsed.append(result)
|
|
89
|
+
else:
|
|
90
|
+
failures.append(result)
|
|
91
|
+
if failures:
|
|
92
|
+
return failures
|
|
93
|
+
cls_iri = f"{current_onto_prefix}{cls_json['name']}"
|
|
94
|
+
return ParsedClassCardinalities(cls_iri, parsed)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _parse_one_cardinality(
|
|
98
|
+
card_json: dict[str, str | int], current_onto_prefix: str, prefixes: dict[str, str]
|
|
99
|
+
) -> ParsedPropertyCardinality | InputProblem:
|
|
100
|
+
prp_name = cast(str, card_json["propname"])
|
|
101
|
+
if not (resolved := resolve_to_absolute_iri(prp_name, current_onto_prefix, prefixes)):
|
|
102
|
+
return InputProblem(prp_name, ProblemType.PREFIX_COULD_NOT_BE_RESOLVED)
|
|
103
|
+
gui = cast(int | None, card_json.get("gui_order"))
|
|
104
|
+
return ParsedPropertyCardinality(
|
|
105
|
+
propname=resolved,
|
|
106
|
+
cardinality=CARDINALITY_MAPPER[cast(str, card_json["cardinality"])],
|
|
107
|
+
gui_order=gui,
|
|
108
|
+
)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from loguru import logger
|
|
5
|
+
|
|
6
|
+
from dsp_tools.commands.create.models.input_problems import CollectedProblems
|
|
7
|
+
from dsp_tools.commands.create.models.parsed_ontology import ParsedOntology
|
|
8
|
+
from dsp_tools.commands.create.models.parsed_project import ParsedGroup
|
|
9
|
+
from dsp_tools.commands.create.models.parsed_project import ParsedList
|
|
10
|
+
from dsp_tools.commands.create.models.parsed_project import ParsedPermissions
|
|
11
|
+
from dsp_tools.commands.create.models.parsed_project import ParsedProject
|
|
12
|
+
from dsp_tools.commands.create.models.parsed_project import ParsedProjectMetadata
|
|
13
|
+
from dsp_tools.commands.create.models.parsed_project import ParsedUser
|
|
14
|
+
from dsp_tools.commands.create.parsing.parse_ontology import parse_ontology
|
|
15
|
+
from dsp_tools.commands.create.parsing.parsing_utils import create_prefix_lookup
|
|
16
|
+
from dsp_tools.commands.project.create.project_validate import validate_project
|
|
17
|
+
from dsp_tools.utils.json_parsing import parse_json_input
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def parse_project(
|
|
21
|
+
project_file_as_path_or_parsed: str | Path | dict[str, Any], api_url: str
|
|
22
|
+
) -> ParsedProject | list[CollectedProblems]:
|
|
23
|
+
complete_json = _parse_and_validate(project_file_as_path_or_parsed)
|
|
24
|
+
return _parse_project(complete_json, api_url)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _parse_project(complete_json: dict[str, Any], api_url: str) -> ParsedProject | list[CollectedProblems]:
|
|
28
|
+
prefix_lookup = create_prefix_lookup(complete_json, api_url)
|
|
29
|
+
project_json = complete_json["project"]
|
|
30
|
+
ontologies, failures = _parse_all_ontologies(project_json, prefix_lookup)
|
|
31
|
+
if failures:
|
|
32
|
+
return failures
|
|
33
|
+
return ParsedProject(
|
|
34
|
+
prefixes=prefix_lookup,
|
|
35
|
+
project_metadata=_parse_metadata(project_json),
|
|
36
|
+
permissions=_parse_permissions(project_json),
|
|
37
|
+
groups=_parse_groups(project_json),
|
|
38
|
+
users=_parse_users(project_json),
|
|
39
|
+
lists=_parse_lists(project_json),
|
|
40
|
+
ontologies=ontologies,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _parse_and_validate(project_file_as_path_or_parsed: str | Path | dict[str, Any]) -> dict[str, Any]:
|
|
45
|
+
project_json = parse_json_input(project_file_as_path_or_parsed=project_file_as_path_or_parsed)
|
|
46
|
+
validate_project(project_json)
|
|
47
|
+
print(" JSON project file is syntactically correct and passed validation.")
|
|
48
|
+
logger.info("JSON project file is syntactically correct and passed validation.")
|
|
49
|
+
return project_json
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _parse_metadata(project_json: dict[str, Any]) -> ParsedProjectMetadata:
|
|
53
|
+
return ParsedProjectMetadata(
|
|
54
|
+
shortcode=project_json["shortcode"],
|
|
55
|
+
shortname=project_json["shortname"],
|
|
56
|
+
longname=project_json["longname"],
|
|
57
|
+
descriptions=project_json["descriptions"],
|
|
58
|
+
keywords=project_json["keywords"],
|
|
59
|
+
enabled_licenses=project_json["enabled_licenses"],
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _parse_permissions(project_json: dict[str, Any]) -> ParsedPermissions:
|
|
64
|
+
return ParsedPermissions(
|
|
65
|
+
default_permissions=project_json["default_permissions"],
|
|
66
|
+
default_permissions_overrule=project_json.get("default_permissions_overrule"),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _parse_groups(project_json: dict[str, Any]) -> list[ParsedGroup]:
|
|
71
|
+
if not (found := project_json.get("groups")):
|
|
72
|
+
return []
|
|
73
|
+
return [ParsedGroup(x) for x in found]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _parse_users(project_json: dict[str, Any]) -> list[ParsedUser]:
|
|
77
|
+
if not (found := project_json.get("users")):
|
|
78
|
+
return []
|
|
79
|
+
return [ParsedUser(x) for x in found]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _parse_lists(project_json: dict[str, Any]) -> list[ParsedList]:
|
|
83
|
+
if not (found := project_json.get("lists")):
|
|
84
|
+
return []
|
|
85
|
+
return [ParsedList(x["name"], x) for x in found]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _parse_all_ontologies(
|
|
89
|
+
project_json: dict[str, Any], prefix_lookup: dict[str, str]
|
|
90
|
+
) -> tuple[list[ParsedOntology], list[CollectedProblems]]:
|
|
91
|
+
ontos = []
|
|
92
|
+
failures = []
|
|
93
|
+
for o in project_json["ontologies"]:
|
|
94
|
+
result = parse_ontology(o, prefix_lookup)
|
|
95
|
+
if isinstance(result, ParsedOntology):
|
|
96
|
+
ontos.append(result)
|
|
97
|
+
else:
|
|
98
|
+
failures.append(result)
|
|
99
|
+
return ontos, failures
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import regex
|
|
4
|
+
|
|
5
|
+
from dsp_tools.commands.create.constants import KNORA_API
|
|
6
|
+
from dsp_tools.commands.create.constants import UNIVERSAL_PREFIXES
|
|
7
|
+
from dsp_tools.utils.data_formats.uri_util import is_uri
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def resolve_to_absolute_iri(prefixed: str, current_onto: str, prefix_lookup: dict[str, str]) -> str | None:
|
|
11
|
+
if is_uri(prefixed):
|
|
12
|
+
return prefixed
|
|
13
|
+
if prefixed.startswith(":"):
|
|
14
|
+
return f"{current_onto}{prefixed.lstrip(':')}"
|
|
15
|
+
segments = prefixed.split(":", maxsplit=1)
|
|
16
|
+
if len(segments) == 1:
|
|
17
|
+
return f"{KNORA_API}{segments[0]}"
|
|
18
|
+
if not (found := prefix_lookup.get(segments[0])):
|
|
19
|
+
return None
|
|
20
|
+
return f"{found}{segments[1]}"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def create_prefix_lookup(project_json: dict[str, Any], api_url: str) -> dict[str, str]:
|
|
24
|
+
defined_prefixes = project_json.get("prefixes", {})
|
|
25
|
+
defined_prefixes = defined_prefixes | UNIVERSAL_PREFIXES
|
|
26
|
+
defined_prefixes = _correct_external_prefix(defined_prefixes)
|
|
27
|
+
shortcode = project_json["project"]["shortcode"]
|
|
28
|
+
for onto in project_json["project"]["ontologies"]:
|
|
29
|
+
onto_name = onto["name"]
|
|
30
|
+
defined_prefixes[onto_name] = _make_dsp_ontology_prefix(api_url, shortcode, onto_name)
|
|
31
|
+
return defined_prefixes
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _correct_external_prefix(prefixes: dict[str, str]) -> dict[str, str]:
|
|
35
|
+
for prfx, namespace in prefixes.items():
|
|
36
|
+
if regex.search(r"(#|\/)$", namespace):
|
|
37
|
+
continue
|
|
38
|
+
prefixes[prfx] = f"{namespace}/"
|
|
39
|
+
return prefixes
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _make_dsp_ontology_prefix(api_url: str, shortcode: str, onto_name: str) -> str:
|
|
43
|
+
return f"{api_url}/ontology/{shortcode}/{onto_name}/v2#"
|
|
@@ -180,10 +180,11 @@ def _check_licenses(df: pd.DataFrame) -> ExcelSheetProblem | None:
|
|
|
180
180
|
def _check_all_users(df: pd.DataFrame) -> ExcelSheetProblem | None:
|
|
181
181
|
if not len(df) > 0:
|
|
182
182
|
return None
|
|
183
|
-
|
|
184
|
-
if missing_cols := check_contains_required_columns(df, set(
|
|
183
|
+
required_columns = ["username", "email", "givenname", "password", "familyname", "lang", "role"]
|
|
184
|
+
if missing_cols := check_contains_required_columns(df, set(required_columns)):
|
|
185
185
|
return ExcelSheetProblem("users", [missing_cols])
|
|
186
|
-
|
|
186
|
+
required_values = ["username", "email", "givenname", "familyname", "lang", "role"]
|
|
187
|
+
if missing_vals := find_missing_required_values(df, required_values):
|
|
187
188
|
return ExcelSheetProblem("users", [MissingValuesProblem(missing_vals)])
|
|
188
189
|
problems: list[Problem] = []
|
|
189
190
|
for i, row in df.iterrows():
|
|
@@ -307,12 +308,14 @@ def _extract_users(df: pd.DataFrame) -> Users:
|
|
|
307
308
|
|
|
308
309
|
def _extract_one_user(row: pd.Series[str]) -> User:
|
|
309
310
|
isProjectAdmin = row["role"].lower() == "projectadmin"
|
|
311
|
+
if pd.isna(pw := row["password"]):
|
|
312
|
+
pw = None
|
|
310
313
|
return User(
|
|
311
314
|
username=row["username"],
|
|
312
315
|
email=row["email"],
|
|
313
316
|
givenName=row["givenname"],
|
|
314
317
|
familyName=row["familyname"],
|
|
315
|
-
password=
|
|
318
|
+
password=pw,
|
|
316
319
|
lang=row["lang"],
|
|
317
320
|
isProjectAdmin=isProjectAdmin,
|
|
318
321
|
)
|
|
@@ -119,12 +119,12 @@ class User:
|
|
|
119
119
|
email: str
|
|
120
120
|
givenName: str
|
|
121
121
|
familyName: str
|
|
122
|
-
password: str
|
|
122
|
+
password: str | None
|
|
123
123
|
lang: str
|
|
124
124
|
isProjectAdmin: bool
|
|
125
125
|
|
|
126
126
|
def to_dict(self) -> dict[str, Any]:
|
|
127
|
-
usr_dict = {
|
|
127
|
+
usr_dict: dict[str, Any] = {
|
|
128
128
|
"username": self.username,
|
|
129
129
|
"email": self.email,
|
|
130
130
|
"givenName": self.givenName,
|
|
@@ -132,7 +132,8 @@ def excel2json(
|
|
|
132
132
|
overall_success, project = _create_project_json(data_model_files, onto_folders, listfolder)
|
|
133
133
|
|
|
134
134
|
with open(path_to_output_file, "w", encoding="utf-8") as f:
|
|
135
|
-
json.
|
|
135
|
+
dump_str = json.dumps(project, indent=4, ensure_ascii=False)
|
|
136
|
+
f.write(dump_str)
|
|
136
137
|
|
|
137
138
|
print(f"JSON project file successfully saved at {path_to_output_file}")
|
|
138
139
|
|
dsp_tools/error/exceptions.py
CHANGED
|
@@ -45,6 +45,18 @@ class InternalError(BaseError):
|
|
|
45
45
|
super().__init__(default_msg)
|
|
46
46
|
|
|
47
47
|
|
|
48
|
+
class DockerNotReachableError(BaseError):
|
|
49
|
+
"""This error is raised when docker is not running."""
|
|
50
|
+
|
|
51
|
+
def __init__(self) -> None:
|
|
52
|
+
msg = "Docker is not running properly. Please start Docker and try again."
|
|
53
|
+
super().__init__(msg)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class DspApiNotReachableError(BaseError):
|
|
57
|
+
"""This error is raised when the DSP-API could not be reached on localhost."""
|
|
58
|
+
|
|
59
|
+
|
|
48
60
|
class InputError(BaseError):
|
|
49
61
|
"""This error is raised when the user input is invalid. The message should be as user-friendly as possible."""
|
|
50
62
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: dsp-tools
|
|
3
|
-
Version: 17.0.0.
|
|
3
|
+
Version: 17.0.0.post14
|
|
4
4
|
Summary: DSP-TOOLS is a Python package with a command line interface that helps you interact with a DaSCH service platform (DSP) server.
|
|
5
5
|
Author: DaSCH - Swiss National Data and Service Center for the Humanities
|
|
6
6
|
Author-email: DaSCH - Swiss National Data and Service Center for the Humanities <info@dasch.swiss>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
dsp_tools/__init__.py,sha256=XdzLhY_8FUFmPtUEawAvEA8apQ_jlgspb2HpmNjlDV8,158
|
|
2
2
|
dsp_tools/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
dsp_tools/cli/args.py,sha256=tS16NvgIMdQuaDWb0cNC8TIK7wY-i4pQe7NFYrzt5Bo,694
|
|
4
|
-
dsp_tools/cli/call_action.py,sha256=
|
|
4
|
+
dsp_tools/cli/call_action.py,sha256=i0VS1qkokvxtcxKj_4V0QWe49sLMZxa8eEqMnl2yr9E,12556
|
|
5
5
|
dsp_tools/cli/create_parsers.py,sha256=zi0E7AHV25B_wXt_8Jcan0Tc6y7aG0vipS5rya5gP9s,17764
|
|
6
6
|
dsp_tools/cli/entry_point.py,sha256=gdexHqVDAy8_Atf0oUxvPVQyDGWUSUhio396U5Oc0RI,10331
|
|
7
7
|
dsp_tools/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -13,9 +13,20 @@ dsp_tools/clients/fuseki_metrics.py,sha256=Vy_aWOusnzlD0EnbyHZcTtPIpMEfRA_ihtYby
|
|
|
13
13
|
dsp_tools/clients/legal_info_client.py,sha256=itDvGQf1VV1WiH2oHVcH1epSUSdJPRDwdRUvmCw8P4s,742
|
|
14
14
|
dsp_tools/clients/legal_info_client_live.py,sha256=9nOe8Y-oQRHh4TkD2-tjdxLNNTonQ0i-P6UjCGgIBGA,5987
|
|
15
15
|
dsp_tools/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
dsp_tools/commands/create/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
dsp_tools/commands/create/constants.py,sha256=UHbYEERF8Rss7JINKBaR6z85LpwaH8w892airFKYHcw,191
|
|
18
|
+
dsp_tools/commands/create/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
+
dsp_tools/commands/create/models/input_problems.py,sha256=xU4nzl-DEghXWg7eT5V6Z3V_srzUlM-WmzeitDY3fBE,483
|
|
20
|
+
dsp_tools/commands/create/models/parsed_ontology.py,sha256=EUuD47SmWXyB74vGdxGwuvRzq1f4_YdciwYmqcEwqWc,815
|
|
21
|
+
dsp_tools/commands/create/models/parsed_project.py,sha256=Gnl9gJEm5sNbccYG24cwT4doS-oqh_cywjbqk1V4wZQ,928
|
|
22
|
+
dsp_tools/commands/create/parsing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
+
dsp_tools/commands/create/parsing/parse_lists.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
+
dsp_tools/commands/create/parsing/parse_ontology.py,sha256=4G_IN7lUdfTmZRTpcsE644MjP9HXe3NQkQ8c48lCiGQ,4261
|
|
25
|
+
dsp_tools/commands/create/parsing/parse_project.py,sha256=yWcBSAmlcML5GsSTFVL69XFuzqyntrANq6XO9Nfh3n8,3997
|
|
26
|
+
dsp_tools/commands/create/parsing/parsing_utils.py,sha256=u_PG8EWnhT1aV5GQSb5eCUbGY0Bd7mo4W49ciIhFzVo,1628
|
|
16
27
|
dsp_tools/commands/excel2json/CLAUDE.md,sha256=y7ZcmrHbewN48sV0wyZhNfXDXdERG4PRvdz02Mqh0gg,3399
|
|
17
28
|
dsp_tools/commands/excel2json/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
-
dsp_tools/commands/excel2json/json_header.py,sha256=
|
|
29
|
+
dsp_tools/commands/excel2json/json_header.py,sha256=3PkhYHloGhuK9_zl7XrqboRksquWzGsdNNNo7-LC2c8,13543
|
|
19
30
|
dsp_tools/commands/excel2json/lists/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
31
|
dsp_tools/commands/excel2json/lists/compliance_checks.py,sha256=qPn4P0ZQGn1__fts85c032krp9bPNGoTx2yP3MzOsIc,13955
|
|
21
32
|
dsp_tools/commands/excel2json/lists/make_lists.py,sha256=jgboXW1MdnJs6D0tOcP08uO7NWyd4Ag-Spxv-yijh3g,10546
|
|
@@ -26,11 +37,11 @@ dsp_tools/commands/excel2json/lists/models/serialise.py,sha256=Ph8tfbmcU5ehyiNem
|
|
|
26
37
|
dsp_tools/commands/excel2json/lists/utils.py,sha256=5ydsm8poSHksGL-r3uK5F7bEmZc1Bpa7RFJZ6m9mS7c,3039
|
|
27
38
|
dsp_tools/commands/excel2json/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
39
|
dsp_tools/commands/excel2json/models/input_error.py,sha256=V6MMtjcSj4EODmCs7asmtqTsHJRCnWndGyKHCuLQFTI,13537
|
|
29
|
-
dsp_tools/commands/excel2json/models/json_header.py,sha256=
|
|
40
|
+
dsp_tools/commands/excel2json/models/json_header.py,sha256=KLTWtssTsGAPh12RkNYtX4rWuGTbWDQ2sumXzjG4Mwg,4465
|
|
30
41
|
dsp_tools/commands/excel2json/models/list_node_name.py,sha256=hY7LQihUwkqBUEsNwHgNJPniiYLIIZ2XdIJTocghHIk,501
|
|
31
42
|
dsp_tools/commands/excel2json/models/ontology.py,sha256=VBEdfvJD94CuhHyK7hwHgdtsfk-v9t_HLu_ROOnaK2k,2070
|
|
32
43
|
dsp_tools/commands/excel2json/old_lists.py,sha256=-NJoGgvkKnlGPPNkSbOzYx7nq3fkUJDU9f7Fx0ycXEA,13576
|
|
33
|
-
dsp_tools/commands/excel2json/project.py,sha256=
|
|
44
|
+
dsp_tools/commands/excel2json/project.py,sha256=8x16UJz5SygSjIaQjZc5uXU3-cxWAvJo8seTYaOzSSI,11644
|
|
34
45
|
dsp_tools/commands/excel2json/properties.py,sha256=sdlseESYVhrklsiDNV_Uw8H5K35JIi6Ivrwm0sXUldw,14517
|
|
35
46
|
dsp_tools/commands/excel2json/resources.py,sha256=xX-ZxGFQYmA9ZPnacKo6nqfvDcZbUUviE5w_-VZLquk,14226
|
|
36
47
|
dsp_tools/commands/excel2json/utils.py,sha256=yVap58SRdwo1K8qteSe0Ji_8h1-f-3akmg3Va9NXEaA,15396
|
|
@@ -164,7 +175,7 @@ dsp_tools/config/logger_config.py,sha256=Bw2Gu5F2d8un_KNk0hvNtV7fvN2TlThqo6gSwqe
|
|
|
164
175
|
dsp_tools/config/warnings_config.py,sha256=15_Lt227HLqhdn6v-zJbi1KG9Fo6Zi1_4fp_a-iY72w,1142
|
|
165
176
|
dsp_tools/error/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
166
177
|
dsp_tools/error/custom_warnings.py,sha256=7C2DscIz9k7IfM8uebIsKWPcWcSjwpDqbIRDaPw7bI8,1053
|
|
167
|
-
dsp_tools/error/exceptions.py,sha256=
|
|
178
|
+
dsp_tools/error/exceptions.py,sha256=cBhH1bpFnOba6rJAEKvgVRwBg6Ni2gN1MPQLDdElPaQ,4686
|
|
168
179
|
dsp_tools/error/problems.py,sha256=DotzVg3MYvMJmernd9tTBmDHoT1MOkHdiWVv8iMoFSk,265
|
|
169
180
|
dsp_tools/error/xmllib_errors.py,sha256=DpYCsBIx_GmsBAUlfk2VMqtzD5IGMRbd2yXTcrJFHR4,549
|
|
170
181
|
dsp_tools/error/xmllib_warnings.py,sha256=sS9jJXGJtQqCiJ9P2zCM5gpIhTpChbujQz_fPvxLm8g,1557
|
|
@@ -237,7 +248,7 @@ dsp_tools/xmllib/models/res.py,sha256=c3edvilYZVDmv2O6Z36sSkHXcuKPAJLfWVpStDTMuJ
|
|
|
237
248
|
dsp_tools/xmllib/models/root.py,sha256=x8_vrDSJ1pZUJUL8LR460dZe4Cg57G_Hy-Zfr2S29dw,13562
|
|
238
249
|
dsp_tools/xmllib/value_checkers.py,sha256=Yx3r6_WoZ5Lev8Orp8yDzd03JvP2GBmFNSFT2dzrycM,10712
|
|
239
250
|
dsp_tools/xmllib/value_converters.py,sha256=WMYS5hd1VlrLLBXnf6pv9yYoPBsv_2MxOO6xv-QsRW4,29218
|
|
240
|
-
dsp_tools-17.0.0.
|
|
241
|
-
dsp_tools-17.0.0.
|
|
242
|
-
dsp_tools-17.0.0.
|
|
243
|
-
dsp_tools-17.0.0.
|
|
251
|
+
dsp_tools-17.0.0.post14.dist-info/WHEEL,sha256=M6du7VZflc4UPsGphmOXHANdgk8zessdJG0DBUuoA-U,78
|
|
252
|
+
dsp_tools-17.0.0.post14.dist-info/entry_points.txt,sha256=qjRfEbkeAwLU_AE2Q-l4Y9irPNmu4Wna-3bfRp1bqV4,62
|
|
253
|
+
dsp_tools-17.0.0.post14.dist-info/METADATA,sha256=tekPavfqdVzjQmW5uibBjMcpbJe8Ty1URY8MhmFCv_4,4285
|
|
254
|
+
dsp_tools-17.0.0.post14.dist-info/RECORD,,
|
|
File without changes
|