cmem-cmemc 24.3.3__py3-none-any.whl → 25.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cmem_cmemc/__init__.py +1 -160
- cmem_cmemc/cli.py +138 -0
- cmem_cmemc/command.py +36 -0
- cmem_cmemc/commands/admin.py +7 -6
- cmem_cmemc/commands/client.py +4 -3
- cmem_cmemc/commands/config.py +3 -2
- cmem_cmemc/commands/dataset.py +18 -17
- cmem_cmemc/commands/graph.py +20 -22
- cmem_cmemc/commands/manual.py +56 -0
- cmem_cmemc/commands/metrics.py +11 -11
- cmem_cmemc/commands/migration.py +4 -3
- cmem_cmemc/commands/project.py +10 -10
- cmem_cmemc/commands/python.py +29 -2
- cmem_cmemc/commands/query.py +31 -14
- cmem_cmemc/commands/resource.py +4 -3
- cmem_cmemc/commands/scheduler.py +4 -4
- cmem_cmemc/commands/store.py +2 -2
- cmem_cmemc/commands/user.py +10 -9
- cmem_cmemc/commands/validation.py +2 -2
- cmem_cmemc/commands/variable.py +1 -1
- cmem_cmemc/commands/vocabulary.py +11 -10
- cmem_cmemc/commands/workflow.py +18 -18
- cmem_cmemc/completion.py +76 -49
- cmem_cmemc/config_parser.py +44 -0
- cmem_cmemc/context.py +166 -97
- cmem_cmemc/exceptions.py +15 -2
- cmem_cmemc/manual_helper/graph.py +2 -0
- cmem_cmemc/manual_helper/multi_page.py +1 -1
- cmem_cmemc/manual_helper/single_page.py +2 -0
- cmem_cmemc/migrations/remove_noop_triple_251.py +50 -0
- cmem_cmemc/migrations/shapes_widget_integrations_243.py +0 -5
- cmem_cmemc/object_list.py +3 -3
- cmem_cmemc/parameter_types/path.py +7 -0
- cmem_cmemc/placeholder.py +69 -0
- cmem_cmemc/utils.py +49 -15
- {cmem_cmemc-24.3.3.dist-info → cmem_cmemc-25.1.0.dist-info}/METADATA +14 -14
- cmem_cmemc-25.1.0.dist-info/RECORD +59 -0
- cmem_cmemc-25.1.0.dist-info/entry_points.txt +3 -0
- cmem_cmemc-24.3.3.dist-info/RECORD +0 -54
- cmem_cmemc-24.3.3.dist-info/entry_points.txt +0 -3
- {cmem_cmemc-24.3.3.dist-info → cmem_cmemc-25.1.0.dist-info}/LICENSE +0 -0
- {cmem_cmemc-24.3.3.dist-info → cmem_cmemc-25.1.0.dist-info}/WHEEL +0 -0
cmem_cmemc/context.py
CHANGED
|
@@ -24,8 +24,10 @@ from rich.console import Console
|
|
|
24
24
|
from rich.table import Table
|
|
25
25
|
from urllib3.exceptions import InsecureRequestWarning
|
|
26
26
|
|
|
27
|
+
from cmem_cmemc.config_parser import PureSectionConfigParser
|
|
27
28
|
from cmem_cmemc.exceptions import InvalidConfigurationError
|
|
28
29
|
from cmem_cmemc.string_processor import StringProcessor, process_row
|
|
30
|
+
from cmem_cmemc.utils import is_enabled, str_to_bool
|
|
29
31
|
|
|
30
32
|
DI_TARGET_VERSION = "v24.3.0"
|
|
31
33
|
|
|
@@ -56,34 +58,77 @@ SSL_VERIFY_WARNING = "SSL verification is disabled (SSL_VERIFY=False)."
|
|
|
56
58
|
class ApplicationContext:
|
|
57
59
|
"""Context of the command line interface."""
|
|
58
60
|
|
|
61
|
+
APP_NAME: str = "cmemc"
|
|
62
|
+
DEFAULT_CONFIG_FILE: str = str(Path(click.get_app_dir(APP_NAME)) / "config.ini")
|
|
63
|
+
DEFAULT_EXTERNAL_HTTP_TIMEOUT: int = 10
|
|
64
|
+
|
|
59
65
|
debug: bool
|
|
60
66
|
quiet: bool
|
|
67
|
+
external_http_timeout: int
|
|
61
68
|
config_dir: Path
|
|
62
69
|
config_file: Path
|
|
63
|
-
config:
|
|
64
|
-
connection:
|
|
70
|
+
config: PureSectionConfigParser
|
|
71
|
+
connection: str | None
|
|
65
72
|
console: Console
|
|
66
73
|
console_width: int | None = None
|
|
67
74
|
|
|
68
75
|
# pylint: disable=too-many-instance-attributes
|
|
69
76
|
# pylint: disable=too-many-public-methods
|
|
70
77
|
|
|
71
|
-
def __init__(
|
|
78
|
+
def __init__(
|
|
79
|
+
self,
|
|
80
|
+
config_file: str,
|
|
81
|
+
connection: str | None = None,
|
|
82
|
+
debug: bool = False,
|
|
83
|
+
quiet: bool = False,
|
|
84
|
+
):
|
|
72
85
|
"""Initialize main context."""
|
|
86
|
+
self.config_file = Path(config_file)
|
|
73
87
|
self.app_name = "cmemc"
|
|
74
88
|
self.set_debug(debug)
|
|
75
89
|
self.set_quiet(quiet)
|
|
76
|
-
self.
|
|
77
|
-
self.
|
|
78
|
-
|
|
79
|
-
self.config_file_default = env["CMEMC_CONFIG_FILE"]
|
|
80
|
-
else:
|
|
81
|
-
self.config_file_default = str(self.config_dir / "config.ini")
|
|
82
|
-
# since CMEM-3199, we do not initialize the connection on init
|
|
83
|
-
self.connection = None
|
|
90
|
+
self.ensure_app_config_dir()
|
|
91
|
+
self.set_connection(connection)
|
|
92
|
+
self.set_external_http_timeout(self.DEFAULT_EXTERNAL_HTTP_TIMEOUT)
|
|
84
93
|
self.console = Console(markup=True, emoji_variant="emoji")
|
|
85
94
|
self.update_console_width()
|
|
86
95
|
|
|
96
|
+
@staticmethod
|
|
97
|
+
def from_params(params: dict) -> "ApplicationContext":
|
|
98
|
+
"""Create an ApplicationContext instance from a dictionary.
|
|
99
|
+
|
|
100
|
+
Expects 'config_file' key to be present in the dictionary.
|
|
101
|
+
"""
|
|
102
|
+
config_file = params.get("config_file")
|
|
103
|
+
debug = str_to_bool(str(params.get("debug")))
|
|
104
|
+
quiet = str_to_bool(str(params.get("quiet")))
|
|
105
|
+
external_http_timeout = (
|
|
106
|
+
int(str(params.get("external_http_timeout")))
|
|
107
|
+
if "external_http_timeout" in params
|
|
108
|
+
else ApplicationContext.DEFAULT_EXTERNAL_HTTP_TIMEOUT
|
|
109
|
+
)
|
|
110
|
+
connection = str(params.get("connection")) if params.get("connection") else None
|
|
111
|
+
if not config_file:
|
|
112
|
+
raise ValueError("Missing required key: 'config_file' in config dictionary")
|
|
113
|
+
app = ApplicationContext(
|
|
114
|
+
config_file=config_file, connection=connection, debug=debug, quiet=quiet
|
|
115
|
+
)
|
|
116
|
+
app.set_external_http_timeout(external_http_timeout)
|
|
117
|
+
if not app.debug:
|
|
118
|
+
app.set_debug(is_enabled(params, app.get_config(), "debug"))
|
|
119
|
+
if not app.quiet:
|
|
120
|
+
app.set_quiet(is_enabled(params, app.get_config(), "quiet"))
|
|
121
|
+
app.echo_debug(f"use config file: {app.config_file}")
|
|
122
|
+
if app.connection:
|
|
123
|
+
app.echo_debug(f"set connection: {app.connection}")
|
|
124
|
+
defaults = app.get_defaults()
|
|
125
|
+
app.apply_config(defaults)
|
|
126
|
+
section_config = app.get_connection_config()
|
|
127
|
+
app.clear_known_keys()
|
|
128
|
+
app.apply_config(section_config)
|
|
129
|
+
app.configure_cmempy()
|
|
130
|
+
return app
|
|
131
|
+
|
|
87
132
|
def update_console_width(self) -> None:
|
|
88
133
|
"""Update console width from environment variable."""
|
|
89
134
|
console_width_env = getenv("CMEMC_CONSOLE_WIDTH", None)
|
|
@@ -98,7 +143,7 @@ class ApplicationContext:
|
|
|
98
143
|
today = str(datetime.now(tz=timezone.utc).date())
|
|
99
144
|
data.update(date=today)
|
|
100
145
|
if self.connection is not None:
|
|
101
|
-
data.update(connection=self.connection
|
|
146
|
+
data.update(connection=self.connection)
|
|
102
147
|
else:
|
|
103
148
|
data.update(connection="unnamed")
|
|
104
149
|
return data
|
|
@@ -111,8 +156,13 @@ class ApplicationContext:
|
|
|
111
156
|
"""Set quiets state"""
|
|
112
157
|
self.quiet = quiet
|
|
113
158
|
|
|
114
|
-
def
|
|
115
|
-
"""Set
|
|
159
|
+
def set_external_http_timeout(self, timeout: int) -> None:
|
|
160
|
+
"""Set external http timeout"""
|
|
161
|
+
self.external_http_timeout = timeout
|
|
162
|
+
os.environ["CMEMC_EXTERNAL_HTTP_TIMEOUT"] = str(timeout)
|
|
163
|
+
|
|
164
|
+
def ensure_app_config_dir(self) -> None:
|
|
165
|
+
"""Ensure the application's configuration directory exists."""
|
|
116
166
|
try:
|
|
117
167
|
self.config_dir = Path(click.get_app_dir(self.app_name))
|
|
118
168
|
self.config_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -127,84 +177,77 @@ class ApplicationContext:
|
|
|
127
177
|
self.config_file = self.config_dir / "config.ini"
|
|
128
178
|
self.echo_debug(f"Set config to {self.config_file.absolute()}")
|
|
129
179
|
|
|
130
|
-
def
|
|
131
|
-
"""
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
180
|
+
def get_defaults(self) -> dict[str, str]:
|
|
181
|
+
"""Populate missing values in `params` using defaults from the configuration."""
|
|
182
|
+
config = self.get_config()
|
|
183
|
+
defaults = dict(config.defaults())
|
|
184
|
+
if "CMEMC_CONFIG_FILE" in defaults:
|
|
185
|
+
defaults.pop("CMEMC_CONFIG_FILE")
|
|
186
|
+
if "CMEMC_DEBUG" in defaults and self.debug:
|
|
187
|
+
defaults.pop("CMEMC_DEBUG")
|
|
188
|
+
if "CMEMC_QUIET" in defaults and self.quiet:
|
|
189
|
+
defaults.pop("CMEMC_QUIET")
|
|
190
|
+
if "CMEMC_CONNECTION" in defaults and self.connection:
|
|
191
|
+
defaults.pop("CMEMC_CONNECTION")
|
|
192
|
+
if (
|
|
193
|
+
"CMEMC_EXTERNAL_HTTP_TIMEOUT" in defaults
|
|
194
|
+
and self.external_http_timeout != self.DEFAULT_EXTERNAL_HTTP_TIMEOUT
|
|
195
|
+
):
|
|
196
|
+
defaults.pop("CMEMC_EXTERNAL_HTTP_TIMEOUT")
|
|
197
|
+
for key in defaults:
|
|
198
|
+
if key in os.environ:
|
|
199
|
+
defaults.pop(key)
|
|
200
|
+
return defaults
|
|
201
|
+
|
|
202
|
+
def get_connection_config(self) -> dict[str, str]:
|
|
203
|
+
"""Get connection section config"""
|
|
204
|
+
if not self.connection:
|
|
205
|
+
return {}
|
|
206
|
+
config = dict(self.get_config()[self.connection])
|
|
207
|
+
if "CMEMC_CONFIG_FILE" in config:
|
|
208
|
+
config.pop("CMEMC_CONFIG_FILE")
|
|
209
|
+
if "CMEMC_CONNECTION" in config:
|
|
210
|
+
config.pop("CMEMC_CONNECTION")
|
|
211
|
+
return config
|
|
212
|
+
|
|
213
|
+
def apply_config(self, config: dict[str, str]) -> None:
|
|
214
|
+
"""Apply the configuration to the context."""
|
|
215
|
+
for key, value in config.items():
|
|
216
|
+
if key == "CMEMC_DEBUG":
|
|
217
|
+
self.set_debug(str_to_bool(value))
|
|
218
|
+
elif key == "CMEMC_QUIET":
|
|
219
|
+
self.set_quiet(str_to_bool(value))
|
|
220
|
+
elif key == "CMEMC_CONNECTION":
|
|
221
|
+
self.echo_debug(f"set connection to {value}")
|
|
222
|
+
self.set_connection(value)
|
|
223
|
+
elif key == "CMEMC_EXTERNAL_HTTP_TIMEOUT":
|
|
224
|
+
self.set_external_http_timeout(int(value))
|
|
225
|
+
else:
|
|
143
226
|
if key in KNOWN_SECRET_KEYS:
|
|
144
227
|
self.echo_debug(key + " set by config")
|
|
145
228
|
else:
|
|
146
229
|
self.echo_debug(key + " set by config to " + value)
|
|
230
|
+
env[key] = value
|
|
147
231
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
232
|
+
def clear_known_keys(self) -> None:
|
|
233
|
+
"""Clear know keys if the connection is set."""
|
|
234
|
+
if self.connection:
|
|
235
|
+
# clear all known keys
|
|
236
|
+
for key in KNOWN_CONFIG_KEYS:
|
|
237
|
+
if key in env:
|
|
238
|
+
env.pop(key)
|
|
154
239
|
|
|
155
|
-
def
|
|
156
|
-
"""
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if params["connection"]:
|
|
165
|
-
self.set_connection(params["connection"])
|
|
166
|
-
|
|
167
|
-
def set_connection_from_args(self, args: dict) -> None:
|
|
168
|
-
"""Set connection and config by manually checking param (completion)."""
|
|
169
|
-
# look for environment and load config
|
|
170
|
-
self.set_config_file(getenv("CMEMC_CONFIG_FILE", str(self.config_file)))
|
|
171
|
-
# look for config file in arguments and load config
|
|
172
|
-
found_config_file = False
|
|
173
|
-
for arg in args:
|
|
174
|
-
if found_config_file is True:
|
|
175
|
-
self.set_config_file(arg)
|
|
176
|
-
break
|
|
177
|
-
if arg == "--config-file":
|
|
178
|
-
found_config_file = True
|
|
179
|
-
self.config = self.get_config()
|
|
180
|
-
# look for connection in environment and set connection
|
|
181
|
-
self.set_connection(getenv("CMEMC_CONNECTION", ""))
|
|
182
|
-
# look for connection in arguments and set connection
|
|
183
|
-
found_connection = False
|
|
184
|
-
for arg in args:
|
|
185
|
-
if found_connection is True:
|
|
186
|
-
self.set_connection(arg)
|
|
187
|
-
return
|
|
188
|
-
if arg in ("-c", "--connection"):
|
|
189
|
-
found_connection = True
|
|
190
|
-
return
|
|
191
|
-
|
|
192
|
-
def set_connection(self, section_string: str) -> configparser.SectionProxy | None:
|
|
193
|
-
"""Set connection config section based on section string."""
|
|
194
|
-
self.config = self.get_config()
|
|
195
|
-
self.connection = None
|
|
196
|
-
if section_string is None or section_string == "":
|
|
197
|
-
self.echo_debug("No config given, use API defaults or environment connection.")
|
|
198
|
-
elif section_string not in self.config:
|
|
199
|
-
raise InvalidConfigurationError(
|
|
200
|
-
f"There is no connection '{section_string}' configured in "
|
|
201
|
-
f"config '{self.config_file}'."
|
|
202
|
-
)
|
|
203
|
-
else:
|
|
204
|
-
self.echo_debug(f"Use connection config: {section_string}")
|
|
205
|
-
self.connection = self.config[section_string]
|
|
206
|
-
self.configure_cmempy(self.connection)
|
|
240
|
+
def configure_cmempy(self) -> None:
|
|
241
|
+
"""Configure the cmempy API to use a new connection."""
|
|
242
|
+
# With or without connection config, we do not want API stdout prints
|
|
243
|
+
env["CMEMPY_IS_CHATTY"] = "False"
|
|
244
|
+
if self.connection:
|
|
245
|
+
config = self.get_config()[self.connection]
|
|
246
|
+
# allow to fetch all secrets from an external process
|
|
247
|
+
for _ in KNOWN_SECRET_KEYS:
|
|
248
|
+
self.set_credential_from_process(_, _ + "_PROCESS", config)
|
|
207
249
|
|
|
250
|
+
self.echo_debug(f"CA bundle loaded from {cmempy_config.get_requests_ca_bundle()}")
|
|
208
251
|
# If cert validation is disabled, output a warning
|
|
209
252
|
# Also disable library warnings:
|
|
210
253
|
# https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
|
|
@@ -212,27 +255,49 @@ class ApplicationContext:
|
|
|
212
255
|
self.echo_warning(SSL_VERIFY_WARNING)
|
|
213
256
|
urllib3.disable_warnings(category=InsecureRequestWarning)
|
|
214
257
|
|
|
215
|
-
|
|
258
|
+
@staticmethod
|
|
259
|
+
def set_connection_from_params(params: dict) -> None:
|
|
260
|
+
"""Set connection and config by manually checking params (completion)."""
|
|
261
|
+
app = ApplicationContext.from_params(params)
|
|
262
|
+
defaults = app.get_defaults()
|
|
263
|
+
app.apply_config(defaults)
|
|
264
|
+
section_config = app.get_connection_config()
|
|
265
|
+
app.clear_known_keys()
|
|
266
|
+
app.apply_config(section_config)
|
|
267
|
+
app.configure_cmempy()
|
|
268
|
+
|
|
269
|
+
def set_connection(self, section_string: str | None = None) -> None:
|
|
270
|
+
"""Set connection config section based on section string."""
|
|
271
|
+
self.connection = None
|
|
272
|
+
if section_string is None or section_string == "":
|
|
273
|
+
return
|
|
274
|
+
if section_string not in self.get_config():
|
|
275
|
+
raise InvalidConfigurationError(
|
|
276
|
+
self,
|
|
277
|
+
f"There is no connection '{section_string}' configured in "
|
|
278
|
+
f"config '{self.config_file}'.",
|
|
279
|
+
)
|
|
280
|
+
self.connection = section_string
|
|
216
281
|
|
|
217
282
|
def get_config_file(self) -> Path:
|
|
218
283
|
"""Check the connection config file."""
|
|
219
284
|
if not self.config_file.exists():
|
|
220
285
|
with self.config_file.open(mode="a", encoding="UTF-8"):
|
|
221
286
|
self.echo_warning(f"Empty config created: {self.config_file.name}")
|
|
222
|
-
self.echo_debug("Config loaded: " + self.config_file.name)
|
|
223
287
|
return self.config_file
|
|
224
288
|
|
|
225
|
-
def get_config(self) ->
|
|
289
|
+
def get_config(self) -> PureSectionConfigParser:
|
|
226
290
|
"""Parse the configuration"""
|
|
227
|
-
config =
|
|
291
|
+
config = PureSectionConfigParser()
|
|
228
292
|
try:
|
|
229
293
|
# https://stackoverflow.com/questions/1648517/
|
|
230
294
|
config.read(self.get_config_file(), encoding="utf-8")
|
|
231
295
|
except configparser.Error as error:
|
|
232
296
|
raise InvalidConfigurationError(
|
|
297
|
+
self,
|
|
233
298
|
"The following config parser error needs to be fixed with your config file:\n"
|
|
234
299
|
f"{error!s}\n"
|
|
235
|
-
"You can use the 'config edit' command to fix this."
|
|
300
|
+
"You can use the 'config edit' command to fix this.",
|
|
236
301
|
) from error
|
|
237
302
|
except Exception as error: # noqa: BLE001
|
|
238
303
|
self.echo_debug(f"Could not read config file - provide empty config: {error!s}")
|
|
@@ -282,12 +347,19 @@ class ApplicationContext:
|
|
|
282
347
|
"""Output a debug message if --debug is enabled.
|
|
283
348
|
|
|
284
349
|
2024-05-17: also allows list of strings now
|
|
350
|
+
2025-04-07: extract output function
|
|
285
351
|
"""
|
|
286
352
|
messages: list[str] = [message] if isinstance(message, str) else message
|
|
287
|
-
if self.debug:
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
353
|
+
if self.debug and not ApplicationContext.is_completing():
|
|
354
|
+
self.echo_debug_string(messages)
|
|
355
|
+
|
|
356
|
+
@staticmethod
|
|
357
|
+
def echo_debug_string(message: str | list[str]) -> None:
|
|
358
|
+
"""Output a debug message"""
|
|
359
|
+
messages: list[str] = [message] if isinstance(message, str) else message
|
|
360
|
+
now = datetime.now(tz=timezone.utc)
|
|
361
|
+
for _ in messages:
|
|
362
|
+
click.secho(f"[{now!s}] {_}", err=True, dim=True)
|
|
291
363
|
|
|
292
364
|
def echo_info(
|
|
293
365
|
self,
|
|
@@ -491,6 +563,3 @@ class ApplicationContext:
|
|
|
491
563
|
pw_value = split_output[0]
|
|
492
564
|
# set the password environment variable
|
|
493
565
|
env[pw_key] = pw_value
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
CONTEXT = ApplicationContext()
|
cmem_cmemc/exceptions.py
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
"""Declares all cli exceptions."""
|
|
2
2
|
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from cmem_cmemc.context import ApplicationContext
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CmemcError(ValueError):
|
|
10
|
+
"""Base exception for CMEM-CMEMC-related errors."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, app: "ApplicationContext", *args: str):
|
|
13
|
+
super().__init__(*args)
|
|
14
|
+
self.app = app
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class InvalidConfigurationError(CmemcError):
|
|
5
18
|
"""The configuration given was not found or is broken."""
|
|
6
19
|
|
|
7
20
|
|
|
8
|
-
class ServerError(
|
|
21
|
+
class ServerError(CmemcError):
|
|
9
22
|
"""The server reported an error with a process."""
|
|
@@ -43,6 +43,8 @@ def print_group_manual_graph_recursive(
|
|
|
43
43
|
"""Output documentation graph (recursive)."""
|
|
44
44
|
commands = command_group.commands # type: ignore[union-attr]
|
|
45
45
|
for key in commands:
|
|
46
|
+
if key == "manual":
|
|
47
|
+
continue
|
|
46
48
|
item = commands[key]
|
|
47
49
|
iri = f":{prefix}{key}"
|
|
48
50
|
ctx.obj.echo_info(f"{iri} skos:notation '{key}' .")
|
|
@@ -205,7 +205,7 @@ tags:
|
|
|
205
205
|
|
|
206
206
|
|
|
207
207
|
def create_multi_page_documentation(ctx: click.core.Context, directory: str) -> None:
|
|
208
|
-
"""Create a
|
|
208
|
+
"""Create a multipage reference manual for documentation.eccenca.com.
|
|
209
209
|
|
|
210
210
|
Returns: None
|
|
211
211
|
"""
|
|
@@ -24,6 +24,8 @@ def print_group_manual_recursive(
|
|
|
24
24
|
"""Output the help text of a command group (recursive)."""
|
|
25
25
|
commands = command_group.commands # type: ignore[union-attr]
|
|
26
26
|
for key in commands:
|
|
27
|
+
if key == "manual":
|
|
28
|
+
continue
|
|
27
29
|
command = commands[key]
|
|
28
30
|
formatter = ctx.make_formatter()
|
|
29
31
|
if isinstance(command, click.Group):
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""RemoveHideHeaderFooterStatements migration"""
|
|
2
|
+
|
|
3
|
+
from typing import ClassVar
|
|
4
|
+
|
|
5
|
+
from cmem_cmemc.migrations.abc import MigrationRecipe, components
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RemoveHideHeaderFooterStatements(MigrationRecipe):
|
|
9
|
+
"""25.1 Remove Non-Operational shui:valueQueryHideHeader|Footer Triple"""
|
|
10
|
+
|
|
11
|
+
id = "hide-header-footer-25.1"
|
|
12
|
+
description = "Remove triples using deprecated shui:valueQueryHideHeader|Footer terms"
|
|
13
|
+
component: components = "explore"
|
|
14
|
+
first_version = "24.3" # needs to changed right before release
|
|
15
|
+
tags: ClassVar[list[str]] = ["shapes", "user"]
|
|
16
|
+
check_query = """{{DEFAULT_PREFIXES}}
|
|
17
|
+
SELECT ?shape
|
|
18
|
+
WHERE {
|
|
19
|
+
GRAPH ?shapeCatalog {
|
|
20
|
+
?shapeCatalog a shui:ShapeCatalog .
|
|
21
|
+
?shape ?deprecatedProperty ?value .
|
|
22
|
+
}
|
|
23
|
+
VALUES ?deprecatedProperty {
|
|
24
|
+
shui:valueQueryHideHeader shui:valueQueryHideFooter
|
|
25
|
+
}
|
|
26
|
+
} LIMIT 1
|
|
27
|
+
"""
|
|
28
|
+
delete_query = """{{DEFAULT_PREFIXES}}
|
|
29
|
+
DELETE {
|
|
30
|
+
GRAPH ?shapeCatalog { ?shape ?deprecatedProperty ?value . }
|
|
31
|
+
}
|
|
32
|
+
WHERE {
|
|
33
|
+
GRAPH ?shapeCatalog {
|
|
34
|
+
?shapeCatalog a shui:ShapeCatalog .
|
|
35
|
+
?shape ?deprecatedProperty ?value .
|
|
36
|
+
}
|
|
37
|
+
VALUES ?deprecatedProperty {
|
|
38
|
+
shui:valueQueryHideHeader shui:valueQueryHideFooter
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def is_applicable(self) -> bool:
|
|
44
|
+
"""Test if the recipe can be applied."""
|
|
45
|
+
existing_triples = self._select(self.check_query)
|
|
46
|
+
return len(existing_triples) == 1
|
|
47
|
+
|
|
48
|
+
def apply(self) -> None:
|
|
49
|
+
"""Apply the recipe to the current version."""
|
|
50
|
+
self._update(self.delete_query)
|
|
@@ -27,11 +27,6 @@ WHERE {
|
|
|
27
27
|
}
|
|
28
28
|
"""
|
|
29
29
|
move_query = """{{DEFAULT_PREFIXES}}
|
|
30
|
-
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
|
|
31
|
-
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
32
|
-
PREFIX shui: <https://vocab.eccenca.com/shui/>
|
|
33
|
-
PREFIX sh: <http://www.w3.org/ns/shacl#>
|
|
34
|
-
|
|
35
30
|
DELETE {
|
|
36
31
|
GRAPH ?shapeCatalog {
|
|
37
32
|
?nodeShape shui:provideChartVisualization ?shuiChart .
|
cmem_cmemc/object_list.py
CHANGED
|
@@ -10,7 +10,7 @@ from click import Argument, Context, UsageError
|
|
|
10
10
|
from click.shell_completion import CompletionItem
|
|
11
11
|
|
|
12
12
|
from cmem_cmemc.completion import finalize_completion, get_completion_args
|
|
13
|
-
from cmem_cmemc.context import
|
|
13
|
+
from cmem_cmemc.context import ApplicationContext
|
|
14
14
|
from cmem_cmemc.title_helper import TitleHelper
|
|
15
15
|
|
|
16
16
|
|
|
@@ -291,7 +291,7 @@ class ObjectList:
|
|
|
291
291
|
if not isinstance(added_filter, Filter):
|
|
292
292
|
raise TypeError("'filter_' parameter must be an instance OR a subclass of Filter")
|
|
293
293
|
if added_filter.name in self.filters:
|
|
294
|
-
raise
|
|
294
|
+
raise UsageError(f"Filter {added_filter.name} already exists")
|
|
295
295
|
self.filters[added_filter.name] = added_filter
|
|
296
296
|
|
|
297
297
|
def remove_filter(self, filter_name: str) -> None:
|
|
@@ -361,7 +361,7 @@ class ObjectList:
|
|
|
361
361
|
incomplete=incomplete,
|
|
362
362
|
)
|
|
363
363
|
|
|
364
|
-
|
|
364
|
+
ApplicationContext.set_connection_from_params(ctx.find_root().params)
|
|
365
365
|
# This will filter the object list with name/values filter from the command line,
|
|
366
366
|
# up to the current filter which values are completed
|
|
367
367
|
objects = self.apply_filters(ctx=ctx, filter_=previous_filter)
|
|
@@ -8,6 +8,8 @@ import smart_open
|
|
|
8
8
|
from click.core import Context, Parameter
|
|
9
9
|
from smart_open import compression
|
|
10
10
|
|
|
11
|
+
from cmem_cmemc.context import ApplicationContext
|
|
12
|
+
|
|
11
13
|
|
|
12
14
|
class ClickSmartPath(click.Path):
|
|
13
15
|
"""Custom Click smart_path ParamType"""
|
|
@@ -61,6 +63,11 @@ class ClickSmartPath(click.Path):
|
|
|
61
63
|
"""Open the file and return the file handle."""
|
|
62
64
|
if file_path == "-":
|
|
63
65
|
return click.open_file(file_path, mode=mode)
|
|
66
|
+
if transport_params is None:
|
|
67
|
+
transport_params = {}
|
|
68
|
+
transport_params["timeout"] = os.getenv(
|
|
69
|
+
"CMEM_TRANSPORT_TIMEOUT", ApplicationContext.DEFAULT_EXTERNAL_HTTP_TIMEOUT
|
|
70
|
+
)
|
|
64
71
|
return smart_open.open( # type: ignore[no-any-return]
|
|
65
72
|
file_path,
|
|
66
73
|
mode,
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Class to retrieve query placeholder resources
|
|
2
|
+
|
|
3
|
+
note: this is not the correct place for this class - it should be cmem_client or cmempy
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from cmem.cmempy.queries import SparqlQuery
|
|
7
|
+
|
|
8
|
+
FETCH_PLACEHOLDERS = """
|
|
9
|
+
PREFIX shui: <https://vocab.eccenca.com/shui/>
|
|
10
|
+
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
|
|
11
|
+
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
12
|
+
|
|
13
|
+
SELECT ?key ?valueQuery
|
|
14
|
+
FROM <{{graph}}>
|
|
15
|
+
WHERE {
|
|
16
|
+
?placeholder a shui:QueryPlaceholder ;
|
|
17
|
+
shui:QueryPlaceholder_key ?key ;
|
|
18
|
+
shui:QueryPlaceholder_valueQuery ?valueQueryR ;
|
|
19
|
+
shui:QueryPlaceholder_usedInQuery <{{query}}> .
|
|
20
|
+
?valueQueryR shui:queryText ?valueQuery
|
|
21
|
+
}
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class QueryPlaceholder:
|
|
26
|
+
"""A query placeholder"""
|
|
27
|
+
|
|
28
|
+
key: str
|
|
29
|
+
value_query: SparqlQuery
|
|
30
|
+
|
|
31
|
+
def __init__(self, key: str, value_query: str):
|
|
32
|
+
self.key = key
|
|
33
|
+
self.value_query = SparqlQuery(value_query, query_type="SELECT")
|
|
34
|
+
|
|
35
|
+
def complete(self, incomplete: str = "") -> list[str] | list[tuple[str, str]]: # noqa: ARG002
|
|
36
|
+
"""Prepare a list of placeholder values"""
|
|
37
|
+
result = self.value_query.get_json_results()
|
|
38
|
+
projection_vars = result["head"]["vars"]
|
|
39
|
+
bindings = result["results"]["bindings"]
|
|
40
|
+
if "value" not in projection_vars:
|
|
41
|
+
return []
|
|
42
|
+
|
|
43
|
+
if "description" not in projection_vars:
|
|
44
|
+
values_without_description = []
|
|
45
|
+
for _ in bindings:
|
|
46
|
+
value = str(_["value"]["value"])
|
|
47
|
+
values_without_description.append(value)
|
|
48
|
+
return values_without_description
|
|
49
|
+
|
|
50
|
+
values_with_description = []
|
|
51
|
+
for _ in bindings:
|
|
52
|
+
value = str(_["value"]["value"])
|
|
53
|
+
description = str(_["description"]["value"])
|
|
54
|
+
values_with_description.append((value, description))
|
|
55
|
+
return values_with_description
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_placeholders_for_query(iri: str) -> dict[str, QueryPlaceholder]:
|
|
59
|
+
"""Get a placeholder dict for a query"""
|
|
60
|
+
placeholders = {}
|
|
61
|
+
results = SparqlQuery(
|
|
62
|
+
FETCH_PLACEHOLDERS,
|
|
63
|
+
query_type="SELECT",
|
|
64
|
+
).get_json_results(placeholder={"graph": "https://ns.eccenca.com/data/queries/", "query": iri})
|
|
65
|
+
for binding in results["results"]["bindings"]:
|
|
66
|
+
key = binding["key"]["value"]
|
|
67
|
+
value_query = binding["valueQuery"]["value"]
|
|
68
|
+
placeholders[key] = QueryPlaceholder(key=key, value_query=value_query)
|
|
69
|
+
return placeholders
|