cmem-cmemc 25.5.0rc1__py3-none-any.whl → 26.1.0rc1__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/cli.py +11 -6
  2. cmem_cmemc/command.py +1 -1
  3. cmem_cmemc/command_group.py +59 -31
  4. cmem_cmemc/commands/acl.py +403 -26
  5. cmem_cmemc/commands/admin.py +10 -10
  6. cmem_cmemc/commands/client.py +12 -5
  7. cmem_cmemc/commands/config.py +106 -12
  8. cmem_cmemc/commands/dataset.py +163 -172
  9. cmem_cmemc/commands/file.py +509 -0
  10. cmem_cmemc/commands/graph.py +200 -72
  11. cmem_cmemc/commands/graph_imports.py +12 -5
  12. cmem_cmemc/commands/graph_insights.py +157 -53
  13. cmem_cmemc/commands/metrics.py +15 -9
  14. cmem_cmemc/commands/migration.py +12 -4
  15. cmem_cmemc/commands/package.py +548 -0
  16. cmem_cmemc/commands/project.py +157 -22
  17. cmem_cmemc/commands/python.py +9 -5
  18. cmem_cmemc/commands/query.py +119 -25
  19. cmem_cmemc/commands/scheduler.py +6 -4
  20. cmem_cmemc/commands/store.py +2 -1
  21. cmem_cmemc/commands/user.py +124 -24
  22. cmem_cmemc/commands/validation.py +15 -10
  23. cmem_cmemc/commands/variable.py +264 -61
  24. cmem_cmemc/commands/vocabulary.py +31 -17
  25. cmem_cmemc/commands/workflow.py +21 -11
  26. cmem_cmemc/completion.py +126 -109
  27. cmem_cmemc/context.py +40 -10
  28. cmem_cmemc/exceptions.py +8 -2
  29. cmem_cmemc/manual_helper/graph.py +2 -2
  30. cmem_cmemc/manual_helper/multi_page.py +5 -7
  31. cmem_cmemc/object_list.py +234 -7
  32. cmem_cmemc/placeholder.py +2 -2
  33. cmem_cmemc/string_processor.py +153 -4
  34. cmem_cmemc/title_helper.py +50 -0
  35. cmem_cmemc/utils.py +9 -8
  36. {cmem_cmemc-25.5.0rc1.dist-info → cmem_cmemc-26.1.0rc1.dist-info}/METADATA +7 -6
  37. cmem_cmemc-26.1.0rc1.dist-info/RECORD +62 -0
  38. {cmem_cmemc-25.5.0rc1.dist-info → cmem_cmemc-26.1.0rc1.dist-info}/WHEEL +1 -1
  39. cmem_cmemc/commands/resource.py +0 -220
  40. cmem_cmemc-25.5.0rc1.dist-info/RECORD +0 -61
  41. {cmem_cmemc-25.5.0rc1.dist-info → cmem_cmemc-26.1.0rc1.dist-info}/entry_points.txt +0 -0
  42. {cmem_cmemc-25.5.0rc1.dist-info → cmem_cmemc-26.1.0rc1.dist-info}/licenses/LICENSE +0 -0
@@ -4,8 +4,11 @@ from abc import ABC, abstractmethod
4
4
  from datetime import datetime, timezone
5
5
  from urllib.parse import quote
6
6
 
7
- import timeago
8
- from cmem.cmempy.config import get_cmem_base_uri
7
+ from cmem.cmempy.config import get_cmem_base_uri, get_di_api_endpoint
8
+ from cmem.cmempy.workflow.workflow import get_workflow_editor_uri
9
+ from cmem.cmempy.workspace import get_task_plugins
10
+ from cmem.cmempy.workspace.search import list_items
11
+ from humanize import naturalsize, naturaltime
9
12
 
10
13
  from cmem_cmemc.title_helper import TitleHelper
11
14
  from cmem_cmemc.utils import get_graphs_as_dict
@@ -19,6 +22,17 @@ class StringProcessor(ABC):
19
22
  """Process a single string content and output the processed string."""
20
23
 
21
24
 
25
+ class FileSize(StringProcessor):
26
+ """Create a human-readable file size string."""
27
+
28
+ def process(self, text: str) -> str:
29
+ """Process a single string content and output the processed string."""
30
+ try:
31
+ return "" if text is None else naturalsize(value=text, gnu=True)
32
+ except ValueError:
33
+ return text
34
+
35
+
22
36
  class TimeAgo(StringProcessor):
23
37
  """Create a string similar to 'x minutes ago' from a timestamp or iso-formated string."""
24
38
 
@@ -28,13 +42,13 @@ class TimeAgo(StringProcessor):
28
42
  return ""
29
43
  try:
30
44
  stamp = datetime.fromisoformat(str(text))
31
- return str(timeago.format(stamp, datetime.now(tz=timezone.utc)))
45
+ return str(naturaltime(stamp, when=datetime.now(tz=timezone.utc)))
32
46
  except (ValueError, TypeError):
33
47
  pass
34
48
  try:
35
49
  text_as_int = int(text)
36
50
  stamp = datetime.fromtimestamp(text_as_int / 1000, tz=timezone.utc)
