cmem-cmemc 25.5.0rc1__py3-none-any.whl → 25.6.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  """Graph Insights command group"""
2
2
 
3
- from typing import TYPE_CHECKING
3
+ import time
4
4
 
5
5
  import click
6
6
  from click import Argument, Context
@@ -12,6 +12,7 @@ from requests import HTTPError
12
12
  from cmem_cmemc.command import CmemcCommand
13
13
  from cmem_cmemc.command_group import CmemcGroup
14
14
  from cmem_cmemc.completion import NOT_SORTED, finalize_completion, graph_uris
15
+ from cmem_cmemc.context import ApplicationContext
15
16
  from cmem_cmemc.exceptions import CmemcError
16
17
  from cmem_cmemc.object_list import (
17
18
  DirectListPropertyFilter,
@@ -22,11 +23,30 @@ from cmem_cmemc.object_list import (
22
23
  from cmem_cmemc.string_processor import GraphLink, TimeAgo
23
24
  from cmem_cmemc.utils import get_graphs_as_dict, struct_to_table
24
25
 
25
- if TYPE_CHECKING:
26
- from cmem_cmemc.context import ApplicationContext
27
26
 
27
+ def get_api_url(path: str = "") -> str:
28
+ """Get URLs of the graph insights API.
28
29
 
29
- API_BASE = get_dp_api_endpoint() + "/api/ext/semspect"
30
+ Constructs the full URL for accessing graph insights API endpoints by combining
31
+ the DataPlatform API endpoint with the semspect extension base path and an
32
+ optional resource path.
33
+
34
+ Args:
35
+ path: The API resource path to append to the base URL. Defaults to an empty
36
+ string for the root endpoint.
37
+
38
+ Returns:
39
+ The complete URL for the specified graph insights API endpoint.
40
+
41
+ Example:
42
+ >>> get_api_url()
43
+ 'https://example.com/dataplatform/api/ext/semspect'
44
+ >>> get_api_url("/snapshot/status")
45
+ 'https://example.com/dataplatform/api/ext/semspect/snapshot/status'
46
+
47
+ """
48
+ base_url = get_dp_api_endpoint() + "/api/ext/semspect"
49
+ return f"{base_url}{path}"
30
50
 
31
51
 
32
52
  def is_available() -> bool:
@@ -38,7 +58,7 @@ def is_available() -> bool:
38
58
  }
39
59
  """
40
60
  try:
41
- data: dict[str, bool] = get_json(API_BASE)
61
+ data: dict[str, bool] = get_json(get_api_url())
42
62
  except HTTPError:
43
63
  return False
44
64
  return bool(data["isActive"] is True and data["isUserAllowed"] is True)
@@ -56,13 +76,16 @@ def get_snapshots(ctx: click.Context) -> list[dict[str, str | bool | list[str]]]
56
76
  """Get the snapshot list (all snapshots)"""
57
77
  check_availability(ctx)
58
78
  data: list[dict[str, str | bool | list[str]]] = get_json(
59
- API_BASE + "/snapshot/status", params={"includeManagementOnly": True}
79
+ get_api_url("/snapshot/status"), params={"includeManagementOnly": True}
60
80
  )
61
81
  return data
62
82
 
63
83
 
64
84
  def complete_snapshot_ids(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]: # noqa: ARG001
65
85
  """Provide auto-completion for snapshot Ids"""
86
+ ApplicationContext.set_connection_from_params(ctx.find_root().params)
87
+ if not is_available():
88
+ return []
66
89
  snapshots = get_snapshots(ctx)
67
90
  snapshots = sorted(
68
91
  snapshots, key=lambda snapshot: snapshot["updateInfoTimestamp"], reverse=True
@@ -102,6 +125,12 @@ snapshot_list = ObjectList(
102
125
  description="Snapshots with a specific affected graph (main or sub-graphs).",
103
126
  property_key="allGraphsSynced",
104
127
  ),
128
+ DirectValuePropertyFilter(
129
+ name="valid",
130
+ description="Snapshots with a specific validity indicator.",
131
+ property_key="isValid",
132
+ transform=transform_lower,
133
+ ),
105
134
  ],
106
135
  )
107
136
 
@@ -147,13 +176,14 @@ def list_command(ctx: Context, filter_: tuple[tuple[str, str]], id_only: bool, r
147
176
  main_graph = _["mainGraphSynced"]
148
177
  updated = _["updateInfoTimestamp"]
149
178
  status = _["status"]
179
+ is_valid = _["isValid"]
150
180
  if main_graph not in graphs:
151
181
  main_graph = rf"\[missing: {main_graph}]"
152
- table.append([id_, main_graph, updated, status])
182
+ table.append([id_, main_graph, updated, status, is_valid])
153
183
 
154
184
  app.echo_info_table(
155
185
  table,
156
- headers=["ID", "Main Graph", "Updated", "Status"],
186
+ headers=["ID", "Main Graph", "Updated", "Status", "Valid"],
157
187
  sort_column=0,
158
188
  cell_processing={1: GraphLink(), 2: TimeAgo()},
159
189
  empty_table_message="No graph insight snapshots found.",
@@ -188,34 +218,52 @@ def delete_command(
188
218
 
189
219
  if all_:
190
220
  app.echo_info("Deleting all snapshots ... ", nl=False)
191
- request(method="DELETE", uri=f"{API_BASE}/snapshot")
221
+ request(method="DELETE", uri=get_api_url("/snapshot"))
192
222
  app.echo_success("done")
193
223
  return
194
224
 
195
- all_snapshots = get_snapshots(ctx)
196
- all_snapshot_ids = [_["databaseId"] for _ in all_snapshots]
197
225
  filter_to_apply = list(filter_) if filter_ else []
198
226
  if snapshot_id:
199
227
  filter_to_apply.append(("id", snapshot_id))
200
228
  snapshots_to_delete = snapshot_list.apply_filters(ctx=ctx, filter_=filter_to_apply)
201
- if not snapshots_to_delete:
202
- raise click.UsageError("No snapshots found to delete.")
203
229
 
204
- for _ in snapshots_to_delete:
205
- id_to_delete = _["databaseId"]
206
- if id_to_delete not in all_snapshot_ids:
207
- raise click.UsageError(f"Snapshot ID '{id_to_delete}' does not exist.")
230
+ if not snapshots_to_delete and snapshot_id:
231
+ raise click.ClickException(f"Snapshot ID '{snapshot_id}' does not exist.")
232
+
233
+ if not snapshots_to_delete and not snapshot_id:
234
+ raise click.ClickException("No snapshots found to delete.")
235
+
208
236
  for _ in snapshots_to_delete:
209
237
  id_to_delete = _["databaseId"]
210
238
  app.echo_info(f"Deleting snapshot {id_to_delete} ... ", nl=False)
211
- request(method="DELETE", uri=f"{API_BASE}/snapshot/{id_to_delete}")
239
+ request(method="DELETE", uri=get_api_url(f"/snapshot/{id_to_delete}"))
212
240
  app.echo_success("done")
213
241
 
214
242
 
243
+ def wait_for_snapshot(snapshot_id: str, polling_interval: int) -> None:
244
+ """Poll until the snapshot reaches 'DONE' status."""
245
+ while True:
246
+ snapshot: dict[str, str | bool | list[str]] = get_json(
247
+ get_api_url(f"/snapshot/status/{snapshot_id}")
248
+ )
249
+ if snapshot.get("status") == "DONE":
250
+ break
251
+ time.sleep(polling_interval)
252
+
253
+
215
254
  @click.command(cls=CmemcCommand, name="create")
216
255
  @click.argument("iri", type=click.STRING, shell_complete=graph_uris)
256
+ @click.option("--wait", is_flag=True, help="Wait until snapshot creation is done.")
257
+ @click.option(
258
+ "--polling-interval",
259
+ type=click.IntRange(min=0, max=60),
260
+ show_default=True,
261
+ default=1,
262
+ help="How many seconds to wait between status polls. Status polls are"
263
+ " cheap, so a higher polling interval is most likely not needed.",
264
+ )
217
265
  @click.pass_context
218
- def create_command(ctx: Context, iri: str) -> None:
266
+ def create_command(ctx: Context, iri: str, wait: bool, polling_interval: int) -> None:
219
267
  """Create or update a graph insight snapshot.
220
268
 
221
269
  Create a graph insight snapshot for a given graph.
@@ -225,8 +273,14 @@ def create_command(ctx: Context, iri: str) -> None:
225
273
  check_availability(ctx)
226
274
  app: ApplicationContext = ctx.obj
227
275
  app.echo_info(f"Create / Update graph snapshot for graph {iri} ... ", nl=False)
228
- request(method="POST", uri=f"{API_BASE}/snapshot", params={"contextGraph": iri})
229
- app.echo_success("started")
276
+ snapshot_id = request(
277
+ method="POST", uri=get_api_url("/snapshot"), params={"contextGraph": iri}
278
+ ).text
279
+ app.echo_success("started", nl=not wait)
280
+ if wait:
281
+ app.echo_info(" ... ", nl=False)
282
+ wait_for_snapshot(snapshot_id, polling_interval)
283
+ app.echo_success("created")
230
284
 
231
285
 
232
286
  @click.command(cls=CmemcCommand, name="update")
@@ -240,9 +294,23 @@ def create_command(ctx: Context, iri: str) -> None:
240
294
  multiple=True,
241
295
  )
