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,14 +1,18 @@
1
1
  """DataPlatform store commands for the cmem command line interface."""
2
2
  import os
3
- import sys
4
3
  from os.path import exists
5
4
 
6
5
  import click
6
+ from click import UsageError
7
7
 
8
8
  from cmem.cmemc.cli import completion
9
9
  from cmem.cmemc.cli.commands import CmemcCommand, CmemcGroup
10
10
  from cmem.cmemc.cli.context import ApplicationContext
11
- from cmem.cmempy.dp.admin import create_showcase_data, import_bootstrap_data
11
+ from cmem.cmempy.dp.admin import (
12
+ create_showcase_data,
13
+ delete_bootstrap_data,
14
+ import_bootstrap_data
15
+ )
12
16
  from cmem.cmempy.dp.admin.backup import get_zip, post_zip
13
17
 
14
18
 
@@ -17,32 +21,37 @@ from cmem.cmempy.dp.admin.backup import get_zip, post_zip
17
21
  "--import", "import_",
18
22
  is_flag=True,
19
23
  help="Delete existing bootstrap data if present and import bootstrap "
20
- "data which was delivered "
24
+ "data which was delivered with Corporate Memory."
25
+ )
26
+ @click.option(
27
+ "--remove",
28
+ is_flag=True,
29
+ help="Delete existing bootstrap data if present."
21
30
  )
22
31
  @click.pass_obj
23
- def bootstrap_command(app: ApplicationContext, import_):
24
- """Update/Import bootstrap data.
32
+ def bootstrap_command(app: ApplicationContext, import_, remove):
33
+ """Update/Import or remove bootstrap data.
34
+
35
+ Use `--import` to import the bootstrap data needed for managing shapes and
36
+ configuration objects. This will remove the old data first.
25
37
 
26
- This command imports the bootstrap data needed for managing shapes and
27
- configuration objects.
38
+ Use `--remove` to delete bootstrap data.
28
39
 
29
- Note: The command will first remove all existing bootstrap data
30
- (identified with the isSystemResource flag) and will then import the new data
31
- to the corresponding graphs (shape catalog, vocabulary catalog, configuration
32
- graph).
40
+ Note: The removal of existing bootstrap data will search for resources which are
41
+ flagged with the isSystemResource property.
33
42
  """
34
- if "store" not in sys.argv:
35
- app.echo_warning(
36
- "The 'admin bootstrap' command is deprecated and will be removed "
37
- "in the next major release.\n"
38
- "Please use the 'admin store bootstrap' command instead."
39
- )
40
- if not import_:
41
- raise ValueError("Use the --import flag to update/import "
42
- "bootstrap data.")
43
- app.echo_info("Update or import bootstrap data ... ", nl=False)
44
- import_bootstrap_data()
45
- app.echo_success("done")
43
+ if import_ and remove or not import_ and not remove:
44
+ raise UsageError("Either use the --import or the --remove option.")
45
+ if import_:
46
+ app.echo_info("Update or import bootstrap data ... ", nl=False)
47
+ import_bootstrap_data()
48
+ app.echo_success("done")
49
+ return
50
+ if remove:
51
+ app.echo_info("Remove bootstrap data ... ", nl=False)
52
+ delete_bootstrap_data()
53
+ app.echo_success("done")
54
+ return
46
55
 
47
56
 
48
57
  @click.command(cls=CmemcCommand, name="showcase")
@@ -77,12 +86,6 @@ def showcase_command(app, scale, create, delete):
77
86
  you need to remove the showcase graphs manually (or just remove all
78
87
  graphs).
79
88
  """
80
- if "store" not in sys.argv:
81
- app.echo_warning(
82
- "The 'admin showcase' command is deprecated and will be removed "
83
- "in the next major release.\n"
84
- "Please use the 'admin store showcase' command instead."
85
- )
86
89
  if not delete and not create:
87
90
  raise ValueError("Either use the --create or the --delete flag.")
88
91
  if delete:
@@ -102,7 +105,7 @@ def showcase_command(app, scale, create, delete):
102
105
  @click.command(cls=CmemcCommand, name="export")