37
- return str(timeago.format(stamp, datetime.now(tz=timezone.utc)))
51
+ return str(naturaltime(stamp, when=datetime.now(tz=timezone.utc)))
38
52
  except ValueError:
39
53
  return text
40
54
 
@@ -59,6 +73,28 @@ class GraphLink(StringProcessor):
59
73
  return f"[link={link}]{label}[/link]" if label else text
60
74
 
61
75
 
76
+ class QueryLink(StringProcessor):
77
+ """Create a query link from a query IRI cell
78
+
79
+ "Visit my [link=https://www.willmcgugan.com]blog[/link]!"
80
+ """
81
+
82
+ def __init__(self, catalog_graph: str, queries: dict):
83
+ self.catalog_graph = catalog_graph
84
+ self.queries = queries
85
+
86
+ def process(self, text: str) -> str:
87
+ """Process a single string content and output the processed string."""
88
+ # Find the query entry from the queries dict
89
+ query_entry = self.queries.get(text)
90
+ if query_entry:
91
+ # Use the SparqlQuery's get_editor_url method for consistent URL generation
92
+ link = query_entry.get_editor_url(graph=self.catalog_graph)
93
+ label = query_entry.label
94
+ return f"[link={link}]{label}[/link]"
95
+ return text
96
+
97
+
62
98
  class ResourceLink(StringProcessor):
