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.
@@ -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
- autocompletion=completion.connections,
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
- autocompletion=completion.ini_files,
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)
@@ -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
- autocompletion=completion.status_keys,
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)
@@ -41,7 +41,7 @@ def edit_command(app):
41
41
  "KEY",
42
42
  nargs=1,
43
43
  type=click.Choice(
44
- KNOWN_CONFIG_KEYS.keys(),
44
+ list(KNOWN_CONFIG_KEYS.keys()),
45
45
  case_sensitive=False
46
46
  )
47
47
  )
@@ -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=None):
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
- autocompletion=completion.dataset_list_filter,
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
- autocompletion=completion.project_ids,
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
- autocompletion=completion.dataset_list_filter,
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
- autocompletion=completion.dataset_ids
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
- autocompletion=completion.dataset_ids
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
- autocompletion=completion.dataset_ids
609
+ shell_complete=completion.dataset_ids
598
610
  )
599
611
  @click.argument(
600
612
  "input_path",
601
613
  required=True,
602
- autocompletion=completion.dataset_files,
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
- _upload_file_resource(
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
- autocompletion=completion.dataset_ids
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
- autocompletion=completion.dataset_files,
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
- autocompletion=completion.dataset_types,
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
- autocompletion=completion.project_ids,
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
- autocompletion=completion.dataset_parameter,
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 = _check_or_select_project(app, 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
- autocompletion=completion.dataset_ids
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)