cmem-cmemc 24.3.3__py3-none-any.whl → 25.1.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.
Files changed (42) hide show
  1. cmem_cmemc/__init__.py +1 -160
  2. cmem_cmemc/cli.py +138 -0
  3. cmem_cmemc/command.py +36 -0
  4. cmem_cmemc/commands/admin.py +7 -6
  5. cmem_cmemc/commands/client.py +4 -3
  6. cmem_cmemc/commands/config.py +3 -2
  7. cmem_cmemc/commands/dataset.py +18 -17
  8. cmem_cmemc/commands/graph.py +20 -22
  9. cmem_cmemc/commands/manual.py +56 -0
  10. cmem_cmemc/commands/metrics.py +11 -11
  11. cmem_cmemc/commands/migration.py +4 -3
  12. cmem_cmemc/commands/project.py +10 -10
  13. cmem_cmemc/commands/python.py +29 -2
  14. cmem_cmemc/commands/query.py +31 -14
  15. cmem_cmemc/commands/resource.py +4 -3
  16. cmem_cmemc/commands/scheduler.py +4 -4
  17. cmem_cmemc/commands/store.py +2 -2
  18. cmem_cmemc/commands/user.py +10 -9
  19. cmem_cmemc/commands/validation.py +2 -2
  20. cmem_cmemc/commands/variable.py +1 -1
  21. cmem_cmemc/commands/vocabulary.py +11 -10
  22. cmem_cmemc/commands/workflow.py +18 -18
  23. cmem_cmemc/completion.py +76 -49
  24. cmem_cmemc/config_parser.py +44 -0
  25. cmem_cmemc/context.py +166 -97
  26. cmem_cmemc/exceptions.py +15 -2
  27. cmem_cmemc/manual_helper/graph.py +2 -0
  28. cmem_cmemc/manual_helper/multi_page.py +1 -1
  29. cmem_cmemc/manual_helper/single_page.py +2 -0
  30. cmem_cmemc/migrations/remove_noop_triple_251.py +50 -0
  31. cmem_cmemc/migrations/shapes_widget_integrations_243.py +0 -5
  32. cmem_cmemc/object_list.py +3 -3
  33. cmem_cmemc/parameter_types/path.py +7 -0
  34. cmem_cmemc/placeholder.py +69 -0
  35. cmem_cmemc/utils.py +49 -15
  36. {cmem_cmemc-24.3.3.dist-info → cmem_cmemc-25.1.0.dist-info}/METADATA +14 -14
  37. cmem_cmemc-25.1.0.dist-info/RECORD +59 -0
  38. cmem_cmemc-25.1.0.dist-info/entry_points.txt +3 -0
  39. cmem_cmemc-24.3.3.dist-info/RECORD +0 -54
  40. cmem_cmemc-24.3.3.dist-info/entry_points.txt +0 -3
  41. {cmem_cmemc-24.3.3.dist-info → cmem_cmemc-25.1.0.dist-info}/LICENSE +0 -0
  42. {cmem_cmemc-24.3.3.dist-info → cmem_cmemc-25.1.0.dist-info}/WHEEL +0 -0
@@ -3,7 +3,7 @@
3
3
  from typing import Any
4
4
 
5
5
  import click
6
- from click import Argument
6
+ from click import Argument, ClickException, UsageError
7
7
  from cmem.cmempy.config import get_cmem_base_uri
8
8
  from cmem.cmempy.workflow.workflow import get_workflow_editor_uri
9
9
  from cmem.cmempy.workspace.search import list_items
