cmem-cmemc 23.1.3__py3-none-any.whl → 23.3.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/cli/__init__.py +9 -3
- cmem/cmemc/cli/_cmemc.zsh +44 -0
- cmem/cmemc/cli/commands/__init__.py +3 -0
- cmem/cmemc/cli/commands/admin.py +3 -1
- cmem/cmemc/cli/commands/client.py +173 -0
- cmem/cmemc/cli/commands/config.py +1 -1
- cmem/cmemc/cli/commands/dataset.py +161 -70
- cmem/cmemc/cli/commands/graph.py +10 -10
- cmem/cmemc/cli/commands/metrics.py +3 -3
- cmem/cmemc/cli/commands/project.py +90 -27
- cmem/cmemc/cli/commands/python.py +110 -29
- cmem/cmemc/cli/commands/query.py +6 -6
- cmem/cmemc/cli/commands/resource.py +5 -5
- cmem/cmemc/cli/commands/scheduler.py +4 -4
- cmem/cmemc/cli/commands/store.py +34 -31
- cmem/cmemc/cli/commands/user.py +27 -10
- cmem/cmemc/cli/commands/variable.py +364 -0
- cmem/cmemc/cli/commands/vocabulary.py +5 -5
- cmem/cmemc/cli/commands/workflow.py +118 -55
- cmem/cmemc/cli/commands/workspace.py +5 -5
- cmem/cmemc/cli/completion.py +393 -154
- cmem/cmemc/cli/context.py +20 -3
- cmem/cmemc/cli/manual_helper/multi_page.py +11 -6
- cmem/cmemc/cli/utils.py +80 -0
- {cmem_cmemc-23.1.3.dist-info → cmem_cmemc-23.3.0.dist-info}/METADATA +6 -8
- cmem_cmemc-23.3.0.dist-info/RECORD +35 -0
- {cmem_cmemc-23.1.3.dist-info → cmem_cmemc-23.3.0.dist-info}/WHEEL +1 -1
- cmem_cmemc-23.1.3.dist-info/RECORD +0 -32
- {cmem_cmemc-23.1.3.dist-info → cmem_cmemc-23.3.0.dist-info}/LICENSE +0 -0
- {cmem_cmemc-23.1.3.dist-info → cmem_cmemc-23.3.0.dist-info}/entry_points.txt +0 -0
cmem/cmemc/cli/__init__.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""The main command line interface."""
|
|
2
|
-
|
|
2
|
+
from importlib.resources import open_text
|
|
3
3
|
import os
|
|
4
4
|
from subprocess import CalledProcessError # nosec
|
|
5
5
|
import sys
|
|
@@ -38,6 +38,12 @@ from cmem.cmemc.cli.commands import CmemcGroup
|
|
|
38
38
|
|
|
39
39
|
CMEMC_VERSION = get_version()
|
|
40
40
|
|
|
41
|
+
# this will output a custom zsh completion function
|
|
42
|
+
if os.environ.get("_CMEMC_COMPLETE", "") == "zsh_source":
|
|
43
|
+
with open_text("cmem.cmemc.cli", "_cmemc.zsh") as zsh_output:
|
|
44
|
+
print(zsh_output.read())
|
|
45
|
+
sys.exit(0)
|
|
46
|
+
|
|
41
47
|
version = sys.version_info
|
|
42
48
|
PYTHON_VERSION = f"{version.major}.{version.minor}.{version.micro}"
|
|
43
49
|
PYTHON_EXPECTED = "3.11"
|
|
@@ -65,12 +71,12 @@ CONTEXT_SETTINGS = {
|
|
|
65
71
|
@click.option(
|
|
66
72
|
'-c', '--connection',
|
|
67
73
|
type=click.STRING,
|
|
68
|
-
|
|
74
|
+
shell_complete=completion.connections,
|
|
69
75
|
help='Use a specific connection from the config file.'
|
|
70
76
|
)
|
|
71
77
|
@click.option(
|
|
72
78
|
'--config-file',
|
|
73
|
-
|
|
79
|
+
shell_complete=completion.ini_files,
|
|
74
80
|
type=click.Path(
|
|
75
81
|
readable=True,
|
|
76
82
|
allow_dash=False,
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#compdef cmemc
|
|
2
|
+
|
|
3
|
+
_cmemc_completion() {
|
|
4
|
+
local -a completions
|
|
5
|
+
local -a completions_with_descriptions
|
|
6
|
+
local -a response
|
|
7
|
+
(( ! $+commands[cmemc] )) && return 1
|
|
8
|
+
|
|
9
|
+
response=("${(@f)$(env COMP_WORDS="${words[*]}" COMP_CWORD=$((CURRENT-1)) _CMEMC_COMPLETE=zsh_complete cmemc)}")
|
|
10
|
+
|
|
11
|
+
for type key descr in ${response}; do
|
|
12
|
+
if [[ "$type" == "plain" ]]; then
|
|
13
|
+
if [[ "$descr" == "_" ]]; then
|
|
14
|
+
completions+=("$key")
|
|
15
|
+
else
|
|
16
|
+
completions_with_descriptions+=("$key":"$descr")
|
|
17
|
+
fi
|
|
18
|
+
elif [[ "$type" == "dir" ]]; then
|
|
19
|
+
_path_files -/
|
|
20
|
+
elif [[ "$type" == "file" ]]; then
|
|
21
|
+
_path_files -f
|
|
22
|
+
fi
|
|
23
|
+
done
|
|
24
|
+
|
|
25
|
+
if [ -n "$completions_with_descriptions" ]; then
|
|
26
|
+
_describe -V unsorted completions_with_descriptions -U
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
if [ -n "$completions" ]; then
|
|
30
|
+
compadd -U -V unsorted -a completions
|
|
31
|
+
fi
|
|
32
|
+
# Other than the standard click completion function, we re-added this line
|
|
33
|
+
# in order to have working completions for task IDs
|
|
34
|
+
compstate[insert]="automenu"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if [[ $zsh_eval_context[-1] == loadautofunc ]]; then
|
|
38
|
+
# autoload from fpath, call function directly
|
|
39
|
+
_cmemc_completion "$@"
|
|
40
|
+
else
|
|
41
|
+
# eval/source/. command, register function for later
|
|
42
|
+
compdef _cmemc_completion cmemc
|
|
43
|
+
fi
|
|
44
|
+
|
|
@@ -29,6 +29,7 @@ class CmemcGroup(HelpColorsGroup, DYMGroup):
|
|
|
29
29
|
"showcase": COLOR_FOR_WRITING_COMMANDS,
|
|
30
30
|
"delete": COLOR_FOR_WRITING_COMMANDS,
|
|
31
31
|
"password": COLOR_FOR_WRITING_COMMANDS,
|
|
32
|
+
"secret": COLOR_FOR_WRITING_COMMANDS,
|
|
32
33
|
"upload": COLOR_FOR_WRITING_COMMANDS,
|
|
33
34
|
"import": COLOR_FOR_WRITING_COMMANDS,
|
|
34
35
|
"create": COLOR_FOR_WRITING_COMMANDS,
|
|
@@ -59,6 +60,8 @@ class CmemcGroup(HelpColorsGroup, DYMGroup):
|
|
|
59
60
|
"python": COLOR_FOR_COMMAND_GROUPS,
|
|
60
61
|
"cache": COLOR_FOR_COMMAND_GROUPS,
|
|
61
62
|
"resource": COLOR_FOR_COMMAND_GROUPS,
|
|
63
|
+
"client": COLOR_FOR_COMMAND_GROUPS,
|
|
64
|
+
"variable": COLOR_FOR_COMMAND_GROUPS,
|
|
62
65
|
}
|
|
63
66
|
)
|
|
64
67
|
super().__init__(*args, **kwargs)
|
cmem/cmemc/cli/commands/admin.py
CHANGED
|
@@ -4,6 +4,7 @@ import click
|
|
|
4
4
|
import jwt
|
|
5
5
|
|
|
6
6
|
from cmem.cmemc.cli import completion
|
|
7
|
+
from cmem.cmemc.cli.commands.client import client
|
|
7
8
|
from cmem.cmemc.cli.commands.user import user
|
|
8
9
|
from cmem.cmemc.cli.context import ApplicationContext
|
|
9
10
|
from cmem.cmemc.cli.utils import struct_to_table
|
|
@@ -20,7 +21,7 @@ from cmem.cmempy.health import get_complete_status_info
|
|
|
20
21
|
@click.command(cls=CmemcCommand, name="status")
|
|
21
22
|
@click.option(
|
|
22
23
|
"--key", "key",
|
|
23
|
-
|
|
24
|
+
shell_complete=completion.status_keys,
|
|
24
25
|
help="Get only specific key(s) from the status / info output. There are "
|
|
25
26
|
"two special keys available: 'all' will list all available keys in "
|
|
26
27
|
"the table, 'overall.healthy' with result in UP in case all "
|
|
@@ -192,3 +193,4 @@ admin.add_command(metrics)
|
|
|
192
193
|
admin.add_command(workspace)
|
|
193
194
|
admin.add_command(store)
|
|
194
195
|
admin.add_command(user)
|
|
196
|
+
admin.add_command(client)
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""Keycloak client management commands"""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from cmem.cmemc.cli import completion
|
|
6
|
+
from cmem.cmemc.cli.commands import CmemcCommand, CmemcGroup
|
|
7
|
+
from cmem.cmemc.cli.context import ApplicationContext
|
|
8
|
+
from cmem.cmempy.config import get_keycloak_base_uri, get_keycloak_realm_id
|
|
9
|
+
from cmem.cmempy.keycloak.client import (
|
|
10
|
+
get_client_by_client_id,
|
|
11
|
+
generate_client_secret,
|
|
12
|
+
get_client_secret, list_open_id_clients
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
NO_CLIENT_ERROR = "{} is not a valid client account. " \
|
|
16
|
+
"Use the 'admin client list' command " \
|
|
17
|
+
"to get a list of existing client accounts."
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@click.command(cls=CmemcCommand, name="list")
|
|
21
|
+
@click.option(
|
|
22
|
+
"--raw",
|
|
23
|
+
is_flag=True,
|
|
24
|
+
help="Outputs raw JSON."
|
|
25
|
+
)
|
|
26
|
+
@click.option(
|
|
27
|
+
"--id-only",
|
|
28
|
+
is_flag=True,
|
|
29
|
+
help="Lists only Client ID. "
|
|
30
|
+
"This is useful for piping the IDs into other commands."
|
|
31
|
+
)
|
|
32
|
+
@click.pass_obj
|
|
33
|
+
def list_command(app: ApplicationContext, raw, id_only):
|
|
34
|
+
"""
|
|
35
|
+
List client accounts.
|
|
36
|
+
|
|
37
|
+
Outputs a list of client accounts, which can be used to get an overview as well
|
|
38
|
+
as a reference for the other commands of the `admin client` command group.
|
|
39
|
+
|
|
40
|
+
Note: The list command only outputs clients which have a client secret.
|
|
41
|
+
Use the `--raw` option to get a JSON description of all clients.
|
|
42
|
+
"""
|
|
43
|
+
clients = list_open_id_clients()
|
|
44
|
+
if raw:
|
|
45
|
+
app.echo_info_json(clients)
|
|
46
|
+
return
|
|
47
|
+
if id_only:
|
|
48
|
+
for cnt in clients:
|
|
49
|
+
app.echo_info(cnt["clientId"])
|
|
50
|
+
return
|
|
51
|
+
table = []
|
|
52
|
+
for cnt in clients:
|
|
53
|
+
table.append((
|
|
54
|
+
cnt["clientId"],
|
|
55
|
+
cnt.get("description", "-")
|
|
56
|
+
))
|
|
57
|
+
app.echo_info_table(
|
|
58
|
+
table,
|
|
59
|
+
headers=["Client ID", "Description"],
|
|
60
|
+
sort_column=0
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@click.command(cls=CmemcCommand, name="secret")
|
|
65
|
+
@click.argument(
|
|
66
|
+
"client-id",
|
|
67
|
+
shell_complete=completion.client_ids
|
|
68
|
+
)
|
|
69
|
+
@click.option(
|
|
70
|
+
"--generate",
|
|
71
|
+
is_flag=True,
|
|
72
|
+
help="Generate a new secret"
|
|
73
|
+
)
|
|
74
|
+
@click.option(
|
|
75
|
+
"--output",
|
|
76
|
+
is_flag=True,
|
|
77
|
+
help="Display client secret"
|
|
78
|
+
)
|
|
79
|
+
@click.pass_obj
|
|
80
|
+
def secret_command(app: ApplicationContext, client_id, generate, output):
|
|
81
|
+
"""
|
|
82
|
+
Get or generate a new secret for a client account.
|
|
83
|
+
|
|
84
|
+
This command retrieves or generates a new secret for a client account from a realm.
|
|
85
|
+
"""
|
|
86
|
+
if not output and not generate:
|
|
87
|
+
app.echo_info(click.get_current_context().get_help())
|
|
88
|
+
raise ValueError(
|
|
89
|
+
"You need to use '--output' or '--generate' as an option."
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
clients = get_client_by_client_id(client_id)
|
|
93
|
+
if not clients:
|
|
94
|
+
raise ValueError(NO_CLIENT_ERROR.format(client_id))
|
|
95
|
+
|
|
96
|
+
if generate:
|
|
97
|
+
if not output:
|
|
98
|
+
app.echo_info(
|
|
99
|
+
f"Generating a new secret for {client_id} ... ",
|
|
100
|
+
nl=False
|
|
101
|
+
)
|
|
102
|
+
credential = generate_client_secret(client_id=clients[0]["id"])
|
|
103
|
+
if not output:
|
|
104
|
+
app.echo_success("done")
|
|
105
|
+
else:
|
|
106
|
+
credential = get_client_secret(client_id=clients[0]["id"])
|
|
107
|
+
|
|
108
|
+
if output:
|
|
109
|
+
app.echo_result(credential["value"])
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@click.command(cls=CmemcCommand, name="open")
|
|
113
|
+
@click.argument(
|
|
114
|
+
"client-ids",
|
|
115
|
+
nargs=-1,
|
|
116
|
+
required=False,
|
|
117
|
+
type=click.STRING,
|
|
118
|
+
shell_complete=completion.client_ids
|
|
119
|
+
)
|
|
120
|
+
@click.pass_obj
|
|
121
|
+
def open_command(app: ApplicationContext, client_ids):
|
|
122
|
+
"""Open clients in the browser.
|
|
123
|
+
|
|
124
|
+
With this command, you can open a client in the keycloak web
|
|
125
|
+
interface in your browser.
|
|
126
|
+
|
|
127
|
+
The command accepts multiple client IDs which results in
|
|
128
|
+
opening multiple browser tabs.
|
|
129
|
+
"""
|
|
130
|
+
open_client_base_uri = (
|
|
131
|
+
f"{get_keycloak_base_uri()}/admin/master/console/#/"
|
|
132
|
+
f"{get_keycloak_realm_id()}/clients"
|
|
133
|
+
)
|
|
134
|
+
if not client_ids:
|
|
135
|
+
app.echo_debug(f"Open users list: {open_client_base_uri}")
|
|
136
|
+
click.launch(open_client_base_uri)
|
|
137
|
+
else:
|
|
138
|
+
clients = list_open_id_clients()
|
|
139
|
+
client_id_map = {c["clientId"]: c["id"] for c in clients}
|
|
140
|
+
for _ in client_ids:
|
|
141
|
+
if _ not in client_id_map.keys():
|
|
142
|
+
raise ValueError(NO_CLIENT_ERROR.format(_))
|
|
143
|
+
client_id = client_id_map[_]
|
|
144
|
+
open_user_uri = f"{open_client_base_uri}/{client_id}/settings"
|
|
145
|
+
|
|
146
|
+
app.echo_debug(f"Open {_}: {open_user_uri}")
|
|
147
|
+
click.launch(open_user_uri)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@click.group(cls=CmemcGroup)
|
|
151
|
+
def client():
|
|
152
|
+
"""
|
|
153
|
+
List client accounts, get or generate client account secrets.
|
|
154
|
+
|
|
155
|
+
This command group is an opinionated interface to the Keycloak realm of your
|
|
156
|
+
Corporate Memory instance. In order to be able to use the commands in this group,
|
|
157
|
+
the configured cmemc connection account needs to be equipped with the
|
|
158
|
+
`manage-clients` role in the used realm.
|
|
159
|
+
|
|
160
|
+
Client accounts are identified by a client ID which is unique in the scope of
|
|
161
|
+
the used realm.
|
|
162
|
+
|
|
163
|
+
In case your Corporate Memory deployment does not use the default deployment
|
|
164
|
+
layout, the following additional config variables can be used in your
|
|
165
|
+
connection configuration: `KEYCLOAK_BASE_URI` defaults to
|
|
166
|
+
`{CMEM_BASE_URI}/auth` and locates your Keycloak deployment;
|
|
167
|
+
`KEYCLOAK_REALM_ID` defaults to `cmem` and identifies the used realm.
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
client.add_command(list_command)
|
|
172
|
+
client.add_command(secret_command)
|
|
173
|
+
client.add_command(open_command)
|
|
@@ -6,21 +6,21 @@ import re
|
|
|
6
6
|
import click
|
|
7
7
|
|
|
8
8
|
import requests.exceptions
|
|
9
|
+
from click import UsageError
|
|
9
10
|
|
|
10
11
|
from cmem.cmemc.cli import completion
|
|
11
12
|
from cmem.cmemc.cli.commands import CmemcCommand, CmemcGroup
|
|
12
13
|
from cmem.cmemc.cli.commands.resource import resource
|
|
13
14
|
from cmem.cmemc.cli.context import ApplicationContext
|
|
14
|
-
from cmem.cmemc.cli.utils import struct_to_table
|
|
15
|
+
from cmem.cmemc.cli.utils import struct_to_table, check_or_select_project
|
|
15
16
|
from cmem.cmempy.config import get_cmem_base_uri
|
|
16
17
|
from cmem.cmempy.workspace.search import list_items
|
|
17
|
-
from cmem.cmempy.workspace.projects.project import (
|
|
18
|
-
get_projects
|
|
19
|
-
)
|
|
20
18
|
from cmem.cmempy.workspace.projects.datasets.dataset import (
|
|
21
19
|
create_dataset,
|
|
22
20
|
delete_dataset,
|
|
23
|
-
get_dataset
|
|
21
|
+
get_dataset,
|
|
22
|
+
post_resource,
|
|
23
|
+
update_dataset
|
|
24
24
|
)
|
|
25
25
|
from cmem.cmempy.workspace.projects.resources.resource import (
|
|
26
26
|
create_resource,
|
|
@@ -118,6 +118,37 @@ def _validate_and_split_dataset_id(dataset_id):
|
|
|
118
118
|
return project_part, dataset_part
|
|
119
119
|
|
|
120
120
|
|
|
121
|
+
def _post_file_resource(
|
|
122
|
+
app=None,
|
|
123
|
+
project_id=None,
|
|
124
|
+
dataset_id=None,
|
|
125
|
+
local_file_name=None
|
|
126
|
+
|
|
127
|
+
):
|
|
128
|
+
"""Upload a local file as a dataset resource to a project.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
app (ApplicationContext): the click cli app context.
|
|
132
|
+
project_id (str): The project ID in the workspace.
|
|
133
|
+
dataset_id (str): The dataset ID in the workspace.
|
|
134
|
+
local_file_name (str): The path to the local file name
|
|
135
|
+
|
|
136
|
+
Raises:
|
|
137
|
+
ValueError: if resource exists and no replace
|
|
138
|
+
"""
|
|
139
|
+
app.echo_info(
|
|
140
|
+
f"Upload {local_file_name} as a file resource of dataset "
|
|
141
|
+
f"{dataset_id} to project {project_id} ... ",
|
|
142
|
+
nl=False
|
|
143
|
+
)
|
|
144
|
+
post_resource(
|
|
145
|
+
project_id=project_id,
|
|
146
|
+
dataset_id=dataset_id,
|
|
147
|
+
file_resource=click.open_file(local_file_name, "rb"),
|
|
148
|
+
)
|
|
149
|
+
app.echo_success("done")
|
|
150
|
+
|
|
151
|
+
|
|
121
152
|
def _upload_file_resource(
|
|
122
153
|
app=None,
|
|
123
154
|
project_id=None,
|
|
@@ -185,6 +216,23 @@ def _get_metadata_out_of_parameter(parameter_dict):
|
|
|
185
216
|
return metadata_dict
|
|
186
217
|
|
|
187
218
|
|
|
219
|
+
def _get_read_only_out_of_parameter(parameter_dict):
|
|
220
|
+
"""Extract readonly key value out of the parameter dict.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
parameter_dict (dict): the dictionary of given parameters.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
The value of read only field.
|
|
227
|
+
"""
|
|
228
|
+
read_only = parameter_dict.get("readOnly", False)
|
|
229
|
+
if read_only == "true":
|
|
230
|
+
return True
|
|
231
|
+
if read_only == "false":
|
|
232
|
+
return False
|
|
233
|
+
return read_only
|
|
234
|
+
|
|
235
|
+
|
|
188
236
|
def _extend_parameter_with_metadata(
|
|
189
237
|
app,
|
|
190
238
|
parameter_dict=None,
|
|
@@ -263,7 +311,7 @@ def _check_or_set_dataset_type(
|
|
|
263
311
|
return dataset_type
|
|
264
312
|
|
|
265
313
|
|
|
266
|
-
def _show_parameter_list(app, dataset_type
|
|
314
|
+
def _show_parameter_list(app, dataset_type):
|
|
267
315
|
"""Output the parameter list for a given dataset type.
|
|
268
316
|
|
|
269
317
|
Args:
|
|
@@ -286,6 +334,9 @@ def _show_parameter_list(app, dataset_type=None):
|
|
|
286
334
|
description,
|
|
287
335
|
]
|
|
288
336
|
table.append(row)
|
|
337
|
+
|
|
338
|
+
table = completion.add_read_only_and_uri_property_parameters(table)
|
|
339
|
+
|
|
289
340
|
# metadata always on top, then sorted by key
|
|
290
341
|
table = sorted(table, key=lambda k: k[0].lower())
|
|
291
342
|
table = completion.add_metadata_parameter(table)
|
|
@@ -321,45 +372,6 @@ def _show_type_list(app):
|
|
|
321
372
|
)
|
|
322
373
|
|
|
323
374
|
|
|
324
|
-
def _check_or_select_project(app, project_id=None):
|
|
325
|
-
"""Check for given project, select the first one if there is only one.
|
|
326
|
-
|
|
327
|
-
Args:
|
|
328
|
-
app (ApplicationContext): the click cli app context.
|
|
329
|
-
project_id (str): The project ID.
|
|
330
|
-
|
|
331
|
-
Raises:
|
|
332
|
-
ValueError: if no projects available.
|
|
333
|
-
ValueError: if more than one project is.
|
|
334
|
-
|
|
335
|
-
Returns:
|
|
336
|
-
Maybe project_id if there was no project_id before.
|
|
337
|
-
"""
|
|
338
|
-
if project_id is not None:
|
|
339
|
-
return project_id
|
|
340
|
-
|
|
341
|
-
projects = get_projects()
|
|
342
|
-
if len(projects) == 1:
|
|
343
|
-
project_name = projects[0]["name"]
|
|
344
|
-
app.echo_warning(
|
|
345
|
-
"Missing project (--project) - since there is only one project, "
|
|
346
|
-
f"this is selected: {project_name}"
|
|
347
|
-
)
|
|
348
|
-
return project_name
|
|
349
|
-
|
|
350
|
-
if len(projects) == 0:
|
|
351
|
-
raise ValueError(
|
|
352
|
-
"There are no projects available. "
|
|
353
|
-
"Please create a project with 'cmemc project create'."
|
|
354
|
-
)
|
|
355
|
-
|
|
356
|
-
# more than one project
|
|
357
|
-
raise ValueError(
|
|
358
|
-
"There is more than one project available so you need to "
|
|
359
|
-
"specify the project with '--project'."
|
|
360
|
-
)
|
|
361
|
-
|
|
362
|
-
|
|
363
375
|
def _check_or_select_dataset_type(app, dataset_type):
|
|
364
376
|
"""Test type and return plugin.
|
|
365
377
|
|
|
@@ -388,7 +400,7 @@ def _check_or_select_dataset_type(app, dataset_type):
|
|
|
388
400
|
"--filter", "filter_",
|
|
389
401
|
type=(str, str),
|
|
390
402
|
multiple=True,
|
|
391
|
-
|
|
403
|
+
shell_complete=completion.dataset_list_filter,
|
|
392
404
|
help=DATASET_LIST_FILTER_HELP_TEXT,
|
|
393
405
|
)
|
|
394
406
|
@click.option(
|
|
@@ -445,7 +457,7 @@ def list_command(app: ApplicationContext, filter_, raw, id_only):
|
|
|
445
457
|
@click.option(
|
|
446
458
|
"--project", "project_id",
|
|
447
459
|
type=click.STRING,
|
|
448
|
-
|
|
460
|
+
shell_complete=completion.project_ids,
|
|
449
461
|
help="In combination with the '--all' flag, this option allows for "
|
|
450
462
|
"deletion of all datasets of a certain project. The behaviour is "
|
|
451
463
|
"similar to the 'dataset list --project' command."
|
|
@@ -454,14 +466,14 @@ def list_command(app: ApplicationContext, filter_, raw, id_only):
|
|
|
454
466
|
"--filter", "filter_",
|
|
455
467
|
type=(str, str),
|
|
456
468
|
multiple=True,
|
|
457
|
-
|
|
469
|
+
shell_complete=completion.dataset_list_filter,
|
|
458
470
|
help=DATASET_DELETE_FILTER_HELP_TEXT,
|
|
459
471
|
)
|
|
460
472
|
@click.argument(
|
|
461
473
|
"dataset_ids",
|
|
462
474
|
nargs=-1,
|
|
463
475
|
type=click.STRING,
|
|
464
|
-
|
|
476
|
+
shell_complete=completion.dataset_ids
|
|
465
477
|
)
|
|
466
478
|
@click.pass_obj
|
|
467
479
|
def delete_command(app, project_id, all_, filter_, dataset_ids):
|
|
@@ -525,7 +537,7 @@ def delete_command(app, project_id, all_, filter_, dataset_ids):
|
|
|
525
537
|
@click.argument(
|
|
526
538
|
"dataset_id",
|
|
527
539
|
type=click.STRING,
|
|
528
|
-
|
|
540
|
+
shell_complete=completion.dataset_ids
|
|
529
541
|
)
|
|
530
542
|
@click.argument(
|
|
531
543
|
"output_path",
|
|
@@ -594,12 +606,12 @@ def download_command(app, dataset_id, output_path, replace):
|
|
|
594
606
|
@click.argument(
|
|
595
607
|
"dataset_id",
|
|
596
608
|
type=click.STRING,
|
|
597
|
-
|
|
609
|
+
shell_complete=completion.dataset_ids
|
|
598
610
|
)
|
|
599
611
|
@click.argument(
|
|
600
612
|
"input_path",
|
|
601
613
|
required=True,
|
|
602
|
-
|
|
614
|
+
shell_complete=completion.dataset_files,
|
|
603
615
|
type=click.Path(
|
|
604
616
|
allow_dash=True,
|
|
605
617
|
dir_okay=False,
|
|
@@ -625,20 +637,12 @@ def upload_command(app, dataset_id, input_path):
|
|
|
625
637
|
Example: cmemc dataset upload cmem:my-dataset new-file.csv
|
|
626
638
|
"""
|
|
627
639
|
project_part, dataset_part = _validate_and_split_dataset_id(dataset_id)
|
|
628
|
-
project = get_dataset(project_part, dataset_part)
|
|
629
|
-
try:
|
|
630
|
-
remote_file_name = project["data"]["parameters"]["file"]
|
|
631
|
-
except KeyError as error:
|
|
632
|
-
raise ValueError(
|
|
633
|
-
f"Dataset {dataset_id} has no attached file resource to replace."
|
|
634
|
-
) from error
|
|
635
640
|
|
|
636
|
-
|
|
641
|
+
_post_file_resource(
|
|
637
642
|
app=app,
|
|
638
643
|
project_id=project_part,
|
|
644
|
+
dataset_id=dataset_part,
|
|
639
645
|
local_file_name=input_path,
|
|
640
|
-
remote_file_name=remote_file_name,
|
|
641
|
-
replace=True
|
|
642
646
|
)
|
|
643
647
|
|
|
644
648
|
|
|
@@ -646,7 +650,7 @@ def upload_command(app, dataset_id, input_path):
|
|
|
646
650
|
@click.argument(
|
|
647
651
|
"dataset_id",
|
|
648
652
|
type=click.STRING,
|
|
649
|
-
|
|
653
|
+
shell_complete=completion.dataset_ids
|
|
650
654
|
)
|
|
651
655
|
@click.option(
|
|
652
656
|
"--raw",
|
|
@@ -678,7 +682,7 @@ def inspect_command(app, dataset_id, raw):
|
|
|
678
682
|
@click.argument(
|
|
679
683
|
"DATASET_FILE",
|
|
680
684
|
required=False,
|
|
681
|
-
|
|
685
|
+
shell_complete=completion.dataset_files,
|
|
682
686
|
type=click.Path(
|
|
683
687
|
allow_dash=False,
|
|
684
688
|
readable=True,
|
|
@@ -690,21 +694,21 @@ def inspect_command(app, dataset_id, raw):
|
|
|
690
694
|
"--type", "-t", "dataset_type",
|
|
691
695
|
multiple=False,
|
|
692
696
|
type=click.STRING,
|
|
693
|
-
|
|
697
|
+
shell_complete=completion.dataset_types,
|
|
694
698
|
help="The dataset type of the dataset to create. Example types are 'csv',"
|
|
695
699
|
"'json' and 'eccencaDataPlatform' (-> Knowledge Graph)."
|
|
696
700
|
)
|
|
697
701
|
@click.option(
|
|
698
702
|
"--project", "project_id",
|
|
699
703
|
type=click.STRING,
|
|
700
|
-
|
|
704
|
+
shell_complete=completion.project_ids,
|
|
701
705
|
help="The project, where you want to create the dataset in. If there is "
|
|
702
706
|
"only one project in the workspace, this option can be omitted."
|
|
703
707
|
)
|
|
704
708
|
@click.option(
|
|
705
709
|
"--parameter", "-p",
|
|
706
710
|
type=(str, str),
|
|
707
|
-
|
|
711
|
+
shell_complete=completion.dataset_parameter,
|
|
708
712
|
multiple=True,
|
|
709
713
|
help="A set of key/value pairs. Each dataset type has different "
|
|
710
714
|
"parameters (such as charset, arraySeparator, ignoreBadLines, ...). "
|
|
@@ -782,7 +786,7 @@ def create_command(
|
|
|
782
786
|
dataset_file=dataset_file
|
|
783
787
|
)
|
|
784
788
|
|
|
785
|
-
project_id =
|
|
789
|
+
project_id = check_or_select_project(app, project_id)
|
|
786
790
|
|
|
787
791
|
# file required but not given
|
|
788
792
|
if "file" in plugin["required"] \
|
|
@@ -816,20 +820,106 @@ def create_command(
|
|
|
816
820
|
created_dataset = create_dataset(
|
|
817
821
|
dataset_id=dataset_id, project_id=project_id,
|
|
818
822
|
dataset_type=dataset_type, parameter=parameter_dict,
|
|
819
|
-
metadata=_get_metadata_out_of_parameter(parameter_dict)
|
|
823
|
+
metadata=_get_metadata_out_of_parameter(parameter_dict),
|
|
824
|
+
read_only=_get_read_only_out_of_parameter(parameter_dict),
|
|
825
|
+
uri_property=parameter_dict.get("uriProperty", "")
|
|
820
826
|
)
|
|
821
827
|
returned_id = json.loads(created_dataset)["id"]
|
|
822
828
|
app.echo_info(f"{returned_id} ... ", nl=False)
|
|
823
829
|
app.echo_success("done")
|
|
824
830
|
|
|
825
831
|
|
|
832
|
+
@click.command(cls=CmemcCommand, name="update")
|
|
833
|
+
@click.argument(
|
|
834
|
+
"dataset_id",
|
|
835
|
+
type=click.STRING,
|
|
836
|
+
required=True,
|
|
837
|
+
shell_complete=completion.dataset_ids,
|
|
838
|
+
)
|
|
839
|
+
@click.option(
|
|
840
|
+
"--parameter", "-p",
|
|
841
|
+
type=(str, str),
|
|
842
|
+
shell_complete=completion.dataset_parameter,
|
|
843
|
+
multiple=True,
|
|
844
|
+
help="A configuration parameter key/value pair. Each dataset type has different "
|
|
845
|
+
"parameters (such as charset, arraySeparator, ignoreBadLines, ...). "
|
|
846
|
+
"In order to get a list of possible parameter, use the"
|
|
847
|
+
"'--help-parameter' option."
|
|
848
|
+
)
|
|
849
|
+
@click.option(
|
|
850
|
+
"--help-parameter",
|
|
851
|
+
is_flag=True,
|
|
852
|
+
help="Lists all possible (optional and mandatory) configuration parameter for"
|
|
853
|
+
" a given dataset. Note that this option already needs access to the instance."
|
|
854
|
+
)
|
|
855
|
+
@click.pass_obj
|
|
856
|
+
def update_command(
|
|
857
|
+
app, dataset_id, parameter,
|
|
858
|
+
help_parameter
|
|
859
|
+
):
|
|
860
|
+
"""Update a dataset.
|
|
861
|
+
|
|
862
|
+
With this command, you can update the configuration of an existing dataset.
|
|
863
|
+
Similar to the `dataset create` command, you need to use configuration key/value
|
|
864
|
+
pairs on the `--parameter` option.
|
|
865
|
+
|
|
866
|
+
To get more information about the available configuration parameters on a dataset,
|
|
867
|
+
use the `--help-parameter` option.
|
|
868
|
+
|
|
869
|
+
Example: cmemc dataset update my-project:my-csv -p separator ";"
|
|
870
|
+
"""
|
|
871
|
+
project_part, dataset_part = _validate_and_split_dataset_id(dataset_id)
|
|
872
|
+
try:
|
|
873
|
+
project = get_dataset(project_part, dataset_part)
|
|
874
|
+
except requests.exceptions.HTTPError as http_exception:
|
|
875
|
+
if http_exception.response.status_code == 404:
|
|
876
|
+
raise UsageError(
|
|
877
|
+
f"Dataset {dataset_part} does not exist in project {project_part}."
|
|
878
|
+
) from http_exception
|
|
879
|
+
dataset_type = project["data"]["type"]
|
|
880
|
+
|
|
881
|
+
if help_parameter:
|
|
882
|
+
_show_parameter_list(app, dataset_type=dataset_type)
|
|
883
|
+
return
|
|
884
|
+
|
|
885
|
+
if not parameter:
|
|
886
|
+
raise UsageError("You need to use the `--parameter/-p` option at least once,"
|
|
887
|
+
" in order to execute this command.")
|
|
888
|
+
|
|
889
|
+
desc = get_task_plugin_description(dataset_type)
|
|
890
|
+
possible_keys = ["label", "description", "readOnly"]
|
|
891
|
+
possible_keys.extend(desc["properties"])
|
|
892
|
+
possible_keys.extend(desc["required"])
|
|
893
|
+
|
|
894
|
+
# transform the parameter list of tuple to a dictionary
|
|
895
|
+
parameter_dict = {}
|
|
896
|
+
for key, value in parameter:
|
|
897
|
+
if key not in possible_keys:
|
|
898
|
+
raise UsageError(
|
|
899
|
+
f"Configuration key '{key}' is not valid for"
|
|
900
|
+
f" the dataset type '{dataset_type}'."
|
|
901
|
+
)
|
|
902
|
+
parameter_dict[key] = value
|
|
903
|
+
|
|
904
|
+
app.echo_info(f"Updating dataset {dataset_id} ... ", nl=False)
|
|
905
|
+
update_dataset(
|
|
906
|
+
dataset_id=dataset_part,
|
|
907
|
+
project_id=project_part,
|
|
908
|
+
parameters=parameter_dict,
|
|
909
|
+
metadata=_get_metadata_out_of_parameter(parameter_dict),
|
|
910
|
+
read_only=_get_read_only_out_of_parameter(parameter_dict),
|
|
911
|
+
uri_property=parameter_dict.get("uriProperty", "")
|
|
912
|
+
)
|
|
913
|
+
app.echo_success("done")
|
|
914
|
+
|
|
915
|
+
|
|
826
916
|
@click.command(cls=CmemcCommand, name="open")
|
|
827
917
|
@click.argument(
|
|
828
918
|
"dataset_ids",
|
|
829
919
|
nargs=-1,
|
|
830
920
|
required=True,
|
|
831
921
|
type=click.STRING,
|
|
832
|
-
|
|
922
|
+
shell_complete=completion.dataset_ids
|
|
833
923
|
)
|
|
834
924
|
@click.pass_obj
|
|
835
925
|
def open_command(app, dataset_ids):
|
|
@@ -879,4 +969,5 @@ dataset.add_command(upload_command)
|
|
|
879
969
|
dataset.add_command(inspect_command)
|
|
880
970
|
dataset.add_command(create_command)
|
|
881
971
|
dataset.add_command(open_command)
|
|
972
|
+
dataset.add_command(update_command)
|
|
882
973
|
dataset.add_command(resource)
|