cmem-cmemc 25.5.0__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 +1 -0
- cmem_cmemc/commands/dataset.py +1 -54
- cmem_cmemc/commands/file.py +465 -0
- cmem_cmemc/commands/project.py +2 -0
- cmem_cmemc/commands/python.py +1 -1
- cmem_cmemc/completion.py +20 -1
- cmem_cmemc/manual_helper/graph.py +2 -2
- cmem_cmemc/manual_helper/multi_page.py +2 -2
- cmem_cmemc/placeholder.py +2 -2
- cmem_cmemc/string_processor.py +12 -0
- {cmem_cmemc-25.5.0.dist-info → cmem_cmemc-25.6.0.dist-info}/METADATA +2 -1
- {cmem_cmemc-25.5.0.dist-info → cmem_cmemc-25.6.0.dist-info}/RECORD +15 -15
- cmem_cmemc/commands/resource.py +0 -220
- {cmem_cmemc-25.5.0.dist-info → cmem_cmemc-25.6.0.dist-info}/WHEEL +0 -0
- {cmem_cmemc-25.5.0.dist-info → cmem_cmemc-25.6.0.dist-info}/entry_points.txt +0 -0
- {cmem_cmemc-25.5.0.dist-info → cmem_cmemc-25.6.0.dist-info}/licenses/LICENSE +0 -0
cmem_cmemc/command_group.py
CHANGED
|
@@ -38,6 +38,7 @@ class CmemcGroup(HelpColorsGroup, DYMGroup):
|
|
|
38
38
|
"enable": self.color_for_writing_commands,
|
|
39
39
|
"eval": self.color_for_writing_commands,
|
|
40
40
|
"execute": self.color_for_writing_commands,
|
|
41
|
+
"file": self.color_for_command_groups,
|
|
41
42
|
"graph": self.color_for_command_groups,
|
|
42
43
|
"import": self.color_for_writing_commands,
|
|
43
44
|
"imports": self.color_for_command_groups,
|
cmem_cmemc/commands/dataset.py
CHANGED
|
@@ -16,16 +16,14 @@ from cmem.cmempy.workspace.projects.datasets.dataset import (
|
|
|
16
16
|
update_dataset,
|
|
17
17
|
)
|
|
18
18
|
from cmem.cmempy.workspace.projects.resources.resource import (
|
|
19
|
-
create_resource,
|
|
20
19
|
get_resource_response,
|
|
21
|
-
resource_exist,
|
|
22
20
|
)
|
|
23
21
|
from cmem.cmempy.workspace.search import list_items
|
|
24
22
|
|
|
25
23
|
from cmem_cmemc import completion
|
|
26
24
|
from cmem_cmemc.command import CmemcCommand
|
|
27
25
|
from cmem_cmemc.command_group import CmemcGroup
|
|
28
|
-
from cmem_cmemc.commands.
|
|
26
|
+
from cmem_cmemc.commands.file import _upload_file_resource, resource
|
|
29
27
|
from cmem_cmemc.completion import get_dataset_file_mapping
|
|
30
28
|
from cmem_cmemc.context import ApplicationContext
|
|
31
29
|
from cmem_cmemc.exceptions import CmemcError
|
|
@@ -147,57 +145,6 @@ def _post_file_resource(
|
|
|
147
145
|
app.echo_success("done")
|
|
148
146
|
|
|
149
147
|
|
|
150
|
-
def _upload_file_resource(
|
|
151
|
-
app: ApplicationContext,
|
|
152
|
-
project_id: str,
|
|
153
|
-
local_file_name: str,
|
|
154
|
-
remote_file_name: str,
|
|
155
|
-
replace: bool,
|
|
156
|
-
) -> None:
|
|
157
|
-
"""Upload a local file as a dataset resource to a project.
|
|
158
|
-
|
|
159
|
-
Args:
|
|
160
|
-
----
|
|
161
|
-
app: the click cli app context.
|
|
162
|
-
project_id: The project ID in the workspace.
|
|
163
|
-
local_file_name: The path to the local file name
|
|
164
|
-
remote_file_name: The remote file name
|
|
165
|
-
replace: Replace resource if needed.
|
|
166
|
-
|
|
167
|
-
Raises:
|
|
168
|
-
------
|
|
169
|
-
ValueError: if resource exists and no replace
|
|
170
|
-
|
|
171
|
-
"""
|
|
172
|
-
exist = resource_exist(project_name=project_id, resource_name=remote_file_name)
|
|
173
|
-
if exist and not replace:
|
|
174
|
-
raise ClickException(
|
|
175
|
-
f"A file resource with the name '{remote_file_name}' already "
|
|
176
|
-
"exists in this project. \n"
|
|
177
|
-
"Please rename the file or use the '--replace' "
|
|
178
|
-
"parameter in order to overwrite the remote file."
|
|
179
|
-
)
|
|
180
|
-
if exist:
|
|
181
|
-
app.echo_info(
|
|
182
|
-
f"Replace content of {remote_file_name} with content from "
|
|
183
|
-
f"{local_file_name} in project {project_id} ... ",
|
|
184
|
-
nl=False,
|
|
185
|
-
)
|
|
186
|
-
else:
|
|
187
|
-
app.echo_info(
|
|
188
|
-
f"Upload {local_file_name} as a file resource "
|
|
189
|
-
f"{remote_file_name} to project {project_id} ... ",
|
|
190
|
-
nl=False,
|
|
191
|
-
)
|
|
192
|
-
create_resource(
|
|
193
|
-
project_name=project_id,
|
|
194
|
-
resource_name=remote_file_name,
|
|
195
|
-
file_resource=ClickSmartPath.open(local_file_name),
|
|
196
|
-
replace=replace,
|
|
197
|
-
)
|
|
198
|
-
app.echo_success("done")
|
|
199
|
-
|
|
200
|
-
|
|
201
148
|
def _get_metadata_out_of_parameter(parameter_dict: dict) -> dict:
|
|
202
149
|
"""Extract metadata keys out of the parameter dict.
|
|
203
150
|
|
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
"""Build project file commands for cmemc."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from click import ClickException, Context, UsageError
|
|
7
|
+
from cmem.cmempy.config import get_cmem_base_uri
|
|
8
|
+
from cmem.cmempy.workspace.projects.resources import get_all_resources
|
|
9
|
+
from cmem.cmempy.workspace.projects.resources.resource import (
|
|
10
|
+
create_resource,
|
|
11
|
+
delete_resource,
|
|
12
|
+
get_resource_metadata,
|
|
13
|
+
get_resource_response,
|
|
14
|
+
get_resource_usage_data,
|
|
15
|
+
resource_exist,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from cmem_cmemc import completion
|
|
19
|
+
from cmem_cmemc.command import CmemcCommand
|
|
20
|
+
from cmem_cmemc.command_group import CmemcGroup
|
|
21
|
+
from cmem_cmemc.context import ApplicationContext
|
|
22
|
+
from cmem_cmemc.exceptions import CmemcError
|
|
23
|
+
from cmem_cmemc.parameter_types.path import ClickSmartPath
|
|
24
|
+
from cmem_cmemc.smart_path import SmartPath as Path
|
|
25
|
+
from cmem_cmemc.string_processor import FileSize, TimeAgo
|
|
26
|
+
from cmem_cmemc.utils import check_or_select_project, split_task_id, struct_to_table
|
|
27
|
+
|
|
28
|
+
RESOURCE_FILTER_TYPES = ["project", "regex"]
|
|
29
|
+
RESOURCE_FILTER_TYPES_HIDDEN = ["ids"]
|
|
30
|
+
RESOURCE_FILTER_TEXT = (
|
|
31
|
+
"Filter file resources based on metadata. "
|
|
32
|
+
f"First parameter CHOICE can be one of {RESOURCE_FILTER_TYPES!s}"
|
|
33
|
+
". The second parameter is based on CHOICE, e.g. a project "
|
|
34
|
+
"ID or a regular expression string."
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _upload_file_resource(
|
|
39
|
+
app: ApplicationContext,
|
|
40
|
+
project_id: str,
|
|
41
|
+
local_file_name: str,
|
|
42
|
+
remote_file_name: str,
|
|
43
|
+
replace: bool,
|
|
44
|
+
) -> None:
|
|
45
|
+
"""Upload a local file as a dataset resource to a project.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
----
|
|
49
|
+
app: the click cli app context.
|
|
50
|
+
project_id: The project ID in the workspace.
|
|
51
|
+
local_file_name: The path to the local file name
|
|
52
|
+
remote_file_name: The remote file name
|
|
53
|
+
replace: Replace resource if needed.
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
------
|
|
57
|
+
ValueError: if resource exists and no replace
|
|
58
|
+
|
|
59
|
+
"""
|
|
60
|
+
exist = resource_exist(project_name=project_id, resource_name=remote_file_name)
|
|
61
|
+
if exist and not replace:
|
|
62
|
+
raise CmemcError(
|
|
63
|
+
app,
|
|
64
|
+
f"A file resource with the name '{remote_file_name}' already "
|
|
65
|
+
"exists in this project. \n"
|
|
66
|
+
"Please rename the file or use the '--replace' "
|
|
67
|
+
"parameter in order to overwrite the remote file.",
|
|
68
|
+
)
|
|
69
|
+
if exist:
|
|
70
|
+
app.echo_info(
|
|
71
|
+
f"Replace content of {remote_file_name} with content from "
|
|
72
|
+
f"{local_file_name} in project {project_id} ... ",
|
|
73
|
+
nl=False,
|
|
74
|
+
)
|
|
75
|
+
else:
|
|
76
|
+
app.echo_info(
|
|
77
|
+
f"Upload {local_file_name} as a file resource "
|
|
78
|
+
f"{remote_file_name} to project {project_id} ... ",
|
|
79
|
+
nl=False,
|
|
80
|
+
)
|
|
81
|
+
create_resource(
|
|
82
|
+
project_name=project_id,
|
|
83
|
+
resource_name=remote_file_name,
|
|
84
|
+
file_resource=ClickSmartPath.open(local_file_name),
|
|
85
|
+
replace=replace,
|
|
86
|
+
)
|
|
87
|
+
app.echo_success("done")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _get_resources_filtered(
|
|
91
|
+
resources: list[dict], filter_name: str, filter_value: str | tuple[str, ...]
|
|
92
|
+
) -> list[dict]:
|
|
93
|
+
"""Get file resources but filtered according to name and value."""
|
|
94
|
+
# check for correct filter names (filter ids is used internally only)
|
|
95
|
+
if filter_name not in RESOURCE_FILTER_TYPES + RESOURCE_FILTER_TYPES_HIDDEN:
|
|
96
|
+
raise UsageError(
|
|
97
|
+
f"{filter_name} is an unknown filter name. " f"Use one of {RESOURCE_FILTER_TYPES}."
|
|
98
|
+
)
|
|
99
|
+
# filter by ID list
|
|
100
|
+
if filter_name == "ids":
|
|
101
|
+
return [_ for _ in resources if _["id"] in filter_value]
|
|
102
|
+
# filter by project
|
|
103
|
+
if filter_name == "project":
|
|
104
|
+
return [_ for _ in resources if _["project"] == str(filter_value)]
|
|
105
|
+
# filter by regex
|
|
106
|
+
if filter_name == "regex":
|
|
107
|
+
return [_ for _ in resources if re.search(str(filter_value), _["name"])]
|
|
108
|
+
# return unfiltered list
|
|
109
|
+
return resources
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@click.command(cls=CmemcCommand, name="list")
|
|
113
|
+
@click.option("--raw", is_flag=True, help="Outputs raw JSON.")
|
|
114
|
+
@click.option(
|
|
115
|
+
"--id-only",
|
|
116
|
+
is_flag=True,
|
|
117
|
+
help="Lists only resource IDs and no other metadata. "
|
|
118
|
+
"This is useful for piping the IDs into other commands.",
|
|
119
|
+
)
|
|
120
|
+
@click.option(
|
|
121
|
+
"--filter",
|
|
122
|
+
"filters_",
|
|
123
|
+
multiple=True,
|
|
124
|
+
type=(str, str),
|
|
125
|
+
shell_complete=completion.resource_list_filter,
|
|
126
|
+
help=RESOURCE_FILTER_TEXT,
|
|
127
|
+
)
|
|
128
|
+
@click.pass_obj
|
|
129
|
+
def list_command(
|
|
130
|
+
app: ApplicationContext, raw: bool, id_only: bool, filters_: tuple[tuple[str, str], ...]
|
|
131
|
+
) -> None:
|
|
132
|
+
"""List available file resources.
|
|
133
|
+
|
|
134
|
+
Outputs a table or a list of file resources.
|
|
135
|
+
"""
|
|
136
|
+
resources = get_all_resources()
|
|
137
|
+
for _ in filters_:
|
|
138
|
+
filter_name, filter_value = _
|
|
139
|
+
resources = _get_resources_filtered(resources, filter_name, filter_value)
|
|
140
|
+
if raw:
|
|
141
|
+
app.echo_info_json(resources)
|
|
142
|
+
return
|
|
143
|
+
if id_only:
|
|
144
|
+
for _ in sorted(_["id"] for _ in resources):
|
|
145
|
+
app.echo_result(_)
|
|
146
|
+
return
|
|
147
|
+
# output a user table
|
|
148
|
+
table = []
|
|
149
|
+
headers = ["ID", "Modified", "Size"]
|
|
150
|
+
for _ in resources:
|
|
151
|
+
row = [
|
|
152
|
+
_["id"],
|
|
153
|
+
_["modified"],
|
|
154
|
+
_["size"],
|
|
155
|
+
]
|
|
156
|
+
table.append(row)
|
|
157
|
+
|
|
158
|
+
caption = f"{len(table)} files of {get_cmem_base_uri()}"
|
|
159
|
+
empty_note = "No resources found."
|
|
160
|
+
if len(filters_) > 0:
|
|
161
|
+
caption += " (filtered)"
|
|
162
|
+
empty_note = "No resources found for these filters."
|
|
163
|
+
|
|
164
|
+
app.echo_info_table(
|
|
165
|
+
table,
|
|
166
|
+
headers=headers,
|
|
167
|
+
sort_column=0,
|
|
168
|
+
cell_processing={1: TimeAgo(), 2: FileSize()},
|
|
169
|
+
caption=caption,
|
|
170
|
+
empty_table_message=f"{empty_note} "
|
|
171
|
+
"Use the `dataset create` command to create a new file-based dataset, or "
|
|
172
|
+
"the `project file upload` command to create only a file resource.",
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@click.command(cls=CmemcCommand, name="delete")
|
|
177
|
+
@click.argument("resource_ids", nargs=-1, type=click.STRING, shell_complete=completion.resource_ids)
|
|
178
|
+
@click.option("--force", is_flag=True, help="Delete resource even if in use by a task.")
|
|
179
|
+
@click.option(
|
|
180
|
+
"-a",
|
|
181
|
+
"--all",
|
|
182
|
+
"all_",
|
|
183
|
+
is_flag=True,
|
|
184
|
+
help="Delete all resources. " "This is a dangerous option, so use it with care.",
|
|
185
|
+
)
|
|
186
|
+
@click.option(
|
|
187
|
+
"--filter",
|
|
188
|
+
"filters_",
|
|
189
|
+
multiple=True,
|
|
190
|
+
type=(str, str),
|
|
191
|
+
shell_complete=completion.resource_list_filter,
|
|
192
|
+
help=RESOURCE_FILTER_TEXT,
|
|
193
|
+
)
|
|
194
|
+
@click.pass_obj
|
|
195
|
+
def delete_command(
|
|
196
|
+
app: ApplicationContext,
|
|
197
|
+
resource_ids: tuple[str, ...],
|
|
198
|
+
force: bool,
|
|
199
|
+
all_: bool,
|
|
200
|
+
filters_: tuple[tuple[str, str], ...],
|
|
201
|
+
) -> None:
|
|
202
|
+
"""Delete file resources.
|
|
203
|
+
|
|
204
|
+
There are three selection mechanisms: with specific IDs - only those
|
|
205
|
+
specified resources will be deleted; by using --filter - resources based
|
|
206
|
+
on the filter type and value will be deleted; by using --all, which will
|
|
207
|
+
delete all resources.
|
|
208
|
+
"""
|
|
209
|
+
if resource_ids == () and not all_ and filters_ == ():
|
|
210
|
+
raise UsageError(
|
|
211
|
+
"Either specify at least one resource ID or use the --all or "
|
|
212
|
+
"--filter options to specify resources for deletion."
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
resources = get_all_resources()
|
|
216
|
+
if len(resource_ids) > 0:
|
|
217
|
+
for resource_id in resource_ids:
|
|
218
|
+
if resource_id not in [_["id"] for _ in resources]:
|
|
219
|
+
raise ClickException(f"Resource {resource_id} not available.")
|
|
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())
|
|
227
|
+
count = len(processed_ids)
|
|
228
|
+
for current, resource_id in enumerate(processed_ids, start=1):
|
|
229
|
+
current_string = str(current).zfill(len(str(count)))
|
|
230
|
+
app.echo_info(f"Delete resource {current_string}/{count}: {resource_id} ... ", nl=False)
|
|
231
|
+
project_id, resource_local_id = split_task_id(resource_id)
|
|
232
|
+
usage = get_resource_usage_data(project_id, resource_local_id)
|
|
233
|
+
if len(usage) > 0:
|
|
234
|
+
app.echo_error(f"in use by {len(usage)} task(s)", nl=False)
|
|
235
|
+
if force:
|
|
236
|
+
app.echo_info(" ... ", nl=False)
|
|
237
|
+
else:
|
|
238
|
+
app.echo_info("")
|
|
239
|
+
continue
|
|
240
|
+
delete_resource(project_name=project_id, resource_name=resource_local_id)
|
|
241
|
+
app.echo_success("deleted")
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@click.command(cls=CmemcCommand, name="download")
|
|
245
|
+
@click.argument("resource_ids", nargs=-1, type=click.STRING, shell_complete=completion.resource_ids)
|
|
246
|
+
@click.option(
|
|
247
|
+
"--output-dir",
|
|
248
|
+
default=".",
|
|
249
|
+
show_default=True,
|
|
250
|
+
type=ClickSmartPath(writable=True, file_okay=False),
|
|
251
|
+
help="The directory where the downloaded files will be saved. "
|
|
252
|
+
"If this directory does not exist, it will be created.",
|
|
253
|
+
)
|
|
254
|
+
@click.option(
|
|
255
|
+
"--replace",
|
|
256
|
+
is_flag=True,
|
|
257
|
+
help="Replace existing files. This is a dangerous option, " "so use it with care!",
|
|
258
|
+
)
|
|
259
|
+
@click.pass_obj
|
|
260
|
+
def download_command(
|
|
261
|
+
app: ApplicationContext, resource_ids: tuple[str], output_dir: str, replace: bool
|
|
262
|
+
) -> None:
|
|
263
|
+
"""Download file resources to the local file system.
|
|
264
|
+
|
|
265
|
+
This command downloads one or more file resources from projects to your local
|
|
266
|
+
file system. Files are saved with their resource names in the output directory.
|
|
267
|
+
|
|
268
|
+
Resources are identified by their IDs in the format PROJECT_ID:RESOURCE_NAME.
|
|
269
|
+
|
|
270
|
+
Example: cmemc project file download my-proj:my-file.csv
|
|
271
|
+
|
|
272
|
+
Example: cmemc project file download my-proj:file1.csv my-proj:file2.csv --output-dir /tmp
|
|
273
|
+
"""
|
|
274
|
+
import os
|
|
275
|
+
|
|
276
|
+
if not resource_ids:
|
|
277
|
+
raise UsageError(
|
|
278
|
+
"At least one resource ID must be specified. "
|
|
279
|
+
"Use 'project file list' to see available resources."
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
count = len(resource_ids)
|
|
283
|
+
for current, resource_id in enumerate(resource_ids, start=1):
|
|
284
|
+
try:
|
|
285
|
+
project_id, resource_name = split_task_id(resource_id)
|
|
286
|
+
except ValueError:
|
|
287
|
+
app.echo_error(f"Invalid resource ID format: {resource_id}")
|
|
288
|
+
continue
|
|
289
|
+
|
|
290
|
+
# Build output path
|
|
291
|
+
output_path = os.path.normpath(str(Path(output_dir) / resource_name))
|
|
292
|
+
|
|
293
|
+
app.echo_info(
|
|
294
|
+
f"Download resource {current}/{count}: {resource_id} to {output_path} ... ",
|
|
295
|
+
nl=False,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
if Path(output_path).exists() and replace is not True:
|
|
299
|
+
app.echo_error("target file exists")
|
|
300
|
+
continue
|
|
301
|
+
|
|
302
|
+
# Create parent directory if it doesn't exist
|
|
303
|
+
Path(output_path).parent.mkdir(exist_ok=True, parents=True)
|
|
304
|
+
|
|
305
|
+
try:
|
|
306
|
+
with (
|
|
307
|
+
get_resource_response(project_id, resource_name) as response,
|
|
308
|
+
click.open_file(output_path, "wb") as resource_file,
|
|
309
|
+
):
|
|
310
|
+
for chunk in response.iter_content(chunk_size=8192):
|
|
311
|
+
resource_file.write(chunk)
|
|
312
|
+
app.echo_success("done")
|
|
313
|
+
except (OSError, ClickException) as error:
|
|
314
|
+
app.echo_error(f"failed: {error!s}")
|
|
315
|
+
continue
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
@click.command(cls=CmemcCommand, name="upload")
|
|
319
|
+
@click.argument(
|
|
320
|
+
"input_path",
|
|
321
|
+
required=True,
|
|
322
|
+
type=ClickSmartPath(
|
|
323
|
+
allow_dash=False, dir_okay=False, readable=True, exists=True, remote_okay=True
|
|
324
|
+
),
|
|
325
|
+
)
|
|
326
|
+
@click.option(
|
|
327
|
+
"--project",
|
|
328
|
+
"project_id",
|
|
329
|
+
type=click.STRING,
|
|
330
|
+
shell_complete=completion.project_ids,
|
|
331
|
+
help="The project where you want to upload the file. If there is "
|
|
332
|
+
"only one project in the workspace, this option can be omitted.",
|
|
333
|
+
)
|
|
334
|
+
@click.option(
|
|
335
|
+
"--path",
|
|
336
|
+
"remote_name",
|
|
337
|
+
type=click.STRING,
|
|
338
|
+
shell_complete=completion.resource_paths,
|
|
339
|
+
help="The path/name of the file resource in the project (e.g., 'data/file.csv'). "
|
|
340
|
+
"If not specified, the local file name will be used.",
|
|
341
|
+
)
|
|
342
|
+
@click.option(
|
|
343
|
+
"--replace",
|
|
344
|
+
is_flag=True,
|
|
345
|
+
help="Replace existing file resource. This is a dangerous option, " "so use it with care!",
|
|
346
|
+
)
|
|
347
|
+
@click.pass_obj
|
|
348
|
+
def upload_command(
|
|
349
|
+
app: ApplicationContext, input_path: str, project_id: str, remote_name: str, replace: bool
|
|
350
|
+
) -> None:
|
|
351
|
+
"""Upload a file to a project.
|
|
352
|
+
|
|
353
|
+
This command uploads a file to a project as a file resource.
|
|
354
|
+
|
|
355
|
+
Note: If you want to create a dataset from your file, the `dataset create`
|
|
356
|
+
command is maybe the better option.
|
|
357
|
+
|
|
358
|
+
Example: cmemc project file upload my-file.csv --project my-project
|
|
359
|
+
"""
|
|
360
|
+
project_id = check_or_select_project(app, project_id)
|
|
361
|
+
local_file_name = Path(input_path).name
|
|
362
|
+
|
|
363
|
+
if remote_name and remote_name.endswith("/"):
|
|
364
|
+
app.echo_warning(
|
|
365
|
+
f"Remote path ends with a slash, so the local file name is appended: {local_file_name}."
|
|
366
|
+
)
|
|
367
|
+
remote_name = remote_name + local_file_name
|
|
368
|
+
|
|
369
|
+
# Use local filename if remote name not specified
|
|
370
|
+
if not remote_name:
|
|
371
|
+
remote_name = local_file_name
|
|
372
|
+
|
|
373
|
+
_upload_file_resource(
|
|
374
|
+
app=app,
|
|
375
|
+
remote_file_name=remote_name,
|
|
376
|
+
project_id=project_id,
|
|
377
|
+
local_file_name=input_path,
|
|
378
|
+
replace=replace,
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
@click.command(cls=CmemcCommand, name="inspect")
|
|
383
|
+
@click.argument("resource_id", type=click.STRING, shell_complete=completion.resource_ids)
|
|
384
|
+
@click.option("--raw", is_flag=True, help="Outputs raw JSON.")
|
|
385
|
+
@click.pass_obj
|
|
386
|
+
def inspect_command(app: ApplicationContext, resource_id: str, raw: bool) -> None:
|
|
387
|
+
"""Display all metadata of a file resource."""
|
|
388
|
+
project_id, resource_id = split_task_id(resource_id)
|
|
389
|
+
resource_data = get_resource_metadata(project_id, resource_id)
|
|
390
|
+
if raw:
|
|
391
|
+
app.echo_info_json(resource_data)
|
|
392
|
+
else:
|
|
393
|
+
table = struct_to_table(resource_data)
|
|
394
|
+
app.echo_info_table(table, headers=["Key", "Value"], sort_column=0)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
@click.command(cls=CmemcCommand, name="usage")
|
|
398
|
+
@click.argument("resource_id", type=click.STRING, shell_complete=completion.resource_ids)
|
|
399
|
+
@click.option("--raw", is_flag=True, help="Outputs raw JSON.")
|
|
400
|
+
@click.pass_obj
|
|
401
|
+
def usage_command(app: ApplicationContext, resource_id: str, raw: bool) -> None:
|
|
402
|
+
"""Display all usage data of a file resource."""
|
|
403
|
+
project_id, resource_id = split_task_id(resource_id)
|
|
404
|
+
usage = get_resource_usage_data(project_id, resource_id)
|
|
405
|
+
if raw:
|
|
406
|
+
app.echo_info_json(usage)
|
|
407
|
+
return
|
|
408
|
+
# output a user table
|
|
409
|
+
table = []
|
|
410
|
+
headers = ["Task ID", "Type", "Label"]
|
|
411
|
+
for _ in usage:
|
|
412
|
+
row = [project_id + ":" + _["id"], _["taskType"], _["label"]]
|
|
413
|
+
table.append(row)
|
|
414
|
+
app.echo_info_table(
|
|
415
|
+
table,
|
|
416
|
+
empty_table_message=f"The file resource '{resource_id}' is not used in "
|
|
417
|
+
f"any task in project '{project_id}'.",
|
|
418
|
+
headers=headers,
|
|
419
|
+
sort_column=2,
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
@click.group(
|
|
424
|
+
cls=CmemcGroup,
|
|
425
|
+
hidden=True,
|
|
426
|
+
)
|
|
427
|
+
@click.pass_context
|
|
428
|
+
def resource(ctx: Context) -> None:
|
|
429
|
+
"""List, inspect or delete dataset file resources.
|
|
430
|
+
|
|
431
|
+
File resources are identified by their paths and project IDs.
|
|
432
|
+
|
|
433
|
+
Warning: This command group is deprecated and will be removed with the next major release.
|
|
434
|
+
Please use the `project file` command group instead.
|
|
435
|
+
"""
|
|
436
|
+
app: ApplicationContext = ctx.obj
|
|
437
|
+
app.echo_warning(
|
|
438
|
+
"The 'dataset resource' command group is deprecated and will be removed with the next"
|
|
439
|
+
" major release. Please use the 'project file' command group instead.",
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
@click.group(cls=CmemcGroup)
|
|
444
|
+
def file() -> CmemcGroup: # type: ignore[empty-body]
|
|
445
|
+
"""List, inspect, up-/download or delete project file resources.
|
|
446
|
+
|
|
447
|
+
File resources are identified with a RESOURCE_ID which is a concatenation
|
|
448
|
+
of its project ID and its relative path, e.g. `my-project:path-to/table.csv`.
|
|
449
|
+
|
|
450
|
+
Note: To get a list of existing file resources, execute the `project file list` command
|
|
451
|
+
or use tab-completion.
|
|
452
|
+
"""
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
resource.add_command(list_command)
|
|
456
|
+
resource.add_command(delete_command)
|
|
457
|
+
resource.add_command(inspect_command)
|
|
458
|
+
resource.add_command(usage_command)
|
|
459
|
+
|
|
460
|
+
file.add_command(list_command)
|
|
461
|
+
file.add_command(delete_command)
|
|
462
|
+
file.add_command(download_command)
|
|
463
|
+
file.add_command(upload_command)
|
|
464
|
+
file.add_command(inspect_command)
|
|
465
|
+
file.add_command(usage_command)
|
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
|
|
@@ -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:
|
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
|
|
@@ -1053,3 +1053,22 @@ def transformation_task_ids(ctx: Context, param: Argument, incomplete: str) -> l
|
|
|
1053
1053
|
datasets = results["results"]
|
|
1054
1054
|
options = [(f"{_['projectId']}:{_['id']}", _["label"]) for _ in datasets]
|
|
1055
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)
|
|
@@ -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,8 +26,8 @@ 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",
|
|
@@ -55,8 +55,8 @@ def get_tags_for_command_group(full_name: str) -> str:
|
|
|
55
55
|
"admin workspace python": ["Python", "cmemc"],
|
|
56
56
|
"config": ["Configuration", "cmemc"],
|
|
57
57
|
"dataset": ["cmemc"],
|
|
58
|
-
"dataset resource": ["cmemc"],
|
|
59
58
|
"project": ["Project", "cmemc"],
|
|
59
|
+
"project file": ["Files", "cmemc"],
|
|
60
60
|
"project variable": ["Variables", "cmemc"],
|
|
61
61
|
"query": ["SPARQL", "cmemc"],
|
|
62
62
|
"graph": ["KnowledgeGraph", "cmemc"],
|
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
|
|
|
@@ -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,23 +2,23 @@ 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
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
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
|
|
@@ -27,14 +27,14 @@ cmem_cmemc/commands/variable.py,sha256=aLRH_rFe0h7JBpKIqzcevbk26vczgUGokIDY8g6LP
|
|
|
27
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
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
56
|
cmem_cmemc/utils.py,sha256=LlvAMHxciY9ge-REdwHQhRetJGrYghRqBZADxqE0yL4,14657
|
|
57
|
-
cmem_cmemc-25.
|
|
58
|
-
cmem_cmemc-25.
|
|
59
|
-
cmem_cmemc-25.
|
|
60
|
-
cmem_cmemc-25.
|
|
61
|
-
cmem_cmemc-25.
|
|
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,,
|
cmem_cmemc/commands/resource.py
DELETED
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
"""Build dataset resource commands for cmemc."""
|
|
2
|
-
|
|
3
|
-
import re
|
|
4
|
-
|
|
5
|
-
import click
|
|
6
|
-
from click import ClickException, UsageError
|
|
7
|
-
from cmem.cmempy.workspace.projects.resources import get_all_resources
|
|
8
|
-
from cmem.cmempy.workspace.projects.resources.resource import (
|
|
9
|
-
delete_resource,
|
|
10
|
-
get_resource_metadata,
|
|
11
|
-
get_resource_usage_data,
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
from cmem_cmemc import completion
|
|
15
|
-
from cmem_cmemc.command import CmemcCommand
|
|
16
|
-
from cmem_cmemc.command_group import CmemcGroup
|
|
17
|
-
from cmem_cmemc.context import ApplicationContext
|
|
18
|
-
from cmem_cmemc.utils import split_task_id, struct_to_table
|
|
19
|
-
|
|
20
|
-
RESOURCE_FILTER_TYPES = ["project", "regex"]
|
|
21
|
-
RESOURCE_FILTER_TYPES_HIDDEN = ["ids"]
|
|
22
|
-
RESOURCE_FILTER_TEXT = (
|
|
23
|
-
"Filter file resources based on metadata. "
|
|
24
|
-
f"First parameter CHOICE can be one of {RESOURCE_FILTER_TYPES!s}"
|
|
25
|
-
". The second parameter is based on CHOICE, e.g. a project "
|
|
26
|
-
"ID or a regular expression string."
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def _get_resources_filtered(
|
|
31
|
-
resources: list[dict], filter_name: str, filter_value: str | tuple[str, ...]
|
|
32
|
-
) -> list[dict]:
|
|
33
|
-
"""Get file resources but filtered according to name and value."""
|
|
34
|
-
# check for correct filter names (filter ids is used internally only)
|
|
35
|
-
if filter_name not in RESOURCE_FILTER_TYPES + RESOURCE_FILTER_TYPES_HIDDEN:
|
|
36
|
-
raise UsageError(
|
|
37
|
-
f"{filter_name} is an unknown filter name. " f"Use one of {RESOURCE_FILTER_TYPES}."
|
|
38
|
-
)
|
|
39
|
-
# filter by ID list
|
|
40
|
-
if filter_name == "ids":
|
|
41
|
-
return [_ for _ in resources if _["id"] in filter_value]
|
|
42
|
-
# filter by project
|
|
43
|
-
if filter_name == "project":
|
|
44
|
-
return [_ for _ in resources if _["project"] == str(filter_value)]
|
|
45
|
-
# filter by regex
|
|
46
|
-
if filter_name == "regex":
|
|
47
|
-
return [_ for _ in resources if re.search(str(filter_value), _["name"])]
|
|
48
|
-
# return unfiltered list
|
|
49
|
-
return resources
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
@click.command(cls=CmemcCommand, name="list")
|
|
53
|
-
@click.option("--raw", is_flag=True, help="Outputs raw JSON.")
|
|
54
|
-
@click.option(
|
|
55
|
-
"--id-only",
|
|
56
|
-
is_flag=True,
|
|
57
|
-
help="Lists only resource names and no other metadata. "
|
|
58
|
-
"This is useful for piping the IDs into other commands.",
|
|
59
|
-
)
|
|
60
|
-
@click.option(
|
|
61
|
-
"--filter",
|
|
62
|
-
"filters_",
|
|
63
|
-
multiple=True,
|
|
64
|
-
type=(str, str),
|
|
65
|
-
shell_complete=completion.resource_list_filter,
|
|
66
|
-
help=RESOURCE_FILTER_TEXT,
|
|
67
|
-
)
|
|
68
|
-
@click.pass_obj
|
|
69
|
-
def list_command(
|
|
70
|
-
app: ApplicationContext, raw: bool, id_only: bool, filters_: tuple[tuple[str, str], ...]
|
|
71
|
-
) -> None:
|
|
72
|
-
"""List available file resources.
|
|
73
|
-
|
|
74
|
-
Outputs a table or a list of dataset resources (files).
|
|
75
|
-
"""
|
|
76
|
-
resources = get_all_resources()
|
|
77
|
-
for _ in filters_:
|
|
78
|
-
filter_name, filter_value = _
|
|
79
|
-
resources = _get_resources_filtered(resources, filter_name, filter_value)
|
|
80
|
-
if raw:
|
|
81
|
-
app.echo_info_json(resources)
|
|
82
|
-
return
|
|
83
|
-
if id_only:
|
|
84
|
-
for _ in sorted(_["id"] for _ in resources):
|
|
85
|
-
app.echo_result(_)
|
|
86
|
-
return
|
|
87
|
-
# output a user table
|
|
88
|
-
table = []
|
|
89
|
-
headers = ["ID", "Modified", "Size"]
|
|
90
|
-
for _ in resources:
|
|
91
|
-
row = [
|
|
92
|
-
_["id"],
|
|
93
|
-
_["modified"],
|
|
94
|
-
_["size"],
|
|
95
|
-
]
|
|
96
|
-
table.append(row)
|
|
97
|
-
app.echo_info_table(
|
|
98
|
-
table,
|
|
99
|
-
headers=headers,
|
|
100
|
-
sort_column=0,
|
|
101
|
-
empty_table_message="No dataset resources found. "
|
|
102
|
-
"Use the `dataset create` command to create a new file based dataset.",
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
@click.command(cls=CmemcCommand, name="delete")
|
|
107
|
-
@click.argument("resource_ids", nargs=-1, type=click.STRING, shell_complete=completion.resource_ids)
|
|
108
|
-
@click.option("--force", is_flag=True, help="Delete resource even if in use by a task.")
|
|
109
|
-
@click.option(
|
|
110
|
-
"-a",
|
|
111
|
-
"--all",
|
|
112
|
-
"all_",
|
|
113
|
-
is_flag=True,
|
|
114
|
-
help="Delete all resources. " "This is a dangerous option, so use it with care.",
|
|
115
|
-
)
|
|
116
|
-
@click.option(
|
|
117
|
-
"--filter",
|
|
118
|
-
"filters_",
|
|
119
|
-
multiple=True,
|
|
120
|
-
type=(str, str),
|
|
121
|
-
shell_complete=completion.resource_list_filter,
|
|
122
|
-
help=RESOURCE_FILTER_TEXT,
|
|
123
|
-
)
|
|
124
|
-
@click.pass_obj
|
|
125
|
-
def delete_command(
|
|
126
|
-
app: ApplicationContext,
|
|
127
|
-
resource_ids: tuple[str, ...],
|
|
128
|
-
force: bool,
|
|
129
|
-
all_: bool,
|
|
130
|
-
filters_: tuple[tuple[str, str], ...],
|
|
131
|
-
) -> None:
|
|
132
|
-
"""Delete file resources.
|
|
133
|
-
|
|
134
|
-
There are three selection mechanisms: with specific IDs, only those
|
|
135
|
-
specified resources will be deleted; by using --filter, resources based
|
|
136
|
-
on the filter type and value will be deleted; using --all will delete
|
|
137
|
-
all resources.
|
|
138
|
-
"""
|
|
139
|
-
if resource_ids == () and not all_ and filters_ == ():
|
|
140
|
-
raise UsageError(
|
|
141
|
-
"Either specify at least one resource ID or use the --all or "
|
|
142
|
-
"--filter options to specify resources for deletion."
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
resources = get_all_resources()
|
|
146
|
-
if len(resource_ids) > 0:
|
|
147
|
-
for resource_id in resource_ids:
|
|
148
|
-
if resource_id not in [_["id"] for _ in resources]:
|
|
149
|
-
raise ClickException(f"Resource {resource_id} not available.")
|
|
150
|
-
# "filter" by id
|
|
151
|
-
resources = _get_resources_filtered(resources, "ids", resource_ids)
|
|
152
|
-
for _ in filters_:
|
|
153
|
-
resources = _get_resources_filtered(resources, _[0], _[1])
|
|
154
|
-
|
|
155
|
-
# avoid double removal as well as sort IDs
|
|
156
|
-
processed_ids = sorted({_["id"] for _ in resources}, key=lambda v: v.lower())
|
|
157
|
-
count = len(processed_ids)
|
|
158
|
-
for current, resource_id in enumerate(processed_ids, start=1):
|
|
159
|
-
current_string = str(current).zfill(len(str(count)))
|
|
160
|
-
app.echo_info(f"Delete resource {current_string}/{count}: {resource_id} ... ", nl=False)
|
|
161
|
-
project_id, resource_local_id = split_task_id(resource_id)
|
|
162
|
-
usage = get_resource_usage_data(project_id, resource_local_id)
|
|
163
|
-
if len(usage) > 0:
|
|
164
|
-
app.echo_error(f"in use by {len(usage)} task(s)", nl=False)
|
|
165
|
-
if force:
|
|
166
|
-
app.echo_info(" ... ", nl=False)
|
|
167
|
-
else:
|
|
168
|
-
app.echo_info("")
|
|
169
|
-
continue
|
|
170
|
-
delete_resource(project_name=project_id, resource_name=resource_local_id)
|
|
171
|
-
app.echo_success("deleted")
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
@click.command(cls=CmemcCommand, name="inspect")
|
|
175
|
-
@click.argument("resource_id", type=click.STRING, shell_complete=completion.resource_ids)
|
|
176
|
-
@click.option("--raw", is_flag=True, help="Outputs raw JSON.")
|
|
177
|
-
@click.pass_obj
|
|
178
|
-
def inspect_command(app: ApplicationContext, resource_id: str, raw: bool) -> None:
|
|
179
|
-
"""Display all metadata of a file resource."""
|
|
180
|
-
project_id, resource_id = split_task_id(resource_id)
|
|
181
|
-
resource_data = get_resource_metadata(project_id, resource_id)
|
|
182
|
-
if raw:
|
|
183
|
-
app.echo_info_json(resource_data)
|
|
184
|
-
else:
|
|
185
|
-
table = struct_to_table(resource_data)
|
|
186
|
-
app.echo_info_table(table, headers=["Key", "Value"], sort_column=0)
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
@click.command(cls=CmemcCommand, name="usage")
|
|
190
|
-
@click.argument("resource_id", type=click.STRING, shell_complete=completion.resource_ids)
|
|
191
|
-
@click.option("--raw", is_flag=True, help="Outputs raw JSON.")
|
|
192
|
-
@click.pass_obj
|
|
193
|
-
def usage_command(app: ApplicationContext, resource_id: str, raw: bool) -> None:
|
|
194
|
-
"""Display all usage data of a file resource."""
|
|
195
|
-
project_id, resource_id = split_task_id(resource_id)
|
|
196
|
-
usage = get_resource_usage_data(project_id, resource_id)
|
|
197
|
-
if raw:
|
|
198
|
-
app.echo_info_json(usage)
|
|
199
|
-
return
|
|
200
|
-
# output a user table
|
|
201
|
-
table = []
|
|
202
|
-
headers = ["Task ID", "Type", "Label"]
|
|
203
|
-
for _ in usage:
|
|
204
|
-
row = [project_id + ":" + _["id"], _["taskType"], _["label"]]
|
|
205
|
-
table.append(row)
|
|
206
|
-
app.echo_info_table(table, headers=headers, sort_column=2)
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
@click.group(cls=CmemcGroup)
|
|
210
|
-
def resource() -> CmemcGroup: # type: ignore[empty-body]
|
|
211
|
-
"""List, inspect or delete dataset file resources.
|
|
212
|
-
|
|
213
|
-
File resources are identified by their paths and project IDs.
|
|
214
|
-
"""
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
resource.add_command(list_command)
|
|
218
|
-
resource.add_command(delete_command)
|
|
219
|
-
resource.add_command(inspect_command)
|
|
220
|
-
resource.add_command(usage_command)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|