242
296
  @click.option("-a", "--all", "all_", is_flag=True, help="Delete all snapshots.")
297
+ @click.option("--wait", is_flag=True, help="Wait until snapshot creation is done.")
298
+ @click.option(
299
+ "--polling-interval",
300
+ type=click.IntRange(min=0, max=60),
301
+ show_default=True,
302
+ default=1,
303
+ help="How many seconds to wait between status polls. Status polls are"
304
+ " cheap, so a higher polling interval is most likely not needed.",
305
+ )
243
306
  @click.pass_context
244
- def update_command(
245
- ctx: Context, snapshot_id: str | None, filter_: tuple[tuple[str, str]], all_: bool
307
+ def update_command( # noqa: PLR0913
308
+ ctx: Context,
309
+ snapshot_id: str | None,
310
+ filter_: tuple[tuple[str, str]],
311
+ all_: bool,
312
+ wait: bool,
313
+ polling_interval: int,
246
314
  ) -> None:
247
315
  """Update a graph insight snapshot.
248
316
 
@@ -259,24 +327,24 @@ def update_command(
259
327
  filter_to_apply.append(("id", snapshot_id))
260
328
  snapshots_to_update = snapshot_list.apply_filters(ctx=ctx, filter_=filter_to_apply)
261
329
 
262
- all_snapshots = get_snapshots(ctx)
263
- all_snapshot_ids = [_["databaseId"] for _ in all_snapshots]
264
-
265
330
  if all_:
266
331
  snapshots_to_update = get_snapshots(ctx)
267
332
 
268
- if not snapshots_to_update:
269
- raise click.UsageError("No snapshots found to delete.")
333
+ if not snapshots_to_update and snapshot_id:
334
+ raise click.ClickException(f"Snapshot ID '{snapshot_id}' does not exist.")
335
+
336
+ if not snapshots_to_update and not snapshot_id:
337
+ raise click.ClickException("No snapshots found to update.")
270
338
 
271
- for _ in snapshots_to_update:
272
- id_to_update = _["databaseId"]
273
- if id_to_update not in all_snapshot_ids:
274
- raise click.UsageError(f"Snapshot ID '{id_to_update}' does not exist.")
275
339
  for _ in snapshots_to_update:
276
340
  id_to_update = _["databaseId"]
277
341
  app.echo_info(f"Update snapshot {id_to_update} ... ", nl=False)
278
- request(method="PUT", uri=f"{API_BASE}/snapshot/{snapshot_id}")
279
- app.echo_success("started")
342
+ request(method="PUT", uri=get_api_url(f"/snapshot/{id_to_update}"))
343
+ app.echo_success("started", nl=not wait)
344
+ if wait:
345
+ app.echo_info(" ... ", nl=False)
346
+ wait_for_snapshot(id_to_update, polling_interval)
347
+ app.echo_success("updated")
280
348
 
281
349
 
282
350
  @click.command(cls=CmemcCommand, name="inspect")
@@ -288,7 +356,7 @@ def inspect_command(ctx: Context, snapshot_id: str, raw: bool) -> None:
288
356
  check_availability(ctx)
289
357
  app: ApplicationContext = ctx.obj
290
358
  snapshot: dict[str, str | bool | list[str]] = get_json(
291
- f"{API_BASE}/snapshot/status/{snapshot_id}"
359
+ get_api_url(f"/snapshot/status/{snapshot_id}")
292
360
  )
293
361
  if raw:
294
362
  app.echo_info_json(snapshot)
@@ -32,6 +32,7 @@ from jinja2 import Template
32
32
  from cmem_cmemc import completion
33
33
  from cmem_cmemc.command import CmemcCommand
34
34
  from cmem_cmemc.command_group import CmemcGroup
35
+ from cmem_cmemc.commands.file import file
35
36
  from cmem_cmemc.commands.variable import variable
36
37
  from cmem_cmemc.context import ApplicationContext
37
38
  from cmem_cmemc.parameter_types.path import ClickSmartPath
@@ -138,7 +139,7 @@ def list_command(app: ApplicationContext, raw: bool, id_only: bool) -> None:
138
139
  for _ in projects:
139
140
  row = [
140
141
  _["name"],
141
- _["metaData"]["label"],
142
+ _["metaData"].get("label", ""),
142
143
  ]
143
144
  table.append(row)
144
145
  app.echo_info_table(
@@ -584,3 +585,4 @@ project.add_command(delete_command)
584
585
  project.add_command(create_command)
585
586
  project.add_command(reload_command)
586
587
  project.add_command(variable)
588
+ project.add_command(file)
@@ -267,7 +267,7 @@ def list_plugins_command(
267
267
  table,
268
268
  headers=["ID", "Package ID", "Type", "Label"],
269
269
  sort_column=0,
270
- empty_table_message="No plugin plugins found. "
270
+ empty_table_message="No plugins found. "
271
271
  "Use the `admin workspace python install` command to install python packages with plugins.",
272
272
  )
273
273
  if "error" in raw_output:
@@ -69,7 +69,9 @@ WHERE {{}}
69
69
  """
70
70
 
71
71
 
72
- def _validate_vocabs_to_process(iris: tuple[str], filter_: str, all_flag: bool) -> list[str]:
72
+ def _validate_vocabs_to_process(
73
+ iris: tuple[str], filter_: str, all_flag: bool, replace: bool = False
74
+ ) -> list[str]:
73
75
  """Return a list of vocabulary IRTs which will be processed.
74
76
 
75
77
  list is without duplicates, and validated if they exist
@@ -85,6 +87,8 @@ def _validate_vocabs_to_process(iris: tuple[str], filter_: str, all_flag: bool)
85
87
  if filter_ == "installed": # uninstall command
86
88
  return [_ for _ in all_vocabs if all_vocabs[_]["installed"]]
87
89
  # install command
90
+ if replace:
91
+ return list(all_vocabs)
88
92
  return [_ for _ in all_vocabs if not all_vocabs[_]["installed"]]
89
93
 
90
94
  vocabs_to_process = list(set(iris)) # avoid double removal / installation
@@ -96,7 +100,7 @@ def _validate_vocabs_to_process(iris: tuple[str], filter_: str, all_flag: bool)
96
100
  if filter_ == "installable": # install command
97
101
  if _ not in all_vocabs:
98
102
  raise click.UsageError(f"Vocabulary {_} does not exist.")
99
- if all_vocabs[_]["installed"]:
103
+ if all_vocabs[_]["installed"] and not replace:
100
104
  raise click.UsageError(f"Vocabulary {_} already installed.")
101
105
  return vocabs_to_process
102
106
 
@@ -290,15 +294,20 @@ def list_command(app: ApplicationContext, id_only: bool, filter_: str, raw: bool
290
294
  @click.option(
291
295
  "-a", "--all", "all_", is_flag=True, help="Install all vocabularies from the catalog."
292
296
  )
297
+ @click.option(
298
+ "--replace", is_flag=True, help="Replace (overwrite) existing vocabulary, if present."
299
+ )
293
300
  @click.pass_obj
294
- def install_command(app: ApplicationContext, iris: tuple[str], all_: bool) -> None:
301
+ def install_command(app: ApplicationContext, iris: tuple[str], all_: bool, replace: bool) -> None:
295
302
  """Install one or more vocabularies from the catalog.
296
303
 
297
304
  Vocabularies are identified by their graph IRI.
298
305
  Installable vocabularies can be listed with the
299
306
  vocabulary list command.
300
307
  """
301
- vocabs_to_install = _validate_vocabs_to_process(iris=iris, filter_="installable", all_flag=all_)
308
+ vocabs_to_install = _validate_vocabs_to_process(
309
+ iris=iris, filter_="installable", all_flag=all_, replace=replace
310
+ )
302
311
  count: int = len(vocabs_to_install)
303
312
  for current, vocab in enumerate(vocabs_to_install, start=1):
304
313
  app.echo_info(f"Install vocabulary {current}/{count}: {vocab} ... ", nl=False)
cmem_cmemc/completion.py CHANGED
@@ -31,7 +31,7 @@ from cmem.cmempy.workspace import (
31
31
  )
32
32
  from cmem.cmempy.workspace.projects.datasets.dataset import get_dataset
33
33
  from cmem.cmempy.workspace.projects.project import get_projects
34
- from cmem.cmempy.workspace.projects.resources import get_all_resources
34
+ from cmem.cmempy.workspace.projects.resources import get_all_resources, get_resources
35
35
  from cmem.cmempy.workspace.projects.variables import get_all_variables
36
36
  from cmem.cmempy.workspace.python import list_packages
37
37
  from cmem.cmempy.workspace.search import list_items
@@ -728,7 +728,7 @@ def project_ids(ctx: Context, param: Argument, incomplete: str) -> list[Completi
728
728
  options = []
729
729
  for _ in projects:
730
730
  project_id = _["name"]
731
- label = _["metaData"]["label"]
731
+ label = _["metaData"].get("label", "")
732
732
  # do not add project if already in the command line
733
733
  if check_option_in_params(project_id, ctx.params.get(str(param.name))):
734
734
  continue
@@ -934,9 +934,7 @@ def resource_list_filter(ctx: Context, param: Argument, incomplete: str) -> list
934
934
  if args[len(args) - 1] == "--filter":
935
935
  return [CompletionItem(value=f[0], help=f[1]) for f in filter_names]
936
936
  if args[len(args) - 1] == "project":
937
- return finalize_completion(
938
- candidates=project_ids(ctx, param, incomplete), incomplete=incomplete
939
- )
937
+ return project_ids(ctx, param, incomplete)
940
938
  if args[len(args) - 1] == "regex":
941
939
  return finalize_completion(candidates=filter_values_regex, incomplete=incomplete)
942
940
  return []
@@ -1055,3 +1053,22 @@ def transformation_task_ids(ctx: Context, param: Argument, incomplete: str) -> l
1055
1053
  datasets = results["results"]
1056
1054
  options = [(f"{_['projectId']}:{_['id']}", _["label"]) for _ in datasets]
1057
1055
  return finalize_completion(candidates=options, incomplete=incomplete, sort_by=SORT_BY_DESC)
1056
+
1057
+
1058
+ def resource_paths(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]:
1059
+ """Prepare a list of file resource paths within a project.
1060
+
1061
+ Returns the full path of file resources (not including the project ID prefix).
1062
+ If a project_id is available in context, lists resources from that project.
1063
+ If only one project exists, automatically uses that project.
1064
+ """
1065
+ ApplicationContext.set_connection_from_params(ctx.find_root().params)
1066
+ project_id = ctx.params.get("project_id")
1067
+ if not project_id:
1068
+ projects = get_projects()
1069
+ if len(projects) == 1:
1070
+ project_id = projects[0]["name"]
1071
+ if project_id is None:
1072
+ return []
1073
+ options = [_["fullPath"] for _ in list(get_resources(project_id))]
1074
+ return finalize_completion(candidates=options, incomplete=incomplete, sort_by=SORT_BY_DESC)
cmem_cmemc/context.py CHANGED
@@ -29,9 +29,9 @@ from cmem_cmemc.exceptions import InvalidConfigurationError
29
29
  from cmem_cmemc.string_processor import StringProcessor, process_row
30
30
  from cmem_cmemc.utils import is_enabled, str_to_bool
31
31
 
32
- DI_TARGET_VERSION = "v25.2.0"
32
+ DI_TARGET_VERSION = "v25.3.0"
33
33
 
34
- EXPLORE_TARGET_VERSION = "v25.2.0"
34
+ EXPLORE_TARGET_VERSION = "v25.3.0"
35
35
 
36
36
  KNOWN_CONFIG_KEYS = {
37
37
  "CMEM_BASE_URI": cmempy_config.get_cmem_base_uri,
@@ -56,7 +56,7 @@ def print_group_manual_graph_recursive(
56
56
  ctx.obj.echo_info(f"{iri} a cli:CommandGroup .")
57
57
  ctx.obj.echo_info(f"{iri} rdfs:label '{prefix}{key} Command Group' .")
58
58
  ctx.obj.echo_info(f"{iri} cli:subGroupOf {sub_group_iri} .")
59
- ctx.obj.echo_info(f"{iri} rdfs:comment '{comment}' .")
59
+ ctx.obj.echo_info(f'{iri} rdfs:comment """{comment}""" .')
60
60
  print_group_manual_graph_recursive(item, ctx=ctx, prefix=f"{prefix}{key}-")
61
61
  elif isinstance(item, click.Command):
62
62
  comment = item.get_short_help_str(limit=200)
@@ -64,7 +64,7 @@ def print_group_manual_graph_recursive(
64
64
  ctx.obj.echo_info(f"{iri} a cli:Command .")
65
65
  ctx.obj.echo_info(f"{iri} rdfs:label '{prefix}{key} Command' .")
66
66
  ctx.obj.echo_info(f"{iri} cli:group {group_iri} .")
67
- ctx.obj.echo_info(f"{iri} rdfs:comment '{comment}' .")
67
+ ctx.obj.echo_info(f'{iri} rdfs:comment """{comment}""" .')
68
68
  for parameter in item.params:
69
69
  print_parameter_manual_graph(parameter, ctx=ctx, prefix=f"{prefix}{key}-")
70
70
  else:
@@ -26,12 +26,13 @@ def get_icon_for_command_group(full_name: str) -> str:
26
26
  "admin workspace python": "material/language-python",
27
27
  "config": "material/cog-outline",
28
28
  "dataset": "eccenca/artefact-dataset",
29
- "dataset resource": "eccenca/artefact-file",
30
29
  "project": "eccenca/artefact-project",
30
+ "project file": "eccenca/artefact-file",
31
31
  "project variable": "material/variable-box",
32
32
  "query": "eccenca/application-queries",
33
33
  "graph": "eccenca/artefact-dataset-eccencadataplatform",
34
34
  "graph imports": "material/family-tree",
35
+ "graph insights": "eccenca/graph-insights",
35
36
  "graph validation": "octicons/verified-16",
36
37
  "vocabulary": "eccenca/application-vocabularies",
37
38
  "vocabulary cache": "eccenca/application-vocabularies",
@@ -54,8 +55,8 @@ def get_tags_for_command_group(full_name: str) -> str:
54
55
  "admin workspace python": ["Python", "cmemc"],
55
56
  "config": ["Configuration", "cmemc"],
56
57
  "dataset": ["cmemc"],
57
- "dataset resource": ["cmemc"],
58
58
  "project": ["Project", "cmemc"],
59
+ "project file": ["Files", "cmemc"],
59
60
  "project variable": ["Variables", "cmemc"],
60
61
  "query": ["SPARQL", "cmemc"],
61
62
  "graph": ["KnowledgeGraph", "cmemc"],
@@ -145,12 +146,10 @@ def get_commands_for_table_recursive(
145
146
  if isinstance(item, Command):
146
147
  command_name = item.name
147
148
  group_link = f"{prefix}/{group_name}/index.md".replace(" ", "/")
148
- if group_link.startswith("/"):
149
- group_link = group_link[1:]
149
+ group_link = group_link.removeprefix("/")
150
150
  command_anchor = f"{prefix}-{group_name}".replace(" ", "-")
151
151
  command_anchor += f"-{command_name}"
152
- if command_anchor.startswith("-"):
153
- command_anchor = command_anchor[1:]
152
+ command_anchor = command_anchor.removeprefix("-")
154
153
  command_link = f"{group_link}#{command_anchor}"
155
154
  new_command = {
156
155
  "command_name": command_name,
cmem_cmemc/placeholder.py CHANGED
@@ -36,7 +36,7 @@ class QueryPlaceholder:
36
36
  """Prepare a list of placeholder values"""
37
37
  result = self.value_query.get_json_results()
38
38
  projection_vars = result["head"]["vars"]
39
- bindings = result["results"]["bindings"]
39
+ bindings: list[dict] = result["results"]["bindings"]
40
40
  if "value" not in projection_vars:
41
41
  return []
42
42
 
@@ -50,7 +50,7 @@ class QueryPlaceholder:
50
50
  values_with_description = []
51
51
  for _ in bindings:
52
52
  value = str(_["value"]["value"])
53
- description = str(_["description"]["value"])
53
+ description = _.get("description", {}).get("value", "")
54
54
  values_with_description.append((value, description))
55
55
  return values_with_description
56
56
 
@@ -6,6 +6,7 @@ from urllib.parse import quote
6
6
 
7
7
  import timeago
8
8
  from cmem.cmempy.config import get_cmem_base_uri
9
+ from humanize import naturalsize
9
10
 
10
11
  from cmem_cmemc.title_helper import TitleHelper
11
12
  from cmem_cmemc.utils import get_graphs_as_dict
@@ -19,6 +20,17 @@ class StringProcessor(ABC):
19
20
  """Process a single string content and output the processed string."""
20
21
 
21
22
 
23
+ class FileSize(StringProcessor):
24
+ """Create a human-readable file size string."""
25
+
26
+ def process(self, text: str) -> str:
27
+ """Process a single string content and output the processed string."""
28
+ try:
29
+ return "" if text is None else naturalsize(value=text, gnu=True)
30
+ except ValueError:
31
+ return text
32
+
33
+
22
34
  class TimeAgo(StringProcessor):
23
35
  """Create a string similar to 'x minutes ago' from a timestamp or iso-formated string."""
24
36
 
cmem_cmemc/utils.py CHANGED
@@ -35,7 +35,7 @@ def check_python_version(ctx: type["ApplicationContext"]) -> None:
35
35
  """Check the runtime python version and warn or error."""
36
36
  version = sys.version_info
37
37
  major_expected = [3]
38
- minor_expected = [10, 11, 12, 13]
38
+ minor_expected = [13]
39
39
  if version.major not in major_expected:
40
40
  ctx.echo_error(f"Error: cmemc can not be executed with Python {version.major}.")
41
41
  sys.exit(1)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cmem-cmemc
3
- Version: 25.5.0rc1
3
+ Version: 25.6.0
4
4
  Summary: Command line client for eccenca Corporate Memory
5
5
  License: Apache-2.0
6
6
  License-File: LICENSE
@@ -34,6 +34,7 @@ Requires-Dist: click-didyoumean (>=0.3.1,<0.4.0)
34
34
  Requires-Dist: click-help-colors (>=0.9.4,<0.10.0)
35
35
  Requires-Dist: cmem-cmempy (==25.4.0)
36
36
  Requires-Dist: configparser (>=7.2.0,<8.0.0)
37
+ Requires-Dist: humanize (>=4.14.0,<5.0.0)
37
38
  Requires-Dist: jinja2 (>=3.1.6,<4.0.0)
38
39
  Requires-Dist: junit-xml (>=1.9,<2.0)
39
40
  Requires-Dist: natsort (>=8.4.0,<9.0.0)
@@ -2,39 +2,39 @@ cmem_cmemc/__init__.py,sha256=-RPEVweA-fcmEAynszDDMKwArJgxZpGW61UBiV7O4Og,24
2
2
  cmem_cmemc/_cmemc.zsh,sha256=fmkrBHIQxus8cp2AgO1tzZ5mNZdGL_83cYz3a9uAdsg,1326
3
3
  cmem_cmemc/cli.py,sha256=vDdSHFmXUstC3T7OlbPSd0hXxyigJE4VVgRMcsNz5cc,4538
4
4
  cmem_cmemc/command.py,sha256=nBtrwPKFJLRpD3IPk5hKyn2LOMl-1ae7SV9iRhgky8k,1958
5
- cmem_cmemc/command_group.py,sha256=0fG4A68Ic8NPCXj5mC-w42j6ryMPGxBBCHtLJzQHrn0,3488
5
+ cmem_cmemc/command_group.py,sha256=72_7yiBl04JumnhpHWIZ-cggdCUjj71Rm5zT-SPdv78,3543
6
6
  cmem_cmemc/commands/__init__.py,sha256=NaGM5jOzf0S_-4UIAwlVDOf2AZ3mliGPoRLXQJfTyZs,22
7
- cmem_cmemc/commands/acl.py,sha256=LlRoPT-c7AcqZBJ19XGVZZJPkCbM2A0RtlLzjMuDqIQ,18484
7
+ cmem_cmemc/commands/acl.py,sha256=vJ3H5eeWiVCtGk1ZpEF2OqXeHurZgPwakD8YT09lnVM,19030
8
8
  cmem_cmemc/commands/admin.py,sha256=F-393oXTVYV7HxK5NxuhONlBIEg7wffxE7DAKDuasG4,10192
9
9
  cmem_cmemc/commands/client.py,sha256=nBs7MoF2wF45AteTCeIQrXcOwKmHHCd8_lG_SM2mQSA,5127
10
10
  cmem_cmemc/commands/config.py,sha256=VHiVkW6NFuz-tpKXRPl7dO1gIXQLOuEhlGVxb422qwA,5803
11
- cmem_cmemc/commands/dataset.py,sha256=zylVsfBD92LzHgQ96edNKaQNsf0zKoPro0VQdX--Mu8,30488
11
+ cmem_cmemc/commands/dataset.py,sha256=vz4vqm6mSCY2ELCdEjCDUwM5YbszXjvRW2NR2ILP0aI,28891
12
+ cmem_cmemc/commands/file.py,sha256=CGut4VpSB-7A7wGMl4uP0m4xv6G--pnYO2faVI72PbM,15935
12
13
  cmem_cmemc/commands/graph.py,sha256=uAI7dEDRjwLSGl6QsJ0PsAycJf2-EZNNn_lIFliDE9Q,32662
13
14
  cmem_cmemc/commands/graph_imports.py,sha256=CYgTUSj8giHoWzz0Qjtup0H1V5GKZEI3xXdA_9w_btI,14046
14
- cmem_cmemc/commands/graph_insights.py,sha256=KDBhIVDxhmcAjI81pSEi2IG0km1SML_PeE7WTBY4moI,10810
15
+ cmem_cmemc/commands/graph_insights.py,sha256=ARgaYeKBH1b2OIKVjgOPwgAa8mVzo3CLCstcw4Z53z4,13193
15
16
  cmem_cmemc/commands/manual.py,sha256=-sZWeFL92Kj8gL3VYsbpKh2ZaVTyM3LgKaUcpNn9u3A,2179
16
17
  cmem_cmemc/commands/metrics.py,sha256=pIBRTq90f7MEI99HLdFLN3D1xQ2Z2u6VKUeTIz0X7DY,12205
17
18
  cmem_cmemc/commands/migration.py,sha256=y9v4Be7WELGjDGDBZrfLBeqU_G_JH1fnP5UVG9qjB3g,9638
18
- cmem_cmemc/commands/project.py,sha256=zHqs7XBsRjzTO6EGxaN_TgZ_rsqyIPF59aObuMhsfmA,20593
19
- cmem_cmemc/commands/python.py,sha256=7ExdKr7nTED5ZZGjeBk5UngNI3F_KNcavwUgwaxApV0,11965
19
+ cmem_cmemc/commands/project.py,sha256=H4UBlqJXjGzkL_afpn4dbFO3tpQbQa5L4XDsCyKS5RM,20669
20
+ cmem_cmemc/commands/python.py,sha256=dXFRV3pD4nRC7iTuy3dFFbdpilip-zZVuxkjyJX67Ls,11958
20
21
  cmem_cmemc/commands/query.py,sha256=Dra4BHshfec1nvVJbDvLT9VTjfesWJGSQCbyNmnnMWI,28788
21
- cmem_cmemc/commands/resource.py,sha256=74cn_yqMv3a6xOQAPpNCuluTWEH-_2PGENJnl7y8qz4,7778
22
22
  cmem_cmemc/commands/scheduler.py,sha256=zYeO1-Hlxh9D-I9JIweQ-SEA0la0wv0EicY_UY7rNCg,8751
23
23
  cmem_cmemc/commands/store.py,sha256=W_6LXq98If50-X-XYZUQsYodVwUjOSm3_jyMp62pFuA,10599
24
24
  cmem_cmemc/commands/user.py,sha256=ANZpeOBA46xiqOcNPrueComsCV0gEBbav-vOL9VgyX4,12535
25
25
  cmem_cmemc/commands/validation.py,sha256=ebolVeKpTTQ-tNjsmGWnfIlvv77lDwiWZRtd-qLbrHM,29509
26
26
  cmem_cmemc/commands/variable.py,sha256=aLRH_rFe0h7JBpKIqzcevbk26vczgUGokIDY8g6LPxA,11576
27
- cmem_cmemc/commands/vocabulary.py,sha256=fdXsG7gspA6HeOasXis1ky9UIZG-qRYP-NiFcvzCTKM,17840
27
+ cmem_cmemc/commands/vocabulary.py,sha256=erf3zqSRqCVrN_OlCZj5Z5w4L6MRwSaUmC6V11v2vHc,18095
28
28
  cmem_cmemc/commands/workflow.py,sha256=BINC-P5RsDvKTkHUbKZpzkfV5M12Cl7EPD4RLmygDOQ,25798
29
29
  cmem_cmemc/commands/workspace.py,sha256=IcZgBsvtulLRFofS70qpln6oKQIZunrVLfSAUeiFhCA,4579
30
- cmem_cmemc/completion.py,sha256=k5sqbZtjcOBcrmCoottbav9WM-dzwkL4Qyu4QUaAuRo,45306
30
+ cmem_cmemc/completion.py,sha256=mLNikJ2lHojj0El4PlhUivTOu4gPlWEdo49VoHe3cnI,46111
31
31
  cmem_cmemc/config_parser.py,sha256=NduwOT-BB_uAk3pz1Y-ex18RQJW-jjHzkQKCEUUK6Hc,1276
32
32
  cmem_cmemc/constants.py,sha256=pzZYbSaTDUiWmE-VOAHB20oivHew5_FP9UTejySsVK4,550
33
- cmem_cmemc/context.py,sha256=giSw-5BYQtf_Tw0x33JlpzoZY5wP4IMaaoSJMvYzMSc,22247
33
+ cmem_cmemc/context.py,sha256=Axk5zTbhAMPBSMPnk9jIaOQr4GvS3Ih5Jb6ZZc-c5O0,22247
34
34
  cmem_cmemc/exceptions.py,sha256=0lsGOfXhciNGJloJGERMbbPuBbs5IwIIJ_5YnY9qQ-8,546
35
35
  cmem_cmemc/manual_helper/__init__.py,sha256=G3Lqw2aPxo8x63Tg7L0aa5VD9BMaRzZDmhrog7IuEPg,43
36
- cmem_cmemc/manual_helper/graph.py,sha256=HU04NYWeJ6LmW4UC7qHr1v1qsm2Md61pJ-pgWUHFmHY,3647
37
- cmem_cmemc/manual_helper/multi_page.py,sha256=_nw5yFVo7K1e9n5qETJcbkq12ru_Fq9TH0u8EzyyRz4,12338
36
+ cmem_cmemc/manual_helper/graph.py,sha256=dTkFXgU9fgySn54rE93t79v1MjWjQkprKRIfJhc7Jps,3655
37
+ cmem_cmemc/manual_helper/multi_page.py,sha256=asJ8QdAxRBU5EXZxP7UWaWnK7JtQ3cN7f3JY4ArjX4c,12321
38
38
  cmem_cmemc/manual_helper/single_page.py,sha256=0mMn_IJwFCe-WPKAmxGEStb8IINLpQRxAx_F1pIxg1E,1526
39
39
  cmem_cmemc/migrations/__init__.py,sha256=i6Ri7qN58ou_MwOzm2KibPkXOD7u-1ELky-nUE5LjAA,24
40
40
  cmem_cmemc/migrations/abc.py,sha256=UGJzrvMzUFdp2-sosp49ObRI-SrUSzLJqLEhvB4QTzg,3564
@@ -47,15 +47,15 @@ cmem_cmemc/migrations/workspace_configurations.py,sha256=tFmCdfEL10ICjqMXQEIf-9f
47
47
  cmem_cmemc/object_list.py,sha256=PKDZ-p61ep2MzygRXAe4uzP9CB9D8esoiC3lwra84P4,14734
48
48
  cmem_cmemc/parameter_types/__init__.py,sha256=Jqhwnw5a2oPNMClzUyovWiieK60RCl3rvSNr-t3wP84,36
49
49
  cmem_cmemc/parameter_types/path.py,sha256=M56PGdjploN2pEYaNAk6_qomAX54crLW8E9XZsFvRuI,2270
50
- cmem_cmemc/placeholder.py,sha256=PzZWpVBa7WPP5_5f-HlSoaZaOnto-1Dr9XuCTSpPnNM,2386
50
+ cmem_cmemc/placeholder.py,sha256=Rf20OqwDjISnVPJsYlvuSgzeUbfJ2sklE2PWnZ5TSYg,2409
51
51
  cmem_cmemc/smart_path/__init__.py,sha256=zDgm1kDrzLyCuIcNb8VXSdnb_CcVNjGkjgiIDVlsh74,3023
52
52
  cmem_cmemc/smart_path/clients/__init__.py,sha256=YFOm69BfTCRvAcJjN_CoUmCv3kzEciyYOPUG337p_pA,1696
53
53
  cmem_cmemc/smart_path/clients/http.py,sha256=3clZu2v4uuOvPY4MY_8SVSy7hIXJDNooahFRBRpy0ok,2347
54
- cmem_cmemc/string_processor.py,sha256=0EZVl3UeVgV5EEYGLPvCGHrJDMd0Ezekkwg6mDQVBZI,3112
54
+ cmem_cmemc/string_processor.py,sha256=bwFs6BYoxX3DrUaxdqkonTC4AO99doBSmwqI05qvYyY,3494
55
55
  cmem_cmemc/title_helper.py,sha256=7frjAR54_Xc1gszOWXfzSmKFTawNJQ7kkXhZcHmQLyw,1250
56
- cmem_cmemc/utils.py,sha256=PkDFDISz7uemJCmyIWmtCcjfR_gRnRBL8ao76Ex-py8,14669
57
- cmem_cmemc-25.5.0rc1.dist-info/METADATA,sha256=NmLZwGDtjnG7MHqkSozPvI85YUTWbt1KVnLUAJdeZFQ,5718
58
- cmem_cmemc-25.5.0rc1.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
59
- cmem_cmemc-25.5.0rc1.dist-info/entry_points.txt,sha256=2G0AWAyz501EHpFTjIxccdlCTsHt80NT0pdUGP1QkPA,45
60
- cmem_cmemc-25.5.0rc1.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
61
- cmem_cmemc-25.5.0rc1.dist-info/RECORD,,
56
+ cmem_cmemc/utils.py,sha256=LlvAMHxciY9ge-REdwHQhRetJGrYghRqBZADxqE0yL4,14657
57
+ cmem_cmemc-25.6.0.dist-info/METADATA,sha256=ZUPmXLqT9akk7zF8ErvMXTr7zTgBr1h-UBSBm0V_dZQ,5757
58
+ cmem_cmemc-25.6.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
59
+ cmem_cmemc-25.6.0.dist-info/entry_points.txt,sha256=2G0AWAyz501EHpFTjIxccdlCTsHt80NT0pdUGP1QkPA,45
60
+ cmem_cmemc-25.6.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
61
+ cmem_cmemc-25.6.0.dist-info/RECORD,,