cmem-cmemc 25.6.0__py3-none-any.whl → 26.1.0rc1__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/cli.py +11 -6
- cmem_cmemc/command.py +1 -1
- cmem_cmemc/command_group.py +27 -0
- cmem_cmemc/commands/acl.py +388 -20
- cmem_cmemc/commands/admin.py +10 -10
- cmem_cmemc/commands/client.py +12 -5
- cmem_cmemc/commands/config.py +106 -12
- cmem_cmemc/commands/dataset.py +162 -118
- cmem_cmemc/commands/file.py +117 -73
- cmem_cmemc/commands/graph.py +200 -72
- cmem_cmemc/commands/graph_imports.py +12 -5
- cmem_cmemc/commands/graph_insights.py +61 -25
- cmem_cmemc/commands/metrics.py +15 -9
- cmem_cmemc/commands/migration.py +12 -4
- cmem_cmemc/commands/package.py +548 -0
- cmem_cmemc/commands/project.py +155 -22
- cmem_cmemc/commands/python.py +8 -4
- cmem_cmemc/commands/query.py +119 -25
- cmem_cmemc/commands/scheduler.py +6 -4
- cmem_cmemc/commands/store.py +2 -1
- cmem_cmemc/commands/user.py +124 -24
- cmem_cmemc/commands/validation.py +15 -10
- cmem_cmemc/commands/variable.py +264 -61
- cmem_cmemc/commands/vocabulary.py +18 -13
- cmem_cmemc/commands/workflow.py +21 -11
- cmem_cmemc/completion.py +105 -105
- cmem_cmemc/context.py +38 -8
- cmem_cmemc/exceptions.py +8 -2
- cmem_cmemc/manual_helper/multi_page.py +0 -1
- cmem_cmemc/object_list.py +234 -7
- cmem_cmemc/string_processor.py +142 -5
- cmem_cmemc/title_helper.py +50 -0
- cmem_cmemc/utils.py +8 -7
- {cmem_cmemc-25.6.0.dist-info → cmem_cmemc-26.1.0rc1.dist-info}/METADATA +6 -6
- cmem_cmemc-26.1.0rc1.dist-info/RECORD +62 -0
- {cmem_cmemc-25.6.0.dist-info → cmem_cmemc-26.1.0rc1.dist-info}/WHEEL +1 -1
- cmem_cmemc-25.6.0.dist-info/RECORD +0 -61
- {cmem_cmemc-25.6.0.dist-info → cmem_cmemc-26.1.0rc1.dist-info}/entry_points.txt +0 -0
- {cmem_cmemc-25.6.0.dist-info → cmem_cmemc-26.1.0rc1.dist-info}/licenses/LICENSE +0 -0
cmem_cmemc/commands/file.py
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
"""Build project file commands for cmemc."""
|
|
2
2
|
|
|
3
|
-
import re
|
|
4
|
-
|
|
5
3
|
import click
|
|
6
|
-
from click import
|
|
7
|
-
from
|
|
4
|
+
from click import Context, UsageError
|
|
5
|
+
from click.shell_completion import CompletionItem
|
|
8
6
|
from cmem.cmempy.workspace.projects.resources import get_all_resources
|
|
9
7
|
from cmem.cmempy.workspace.projects.resources.resource import (
|
|
10
8
|
create_resource,
|
|
@@ -18,20 +16,56 @@ from cmem.cmempy.workspace.projects.resources.resource import (
|
|
|
18
16
|
from cmem_cmemc import completion
|
|
19
17
|
from cmem_cmemc.command import CmemcCommand
|
|
20
18
|
from cmem_cmemc.command_group import CmemcGroup
|
|
21
|
-
from cmem_cmemc.context import ApplicationContext
|
|
19
|
+
from cmem_cmemc.context import ApplicationContext, build_caption
|
|
22
20
|
from cmem_cmemc.exceptions import CmemcError
|
|
21
|
+
from cmem_cmemc.object_list import (
|
|
22
|
+
DirectMultiValuePropertyFilter,
|
|
23
|
+
DirectValuePropertyFilter,
|
|
24
|
+
ObjectList,
|
|
25
|
+
compare_regex,
|
|
26
|
+
)
|
|
23
27
|
from cmem_cmemc.parameter_types.path import ClickSmartPath
|
|
24
28
|
from cmem_cmemc.smart_path import SmartPath as Path
|
|
25
29
|
from cmem_cmemc.string_processor import FileSize, TimeAgo
|
|
26
30
|
from cmem_cmemc.utils import check_or_select_project, split_task_id, struct_to_table
|
|
27
31
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
|
|
33
|
+
def get_resources(ctx: Context) -> list[dict]: # noqa: ARG001
|
|
34
|
+
"""Get file resources for object list."""
|
|
35
|
+
_: list[dict] = get_all_resources()
|
|
36
|
+
return _
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
resource_list = ObjectList(
|
|
40
|
+
name="file resources",
|
|
41
|
+
get_objects=get_resources,
|
|
42
|
+
filters=[
|
|
43
|
+
DirectValuePropertyFilter(
|
|
44
|
+
name="project",
|
|
45
|
+
description="Filter file resources by project ID.",
|
|
46
|
+
property_key="project",
|
|
47
|
+
completion_method="values",
|
|
48
|
+
),
|
|
49
|
+
DirectValuePropertyFilter(
|
|
50
|
+
name="regex",
|
|
51
|
+
description="Filter by regex matching the resource name.",
|
|
52
|
+
property_key="name",
|
|
53
|
+
compare=compare_regex,
|
|
54
|
+
fixed_completion=[
|
|
55
|
+
CompletionItem("csv$", help="File resources which name ends with .csv"),
|
|
56
|
+
CompletionItem(
|
|
57
|
+
"2021-10-[0-9][0-9]",
|
|
58
|
+
help="File resources which name has a date from 2021-10 in it",
|
|
59
|
+
),
|
|
60
|
+
],
|
|
61
|
+
fixed_completion_only=False,
|
|
62
|
+
),
|
|
63
|
+
DirectMultiValuePropertyFilter(
|
|
64
|
+
name="ids",
|
|
65
|
+
description="Internal filter for multiple resource IDs.",
|
|
66
|
+
property_key="id",
|
|
67
|
+
),
|
|
68
|
+
],
|
|
35
69
|
)
|
|
36
70
|
|
|
37
71
|
|
|
@@ -60,7 +94,6 @@ def _upload_file_resource(
|
|
|
60
94
|
exist = resource_exist(project_name=project_id, resource_name=remote_file_name)
|
|
61
95
|
if exist and not replace:
|
|
62
96
|
raise CmemcError(
|
|
63
|
-
app,
|
|
64
97
|
f"A file resource with the name '{remote_file_name}' already "
|
|
65
98
|
"exists in this project. \n"
|
|
66
99
|
"Please rename the file or use the '--replace' "
|
|
@@ -87,25 +120,45 @@ def _upload_file_resource(
|
|
|
87
120
|
app.echo_success("done")
|
|
88
121
|
|
|
89
122
|
|
|
90
|
-
def
|
|
91
|
-
|
|
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], ...],
|
|
92
139
|
) -> list[dict]:
|
|
93
|
-
"""Get
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
#
|
|
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
|
+
|
|
109
162
|
return resources
|
|
110
163
|
|
|
111
164
|
|
|
@@ -119,24 +172,21 @@ def _get_resources_filtered(
|
|
|
119
172
|
)
|
|
120
173
|
@click.option(
|
|
121
174
|
"--filter",
|
|
122
|
-
"
|
|
175
|
+
"filter_",
|
|
123
176
|
multiple=True,
|
|
124
177
|
type=(str, str),
|
|
125
|
-
shell_complete=
|
|
126
|
-
help=
|
|
178
|
+
shell_complete=resource_list.complete_values,
|
|
179
|
+
help=resource_list.get_filter_help_text(),
|
|
127
180
|
)
|
|
128
|
-
@click.
|
|
129
|
-
def list_command(
|
|
130
|
-
app: ApplicationContext, raw: bool, id_only: bool, filters_: tuple[tuple[str, str], ...]
|
|
131
|
-
) -> None:
|
|
181
|
+
@click.pass_context
|
|
182
|
+
def list_command(ctx: Context, raw: bool, id_only: bool, filter_: tuple[tuple[str, str]]) -> None:
|
|
132
183
|
"""List available file resources.
|
|
133
184
|
|
|
134
185
|
Outputs a table or a list of file resources.
|
|
135
186
|
"""
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
resources = _get_resources_filtered(resources, filter_name, filter_value)
|
|
187
|
+
app: ApplicationContext = ctx.obj
|
|
188
|
+
resources = resource_list.apply_filters(ctx=ctx, filter_=filter_)
|
|
189
|
+
|
|
140
190
|
if raw:
|
|
141
191
|
app.echo_info_json(resources)
|
|
142
192
|
return
|
|
@@ -155,21 +205,17 @@ def list_command(
|
|
|
155
205
|
]
|
|
156
206
|
table.append(row)
|
|
157
207
|
|
|
158
|
-
|
|
159
|
-
empty_note = "No resources found."
|
|
160
|
-
if len(filters_) > 0:
|
|
161
|
-
caption += " (filtered)"
|
|
162
|
-
empty_note = "No resources found for these filters."
|
|
163
|
-
|
|
208
|
+
filtered = len(filter_) > 0
|
|
164
209
|
app.echo_info_table(
|
|
165
210
|
table,
|
|
166
211
|
headers=headers,
|
|
167
212
|
sort_column=0,
|
|
168
213
|
cell_processing={1: TimeAgo(), 2: FileSize()},
|
|
169
|
-
caption=
|
|
170
|
-
empty_table_message=
|
|
171
|
-
|
|
172
|
-
"the `
|
|
214
|
+
caption=build_caption(len(table), "file resource", filtered=filtered),
|
|
215
|
+
empty_table_message="No resources found for these filters."
|
|
216
|
+
if filtered
|
|
217
|
+
else "No resources found. Use the `dataset create` command to create a new "
|
|
218
|
+
"file-based dataset, or the `project file upload` command to create only a file resource.",
|
|
173
219
|
)
|
|
174
220
|
|
|
175
221
|
|
|
@@ -181,23 +227,23 @@ def list_command(
|
|
|
181
227
|
"--all",
|
|
182
228
|
"all_",
|
|
183
229
|
is_flag=True,
|
|
184
|
-
help="Delete all resources.
|
|
230
|
+
help="Delete all resources. This is a dangerous option, so use it with care.",
|
|
185
231
|
)
|
|
186
232
|
@click.option(
|
|
187
233
|
"--filter",
|
|
188
|
-
"
|
|
234
|
+
"filter_",
|
|
189
235
|
multiple=True,
|
|
190
236
|
type=(str, str),
|
|
191
|
-
shell_complete=
|
|
192
|
-
help=
|
|
237
|
+
shell_complete=resource_list.complete_values,
|
|
238
|
+
help=resource_list.get_filter_help_text(),
|
|
193
239
|
)
|
|
194
|
-
@click.
|
|
240
|
+
@click.pass_context
|
|
195
241
|
def delete_command(
|
|
196
|
-
|
|
242
|
+
ctx: Context,
|
|
197
243
|
resource_ids: tuple[str, ...],
|
|
198
244
|
force: bool,
|
|
199
245
|
all_: bool,
|
|
200
|
-
|
|
246
|
+
filter_: tuple[tuple[str, str], ...],
|
|
201
247
|
) -> None:
|
|
202
248
|
"""Delete file resources.
|
|
203
249
|
|
|
@@ -206,25 +252,23 @@ def delete_command(
|
|
|
206
252
|
on the filter type and value will be deleted; by using --all, which will
|
|
207
253
|
delete all resources.
|
|
208
254
|
"""
|
|
209
|
-
|
|
255
|
+
app: ApplicationContext = ctx.obj
|
|
256
|
+
|
|
257
|
+
# Validation: require at least one selection method
|
|
258
|
+
if not resource_ids and not all_ and not filter_:
|
|
210
259
|
raise UsageError(
|
|
211
260
|
"Either specify at least one resource ID or use the --all or "
|
|
212
261
|
"--filter options to specify resources for deletion."
|
|
213
262
|
)
|
|
214
263
|
|
|
215
|
-
resources
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
# "filter" by id
|
|
221
|
-
resources = _get_resources_filtered(resources, "ids", resource_ids)
|
|
222
|
-
for _ in filters_:
|
|
223
|
-
resources = _get_resources_filtered(resources, _[0], _[1])
|
|
224
|
-
|
|
225
|
-
# avoid double removal as well as sort IDs
|
|
226
|
-
processed_ids = sorted({_["id"] for _ in resources}, key=lambda v: v.lower())
|
|
264
|
+
# Get resources to delete based on selection method
|
|
265
|
+
resources_to_delete = _get_resources_to_delete(ctx, resource_ids, all_, filter_)
|
|
266
|
+
|
|
267
|
+
# Avoid double removal as well as sort IDs
|
|
268
|
+
processed_ids = sorted({_["id"] for _ in resources_to_delete}, key=lambda v: v.lower())
|
|
227
269
|
count = len(processed_ids)
|
|
270
|
+
|
|
271
|
+
# Delete each resource
|
|
228
272
|
for current, resource_id in enumerate(processed_ids, start=1):
|
|
229
273
|
current_string = str(current).zfill(len(str(count)))
|
|
230
274
|
app.echo_info(f"Delete resource {current_string}/{count}: {resource_id} ... ", nl=False)
|
|
@@ -310,7 +354,7 @@ def download_command(
|
|
|
310
354
|
for chunk in response.iter_content(chunk_size=8192):
|
|
311
355
|
resource_file.write(chunk)
|
|
312
356
|
app.echo_success("done")
|
|
313
|
-
except (OSError,
|
|
357
|
+
except (OSError, CmemcError) as error:
|
|
314
358
|
app.echo_error(f"failed: {error!s}")
|
|
315
359
|
continue
|
|
316
360
|
|