63
99
  """Create a resource link from an IRI cell
64
100
 
@@ -77,6 +113,119 @@ class ResourceLink(StringProcessor):
77
113
  return f"[link={link}]{label}[/link]"
78
114
 
79
115
 
116
+ class ProjectLink(StringProcessor):
117
+ """Create a project link from a project ID cell
118
+
119
+ "Visit my [link=https://www.willmcgugan.com]blog[/link]!"
120
+ """
121
+
122
+ def __init__(self, projects: dict):
123
+ self.projects = projects
124
+ self.base = get_di_api_endpoint() + "/workbench/projects/"
125
+
126
+ def process(self, text: str) -> str:
127
+ """Process a single string content and output the processed string."""
128
+ project = self.projects.get(text)
129
+ if project:
130
+ link = self.base + text
131
+ label = project["metaData"].get("label", text)
132
+ return f"[link={link}]{label}[/link]"
133
+ return text
134
+
135
+
136
+ class WorkflowLink(StringProcessor):
137
+ """Create a workflow link from a workflow ID cell
138
+
139
+ "Visit my [link=https://www.willmcgugan.com]blog[/link]!"
140
+ """
141
+
142
+ def __init__(self, workflows: dict):
143
+ self.workflows = workflows
144
+ self.base_uri = get_workflow_editor_uri()
145
+
146
+ def process(self, text: str) -> str:
147
+ """Process a single string content and output the processed string."""
148
+ workflow = self.workflows.get(text)
149
+ if workflow:
150
+ project_id, task_id = text.split(":")
151
+ link = self.base_uri.format(project_id, task_id)
152
+ label = workflow["label"]
153
+ return f"[link={link}]{label}[/link]"
154
+ return text
155
+
156
+
157
+ class DatasetTypeLink(StringProcessor):
158
+ """Create a documentation link from a dataset type (pluginId) cell
159
+
160
+ Links to the Corporate Memory dataset documentation.
161
+ Example: "json" -> "[link=https://documentation.eccenca.com/latest/build/reference/dataset/json/]JSON[/link]"
162
+ """
163
+
164
+ def __init__(
165
+ self, base_url: str = "https://documentation.eccenca.com/latest/build/reference/dataset/"
166
+ ):
167
+ self.base_url = base_url
168
+ self.type_labels: dict[str, str] = {}
169
+ # Dataset types that don't have documentation pages
170
+ self.undocumented_types = {"variableDataset"}
171
+ # Import here to avoid circular imports
172
+
173
+ plugins = get_task_plugins()
174
+ for plugin_id, plugin in plugins.items():
175
+ if plugin["taskType"] == "Dataset":
176
+ self.type_labels[plugin_id] = plugin["title"]
177
+
178
+ def process(self, text: str) -> str:
179
+ """Process a dataset type and create a documentation link if available."""
180
+ if not text:
181
+ return text
182
+
183
+ # Use the title if available, otherwise fall back to the text
184
+ label = self.type_labels.get(text, text)
185
+
186
+ # Only create link if this type is not in the undocumented list
187
+ if text not in self.undocumented_types:
188
+ link = f"{self.base_url}{text}/"
189
+ return f"[link={link}]{label}[/link]"
190
+
191
+ return label
192
+
193
+
194
+ class DatasetLink(StringProcessor):
195
+ """Create a workspace link from a dataset ID cell
196
+
197
+ Links to the Corporate Memory workspace dataset page and displays the dataset label.
198
+ Example: "project:dataset" ->
199
+ "[link=https://cmem.example.com/workspaces/datasets/...]Dataset Label[/link]"
200
+ """
201
+
202
+ def __init__(self) -> None:
203
+ self.cmem_base_uri = get_cmem_base_uri()
204
+ self.dataset_urls: dict[str, str] = {}
205
+ self.dataset_labels: dict[str, str] = {}
206
+ # Build a mapping of dataset_id -> URL path and label
207
+ datasets = list_items(item_type="dataset")["results"]
208
+ for dataset in datasets:
209
+ dataset_id = dataset["projectId"] + ":" + dataset["id"]
210
+ url_path = dataset["itemLinks"][0]["path"]
211
+ self.dataset_urls[dataset_id] = url_path
212
+ self.dataset_labels[dataset_id] = dataset["label"]
213
+
214
+ def process(self, text: str) -> str:
215
+ """Process a dataset ID and create a workspace link with label if available."""
216
+ if not text:
217
+ return text
218
+
219
+ # Check if we have a URL for this dataset ID
220
+ if text in self.dataset_urls:
221
+ full_url = self.cmem_base_uri + self.dataset_urls[text]
222
+ label = self.dataset_labels.get(text, text)
223
+ return f"[link={full_url}]{label}[/link]"
224
+
225
+ # If no URL found, return the text as-is
226
+ return text
227
+
228
+
80
229
  def process_row(row: list[str], hints: dict[int, StringProcessor]) -> list[str]:
81
230
  """Process all cells in a row according to the StringProcessors"""
82
231
  processed_row = []
@@ -1,9 +1,12 @@
1
1
  """Title helper functions."""
2
2
 
3
3
  import json
4
+ from typing import ClassVar
4
5
 
5
6
  from cmem.cmempy.api import get_json
6
7
  from cmem.cmempy.config import get_dp_api_endpoint
8
+ from cmem.cmempy.workspace import get_task_plugins
9
+ from cmem.cmempy.workspace.projects.project import get_projects
7
10
 
8
11
 
9
12
  class TitleHelper:
@@ -39,3 +42,50 @@ class TitleHelper:
39
42
  output[title["iri"]] = title["title"]
40
43
 
41
44
  return output[iri] if isinstance(iri, str) else output
45
+
46
+
47
+ class ProjectTitleHelper(TitleHelper):
48
+ """Title helper for project IDs with class-level caching."""
49
+
50
+ _labels_cache: ClassVar[dict[str, str]] = {}
51
+ _cache_initialized: ClassVar[bool] = False
52
+
53
+ def get(self, project_id: str | list[str]) -> str | dict[str, str]:
54
+ """Get the label of a project (or list of projects)."""
55
+ # Fetch all project labels once at class level
56
+ if not ProjectTitleHelper._cache_initialized:
57
+ projects = get_projects()
58
+ for project in projects:
59
+ ProjectTitleHelper._labels_cache[project["name"]] = project["metaData"].get(
60
+ "label", ""
61
+ )
62
+ ProjectTitleHelper._cache_initialized = True
63
+
64
+ # Build and return output
65
+ if isinstance(project_id, str):
66
+ return ProjectTitleHelper._labels_cache.get(project_id, "")
67
+ return {pid: ProjectTitleHelper._labels_cache.get(pid, "") for pid in project_id}
68
+
69
+
70
+ class DatasetTypeTitleHelper(TitleHelper):
71
+ """Title helper for dataset types with class-level caching."""
72
+
73
+ _labels_cache: ClassVar[dict[str, str]] = {}
74
+ _cache_initialized: ClassVar[bool] = False
75
+
76
+ def get(self, plugin_id: str | list[str]) -> str | dict[str, str]:
77
+ """Get the description of a dataset type (or list of types)."""
78
+ # Fetch all plugin descriptions once at class level
79
+ if not DatasetTypeTitleHelper._cache_initialized:
80
+ plugins = get_task_plugins()
81
+ for pid, plugin in plugins.items():
82
+ if plugin["taskType"] == "Dataset":
83
+ title = plugin["title"]
84
+ description = plugin["description"].partition("\n")[0]
85
+ DatasetTypeTitleHelper._labels_cache[pid] = f"{title}: {description}"
86
+ DatasetTypeTitleHelper._cache_initialized = True
87
+
88
+ # Build and return output
89
+ if isinstance(plugin_id, str):
90
+ return DatasetTypeTitleHelper._labels_cache.get(plugin_id, plugin_id)
91
+ return {pid: DatasetTypeTitleHelper._labels_cache.get(pid, pid) for pid in plugin_id}
cmem_cmemc/utils.py CHANGED
@@ -12,7 +12,7 @@ from typing import TYPE_CHECKING
12
12
  from zipfile import BadZipFile, ZipFile
13
13
 
14
14
  import requests
15
- from click import Argument, ClickException
15
+ from click import Argument
16
16
  from cmem.cmempy.dp.proxy.graph import get_graphs_list
17
17
  from cmem.cmempy.queries import QueryCatalog
18
18
  from cmem.cmempy.workspace.projects.project import get_projects
@@ -20,6 +20,7 @@ from prometheus_client import Metric
20
20
 
21
21
  from cmem_cmemc.config_parser import PureSectionConfigParser
22
22
  from cmem_cmemc.constants import NAMESPACES
23
+ from cmem_cmemc.exceptions import CmemcError
23
24
  from cmem_cmemc.smart_path import SmartPath
24
25
 
25
26
  if TYPE_CHECKING:
@@ -35,7 +36,7 @@ def check_python_version(ctx: type["ApplicationContext"]) -> None:
35
36
  """Check the runtime python version and warn or error."""
36
37
  version = sys.version_info
37
38
  major_expected = [3]
38
- minor_expected = [10, 11, 12, 13]
39
+ minor_expected = [13]
39
40
  if version.major not in major_expected:
40
41
  ctx.echo_error(f"Error: cmemc can not be executed with Python {version.major}.")
41
42
  sys.exit(1)
@@ -246,7 +247,7 @@ def split_task_id(task_id: str) -> tuple[str, str]:
246
247
  project_part = task_id.split(":")[0]
247
248
  task_part = task_id.split(":")[1]
248
249
  except IndexError as error:
249
- raise ClickException(f"{task_id} is not a valid task ID.") from error
250
+ raise CmemcError(f"{task_id} is not a valid task ID.") from error
250
251
  return project_part, task_part
251
252
 
252
253
 
@@ -303,13 +304,13 @@ def check_or_select_project(app: "ApplicationContext", project_id: str | None =
303
304
  return project_name
304
305
 
305
306
  if len(projects) == 0:
306
- raise ClickException(
307
+ raise CmemcError(
307
308
  "There are no projects available. "
308
309
  "Please create a project with 'cmemc project create'."
309
310
  )
310
311
 
311
312
  # more than one project
312
- raise ClickException(
313
+ raise CmemcError(
313
314
  "There is more than one project available so you need to "
314
315
  "specify the project with '--project'."
315
316
  )
@@ -391,10 +392,10 @@ def get_query_text(file_or_uri: str, required_projections: set) -> str:
391
392
  """
