cmem-cmemc 24.3.3__py3-none-any.whl → 25.1.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.
- cmem_cmemc/__init__.py +1 -160
- cmem_cmemc/cli.py +138 -0
- cmem_cmemc/command.py +36 -0
- cmem_cmemc/commands/admin.py +7 -6
- cmem_cmemc/commands/client.py +4 -3
- cmem_cmemc/commands/config.py +3 -2
- cmem_cmemc/commands/dataset.py +18 -17
- cmem_cmemc/commands/graph.py +20 -22
- cmem_cmemc/commands/manual.py +56 -0
- cmem_cmemc/commands/metrics.py +11 -11
- cmem_cmemc/commands/migration.py +4 -3
- cmem_cmemc/commands/project.py +10 -10
- cmem_cmemc/commands/python.py +29 -2
- cmem_cmemc/commands/query.py +31 -14
- cmem_cmemc/commands/resource.py +4 -3
- cmem_cmemc/commands/scheduler.py +4 -4
- cmem_cmemc/commands/store.py +2 -2
- cmem_cmemc/commands/user.py +10 -9
- cmem_cmemc/commands/validation.py +2 -2
- cmem_cmemc/commands/variable.py +1 -1
- cmem_cmemc/commands/vocabulary.py +11 -10
- cmem_cmemc/commands/workflow.py +18 -18
- cmem_cmemc/completion.py +76 -49
- cmem_cmemc/config_parser.py +44 -0
- cmem_cmemc/context.py +166 -97
- cmem_cmemc/exceptions.py +15 -2
- cmem_cmemc/manual_helper/graph.py +2 -0
- cmem_cmemc/manual_helper/multi_page.py +1 -1
- cmem_cmemc/manual_helper/single_page.py +2 -0
- cmem_cmemc/migrations/remove_noop_triple_251.py +50 -0
- cmem_cmemc/migrations/shapes_widget_integrations_243.py +0 -5
- cmem_cmemc/object_list.py +3 -3
- cmem_cmemc/parameter_types/path.py +7 -0
- cmem_cmemc/placeholder.py +69 -0
- cmem_cmemc/utils.py +49 -15
- {cmem_cmemc-24.3.3.dist-info → cmem_cmemc-25.1.0.dist-info}/METADATA +14 -14
- cmem_cmemc-25.1.0.dist-info/RECORD +59 -0
- cmem_cmemc-25.1.0.dist-info/entry_points.txt +3 -0
- cmem_cmemc-24.3.3.dist-info/RECORD +0 -54
- cmem_cmemc-24.3.3.dist-info/entry_points.txt +0 -3
- {cmem_cmemc-24.3.3.dist-info → cmem_cmemc-25.1.0.dist-info}/LICENSE +0 -0
- {cmem_cmemc-24.3.3.dist-info → cmem_cmemc-25.1.0.dist-info}/WHEEL +0 -0
cmem_cmemc/commands/graph.py
CHANGED
|
@@ -14,7 +14,7 @@ from xml.etree.ElementTree import ( # nosec
|
|
|
14
14
|
)
|
|
15
15
|
|
|
16
16
|
import click
|
|
17
|
-
from click import Argument
|
|
17
|
+
from click import Argument, ClickException, UsageError
|
|
18
18
|
from cmem.cmempy.config import get_cmem_base_uri
|
|
19
19
|
from cmem.cmempy.dp.authorization import refresh
|
|
20
20
|
from cmem.cmempy.dp.proxy import graph as graph_api
|
|
@@ -121,7 +121,7 @@ def _get_export_names(
|
|
|
121
121
|
|
|
122
122
|
Raises:
|
|
123
123
|
------
|
|
124
|
-
|
|
124
|
+
ClickException in case the template string produces a naming clash,
|
|
125
125
|
means two IRIs result in the same filename
|
|
126
126
|
|
|
127
127
|
"""
|
|
@@ -135,7 +135,7 @@ def _get_export_names(
|
|
|
135
135
|
_name_created = f"{Template(template).render(template_data)}{file_extension}"
|
|
136
136
|
_names[iri] = _name_created
|
|
137
137
|
if len(_names.values()) != len(set(_names.values())):
|
|
138
|
-
raise
|
|
138
|
+
raise ClickException(
|
|
139
139
|
"The given template string produces a naming clash. "
|
|
140
140
|
"Please use a different template to produce unique names."
|
|
141
141
|
)
|
|
@@ -208,7 +208,7 @@ def _get_graphs_filtered(filter_name: str, filter_value: str) -> list[dict]:
|
|
|
208
208
|
# check for correct filter names
|
|
209
209
|
possible_filter_names = ("access", "imported-by")
|
|
210
210
|
if filter_name not in possible_filter_names:
|
|
211
|
-
raise
|
|
211
|
+
raise ClickException(
|
|
212
212
|
f"{filter_name} is an unknown filter name. " f"Use one of {possible_filter_names}."
|
|
213
213
|
)
|
|
214
214
|
# filter by access condition
|
|
@@ -218,14 +218,14 @@ def _get_graphs_filtered(filter_name: str, filter_value: str) -> list[dict]:
|
|
|
218
218
|
elif filter_value == "readonly":
|
|
219
219
|
graphs = get_graphs(writeable=False, readonly=True)
|
|
220
220
|
else:
|
|
221
|
-
raise
|
|
221
|
+
raise ClickException("Filter access is either 'readonly' or 'writeable'.")
|
|
222
222
|
else:
|
|
223
223
|
# default is all graphs
|
|
224
224
|
graphs = get_graphs()
|
|
225
225
|
# filter by imported-by
|
|
226
226
|
if filter_name == "imported-by":
|
|
227
227
|
if filter_value not in get_graphs_as_dict():
|
|
228
|
-
raise
|
|
228
|
+
raise ClickException(UNKNOWN_GRAPH_ERROR.format(filter_value))
|
|
229
229
|
imported_graphs = get_graph_imports(filter_value)
|
|
230
230
|
graphs = [_ for _ in graphs if _["iri"] in imported_graphs]
|
|
231
231
|
return graphs
|
|
@@ -271,19 +271,19 @@ def _check_and_extend_exported_graphs(
|
|
|
271
271
|
|
|
272
272
|
Raises:
|
|
273
273
|
------
|
|
274
|
-
|
|
274
|
+
UsageError or ClickException
|
|
275
275
|
|
|
276
276
|
"""
|
|
277
277
|
# transform given IRI-tuple to a distinct IRI-list
|
|
278
278
|
iris = list(set(iris))
|
|
279
279
|
if len(iris) == 0 and not all_flag:
|
|
280
|
-
raise
|
|
280
|
+
raise UsageError(
|
|
281
281
|
"Either provide at least one graph IRI or use the --all option "
|
|
282
282
|
"in order to work with all graphs."
|
|
283
283
|
)
|
|
284
284
|
for iri in iris:
|
|
285
285
|
if iri not in all_graphs:
|
|
286
|
-
raise
|
|
286
|
+
raise ClickException(UNKNOWN_GRAPH_ERROR.format(iri))
|
|
287
287
|
if all_flag:
|
|
288
288
|
# in case --all is given,
|
|
289
289
|
# list of graphs is filled with all available graph IRIs
|
|
@@ -377,7 +377,7 @@ def tree_command(
|
|
|
377
377
|
"""
|
|
378
378
|
graphs = get_graphs_as_dict()
|
|
379
379
|
if not iris and not all_:
|
|
380
|
-
raise
|
|
380
|
+
raise UsageError(
|
|
381
381
|
"Either specify at least one graph IRI or use the "
|
|
382
382
|
"--all option to show the owl:imports tree of all graphs."
|
|
383
383
|
)
|
|
@@ -386,7 +386,7 @@ def tree_command(
|
|
|
386
386
|
|
|
387
387
|
for iri in iris:
|
|
388
388
|
if iri not in graphs:
|
|
389
|
-
raise
|
|
389
|
+
raise ClickException(UNKNOWN_GRAPH_ERROR.format(iri))
|
|
390
390
|
|
|
391
391
|
iris = sorted(iris, key=lambda x: graphs[x]["label"]["title"].lower())
|
|
392
392
|
|
|
@@ -413,7 +413,7 @@ def tree_command(
|
|
|
413
413
|
output += os.linesep.join(
|
|
414
414
|
[
|
|
415
415
|
line
|
|
416
|
-
for line in tree.show(key=lambda x: x.tag.lower(), stdout=False).splitlines()
|
|
416
|
+
for line in tree.show(key=lambda x: x.tag.lower(), stdout=False).splitlines() # type: ignore[arg-type, return-value]
|
|
417
417
|
if line.strip()
|
|
418
418
|
]
|
|
419
419
|
)
|
|
@@ -486,12 +486,12 @@ def _validate_export_command_input_parameters(
|
|
|
486
486
|
) -> None:
|
|
487
487
|
"""Validate export command input parameters combinations"""
|
|
488
488
|
if output_dir and create_catalog and compress:
|
|
489
|
-
raise
|
|
489
|
+
raise UsageError(
|
|
490
490
|
"Cannot create a catalog file when using a compressed graph file."
|
|
491
491
|
" Please remove either the --create-catalog or --compress option."
|
|
492
492
|
)
|
|
493
493
|
if output_file == "- " and compress:
|
|
494
|
-
raise
|
|
494
|
+
raise UsageError("Cannot output a binary file to terminal. Use --output-file option.")
|
|
495
495
|
|
|
496
496
|
|
|
497
497
|
# pylint: disable=too-many-arguments,too-many-locals
|
|
@@ -618,9 +618,7 @@ def export_command( # noqa: PLR0913
|
|
|
618
618
|
# no output directory set -> file export
|
|
619
619
|
if output_file == "-":
|
|
620
620
|
if compress:
|
|
621
|
-
raise
|
|
622
|
-
"Cannot output a binary file to terminal. Use --output-file option."
|
|
623
|
-
)
|
|
621
|
+
raise UsageError("Cannot output a binary file to terminal. Use --output-file option.")
|
|
624
622
|
# in case a file is stdout,
|
|
625
623
|
# all triples from all graphs go in and other output is suppressed
|
|
626
624
|
app.echo_debug("output is stdout")
|
|
@@ -647,7 +645,7 @@ def validate_input_path(input_path: str) -> None:
|
|
|
647
645
|
"""Validate input path
|
|
648
646
|
|
|
649
647
|
This function checks the provided folder for any .ttl or .nt files
|
|
650
|
-
that have corresponding .gz files. If such files are found, it raises a
|
|
648
|
+
that have corresponding .gz files. If such files are found, it raises a ClickException.
|
|
651
649
|
"""
|
|
652
650
|
files = os.listdir(input_path)
|
|
653
651
|
|
|
@@ -659,7 +657,7 @@ def validate_input_path(input_path: str) -> None:
|
|
|
659
657
|
conflicting_files = [f for f in gz_files if f in files]
|
|
660
658
|
|
|
661
659
|
if conflicting_files:
|
|
662
|
-
raise
|
|
660
|
+
raise ClickException(
|
|
663
661
|
f"The following RDF files (.ttl/.nt) have corresponding '.gz' files,"
|
|
664
662
|
f" which is not allowed: {', '.join(conflicting_files)}"
|
|
665
663
|
)
|
|
@@ -755,7 +753,7 @@ def import_command(
|
|
|
755
753
|
Note: Directories are scanned on the first level only (not recursively).
|
|
756
754
|
"""
|
|
757
755
|
if replace and skip_existing:
|
|
758
|
-
raise
|
|
756
|
+
raise UsageError(
|
|
759
757
|
"The options --replace and --skip-existing are mutually "
|
|
760
758
|
"exclusive, so please remove one of them."
|
|
761
759
|
)
|
|
@@ -779,7 +777,7 @@ def import_command(
|
|
|
779
777
|
|
|
780
778
|
elif SmartPath(input_path).is_file():
|
|
781
779
|
if iri is None:
|
|
782
|
-
raise
|
|
780
|
+
raise UsageError(
|
|
783
781
|
"Either specify an input file AND a graph IRI or an input directory ONLY."
|
|
784
782
|
)
|
|
785
783
|
graphs = [(input_path, iri)]
|
|
@@ -878,7 +876,7 @@ def count_command(
|
|
|
878
876
|
Counts do not include imported graphs.
|
|
879
877
|
"""
|
|
880
878
|
if not iris and not all_:
|
|
881
|
-
raise
|
|
879
|
+
raise UsageError(
|
|
882
880
|
"Either specify at least one graph IRI " "or use the --all option to count all graphs."
|
|
883
881
|
)
|
|
884
882
|
if all_:
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""manual command for cmem command line interface"""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from click import Context, UsageError
|
|
5
|
+
|
|
6
|
+
from cmem_cmemc.command import CmemcCommand
|
|
7
|
+
from cmem_cmemc.manual_helper.graph import print_manual_graph
|
|
8
|
+
from cmem_cmemc.manual_helper.multi_page import create_multi_page_documentation
|
|
9
|
+
from cmem_cmemc.manual_helper.single_page import print_manual
|
|
10
|
+
from cmem_cmemc.parameter_types.path import ClickSmartPath
|
|
11
|
+
from cmem_cmemc.utils import get_version
|
|
12
|
+
|
|
13
|
+
FORMAT_MD_FILE = "markdown-single-page"
|
|
14
|
+
FORMAT_MD_DIRECTORY = "markdown-multi-page"
|
|
15
|
+
FORMAT_TURTLE_FILE = "turtle"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@click.command(cls=CmemcCommand, name="manual", hidden=True)
|
|
19
|
+
@click.option(
|
|
20
|
+
"--format",
|
|
21
|
+
"format_",
|
|
22
|
+
type=click.Choice([FORMAT_MD_FILE, FORMAT_MD_DIRECTORY, FORMAT_TURTLE_FILE]),
|
|
23
|
+
help=f"Output format. The '{FORMAT_TURTLE_FILE}' and '{FORMAT_MD_FILE}' formats will "
|
|
24
|
+
f"be returned to stdout. The '{FORMAT_MD_DIRECTORY}' format creates a directory tree "
|
|
25
|
+
f"(use '--output-dir' to specify the root directory)",
|
|
26
|
+
)
|
|
27
|
+
@click.option(
|
|
28
|
+
"--output-dir",
|
|
29
|
+
type=ClickSmartPath(writable=True, file_okay=False),
|
|
30
|
+
help=f"The output directory to create the '{FORMAT_MD_DIRECTORY}' documentation. "
|
|
31
|
+
f"Warning: Existing directories will be overwritten.",
|
|
32
|
+
)
|
|
33
|
+
@click.pass_context
|
|
34
|
+
def manual_command(ctx: Context, format_: str, output_dir: str) -> None:
|
|
35
|
+
"""Generate reference documentation assets.
|
|
36
|
+
|
|
37
|
+
This command generates the cmemc reference documentation from the help texts
|
|
38
|
+
and outputs it in different formats.
|
|
39
|
+
"""
|
|
40
|
+
if format_ is None:
|
|
41
|
+
raise UsageError(
|
|
42
|
+
"Please use the --format option to specify an output format. "
|
|
43
|
+
"Use --help for more information."
|
|
44
|
+
)
|
|
45
|
+
if format_ == FORMAT_TURTLE_FILE:
|
|
46
|
+
print_manual_graph(ctx.find_root(), get_version())
|
|
47
|
+
return
|
|
48
|
+
if format_ == FORMAT_MD_FILE:
|
|
49
|
+
print_manual(ctx.find_root())
|
|
50
|
+
return
|
|
51
|
+
if output_dir is None:
|
|
52
|
+
raise UsageError(
|
|
53
|
+
f"The output format '{FORMAT_MD_DIRECTORY}' needs an output directory "
|
|
54
|
+
f"('--output-dir'). Use --help for more information."
|
|
55
|
+
)
|
|
56
|
+
create_multi_page_documentation(ctx.find_root(), str(output_dir))
|
cmem_cmemc/commands/metrics.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""metrics commands for cmem command line interface."""
|
|
2
2
|
|
|
3
3
|
import click
|
|
4
|
-
from click import Argument, Context
|
|
4
|
+
from click import Argument, ClickException, Context, UsageError
|
|
5
5
|
from click.shell_completion import CompletionItem
|
|
6
6
|
from cmem.cmempy.api import request
|
|
7
7
|
from cmem.cmempy.config import get_cmem_base_uri, get_di_api_endpoint, get_dp_api_endpoint
|
|
@@ -11,7 +11,7 @@ from requests import HTTPError
|
|
|
11
11
|
from cmem_cmemc import completion
|
|
12
12
|
from cmem_cmemc.command import CmemcCommand
|
|
13
13
|
from cmem_cmemc.command_group import CmemcGroup
|
|
14
|
-
from cmem_cmemc.context import
|
|
14
|
+
from cmem_cmemc.context import ApplicationContext
|
|
15
15
|
from cmem_cmemc.object_list import (
|
|
16
16
|
DirectValuePropertyFilter,
|
|
17
17
|
ObjectList,
|
|
@@ -96,7 +96,7 @@ metrics_list = ObjectList(
|
|
|
96
96
|
|
|
97
97
|
def _complete_metrics_id(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]: # noqa: ARG001
|
|
98
98
|
"""Prepare a list of metric identifier."""
|
|
99
|
-
|
|
99
|
+
ApplicationContext.set_connection_from_params(ctx.find_root().params)
|
|
100
100
|
candidates = [(_["id"], _["documentation"]) for _ in metrics_list.apply_filters(ctx=ctx)]
|
|
101
101
|
return completion.finalize_completion(candidates=candidates, incomplete=incomplete)
|
|
102
102
|
|
|
@@ -107,7 +107,7 @@ def _complete_metric_label_filter(
|
|
|
107
107
|
incomplete: str,
|
|
108
108
|
) -> list[CompletionItem]:
|
|
109
109
|
"""Prepare a list of label name or values"""
|
|
110
|
-
|
|
110
|
+
ApplicationContext.set_connection_from_params(ctx.find_root().params)
|
|
111
111
|
args = completion.get_completion_args(incomplete)
|
|
112
112
|
incomplete = incomplete.lower()
|
|
113
113
|
options: list[str] = []
|
|
@@ -141,11 +141,11 @@ def _filter_samples(family: dict, label_filter: tuple[tuple[str, str], ...]) ->
|
|
|
141
141
|
sample_labels = sample[1]
|
|
142
142
|
for name, value in label_filter:
|
|
143
143
|
if name not in labels:
|
|
144
|
-
raise
|
|
144
|
+
raise ClickException(
|
|
145
145
|
f"The metric '{family_name}' does " f"not have a label named '{name}'."
|
|
146
146
|
)
|
|
147
147
|
if value not in labels[name]:
|
|
148
|
-
raise
|
|
148
|
+
raise ClickException(
|
|
149
149
|
f"The metric '{family_name}' does "
|
|
150
150
|
f"not have a label '{name}' with the value '{value}'."
|
|
151
151
|
)
|
|
@@ -200,12 +200,12 @@ def get_command(
|
|
|
200
200
|
app = ctx.obj
|
|
201
201
|
data = metrics_list.apply_filters(ctx=ctx, filter_=[("id", metric_id)])
|
|
202
202
|
if len(data) == 0:
|
|
203
|
-
raise
|
|
203
|
+
raise UsageError(
|
|
204
204
|
f"No metric with ID '{metric_id}' found. "
|
|
205
205
|
"Use the `metrics list` command to list all metrics."
|
|
206
206
|
)
|
|
207
207
|
if len(data) > 1:
|
|
208
|
-
raise
|
|
208
|
+
raise UsageError("Unknown Error - More than one metric with ID '{metric_id}' found.")
|
|
209
209
|
metric = data[0]
|
|
210
210
|
|
|
211
211
|
samples = _filter_samples(metric, label_filter)
|
|
@@ -214,7 +214,7 @@ def get_command(
|
|
|
214
214
|
return
|
|
215
215
|
|
|
216
216
|
if len(samples) == 0:
|
|
217
|
-
raise
|
|
217
|
+
raise ClickException(
|
|
218
218
|
"No data - the given label combination filtered out "
|
|
219
219
|
f"all available samples of the metric {metric_id}."
|
|
220
220
|
)
|
|
@@ -248,12 +248,12 @@ def inspect_command(ctx: Context, metric_id: str, raw: bool) -> None:
|
|
|
248
248
|
app = ctx.obj
|
|
249
249
|
data = metrics_list.apply_filters(ctx=ctx, filter_=[("id", metric_id)])
|
|
250
250
|
if len(data) == 0:
|
|
251
|
-
raise
|
|
251
|
+
raise UsageError(
|
|
252
252
|
f"No metric with ID '{metric_id}' found. "
|
|
253
253
|
"Use the `metrics list` command to list all metrics."
|
|
254
254
|
)
|
|
255
255
|
if len(data) > 1:
|
|
256
|
-
raise
|
|
256
|
+
raise UsageError("Unknown Error - More than one metric with ID '{metric_id}' found.")
|
|
257
257
|
if raw:
|
|
258
258
|
app.echo_info_json(data)
|
|
259
259
|
return
|
cmem_cmemc/commands/migration.py
CHANGED
|
@@ -9,12 +9,13 @@ from click.shell_completion import CompletionItem
|
|
|
9
9
|
from cmem_cmemc.command import CmemcCommand
|
|
10
10
|
from cmem_cmemc.command_group import CmemcGroup
|
|
11
11
|
from cmem_cmemc.completion import check_option_in_params, finalize_completion
|
|
12
|
-
from cmem_cmemc.context import
|
|
12
|
+
from cmem_cmemc.context import ApplicationContext
|
|
13
13
|
from cmem_cmemc.migrations.access_conditions_243 import (
|
|
14
14
|
MoveAccessConditionsToNewGraph,
|
|
15
15
|
RenameAuthVocabularyResources,
|
|
16
16
|
)
|
|
17
17
|
from cmem_cmemc.migrations.bootstrap_data import MigrateBootstrapData
|
|
18
|
+
from cmem_cmemc.migrations.remove_noop_triple_251 import RemoveHideHeaderFooterStatements
|
|
18
19
|
from cmem_cmemc.migrations.shapes_widget_integrations_243 import (
|
|
19
20
|
ChartsOnNodeShapesToToWidgetIntegrations,
|
|
20
21
|
ChartsOnPropertyShapesToWidgetIntegrations,
|
|
@@ -32,7 +33,6 @@ from cmem_cmemc.object_list import (
|
|
|
32
33
|
)
|
|
33
34
|
|
|
34
35
|
if TYPE_CHECKING:
|
|
35
|
-
from cmem_cmemc.context import ApplicationContext
|
|
36
36
|
from cmem_cmemc.migrations.abc import MigrationRecipe
|
|
37
37
|
|
|
38
38
|
|
|
@@ -58,6 +58,7 @@ def get_migrations(ctx: click.Context) -> list[dict]: # noqa: ARG001
|
|
|
58
58
|
WorkflowTriggerPropertyShapesToWidgetIntegrations(),
|
|
59
59
|
TableReportPropertyShapesToWidgetIntegrations(),
|
|
60
60
|
SparqlDatatypesToXsdString(),
|
|
61
|
+
RemoveHideHeaderFooterStatements(),
|
|
61
62
|
]
|
|
62
63
|
]
|
|
63
64
|
data.sort(key=lambda x: x["first_version"])
|
|
@@ -66,7 +67,7 @@ def get_migrations(ctx: click.Context) -> list[dict]: # noqa: ARG001
|
|
|
66
67
|
|
|
67
68
|
def complete_migration_ids(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]:
|
|
68
69
|
"""Prepare a list of migration recipe IDs"""
|
|
69
|
-
|
|
70
|
+
ApplicationContext.set_connection_from_params(ctx.find_root().params)
|
|
70
71
|
options = []
|
|
71
72
|
for _ in get_migrations(ctx=ctx):
|
|
72
73
|
id_ = _["id"]
|
cmem_cmemc/commands/project.py
CHANGED
|
@@ -7,7 +7,7 @@ import tempfile
|
|
|
7
7
|
from zipfile import ZipFile
|
|
8
8
|
|
|
9
9
|
import click
|
|
10
|
-
from click import UsageError
|
|
10
|
+
from click import ClickException, UsageError
|
|
11
11
|
from cmem.cmempy.config import get_di_api_endpoint
|
|
12
12
|
from cmem.cmempy.plugins.marshalling import (
|
|
13
13
|
get_extension_by_plugin,
|
|
@@ -44,7 +44,7 @@ def _validate_projects_to_process(project_ids: tuple[str], all_flag: bool) -> li
|
|
|
44
44
|
list of IDs is without duplicates, and validated if they exist
|
|
45
45
|
"""
|
|
46
46
|
if project_ids == () and not all_flag:
|
|
47
|
-
raise
|
|
47
|
+
raise UsageError(
|
|
48
48
|
"Either specify at least one project ID "
|
|
49
49
|
"or use the --all option to process over all projects."
|
|
50
50
|
)
|
|
@@ -59,7 +59,7 @@ def _validate_projects_to_process(project_ids: tuple[str], all_flag: bool) -> li
|
|
|
59
59
|
# test if one of the projects does NOT exist
|
|
60
60
|
for _ in projects_to_process:
|
|
61
61
|
if _ not in all_projects:
|
|
62
|
-
raise
|
|
62
|
+
raise ClickException(f"Project {_} does not exist.")
|
|
63
63
|
return projects_to_process
|
|
64
64
|
|
|
65
65
|
|
|
@@ -104,7 +104,7 @@ def open_command(app: ApplicationContext, project_ids: tuple[str]) -> None:
|
|
|
104
104
|
projects = get_projects()
|
|
105
105
|
for _ in project_ids:
|
|
106
106
|
if _ not in (p["name"] for p in projects):
|
|
107
|
-
raise
|
|
107
|
+
raise ClickException(f"Project '{_}' not found.")
|
|
108
108
|
open_project_uri = f"{get_di_api_endpoint()}/workbench/projects/{_}"
|
|
109
109
|
app.echo_debug(f"Open {_}: {open_project_uri}")
|
|
110
110
|
click.launch(open_project_uri)
|
|
@@ -221,7 +221,7 @@ def create_command(
|
|
|
221
221
|
Note: Projects can be listed by using the `project list` command.
|
|
222
222
|
"""
|
|
223
223
|
if from_transformation and len(project_ids) > 1:
|
|
224
|
-
raise
|
|
224
|
+
raise UsageError(
|
|
225
225
|
"By using --from-transformation,"
|
|
226
226
|
" the project ID parameter is limited to a single project ID."
|
|
227
227
|
)
|
|
@@ -229,7 +229,7 @@ def create_command(
|
|
|
229
229
|
all_projects = [_["name"] for _ in get_projects()]
|
|
230
230
|
for project_id in project_ids:
|
|
231
231
|
if project_id in all_projects:
|
|
232
|
-
raise
|
|
232
|
+
raise ClickException(f"Project {project_id} already exists.")
|
|
233
233
|
|
|
234
234
|
if from_transformation:
|
|
235
235
|
transformation_parts = from_transformation.split(":")
|
|
@@ -385,7 +385,7 @@ def export_command( # noqa: PLR0913
|
|
|
385
385
|
|
|
386
386
|
extractable_types = ("xmlZip", "xmlZipWithoutResources")
|
|
387
387
|
if extract and marshalling_plugin not in extractable_types:
|
|
388
|
-
raise
|
|
388
|
+
raise UsageError(
|
|
389
389
|
f"The export type {marshalling_plugin} can not be extracted. "
|
|
390
390
|
f"Use one of {extractable_types}."
|
|
391
391
|
)
|
|
@@ -480,12 +480,12 @@ def import_command(
|
|
|
480
480
|
|
|
481
481
|
all_projects = get_projects()
|
|
482
482
|
if project_id and not replace and project_id in ([_["name"] for _ in all_projects]):
|
|
483
|
-
raise
|
|
483
|
+
raise ClickException(f"Project {project_id} is already there.")
|
|
484
484
|
|
|
485
485
|
if Path(path).is_dir():
|
|
486
486
|
if not (Path(path) / "config.xml").is_file():
|
|
487
487
|
# fail early if directory is not an export
|
|
488
|
-
raise
|
|
488
|
+
raise ClickException(f"Directory {path} seems not to be a export directory.")
|
|
489
489
|
|
|
490
490
|
app.echo_info(f"Import directory {path} to project {project_id} ... ", nl=False)
|
|
491
491
|
# in case of a directory, we zip it to a temp file
|
|
@@ -512,7 +512,7 @@ def import_command(
|
|
|
512
512
|
# Remove the temporary file
|
|
513
513
|
pathlib.Path.unlink(pathlib.Path(uploaded_file))
|
|
514
514
|
if "errorMessage" in validation_response:
|
|
515
|
-
raise
|
|
515
|
+
raise ClickException(validation_response["errorMessage"])
|
|
516
516
|
import_id = validation_response["projectImportId"]
|
|
517
517
|
|
|
518
518
|
# get project_id from response if not given as parameter
|
cmem_cmemc/commands/python.py
CHANGED
|
@@ -5,7 +5,7 @@ from dataclasses import asdict
|
|
|
5
5
|
from re import match
|
|
6
6
|
|
|
7
7
|
import click
|
|
8
|
-
from click import UsageError
|
|
8
|
+
from click import ClickException, UsageError
|
|
9
9
|
from cmem.cmempy.workspace.python import (
|
|
10
10
|
install_package_by_file,
|
|
11
11
|
install_package_by_name,
|
|
@@ -60,7 +60,7 @@ def install_command(app: ApplicationContext, package: str) -> None:
|
|
|
60
60
|
install_response = install_package_by_file(package_file=package)
|
|
61
61
|
except FileNotFoundError as not_found_error:
|
|
62
62
|
if not _looks_like_a_package(package):
|
|
63
|
-
raise
|
|
63
|
+
raise ClickException(
|
|
64
64
|
f"{package} does not look like a package name or requirement "
|
|
65
65
|
"string, and a file with this name also does not exists."
|
|
66
66
|
) from not_found_error
|
|
@@ -231,6 +231,12 @@ def list_plugins_command(
|
|
|
231
231
|
except TypeError:
|
|
232
232
|
plugins = raw_output # DI <= 22.1 output
|
|
233
233
|
|
|
234
|
+
if not all(plugin.get("isRegistered", True) for plugin in plugins):
|
|
235
|
+
app.echo_warning(
|
|
236
|
+
"Some plugins are installed but not registered. "
|
|
237
|
+
"Use the 'admin workspace python reload' command to register all installed plugins."
|
|
238
|
+
)
|
|
239
|
+
|
|
234
240
|
if raw:
|
|
235
241
|
app.echo_info_json(plugins)
|
|
236
242
|
return
|
|
@@ -286,6 +292,26 @@ def open_command(app: ApplicationContext, package: str) -> None:
|
|
|
286
292
|
click.launch(full_url)
|
|
287
293
|
|
|
288
294
|
|
|
295
|
+
@click.command(cls=CmemcCommand, name="reload")
|
|
296
|
+
@click.pass_obj
|
|
297
|
+
def reload_command(app: ApplicationContext) -> None:
|
|
298
|
+
"""Reload / Register all installed plugins.
|
|
299
|
+
|
|
300
|
+
This command will register all installed plugins into the DataIntegration workspace.
|
|
301
|
+
This command is useful, when you are installing packages
|
|
302
|
+
into the DataIntegration Python environment without using the provided cmemc
|
|
303
|
+
commands (e.g. by mounting a prepared filesystem in the docker container).
|
|
304
|
+
"""
|
|
305
|
+
app.echo_info("Reloading python packages ... ", nl=False)
|
|
306
|
+
update_plugin_response = update_plugins()
|
|
307
|
+
app.echo_debug(f"Updated Plugins: {update_plugin_response!s}")
|
|
308
|
+
update_errors = update_plugin_response.get("errors", [])
|
|
309
|
+
if len(update_errors) == 0:
|
|
310
|
+
app.echo_success("done")
|
|
311
|
+
else:
|
|
312
|
+
app.echo_error("error (use --debug for more information)")
|
|
313
|
+
|
|
314
|
+
|
|
289
315
|
@click.group(cls=CmemcGroup)
|
|
290
316
|
def python() -> CmemcGroup: # type: ignore[empty-body]
|
|
291
317
|
"""List, install, or uninstall python packages.
|
|
@@ -304,3 +330,4 @@ python.add_command(uninstall_command)
|
|
|
304
330
|
python.add_command(list_command)
|
|
305
331
|
python.add_command(list_plugins_command)
|
|
306
332
|
python.add_command(open_command)
|
|
333
|
+
python.add_command(reload_command)
|
cmem_cmemc/commands/query.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""query commands for cmem command line interface."""
|
|
2
2
|
|
|
3
|
+
import csv
|
|
3
4
|
import json
|
|
4
5
|
import sys
|
|
5
6
|
from hashlib import sha1
|
|
@@ -9,7 +10,7 @@ from time import sleep, time
|
|
|
9
10
|
from uuid import uuid4
|
|
10
11
|
|
|
11
12
|
import click
|
|
12
|
-
from click import UsageError
|
|
13
|
+
from click import ClickException, UsageError
|
|
13
14
|
from click.shell_completion import CompletionItem
|
|
14
15
|
from cmem.cmempy.queries import (
|
|
15
16
|
QueryCatalog,
|
|
@@ -90,12 +91,14 @@ class ReplayStatistics:
|
|
|
90
91
|
iri = query_["iri"]
|
|
91
92
|
catalog_entry = self.catalog.get_query(iri)
|
|
92
93
|
if catalog_entry is None:
|
|
93
|
-
raise
|
|
94
|
+
raise ClickException(f"measure_query - query {iri} is not in catalog.")
|
|
94
95
|
return catalog_entry
|
|
95
96
|
query_string = query_["queryString"]
|
|
96
97
|
return SparqlQuery(text=query_string)
|
|
97
98
|
except KeyError as error:
|
|
98
|
-
raise
|
|
99
|
+
raise ClickException(
|
|
100
|
+
"measure_query - given input dict has no queryString key."
|
|
101
|
+
) from error
|
|
99
102
|
|
|
100
103
|
def _update_statistic_on_success(self, duration: int) -> None:
|
|
101
104
|
"""Update statistics and counters."""
|
|
@@ -368,11 +371,9 @@ def list_command(app: ApplicationContext, id_only: bool) -> None:
|
|
|
368
371
|
"--accept",
|
|
369
372
|
default="default",
|
|
370
373
|
show_default=True,
|
|
374
|
+
shell_complete=completion.sparql_accept_types,
|
|
371
375
|
help="Accept header for the HTTP request(s). Setting this to 'default' "
|
|
372
|
-
"means that cmemc uses an appropriate
|
|
373
|
-
"output (text/csv for tables, text/turtle for graphs, * otherwise). "
|
|
374
|
-
"Please refer to the Corporate Memory system manual for a list "
|
|
375
|
-
"of accepted mime types.",
|
|
376
|
+
"means that cmemc uses an appropriate output for terminals.",
|
|
376
377
|
)
|
|
377
378
|
@click.option(
|
|
378
379
|
"--no-imports",
|
|
@@ -445,16 +446,16 @@ def execute_command( # noqa: PLR0913
|
|
|
445
446
|
placeholder = {}
|
|
446
447
|
for key, value in parameter:
|
|
447
448
|
if key in placeholder:
|
|
448
|
-
raise
|
|
449
|
+
raise click.UsageError(
|
|
449
450
|
"Parameter can be given only once, " f"Value for '{key}' was given twice."
|
|
450
451
|
)
|
|
451
452
|
placeholder[key] = value
|
|
452
453
|
app.echo_debug("Parameter: " + str(placeholder))
|
|
453
454
|
for file_or_uri in queries:
|
|
454
455
|
app.echo_debug(f"Start of execution: {file_or_uri} with " f"placeholder {placeholder}")
|
|
455
|
-
executed_query = QueryCatalog().get_query(file_or_uri, placeholder=placeholder)
|
|
456
|
+
executed_query: SparqlQuery = QueryCatalog().get_query(file_or_uri, placeholder=placeholder)
|
|
456
457
|
if executed_query is None:
|
|
457
|
-
raise
|
|
458
|
+
raise ClickException(f"{file_or_uri} is neither a (readable) file nor a query URI.")
|
|
458
459
|
app.echo_debug(
|
|
459
460
|
f"Execute ({executed_query.query_type}): "
|
|
460
461
|
f"{executed_query.label} < {executed_query.url}"
|
|
@@ -475,7 +476,23 @@ def execute_command( # noqa: PLR0913
|
|
|
475
476
|
offset=offset,
|
|
476
477
|
timeout=timeout,
|
|
477
478
|
)
|
|
478
|
-
|
|
479
|
+
if accept == "default" and submitted_accept == "text/csv":
|
|
480
|
+
csv_reader = csv.reader(results.splitlines(), delimiter=",", quotechar='"')
|
|
481
|
+
table = []
|
|
482
|
+
headers = []
|
|
483
|
+
for index, row in enumerate(csv_reader):
|
|
484
|
+
if index == 0:
|
|
485
|
+
headers = row
|
|
486
|
+
else:
|
|
487
|
+
table.append(row)
|
|
488
|
+
app.echo_info_table(
|
|
489
|
+
table,
|
|
490
|
+
headers=headers,
|
|
491
|
+
empty_table_message="No results for this query.",
|
|
492
|
+
caption=f"Query results: {executed_query.label}",
|
|
493
|
+
)
|
|
494
|
+
else:
|
|
495
|
+
app.echo_result(results)
|
|
479
496
|
|
|
480
497
|
|
|
481
498
|
@click.command(cls=CmemcCommand, name="open")
|
|
@@ -497,7 +514,7 @@ def open_command(app: ApplicationContext, queries: tuple[str, ...]) -> None:
|
|
|
497
514
|
for file_or_uri in queries:
|
|
498
515
|
opened_query = QueryCatalog().get_query(file_or_uri)
|
|
499
516
|
if opened_query is None:
|
|
500
|
-
raise
|
|
517
|
+
raise ClickException(f"{file_or_uri} is neither a (readable) file nor a query URI.")
|
|
501
518
|
open_query_uri = opened_query.get_editor_url()
|
|
502
519
|
app.echo_debug(f"Open {file_or_uri}: {open_query_uri}")
|
|
503
520
|
click.launch(open_query_uri)
|
|
@@ -653,9 +670,9 @@ def replay_command( # noqa: PLR0913
|
|
|
653
670
|
with Path(replay_file).open(encoding="utf8") as _:
|
|
654
671
|
input_queries = load(_)
|
|
655
672
|
except JSONDecodeError as error:
|
|
656
|
-
raise
|
|
673
|
+
raise ClickException(f"File {replay_file} is not a valid JSON document.") from error
|
|
657
674
|
if len(input_queries) == 0:
|
|
658
|
-
raise
|
|
675
|
+
raise ClickException(f"File {replay_file} contains no queries.")
|
|
659
676
|
app.echo_debug(f"File {replay_file} contains {len(input_queries)} queries.")
|
|
660
677
|
|
|
661
678
|
statistic = ReplayStatistics(app=app, label=run_label)
|
cmem_cmemc/commands/resource.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import re
|
|
4
4
|
|
|
5
5
|
import click
|
|
6
|
+
from click import ClickException, UsageError
|
|
6
7
|
from cmem.cmempy.workspace.projects.resources import get_all_resources
|
|
7
8
|
from cmem.cmempy.workspace.projects.resources.resource import (
|
|
8
9
|
delete_resource,
|
|
@@ -32,7 +33,7 @@ def _get_resources_filtered(
|
|
|
32
33
|
"""Get file resources but filtered according to name and value."""
|
|
33
34
|
# check for correct filter names (filter ids is used internally only)
|
|
34
35
|
if filter_name not in RESOURCE_FILTER_TYPES + RESOURCE_FILTER_TYPES_HIDDEN:
|
|
35
|
-
raise
|
|
36
|
+
raise UsageError(
|
|
36
37
|
f"{filter_name} is an unknown filter name. " f"Use one of {RESOURCE_FILTER_TYPES}."
|
|
37
38
|
)
|
|
38
39
|
# filter by ID list
|
|
@@ -136,7 +137,7 @@ def delete_command(
|
|
|
136
137
|
all resources.
|
|
137
138
|
"""
|
|
138
139
|
if resource_ids == () and not all_ and filters_ == ():
|
|
139
|
-
raise
|
|
140
|
+
raise UsageError(
|
|
140
141
|
"Either specify at least one resource ID or use the --all or "
|
|
141
142
|
"--filter options to specify resources for deletion."
|
|
142
143
|
)
|
|
@@ -145,7 +146,7 @@ def delete_command(
|
|
|
145
146
|
if len(resource_ids) > 0:
|
|
146
147
|
for resource_id in resource_ids:
|
|
147
148
|
if resource_id not in [_["id"] for _ in resources]:
|
|
148
|
-
raise
|
|
149
|
+
raise ClickException(f"Resource {resource_id} not available.")
|
|
149
150
|
# "filter" by id
|
|
150
151
|
resources = _get_resources_filtered(resources, "ids", resource_ids)
|
|
151
152
|
for _ in filters_:
|