cmem-cmemc 24.2.0rc1__py3-none-any.whl → 24.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.
Files changed (51) hide show
  1. cmem_cmemc/__init__.py +7 -12
  2. cmem_cmemc/command.py +20 -0
  3. cmem_cmemc/command_group.py +70 -0
  4. cmem_cmemc/commands/__init__.py +0 -81
  5. cmem_cmemc/commands/acl.py +118 -62
  6. cmem_cmemc/commands/admin.py +46 -35
  7. cmem_cmemc/commands/client.py +2 -1
  8. cmem_cmemc/commands/config.py +3 -1
  9. cmem_cmemc/commands/dataset.py +27 -24
  10. cmem_cmemc/commands/graph.py +160 -19
  11. cmem_cmemc/commands/metrics.py +195 -79
  12. cmem_cmemc/commands/migration.py +267 -0
  13. cmem_cmemc/commands/project.py +62 -17
  14. cmem_cmemc/commands/python.py +56 -25
  15. cmem_cmemc/commands/query.py +23 -14
  16. cmem_cmemc/commands/resource.py +10 -2
  17. cmem_cmemc/commands/scheduler.py +10 -2
  18. cmem_cmemc/commands/store.py +118 -14
  19. cmem_cmemc/commands/user.py +8 -2
  20. cmem_cmemc/commands/validation.py +304 -113
  21. cmem_cmemc/commands/variable.py +10 -2
  22. cmem_cmemc/commands/vocabulary.py +48 -29
  23. cmem_cmemc/commands/workflow.py +86 -59
  24. cmem_cmemc/commands/workspace.py +27 -8
  25. cmem_cmemc/completion.py +190 -140
  26. cmem_cmemc/constants.py +2 -0
  27. cmem_cmemc/context.py +88 -42
  28. cmem_cmemc/manual_helper/graph.py +1 -0
  29. cmem_cmemc/manual_helper/multi_page.py +3 -1
  30. cmem_cmemc/migrations/__init__.py +1 -0
  31. cmem_cmemc/migrations/abc.py +84 -0
  32. cmem_cmemc/migrations/access_conditions_243.py +122 -0
  33. cmem_cmemc/migrations/bootstrap_data.py +28 -0
  34. cmem_cmemc/migrations/shapes_widget_integrations_243.py +274 -0
  35. cmem_cmemc/migrations/workspace_configurations.py +28 -0
  36. cmem_cmemc/object_list.py +53 -22
  37. cmem_cmemc/parameter_types/__init__.py +1 -0
  38. cmem_cmemc/parameter_types/path.py +69 -0
  39. cmem_cmemc/smart_path/__init__.py +94 -0
  40. cmem_cmemc/smart_path/clients/__init__.py +63 -0
  41. cmem_cmemc/smart_path/clients/http.py +65 -0
  42. cmem_cmemc/string_processor.py +83 -0
  43. cmem_cmemc/title_helper.py +41 -0
  44. cmem_cmemc/utils.py +100 -45
  45. {cmem_cmemc-24.2.0rc1.dist-info → cmem_cmemc-24.3.0.dist-info}/LICENSE +1 -1
  46. cmem_cmemc-24.3.0.dist-info/METADATA +89 -0
  47. cmem_cmemc-24.3.0.dist-info/RECORD +53 -0
  48. {cmem_cmemc-24.2.0rc1.dist-info → cmem_cmemc-24.3.0.dist-info}/WHEEL +1 -1
  49. cmem_cmemc-24.2.0rc1.dist-info/METADATA +0 -69
  50. cmem_cmemc-24.2.0rc1.dist-info/RECORD +0 -37
  51. {cmem_cmemc-24.2.0rc1.dist-info → cmem_cmemc-24.3.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,63 @@
1
+ """Client module to handle Path API calls."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABC, abstractmethod
6
+ from typing import TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
9
+ from collections.abc import Generator
10
+
11
+
12
+ class StoragePath(ABC):
13
+ """Storage path interface."""
14
+
15
+ def __init__(self, path: str):
16
+ self.path = path
17
+
18
+ @property
19
+ @abstractmethod
20
+ def suffix(self) -> str:
21
+ """Return the suffix of the path."""
22
+
23
+ @property
24
+ @abstractmethod
25
+ def name(self) -> str:
26
+ """Return the file name."""
27
+
28
+ @property
29
+ @abstractmethod
30
+ def parent(self) -> StoragePath:
31
+ """The logical parent of the path."""
32
+
33
+ @abstractmethod
34
+ def is_dir(self) -> bool:
35
+ """Check if the path is a directory."""
36
+
37
+ @abstractmethod
38
+ def is_file(self) -> bool:
39
+ """Check if the path is a file."""
40
+
41
+ @abstractmethod
42
+ def exists(self) -> bool:
43
+ """Check if the path exists."""
44
+
45
+ @abstractmethod
46
+ def mkdir(self, parents: bool = False, exist_ok: bool = False) -> None:
47
+ """Create a directory."""
48
+
49
+ @abstractmethod
50
+ def absolute(self) -> StoragePath:
51
+ """Return an absolute version of this path"""
52
+
53
+ def resolve(self) -> StoragePath:
54
+ """Resolve the resolved path of the path."""
55
+ raise NotImplementedError(f"resolve in {self.__class__} is not implemented yet.")
56
+
57
+ @abstractmethod
58
+ def glob(self, pattern: str) -> Generator[StoragePath, StoragePath, StoragePath]:
59
+ """Iterate over this subtree and yield all existing files"""
60
+
61
+ @abstractmethod
62
+ def __truediv__(self, key: str) -> StoragePath:
63
+ """Return StoragePath with appending the key to the exising path"""
@@ -0,0 +1,65 @@
1
+ """Provides functionality for interacting with http/https paths"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from cmem_cmemc.smart_path.clients import StoragePath
8
+
9
+ if TYPE_CHECKING:
10
+ from collections.abc import Generator
11
+
12
+
13
+ class HttpPath(StoragePath):
14
+ """Client class for interacting with Amazon S3 storage.
15
+
16
+ This class provides methods for working with http paths within the context
17
+ of the `Path` application.
18
+ """
19
+
20
+ @property
21
+ def suffix(self) -> str:
22
+ """Return the suffix of the path."""
23
+ raise NotImplementedError(f"suffix in {self.__class__} is not implemented yet.")
24
+
25
+ @property
26
+ def name(self) -> str:
27
+ """Determine the name of the path."""
28
+ return self.path.split("/")[-1]
29
+
30
+ @property
31
+ def parent(self) -> HttpPath:
32
+ """The logical parent of the path."""
33
+ raise NotImplementedError(f"parent in {self.__class__} is not implemented yet.")
34
+
35
+ def is_dir(self) -> bool:
36
+ """Determine if path is a directory or not."""
37
+ return False
38
+
39
+ def is_file(self) -> bool:
40
+ """Determine if path is a file or not."""
41
+ return not self.is_dir()
42
+
43
+ def exists(self) -> bool:
44
+ """Return the suffix of the path."""
45
+ raise NotImplementedError(f"exists in {self.__class__} is not implemented yet.")
46
+
47
+ def mkdir(self, parents: bool = False, exist_ok: bool = False) -> None:
48
+ """Return the suffix of the path."""
49
+ raise NotImplementedError(f"mkdir in {self.__class__} is not implemented yet.")
50
+
51
+ def absolute(self) -> HttpPath:
52
+ """Return an absolute version of this path"""
53
+ raise NotImplementedError(f"absolute in {self.__class__} is not implemented yet.")
54
+
55
+ def resolve(self) -> HttpPath:
56
+ """Resolve the resolved path of the path."""
57
+ raise NotImplementedError(f"resolve in {self.__class__} is not implemented yet.")
58
+
59
+ def glob(self, pattern: str) -> Generator[StoragePath, StoragePath, StoragePath]:
60
+ """Iterate over this subtree and yield all existing files"""
61
+ raise NotImplementedError(f"glob in {self.__class__} is not implemented yet.")
62
+
63
+ def __truediv__(self, key: str) -> HttpPath:
64
+ """Return path with appending the key"""
65
+ raise NotImplementedError(f"__truediv__ in {self.__class__} is not implemented yet.")
@@ -0,0 +1,83 @@
1
+ """Helper functions for rich text output"""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from datetime import datetime, timezone
5
+ from urllib.parse import quote
6
+
7
+ import timeago
8
+ from cmem.cmempy.config import get_cmem_base_uri
9
+
10
+ from cmem_cmemc.title_helper import TitleHelper
11
+ from cmem_cmemc.utils import get_graphs_as_dict
12
+
13
+
14
+ class StringProcessor(ABC):
15
+ """ABC of a table cell string processor"""
16
+
17
+ @abstractmethod
18
+ def process(self, text: str) -> str:
19
+ """Process a single string content and output the processed string."""
20
+
21
+
22
+ class TimeAgo(StringProcessor):
23
+ """Create a string similar to 'x minutes ago' from a timestamp"""
24
+
25
+ def process(self, text: str) -> str:
26
+ """Process a single string content and output the processed string."""
27
+ if text is None:
28
+ return ""
29
+ try:
30
+ text_as_int = int(text)
31
+ except ValueError:
32
+ return text
33
+ stamp = datetime.fromtimestamp(text_as_int / 1000, tz=timezone.utc)
34
+ return str(timeago.format(stamp, datetime.now(tz=timezone.utc)))
35
+
36
+
37
+ class GraphLink(StringProcessor):
38
+ """Create a graph link from an IRI cell
39
+
40
+ "Visit my [link=https://www.willmcgugan.com]blog[/link]!"
41
+ """
42
+
43
+ def __init__(self) -> None:
44
+ self.cmem_base_uri = get_cmem_base_uri()
45
+ self.base = self.cmem_base_uri + "/explore?graph="
46
+ self.graph_labels: dict[str, str] = {}
47
+ for _ in get_graphs_as_dict().values():
48
+ self.graph_labels[_["iri"]] = _["label"]["title"]
49
+
50
+ def process(self, text: str) -> str:
51
+ """Process a single string content and output the processed string."""
52
+ link = self.base + quote(text)
53
+ label = self.graph_labels.get(text, None)
54
+ return f"[link={link}]{label}[/link]" if label else text
55
+
56
+
57
+ class ResourceLink(StringProcessor):
58
+ """Create a resource link from an IRI cell
59
+
60
+ "Visit my [link=https://www.willmcgugan.com]blog[/link]!"
61
+ """
62
+
63
+ def __init__(self, graph_iri: str, title_helper: TitleHelper | None = None):
64
+ self.graph_iri = graph_iri
65
+ self.base = get_cmem_base_uri() + "/explore?graph=" + quote(graph_iri) + "&resource="
66
+ self.title_helper = title_helper if title_helper else TitleHelper()
67
+
68
+ def process(self, text: str) -> str:
69
+ """Process a single string content and output the processed string."""
70
+ link = self.base + quote(text)
71
+ label = self.title_helper.get(text)
72
+ return f"[link={link}]{label}[/link]"
73
+
74
+
75
+ def process_row(row: list[str], hints: dict[int, StringProcessor]) -> list[str]:
76
+ """Process all cells in a row according to the StringProcessors"""
77
+ processed_row = []
78
+ for column_number, cell in enumerate(row):
79
+ if hints.get(column_number):
80
+ processed_row.append(hints[column_number].process(cell))
81
+ else:
82
+ processed_row.append(cell)
83
+ return processed_row
@@ -0,0 +1,41 @@
1
+ """Title helper functions."""
2
+
3
+ import json
4
+
5
+ from cmem.cmempy.api import get_json
6
+ from cmem.cmempy.config import get_dp_api_endpoint
7
+
8
+
9
+ class TitleHelper:
10
+ """Title helper class."""
11
+
12
+ fetched_labels: dict[str, dict]
13
+
14
+ def __init__(self) -> None:
15
+ self.fetched_labels = {}
16
+ self.endpoint = f"{get_dp_api_endpoint()}/api/explore/titles"
17
+
18
+ def get(self, iri: str | list[str]) -> str | dict[str, str]:
19
+ """Get the title of an IRI (or list of IRI)."""
20
+ output = {}
21
+ iris = [iri] if isinstance(iri, str) else list(set(iri))
22
+
23
+ iris_to_fetch = []
24
+ for _ in iris:
25
+ if _ in self.fetched_labels:
26
+ output[_] = self.fetched_labels[_]["title"]
27
+ else:
28
+ iris_to_fetch.append(_)
29
+
30
+ if len(iris_to_fetch) > 0:
31
+ titles: dict = get_json(
32
+ self.endpoint,
33
+ method="POST",
34
+ data=json.dumps(iris_to_fetch),
35
+ headers={"Content-type": "application/json"},
36
+ )
37
+ for title in titles.values():
38
+ self.fetched_labels[title["iri"]] = title
39
+ output[title["iri"]] = title["title"]
40
+
41
+ return output[iri] if isinstance(iri, str) else output
cmem_cmemc/utils.py CHANGED
@@ -1,27 +1,47 @@
1
1
  """Utility functions for CLI interface."""
2
+
2
3
  import json
3
4
  import os
5
+ import pathlib
4
6
  import re
7
+ import sys
5
8
  import unicodedata
6
9
  from dataclasses import dataclass
7
- from importlib.metadata import version
8
- from pathlib import Path
10
+ from importlib.metadata import version as cmemc_version
11
+ from typing import TYPE_CHECKING
12
+ from zipfile import BadZipFile, ZipFile
9
13
 
10
14
  import requests
11
- from bs4 import BeautifulSoup
12
- from cmem.cmempy.dp.admin import get_prometheus_data
13
15
  from cmem.cmempy.dp.proxy.graph import get_graphs_list
16
+ from cmem.cmempy.queries import QueryCatalog
14
17
  from cmem.cmempy.workspace.projects.project import get_projects
15
18
  from prometheus_client import Metric
16
- from prometheus_client.parser import text_string_to_metric_families
17
19
 
18
20
  from cmem_cmemc.constants import NAMESPACES
19
- from cmem_cmemc.context import ApplicationContext
21
+
22
+ if TYPE_CHECKING:
23
+ from cmem_cmemc.context import ApplicationContext
24
+ from cmem_cmemc.smart_path import SmartPath as Path
20
25
 
21
26
 
22
27
  def get_version() -> str:
23
28
  """Get the current version or SNAPSHOT."""
24
- return version("cmem-cmemc")
29
+ return cmemc_version("cmem-cmemc")
30
+
31
+
32
+ def check_python_version(ctx: "ApplicationContext") -> None:
33
+ """Check the runtime python version and warn or error."""
34
+ version = sys.version_info
35
+ major_expected = [3]
36
+ minor_expected = [10, 11, 12]
37
+ if version.major not in major_expected:
38
+ ctx.echo_error(f"Error: cmemc can not be executed with Python {version.major}.")
39
+ sys.exit(1)
40
+ if version.minor not in minor_expected and not ctx.is_completing():
41
+ ctx.echo_warning(
42
+ "Warning: You are running cmemc under a non-tested python "
43
+ f"environment ({version.major}.{version.minor})."
44
+ )
25
45
 
26
46
 
27
47
  def extract_error_message(error: Exception) -> str:
@@ -68,10 +88,16 @@ def read_rdf_graph_files(directory_path: str) -> list[tuple[str, str]]:
68
88
  rdf_graphs = []
69
89
  for root, _, files in os.walk(directory_path):
70
90
  for _file in files:
91
+ if _file.endswith(".graph"):
92
+ continue
71
93
  full_file_path = Path(root) / _file
72
- graph_file_name = _file + ".graph"
94
+ # Handle compressed files (like .gz)
95
+ if _file.endswith(".gz"):
96
+ graph_file_name = _file.replace(".gz", ".graph")
97
+ else:
98
+ graph_file_name = f"{_file}.graph"
73
99
  full_graph_file_name_path = Path(root) / graph_file_name
74
- if not _file.endswith(".graph") and Path(full_graph_file_name_path).exists():
100
+ if full_graph_file_name_path.exists():
75
101
  graph_name = read_file_to_string(str(full_graph_file_name_path)).strip()
76
102
  rdf_graphs.append((str(full_file_path.resolve()), graph_name))
77
103
  return rdf_graphs
@@ -80,7 +106,7 @@ def read_rdf_graph_files(directory_path: str) -> list[tuple[str, str]]:
80
106
  def read_file_to_string(file_path: str) -> str:
81
107
  """Read file to string."""
82
108
  with Path(file_path).open(mode="rb") as _file:
83
- return _file.read().decode("utf-8")
109
+ return str(_file.read().decode("utf-8"))
84
110
 
85
111
 
86
112
  def get_graphs(writeable: bool = True, readonly: bool = True) -> list:
@@ -211,28 +237,7 @@ def metric_get_labels(family: Metric, clean: bool = True) -> dict[str, list[str]
211
237
  return labels
212
238
 
213
239
 
214
- def metrics_get_dict(job_id: str = "DP") -> dict[str, Metric]:
215
- """Get metrics data as dict."""
216
- data = {}
217
- if job_id == "DP":
218
- for family in text_string_to_metric_families(get_prometheus_data().text):
219
- data[family.name] = family
220
- return data
221
- raise ValueError(f"job name {job_id} unknown.")
222
-
223
-
224
- def metrics_get_family(job_id: str, metric_id: str) -> Metric:
225
- """Get family data.
226
-
227
- This function returns a dictionary of metric families.
228
- """
229
- try:
230
- return metrics_get_dict(job_id=job_id)[metric_id]
231
- except KeyError as error:
232
- raise ValueError(f"The job {job_id} does not have a metric {metric_id}.") from error
233
-
234
-
235
- def check_or_select_project(app: ApplicationContext, project_id: str | None = None) -> str:
240
+ def check_or_select_project(app: "ApplicationContext", project_id: str | None = None) -> str:
236
241
  """Check for given project, select the first one if there is only one.
237
242
 
238
243
  Args:
@@ -281,28 +286,31 @@ class PublishedPackage:
281
286
 
282
287
  name: str
283
288
  description: str
284
- version: str
285
289
  published: str
290
+ version: str
286
291
 
287
292
 
288
293
  def get_published_packages() -> list[PublishedPackage]:
289
294
  """Get a scraped list of plugin packages scraped from pypi.org."""
290
- url = "https://pypi.org/search/?q=%22cmem-plugin-%22"
291
- soup = BeautifulSoup(requests.get(url, timeout=5).content, "html.parser")
295
+ url = "https://download.eccenca.com/cmem-plugin-index/cmem-plugin-index.json"
296
+ package_names = []
292
297
  packages = []
293
- snippets = soup.find_all("a", class_="package-snippet")
294
- for _ in snippets:
295
- name = _.findChildren(class_="package-snippet__name")[0].getText()
298
+ for _ in requests.get(url, timeout=5).json():
299
+ name = _["name"]
296
300
  if name == "cmem-plugin-base":
297
301
  continue
298
- description = _.findChildren(class_="package-snippet__description")[0].getText()
299
- package_version = _.findChildren(class_="package-snippet__version")[0].getText()
300
- published = _.findChildren(name="time")[0].attrs["datetime"]
301
- packages.append(
302
- PublishedPackage(
303
- name=name, description=description, version=package_version, published=published
302
+ if not name.startswith("cmem-plugin-"):
303
+ continue
304
+ if name not in package_names:
305
+ package_names.append(name)
306
+ packages.append(
307
+ PublishedPackage(
308
+ name=name,
309
+ description=_["summary"],
310
+ published=_["latest_version_time"],
311
+ version=_["latest_version"],
312
+ )
304
313
  )
305
- )
306
314
  return packages
307
315
 
308
316
 
@@ -325,3 +333,50 @@ def convert_qname_to_iri(qname: str, default_ns: str) -> str:
325
333
  return default_ns + qname[1:]
326
334
 
327
335
  return qname
336
+
337
+
338
+ def get_query_text(file_or_uri: str, required_projections: set) -> str:
339
+ """Get query text for a file or graph catalog URI.
340
+
341
+ Args:
342
+ ----
343
+ file_or_uri (str): The file path or URI to fetch the query from.
344
+ required_projections (set): A set of required projections.
345
+
346
+ Returns:
347
+ -------
348
+ str: The query text.
349
+
350
+ Raises:
351
+ ------
352
+ ValueError: If the input is not a readable file or a query URI,
353
+ or if the query contains placeholders,
354
+ or if the query does not include the required projections.
355
+
356
+ """
357
+ sparql_query = QueryCatalog().get_query(file_or_uri)
358
+ if sparql_query is None:
359
+ raise ValueError(f"{file_or_uri} is neither a readable file nor a query URI.")
360
+
361
+ if sparql_query.get_placeholder_keys():
362
+ raise ValueError("Placeholder queries are not supported.")
363
+
364
+ result = sparql_query.get_json_results()
365
+ projected_vars = set(result["head"]["vars"])
366
+
367
+ missing_projections = required_projections - projected_vars
368
+ if missing_projections:
369
+ missing = ", ".join(missing_projections)
370
+ raise ValueError(f"Select query must include projections for: {missing}")
371
+ txt: str = sparql_query.text
372
+ return txt
373
+
374
+
375
+ def validate_zipfile(zipfile: str | pathlib.Path) -> bool:
376
+ """Validate a zipfile."""
377
+ zipfile = pathlib.Path(zipfile)
378
+ try:
379
+ ZipFile(zipfile).testzip()
380
+ except BadZipFile:
381
+ return False
382
+ return True
@@ -199,4 +199,4 @@
199
199
  distributed under the License is distributed on an "AS IS" BASIS,
200
200
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
201
  See the License for the specific language governing permissions and
202
- limitations under the License.
202
+ limitations under the License.
@@ -0,0 +1,89 @@
1
+ Metadata-Version: 2.1
2
+ Name: cmem-cmemc
3
+ Version: 24.3.0
4
+ Summary: Command line client for eccenca Corporate Memory
5
+ Home-page: https://eccenca.com/go/cmemc
6
+ License: Apache-2.0
7
+ Author: eccenca
8
+ Author-email: cmempy-developer@eccenca.com
9
+ Requires-Python: >=3.10,<4.0
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Customer Service
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Information Technology
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: Intended Audience :: System Administrators
17
+ Classifier: License :: OSI Approved :: Apache Software License
18
+ Classifier: Natural Language :: English
19
+ Classifier: Operating System :: OS Independent
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
+ Classifier: Programming Language :: Python :: 3 :: Only
25
+ Classifier: Topic :: Database
26
+ Classifier: Topic :: Software Development :: Testing
27
+ Classifier: Topic :: Utilities
28
+ Requires-Dist: beautifulsoup4 (>=4.12.3,<5.0.0)
29
+ Requires-Dist: certifi (>=2024.2.2)
30
+ Requires-Dist: click (>=8.1.7,<9.0.0)
31
+ Requires-Dist: click-didyoumean (>=0.3.1,<0.4.0)
32
+ Requires-Dist: click-help-colors (>=0.9.4,<0.10.0)
33
+ Requires-Dist: cmem-cmempy (==24.3.0)
34
+ Requires-Dist: configparser (>=6.0.1,<7.0.0)
35
+ Requires-Dist: jinja2 (>=3.1.4,<4.0.0)
36
+ Requires-Dist: junit-xml (>=1.9,<2.0)
37
+ Requires-Dist: natsort (>=8.4.0,<9.0.0)
38
+ Requires-Dist: packaging (>=24.2,<25.0)
39
+ Requires-Dist: pip (>=24.3.1,<25.0.0)
40
+ Requires-Dist: prometheus-client (>=0.21.0,<0.22.0)
41
+ Requires-Dist: pygments (>=2.18.0,<3.0.0)
42
+ Requires-Dist: pyjwt (>=2.9.0,<3.0.0)
43
+ Requires-Dist: python-dateutil (>=2.9.0.post0,<3.0.0)
44
+ Requires-Dist: requests (>=2.32.3,<3.0.0)
45
+ Requires-Dist: rich (>=13.9.1,<14.0.0)
46
+ Requires-Dist: six (>=1.16.0,<2.0.0)
47
+ Requires-Dist: smart-open (>=7.0.5,<8.0.0)
48
+ Requires-Dist: timeago (>=1.0.16,<2.0.0)
49
+ Requires-Dist: treelib (>=1.7.0,<2.0.0)
50
+ Requires-Dist: urllib3 (>=2.2.3,<3.0.0)
51
+ Description-Content-Type: text/markdown
52
+
53
+ # cmemc
54
+
55
+ cmemc is the official command line client for [eccenca Corporate Memory](https://documentation.eccenca.com/).
56
+
57
+ [![version][version-shield]][changelog] [![Python Versions][python-shield]][pypi] [![eccenca Corporate Memory][cmem-shield]][cmem]
58
+
59
+ [![teaser][teaser-image]][cmemc-docu]
60
+
61
+ ## Installation
62
+
63
+ In order to install the cmemc, run:
64
+
65
+ pipx install cmem-cmemc
66
+
67
+ Of course you can install cmemc also with pip, but we recommend [pipx](https://pypa.github.io/pipx/) for normal desktop usage.
68
+
69
+ ## Configuration and Usage
70
+
71
+ cmemc is intended for System Administrators and Linked Data Expert, who wants to automate and remote control activities on eccenca Corporate Memory.
72
+
73
+ The cmemc manual including basic usage pattern, configuration as well as a command reference is available at:
74
+
75
+ [https://eccenca.com/go/cmemc](https://eccenca.com/go/cmemc)
76
+
77
+ cmemc only works with Python 3 and refuses to work with Python 2.x.
78
+ In addition to that, cmemc will warn you in case an untested Python environment is used.
79
+
80
+
81
+ [version-shield]: https://badge.fury.io/py/cmem-cmemc.svg
82
+ [changelog]: https://pypi.org/project/cmem-cmemc/#history
83
+ [python-shield]: https://img.shields.io/pypi/pyversions/cmem-cmemc.svg
84
+ [pypi]: https://pypi.org/project/cmem-cmemc/
85
+ [cmem]: https://documentation.eccenca.com
86
+ [cmem-shield]: https://img.shields.io/badge/made%20for-eccenca%20Corporate%20Memory-blue?logo=data:image/svg%2bxml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgaWQ9IkxheWVyXzEiCiAgIGRhdGEtbmFtZT0iTGF5ZXIgMSIKICAgdmlld0JveD0iMCAwIDgxLjI5MDAwMSA4Mi4yODk4NiIKICAgdmVyc2lvbj0iMS4xIgogICB3aWR0aD0iODEuMjkwMDAxIgogICBoZWlnaHQ9IjgyLjI4OTg2NCIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8ZGVmcwogICAgIGlkPSJkZWZzODI2Ij4KICAgIDxzdHlsZQogICAgICAgaWQ9InN0eWxlODI0Ij4KICAgICAgLmNscy0xIHsKICAgICAgICBmaWxsOiAjZjM5MjAwOwogICAgICB9CgogICAgICAuY2xzLTIgewogICAgICAgIGZpbGw6IG5vbmU7CiAgICAgICAgc3Ryb2tlOiAjZjM5MjAwOwogICAgICAgIHN0cm9rZS13aWR0aDogMS41cHg7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgPC9kZWZzPgogIDxnCiAgICAgaWQ9Imc4NDAiCiAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTMwLjc2LC0zMS4xNDAxMzkpIj4KICAgIDxwYXRoCiAgICAgICBjbGFzcz0iY2xzLTEiCiAgICAgICBkPSJNIDU1LjksODUuMTkgQSAyMC4xNCwyMC4xNCAwIDEgMCAzNS43Niw2NS4wNSAyMC4xNCwyMC4xNCAwIDAgMCA1NS45LDg1LjE5IFoiCiAgICAgICBpZD0icGF0aDgyOCIgLz4KICAgIDxwYXRoCiAgICAgICBjbGFzcz0iY2xzLTEiCiAgICAgICBkPSJtIDk4LDU0LjE0IGEgOSw5IDAgMSAwIC04Ljk1LC05IDguOTUsOC45NSAwIDAgMCA4Ljk1LDkgeiIKICAgICAgIGlkPSJwYXRoODMwIiAvPgogICAgPHBhdGgKICAgICAgIGNsYXNzPSJjbHMtMSIKICAgICAgIGQ9Ik0gODguMzUsMTA4LjQzIEEgMTIuMzEsMTIuMzEgMCAxIDAgNzYsOTYuMTIgMTIuMzEsMTIuMzEgMCAwIDAgODguMzEsMTA4LjQzIFoiCiAgICAgICBpZD0icGF0aDgzMiIgLz4KICAgIDxsaW5lCiAgICAgICBjbGFzcz0iY2xzLTIiCiAgICAgICB4MT0iODYuOTcwMDAxIgogICAgICAgeTE9IjkyLjA1OTk5OCIKICAgICAgIHgyPSI1OC43Nzk5OTkiCiAgICAgICB5Mj0iNjcuMzYwMDAxIgogICAgICAgaWQ9ImxpbmU4MzQiIC8+CiAgICA8bGluZQogICAgICAgY2xhc3M9ImNscy0yIgogICAgICAgeDE9Ijk5LjE4IgogICAgICAgeTE9IjQ1Ljg0IgogICAgICAgeDI9IjU1LjQ4IgogICAgICAgeTI9IjY2LjEyMDAwMyIKICAgICAgIGlkPSJsaW5lODM2IiAvPgogICAgPGxpbmUKICAgICAgIGNsYXNzPSJjbHMtMiIKICAgICAgIHgxPSI5Ny45ODk5OTgiCiAgICAgICB5MT0iNDQuNjUwMDAyIgogICAgICAgeDI9Ijg4LjM0OTk5OCIKICAgICAgIHkyPSI5Mi44Mzk5OTYiCiAgICAgICBpZD0ibGluZTgzOCIgLz4KICA8L2c+Cjwvc3ZnPgo=
87
+ [teaser-image]: https://documentation.eccenca.com/24.1/automate/cmemc-command-line-interface/configuration/completion-setup/22.1-cmemc-create-dataset.gif
88
+ [cmemc-docu]: https://eccenca.com/go/cmemc
89
+
@@ -0,0 +1,53 @@
1
+ cmem_cmemc/__init__.py,sha256=JP__el3oR8pNwDH4_D3mu6jE6ChdeBDnIgu5Vf_VHUg,5516
2
+ cmem_cmemc/_cmemc.zsh,sha256=fmkrBHIQxus8cp2AgO1tzZ5mNZdGL_83cYz3a9uAdsg,1326
3
+ cmem_cmemc/command.py,sha256=OsrQOqHpMjiucdpyac8xzxBaSWehYIA7xkO651z8iTs,702
4
+ cmem_cmemc/command_group.py,sha256=0ltd8xY0yN6_ARR2kkwx7Hibj4glT9M0_bnjKlMhz8g,3371
5
+ cmem_cmemc/commands/__init__.py,sha256=NaGM5jOzf0S_-4UIAwlVDOf2AZ3mliGPoRLXQJfTyZs,22
6
+ cmem_cmemc/commands/acl.py,sha256=zy27D_GeEaYEn3P6EEHtQ55sbWkjgwTgspCeuBWPL14,16006
7
+ cmem_cmemc/commands/admin.py,sha256=IrhWPNJxdE5GO9UumsI8lv9PqFc_0oUh72FmQIKdl9c,9192
8
+ cmem_cmemc/commands/client.py,sha256=gKh22OvYDi95q2U4czH4UPYyDRw_Ngk2K0frDywl3B4,5074
9
+ cmem_cmemc/commands/config.py,sha256=j0zc9EFWW7Kw9bzSb_r16m-A0LZersKbZ9j5GE0mGcQ,5734
10
+ cmem_cmemc/commands/dataset.py,sha256=xsIUiC2aMDiESjnZvjDY5Mh2fvSooOijQmo5A7RvY40,30397
11
+ cmem_cmemc/commands/graph.py,sha256=x0HFeyIRjRPaCqB78esJVDD1WaN3IAHXj2gtXonbEnM,32461
12
+ cmem_cmemc/commands/metrics.py,sha256=zsyHezoYwig6jIdn7g9NZMqt9DxG6dQRoO0vP3Eu0Rc,12176
13
+ cmem_cmemc/commands/migration.py,sha256=IubT8uA-cyn5frofUcknMWEBLMPeEoKMb0xNbJRXNn4,9412
14
+ cmem_cmemc/commands/project.py,sha256=1BwAAZvupImqkW3Q3dXdkMir8-wnWRMsewnOyVui3QY,20545
15
+ cmem_cmemc/commands/python.py,sha256=80k-z6AcL1Ibp5CqVHATjvK0t9wv24QsepDB0vrfDFU,10760
16
+ cmem_cmemc/commands/query.py,sha256=KUck4w7PD0e5JYg5Eykf2QAMBfpwmgskEiIA-X_PqZs,26973
17
+ cmem_cmemc/commands/resource.py,sha256=ZC_0PM0YnEB3Xf4i0siaTKxHT0Ixjz2Y3QTXuVPuhRE,7739
18
+ cmem_cmemc/commands/scheduler.py,sha256=yKVGcewhrXFfSKEOlvEFwaIfWCOK1x7VLhm-i-SSw68,8729
19
+ cmem_cmemc/commands/store.py,sha256=hChKTSQ4zAQZ3bFforyYBW9d2CAitInKGW2WSaE8m7c,10411
20
+ cmem_cmemc/commands/user.py,sha256=9ju6Q4SdqQncbnFRmN0O27Md9KDl8SD-4SXsBDNw4oA,12464
21
+ cmem_cmemc/commands/validation.py,sha256=Iji13uzhWlyjKlEuEJCDIb-XPwCTO_wzThI1PB--8D8,29485
22
+ cmem_cmemc/commands/variable.py,sha256=DSNxiS5PlexzNdYz9f0-O0hxqakbsxv847U7I9UhKUI,11568
23
+ cmem_cmemc/commands/vocabulary.py,sha256=pAxRM-Y9lzmZ0dT3NBpBGKa2nlbY0thWBdfN2FESXQ8,17767
24
+ cmem_cmemc/commands/workflow.py,sha256=vQiaH3L1ZXjf5SuorwdjrD6nF_5nxZ9Xkd7eCNGwxk8,25512
25
+ cmem_cmemc/commands/workspace.py,sha256=IcZgBsvtulLRFofS70qpln6oKQIZunrVLfSAUeiFhCA,4579
26
+ cmem_cmemc/completion.py,sha256=zgT0ihoePxl8RdmH-9wn1LBAYbeD3Scj9-nX0FcFBTY,44330
27
+ cmem_cmemc/constants.py,sha256=VKNF5-6fzZXWzPgm1OAGhWvyL4YE8SRq52kkpJjUgB0,475
28
+ cmem_cmemc/context.py,sha256=JJUCfhLT9xKQScSXL1a7US9YE6c1fpFKLLjE7kxCieM,19482
29
+ cmem_cmemc/exceptions.py,sha256=SpUHdmVM8cZpSjBv6ICgr9NLr3OJ5XO42DlvjohprVo,232
30
+ cmem_cmemc/manual_helper/__init__.py,sha256=G3Lqw2aPxo8x63Tg7L0aa5VD9BMaRzZDmhrog7IuEPg,43
31
+ cmem_cmemc/manual_helper/graph.py,sha256=qDchHdjRRDW2oZ66by81NxhoDgNxXaAUxq2keewEiVU,3598
32
+ cmem_cmemc/manual_helper/multi_page.py,sha256=5nvN1P7zTgzrnuoT7yA7abyT7EOVa24Jvp3Q2xZmXro,12236
33
+ cmem_cmemc/manual_helper/single_page.py,sha256=sVSeaZmPa-Cs6dtp27MqyiO6rIrskY9BtDyeAZhBWXM,1477
34
+ cmem_cmemc/migrations/__init__.py,sha256=i6Ri7qN58ou_MwOzm2KibPkXOD7u-1ELky-nUE5LjAA,24
35
+ cmem_cmemc/migrations/abc.py,sha256=Q4G0tRiFruawm6qvvSC_02FkDTC-Jav4VP2Suoy5hzE,3512
36
+ cmem_cmemc/migrations/access_conditions_243.py,sha256=IXcvSuo9pLaTTo4XNBB6_ln-2TzOV5PU5ugti0BWbxA,5083
37
+ cmem_cmemc/migrations/bootstrap_data.py,sha256=RF0vyFTGUQ_RcpTTWZmm3XLAJAJX2gSYcGwcBmRmU8A,963
38
+ cmem_cmemc/migrations/shapes_widget_integrations_243.py,sha256=jRWtTFeMom-xUI7UaoTa3DoLmmdwFmaw9XYxvnelxMo,8746
39
+ cmem_cmemc/migrations/workspace_configurations.py,sha256=tFmCdfEL10ICjqMXQEIf-9fveE41HBQ_jaWNQJENz50,998
40
+ cmem_cmemc/object_list.py,sha256=NYArisZxCV4pws_Tgk_xyltLN6TStkxQAgy-WLlzOxc,14712
41
+ cmem_cmemc/parameter_types/__init__.py,sha256=Jqhwnw5a2oPNMClzUyovWiieK60RCl3rvSNr-t3wP84,36
42
+ cmem_cmemc/parameter_types/path.py,sha256=hrisrXA1V8AtlWv-zxMYFGyf3RBCet4CW-Z2yTmhcwg,2002
43
+ cmem_cmemc/smart_path/__init__.py,sha256=zDgm1kDrzLyCuIcNb8VXSdnb_CcVNjGkjgiIDVlsh74,3023
44
+ cmem_cmemc/smart_path/clients/__init__.py,sha256=YFOm69BfTCRvAcJjN_CoUmCv3kzEciyYOPUG337p_pA,1696
45
+ cmem_cmemc/smart_path/clients/http.py,sha256=3clZu2v4uuOvPY4MY_8SVSy7hIXJDNooahFRBRpy0ok,2347
46
+ cmem_cmemc/string_processor.py,sha256=kSVePdgFmf2ekurKj6TbDJn6ur82VGLwCsTJ9ODfBEU,2879
47
+ cmem_cmemc/title_helper.py,sha256=7frjAR54_Xc1gszOWXfzSmKFTawNJQ7kkXhZcHmQLyw,1250
48
+ cmem_cmemc/utils.py,sha256=fQ_9ysEhzqvAWevbrDwZvH0eJioLOKJ3dVFVaJrzxu4,12213
49
+ cmem_cmemc-24.3.0.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
50
+ cmem_cmemc-24.3.0.dist-info/METADATA,sha256=CgWDke8W4hSEW8Fk-seTCwo3TIpYjGxEj2EUxR-eu94,5616
51
+ cmem_cmemc-24.3.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
52
+ cmem_cmemc-24.3.0.dist-info/entry_points.txt,sha256=znWUTG-zgDITu6Frsd-OtNxBxj6Uo8Fa7bz6gaZYMrA,41
53
+ cmem_cmemc-24.3.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.8.1
2
+ Generator: poetry-core 1.9.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any