392
393
  sparql_query = QueryCatalog().get_query(file_or_uri)
393
394
  if sparql_query is None:
394
- raise ClickException(f"{file_or_uri} is neither a readable file nor a query URI.")
395
+ raise CmemcError(f"{file_or_uri} is neither a readable file nor a query URI.")
395
396
 
396
397
  if sparql_query.get_placeholder_keys():
397
- raise ClickException("Placeholder queries are not supported.")
398
+ raise CmemcError("Placeholder queries are not supported.")
398
399
 
399
400
  result = sparql_query.get_json_results()
400
401
  projected_vars = set(result["head"]["vars"])
@@ -402,7 +403,7 @@ def get_query_text(file_or_uri: str, required_projections: set) -> str:
402
403
  missing_projections = required_projections - projected_vars
403
404
  if missing_projections:
404
405
  missing = ", ".join(missing_projections)
405
- raise ClickException(f"Select query must include projections for: {missing}")
406
+ raise CmemcError(f"Select query must include projections for: {missing}")
406
407
  txt: str = sparql_query.text
407
408
  return txt
408
409
 
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cmem-cmemc
3
- Version: 25.5.0rc1
3
+ Version: 26.1.0rc1
4
4
  Summary: Command line client for eccenca Corporate Memory
5
5
  License: Apache-2.0
6
6
  License-File: LICENSE
7
7
  Author: eccenca
8
8
  Author-email: cmempy-developer@eccenca.com
9
- Requires-Python: >=3.10,<4.0
9
+ Requires-Python: >=3.13,<4
10
10
  Classifier: Development Status :: 5 - Production/Stable
11
11
  Classifier: Environment :: Console
12
12
  Classifier: Intended Audience :: Customer Service
@@ -18,12 +18,12 @@ Classifier: License :: OSI Approved :: Apache Software License
18
18
  Classifier: Natural Language :: English
19
19
  Classifier: Operating System :: OS Independent
20
20
  Classifier: Programming Language :: Python :: 3
21
- Classifier: Programming Language :: Python :: 3.10
22
- Classifier: Programming Language :: Python :: 3.11
23
- Classifier: Programming Language :: Python :: 3.12
24
21
  Classifier: Programming Language :: Python :: 3.13
25
22
  Classifier: Programming Language :: Python :: 3.14
26
23
  Classifier: Programming Language :: Python :: 3 :: Only
24
+ Classifier: Programming Language :: Python :: 3.10
25
+ Classifier: Programming Language :: Python :: 3.11
26
+ Classifier: Programming Language :: Python :: 3.12
27
27
  Classifier: Topic :: Database
28
28
  Classifier: Topic :: Software Development :: Testing
29
29
  Classifier: Topic :: Utilities
@@ -32,8 +32,10 @@ Requires-Dist: certifi (>=2024.2.2)
32
32
  Requires-Dist: click (>=8.3.0,<9.0.0)
33
33
  Requires-Dist: click-didyoumean (>=0.3.1,<0.4.0)
34
34
  Requires-Dist: click-help-colors (>=0.9.4,<0.10.0)
35
+ Requires-Dist: cmem-client (>=0.5.0,<0.6.0)
35
36
  Requires-Dist: cmem-cmempy (==25.4.0)
36
37
  Requires-Dist: configparser (>=7.2.0,<8.0.0)
38
+ Requires-Dist: humanize (>=4.14.0,<5.0.0)
37
39
  Requires-Dist: jinja2 (>=3.1.6,<4.0.0)
