cmem-cmemc 25.5.0rc1__py3-none-any.whl → 25.6.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/command_group.py +32 -31
- cmem_cmemc/commands/acl.py +15 -6
- cmem_cmemc/commands/dataset.py +1 -54
- cmem_cmemc/commands/file.py +465 -0
- cmem_cmemc/commands/graph_insights.py +103 -35
- cmem_cmemc/commands/project.py +3 -1
- cmem_cmemc/commands/python.py +1 -1
- cmem_cmemc/commands/vocabulary.py +13 -4
- cmem_cmemc/completion.py +22 -5
- cmem_cmemc/context.py +2 -2
- cmem_cmemc/manual_helper/graph.py +2 -2
- cmem_cmemc/manual_helper/multi_page.py +5 -6
- cmem_cmemc/placeholder.py +2 -2
- cmem_cmemc/string_processor.py +12 -0
- cmem_cmemc/utils.py +1 -1
- {cmem_cmemc-25.5.0rc1.dist-info → cmem_cmemc-25.6.0.dist-info}/METADATA +2 -1
- {cmem_cmemc-25.5.0rc1.dist-info → cmem_cmemc-25.6.0.dist-info}/RECORD +20 -20
- cmem_cmemc/commands/resource.py +0 -220
- {cmem_cmemc-25.5.0rc1.dist-info → cmem_cmemc-25.6.0.dist-info}/WHEEL +0 -0
- {cmem_cmemc-25.5.0rc1.dist-info → cmem_cmemc-25.6.0.dist-info}/entry_points.txt +0 -0
- {cmem_cmemc-25.5.0rc1.dist-info → cmem_cmemc-25.6.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Graph Insights command group"""
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import time
|
|
4
4
|
|
|
5
5
|
import click
|
|
6
6
|
from click import Argument, Context
|
|
@@ -12,6 +12,7 @@ from requests import HTTPError
|
|
|
12
12
|
from cmem_cmemc.command import CmemcCommand
|
|
13
13
|
from cmem_cmemc.command_group import CmemcGroup
|
|
14
14
|
from cmem_cmemc.completion import NOT_SORTED, finalize_completion, graph_uris
|
|
15
|
+
from cmem_cmemc.context import ApplicationContext
|
|
15
16
|
from cmem_cmemc.exceptions import CmemcError
|
|
16
17
|
from cmem_cmemc.object_list import (
|
|
17
18
|
DirectListPropertyFilter,
|
|
@@ -22,11 +23,30 @@ from cmem_cmemc.object_list import (
|
|
|
22
23
|
from cmem_cmemc.string_processor import GraphLink, TimeAgo
|
|
23
24
|
from cmem_cmemc.utils import get_graphs_as_dict, struct_to_table
|
|
24
25
|
|
|
25
|
-
if TYPE_CHECKING:
|
|
26
|
-
from cmem_cmemc.context import ApplicationContext
|
|
27
26
|
|
|
27
|
+
def get_api_url(path: str = "") -> str:
|
|
28
|
+
"""Get URLs of the graph insights API.
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
Constructs the full URL for accessing graph insights API endpoints by combining
|
|
31
|
+
the DataPlatform API endpoint with the semspect extension base path and an
|
|
32
|
+
optional resource path.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
path: The API resource path to append to the base URL. Defaults to an empty
|
|
36
|
+
string for the root endpoint.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
The complete URL for the specified graph insights API endpoint.
|
|
40
|
+
|
|
41
|
+
Example:
|
|
42
|
+
>>> get_api_url()
|
|
43
|
+
'https://example.com/dataplatform/api/ext/semspect'
|
|
44
|
+
>>> get_api_url("/snapshot/status")
|
|
45
|
+
'https://example.com/dataplatform/api/ext/semspect/snapshot/status'
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
base_url = get_dp_api_endpoint() + "/api/ext/semspect"
|
|
49
|
+
return f"{base_url}{path}"
|
|
30
50
|
|
|
31
51
|
|
|
32
52
|
def is_available() -> bool:
|
|
@@ -38,7 +58,7 @@ def is_available() -> bool:
|
|
|
38
58
|
}
|
|
39
59
|
"""
|
|
40
60
|
try:
|
|
41
|
-
data: dict[str, bool] = get_json(
|
|
61
|
+
data: dict[str, bool] = get_json(get_api_url())
|
|
42
62
|
except HTTPError:
|
|
43
63
|
return False
|
|
44
64
|
return bool(data["isActive"] is True and data["isUserAllowed"] is True)
|
|
@@ -56,13 +76,16 @@ def get_snapshots(ctx: click.Context) -> list[dict[str, str | bool | list[str]]]
|
|
|
56
76
|
"""Get the snapshot list (all snapshots)"""
|
|
57
77
|
check_availability(ctx)
|
|
58
78
|
data: list[dict[str, str | bool | list[str]]] = get_json(
|
|
59
|
-
|
|
79
|
+
get_api_url("/snapshot/status"), params={"includeManagementOnly": True}
|
|
60
80
|
)
|
|
61
81
|
return data
|
|
62
82
|
|
|
63
83
|
|
|
64
84
|
def complete_snapshot_ids(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]: # noqa: ARG001
|
|
65
85
|
"""Provide auto-completion for snapshot Ids"""
|
|
86
|
+
ApplicationContext.set_connection_from_params(ctx.find_root().params)
|
|
87
|
+
if not is_available():
|
|
88
|
+
return []
|
|
66
89
|
snapshots = get_snapshots(ctx)
|
|
67
90
|
snapshots = sorted(
|
|
68
91
|
snapshots, key=lambda snapshot: snapshot["updateInfoTimestamp"], reverse=True
|
|
@@ -102,6 +125,12 @@ snapshot_list = ObjectList(
|
|
|
102
125
|
description="Snapshots with a specific affected graph (main or sub-graphs).",
|
|
103
126
|
property_key="allGraphsSynced",
|
|
104
127
|
),
|
|
128
|
+
DirectValuePropertyFilter(
|
|
129
|
+
name="valid",
|
|
130
|
+
description="Snapshots with a specific validity indicator.",
|
|
131
|
+
property_key="isValid",
|
|
132
|
+
transform=transform_lower,
|
|
133
|
+
),
|
|
105
134
|
],
|
|
106
135
|
)
|
|
107
136
|
|
|
@@ -147,13 +176,14 @@ def list_command(ctx: Context, filter_: tuple[tuple[str, str]], id_only: bool, r
|
|
|
147
176
|
main_graph = _["mainGraphSynced"]
|
|
148
177
|
updated = _["updateInfoTimestamp"]
|
|
149
178
|
status = _["status"]
|
|
179
|
+
is_valid = _["isValid"]
|
|
150
180
|
if main_graph not in graphs:
|
|
151
181
|
main_graph = rf"\[missing: {main_graph}]"
|
|
152
|
-
table.append([id_, main_graph, updated, status])
|
|
182
|
+
table.append([id_, main_graph, updated, status, is_valid])
|
|
153
183
|
|
|
154
184
|
app.echo_info_table(
|
|
155
185
|
table,
|
|
156
|
-
headers=["ID", "Main Graph", "Updated", "Status"],
|
|
186
|
+
headers=["ID", "Main Graph", "Updated", "Status", "Valid"],
|
|
157
187
|
sort_column=0,
|
|
158
188
|
cell_processing={1: GraphLink(), 2: TimeAgo()},
|
|
159
189
|
empty_table_message="No graph insight snapshots found.",
|
|
@@ -188,34 +218,52 @@ def delete_command(
|
|
|
188
218
|
|
|
189
219
|
if all_:
|
|
190
220
|
app.echo_info("Deleting all snapshots ... ", nl=False)
|
|
191
|
-
request(method="DELETE", uri=
|
|
221
|
+
request(method="DELETE", uri=get_api_url("/snapshot"))
|
|
192
222
|
app.echo_success("done")
|
|
193
223
|
return
|
|
194
224
|
|
|
195
|
-
all_snapshots = get_snapshots(ctx)
|
|
196
|
-
all_snapshot_ids = [_["databaseId"] for _ in all_snapshots]
|
|
197
225
|
filter_to_apply = list(filter_) if filter_ else []
|
|
198
226
|
if snapshot_id:
|
|
199
227
|
filter_to_apply.append(("id", snapshot_id))
|
|
200
228
|
snapshots_to_delete = snapshot_list.apply_filters(ctx=ctx, filter_=filter_to_apply)
|
|
201
|
-
if not snapshots_to_delete:
|
|
202
|
-
raise click.UsageError("No snapshots found to delete.")
|
|
203
229
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
230
|
+
if not snapshots_to_delete and snapshot_id:
|
|
231
|
+
raise click.ClickException(f"Snapshot ID '{snapshot_id}' does not exist.")
|
|
232
|
+
|
|
233
|
+
if not snapshots_to_delete and not snapshot_id:
|
|
234
|
+
raise click.ClickException("No snapshots found to delete.")
|
|
235
|
+
|
|
208
236
|
for _ in snapshots_to_delete:
|
|
209
237
|
id_to_delete = _["databaseId"]
|
|
210
238
|
app.echo_info(f"Deleting snapshot {id_to_delete} ... ", nl=False)
|
|
211
|
-
request(method="DELETE", uri=f"
|
|
239
|
+
request(method="DELETE", uri=get_api_url(f"/snapshot/{id_to_delete}"))
|
|
212
240
|
app.echo_success("done")
|
|
213
241
|
|
|
214
242
|
|
|
243
|
+
def wait_for_snapshot(snapshot_id: str, polling_interval: int) -> None:
|
|
244
|
+
"""Poll until the snapshot reaches 'DONE' status."""
|
|
245
|
+
while True:
|
|
246
|
+
snapshot: dict[str, str | bool | list[str]] = get_json(
|
|
247
|
+
get_api_url(f"/snapshot/status/{snapshot_id}")
|
|
248
|
+
)
|
|
249
|
+
if snapshot.get("status") == "DONE":
|
|
250
|
+
break
|
|
251
|
+
time.sleep(polling_interval)
|
|
252
|
+
|
|
253
|
+
|
|
215
254
|
@click.command(cls=CmemcCommand, name="create")
|
|
216
255
|
@click.argument("iri", type=click.STRING, shell_complete=graph_uris)
|
|
256
|
+
@click.option("--wait", is_flag=True, help="Wait until snapshot creation is done.")
|
|
257
|
+
@click.option(
|
|
258
|
+
"--polling-interval",
|
|
259
|
+
type=click.IntRange(min=0, max=60),
|
|
260
|
+
show_default=True,
|
|
261
|
+
default=1,
|
|
262
|
+
help="How many seconds to wait between status polls. Status polls are"
|
|
263
|
+
" cheap, so a higher polling interval is most likely not needed.",
|
|
264
|
+
)
|
|
217
265
|
@click.pass_context
|
|
218
|
-
def create_command(ctx: Context, iri: str) -> None:
|
|
266
|
+
def create_command(ctx: Context, iri: str, wait: bool, polling_interval: int) -> None:
|
|
219
267
|
"""Create or update a graph insight snapshot.
|
|
220
268
|
|
|
221
269
|
Create a graph insight snapshot for a given graph.
|
|
@@ -225,8 +273,14 @@ def create_command(ctx: Context, iri: str) -> None:
|
|
|
225
273
|
check_availability(ctx)
|
|
226
274
|
app: ApplicationContext = ctx.obj
|
|
227
275
|
app.echo_info(f"Create / Update graph snapshot for graph {iri} ... ", nl=False)
|
|
228
|
-
|
|
229
|
-
|
|
276
|
+
snapshot_id = request(
|
|
277
|
+
method="POST", uri=get_api_url("/snapshot"), params={"contextGraph": iri}
|
|
278
|
+
).text
|
|
279
|
+
app.echo_success("started", nl=not wait)
|
|
280
|
+
if wait:
|
|
281
|
+
app.echo_info(" ... ", nl=False)
|
|
282
|
+
wait_for_snapshot(snapshot_id, polling_interval)
|
|
283
|
+
app.echo_success("created")
|
|
230
284
|
|
|
231
285
|
|
|
232
286
|
@click.command(cls=CmemcCommand, name="update")
|
|
@@ -240,9 +294,23 @@ def create_command(ctx: Context, iri: str) -> None:
|
|
|
240
294
|
multiple=True,
|
|
241
295
|
)
|
|
242
296
|
@click.option("-a", "--all", "all_", is_flag=True, help="Delete all snapshots.")
|
|
297
|
+
@click.option("--wait", is_flag=True, help="Wait until snapshot creation is done.")
|
|
298
|
+
@click.option(
|
|
299
|
+
"--polling-interval",
|
|
300
|
+
type=click.IntRange(min=0, max=60),
|
|
301
|
+
show_default=True,
|
|
302
|
+
default=1,
|
|
303
|
+
help="How many seconds to wait between status polls. Status polls are"
|
|
304
|
+
" cheap, so a higher polling interval is most likely not needed.",
|
|
305
|
+
)
|
|
243
306
|
@click.pass_context
|
|
244
|
-
def update_command(
|
|
245
|
-
ctx: Context,
|
|
307
|
+
def update_command( # noqa: PLR0913
|
|
308
|
+
ctx: Context,
|
|
309
|
+
snapshot_id: str | None,
|
|
310
|
+
filter_: tuple[tuple[str, str]],
|
|
311
|
+
all_: bool,
|
|
312
|
+
wait: bool,
|
|
313
|
+
polling_interval: int,
|
|
246
314
|
) -> None:
|
|
247
315
|
"""Update a graph insight snapshot.
|
|
248
316
|
|
|
@@ -259,24 +327,24 @@ def update_command(
|
|
|
259
327
|
filter_to_apply.append(("id", snapshot_id))
|
|
260
328
|
snapshots_to_update = snapshot_list.apply_filters(ctx=ctx, filter_=filter_to_apply)
|
|
261
329
|
|
|
262
|
-
all_snapshots = get_snapshots(ctx)
|
|
263
|
-
all_snapshot_ids = [_["databaseId"] for _ in all_snapshots]
|
|
264
|
-
|
|
265
330
|
if all_:
|
|
266
331
|
snapshots_to_update = get_snapshots(ctx)
|
|
267
332
|
|
|
268
|
-
if not snapshots_to_update:
|
|
269
|
-
raise click.
|
|
333
|
+
if not snapshots_to_update and snapshot_id:
|
|
334
|
+
raise click.ClickException(f"Snapshot ID '{snapshot_id}' does not exist.")
|
|
335
|
+
|
|
336
|
+
if not snapshots_to_update and not snapshot_id:
|
|
337
|
+
raise click.ClickException("No snapshots found to update.")
|
|
270
338
|
|
|
271
|
-
for _ in snapshots_to_update:
|
|
272
|
-
id_to_update = _["databaseId"]
|
|
273
|
-
if id_to_update not in all_snapshot_ids:
|
|
274
|
-
raise click.UsageError(f"Snapshot ID '{id_to_update}' does not exist.")
|
|
275
339
|
for _ in snapshots_to_update:
|
|
276
340
|
id_to_update = _["databaseId"]
|
|
277
341
|
app.echo_info(f"Update snapshot {id_to_update} ... ", nl=False)
|
|
278
|
-
request(method="PUT", uri=f"
|
|
279
|
-
app.echo_success("started")
|
|
342
|
+
request(method="PUT", uri=get_api_url(f"/snapshot/{id_to_update}"))
|
|
343
|
+
app.echo_success("started", nl=not wait)
|
|
344
|
+
if wait:
|
|
345
|
+
app.echo_info(" ... ", nl=False)
|
|
346
|
+
wait_for_snapshot(id_to_update, polling_interval)
|
|
347
|
+
app.echo_success("updated")
|
|
280
348
|
|
|
281
349
|
|
|
282
350
|
@click.command(cls=CmemcCommand, name="inspect")
|
|
@@ -288,7 +356,7 @@ def inspect_command(ctx: Context, snapshot_id: str, raw: bool) -> None:
|
|
|
288
356
|
check_availability(ctx)
|
|
289
357
|
app: ApplicationContext = ctx.obj
|
|
290
358
|
snapshot: dict[str, str | bool | list[str]] = get_json(
|
|
291
|
-
f"
|
|
359
|
+
get_api_url(f"/snapshot/status/{snapshot_id}")
|
|
292
360
|
)
|
|
293
361
|
if raw:
|
|
294
362
|
app.echo_info_json(snapshot)
|
cmem_cmemc/commands/project.py
CHANGED
|
@@ -32,6 +32,7 @@ from jinja2 import Template
|
|
|
32
32
|
from cmem_cmemc import completion
|
|
33
33
|
from cmem_cmemc.command import CmemcCommand
|
|
34
34
|
from cmem_cmemc.command_group import CmemcGroup
|
|
35
|
+
from cmem_cmemc.commands.file import file
|
|
35
36
|
from cmem_cmemc.commands.variable import variable
|
|
36
37
|
from cmem_cmemc.context import ApplicationContext
|
|
37
38
|
from cmem_cmemc.parameter_types.path import ClickSmartPath
|
|
@@ -138,7 +139,7 @@ def list_command(app: ApplicationContext, raw: bool, id_only: bool) -> None:
|
|
|
138
139
|
for _ in projects:
|
|
139
140
|
row = [
|
|
140
141
|
_["name"],
|
|
141
|
-
_["metaData"]
|
|
142
|
+
_["metaData"].get("label", ""),
|
|
142
143
|
]
|
|
143
144
|
table.append(row)
|
|
144
145
|
app.echo_info_table(
|
|
@@ -584,3 +585,4 @@ project.add_command(delete_command)
|
|
|
584
585
|
project.add_command(create_command)
|
|
585
586
|
project.add_command(reload_command)
|
|
586
587
|
project.add_command(variable)
|
|
588
|
+
project.add_command(file)
|
cmem_cmemc/commands/python.py
CHANGED
|
@@ -267,7 +267,7 @@ def list_plugins_command(
|
|
|
267
267
|
table,
|
|
268
268
|
headers=["ID", "Package ID", "Type", "Label"],
|
|
269
269
|
sort_column=0,
|
|
270
|
-
empty_table_message="No
|
|
270
|
+
empty_table_message="No plugins found. "
|
|
271
271
|
"Use the `admin workspace python install` command to install python packages with plugins.",
|
|
272
272
|
)
|
|
273
273
|
if "error" in raw_output:
|
|
@@ -69,7 +69,9 @@ WHERE {{}}
|
|
|
69
69
|
"""
|
|
70
70
|
|
|
71
71
|
|
|
72
|
-
def _validate_vocabs_to_process(
|
|
72
|
+
def _validate_vocabs_to_process(
|
|
73
|
+
iris: tuple[str], filter_: str, all_flag: bool, replace: bool = False
|
|
74
|
+
) -> list[str]:
|
|
73
75
|
"""Return a list of vocabulary IRTs which will be processed.
|
|
74
76
|
|
|
75
77
|
list is without duplicates, and validated if they exist
|
|
@@ -85,6 +87,8 @@ def _validate_vocabs_to_process(iris: tuple[str], filter_: str, all_flag: bool)
|
|
|
85
87
|
if filter_ == "installed": # uninstall command
|
|
86
88
|
return [_ for _ in all_vocabs if all_vocabs[_]["installed"]]
|
|
87
89
|
# install command
|
|
90
|
+
if replace:
|
|
91
|
+
return list(all_vocabs)
|
|
88
92
|
return [_ for _ in all_vocabs if not all_vocabs[_]["installed"]]
|
|
89
93
|
|
|
90
94
|
vocabs_to_process = list(set(iris)) # avoid double removal / installation
|
|
@@ -96,7 +100,7 @@ def _validate_vocabs_to_process(iris: tuple[str], filter_: str, all_flag: bool)
|
|
|
96
100
|
if filter_ == "installable": # install command
|
|
97
101
|
if _ not in all_vocabs:
|
|
98
102
|
raise click.UsageError(f"Vocabulary {_} does not exist.")
|
|
99
|
-
if all_vocabs[_]["installed"]:
|
|
103
|
+
if all_vocabs[_]["installed"] and not replace:
|
|
100
104
|
raise click.UsageError(f"Vocabulary {_} already installed.")
|
|
101
105
|
return vocabs_to_process
|
|
102
106
|
|
|
@@ -290,15 +294,20 @@ def list_command(app: ApplicationContext, id_only: bool, filter_: str, raw: bool
|
|
|
290
294
|
@click.option(
|
|
291
295
|
"-a", "--all", "all_", is_flag=True, help="Install all vocabularies from the catalog."
|
|
292
296
|
)
|
|
297
|
+
@click.option(
|
|
298
|
+
"--replace", is_flag=True, help="Replace (overwrite) existing vocabulary, if present."
|
|
299
|
+
)
|
|
293
300
|
@click.pass_obj
|
|
294
|
-
def install_command(app: ApplicationContext, iris: tuple[str], all_: bool) -> None:
|
|
301
|
+
def install_command(app: ApplicationContext, iris: tuple[str], all_: bool, replace: bool) -> None:
|
|
295
302
|
"""Install one or more vocabularies from the catalog.
|
|
296
303
|
|
|
297
304
|
Vocabularies are identified by their graph IRI.
|
|
298
305
|
Installable vocabularies can be listed with the
|
|
299
306
|
vocabulary list command.
|
|
300
307
|
"""
|
|
301
|
-
vocabs_to_install = _validate_vocabs_to_process(
|
|
308
|
+
vocabs_to_install = _validate_vocabs_to_process(
|
|
309
|
+
iris=iris, filter_="installable", all_flag=all_, replace=replace
|
|
310
|
+
)
|
|
302
311
|
count: int = len(vocabs_to_install)
|
|
303
312
|
for current, vocab in enumerate(vocabs_to_install, start=1):
|
|
304
313
|
app.echo_info(f"Install vocabulary {current}/{count}: {vocab} ... ", nl=False)
|
cmem_cmemc/completion.py
CHANGED
|
@@ -31,7 +31,7 @@ from cmem.cmempy.workspace import (
|
|
|
31
31
|
)
|
|
32
32
|
from cmem.cmempy.workspace.projects.datasets.dataset import get_dataset
|
|
33
33
|
from cmem.cmempy.workspace.projects.project import get_projects
|
|
34
|
-
from cmem.cmempy.workspace.projects.resources import get_all_resources
|
|
34
|
+
from cmem.cmempy.workspace.projects.resources import get_all_resources, get_resources
|
|
35
35
|
from cmem.cmempy.workspace.projects.variables import get_all_variables
|
|
36
36
|
from cmem.cmempy.workspace.python import list_packages
|
|
37
37
|
from cmem.cmempy.workspace.search import list_items
|
|
@@ -728,7 +728,7 @@ def project_ids(ctx: Context, param: Argument, incomplete: str) -> list[Completi
|
|
|
728
728
|
options = []
|
|
729
729
|
for _ in projects:
|
|
730
730
|
project_id = _["name"]
|
|
731
|
-
label = _["metaData"]
|
|
731
|
+
label = _["metaData"].get("label", "")
|
|
732
732
|
# do not add project if already in the command line
|
|
733
733
|
if check_option_in_params(project_id, ctx.params.get(str(param.name))):
|
|
734
734
|
continue
|
|
@@ -934,9 +934,7 @@ def resource_list_filter(ctx: Context, param: Argument, incomplete: str) -> list
|
|
|
934
934
|
if args[len(args) - 1] == "--filter":
|
|
935
935
|
return [CompletionItem(value=f[0], help=f[1]) for f in filter_names]
|
|
936
936
|
if args[len(args) - 1] == "project":
|
|
937
|
-
return
|
|
938
|
-
candidates=project_ids(ctx, param, incomplete), incomplete=incomplete
|
|
939
|
-
)
|
|
937
|
+
return project_ids(ctx, param, incomplete)
|
|
940
938
|
if args[len(args) - 1] == "regex":
|
|
941
939
|
return finalize_completion(candidates=filter_values_regex, incomplete=incomplete)
|
|
942
940
|
return []
|
|
@@ -1055,3 +1053,22 @@ def transformation_task_ids(ctx: Context, param: Argument, incomplete: str) -> l
|
|
|
1055
1053
|
datasets = results["results"]
|
|
1056
1054
|
options = [(f"{_['projectId']}:{_['id']}", _["label"]) for _ in datasets]
|
|
1057
1055
|
return finalize_completion(candidates=options, incomplete=incomplete, sort_by=SORT_BY_DESC)
|
|
1056
|
+
|
|
1057
|
+
|
|
1058
|
+
def resource_paths(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]:
|
|
1059
|
+
"""Prepare a list of file resource paths within a project.
|
|
1060
|
+
|
|
1061
|
+
Returns the full path of file resources (not including the project ID prefix).
|
|
1062
|
+
If a project_id is available in context, lists resources from that project.
|
|
1063
|
+
If only one project exists, automatically uses that project.
|
|
1064
|
+
"""
|
|
1065
|
+
ApplicationContext.set_connection_from_params(ctx.find_root().params)
|
|
1066
|
+
project_id = ctx.params.get("project_id")
|
|
1067
|
+
if not project_id:
|
|
1068
|
+
projects = get_projects()
|
|
1069
|
+
if len(projects) == 1:
|
|
1070
|
+
project_id = projects[0]["name"]
|
|
1071
|
+
if project_id is None:
|
|
1072
|
+
return []
|
|
1073
|
+
options = [_["fullPath"] for _ in list(get_resources(project_id))]
|
|
1074
|
+
return finalize_completion(candidates=options, incomplete=incomplete, sort_by=SORT_BY_DESC)
|
cmem_cmemc/context.py
CHANGED
|
@@ -29,9 +29,9 @@ from cmem_cmemc.exceptions import InvalidConfigurationError
|
|
|
29
29
|
from cmem_cmemc.string_processor import StringProcessor, process_row
|
|
30
30
|
from cmem_cmemc.utils import is_enabled, str_to_bool
|
|
31
31
|
|
|
32
|
-
DI_TARGET_VERSION = "v25.
|
|
32
|
+
DI_TARGET_VERSION = "v25.3.0"
|
|
33
33
|
|
|
34
|
-
EXPLORE_TARGET_VERSION = "v25.
|
|
34
|
+
EXPLORE_TARGET_VERSION = "v25.3.0"
|
|
35
35
|
|
|
36
36
|
KNOWN_CONFIG_KEYS = {
|
|
37
37
|
"CMEM_BASE_URI": cmempy_config.get_cmem_base_uri,
|
|
@@ -56,7 +56,7 @@ def print_group_manual_graph_recursive(
|
|
|
56
56
|
ctx.obj.echo_info(f"{iri} a cli:CommandGroup .")
|
|
57
57
|
ctx.obj.echo_info(f"{iri} rdfs:label '{prefix}{key} Command Group' .")
|
|
58
58
|
ctx.obj.echo_info(f"{iri} cli:subGroupOf {sub_group_iri} .")
|
|
59
|
-
ctx.obj.echo_info(f
|
|
59
|
+
ctx.obj.echo_info(f'{iri} rdfs:comment """{comment}""" .')
|
|
60
60
|
print_group_manual_graph_recursive(item, ctx=ctx, prefix=f"{prefix}{key}-")
|
|
61
61
|
elif isinstance(item, click.Command):
|
|
62
62
|
comment = item.get_short_help_str(limit=200)
|
|
@@ -64,7 +64,7 @@ def print_group_manual_graph_recursive(
|
|
|
64
64
|
ctx.obj.echo_info(f"{iri} a cli:Command .")
|
|
65
65
|
ctx.obj.echo_info(f"{iri} rdfs:label '{prefix}{key} Command' .")
|
|
66
66
|
ctx.obj.echo_info(f"{iri} cli:group {group_iri} .")
|
|
67
|
-
ctx.obj.echo_info(f
|
|
67
|
+
ctx.obj.echo_info(f'{iri} rdfs:comment """{comment}""" .')
|
|
68
68
|
for parameter in item.params:
|
|
69
69
|
print_parameter_manual_graph(parameter, ctx=ctx, prefix=f"{prefix}{key}-")
|
|
70
70
|
else:
|
|
@@ -26,12 +26,13 @@ def get_icon_for_command_group(full_name: str) -> str:
|
|
|
26
26
|
"admin workspace python": "material/language-python",
|
|
27
27
|
"config": "material/cog-outline",
|
|
28
28
|
"dataset": "eccenca/artefact-dataset",
|
|
29
|
-
"dataset resource": "eccenca/artefact-file",
|
|
30
29
|
"project": "eccenca/artefact-project",
|
|
30
|
+
"project file": "eccenca/artefact-file",
|
|
31
31
|
"project variable": "material/variable-box",
|
|
32
32
|
"query": "eccenca/application-queries",
|
|
33
33
|
"graph": "eccenca/artefact-dataset-eccencadataplatform",
|
|
34
34
|
"graph imports": "material/family-tree",
|
|
35
|
+
"graph insights": "eccenca/graph-insights",
|
|
35
36
|
"graph validation": "octicons/verified-16",
|
|
36
37
|
"vocabulary": "eccenca/application-vocabularies",
|
|
37
38
|
"vocabulary cache": "eccenca/application-vocabularies",
|
|
@@ -54,8 +55,8 @@ def get_tags_for_command_group(full_name: str) -> str:
|
|
|
54
55
|
"admin workspace python": ["Python", "cmemc"],
|
|
55
56
|
"config": ["Configuration", "cmemc"],
|
|
56
57
|
"dataset": ["cmemc"],
|
|
57
|
-
"dataset resource": ["cmemc"],
|
|
58
58
|
"project": ["Project", "cmemc"],
|
|
59
|
+
"project file": ["Files", "cmemc"],
|
|
59
60
|
"project variable": ["Variables", "cmemc"],
|
|
60
61
|
"query": ["SPARQL", "cmemc"],
|
|
61
62
|
"graph": ["KnowledgeGraph", "cmemc"],
|
|
@@ -145,12 +146,10 @@ def get_commands_for_table_recursive(
|
|
|
145
146
|
if isinstance(item, Command):
|
|
146
147
|
command_name = item.name
|
|
147
148
|
group_link = f"{prefix}/{group_name}/index.md".replace(" ", "/")
|
|
148
|
-
|
|
149
|
-
group_link = group_link[1:]
|
|
149
|
+
group_link = group_link.removeprefix("/")
|
|
150
150
|
command_anchor = f"{prefix}-{group_name}".replace(" ", "-")
|
|
151
151
|
command_anchor += f"-{command_name}"
|
|
152
|
-
|
|
153
|
-
command_anchor = command_anchor[1:]
|
|
152
|
+
command_anchor = command_anchor.removeprefix("-")
|
|
154
153
|
command_link = f"{group_link}#{command_anchor}"
|
|
155
154
|
new_command = {
|
|
156
155
|
"command_name": command_name,
|
cmem_cmemc/placeholder.py
CHANGED
|
@@ -36,7 +36,7 @@ class QueryPlaceholder:
|
|
|
36
36
|
"""Prepare a list of placeholder values"""
|
|
37
37
|
result = self.value_query.get_json_results()
|
|
38
38
|
projection_vars = result["head"]["vars"]
|
|
39
|
-
bindings = result["results"]["bindings"]
|
|
39
|
+
bindings: list[dict] = result["results"]["bindings"]
|
|
40
40
|
if "value" not in projection_vars:
|
|
41
41
|
return []
|
|
42
42
|
|
|
@@ -50,7 +50,7 @@ class QueryPlaceholder:
|
|
|
50
50
|
values_with_description = []
|
|
51
51
|
for _ in bindings:
|
|
52
52
|
value = str(_["value"]["value"])
|
|
53
|
-
description =
|
|
53
|
+
description = _.get("description", {}).get("value", "")
|
|
54
54
|
values_with_description.append((value, description))
|
|
55
55
|
return values_with_description
|
|
56
56
|
|
cmem_cmemc/string_processor.py
CHANGED
|
@@ -6,6 +6,7 @@ from urllib.parse import quote
|
|
|
6
6
|
|
|
7
7
|
import timeago
|
|
8
8
|
from cmem.cmempy.config import get_cmem_base_uri
|
|
9
|
+
from humanize import naturalsize
|
|
9
10
|
|
|
10
11
|
from cmem_cmemc.title_helper import TitleHelper
|
|
11
12
|
from cmem_cmemc.utils import get_graphs_as_dict
|
|
@@ -19,6 +20,17 @@ class StringProcessor(ABC):
|
|
|
19
20
|
"""Process a single string content and output the processed string."""
|
|
20
21
|
|
|
21
22
|
|
|
23
|
+
class FileSize(StringProcessor):
|
|
24
|
+
"""Create a human-readable file size string."""
|
|
25
|
+
|
|
26
|
+
def process(self, text: str) -> str:
|
|
27
|
+
"""Process a single string content and output the processed string."""
|
|
28
|
+
try:
|
|
29
|
+
return "" if text is None else naturalsize(value=text, gnu=True)
|
|
30
|
+
except ValueError:
|
|
31
|
+
return text
|
|
32
|
+
|
|
33
|
+
|
|
22
34
|
class TimeAgo(StringProcessor):
|
|
23
35
|
"""Create a string similar to 'x minutes ago' from a timestamp or iso-formated string."""
|
|
24
36
|
|
cmem_cmemc/utils.py
CHANGED
|
@@ -35,7 +35,7 @@ def check_python_version(ctx: type["ApplicationContext"]) -> None:
|
|
|
35
35
|
"""Check the runtime python version and warn or error."""
|
|
36
36
|
version = sys.version_info
|
|
37
37
|
major_expected = [3]
|
|
38
|
-
minor_expected = [
|
|
38
|
+
minor_expected = [13]
|
|
39
39
|
if version.major not in major_expected:
|
|
40
40
|
ctx.echo_error(f"Error: cmemc can not be executed with Python {version.major}.")
|
|
41
41
|
sys.exit(1)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cmem-cmemc
|
|
3
|
-
Version: 25.
|
|
3
|
+
Version: 25.6.0
|
|
4
4
|
Summary: Command line client for eccenca Corporate Memory
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
License-File: LICENSE
|
|
@@ -34,6 +34,7 @@ Requires-Dist: click-didyoumean (>=0.3.1,<0.4.0)
|
|
|
34
34
|
Requires-Dist: click-help-colors (>=0.9.4,<0.10.0)
|
|
35
35
|
Requires-Dist: cmem-cmempy (==25.4.0)
|
|
36
36
|
Requires-Dist: configparser (>=7.2.0,<8.0.0)
|
|
37
|
+
Requires-Dist: humanize (>=4.14.0,<5.0.0)
|
|
37
38
|
Requires-Dist: jinja2 (>=3.1.6,<4.0.0)
|
|
38
39
|
Requires-Dist: junit-xml (>=1.9,<2.0)
|
|
39
40
|
Requires-Dist: natsort (>=8.4.0,<9.0.0)
|
|
@@ -2,39 +2,39 @@ cmem_cmemc/__init__.py,sha256=-RPEVweA-fcmEAynszDDMKwArJgxZpGW61UBiV7O4Og,24
|
|
|
2
2
|
cmem_cmemc/_cmemc.zsh,sha256=fmkrBHIQxus8cp2AgO1tzZ5mNZdGL_83cYz3a9uAdsg,1326
|
|
3
3
|
cmem_cmemc/cli.py,sha256=vDdSHFmXUstC3T7OlbPSd0hXxyigJE4VVgRMcsNz5cc,4538
|
|
4
4
|
cmem_cmemc/command.py,sha256=nBtrwPKFJLRpD3IPk5hKyn2LOMl-1ae7SV9iRhgky8k,1958
|
|
5
|
-
cmem_cmemc/command_group.py,sha256=
|
|
5
|
+
cmem_cmemc/command_group.py,sha256=72_7yiBl04JumnhpHWIZ-cggdCUjj71Rm5zT-SPdv78,3543
|
|
6
6
|
cmem_cmemc/commands/__init__.py,sha256=NaGM5jOzf0S_-4UIAwlVDOf2AZ3mliGPoRLXQJfTyZs,22
|
|
7
|
-
cmem_cmemc/commands/acl.py,sha256=
|
|
7
|
+
cmem_cmemc/commands/acl.py,sha256=vJ3H5eeWiVCtGk1ZpEF2OqXeHurZgPwakD8YT09lnVM,19030
|
|
8
8
|
cmem_cmemc/commands/admin.py,sha256=F-393oXTVYV7HxK5NxuhONlBIEg7wffxE7DAKDuasG4,10192
|
|
9
9
|
cmem_cmemc/commands/client.py,sha256=nBs7MoF2wF45AteTCeIQrXcOwKmHHCd8_lG_SM2mQSA,5127
|
|
10
10
|
cmem_cmemc/commands/config.py,sha256=VHiVkW6NFuz-tpKXRPl7dO1gIXQLOuEhlGVxb422qwA,5803
|
|
11
|
-
cmem_cmemc/commands/dataset.py,sha256=
|
|
11
|
+
cmem_cmemc/commands/dataset.py,sha256=vz4vqm6mSCY2ELCdEjCDUwM5YbszXjvRW2NR2ILP0aI,28891
|
|
12
|
+
cmem_cmemc/commands/file.py,sha256=CGut4VpSB-7A7wGMl4uP0m4xv6G--pnYO2faVI72PbM,15935
|
|
12
13
|
cmem_cmemc/commands/graph.py,sha256=uAI7dEDRjwLSGl6QsJ0PsAycJf2-EZNNn_lIFliDE9Q,32662
|
|
13
14
|
cmem_cmemc/commands/graph_imports.py,sha256=CYgTUSj8giHoWzz0Qjtup0H1V5GKZEI3xXdA_9w_btI,14046
|
|
14
|
-
cmem_cmemc/commands/graph_insights.py,sha256=
|
|
15
|
+
cmem_cmemc/commands/graph_insights.py,sha256=ARgaYeKBH1b2OIKVjgOPwgAa8mVzo3CLCstcw4Z53z4,13193
|
|
15
16
|
cmem_cmemc/commands/manual.py,sha256=-sZWeFL92Kj8gL3VYsbpKh2ZaVTyM3LgKaUcpNn9u3A,2179
|
|
16
17
|
cmem_cmemc/commands/metrics.py,sha256=pIBRTq90f7MEI99HLdFLN3D1xQ2Z2u6VKUeTIz0X7DY,12205
|
|
17
18
|
cmem_cmemc/commands/migration.py,sha256=y9v4Be7WELGjDGDBZrfLBeqU_G_JH1fnP5UVG9qjB3g,9638
|
|
18
|
-
cmem_cmemc/commands/project.py,sha256=
|
|
19
|
-
cmem_cmemc/commands/python.py,sha256=
|
|
19
|
+
cmem_cmemc/commands/project.py,sha256=H4UBlqJXjGzkL_afpn4dbFO3tpQbQa5L4XDsCyKS5RM,20669
|
|
20
|
+
cmem_cmemc/commands/python.py,sha256=dXFRV3pD4nRC7iTuy3dFFbdpilip-zZVuxkjyJX67Ls,11958
|
|
20
21
|
cmem_cmemc/commands/query.py,sha256=Dra4BHshfec1nvVJbDvLT9VTjfesWJGSQCbyNmnnMWI,28788
|
|
21
|
-
cmem_cmemc/commands/resource.py,sha256=74cn_yqMv3a6xOQAPpNCuluTWEH-_2PGENJnl7y8qz4,7778
|
|
22
22
|
cmem_cmemc/commands/scheduler.py,sha256=zYeO1-Hlxh9D-I9JIweQ-SEA0la0wv0EicY_UY7rNCg,8751
|
|
23
23
|
cmem_cmemc/commands/store.py,sha256=W_6LXq98If50-X-XYZUQsYodVwUjOSm3_jyMp62pFuA,10599
|
|
24
24
|
cmem_cmemc/commands/user.py,sha256=ANZpeOBA46xiqOcNPrueComsCV0gEBbav-vOL9VgyX4,12535
|
|
25
25
|
cmem_cmemc/commands/validation.py,sha256=ebolVeKpTTQ-tNjsmGWnfIlvv77lDwiWZRtd-qLbrHM,29509
|
|
26
26
|
cmem_cmemc/commands/variable.py,sha256=aLRH_rFe0h7JBpKIqzcevbk26vczgUGokIDY8g6LPxA,11576
|
|
27
|
-
cmem_cmemc/commands/vocabulary.py,sha256=
|
|
27
|
+
cmem_cmemc/commands/vocabulary.py,sha256=erf3zqSRqCVrN_OlCZj5Z5w4L6MRwSaUmC6V11v2vHc,18095
|
|
28
28
|
cmem_cmemc/commands/workflow.py,sha256=BINC-P5RsDvKTkHUbKZpzkfV5M12Cl7EPD4RLmygDOQ,25798
|
|
29
29
|
cmem_cmemc/commands/workspace.py,sha256=IcZgBsvtulLRFofS70qpln6oKQIZunrVLfSAUeiFhCA,4579
|
|
30
|
-
cmem_cmemc/completion.py,sha256=
|
|
30
|
+
cmem_cmemc/completion.py,sha256=mLNikJ2lHojj0El4PlhUivTOu4gPlWEdo49VoHe3cnI,46111
|
|
31
31
|
cmem_cmemc/config_parser.py,sha256=NduwOT-BB_uAk3pz1Y-ex18RQJW-jjHzkQKCEUUK6Hc,1276
|
|
32
32
|
cmem_cmemc/constants.py,sha256=pzZYbSaTDUiWmE-VOAHB20oivHew5_FP9UTejySsVK4,550
|
|
33
|
-
cmem_cmemc/context.py,sha256=
|
|
33
|
+
cmem_cmemc/context.py,sha256=Axk5zTbhAMPBSMPnk9jIaOQr4GvS3Ih5Jb6ZZc-c5O0,22247
|
|
34
34
|
cmem_cmemc/exceptions.py,sha256=0lsGOfXhciNGJloJGERMbbPuBbs5IwIIJ_5YnY9qQ-8,546
|
|
35
35
|
cmem_cmemc/manual_helper/__init__.py,sha256=G3Lqw2aPxo8x63Tg7L0aa5VD9BMaRzZDmhrog7IuEPg,43
|
|
36
|
-
cmem_cmemc/manual_helper/graph.py,sha256=
|
|
37
|
-
cmem_cmemc/manual_helper/multi_page.py,sha256=
|
|
36
|
+
cmem_cmemc/manual_helper/graph.py,sha256=dTkFXgU9fgySn54rE93t79v1MjWjQkprKRIfJhc7Jps,3655
|
|
37
|
+
cmem_cmemc/manual_helper/multi_page.py,sha256=asJ8QdAxRBU5EXZxP7UWaWnK7JtQ3cN7f3JY4ArjX4c,12321
|
|
38
38
|
cmem_cmemc/manual_helper/single_page.py,sha256=0mMn_IJwFCe-WPKAmxGEStb8IINLpQRxAx_F1pIxg1E,1526
|
|
39
39
|
cmem_cmemc/migrations/__init__.py,sha256=i6Ri7qN58ou_MwOzm2KibPkXOD7u-1ELky-nUE5LjAA,24
|
|
40
40
|
cmem_cmemc/migrations/abc.py,sha256=UGJzrvMzUFdp2-sosp49ObRI-SrUSzLJqLEhvB4QTzg,3564
|
|
@@ -47,15 +47,15 @@ cmem_cmemc/migrations/workspace_configurations.py,sha256=tFmCdfEL10ICjqMXQEIf-9f
|
|
|
47
47
|
cmem_cmemc/object_list.py,sha256=PKDZ-p61ep2MzygRXAe4uzP9CB9D8esoiC3lwra84P4,14734
|
|
48
48
|
cmem_cmemc/parameter_types/__init__.py,sha256=Jqhwnw5a2oPNMClzUyovWiieK60RCl3rvSNr-t3wP84,36
|
|
49
49
|
cmem_cmemc/parameter_types/path.py,sha256=M56PGdjploN2pEYaNAk6_qomAX54crLW8E9XZsFvRuI,2270
|
|
50
|
-
cmem_cmemc/placeholder.py,sha256=
|
|
50
|
+
cmem_cmemc/placeholder.py,sha256=Rf20OqwDjISnVPJsYlvuSgzeUbfJ2sklE2PWnZ5TSYg,2409
|
|
51
51
|
cmem_cmemc/smart_path/__init__.py,sha256=zDgm1kDrzLyCuIcNb8VXSdnb_CcVNjGkjgiIDVlsh74,3023
|
|
52
52
|
cmem_cmemc/smart_path/clients/__init__.py,sha256=YFOm69BfTCRvAcJjN_CoUmCv3kzEciyYOPUG337p_pA,1696
|
|
53
53
|
cmem_cmemc/smart_path/clients/http.py,sha256=3clZu2v4uuOvPY4MY_8SVSy7hIXJDNooahFRBRpy0ok,2347
|
|
54
|
-
cmem_cmemc/string_processor.py,sha256=
|
|
54
|
+
cmem_cmemc/string_processor.py,sha256=bwFs6BYoxX3DrUaxdqkonTC4AO99doBSmwqI05qvYyY,3494
|
|
55
55
|
cmem_cmemc/title_helper.py,sha256=7frjAR54_Xc1gszOWXfzSmKFTawNJQ7kkXhZcHmQLyw,1250
|
|
56
|
-
cmem_cmemc/utils.py,sha256=
|
|
57
|
-
cmem_cmemc-25.
|
|
58
|
-
cmem_cmemc-25.
|
|
59
|
-
cmem_cmemc-25.
|
|
60
|
-
cmem_cmemc-25.
|
|
61
|
-
cmem_cmemc-25.
|
|
56
|
+
cmem_cmemc/utils.py,sha256=LlvAMHxciY9ge-REdwHQhRetJGrYghRqBZADxqE0yL4,14657
|
|
57
|
+
cmem_cmemc-25.6.0.dist-info/METADATA,sha256=ZUPmXLqT9akk7zF8ErvMXTr7zTgBr1h-UBSBm0V_dZQ,5757
|
|
58
|
+
cmem_cmemc-25.6.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
59
|
+
cmem_cmemc-25.6.0.dist-info/entry_points.txt,sha256=2G0AWAyz501EHpFTjIxccdlCTsHt80NT0pdUGP1QkPA,45
|
|
60
|
+
cmem_cmemc-25.6.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
61
|
+
cmem_cmemc-25.6.0.dist-info/RECORD,,
|