cmem-cmemc 24.2.0rc2__py3-none-any.whl → 24.3.0rc2__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.
- cmem_cmemc/__init__.py +7 -12
- cmem_cmemc/command.py +20 -0
- cmem_cmemc/command_group.py +70 -0
- cmem_cmemc/commands/__init__.py +0 -81
- cmem_cmemc/commands/acl.py +118 -62
- cmem_cmemc/commands/admin.py +46 -35
- cmem_cmemc/commands/client.py +2 -1
- cmem_cmemc/commands/config.py +3 -1
- cmem_cmemc/commands/dataset.py +27 -24
- cmem_cmemc/commands/graph.py +160 -19
- cmem_cmemc/commands/metrics.py +195 -79
- cmem_cmemc/commands/migration.py +267 -0
- cmem_cmemc/commands/project.py +62 -17
- cmem_cmemc/commands/python.py +56 -25
- cmem_cmemc/commands/query.py +23 -14
- cmem_cmemc/commands/resource.py +10 -2
- cmem_cmemc/commands/scheduler.py +10 -2
- cmem_cmemc/commands/store.py +118 -14
- cmem_cmemc/commands/user.py +8 -2
- cmem_cmemc/commands/validation.py +165 -78
- cmem_cmemc/commands/variable.py +10 -2
- cmem_cmemc/commands/vocabulary.py +48 -29
- cmem_cmemc/commands/workflow.py +86 -59
- cmem_cmemc/commands/workspace.py +27 -8
- cmem_cmemc/completion.py +190 -140
- cmem_cmemc/constants.py +2 -0
- cmem_cmemc/context.py +88 -42
- cmem_cmemc/manual_helper/graph.py +1 -0
- cmem_cmemc/manual_helper/multi_page.py +3 -1
- cmem_cmemc/migrations/__init__.py +1 -0
- cmem_cmemc/migrations/abc.py +84 -0
- cmem_cmemc/migrations/access_conditions_243.py +122 -0
- cmem_cmemc/migrations/bootstrap_data.py +28 -0
- cmem_cmemc/migrations/shapes_widget_integrations_243.py +274 -0
- cmem_cmemc/migrations/workspace_configurations.py +28 -0
- cmem_cmemc/object_list.py +53 -22
- cmem_cmemc/parameter_types/__init__.py +1 -0
- cmem_cmemc/parameter_types/path.py +69 -0
- cmem_cmemc/smart_path/__init__.py +94 -0
- cmem_cmemc/smart_path/clients/__init__.py +63 -0
- cmem_cmemc/smart_path/clients/http.py +65 -0
- cmem_cmemc/string_processor.py +83 -0
- cmem_cmemc/title_helper.py +41 -0
- cmem_cmemc/utils.py +100 -45
- {cmem_cmemc-24.2.0rc2.dist-info → cmem_cmemc-24.3.0rc2.dist-info}/LICENSE +1 -1
- cmem_cmemc-24.3.0rc2.dist-info/METADATA +89 -0
- cmem_cmemc-24.3.0rc2.dist-info/RECORD +53 -0
- {cmem_cmemc-24.2.0rc2.dist-info → cmem_cmemc-24.3.0rc2.dist-info}/WHEEL +1 -1
- cmem_cmemc-24.2.0rc2.dist-info/METADATA +0 -69
- cmem_cmemc-24.2.0rc2.dist-info/RECORD +0 -37
- {cmem_cmemc-24.2.0rc2.dist-info → cmem_cmemc-24.3.0rc2.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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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://
|
|
291
|
-
|
|
295
|
+
url = "https://download.eccenca.com/cmem-plugin-index/cmem-plugin-index.json"
|
|
296
|
+
package_names = []
|
|
292
297
|
packages = []
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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.0rc2
|
|
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=fy2gwoW-15hl-yCCY9f0t59j4Sp2Mj2GNatZ2G-mrxA,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.0rc2.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
50
|
+
cmem_cmemc-24.3.0rc2.dist-info/METADATA,sha256=Wf6-ZA-YWKg15I1zy_gMursZwBSLH_aUyxniZn36mUU,5619
|
|
51
|
+
cmem_cmemc-24.3.0rc2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
52
|
+
cmem_cmemc-24.3.0rc2.dist-info/entry_points.txt,sha256=znWUTG-zgDITu6Frsd-OtNxBxj6Uo8Fa7bz6gaZYMrA,41
|
|
53
|
+
cmem_cmemc-24.3.0rc2.dist-info/RECORD,,
|