103
106
  @click.argument(
104
107
  "BACKUP_FILE",
105
- autocompletion=completion.graph_backup_files,
108
+ shell_complete=completion.graph_backup_files,
106
109
  required=True,
107
110
  type=click.Path(
108
111
  writable=True,
@@ -161,7 +164,7 @@ def export_command(app, backup_file, overwrite):
161
164
  @click.command(cls=CmemcCommand, name="import")
162
165
  @click.argument(
163
166
  "BACKUP_FILE",
164
- autocompletion=completion.graph_backup_files,
167
+ shell_complete=completion.graph_backup_files,
165
168
  required=True,
166
169
  type=click.Path(
167
170
  readable=True,
@@ -17,7 +17,7 @@ from cmem.cmempy.keycloak.user import (
17
17
  create_user,
18
18
  update_user,
19
19
  assign_groups,
20
- unassign_groups, user_groups
20
+ unassign_groups, user_groups, request_password_change
21
21
  )
22
22
 
23
23
  NO_USER_ERROR = "{} is not a valid user account. Use the 'admin user list' command " \
@@ -27,6 +27,7 @@ NO_GROUP_ERROR = "{} is not a valid group. Valid groups are {}"
27
27
  INVALID_UNASSIGN_GROUP_ERROR = (
28
28
  "Group {} is not assigned to user. Valid groups are {}"
29
29
  )
30
+ NO_EMAIL_ERROR = "Email is empty for {} user."
30
31
 
31
32
 
32
33
  @click.command(cls=CmemcCommand, name="list")
@@ -75,7 +76,7 @@ def list_command(app: ApplicationContext, raw, id_only):
75
76
  @click.command(cls=CmemcCommand, name="delete")
76
77
  @click.argument(
77
78
  "username",
78
- autocompletion=completion.user_ids
79
+ shell_complete=completion.user_ids
79
80
  )
80
81
  @click.pass_obj
81
82
  def delete_command(app: ApplicationContext, username):
@@ -129,7 +130,7 @@ def create_command(app: ApplicationContext, username):
129
130
  @click.command(cls=CmemcCommand, name="update")
130
131
  @click.argument(
131
132
  "username",
132
- autocompletion=completion.user_ids
133
+ shell_complete=completion.user_ids
133
134
  )
134
135
  @click.option(
135
136
  "--first-name",
@@ -153,14 +154,14 @@ def create_command(app: ApplicationContext, username):
153
154
  "--assign-group",
154
155
  type=click.STRING,
155
156
  multiple=True,
156
- autocompletion=completion.user_group_ids,
157
+ shell_complete=completion.user_group_ids,
157
158
  help="Assign a group."
158
159
  )
159
160
  @click.option(
160
161
  "--unassign-group",
161
162
  type=click.STRING,
162
163
  multiple=True,
163
- autocompletion=completion.user_group_ids,
164
+ shell_complete=completion.user_group_ids,
164
165
  help="Unassign a group."
165
166
  )
166
167
  @click.pass_obj
@@ -237,7 +238,7 @@ def update_command(
237
238
  @click.command(cls=CmemcCommand, name="password")
238
239
  @click.argument(
239
240
  "username",
240
- autocompletion=completion.user_ids
241
+ shell_complete=completion.user_ids
241
242
  )
242
243
  @click.option(
243
244
  "--value",
@@ -248,8 +249,19 @@ def update_command(
248
249
  is_flag=True,
249
250
  help="If enabled, the user must change the password on next login."
250
251
  )
252
+ @click.option(
253
+ "--request-change",
254
+ is_flag=True,
255
+ help="If enabled, will send a email to user to reset the password."
256
+ )
251
257
  @click.pass_obj
252
- def password_command(app: ApplicationContext, username, value, temporary):
258
+ def password_command(
259
+ app: ApplicationContext,
260
+ username,
261
+ value,
262
+ temporary,
263
+ request_change
264
+ ):
253
265
  """
254
266
  Change the password of a user account.
255
267
 
@@ -268,7 +280,7 @@ def password_command(app: ApplicationContext, username, value, temporary):
268
280
  users = get_user_by_username(username)
269
281
  if not users:
270
282
  raise ValueError(NO_USER_ERROR.format(username))
271
- if not value:
283
+ if not value and not request_change:
272
284
  app.echo_info("\nNew password: ", nl=False)
273
285
  value = getpass(prompt="")
274
286
  app.echo_info("Retype new password: ", nl=False)
@@ -277,7 +289,12 @@ def password_command(app: ApplicationContext, username, value, temporary):
277
289
  app.echo_error("Sorry, passwords do not match.")
278
290
  app.echo_error("password unchanged")
279
291
  sys.exit(1)
280
- reset_password(user_id=users[0]["id"], value=value, temporary=temporary)
292
+ if value:
293
+ reset_password(user_id=users[0]["id"], value=value, temporary=temporary)
294
+ if request_change and not users[0].get("email", None):
295
+ raise ValueError(NO_EMAIL_ERROR.format(username))
296
+ if request_change:
297
+ request_password_change(users[0]["id"])
281
298
  app.echo_success("done")
282
299
 
283
300
 
@@ -287,7 +304,7 @@ def password_command(app: ApplicationContext, username, value, temporary):
287
304
  nargs=-1,
288
305
  required=False,
289
306
  type=click.STRING,
290
- autocompletion=completion.user_ids
307
+ shell_complete=completion.user_ids
291
308
  )
292
309
  @click.pass_obj
293
310
  def open_command(app: ApplicationContext, usernames: str):
@@ -0,0 +1,364 @@
1
+ """DataIntegration variable commands for cmemc."""
2
+ import re
3
+
4
+ import click
5
+ from click import UsageError
6
+
7
+ from cmem.cmemc.cli import completion
8
+ from cmem.cmemc.cli.commands import CmemcCommand, CmemcGroup
9
+ from cmem.cmemc.cli.utils import split_task_id, check_or_select_project
10
+ from cmem.cmempy.workspace.projects.variables import (
11
+ create_or_update_variable,
12
+ delete_variable,
13
+ get_all_variables,
14
+ get_variable
15
+ )
16
+ from cmem.cmemc.cli.context import ApplicationContext
17
+
18
+ VARIABLES_FILTER_TYPES = ["project", 'regex']
19
+ VARIABLES_FILTER_TYPES_HIDDEN = ["ids"]
20
+ VARIABLES_FILTER_TEXT = (
21
+ "Filter variables based on metadata. "
22
+ f"First parameter CHOICE can be one of {str(VARIABLES_FILTER_TYPES)}"
23
+ ". The second parameter is based on CHOICE, e.g. a project "
24
+ "ID or a regular expression string."
25
+ )
26
+
27
+
28
+ def _get_variables_filtered(variables, filter_name, filter_value):
29
+ """Get variables but filtered according to name and value."""
30
+ filter_types = VARIABLES_FILTER_TYPES + VARIABLES_FILTER_TYPES_HIDDEN
31
+ # check for correct filter names (filter ids is used internally only)
32
+ if filter_name not in filter_types:
33
+ raise ValueError(
34
+ f"{filter_name} is an unknown filter name. "
35
+ f"Use one of {VARIABLES_FILTER_TYPES}."
36
+ )
37
+ # filter by ID list
38
+ if filter_name == "ids":
39
+ return [_ for _ in variables if _["id"] in filter_value]
40
+ # filter by project
41
+ if filter_name == "project":
42
+ return [_ for _ in variables if _["project"] == filter_value]
43
+ # filter by regex
44
+ if filter_name == "regex":
45
+ return [
46
+ _ for _ in variables
47
+ if re.search(filter_value, _["id"])
48
+ or re.search(filter_value, _["value"])
49
+ or re.search(filter_value, _.get("description", ""))
50
+ or re.search(filter_value, _.get("template", ""))
51
+ ]
52
+ # return unfiltered list
53
+ return variables
54
+
55
+
56
+ @click.command(cls=CmemcCommand, name="list")
57
+ @click.option(
58
+ "--raw",
59
+ is_flag=True,
60
+ help="Outputs raw JSON."
61
+ )
62
+ @click.option(
63
+ "--id-only",
64
+ is_flag=True,
65
+ help="Lists only variables names and no other metadata. "
66
+ "This is useful for piping the IDs into other commands."
67
+ )
68
+ @click.option(
69
+ "--filter", "filters_",
70
+ multiple=True,
71
+ type=(str, str),
72
+ shell_complete=completion.variable_list_filter,
73
+ help=VARIABLES_FILTER_TEXT
74
+ )
75
+ @click.pass_obj
76
+ def list_command(app: ApplicationContext, raw, id_only, filters_):
77
+ """List available project variables.
78
+
79
+ Outputs a table or a list of project variables.
80
+ """
81
+ variables = get_all_variables()
82
+
83
+ for _ in filters_:
84
+ filter_name, filter_value = _
85
+ variables = _get_variables_filtered(
86
+ variables, filter_name, filter_value
87
+ )
88
+ if raw:
89
+ app.echo_info_json(variables)
90
+ return
91
+ if id_only:
92
+ for _ in sorted(_["id"] for _ in variables):
93
+ app.echo_result(_)
94
+ return
95
+ # output a user table
96
+ table = []
97
+ headers = ["ID", "Value", "Template", "Description"]
98
+ for _ in variables:
99
+ row = [
100
+ _["id"],
101
+ _["value"],
102
+ _.get("template", ""),
103
+ _.get("description", ""),
104
+ ]
105
+ table.append(row)
106
+ app.echo_info_table(table, headers=headers, sort_column=0)
107
+
108
+
109
+ @click.command(cls=CmemcCommand, name="get")
110
+ @click.argument(
111
+ "variable_id",
112
+ required=True,
113
+ type=click.STRING,
114
+ shell_complete=completion.variable_ids
115
+ )
116
+ @click.option(
117
+ "--key",
118
+ type=click.Choice(
119
+ ["value", "template", "description"],
120
+ case_sensitive=False
121
+ ),
122
+ default="value",
123
+ show_default=True,
124
+ help="Specify the name of the value you want to get."
125
+ )
126
+ @click.option(
127
+ "--raw",
128
+ is_flag=True,
129
+ help="Outputs raw json."
130
+ )
131
+ @click.pass_obj
132
+ def get_command(app: ApplicationContext, variable_id, key, raw):
133
+ """Get the value or other data of a project variable.
134
+
135
+ Use the `--key` option to specify which information you want to get.
136
+
137
+ Note: Only the `value` key is always available on a project variable.
138
+ Static value variables have no `template` key, and the `description` key
139
+ is optional for both types of variables.
140
+ """
141
+ project_name, variable_name = split_task_id(variable_id)
142
+ _ = get_variable(variable_name=variable_name, project_name=project_name)
143
+ if raw:
144
+ app.echo_info_json(_)
145
+ return
146
+ try:
147
+ app.echo_info(_[key], nl=False)
148
+ except KeyError as error:
149
+ raise UsageError(
150
+ f"Variable {variable_name} has no value of '{key}'."
151
+ ) from error
152
+
153
+
154
+ @click.command(cls=CmemcCommand, name="delete")
155
+ @click.argument(
156
+ "variable_id",
157
+ required=True,
158
+ type=click.STRING,
159
+ shell_complete=completion.variable_ids
160
+ )
161
+ @click.pass_obj
162
+ def delete_command(app: ApplicationContext, variable_id):
163
+ """Delete a project variable.
164
+
165
+ Note: You can not delete a variable which is used by another
166
+ (template based) variable. In order to do so, delete the template based
167
+ variable first.
168
+ """
169
+ project_name, variable_name = split_task_id(variable_id)
170
+ app.echo_info(
171
+ f"Delete variable {variable_name} from project {project_name} ... ",
172
+ nl=False
173
+ )
174
+ delete_variable(variable_name=variable_name, project_name=project_name)
175
+ app.echo_success("done")
176
+
177
+
178
+ # pylint: disable=too-many-arguments
179
+ @click.command(cls=CmemcCommand, name="create")
180
+ @click.argument(
181
+ "variable_name",
182
+ required=True,
183
+ type=click.STRING,
184
+ )
185
+ @click.option(
186
+ "--value",
187
+ type=click.STRING,
188
+ help="The value of the new project variable."
189
+ )
190
+ @click.option(
191
+ "--template",
192
+ type=click.STRING,
193
+ help="The template of the new project variable. You can use Jinja template "
194
+ "syntax, e.g. use '{{global.myVar}}' for accessing global variables, or "
195
+ "'{{project.myVar}}' for accessing variables from the same project."
196
+ )
197
+ @click.option(
198
+ "--description",
199
+ type=click.STRING,
200
+ help="The optional description of the new project variable."
201
+ )
202
+ @click.option(
203
+ "--project", "project_id",
204
+ type=click.STRING,
205
+ shell_complete=completion.project_ids,
206
+ help="The project, where you want to create the variable in. If there is "
207
+ "only one project in the workspace, this option can be omitted."
208
+ )
209
+ @click.pass_obj
210
+ def create_command(
211
+ app: ApplicationContext,
212
+ variable_name, value, template, description,
213
+ project_id
214
+ ):
215
+ """Create a new project variable.
216
+
217
+ Variables need to be created with a value or a template (not both).
218
+ In addition to that, a project ID and a name are mandatory.
219
+
220
+ Example: cmemc project variable create my_var --project my_project --value abc
221
+
222
+ Note: cmemc is currently not able to manage the order of the variables in a
223
+ project. This means you have to create plain value variables in advance,
224
+ before you can create template based variables, which access these values.
225
+ """
226
+ if value and template:
227
+ raise UsageError(
228
+ "Either use '--value' or '--template' but not both."
229
+ )
230
+ if not value and not template:
231
+ raise UsageError(
232
+ "Use '--value' or '--template' to create a new variable."
233
+ )
234
+ project_id = check_or_select_project(app, project_id)
235
+ data = get_variable(project_name=project_id, variable_name=variable_name)
236
+ if data:
237
+ raise UsageError(
238
+ f"Variable '{variable_name}' already exist in project '{project_id}'."
239
+ )
240
+ data = {
241
+ "name": variable_name,
242
+ "isSensitive": False,
243
+ "scope": "project"
244
+ }
245
+ if value:
246
+ data["value"] = value
247
+ if template:
248
+ data["template"] = template
249
+ if description:
250
+ data["description"] = description
251
+ app.echo_info(
252
+ f"Create variable {variable_name} in project {project_id} ... ",
253
+ nl=False
254
+ )
255
+ create_or_update_variable(
256
+ project_name=project_id,
257
+ variable_name=variable_name,
258
+ data=data
259
+ )
260
+ app.echo_success("done")
261
+
262
+
263
+ @click.command(cls=CmemcCommand, name="update")
264
+ @click.argument(
265
+ "variable_id",
266
+ required=True,
267
+ type=click.STRING,
268
+ shell_complete=completion.variable_ids
269
+ )
270
+ @click.option(
271
+ "--value",
272
+ type=click.STRING,
273
+ help="The new value of the project variable."
274
+ )
275
+ @click.option(
276
+ "--template",
277
+ type=click.STRING,
278
+ help="The new template of the project variable. You can use Jinja template "
279
+ "syntax, e.g. use '{{global.myVar}}' for accessing global variables, or "
280
+ "'{{project.myVar}}' for accessing variables from the same project."
281
+ )
282
+ @click.option(
283
+ "--description",
284
+ type=click.STRING,
285
+ help="The new description of the project variable."
286
+ )
287
+ @click.pass_obj
288
+ def update_command(
289
+ app: ApplicationContext,
290
+ variable_id, value, template, description,
291
+ ):
292
+ """Update data of an existing project variable.
293
+
294
+ With this command you can update the value or the template, as well as the
295
+ description of a project variable.
296
+
297
+ Note: If you update the template of a static variable, it will be transformed
298
+ to a template based variable. If you want to change the value of a template
299
+ based variable, an error will be shown.
300
+ """
301
+ project_id, variable_name = split_task_id(variable_id)
302
+ data = get_variable(project_name=project_id, variable_name=variable_name)
303
+ if not data:
304
+ raise UsageError(
305
+ f"Variable '{variable_name}' does not exist in project '{project_id}'."
306
+ )
307
+ if value and template:
308
+ raise UsageError(
309
+ "Project variables are based on a static value or on a template, but not "
310
+ "both."
311
+ )
312
+ if not value and not template and not description:
313
+ raise UsageError(
314
+ "Please specify what you want to update. "
315
+ "Use at least one of the following options: "
316
+ "'--value', '--template', '--description'."
317
+ )
318
+ if value:
319
+ if data.get("template", None):
320
+ raise UsageError(
321
+ "You can not change the value of a template based variable."
322
+ )
323
+ data["value"] = value
324
+ if template:
325
+ if not data.get("template", None):
326
+ app.echo_warning(f"Variable '{variable_id}' will be converted from a "
327
+ f"simple to a template based variable.")
328
+ data["template"] = template
329
+ if description:
330
+ data["description"] = description
331
+ app.echo_info(
332
+ f"Update variable {variable_name} in project {project_id} ... ",
333
+ nl=False
334
+ )
335
+ create_or_update_variable(
336
+ project_name=project_id,
337
+ variable_name=variable_name,
338
+ data=data
339
+ )
340
+ app.echo_success("done")
341
+
342
+
343
+ @click.group(cls=CmemcGroup)
344
+ def variable():
345
+ """List, create, delete or get data from project variables.
346
+
347
+ Project variables can be used in dataset and task parameters, and in the template
348
+ transform operator.
349
+ Variables are either based on a static value or based on a template.
350
+ They may use templates that access globally configured
351
+ variables or other preceding variables from the same project.
352
+
353
+ Variables are identified by a VARIABLE_ID. To get a list of existing
354
+ variables, execute the list command or use tab-completion.
355
+ The VARIABLE_ID is a concatenation of a PROJECT_ID and a VARIABLE_NAME,
356
+ such as `my-project:my-variable`.
357
+ """
358
+
359
+
360
+ variable.add_command(list_command)
361
+ variable.add_command(get_command)
362
+ variable.add_command(delete_command)
363
+ variable.add_command(create_command)
364
+ variable.add_command(update_command)
@@ -202,7 +202,7 @@ def _transform_cache_to_table(cache_category, table):
202
202
  @click.argument(
203
203
  "iri",
204
204
  type=click.STRING,
205
- autocompletion=completion.installed_vocabularies
205
+ shell_complete=completion.installed_vocabularies
206
206
  )
207
207
  @click.pass_obj
208
208
  def open_command(app, iri):
@@ -276,7 +276,7 @@ def list_command(app, id_only, filter_, raw):
276
276
  "iris",
277
277
  nargs=-1,
278
278
  type=click.STRING,
279
- autocompletion=completion.installable_vocabularies
279
+ shell_complete=completion.installable_vocabularies
280
280
  )
281
281
  @click.option(
282
282
  "-a", "--all", "all_",
@@ -316,7 +316,7 @@ def install_command(app: ApplicationContext, iris: tuple[str, ...], all_):
316
316
  "iris",
317
317
  nargs=-1,
318
318
  type=click.STRING,
319
- autocompletion=completion.installed_vocabularies
319
+ shell_complete=completion.installed_vocabularies
320
320
  )
321
321
  @click.option(
322
322
  "-a", "--all", "all_",
@@ -355,7 +355,7 @@ def uninstall_command(app: ApplicationContext, iris: tuple[str, ...], all_):
355
355
  @click.argument(
356
356
  "FILE",
357
357
  required=True,
358
- autocompletion=completion.triple_files,
358
+ shell_complete=completion.triple_files,
359
359
  type=click.Path(
360
360
  allow_dash=False,
361
361
  readable=True
@@ -437,7 +437,7 @@ def import_command(app: ApplicationContext, file, namespace, replace):
437
437
  "iris",
438
438
  nargs=-1,
439
439
  type=click.STRING,
440
- autocompletion=completion.installed_vocabularies
440
+ shell_complete=completion.installed_vocabularies
441
441
  )
442
442
  @click.option(
443
443
  "-a", "--all", "all_",