@@ -68,7 +68,7 @@ def open_command(app: ApplicationContext, scheduler_ids: tuple[str, ...], workfl
68
68
  all_scheduler_ids = [s["projectId"] + ":" + s["id"] for s in schedulers]
69
69
  for scheduler_id in scheduler_ids:
70
70
  if scheduler_id not in all_scheduler_ids:
71
- raise ValueError(f"Scheduler '{scheduler_id}' not found.")
71
+ raise ClickException(f"Scheduler '{scheduler_id}' not found.")
72
72
  for scheduler_id in scheduler_ids:
73
73
  for _ in schedulers:
74
74
  current_id = _["projectId"] + ":" + _["id"]
@@ -158,7 +158,7 @@ def disable_command(app: ApplicationContext, scheduler_ids: list[str], all_: boo
158
158
  them one after the other.
159
159
  """
160
160
  if not scheduler_ids and not all_:
161
- raise ValueError(
161
+ raise UsageError(
162
162
  "Either specify at least one scheduler ID or use "
163
163
  "the --all option to disable all scheduler."
164
164
  )
@@ -201,7 +201,7 @@ def enable_command(app: ApplicationContext, scheduler_ids: list[str], all_: bool
201
201
  them one after the other.
202
202
  """
203
203
  if not scheduler_ids and not all_:
204
- raise ValueError(
204
+ raise UsageError(
205
205
  "Either specify at least one scheduler ID or use "
206
206
  "the --all option to enable all scheduler."
207
207
  )
@@ -96,7 +96,7 @@ def showcase_command(app: ApplicationContext, scale: int, create: bool, delete:
96
96
  graphs).
97
97
  """
98
98
  if not delete and not create:
99
- raise ValueError("Either use the --create or the --delete flag.")
99
+ raise UsageError("Either use the --create or the --delete flag.")
100
100
  if delete:
101
101
  raise NotImplementedError(
102
102
  "This feature is not implemented yet. " "Please delete the graphs manually."
@@ -145,7 +145,7 @@ def export_command(
145
145
  " Please use the --replace option instead."
146
146
  )
147
147
  if Path(backup_file).exists() and replace is not True:
148
- raise ValueError(
148
+ raise UsageError(
149
149
  f"Export file {backup_file} already exists and --replace option is not used."
150
150
  )
151
151
  with get_zip() as request:
@@ -4,6 +4,7 @@ import sys
4
4
  from getpass import getpass
5
5
 
6
6
  import click
7
+ from click import ClickException
7
8
  from cmem.cmempy.config import get_keycloak_base_uri, get_keycloak_realm_id
8
9
  from cmem.cmempy.keycloak.group import list_groups
9
10
  from cmem.cmempy.keycloak.user import (
@@ -139,7 +140,7 @@ def delete_command(app: ApplicationContext, username: str) -> None:
139
140
  app.echo_info(f"Deleting user {username} ... ", nl=False)
140
141
  users = get_user_by_username(username)
141
142
  if not users:
142
- raise ValueError(NO_USER_ERROR.format(username))
143
+ raise ClickException(NO_USER_ERROR.format(username))
143
144
 
144
145
  delete_user(users[0]["id"])
145
146
  app.echo_success("deleted")
@@ -160,7 +161,7 @@ def create_command(app: ApplicationContext, username: str) -> None:
160
161
  app.echo_info(f"Creating user {username} ... ", nl=False)
161
162
  users = get_user_by_username(username)
162
163
  if users:
163
- raise ValueError(EXISTING_USER_ERROR.format(username))
164
+ raise ClickException(EXISTING_USER_ERROR.format(username))
164
165
 
165
166
  create_user(username=username)
166
167
  app.echo_success("done")
@@ -207,21 +208,21 @@ def update_command( # noqa: PLR0913
207
208
  """
208
209
  options = (first_name, last_name, email, assign_group, unassign_group)
209
210
  if all(_ is None or _ == () for _ in options):
210
- raise ValueError(
211
+ raise click.UsageError(
211
212
  "This commands needs to be used with at least one option "
212
213
  "(e.g. --email). See the command help for a list of options."
213
214
  )
214
215
  app.echo_info(f"Updating user {username} ... ", nl=False)
215
216
  users = get_user_by_username(username)
216
217
  if not users:
217
- raise ValueError(NO_USER_ERROR.format(username))
218
+ raise ClickException(NO_USER_ERROR.format(username))
218
219
  user_id = users[0]["id"]
219
220
  all_groups = {group["name"]: group["id"] for group in list_groups()}
220
221
  invalid_groups = [group for group in assign_group if group not in all_groups]
221
222
  existing_user_groups = {group["name"] for group in user_groups(user_id)}
222
223
 
223
224
  if invalid_groups:
224
- raise ValueError(
225
+ raise ClickException(
225
226
  NO_GROUP_ERROR.format(
226
227
  ", ".join(invalid_groups), ", ".join(all_groups.keys() - set(existing_user_groups))
227
228
  )
@@ -231,7 +232,7 @@ def update_command( # noqa: PLR0913
231
232
  group for group in unassign_group if group not in existing_user_groups
232
233
  ]
233
234
  if invalid_unassign_groups:
234
- raise ValueError(
235
+ raise ClickException(
235
236
  INVALID_UNASSIGN_GROUP_ERROR.format(
236
237
  ", ".join(invalid_unassign_groups), ", ".join(existing_user_groups)
237
238
  )
@@ -286,7 +287,7 @@ def password_command(
286
287
  app.echo_info(f"Changing password for account {username} ... ", nl=False)
287
288
  users = get_user_by_username(username)
288
289
  if not users:
289
- raise ValueError(NO_USER_ERROR.format(username))
290
+ raise ClickException(NO_USER_ERROR.format(username))
290
291
  if not value and not request_change:
291
292
  app.echo_info("\nNew password: ", nl=False)
292
293
  value = getpass(prompt="")
@@ -299,7 +300,7 @@ def password_command(
299
300
  if value:
300
301
  reset_password(user_id=users[0]["id"], value=value, temporary=temporary)
301
302
  if request_change and not users[0].get("email", None):
302
- raise ValueError(NO_EMAIL_ERROR.format(username))
303
+ raise ClickException(NO_EMAIL_ERROR.format(username))
303
304
  if request_change:
304
305
  request_password_change(users[0]["id"])
305
306
  app.echo_success("done")
@@ -330,7 +331,7 @@ def open_command(app: ApplicationContext, usernames: str) -> None:
330
331
  user_name_id_map = {u["username"]: u["id"] for u in users}
331
332
  for _ in usernames:
332
333
  if _ not in user_name_id_map:
333
- raise ValueError(NO_USER_ERROR.format(_))
334
+ raise ClickException(NO_USER_ERROR.format(_))
334
335
  user_id = user_name_id_map[_]
335
336
  open_user_uri = f"{open_user_base_uri}/{user_id}/settings"
336
337
 
@@ -330,10 +330,10 @@ def _wait_for_process_completion(
330
330
  progress.stop()
331
331
  progress.__exit__(None, None, None)
332
332
  if state.status == validation.STATUS_CANCELLED:
333
- raise ServerError("Process was cancelled.")
333
+ raise ServerError(app, "Process was cancelled.")
334
334
  if state.status == validation.STATUS_ERROR:
335
335
  error_message = state.data.get("error", "")
336
- raise ServerError(f"Process ended with error: {error_message}")
336
+ raise ServerError(app, f"Process ended with error: {error_message}")
337
337
  return state.status
338
338
 
339
339
 
@@ -34,7 +34,7 @@ def _get_variables_filtered(
34
34
  filter_types = VARIABLES_FILTER_TYPES + VARIABLES_FILTER_TYPES_HIDDEN
35
35
  # check for correct filter names (filter ids is used internally only)
36
36
  if filter_name not in filter_types:
37
- raise ValueError(
37
+ raise UsageError(
38
38
  f"{filter_name} is an unknown filter name. " f"Use one of {VARIABLES_FILTER_TYPES}."
39
39
  )
40
40
  # filter by ID list
@@ -6,6 +6,7 @@ from re import match
6
6
  from urllib.parse import urlparse
7
7
 
8
8
  import click
9
+ from click import ClickException
9
10
  from cmem.cmempy.config import get_cmem_base_uri
10
11
  from cmem.cmempy.dp.proxy import graph as graph_api
11
12
  from cmem.cmempy.dp.titles import resolve
@@ -104,7 +105,7 @@ def _validate_namespace(app: ApplicationContext, namespace: tuple[str | None, st
104
105
  """User input validation for the namespace."""
105
106
  prefix, uri = namespace
106
107
  if prefix is None or uri is None:
107
- raise ValueError("No namespace given.")
108
+ raise ClickException("No namespace given.")
108
109
 
109
110
  if uri[-1] not in ("/", "#"):
110
111
  app.echo_warning(
@@ -114,10 +115,10 @@ def _validate_namespace(app: ApplicationContext, namespace: tuple[str | None, st
114
115
  parsed_url = urlparse(uri)
115
116
  app.echo_debug(str(parsed_url))
116
117
  if parsed_url.scheme not in ("http", "https", "urn"):
117
- raise ValueError(f"Namespace IRI '{uri}' is not a https(s) URL or an URN.")
118
+ raise ClickException(f"Namespace IRI '{uri}' is not a https(s) URL or an URN.")
118
119
  prefix_expression = r"^[a-z][a-z0-9]*$"
119
120
  if not match(prefix_expression, prefix):
120
- raise ValueError(
121
+ raise ClickException(
121
122
  "Prefix string does not match this regular" f" expression: {prefix_expression}"
122
123
  )
123
124
 
@@ -167,14 +168,14 @@ def _get_vocabulary_metadata_from_file(
167
168
  try:
168
169
  graph = Graph().parse(file, format="ttl")
169
170
  except BadSyntax as error:
170
- raise ValueError("File {file} could not be parsed as turtle.") from error
171
+ raise ClickException("File {file} could not be parsed as turtle.") from error
171
172
 
172
173
  ontology_iris = graph.query(GET_ONTOLOGY_IRI_QUERY)
173
174
  if len(ontology_iris) == 0:
174
- raise ValueError("There is no owl:Ontology resource described " "in the turtle file.")
175
+ raise ClickException("There is no owl:Ontology resource described " "in the turtle file.")
175
176
  if len(ontology_iris) > 1:
176
177
  ontology_iris_str = [str(iri[0]) for iri in ontology_iris] # type: ignore[index]
177
- raise ValueError(
178
+ raise ClickException(
178
179
  "There are more than one owl:Ontology resources described "
179
180
  f"in the turtle file: {ontology_iris_str}"
180
181
  )
@@ -182,7 +183,7 @@ def _get_vocabulary_metadata_from_file(
182
183
  metadata["iri"] = iri
183
184
  vann_data = graph.query(GET_PREFIX_DECLARATION.format(iri))
184
185
  if not vann_data and not namespace_given:
185
- raise ValueError(
186
+ raise ClickException(
186
187
  "There is no namespace defined "
187
188
  f"for the ontology '{iri}'.\n"
188
189
  "Please add a prefix and namespace to the sources"
@@ -191,13 +192,13 @@ def _get_vocabulary_metadata_from_file(
191
192
  "https://vocab.org/vann/ for more information."
192
193
  )
193
194
  if vann_data and namespace_given:
194
- raise ValueError(
195
+ raise ClickException(
195
196
  "There is already a namespace defined "
196
197
  f"in the file for the ontology '{iri}'.\n"
197
198
  "You can not use the --namespace option with this file."
198
199
  )
199
200
  if len(vann_data) > 1:
200
- raise ValueError(
201
+ raise ClickException(
201
202
  "There is more than one vann namespace defined " f"for the ontology: {iri}"
202
203
  )
203
204
  if not namespace_given:
@@ -378,7 +379,7 @@ def import_command(
378
379
  if replace:
379
380
  success_message = "replaced"
380
381
  else:
381
- raise ValueError(f"Proposed graph {iri} does already exist.")
382
+ raise ClickException(f"Proposed graph {iri} does already exist.")
382
383
  app.echo_info(f"Import {file} as vocabulary to {iri} ... ", nl=False)
383
384
  # upload graph
384
385
  _buffer.seek(0)
@@ -7,7 +7,7 @@ from datetime import datetime, timezone
7
7
 
8
8
  import click
9
9
  import timeago
10
- from click import UsageError
10
+ from click import ClickException, UsageError
11
11
  from cmem.cmempy.workflow import get_workflows
12
12
  from cmem.cmempy.workflow.workflow import (
13
13
  execute_workflow_io,
@@ -99,12 +99,12 @@ def _get_workflows_filtered_by_io_feature(workflows: list[dict], feature: str) -
99
99
 
100
100
  Raises:
101
101
  ------
102
- ValueError
102
+ UsageError
103
103
 
104
104
  """
105
105
  possible_io_filter_values = ("input-only", "output-only", "input-output", "any")
106
106
  if feature not in possible_io_filter_values:
107
- raise ValueError(
107
+ raise UsageError(
108
108
  f"{feature} is an unknown filter value. " f"Use one of {possible_io_filter_values}."
109
109
  )
110
110
  filtered_workflows_ids = []
@@ -141,11 +141,11 @@ def _get_workflows_filtered(workflows: list, filter_name: str, filter_value: str
141
141
 
142
142
  Raises:
143
143
  ------
144
- ValueError
144
+ UsageError
145
145
 
146
146
  """
147
147
  if filter_name not in WORKFLOW_FILTER_TYPES:
148
- raise ValueError(
148
+ raise UsageError(
149
149
  f"{filter_name} is an unknown filter name. " f"Use one of {WORKFLOW_FILTER_TYPES}."
150
150
  )
151
151
  # filter by regex on the label
@@ -167,30 +167,30 @@ def _get_workflows_filtered(workflows: list, filter_name: str, filter_value: str
167
167
  def _io_check_request(info: dict, input_file: str, output_file: str, output_mimetype: str) -> None:
168
168
  """Check the requested io execution
169
169
 
170
- Raise ValueError in multiple cases.
170
+ Raise UsageError in multiple cases.
171
171
  """
172
172
  if len(info["variableInputs"]) == 0 and input_file:
173
- raise ValueError(
173
+ raise UsageError(
174
174
  "You are trying to send data to a workflow without a variable "
175
175
  "input. Please remove the '-i' parameter."
176
176
  )
177
177
  if len(info["variableOutputs"]) == 0 and output_file:
178
- raise ValueError(
178
+ raise UsageError(
179
179
  "You are trying to retrieve data to a workflow without a variable "
180
180
  "output. Please remove the '-o' parameter."
181
181
  )
182
182
  if len(info["variableInputs"]) == 1 and not input_file:
183
- raise ValueError(
183
+ raise UsageError(
184
184
  "This workflow has a defined input so you need to use the '-i' "
185
185
  "parameter to send data to it."
186
186
  )
187
187
  if len(info["variableOutputs"]) == 1 and not output_file:
188
- raise ValueError(
188
+ raise UsageError(
189
189
  "This workflow has a defined output so you need to use the '-o' "
190
190
  "parameter to retrieve data from it."
191
191
  )
192
192
  if output_mimetype in STDOUT_UNSUPPORTED_MIME_TYPES and output_file == "-":
193
- raise ValueError(
193
+ raise UsageError(
194
194
  f"Trying to output an {STDOUT_UNSUPPORTED_MIME_TYPES[output_mimetype]} "
195
195
  "file to stdout will fail.\n"
196
196
  "Please output to a regular file instead "
@@ -205,7 +205,7 @@ def _io_get_info(project_id: str, workflow_id: str) -> dict[str, str]:
205
205
  info: dict[str, str] = _
206
206
  if info["id"] == workflow_id and info["projectId"] == project_id:
207
207
  return info
208
- raise ValueError(
208
+ raise ClickException(
209
209
  "The given workflow does not exist or is not suitable to be executed "
210
210
  "with this command.\n"
211
211
  "An io workflow needs exactly one variable input and/or one variable "
@@ -229,12 +229,12 @@ def _io_process_response(response: Response, app: ApplicationContext, output_fil
229
229
  def _io_guess_output(output_file: str) -> str:
230
230
  """Guess the mime type of output file name."""
231
231
  if output_file == "-":
232
- raise ValueError("Output mime-type not guessable, please use the --output-mimetype option.")
232
+ raise UsageError("Output mime-type not guessable, please use the --output-mimetype option.")
233
233
  file_extension = Path(output_file).suffix
234
234
  if file_extension in VALID_EXTENSIONS:
235
235
  return f"application/x-plugin-{FILE_EXTENSIONS_TO_PLUGIN_ID[file_extension]}"
236
236
  valid_extensions = ", ".join(VALID_EXTENSIONS)
237
- raise ValueError(
237
+ raise UsageError(
238
238
  f"Files with the extension {file_extension} can not be generated. "
239
239
  f"Try one of {valid_extensions}"
240
240
  )
@@ -243,12 +243,12 @@ def _io_guess_output(output_file: str) -> str:
243
243
  def _io_guess_input(input_file: str) -> str:
244
244
  """Guess the mime type of input file name."""
245
245
  if input_file == "-":
246
- raise ValueError("Input mime-type not guessable, please use the --output-mimetype option.")
246
+ raise UsageError("Input mime-type not guessable, please use the --output-mimetype option.")
247
247
  file_extension = Path(input_file).suffix
248
248
  if file_extension in VALID_EXTENSIONS:
249
249
  return f"application/x-plugin-{FILE_EXTENSIONS_TO_PLUGIN_ID[file_extension]}"
250
250
  valid_extensions = ", ".join(VALID_EXTENSIONS)
251
- raise ValueError(
251
+ raise UsageError(
252
252
  f"Files with the extension {file_extension} can not be processed. "
253
253
  f"Try one of {valid_extensions}"
254
254
  )
@@ -335,7 +335,7 @@ def _workflow_echo_status(app: ApplicationContext, status: dict) -> None:
335
335
  if status_name in ("Running", "Canceling", "Waiting"):
336
336
  app.echo_warning(message)
337
337
  return
338
- raise ValueError(
338
+ raise ClickException(
339
339
  f"statusName is {status_name}, expecting one of: " "Running, Canceling or Waiting."
340
340
  )
341
341
  # not running can be Idle or Finished
@@ -390,7 +390,7 @@ def execute_command( # noqa: PLR0913
390
390
  a workflow, the next workflow is not started.
391
391
  """
392
392
  if workflow_ids == () and not all_:
393
- raise ValueError(
393
+ raise UsageError(
394
394
  "Either specify at least one workflow or use the"
395
395
  " --all option to execute all workflows."
396
396
  )