38
40
  Requires-Dist: junit-xml (>=1.9,<2.0)
39
41
  Requires-Dist: natsort (>=8.4.0,<9.0.0)
@@ -46,7 +48,6 @@ Requires-Dist: requests (>=2.32.3,<3.0.0)
46
48
  Requires-Dist: rich (>=14.0.0,<15.0.0)
47
49
  Requires-Dist: six (>=1.17.0,<2.0.0)
48
50
  Requires-Dist: smart-open (>=7.1.0,<8.0.0)
49
- Requires-Dist: timeago (>=1.0.16,<2.0.0)
50
51
  Requires-Dist: treelib (>=1.7.1,<2.0.0)
51
52
  Requires-Dist: urllib3 (>=2.3.0,<3.0.0)
52
53
  Project-URL: Homepage, https://eccenca.com/go/cmemc
@@ -0,0 +1,62 @@
1
+ cmem_cmemc/__init__.py,sha256=-RPEVweA-fcmEAynszDDMKwArJgxZpGW61UBiV7O4Og,24
2
+ cmem_cmemc/_cmemc.zsh,sha256=fmkrBHIQxus8cp2AgO1tzZ5mNZdGL_83cYz3a9uAdsg,1326
3
+ cmem_cmemc/cli.py,sha256=VGfHqSIPPTvYpPHrjwbnLYLB6O0u8kwiJDXeXMlI1xU,4855
4
+ cmem_cmemc/command.py,sha256=CuaskaqD12soZLhDP1prgXOT4cRFu1CzuJm6LBp4zLM,1949
5
+ cmem_cmemc/command_group.py,sha256=0I2Jg1lCdoozcMg7d0g9sk0zVJ8_LiKvWWwqM6tZGI0,4793
6
+ cmem_cmemc/commands/__init__.py,sha256=NaGM5jOzf0S_-4UIAwlVDOf2AZ3mliGPoRLXQJfTyZs,22
7
+ cmem_cmemc/commands/acl.py,sha256=IHLEmrbz3r9SVEsaEWxpGVRjoT0oXWhYJ4d3y-_402Y,31421
8
+ cmem_cmemc/commands/admin.py,sha256=XyJVk9CuxLh60GdWNtMkGPpZ3BOftDF4T04M8gRaOQ0,10193
9
+ cmem_cmemc/commands/client.py,sha256=4Ke9Pl6-KQ-6_uSpRbVhB7spAi8tP0r39y99fwFRSWE,5304
10
+ cmem_cmemc/commands/config.py,sha256=tRB7YK6vzkaasmyZVWGLOw1_x0SM2x-coYMBRIJwEJs,8392
11
+ cmem_cmemc/commands/dataset.py,sha256=OCRBcDFVBBpap4bnjSIOfrWgFNRU9m0V_QlSobvObkY,30472
12
+ cmem_cmemc/commands/file.py,sha256=FBmi0D8bZs5NoKcOWEYIuaJM0x9kWQAaIQ2IyL8sFiE,17038
13
+ cmem_cmemc/commands/graph.py,sha256=HzddrKcoxq0w2xWMIhVft5p5Oacq0TSrmhebLngJdMs,36623
14
+ cmem_cmemc/commands/graph_imports.py,sha256=VcOisHSvNYJPxMiRb6sHEswYIQHwlIamLwQD1jgr_e0,14368
15
+ cmem_cmemc/commands/graph_insights.py,sha256=sx-Lvv4FWK9-eB8X4R5fDU63lrErAhc8W3a2fEQAjDI,14473
16
+ cmem_cmemc/commands/manual.py,sha256=-sZWeFL92Kj8gL3VYsbpKh2ZaVTyM3LgKaUcpNn9u3A,2179
17
+ cmem_cmemc/commands/metrics.py,sha256=t5I6VjBzjp_bQEoGkU9cdqNu_sa_WQwiIeJA3f9KNWc,12385
18
+ cmem_cmemc/commands/migration.py,sha256=FibmYpvZD2mrutjyRBhs7xDAZ-sjPiH9Kn3CI-zUPm0,9861
19
+ cmem_cmemc/commands/package.py,sha256=mJ9vDj34YdGySGK_Nh7Yl3O2tyuT-FhV8p4taRNLU_c,17762
20
+ cmem_cmemc/commands/project.py,sha256=ovn9zre7L0EzthMITQHGF9dffiP44UQUWNjG8Is6l8k,24938
21
+ cmem_cmemc/commands/python.py,sha256=lcbBAYZN5NB37HLSmVPs0SXJV7Ey4xVMYQiSiuyGkvc,12225
22
+ cmem_cmemc/commands/query.py,sha256=1cj1QbvwL98YbBGSCO0Zazbzscts_kiv0A7k75KwJXw,32231
23
+ cmem_cmemc/commands/scheduler.py,sha256=3wk3BF6Z3uRb0e5pphOYBusbXgs7C6Lz-D9wi7Nlohc,8855
24
+ cmem_cmemc/commands/store.py,sha256=zKz8FTtVSvFU6gMm6An7Jja9Bu9dZKbI1GW7UCq034s,10655
25
+ cmem_cmemc/commands/user.py,sha256=F0JSRkrj274Hi0i4nBIUkWFm-ItC40VwuqvLYETJBdM,15844
26
+ cmem_cmemc/commands/validation.py,sha256=v9_cXGzaexemuz6xBA358XY1_vP42SBfOD3PEZLcqbw,29731
27
+ cmem_cmemc/commands/variable.py,sha256=ZcvrQDb0CztWGkFIX-jnvqBTLp3qpQEZ7ZpBmKV16SI,18841
28
+ cmem_cmemc/commands/vocabulary.py,sha256=sv7hDZOeRPrPlc5RJfpAKzKH5JRyKjB93D-Jl4eLNqI,18359
29
+ cmem_cmemc/commands/workflow.py,sha256=UdKAsY3chxfrIpkjL1K9MqLVyu7jTdFYkOXfGVvJISI,26369
30
+ cmem_cmemc/commands/workspace.py,sha256=IcZgBsvtulLRFofS70qpln6oKQIZunrVLfSAUeiFhCA,4579
31
+ cmem_cmemc/completion.py,sha256=JbMZmTLjgu_nrIS9NuuFHqfqAFwHE1dCvFNk0g2c6d0,44805
32
+ cmem_cmemc/config_parser.py,sha256=NduwOT-BB_uAk3pz1Y-ex18RQJW-jjHzkQKCEUUK6Hc,1276
33
+ cmem_cmemc/constants.py,sha256=pzZYbSaTDUiWmE-VOAHB20oivHew5_FP9UTejySsVK4,550
34
+ cmem_cmemc/context.py,sha256=oCcd6dFl6BdYqKsueVqzQhSEwTNW7b1MjrE4CRznxt8,23220
35
+ cmem_cmemc/exceptions.py,sha256=c4Z6CKgymu0a7gD8MtHxzK_7WCsb9I2Zl-EgEkwu-YY,760
36
+ cmem_cmemc/manual_helper/__init__.py,sha256=G3Lqw2aPxo8x63Tg7L0aa5VD9BMaRzZDmhrog7IuEPg,43
37
+ cmem_cmemc/manual_helper/graph.py,sha256=dTkFXgU9fgySn54rE93t79v1MjWjQkprKRIfJhc7Jps,3655
38
+ cmem_cmemc/manual_helper/multi_page.py,sha256=I1gTCDETlCli2k-G7Mkdpw_MCqey60HxFl35wTmvYFU,12279
39
+ cmem_cmemc/manual_helper/single_page.py,sha256=0mMn_IJwFCe-WPKAmxGEStb8IINLpQRxAx_F1pIxg1E,1526
40
+ cmem_cmemc/migrations/__init__.py,sha256=i6Ri7qN58ou_MwOzm2KibPkXOD7u-1ELky-nUE5LjAA,24
41
+ cmem_cmemc/migrations/abc.py,sha256=UGJzrvMzUFdp2-sosp49ObRI-SrUSzLJqLEhvB4QTzg,3564
42
+ cmem_cmemc/migrations/access_conditions_243.py,sha256=IXcvSuo9pLaTTo4XNBB6_ln-2TzOV5PU5ugti0BWbxA,5083
43
+ cmem_cmemc/migrations/bootstrap_data.py,sha256=RF0vyFTGUQ_RcpTTWZmm3XLAJAJX2gSYcGwcBmRmU8A,963
44
+ cmem_cmemc/migrations/remove_noop_triple_251.py,sha256=392FZV5ipUMeqqc2QJWfupFeNRR4ceKPmyak17I5xVk,1451
45
+ cmem_cmemc/migrations/shapes_widget_integrations_243.py,sha256=8lQTOlEJvlrDvdvKkl5OAjnyx1jMRgQnU6Bk_fEORYQ,8543
46
+ cmem_cmemc/migrations/sparql_query_texts_242.py,sha256=K_GbxaX5-kkQKDZMq8UvT1vHazde53htwdDHGyB0b9s,1568
47
+ cmem_cmemc/migrations/workspace_configurations.py,sha256=tFmCdfEL10ICjqMXQEIf-9fveE41HBQ_jaWNQJENz50,998
48
+ cmem_cmemc/object_list.py,sha256=RII_nhRIRThzVdrtABELXW-Li-O6x970C69Vy2T3ruE,23364
49
+ cmem_cmemc/parameter_types/__init__.py,sha256=Jqhwnw5a2oPNMClzUyovWiieK60RCl3rvSNr-t3wP84,36
50
+ cmem_cmemc/parameter_types/path.py,sha256=M56PGdjploN2pEYaNAk6_qomAX54crLW8E9XZsFvRuI,2270
51
+ cmem_cmemc/placeholder.py,sha256=Rf20OqwDjISnVPJsYlvuSgzeUbfJ2sklE2PWnZ5TSYg,2409
52
+ cmem_cmemc/smart_path/__init__.py,sha256=zDgm1kDrzLyCuIcNb8VXSdnb_CcVNjGkjgiIDVlsh74,3023
53
+ cmem_cmemc/smart_path/clients/__init__.py,sha256=YFOm69BfTCRvAcJjN_CoUmCv3kzEciyYOPUG337p_pA,1696
54
+ cmem_cmemc/smart_path/clients/http.py,sha256=3clZu2v4uuOvPY4MY_8SVSy7hIXJDNooahFRBRpy0ok,2347
55
+ cmem_cmemc/string_processor.py,sha256=19YSLUF9PIbfTmsTm2bZslsNhFUAYx0MerWYwC3BVEo,8616
56
+ cmem_cmemc/title_helper.py,sha256=8Cyes2U4lHTQbzYwBSYqCrZbq29_oBg6uibe7xZ6DEg,3486
57
+ cmem_cmemc/utils.py,sha256=jmXjbEQ2MDIz031A81kivGnir7HG5XUkLbOd1OCoV6s,14662
58
+ cmem_cmemc-26.1.0rc1.dist-info/METADATA,sha256=-8H1iGcueln3Y31sViwBpY-g2hBleljk2Vm-YSsRL2Y,5761
59
+ cmem_cmemc-26.1.0rc1.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
60
+ cmem_cmemc-26.1.0rc1.dist-info/entry_points.txt,sha256=2G0AWAyz501EHpFTjIxccdlCTsHt80NT0pdUGP1QkPA,45
61
+ cmem_cmemc-26.1.0rc1.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
62
+ cmem_cmemc-26.1.0rc1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.2.1
2
+ Generator: poetry-core 2.3.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,220 +0,0 @@
1
- """Build dataset resource commands for cmemc."""
2
-
3
- import re
4
-
5
- import click
6
- from click import ClickException, UsageError
7
- from cmem.cmempy.workspace.projects.resources import get_all_resources
8
- from cmem.cmempy.workspace.projects.resources.resource import (
9
- delete_resource,
10
- get_resource_metadata,
11
- get_resource_usage_data,
12
- )
13
-
14
- from cmem_cmemc import completion
15
- from cmem_cmemc.command import CmemcCommand
16
- from cmem_cmemc.command_group import CmemcGroup
17
- from cmem_cmemc.context import ApplicationContext
18
- from cmem_cmemc.utils import split_task_id, struct_to_table
19
-
20
- RESOURCE_FILTER_TYPES = ["project", "regex"]
21
- RESOURCE_FILTER_TYPES_HIDDEN = ["ids"]
22
- RESOURCE_FILTER_TEXT = (
23
- "Filter file resources based on metadata. "
24
- f"First parameter CHOICE can be one of {RESOURCE_FILTER_TYPES!s}"
25
- ". The second parameter is based on CHOICE, e.g. a project "
26
- "ID or a regular expression string."
27
- )
28
-
29
-
30
- def _get_resources_filtered(
31
- resources: list[dict], filter_name: str, filter_value: str | tuple[str, ...]
32
- ) -> list[dict]:
33
- """Get file resources but filtered according to name and value."""
34
- # check for correct filter names (filter ids is used internally only)
35
- if filter_name not in RESOURCE_FILTER_TYPES + RESOURCE_FILTER_TYPES_HIDDEN:
36
- raise UsageError(
37
- f"{filter_name} is an unknown filter name. " f"Use one of {RESOURCE_FILTER_TYPES}."
38
- )
39
- # filter by ID list
40
- if filter_name == "ids":
41
- return [_ for _ in resources if _["id"] in filter_value]
42
- # filter by project
43
- if filter_name == "project":
44
- return [_ for _ in resources if _["project"] == str(filter_value)]
45
- # filter by regex
46
- if filter_name == "regex":
47
- return [_ for _ in resources if re.search(str(filter_value), _["name"])]
48
- # return unfiltered list
49
- return resources
50
-
51
-
52
- @click.command(cls=CmemcCommand, name="list")
53
- @click.option("--raw", is_flag=True, help="Outputs raw JSON.")
54
- @click.option(
55
- "--id-only",
56
- is_flag=True,
57
- help="Lists only resource names and no other metadata. "
58
- "This is useful for piping the IDs into other commands.",
59
- )
60
- @click.option(
61
- "--filter",
62
- "filters_",
63
- multiple=True,
64
- type=(str, str),
65
- shell_complete=completion.resource_list_filter,
66
- help=RESOURCE_FILTER_TEXT,
67
- )
68
- @click.pass_obj
69
- def list_command(
70
- app: ApplicationContext, raw: bool, id_only: bool, filters_: tuple[tuple[str, str], ...]
71
- ) -> None:
72
- """List available file resources.
73
-
74
- Outputs a table or a list of dataset resources (files).
75
- """
76
- resources = get_all_resources()
77
- for _ in filters_:
78
- filter_name, filter_value = _
79
- resources = _get_resources_filtered(resources, filter_name, filter_value)
80
- if raw:
81
- app.echo_info_json(resources)
82
- return
83
- if id_only:
84
- for _ in sorted(_["id"] for _ in resources):
85
- app.echo_result(_)
86
- return
87
- # output a user table
88
- table = []
89
- headers = ["ID", "Modified", "Size"]
90
- for _ in resources:
91
- row = [
92
- _["id"],
93
- _["modified"],
94
- _["size"],
95
- ]
96
- table.append(row)
97
- app.echo_info_table(
98
- table,
99
- headers=headers,
100
- sort_column=0,
101
- empty_table_message="No dataset resources found. "
102
- "Use the `dataset create` command to create a new file based dataset.",
103
- )
104
-
105
-
106
- @click.command(cls=CmemcCommand, name="delete")
107
- @click.argument("resource_ids", nargs=-1, type=click.STRING, shell_complete=completion.resource_ids)
108
- @click.option("--force", is_flag=True, help="Delete resource even if in use by a task.")
109
- @click.option(
110
- "-a",
111
- "--all",
112
- "all_",
113
- is_flag=True,
114
- help="Delete all resources. " "This is a dangerous option, so use it with care.",
115
- )
116
- @click.option(
117
- "--filter",
118
- "filters_",
119
- multiple=True,
120
- type=(str, str),
121
- shell_complete=completion.resource_list_filter,
122
- help=RESOURCE_FILTER_TEXT,
123
- )
124
- @click.pass_obj
125
- def delete_command(
126
- app: ApplicationContext,
127
- resource_ids: tuple[str, ...],
128
- force: bool,
129
- all_: bool,
130
- filters_: tuple[tuple[str, str], ...],
131
- ) -> None:
132
- """Delete file resources.
133
-
134
- There are three selection mechanisms: with specific IDs, only those
135
- specified resources will be deleted; by using --filter, resources based
136
- on the filter type and value will be deleted; using --all will delete
137
- all resources.
138
- """
139
- if resource_ids == () and not all_ and filters_ == ():
140
- raise UsageError(
141
- "Either specify at least one resource ID or use the --all or "
142
- "--filter options to specify resources for deletion."
143
- )
144
-
145
- resources = get_all_resources()
146
- if len(resource_ids) > 0:
147
- for resource_id in resource_ids:
148
- if resource_id not in [_["id"] for _ in resources]:
149
- raise ClickException(f"Resource {resource_id} not available.")
150
- # "filter" by id
151
- resources = _get_resources_filtered(resources, "ids", resource_ids)
152
- for _ in filters_:
153
- resources = _get_resources_filtered(resources, _[0], _[1])
154
-
155
- # avoid double removal as well as sort IDs
156
- processed_ids = sorted({_["id"] for _ in resources}, key=lambda v: v.lower())
157
- count = len(processed_ids)
158
- for current, resource_id in enumerate(processed_ids, start=1):
159
- current_string = str(current).zfill(len(str(count)))
160
- app.echo_info(f"Delete resource {current_string}/{count}: {resource_id} ... ", nl=False)
161
- project_id, resource_local_id = split_task_id(resource_id)
162
- usage = get_resource_usage_data(project_id, resource_local_id)
163
- if len(usage) > 0:
164
- app.echo_error(f"in use by {len(usage)} task(s)", nl=False)
165
- if force:
166
- app.echo_info(" ... ", nl=False)
167
- else:
168
- app.echo_info("")
169
- continue
170
- delete_resource(project_name=project_id, resource_name=resource_local_id)
171
- app.echo_success("deleted")
172
-
173
-
174
- @click.command(cls=CmemcCommand, name="inspect")
175
- @click.argument("resource_id", type=click.STRING, shell_complete=completion.resource_ids)
176
- @click.option("--raw", is_flag=True, help="Outputs raw JSON.")
177
- @click.pass_obj
178
- def inspect_command(app: ApplicationContext, resource_id: str, raw: bool) -> None:
179
- """Display all metadata of a file resource."""
180
- project_id, resource_id = split_task_id(resource_id)
181
- resource_data = get_resource_metadata(project_id, resource_id)
182
- if raw:
183
- app.echo_info_json(resource_data)
184
- else:
185
- table = struct_to_table(resource_data)
186
- app.echo_info_table(table, headers=["Key", "Value"], sort_column=0)
187
-
188
-
189
- @click.command(cls=CmemcCommand, name="usage")
190
- @click.argument("resource_id", type=click.STRING, shell_complete=completion.resource_ids)
191
- @click.option("--raw", is_flag=True, help="Outputs raw JSON.")
192
- @click.pass_obj
193
- def usage_command(app: ApplicationContext, resource_id: str, raw: bool) -> None:
194
- """Display all usage data of a file resource."""
195
- project_id, resource_id = split_task_id(resource_id)
196
- usage = get_resource_usage_data(project_id, resource_id)
197
- if raw:
198
- app.echo_info_json(usage)
199
- return
200
- # output a user table
201
- table = []
202
- headers = ["Task ID", "Type", "Label"]
203
- for _ in usage:
204
- row = [project_id + ":" + _["id"], _["taskType"], _["label"]]
205
- table.append(row)
206
- app.echo_info_table(table, headers=headers, sort_column=2)
207
-
208
-
209
- @click.group(cls=CmemcGroup)
210
- def resource() -> CmemcGroup: # type: ignore[empty-body]
211
- """List, inspect or delete dataset file resources.
212
-
213
- File resources are identified by their paths and project IDs.
214
- """
215
-
216
-
217
- resource.add_command(list_command)
218
- resource.add_command(delete_command)
219
- resource.add_command(inspect_command)
220
- resource.add_command(usage_command)