cmem-cmemc 26.1.0rc1__py3-none-any.whl → 26.1.0rc3__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/commands/acl.py +17 -48
- cmem_cmemc/commands/dataset.py +33 -58
- cmem_cmemc/commands/file.py +16 -45
- cmem_cmemc/commands/graph.py +13 -45
- cmem_cmemc/commands/graph_insights.py +18 -14
- cmem_cmemc/commands/package.py +34 -69
- cmem_cmemc/commands/project.py +17 -12
- cmem_cmemc/commands/user.py +13 -49
- cmem_cmemc/commands/variable.py +15 -39
- cmem_cmemc/manual_helper/multi_page.py +2 -0
- cmem_cmemc/utils.py +113 -0
- {cmem_cmemc-26.1.0rc1.dist-info → cmem_cmemc-26.1.0rc3.dist-info}/METADATA +2 -2
- {cmem_cmemc-26.1.0rc1.dist-info → cmem_cmemc-26.1.0rc3.dist-info}/RECORD +16 -16
- {cmem_cmemc-26.1.0rc1.dist-info → cmem_cmemc-26.1.0rc3.dist-info}/WHEEL +0 -0
- {cmem_cmemc-26.1.0rc1.dist-info → cmem_cmemc-26.1.0rc3.dist-info}/entry_points.txt +0 -0
- {cmem_cmemc-26.1.0rc1.dist-info → cmem_cmemc-26.1.0rc3.dist-info}/licenses/LICENSE +0 -0
cmem_cmemc/commands/acl.py
CHANGED
|
@@ -33,6 +33,7 @@ from cmem_cmemc.smart_path import SmartPath as Path
|
|
|
33
33
|
from cmem_cmemc.utils import (
|
|
34
34
|
convert_iri_to_qname,
|
|
35
35
|
convert_qname_to_iri,
|
|
36
|
+
get_objects_to_delete,
|
|
36
37
|
get_query_text,
|
|
37
38
|
struct_to_table,
|
|
38
39
|
)
|
|
@@ -172,53 +173,6 @@ acl_list = ObjectList(
|
|
|
172
173
|
)
|
|
173
174
|
|
|
174
175
|
|
|
175
|
-
def _validate_acl_ids(access_condition_ids: tuple[str, ...]) -> None:
|
|
176
|
-
"""Validate that all provided access condition IDs exist."""
|
|
177
|
-
if not access_condition_ids:
|
|
178
|
-
return
|
|
179
|
-
all_acls = fetch_all_acls()
|
|
180
|
-
all_iris = {acl["iri"] for acl in all_acls}
|
|
181
|
-
for acl_id in access_condition_ids:
|
|
182
|
-
iri = convert_qname_to_iri(qname=acl_id, default_ns=NS_ACL)
|
|
183
|
-
if iri not in all_iris:
|
|
184
|
-
raise click.ClickException(
|
|
185
|
-
f"Access condition {acl_id} not available. Use the 'admin acl list' "
|
|
186
|
-
"command to get a list of existing access conditions."
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
def _get_acls_to_delete(
|
|
191
|
-
ctx: Context,
|
|
192
|
-
access_condition_ids: tuple[str, ...],
|
|
193
|
-
all_: bool,
|
|
194
|
-
filter_: tuple[tuple[str, str], ...],
|
|
195
|
-
) -> list[dict]:
|
|
196
|
-
"""Get the list of access conditions to delete based on selection method."""
|
|
197
|
-
if all_:
|
|
198
|
-
# Get all access conditions
|
|
199
|
-
return fetch_all_acls() # type: ignore[no-any-return]
|
|
200
|
-
|
|
201
|
-
# Validate provided IDs exist before proceeding
|
|
202
|
-
_validate_acl_ids(access_condition_ids)
|
|
203
|
-
|
|
204
|
-
# Build filter list
|
|
205
|
-
filter_to_apply = list(filter_) if filter_ else []
|
|
206
|
-
|
|
207
|
-
# Add IDs if provided (using internal multi-value filter)
|
|
208
|
-
if access_condition_ids:
|
|
209
|
-
iris = [convert_qname_to_iri(qname=_, default_ns=NS_ACL) for _ in access_condition_ids]
|
|
210
|
-
filter_to_apply.append(("ids", ",".join(iris)))
|
|
211
|
-
|
|
212
|
-
# Apply filters
|
|
213
|
-
acls = acl_list.apply_filters(ctx=ctx, filter_=filter_to_apply)
|
|
214
|
-
|
|
215
|
-
# Validation: ensure we found access conditions
|
|
216
|
-
if not acls and not access_condition_ids:
|
|
217
|
-
raise click.ClickException("No access conditions found matching the provided filters.")
|
|
218
|
-
|
|
219
|
-
return acls
|
|
220
|
-
|
|
221
|
-
|
|
222
176
|
@click.command(cls=CmemcCommand, name="list")
|
|
223
177
|
@click.option("--raw", is_flag=True, help="Outputs raw JSON.")
|
|
224
178
|
@click.option(
|
|
@@ -645,8 +599,23 @@ def delete_command(
|
|
|
645
599
|
"Either specify access condition IDs OR use a --filter or the --all option."
|
|
646
600
|
)
|
|
647
601
|
|
|
602
|
+
# Convert qnames to IRIs for filtering
|
|
603
|
+
iris = [convert_qname_to_iri(qname=_, default_ns=NS_ACL) for _ in access_condition_ids]
|
|
604
|
+
|
|
648
605
|
# Get access conditions to delete based on selection method
|
|
649
|
-
acls_to_delete =
|
|
606
|
+
acls_to_delete = get_objects_to_delete(
|
|
607
|
+
ctx=ctx,
|
|
608
|
+
ids=tuple(iris),
|
|
609
|
+
all_=all_,
|
|
610
|
+
filter_=filter_,
|
|
611
|
+
object_list=acl_list,
|
|
612
|
+
get_all_objects=fetch_all_acls,
|
|
613
|
+
extract_id=lambda x: x["iri"],
|
|
614
|
+
error_message_template=(
|
|
615
|
+
"Access condition {} not available. "
|
|
616
|
+
"Use the 'admin acl list' command to get a list of existing access conditions."
|
|
617
|
+
),
|
|
618
|
+
)
|
|
650
619
|
|
|
651
620
|
# Avoid double removal as well as sort IRIs
|
|
652
621
|
iris_to_delete = sorted({acl["iri"] for acl in acls_to_delete}, key=lambda v: v.lower())
|
cmem_cmemc/commands/dataset.py
CHANGED
|
@@ -38,18 +38,26 @@ from cmem_cmemc.parameter_types.path import ClickSmartPath
|
|
|
38
38
|
from cmem_cmemc.smart_path import SmartPath as Path
|
|
39
39
|
from cmem_cmemc.string_processor import DatasetLink, DatasetTypeLink
|
|
40
40
|
from cmem_cmemc.title_helper import DatasetTypeTitleHelper, ProjectTitleHelper
|
|
41
|
-
from cmem_cmemc.utils import check_or_select_project, struct_to_table
|
|
41
|
+
from cmem_cmemc.utils import check_or_select_project, get_objects_to_delete, struct_to_table
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
def
|
|
45
|
-
"""Get datasets for object list."""
|
|
44
|
+
def get_datasets_for_list(ctx: Context) -> list[dict]:
|
|
45
|
+
"""Get datasets for object list, transforming structure for filtering."""
|
|
46
46
|
_ = ctx
|
|
47
|
-
|
|
47
|
+
# Transform the dataset structure to add combined ID for easier filtering
|
|
48
|
+
transformed = []
|
|
49
|
+
for dataset in list_items(item_type="dataset")["results"]:
|
|
50
|
+
transformed_dataset = {
|
|
51
|
+
**dataset, # Keep all original fields
|
|
52
|
+
"combinedId": f"{dataset['projectId']}:{dataset['id']}", # Add combined ID
|
|
53
|
+
}
|
|
54
|
+
transformed.append(transformed_dataset)
|
|
55
|
+
return transformed
|
|
48
56
|
|
|
49
57
|
|
|
50
58
|
dataset_list = ObjectList(
|
|
51
59
|
name="datasets",
|
|
52
|
-
get_objects=
|
|
60
|
+
get_objects=get_datasets_for_list,
|
|
53
61
|
filters=[
|
|
54
62
|
DirectValuePropertyFilter(
|
|
55
63
|
name="project",
|
|
@@ -84,6 +92,11 @@ dataset_list = ObjectList(
|
|
|
84
92
|
description="Internal filter for multiple dataset IDs.",
|
|
85
93
|
property_key="id",
|
|
86
94
|
),
|
|
95
|
+
DirectMultiValuePropertyFilter(
|
|
96
|
+
name="combinedIds",
|
|
97
|
+
description="Internal filter for multiple combined dataset IDs (projectId:datasetId).",
|
|
98
|
+
property_key="combinedId",
|
|
99
|
+
),
|
|
87
100
|
],
|
|
88
101
|
)
|
|
89
102
|
|
|
@@ -111,57 +124,6 @@ def _validate_and_split_dataset_id(dataset_id: str) -> tuple[str, str]:
|
|
|
111
124
|
return project_part, dataset_part
|
|
112
125
|
|
|
113
126
|
|
|
114
|
-
def _validate_dataset_ids(dataset_ids: tuple[str, ...]) -> None:
|
|
115
|
-
"""Validate that all provided dataset IDs exist."""
|
|
116
|
-
if not dataset_ids:
|
|
117
|
-
return
|
|
118
|
-
all_datasets = list_items(item_type="dataset")["results"]
|
|
119
|
-
all_dataset_ids = [_["projectId"] + ":" + _["id"] for _ in all_datasets]
|
|
120
|
-
for dataset_id in dataset_ids:
|
|
121
|
-
if dataset_id not in all_dataset_ids:
|
|
122
|
-
raise CmemcError(
|
|
123
|
-
f"Dataset {dataset_id} not available. Use the 'dataset list' "
|
|
124
|
-
"command to get a list of existing datasets."
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def _get_datasets_to_delete(
|
|
129
|
-
ctx: Context,
|
|
130
|
-
dataset_ids: tuple[str, ...],
|
|
131
|
-
all_: bool,
|
|
132
|
-
filter_: tuple[tuple[str, str], ...],
|
|
133
|
-
) -> list[str]:
|
|
134
|
-
"""Get the list of dataset IDs to delete based on selection method."""
|
|
135
|
-
if all_:
|
|
136
|
-
# Get all datasets
|
|
137
|
-
datasets = list_items(item_type="dataset")["results"]
|
|
138
|
-
return [_["projectId"] + ":" + _["id"] for _ in datasets]
|
|
139
|
-
|
|
140
|
-
# Validate provided IDs exist before proceeding
|
|
141
|
-
_validate_dataset_ids(dataset_ids)
|
|
142
|
-
|
|
143
|
-
# Build filter list
|
|
144
|
-
filter_to_apply = list(filter_) if filter_ else []
|
|
145
|
-
|
|
146
|
-
# Add IDs if provided (using internal multi-value filter)
|
|
147
|
-
if dataset_ids:
|
|
148
|
-
# Extract just the dataset ID part (after the colon) for filtering
|
|
149
|
-
dataset_id_parts = [_.split(":")[1] for _ in dataset_ids]
|
|
150
|
-
filter_to_apply.append(("ids", ",".join(dataset_id_parts)))
|
|
151
|
-
|
|
152
|
-
# Apply filters
|
|
153
|
-
datasets = dataset_list.apply_filters(ctx=ctx, filter_=filter_to_apply)
|
|
154
|
-
|
|
155
|
-
# Build full dataset IDs
|
|
156
|
-
result = [_["projectId"] + ":" + _["id"] for _ in datasets]
|
|
157
|
-
|
|
158
|
-
# Validation: ensure we found datasets
|
|
159
|
-
if not result and not dataset_ids:
|
|
160
|
-
raise CmemcError("No datasets found matching the provided filters.")
|
|
161
|
-
|
|
162
|
-
return result
|
|
163
|
-
|
|
164
|
-
|
|
165
127
|
def _post_file_resource(
|
|
166
128
|
app: ApplicationContext,
|
|
167
129
|
project_id: str,
|
|
@@ -500,10 +462,23 @@ def delete_command(
|
|
|
500
462
|
raise UsageError("Either specify a dataset ID OR" " use a --filter or the --all option.")
|
|
501
463
|
|
|
502
464
|
# Get datasets to delete based on selection method
|
|
503
|
-
|
|
465
|
+
datasets_data = get_objects_to_delete(
|
|
466
|
+
ctx=ctx,
|
|
467
|
+
ids=dataset_ids,
|
|
468
|
+
all_=all_,
|
|
469
|
+
filter_=filter_,
|
|
470
|
+
object_list=dataset_list,
|
|
471
|
+
get_all_objects=lambda: get_datasets_for_list(ctx),
|
|
472
|
+
extract_id=lambda x: x["combinedId"],
|
|
473
|
+
error_message_template=(
|
|
474
|
+
"Dataset {} not available. "
|
|
475
|
+
"Use the 'dataset list' command to get a list of existing datasets."
|
|
476
|
+
),
|
|
477
|
+
id_filter_name="combinedIds",
|
|
478
|
+
)
|
|
504
479
|
|
|
505
480
|
# Avoid double removal as well as sort IDs
|
|
506
|
-
processed_ids = sorted(
|
|
481
|
+
processed_ids = sorted({_["combinedId"] for _ in datasets_data}, key=lambda v: v.lower())
|
|
507
482
|
count = len(processed_ids)
|
|
508
483
|
|
|
509
484
|
# Delete each dataset
|
cmem_cmemc/commands/file.py
CHANGED
|
@@ -27,7 +27,12 @@ from cmem_cmemc.object_list import (
|
|
|
27
27
|
from cmem_cmemc.parameter_types.path import ClickSmartPath
|
|
28
28
|
from cmem_cmemc.smart_path import SmartPath as Path
|
|
29
29
|
from cmem_cmemc.string_processor import FileSize, TimeAgo
|
|
30
|
-
from cmem_cmemc.utils import
|
|
30
|
+
from cmem_cmemc.utils import (
|
|
31
|
+
check_or_select_project,
|
|
32
|
+
get_objects_to_delete,
|
|
33
|
+
split_task_id,
|
|
34
|
+
struct_to_table,
|
|
35
|
+
)
|
|
31
36
|
|
|
32
37
|
|
|
33
38
|
def get_resources(ctx: Context) -> list[dict]: # noqa: ARG001
|
|
@@ -120,48 +125,6 @@ def _upload_file_resource(
|
|
|
120
125
|
app.echo_success("done")
|
|
121
126
|
|
|
122
127
|
|
|
123
|
-
def _validate_resource_ids(resource_ids: tuple[str, ...]) -> None:
|
|
124
|
-
"""Validate that all provided resource IDs exist."""
|
|
125
|
-
if not resource_ids:
|
|
126
|
-
return
|
|
127
|
-
all_resources = get_all_resources()
|
|
128
|
-
all_resource_ids = [_["id"] for _ in all_resources]
|
|
129
|
-
for resource_id in resource_ids:
|
|
130
|
-
if resource_id not in all_resource_ids:
|
|
131
|
-
raise CmemcError(f"Resource {resource_id} not available.")
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
def _get_resources_to_delete(
|
|
135
|
-
ctx: Context,
|
|
136
|
-
resource_ids: tuple[str, ...],
|
|
137
|
-
all_: bool,
|
|
138
|
-
filter_: tuple[tuple[str, str], ...],
|
|
139
|
-
) -> list[dict]:
|
|
140
|
-
"""Get the list of resources to delete based on selection method."""
|
|
141
|
-
if all_:
|
|
142
|
-
_: list[dict] = get_all_resources()
|
|
143
|
-
return _
|
|
144
|
-
|
|
145
|
-
# Validate provided IDs exist before proceeding
|
|
146
|
-
_validate_resource_ids(resource_ids)
|
|
147
|
-
|
|
148
|
-
# Build filter list
|
|
149
|
-
filter_to_apply = list(filter_) if filter_ else []
|
|
150
|
-
|
|
151
|
-
# Add IDs if provided (using internal multi-value filter)
|
|
152
|
-
if resource_ids:
|
|
153
|
-
filter_to_apply.append(("ids", ",".join(resource_ids)))
|
|
154
|
-
|
|
155
|
-
# Apply filters
|
|
156
|
-
resources = resource_list.apply_filters(ctx=ctx, filter_=filter_to_apply)
|
|
157
|
-
|
|
158
|
-
# Validation: ensure we found resources
|
|
159
|
-
if not resources and not resource_ids:
|
|
160
|
-
raise CmemcError("No resources found matching the provided filters.")
|
|
161
|
-
|
|
162
|
-
return resources
|
|
163
|
-
|
|
164
|
-
|
|
165
128
|
@click.command(cls=CmemcCommand, name="list")
|
|
166
129
|
@click.option("--raw", is_flag=True, help="Outputs raw JSON.")
|
|
167
130
|
@click.option(
|
|
@@ -261,8 +224,16 @@ def delete_command(
|
|
|
261
224
|
"--filter options to specify resources for deletion."
|
|
262
225
|
)
|
|
263
226
|
|
|
264
|
-
|
|
265
|
-
|
|
227
|
+
resources_to_delete = get_objects_to_delete(
|
|
228
|
+
ctx=ctx,
|
|
229
|
+
ids=resource_ids,
|
|
230
|
+
all_=all_,
|
|
231
|
+
filter_=filter_,
|
|
232
|
+
object_list=resource_list,
|
|
233
|
+
get_all_objects=get_all_resources,
|
|
234
|
+
extract_id=lambda x: x["id"],
|
|
235
|
+
error_message_template="Resource {} not available.",
|
|
236
|
+
)
|
|
266
237
|
|
|
267
238
|
# Avoid double removal as well as sort IDs
|
|
268
239
|
processed_ids = sorted({_["id"] for _ in resources_to_delete}, key=lambda v: v.lower())
|
cmem_cmemc/commands/graph.py
CHANGED
|
@@ -47,6 +47,7 @@ from cmem_cmemc.utils import (
|
|
|
47
47
|
convert_uri_to_filename,
|
|
48
48
|
get_graphs,
|
|
49
49
|
get_graphs_as_dict,
|
|
50
|
+
get_objects_to_delete,
|
|
50
51
|
iri_to_qname,
|
|
51
52
|
read_rdf_graph_files,
|
|
52
53
|
tuple_to_list,
|
|
@@ -231,49 +232,6 @@ def _add_imported_graphs(iris: list[str], all_graphs: dict) -> list[str]:
|
|
|
231
232
|
return list(set(extended_list))
|
|
232
233
|
|
|
233
234
|
|
|
234
|
-
def _validate_graph_iris(iris: tuple[str, ...]) -> None:
|
|
235
|
-
"""Validate that all provided graph IRIs exist."""
|
|
236
|
-
if not iris:
|
|
237
|
-
return
|
|
238
|
-
all_graphs = get_graphs_as_dict()
|
|
239
|
-
for iri in iris:
|
|
240
|
-
if iri not in all_graphs:
|
|
241
|
-
raise CmemcError(UNKNOWN_GRAPH_ERROR.format(iri))
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
def _get_graphs_to_delete(
|
|
245
|
-
ctx: Context,
|
|
246
|
-
iris: tuple[str, ...],
|
|
247
|
-
all_: bool,
|
|
248
|
-
filter_: tuple[tuple[str, str], ...],
|
|
249
|
-
) -> list[dict]:
|
|
250
|
-
"""Get the list of graphs to delete based on selection method."""
|
|
251
|
-
if all_:
|
|
252
|
-
return get_graphs(writeable=True, readonly=False)
|
|
253
|
-
|
|
254
|
-
# Validate provided IRIs exist before proceeding
|
|
255
|
-
_validate_graph_iris(iris)
|
|
256
|
-
|
|
257
|
-
# Build filter list
|
|
258
|
-
filter_to_apply = list(filter_) if filter_ else []
|
|
259
|
-
|
|
260
|
-
# Add IRIs if provided (using internal multi-value filter)
|
|
261
|
-
if iris:
|
|
262
|
-
filter_to_apply.append(("iris", ",".join(iris)))
|
|
263
|
-
|
|
264
|
-
# Apply filters to writeable graphs only
|
|
265
|
-
writeable_graphs = get_graphs(writeable=True, readonly=False)
|
|
266
|
-
graphs = graph_delete_obj.apply_filters(
|
|
267
|
-
ctx=ctx, filter_=filter_to_apply, objects=writeable_graphs
|
|
268
|
-
)
|
|
269
|
-
|
|
270
|
-
# Validation: ensure we found graphs
|
|
271
|
-
if not graphs:
|
|
272
|
-
raise CmemcError("No graphs found matching the provided criteria.")
|
|
273
|
-
|
|
274
|
-
return graphs
|
|
275
|
-
|
|
276
|
-
|
|
277
235
|
def _check_and_extend_exported_graphs(
|
|
278
236
|
iris: list[str], all_flag: bool, imported_flag: bool, all_graphs: dict
|
|
279
237
|
) -> list[str]:
|
|
@@ -919,8 +877,18 @@ def delete_command( # noqa: PLR0913
|
|
|
919
877
|
"--filter options to specify graphs for deletion."
|
|
920
878
|
)
|
|
921
879
|
|
|
922
|
-
# Get base list of graphs to delete
|
|
923
|
-
graphs_to_delete =
|
|
880
|
+
# Get base list of graphs to delete (only writeable graphs)
|
|
881
|
+
graphs_to_delete = get_objects_to_delete(
|
|
882
|
+
ctx=ctx,
|
|
883
|
+
ids=iris,
|
|
884
|
+
all_=all_,
|
|
885
|
+
filter_=filter_,
|
|
886
|
+
object_list=graph_delete_obj,
|
|
887
|
+
get_all_objects=lambda: get_graphs(writeable=True, readonly=False),
|
|
888
|
+
extract_id=lambda x: x["iri"],
|
|
889
|
+
error_message_template=UNKNOWN_GRAPH_ERROR,
|
|
890
|
+
id_filter_name="iris",
|
|
891
|
+
)
|
|
924
892
|
iris_to_delete = [g["iri"] for g in graphs_to_delete]
|
|
925
893
|
|
|
926
894
|
# Handle --include-imports flag
|
|
@@ -27,7 +27,11 @@ from cmem_cmemc.object_list import (
|
|
|
27
27
|
transform_lower,
|
|
28
28
|
)
|
|
29
29
|
from cmem_cmemc.string_processor import GraphLink, TimeAgo
|
|
30
|
-
from cmem_cmemc.utils import
|
|
30
|
+
from cmem_cmemc.utils import (
|
|
31
|
+
get_graphs_as_dict,
|
|
32
|
+
get_objects_to_delete,
|
|
33
|
+
struct_to_table,
|
|
34
|
+
)
|
|
31
35
|
|
|
32
36
|
|
|
33
37
|
def get_api_url(path: str = "") -> str:
|
|
@@ -242,6 +246,7 @@ def delete_command(
|
|
|
242
246
|
if snapshot_ids and (all_ or filter_):
|
|
243
247
|
raise click.UsageError("Either specify snapshot IDs OR use a --filter or the --all option.")
|
|
244
248
|
|
|
249
|
+
# Special case: --all uses bulk DELETE endpoint
|
|
245
250
|
if all_:
|
|
246
251
|
app.echo_info("Deleting all snapshots ... ", nl=False)
|
|
247
252
|
request(method="DELETE", uri=get_api_url("/snapshot"))
|
|
@@ -249,20 +254,19 @@ def delete_command(
|
|
|
249
254
|
return
|
|
250
255
|
|
|
251
256
|
# Get snapshots to delete based on selection method
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
257
|
+
snapshots_to_delete = get_objects_to_delete(
|
|
258
|
+
ctx=ctx,
|
|
259
|
+
ids=snapshot_ids,
|
|
260
|
+
all_=False, # Already handled above
|
|
261
|
+
filter_=filter_,
|
|
262
|
+
object_list=snapshot_list,
|
|
263
|
+
get_all_objects=lambda: get_snapshots(ctx),
|
|
264
|
+
extract_id=lambda x: x["databaseId"],
|
|
265
|
+
error_message_template=(
|
|
266
|
+
"Snapshot {} not found. "
|
|
261
267
|
"Use the 'graph insights list' command to get a list of existing snapshots."
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
if not snapshots_to_delete and not snapshot_ids:
|
|
265
|
-
raise CmemcError("No snapshots found to delete.")
|
|
268
|
+
),
|
|
269
|
+
)
|
|
266
270
|
|
|
267
271
|
# Avoid double removal as well as sort IDs
|
|
268
272
|
ids_to_delete = sorted({_["databaseId"] for _ in snapshots_to_delete}, key=lambda v: v.lower())
|
cmem_cmemc/commands/package.py
CHANGED
|
@@ -8,12 +8,12 @@ import click
|
|
|
8
8
|
import requests
|
|
9
9
|
from click.shell_completion import CompletionItem
|
|
10
10
|
from cmem_client.client import Client
|
|
11
|
+
from cmem_client.components.marketplace import Marketplace
|
|
11
12
|
from cmem_client.repositories.marketplace_packages import (
|
|
12
13
|
MarketplacePackagesExportConfig,
|
|
13
14
|
MarketplacePackagesImportConfig,
|
|
14
15
|
MarketplacePackagesRepository,
|
|
15
16
|
)
|
|
16
|
-
from eccenca_marketplace_client.manifests import AbstractPackageManifest, ManifestMetadata
|
|
17
17
|
from eccenca_marketplace_client.package_version import PackageVersion
|
|
18
18
|
from pydantic_extra_types.semantic_version import SemanticVersion
|
|
19
19
|
|
|
@@ -93,53 +93,18 @@ def _complete_installed_package_ids(
|
|
|
93
93
|
return completion.finalize_completion(candidates=candidates, incomplete=incomplete)
|
|
94
94
|
|
|
95
95
|
|
|
96
|
-
@
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
help=AbstractPackageManifest.model_fields.get("package_version").description,
|
|
109
|
-
)
|
|
110
|
-
@click.option(
|
|
111
|
-
"--description",
|
|
112
|
-
type=click.STRING,
|
|
113
|
-
help=ManifestMetadata.model_fields.get("description").description,
|
|
114
|
-
default="This is the first version of a wonderful eccenca Corporate Memory package 🤓",
|
|
115
|
-
show_default=True,
|
|
116
|
-
)
|
|
117
|
-
@click.pass_obj
|
|
118
|
-
def create_command(
|
|
119
|
-
app: ApplicationContext, package_id: str, version: str, name: str | None, description: str
|
|
120
|
-
) -> None:
|
|
121
|
-
"""Initialize an empty package directory with a minimal manifest."""
|
|
122
|
-
if Path(package_id).exists():
|
|
123
|
-
raise click.UsageError(f"Package directory '{package_id}' already exists.")
|
|
124
|
-
if name is None:
|
|
125
|
-
name = package_id
|
|
126
|
-
manifest_src = {
|
|
127
|
-
"package_id": package_id,
|
|
128
|
-
"package_type": "project",
|
|
129
|
-
"package_version": version,
|
|
130
|
-
"metadata": {
|
|
131
|
-
"name": name,
|
|
132
|
-
"description": description,
|
|
133
|
-
},
|
|
134
|
-
"files": [],
|
|
135
|
-
}
|
|
136
|
-
package_version = PackageVersion.from_json(json.dumps(manifest_src))
|
|
137
|
-
directory = Path(package_id)
|
|
138
|
-
app.echo_info(f"Initialize package directory '{directory}' ... ", nl=False)
|
|
139
|
-
directory.mkdir(parents=True, exist_ok=True)
|
|
140
|
-
manifest = directory / "manifest.json"
|
|
141
|
-
manifest.write_text(package_version.manifest.model_dump_json(indent=2))
|
|
142
|
-
app.echo_success("done")
|
|
96
|
+
@suppress_completion_errors
|
|
97
|
+
def _complete_marketplace_package_ids(
|
|
98
|
+
ctx: click.Context,
|
|
99
|
+
param: click.Argument, # noqa: ARG001
|
|
100
|
+
incomplete: str,
|
|
101
|
+
) -> list[CompletionItem]:
|
|
102
|
+
"""Prepare a list of IDs of available packages."""
|
|
103
|
+
ApplicationContext.set_connection_from_params(ctx.find_root().params)
|
|
104
|
+
client = Client.from_cmempy()
|
|
105
|
+
marketplace = Marketplace(client=client)
|
|
106
|
+
candidates = [(_.id, f"{_.name} ({_.type})") for _ in marketplace.get_available_packages()]
|
|
107
|
+
return completion.finalize_completion(candidates=candidates, incomplete=incomplete)
|
|
143
108
|
|
|
144
109
|
|
|
145
110
|
@click.command(cls=CmemcCommand, name="inspect")
|
|
@@ -165,7 +130,9 @@ def inspect_command(app: ApplicationContext, package_path: Path, key: str, raw:
|
|
|
165
130
|
"""Inspect the manifest of a package."""
|
|
166
131
|
path = Path(package_path)
|
|
167
132
|
package_version = (
|
|
168
|
-
PackageVersion.from_directory(path
|
|
133
|
+
PackageVersion.from_directory(path, validate_files=False)
|
|
134
|
+
if path.is_dir()
|
|
135
|
+
else PackageVersion.from_archive(path)
|
|
169
136
|
)
|
|
170
137
|
manifest = package_version.manifest
|
|
171
138
|
manifest_data = json.loads(manifest.model_dump_json(indent=2))
|
|
@@ -247,6 +214,7 @@ def list_command(
|
|
|
247
214
|
"PACKAGE_ID",
|
|
248
215
|
required=False,
|
|
249
216
|
type=click.STRING,
|
|
217
|
+
shell_complete=_complete_marketplace_package_ids,
|
|
250
218
|
)
|
|
251
219
|
@click.option(
|
|
252
220
|
"--input",
|
|
@@ -281,7 +249,7 @@ def install_command(
|
|
|
281
249
|
if input_path:
|
|
282
250
|
package_path = Path(input_path)
|
|
283
251
|
package_version = (
|
|
284
|
-
PackageVersion.from_directory(package_path)
|
|
252
|
+
PackageVersion.from_directory(package_path, validate_files=False)
|
|
285
253
|
if package_path.is_dir()
|
|
286
254
|
else PackageVersion.from_archive(package_path)
|
|
287
255
|
)
|
|
@@ -447,7 +415,7 @@ def build_command(
|
|
|
447
415
|
Package archives can be published to the marketplace using the `package publish` command.
|
|
448
416
|
"""
|
|
449
417
|
package_path = Path(package_directory)
|
|
450
|
-
package_version = PackageVersion.from_directory(package_path)
|
|
418
|
+
package_version = PackageVersion.from_directory(package_path, validate_files=False)
|
|
451
419
|
if version:
|
|
452
420
|
if version.startswith("v"):
|
|
453
421
|
version = version[1:]
|
|
@@ -466,25 +434,32 @@ def build_command(
|
|
|
466
434
|
|
|
467
435
|
if version_str.endswith("dirty"):
|
|
468
436
|
app.echo_warning(
|
|
469
|
-
"Dirty Repository: Your version
|
|
437
|
+
"Dirty Repository: Your version string ends with 'dirty'. "
|
|
470
438
|
"This indicates an unclean repository."
|
|
471
439
|
)
|
|
440
|
+
if version_str == "0.0.0":
|
|
441
|
+
raise CmemcError(
|
|
442
|
+
"Invalid Version 0.0.0: "
|
|
443
|
+
"Use the --version option to override this, or change the manifest."
|
|
444
|
+
)
|
|
472
445
|
if cpa_file.exists() and not replace:
|
|
473
446
|
raise CmemcError(
|
|
474
447
|
f"Package archive `{cpa_file}` already exists. Use `--replace` to overwrite."
|
|
475
448
|
)
|
|
476
449
|
app.echo_info(f"Building package archive `{cpa_file.name}` ... ", nl=False)
|
|
450
|
+
package_version.validate_text_files(package_version.manifest, package_path)
|
|
451
|
+
package_version.validate_image_files(package_version.manifest, package_path)
|
|
477
452
|
package_version.build_archive(archive=cpa_file)
|
|
478
453
|
app.echo_success("done")
|
|
479
454
|
|
|
480
455
|
|
|
481
456
|
@click.command(cls=CmemcCommand, name="publish")
|
|
482
457
|
@click.argument(
|
|
483
|
-
"
|
|
458
|
+
"PACKAGE_ARCHIVE",
|
|
484
459
|
required=True,
|
|
485
460
|
type=ClickSmartPath(
|
|
486
461
|
allow_dash=False,
|
|
487
|
-
dir_okay=
|
|
462
|
+
dir_okay=False,
|
|
488
463
|
readable=True,
|
|
489
464
|
exists=True,
|
|
490
465
|
remote_okay=True,
|
|
@@ -497,25 +472,16 @@ def build_command(
|
|
|
497
472
|
default="https://marketplace.eccenca.dev/",
|
|
498
473
|
)
|
|
499
474
|
@click.pass_obj
|
|
500
|
-
def publish_command(app: ApplicationContext,
|
|
475
|
+
def publish_command(app: ApplicationContext, package_archive: str, marketplace_url: str) -> None:
|
|
501
476
|
"""Publish a package archive to the marketplace server."""
|
|
502
|
-
package_path = Path(
|
|
477
|
+
package_path = Path(package_archive)
|
|
503
478
|
|
|
504
|
-
package_version = (
|
|
505
|
-
PackageVersion.from_directory(package_path)
|
|
506
|
-
if package_path.is_dir()
|
|
507
|
-
else PackageVersion.from_archive(package_path)
|
|
508
|
-
)
|
|
479
|
+
package_version = PackageVersion.from_archive(package_path)
|
|
509
480
|
package_id = package_version.manifest.package_id
|
|
510
481
|
|
|
511
482
|
app.echo_info(f"Publishing package `{package_id}` ... ", nl=False)
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
package_data = b"".join(PackageVersion.create_archive(package_path))
|
|
515
|
-
filename = f"{package_id}.cpa"
|
|
516
|
-
else:
|
|
517
|
-
package_data = package_path.read_bytes()
|
|
518
|
-
filename = package_path.name
|
|
483
|
+
package_data = package_path.read_bytes()
|
|
484
|
+
filename = package_path.name
|
|
519
485
|
|
|
520
486
|
if marketplace_url.endswith("/"):
|
|
521
487
|
marketplace_url = marketplace_url[:-1]
|
|
@@ -538,7 +504,6 @@ def package_group() -> CmemcGroup: # type: ignore[empty-body]
|
|
|
538
504
|
"""List, (un)install, export, create, or inspect packages."""
|
|
539
505
|
|
|
540
506
|
|
|
541
|
-
package_group.add_command(create_command)
|
|
542
507
|
package_group.add_command(inspect_command)
|
|
543
508
|
package_group.add_command(list_command)
|
|
544
509
|
package_group.add_command(install_command)
|
cmem_cmemc/commands/project.py
CHANGED
|
@@ -38,7 +38,7 @@ from cmem_cmemc.context import ApplicationContext, build_caption
|
|
|
38
38
|
from cmem_cmemc.exceptions import CmemcError
|
|
39
39
|
from cmem_cmemc.object_list import (
|
|
40
40
|
DirectListPropertyFilter,
|
|
41
|
-
|
|
41
|
+
DirectMultiValuePropertyFilter,
|
|
42
42
|
Filter,
|
|
43
43
|
MultiFieldPropertyFilter,
|
|
44
44
|
ObjectList,
|
|
@@ -46,6 +46,7 @@ from cmem_cmemc.object_list import (
|
|
|
46
46
|
from cmem_cmemc.parameter_types.path import ClickSmartPath
|
|
47
47
|
from cmem_cmemc.smart_path import SmartPath as Path
|
|
48
48
|
from cmem_cmemc.string_processor import ProjectLink, TimeAgo
|
|
49
|
+
from cmem_cmemc.utils import get_objects_to_delete
|
|
49
50
|
|
|
50
51
|
|
|
51
52
|
def get_projects_for_list(ctx: Context) -> list[dict]:
|
|
@@ -90,11 +91,10 @@ project_list = ObjectList(
|
|
|
90
91
|
name="projects",
|
|
91
92
|
get_objects=get_projects_for_list,
|
|
92
93
|
filters=[
|
|
93
|
-
|
|
94
|
-
name="
|
|
95
|
-
description="
|
|
94
|
+
DirectMultiValuePropertyFilter(
|
|
95
|
+
name="ids",
|
|
96
|
+
description="Internal filter for multiple projects.",
|
|
96
97
|
property_key="name",
|
|
97
|
-
completion_method="values",
|
|
98
98
|
),
|
|
99
99
|
MultiFieldPropertyFilter(
|
|
100
100
|
name="regex",
|
|
@@ -296,15 +296,20 @@ def delete_command(
|
|
|
296
296
|
if project_ids and (all_ or filter_):
|
|
297
297
|
raise UsageError("Either specify a project ID OR use a --filter or the --all option.")
|
|
298
298
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
299
|
+
# Get projects to delete based on selection method
|
|
300
|
+
projects_data = get_objects_to_delete(
|
|
301
|
+
ctx=ctx,
|
|
302
|
+
ids=project_ids,
|
|
303
|
+
all_=all_,
|
|
304
|
+
filter_=filter_,
|
|
305
|
+
object_list=project_list,
|
|
306
|
+
get_all_objects=get_projects,
|
|
307
|
+
extract_id=lambda x: x["name"],
|
|
308
|
+
error_message_template="Project {} not available.",
|
|
309
|
+
)
|
|
305
310
|
|
|
306
311
|
# Avoid double removal as well as sort project IDs
|
|
307
|
-
projects_to_delete = sorted(
|
|
312
|
+
projects_to_delete = sorted({_["name"] for _ in projects_data}, key=lambda v: v.lower())
|
|
308
313
|
count = len(projects_to_delete)
|
|
309
314
|
for current, project_id in enumerate(projects_to_delete, start=1):
|
|
310
315
|
current_string = str(current).zfill(len(str(count)))
|
cmem_cmemc/commands/user.py
CHANGED
|
@@ -31,6 +31,7 @@ from cmem_cmemc.object_list import (
|
|
|
31
31
|
compare_regex,
|
|
32
32
|
transform_lower,
|
|
33
33
|
)
|
|
34
|
+
from cmem_cmemc.utils import get_objects_to_delete
|
|
34
35
|
|
|
35
36
|
NO_USER_ERROR = (
|
|
36
37
|
"{} is not a valid user account. Use the 'admin user list' command "
|
|
@@ -81,52 +82,6 @@ user_list = ObjectList(
|
|
|
81
82
|
)
|
|
82
83
|
|
|
83
84
|
|
|
84
|
-
def _validate_usernames(usernames: tuple[str, ...]) -> None:
|
|
85
|
-
"""Validate that all provided usernames exist."""
|
|
86
|
-
if not usernames:
|
|
87
|
-
return
|
|
88
|
-
all_users = list_users()
|
|
89
|
-
all_usernames = [user["username"] for user in all_users]
|
|
90
|
-
for username in usernames:
|
|
91
|
-
if username not in all_usernames:
|
|
92
|
-
raise CmemcError(NO_USER_ERROR.format(username))
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def _get_users_to_delete(
|
|
96
|
-
ctx: click.Context,
|
|
97
|
-
usernames: tuple[str, ...],
|
|
98
|
-
all_: bool,
|
|
99
|
-
filter_: tuple[tuple[str, str], ...],
|
|
100
|
-
) -> list[str]:
|
|
101
|
-
"""Get the list of usernames to delete based on selection method."""
|
|
102
|
-
if all_:
|
|
103
|
-
# Get all users
|
|
104
|
-
users = list_users()
|
|
105
|
-
return [user["username"] for user in users]
|
|
106
|
-
|
|
107
|
-
# Validate provided usernames exist before proceeding
|
|
108
|
-
_validate_usernames(usernames)
|
|
109
|
-
|
|
110
|
-
# Build filter list
|
|
111
|
-
filter_to_apply = list(filter_) if filter_ else []
|
|
112
|
-
|
|
113
|
-
# Add usernames if provided (using internal multi-value filter)
|
|
114
|
-
if usernames:
|
|
115
|
-
filter_to_apply.append(("usernames", ",".join(usernames)))
|
|
116
|
-
|
|
117
|
-
# Apply filters
|
|
118
|
-
users = user_list.apply_filters(ctx=ctx, filter_=filter_to_apply)
|
|
119
|
-
|
|
120
|
-
# Build list of usernames
|
|
121
|
-
result = [user["username"] for user in users]
|
|
122
|
-
|
|
123
|
-
# Validation: ensure we found users
|
|
124
|
-
if not result and not usernames:
|
|
125
|
-
raise CmemcError("No user accounts found matching the provided filters.")
|
|
126
|
-
|
|
127
|
-
return result
|
|
128
|
-
|
|
129
|
-
|
|
130
85
|
@click.command(cls=CmemcCommand, name="list")
|
|
131
86
|
@click.option("--raw", is_flag=True, help="Outputs raw JSON.")
|
|
132
87
|
@click.option(
|
|
@@ -228,11 +183,20 @@ def delete_command(
|
|
|
228
183
|
if usernames and (all_ or filter_):
|
|
229
184
|
raise click.UsageError("Either specify a username OR use a --filter or the --all option.")
|
|
230
185
|
|
|
231
|
-
|
|
232
|
-
|
|
186
|
+
users_data = get_objects_to_delete(
|
|
187
|
+
ctx=ctx,
|
|
188
|
+
ids=usernames,
|
|
189
|
+
all_=all_,
|
|
190
|
+
filter_=filter_,
|
|
191
|
+
object_list=user_list,
|
|
192
|
+
get_all_objects=list_users,
|
|
193
|
+
extract_id=lambda x: x["username"],
|
|
194
|
+
error_message_template=NO_USER_ERROR,
|
|
195
|
+
id_filter_name="usernames",
|
|
196
|
+
)
|
|
233
197
|
|
|
234
198
|
# Avoid double removal as well as sort usernames
|
|
235
|
-
processed_usernames = sorted(
|
|
199
|
+
processed_usernames = sorted({user["username"] for user in users_data}, key=lambda v: v.lower())
|
|
236
200
|
count = len(processed_usernames)
|
|
237
201
|
|
|
238
202
|
# Delete each user
|
cmem_cmemc/commands/variable.py
CHANGED
|
@@ -17,7 +17,6 @@ from cmem_cmemc import completion
|
|
|
17
17
|
from cmem_cmemc.command import CmemcCommand
|
|
18
18
|
from cmem_cmemc.command_group import CmemcGroup
|
|
19
19
|
from cmem_cmemc.context import ApplicationContext, build_caption
|
|
20
|
-
from cmem_cmemc.exceptions import CmemcError
|
|
21
20
|
from cmem_cmemc.object_list import (
|
|
22
21
|
DirectMultiValuePropertyFilter,
|
|
23
22
|
DirectValuePropertyFilter,
|
|
@@ -25,7 +24,11 @@ from cmem_cmemc.object_list import (
|
|
|
25
24
|
ObjectList,
|
|
26
25
|
compare_regex,
|
|
27
26
|
)
|
|
28
|
-
from cmem_cmemc.utils import
|
|
27
|
+
from cmem_cmemc.utils import (
|
|
28
|
+
check_or_select_project,
|
|
29
|
+
get_objects_to_delete,
|
|
30
|
+
split_task_id,
|
|
31
|
+
)
|
|
29
32
|
|
|
30
33
|
|
|
31
34
|
def get_variables(ctx: Context) -> list[dict]: # noqa: ARG001
|
|
@@ -172,41 +175,6 @@ def _sort_variables_by_dependency(variables: list[dict]) -> list[str]:
|
|
|
172
175
|
return result
|
|
173
176
|
|
|
174
177
|
|
|
175
|
-
def _validate_variable_ids(variable_ids: tuple[str, ...]) -> None:
|
|
176
|
-
"""Validate that provided variable IDs exist."""
|
|
177
|
-
all_variables = get_all_variables()
|
|
178
|
-
all_variable_ids = [_["id"] for _ in all_variables]
|
|
179
|
-
for variable_id in variable_ids:
|
|
180
|
-
if variable_id not in all_variable_ids:
|
|
181
|
-
raise CmemcError(f"Variable {variable_id} not available.")
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
def _get_variables_to_delete(
|
|
185
|
-
ctx: Context,
|
|
186
|
-
variable_ids: tuple[str, ...],
|
|
187
|
-
all_: bool,
|
|
188
|
-
filter_: tuple[tuple[str, str], ...],
|
|
189
|
-
) -> list[dict]:
|
|
190
|
-
"""Get the list of variables to delete based on selection method."""
|
|
191
|
-
if all_:
|
|
192
|
-
_: list[dict] = get_all_variables()
|
|
193
|
-
return _
|
|
194
|
-
|
|
195
|
-
_validate_variable_ids(variable_ids)
|
|
196
|
-
|
|
197
|
-
filter_to_apply = list(filter_) if filter_ else []
|
|
198
|
-
|
|
199
|
-
if variable_ids:
|
|
200
|
-
filter_to_apply.append(("ids", ",".join(variable_ids)))
|
|
201
|
-
|
|
202
|
-
variables = variable_list_obj.apply_filters(ctx=ctx, filter_=filter_to_apply)
|
|
203
|
-
|
|
204
|
-
if not variables and not variable_ids:
|
|
205
|
-
raise CmemcError("No variables found matching the provided filters.")
|
|
206
|
-
|
|
207
|
-
return variables
|
|
208
|
-
|
|
209
|
-
|
|
210
178
|
@click.command(cls=CmemcCommand, name="list")
|
|
211
179
|
@click.option("--raw", is_flag=True, help="Outputs raw JSON.")
|
|
212
180
|
@click.option(
|
|
@@ -340,8 +308,16 @@ def delete_command(
|
|
|
340
308
|
"--filter options to specify variables for deletion."
|
|
341
309
|
)
|
|
342
310
|
|
|
343
|
-
|
|
344
|
-
|
|
311
|
+
variables_to_delete = get_objects_to_delete(
|
|
312
|
+
ctx=ctx,
|
|
313
|
+
ids=variable_ids,
|
|
314
|
+
all_=all_,
|
|
315
|
+
filter_=filter_,
|
|
316
|
+
object_list=variable_list_obj,
|
|
317
|
+
get_all_objects=get_all_variables,
|
|
318
|
+
extract_id=lambda x: x["id"],
|
|
319
|
+
error_message_template="Variable {} not available.",
|
|
320
|
+
)
|
|
345
321
|
|
|
346
322
|
# Remove duplicates while preserving variable objects for dependency analysis
|
|
347
323
|
unique_variables = list({v["id"]: v for v in variables_to_delete}.values())
|
|
@@ -26,6 +26,7 @@ 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
|
+
"package": "material/shopping",
|
|
29
30
|
"project": "eccenca/artefact-project",
|
|
30
31
|
"project file": "eccenca/artefact-file",
|
|
31
32
|
"project variable": "material/variable-box",
|
|
@@ -54,6 +55,7 @@ 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"],
|
|
58
|
+
"package": ["cmemc", "Package"],
|
|
57
59
|
"project": ["Project", "cmemc"],
|
|
58
60
|
"project file": ["Files", "cmemc"],
|
|
59
61
|
"project variable": ["Variables", "cmemc"],
|
cmem_cmemc/utils.py
CHANGED
|
@@ -6,11 +6,13 @@ import pathlib
|
|
|
6
6
|
import re
|
|
7
7
|
import sys
|
|
8
8
|
import unicodedata
|
|
9
|
+
from collections.abc import Callable
|
|
9
10
|
from dataclasses import dataclass
|
|
10
11
|
from importlib.metadata import version as cmemc_version
|
|
11
12
|
from typing import TYPE_CHECKING
|
|
12
13
|
from zipfile import BadZipFile, ZipFile
|
|
13
14
|
|
|
15
|
+
import click
|
|
14
16
|
import requests
|
|
15
17
|
from click import Argument
|
|
16
18
|
from cmem.cmempy.dp.proxy.graph import get_graphs_list
|
|
@@ -24,6 +26,7 @@ from cmem_cmemc.exceptions import CmemcError
|
|
|
24
26
|
from cmem_cmemc.smart_path import SmartPath
|
|
25
27
|
|
|
26
28
|
if TYPE_CHECKING:
|
|
29
|
+
from cmem_cmemc import object_list
|
|
27
30
|
from cmem_cmemc.context import ApplicationContext
|
|
28
31
|
|
|
29
32
|
|
|
@@ -453,3 +456,113 @@ def tuple_to_list(ctx: type["ApplicationContext"], param: Argument, value: tuple
|
|
|
453
456
|
Used as callback to have mutable values
|
|
454
457
|
"""
|
|
455
458
|
return list(value)
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def validate_ids(
|
|
462
|
+
ids: tuple[str, ...],
|
|
463
|
+
get_all_objects: Callable[[], list[dict]],
|
|
464
|
+
extract_id: Callable[[dict], str],
|
|
465
|
+
error_message_template: str,
|
|
466
|
+
) -> None:
|
|
467
|
+
"""Validate that all provided IDs exist.
|
|
468
|
+
|
|
469
|
+
This is a reusable validation function for delete commands.
|
|
470
|
+
|
|
471
|
+
Args:
|
|
472
|
+
----
|
|
473
|
+
ids: Tuple of identifiers to validate
|
|
474
|
+
get_all_objects: Callable that returns list of all objects (e.g., list_items, get_graphs)
|
|
475
|
+
extract_id: Callable that extracts the ID from an object dict (e.g., lambda x: x["iri"])
|
|
476
|
+
error_message_template: Error message template with {} placeholder for the missing ID
|
|
477
|
+
|
|
478
|
+
Raises:
|
|
479
|
+
------
|
|
480
|
+
CmemcError: If any ID is not found
|
|
481
|
+
|
|
482
|
+
Example:
|
|
483
|
+
-------
|
|
484
|
+
validate_ids(
|
|
485
|
+
ids=("graph1", "graph2"),
|
|
486
|
+
get_all_objects=lambda: get_graphs(),
|
|
487
|
+
extract_id=lambda x: x["iri"],
|
|
488
|
+
error_message_template="{} is not a valid graph IRI."
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
"""
|
|
492
|
+
if not ids:
|
|
493
|
+
return
|
|
494
|
+
all_objects = get_all_objects()
|
|
495
|
+
all_ids = {extract_id(obj) for obj in all_objects}
|
|
496
|
+
for id_ in ids:
|
|
497
|
+
if id_ not in all_ids:
|
|
498
|
+
raise CmemcError(error_message_template.format(id_))
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def get_objects_to_delete( # noqa: PLR0913
|
|
502
|
+
ctx: click.Context,
|
|
503
|
+
ids: tuple[str, ...],
|
|
504
|
+
all_: bool,
|
|
505
|
+
filter_: tuple[tuple[str, str], ...],
|
|
506
|
+
object_list: "object_list.ObjectList",
|
|
507
|
+
get_all_objects: Callable[[], list[dict]],
|
|
508
|
+
extract_id: Callable[[dict], str],
|
|
509
|
+
error_message_template: str,
|
|
510
|
+
id_filter_name: str = "ids",
|
|
511
|
+
) -> list[dict]:
|
|
512
|
+
"""Get the list of objects to delete based on selection method.
|
|
513
|
+
|
|
514
|
+
This is a reusable utility for delete commands that encapsulates the common pattern of:
|
|
515
|
+
1. If --all flag is set, return all objects
|
|
516
|
+
2. Validate provided IDs exist
|
|
517
|
+
3. Build filter list with IDs (using internal multi-value filter)
|
|
518
|
+
4. Apply filters using ObjectList
|
|
519
|
+
5. Validate that objects were found
|
|
520
|
+
|
|
521
|
+
Args:
|
|
522
|
+
----
|
|
523
|
+
ctx: Click context
|
|
524
|
+
ids: Tuple of specific identifiers to delete
|
|
525
|
+
all_: Flag to delete all objects
|
|
526
|
+
filter_: Tuple of filter name/value pairs
|
|
527
|
+
object_list: ObjectList instance for filtering
|
|
528
|
+
get_all_objects: Callable that returns list of all objects (for validation)
|
|
529
|
+
extract_id: Callable that extracts the ID from an object dict
|
|
530
|
+
error_message_template: Template string for error messages
|
|
531
|
+
id_filter_name: Name of the internal multi-value filter (default: "ids")
|
|
532
|
+
|
|
533
|
+
Returns:
|
|
534
|
+
-------
|
|
535
|
+
List of object dictionaries to delete
|
|
536
|
+
|
|
537
|
+
Raises:
|
|
538
|
+
------
|
|
539
|
+
CmemcError: If no objects found matching the criteria
|
|
540
|
+
|
|
541
|
+
"""
|
|
542
|
+
if all_:
|
|
543
|
+
return object_list.apply_filters(ctx=ctx, filter_=[])
|
|
544
|
+
|
|
545
|
+
# Validate provided IDs exist before proceeding
|
|
546
|
+
if ids:
|
|
547
|
+
validate_ids(
|
|
548
|
+
ids=ids,
|
|
549
|
+
get_all_objects=get_all_objects,
|
|
550
|
+
extract_id=extract_id,
|
|
551
|
+
error_message_template=error_message_template,
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
# Build filter list
|
|
555
|
+
filter_to_apply = list(filter_) if filter_ else []
|
|
556
|
+
|
|
557
|
+
# Add IDs if provided (using internal multi-value filter)
|
|
558
|
+
if ids:
|
|
559
|
+
filter_to_apply.append((id_filter_name, ",".join(ids)))
|
|
560
|
+
|
|
561
|
+
# Apply filters
|
|
562
|
+
objects = object_list.apply_filters(ctx=ctx, filter_=filter_to_apply)
|
|
563
|
+
|
|
564
|
+
# Validation: ensure we found objects
|
|
565
|
+
if not objects:
|
|
566
|
+
raise CmemcError("No objects found matching the provided criteria.")
|
|
567
|
+
|
|
568
|
+
return objects
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cmem-cmemc
|
|
3
|
-
Version: 26.1.
|
|
3
|
+
Version: 26.1.0rc3
|
|
4
4
|
Summary: Command line client for eccenca Corporate Memory
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
License-File: LICENSE
|
|
@@ -32,7 +32,7 @@ Requires-Dist: certifi (>=2024.2.2)
|
|
|
32
32
|
Requires-Dist: click (>=8.3.0,<9.0.0)
|
|
33
33
|
Requires-Dist: click-didyoumean (>=0.3.1,<0.4.0)
|
|
34
34
|
Requires-Dist: click-help-colors (>=0.9.4,<0.10.0)
|
|
35
|
-
Requires-Dist: cmem-client (
|
|
35
|
+
Requires-Dist: cmem-client (==0.7.1)
|
|
36
36
|
Requires-Dist: cmem-cmempy (==25.4.0)
|
|
37
37
|
Requires-Dist: configparser (>=7.2.0,<8.0.0)
|
|
38
38
|
Requires-Dist: humanize (>=4.14.0,<5.0.0)
|
|
@@ -4,27 +4,27 @@ cmem_cmemc/cli.py,sha256=VGfHqSIPPTvYpPHrjwbnLYLB6O0u8kwiJDXeXMlI1xU,4855
|
|
|
4
4
|
cmem_cmemc/command.py,sha256=CuaskaqD12soZLhDP1prgXOT4cRFu1CzuJm6LBp4zLM,1949
|
|
5
5
|
cmem_cmemc/command_group.py,sha256=0I2Jg1lCdoozcMg7d0g9sk0zVJ8_LiKvWWwqM6tZGI0,4793
|
|
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=X7RMwEO66SvOY4gXrlBJvKTUYiUdURf0YLaZx1Feqx8,30238
|
|
8
8
|
cmem_cmemc/commands/admin.py,sha256=XyJVk9CuxLh60GdWNtMkGPpZ3BOftDF4T04M8gRaOQ0,10193
|
|
9
9
|
cmem_cmemc/commands/client.py,sha256=4Ke9Pl6-KQ-6_uSpRbVhB7spAi8tP0r39y99fwFRSWE,5304
|
|
10
10
|
cmem_cmemc/commands/config.py,sha256=tRB7YK6vzkaasmyZVWGLOw1_x0SM2x-coYMBRIJwEJs,8392
|
|
11
|
-
cmem_cmemc/commands/dataset.py,sha256=
|
|
12
|
-
cmem_cmemc/commands/file.py,sha256=
|
|
13
|
-
cmem_cmemc/commands/graph.py,sha256=
|
|
11
|
+
cmem_cmemc/commands/dataset.py,sha256=ZdiKLXWq9ItnJb4xGiP0qaby5W3Kvkyqg8KIaYsHelI,29715
|
|
12
|
+
cmem_cmemc/commands/file.py,sha256=Ds7vzaiWIfnOesxS9KQQuBinlkSjcSqV_02SX1ad_sE,15926
|
|
13
|
+
cmem_cmemc/commands/graph.py,sha256=oUInQQEuL-n6bFEscNUB7qfPUk0yNajEy0H5VFXFPoQ,35667
|
|
14
14
|
cmem_cmemc/commands/graph_imports.py,sha256=VcOisHSvNYJPxMiRb6sHEswYIQHwlIamLwQD1jgr_e0,14368
|
|
15
|
-
cmem_cmemc/commands/graph_insights.py,sha256=
|
|
15
|
+
cmem_cmemc/commands/graph_insights.py,sha256=slQXBUy7azEG-w7M-2r9ybY4J3HgV9TTWbP5Ih3HDu0,14395
|
|
16
16
|
cmem_cmemc/commands/manual.py,sha256=-sZWeFL92Kj8gL3VYsbpKh2ZaVTyM3LgKaUcpNn9u3A,2179
|
|
17
17
|
cmem_cmemc/commands/metrics.py,sha256=t5I6VjBzjp_bQEoGkU9cdqNu_sa_WQwiIeJA3f9KNWc,12385
|
|
18
18
|
cmem_cmemc/commands/migration.py,sha256=FibmYpvZD2mrutjyRBhs7xDAZ-sjPiH9Kn3CI-zUPm0,9861
|
|
19
|
-
cmem_cmemc/commands/package.py,sha256=
|
|
20
|
-
cmem_cmemc/commands/project.py,sha256=
|
|
19
|
+
cmem_cmemc/commands/package.py,sha256=iY543hVwMqTA5t7mQAe2etz9n5sNgxx5y8Y-B53kJRY,16812
|
|
20
|
+
cmem_cmemc/commands/project.py,sha256=HRA4xpOENajyQub1iWLo5voAsXFUzFPt7kJq9sx4sx4,25066
|
|
21
21
|
cmem_cmemc/commands/python.py,sha256=lcbBAYZN5NB37HLSmVPs0SXJV7Ey4xVMYQiSiuyGkvc,12225
|
|
22
22
|
cmem_cmemc/commands/query.py,sha256=1cj1QbvwL98YbBGSCO0Zazbzscts_kiv0A7k75KwJXw,32231
|
|
23
23
|
cmem_cmemc/commands/scheduler.py,sha256=3wk3BF6Z3uRb0e5pphOYBusbXgs7C6Lz-D9wi7Nlohc,8855
|
|
24
24
|
cmem_cmemc/commands/store.py,sha256=zKz8FTtVSvFU6gMm6An7Jja9Bu9dZKbI1GW7UCq034s,10655
|
|
25
|
-
cmem_cmemc/commands/user.py,sha256=
|
|
25
|
+
cmem_cmemc/commands/user.py,sha256=Uqhqn5gb4Fy4_ZaJbAwVFTRERvRO7A0yYO2ic6Xz1Wc,14707
|
|
26
26
|
cmem_cmemc/commands/validation.py,sha256=v9_cXGzaexemuz6xBA358XY1_vP42SBfOD3PEZLcqbw,29731
|
|
27
|
-
cmem_cmemc/commands/variable.py,sha256=
|
|
27
|
+
cmem_cmemc/commands/variable.py,sha256=LRgZ82lubDnY0yh67KBF0se2W1PeE58pYAJuwftbfyE,17926
|
|
28
28
|
cmem_cmemc/commands/vocabulary.py,sha256=sv7hDZOeRPrPlc5RJfpAKzKH5JRyKjB93D-Jl4eLNqI,18359
|
|
29
29
|
cmem_cmemc/commands/workflow.py,sha256=UdKAsY3chxfrIpkjL1K9MqLVyu7jTdFYkOXfGVvJISI,26369
|
|
30
30
|
cmem_cmemc/commands/workspace.py,sha256=IcZgBsvtulLRFofS70qpln6oKQIZunrVLfSAUeiFhCA,4579
|
|
@@ -35,7 +35,7 @@ cmem_cmemc/context.py,sha256=oCcd6dFl6BdYqKsueVqzQhSEwTNW7b1MjrE4CRznxt8,23220
|
|
|
35
35
|
cmem_cmemc/exceptions.py,sha256=c4Z6CKgymu0a7gD8MtHxzK_7WCsb9I2Zl-EgEkwu-YY,760
|
|
36
36
|
cmem_cmemc/manual_helper/__init__.py,sha256=G3Lqw2aPxo8x63Tg7L0aa5VD9BMaRzZDmhrog7IuEPg,43
|
|
37
37
|
cmem_cmemc/manual_helper/graph.py,sha256=dTkFXgU9fgySn54rE93t79v1MjWjQkprKRIfJhc7Jps,3655
|
|
38
|
-
cmem_cmemc/manual_helper/multi_page.py,sha256=
|
|
38
|
+
cmem_cmemc/manual_helper/multi_page.py,sha256=vTW1xHeMqM0BCFYsftoirXZ6RwxEeV0LjiOPwbqBous,12360
|
|
39
39
|
cmem_cmemc/manual_helper/single_page.py,sha256=0mMn_IJwFCe-WPKAmxGEStb8IINLpQRxAx_F1pIxg1E,1526
|
|
40
40
|
cmem_cmemc/migrations/__init__.py,sha256=i6Ri7qN58ou_MwOzm2KibPkXOD7u-1ELky-nUE5LjAA,24
|
|
41
41
|
cmem_cmemc/migrations/abc.py,sha256=UGJzrvMzUFdp2-sosp49ObRI-SrUSzLJqLEhvB4QTzg,3564
|
|
@@ -54,9 +54,9 @@ cmem_cmemc/smart_path/clients/__init__.py,sha256=YFOm69BfTCRvAcJjN_CoUmCv3kzEciy
|
|
|
54
54
|
cmem_cmemc/smart_path/clients/http.py,sha256=3clZu2v4uuOvPY4MY_8SVSy7hIXJDNooahFRBRpy0ok,2347
|
|
55
55
|
cmem_cmemc/string_processor.py,sha256=19YSLUF9PIbfTmsTm2bZslsNhFUAYx0MerWYwC3BVEo,8616
|
|
56
56
|
cmem_cmemc/title_helper.py,sha256=8Cyes2U4lHTQbzYwBSYqCrZbq29_oBg6uibe7xZ6DEg,3486
|
|
57
|
-
cmem_cmemc/utils.py,sha256=
|
|
58
|
-
cmem_cmemc-26.1.
|
|
59
|
-
cmem_cmemc-26.1.
|
|
60
|
-
cmem_cmemc-26.1.
|
|
61
|
-
cmem_cmemc-26.1.
|
|
62
|
-
cmem_cmemc-26.1.
|
|
57
|
+
cmem_cmemc/utils.py,sha256=rs3qf5UZeiTQO0USUpFQq6upQnG_S43CW7YYUrCwmzk,18240
|
|
58
|
+
cmem_cmemc-26.1.0rc3.dist-info/METADATA,sha256=mB1KAv64UgOmwJ_4WbdqxeH7nI6MkVgq6d96hFrBPaE,5754
|
|
59
|
+
cmem_cmemc-26.1.0rc3.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
|
|
60
|
+
cmem_cmemc-26.1.0rc3.dist-info/entry_points.txt,sha256=2G0AWAyz501EHpFTjIxccdlCTsHt80NT0pdUGP1QkPA,45
|
|
61
|
+
cmem_cmemc-26.1.0rc3.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
62
|
+
cmem_cmemc-26.1.0rc3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|