cmem-cmemc 25.1.1__tar.gz → 25.2.0__tar.gz
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-25.1.1 → cmem_cmemc-25.2.0}/PKG-INFO +2 -2
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/command_group.py +1 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/commands/admin.py +3 -3
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/commands/config.py +2 -2
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/commands/graph.py +149 -177
- cmem_cmemc-25.2.0/cmem_cmemc/commands/graph_imports.py +420 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/commands/migration.py +8 -8
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/commands/project.py +1 -1
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/commands/python.py +5 -4
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/commands/query.py +2 -1
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/commands/resource.py +1 -1
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/commands/scheduler.py +1 -1
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/commands/store.py +1 -1
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/commands/variable.py +1 -1
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/completion.py +23 -19
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/constants.py +2 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/utils.py +52 -14
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/pyproject.toml +3 -2
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/LICENSE +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/README-public.md +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/__init__.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/_cmemc.zsh +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/cli.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/command.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/commands/__init__.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/commands/acl.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/commands/client.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/commands/dataset.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/commands/manual.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/commands/metrics.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/commands/user.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/commands/validation.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/commands/vocabulary.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/commands/workflow.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/commands/workspace.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/config_parser.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/context.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/exceptions.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/manual_helper/__init__.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/manual_helper/graph.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/manual_helper/multi_page.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/manual_helper/single_page.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/migrations/__init__.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/migrations/abc.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/migrations/access_conditions_243.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/migrations/bootstrap_data.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/migrations/remove_noop_triple_251.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/migrations/shapes_widget_integrations_243.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/migrations/sparql_query_texts_242.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/migrations/workspace_configurations.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/object_list.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/parameter_types/__init__.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/parameter_types/path.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/placeholder.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/smart_path/__init__.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/smart_path/clients/__init__.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/smart_path/clients/http.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/string_processor.py +0 -0
- {cmem_cmemc-25.1.1 → cmem_cmemc-25.2.0}/cmem_cmemc/title_helper.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: cmem-cmemc
|
|
3
|
-
Version: 25.
|
|
3
|
+
Version: 25.2.0
|
|
4
4
|
Summary: Command line client for eccenca Corporate Memory
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Author: eccenca
|
|
@@ -27,7 +27,7 @@ Classifier: Topic :: Software Development :: Testing
|
|
|
27
27
|
Classifier: Topic :: Utilities
|
|
28
28
|
Requires-Dist: beautifulsoup4 (>=4.13.3,<5.0.0)
|
|
29
29
|
Requires-Dist: certifi (>=2024.2.2)
|
|
30
|
-
Requires-Dist: click (>=8.1.8,<
|
|
30
|
+
Requires-Dist: click (>=8.1.8,<8.2.0)
|
|
31
31
|
Requires-Dist: click-didyoumean (>=0.3.1,<0.4.0)
|
|
32
32
|
Requires-Dist: click-help-colors (>=0.9.4,<0.10.0)
|
|
33
33
|
Requires-Dist: cmem-cmempy (==25.1.0)
|
|
@@ -65,6 +65,7 @@ class CmemcGroup(HelpColorsGroup, DYMGroup):
|
|
|
65
65
|
"validation": self.color_for_command_groups,
|
|
66
66
|
"migrate": self.color_for_writing_commands,
|
|
67
67
|
"migrations": self.color_for_command_groups,
|
|
68
|
+
"imports": self.color_for_command_groups,
|
|
68
69
|
},
|
|
69
70
|
)
|
|
70
71
|
super().__init__(*args, **kwargs)
|
|
@@ -24,11 +24,11 @@ from cmem_cmemc.context import ApplicationContext
|
|
|
24
24
|
from cmem_cmemc.utils import struct_to_table
|
|
25
25
|
|
|
26
26
|
WARNING_MIGRATION = (
|
|
27
|
-
"Your workspace configuration version does not match your
|
|
27
|
+
"Your workspace configuration version does not match your Explore version. "
|
|
28
28
|
"Please consider migrating your workspace configuration (admin store migrate command)."
|
|
29
29
|
)
|
|
30
30
|
WARNING_SHAPES = (
|
|
31
|
-
"Your ShapeCatalog version does not match your
|
|
31
|
+
"Your ShapeCatalog version does not match your Explore version. "
|
|
32
32
|
"Please consider updating your bootstrap data (admin store boostrap command)."
|
|
33
33
|
)
|
|
34
34
|
|
|
@@ -108,7 +108,7 @@ def status_command( # noqa: C901, PLR0912
|
|
|
108
108
|
Additionally, this command informs you in one of these cases:
|
|
109
109
|
(1) A warning, if the target version of your cmemc client is newer than the version
|
|
110
110
|
of your backend.
|
|
111
|
-
(2) A warning, if the ShapeCatalog has a different version than your
|
|
111
|
+
(2) A warning, if the ShapeCatalog has a different version than your Explore component.
|
|
112
112
|
(3) An error, if your Corporate Memory license is expired (grace period).
|
|
113
113
|
(4) A warning, if your Graph DB license will expire in less than a month.
|
|
114
114
|
|
|
@@ -119,8 +119,8 @@ def config() -> CmemcGroup: # type: ignore[empty-body]
|
|
|
119
119
|
|
|
120
120
|
\b
|
|
121
121
|
SSL_VERIFY=False - for ignoring certificate issues (not recommended)
|
|
122
|
-
DP_API_ENDPOINT=URL - to point to a non-standard DataPlatform location
|
|
123
|
-
DI_API_ENDPOINT=URL - to point to a non-standard DataIntegration location
|
|
122
|
+
DP_API_ENDPOINT=URL - to point to a non-standard Explore backend (DataPlatform) location
|
|
123
|
+
DI_API_ENDPOINT=URL - to point to a non-standard Build (DataIntegration) location
|
|
124
124
|
OAUTH_TOKEN_URI=URL - to point to an external IdentityProvider location
|
|
125
125
|
OAUTH_USER=username - only if OAUTH_GRANT_TYPE=password
|
|
126
126
|
OAUTH_PASSWORD=password - only if OAUTH_GRANT_TYPE=password
|
|
@@ -14,41 +14,34 @@ from xml.etree.ElementTree import ( # nosec
|
|
|
14
14
|
)
|
|
15
15
|
|
|
16
16
|
import click
|
|
17
|
-
from click import
|
|
17
|
+
from click import ClickException, Context, UsageError
|
|
18
18
|
from cmem.cmempy.config import get_cmem_base_uri
|
|
19
19
|
from cmem.cmempy.dp.authorization import refresh
|
|
20
20
|
from cmem.cmempy.dp.proxy import graph as graph_api
|
|
21
|
-
from cmem.cmempy.dp.proxy.graph import
|
|
21
|
+
from cmem.cmempy.dp.proxy.graph import get_graph_imports
|
|
22
22
|
from cmem.cmempy.dp.proxy.sparql import get as sparql_api
|
|
23
23
|
from jinja2 import Template
|
|
24
24
|
from six.moves.urllib.parse import quote
|
|
25
|
-
from treelib import Tree
|
|
26
25
|
|
|
27
26
|
from cmem_cmemc import completion
|
|
28
27
|
from cmem_cmemc.command import CmemcCommand
|
|
29
28
|
from cmem_cmemc.command_group import CmemcGroup
|
|
29
|
+
from cmem_cmemc.commands.graph_imports import graph_imports_list, imports_group
|
|
30
30
|
from cmem_cmemc.commands.validation import validation_group
|
|
31
|
+
from cmem_cmemc.constants import UNKNOWN_GRAPH_ERROR
|
|
31
32
|
from cmem_cmemc.context import ApplicationContext
|
|
32
33
|
from cmem_cmemc.parameter_types.path import ClickSmartPath
|
|
33
34
|
from cmem_cmemc.smart_path import SmartPath
|
|
34
35
|
from cmem_cmemc.utils import (
|
|
36
|
+
RdfGraphData,
|
|
35
37
|
convert_uri_to_filename,
|
|
36
38
|
get_graphs,
|
|
37
39
|
get_graphs_as_dict,
|
|
38
40
|
iri_to_qname,
|
|
39
41
|
read_rdf_graph_files,
|
|
42
|
+
tuple_to_list,
|
|
40
43
|
)
|
|
41
44
|
|
|
42
|
-
UNKNOWN_GRAPH_ERROR = "The graph {} is not accessible or does not exist."
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def tuple_to_list(ctx: ApplicationContext, param: Argument, value: tuple) -> list: # noqa: ARG001
|
|
46
|
-
"""Get a list from a tuple
|
|
47
|
-
|
|
48
|
-
Used as callback to have mutable values
|
|
49
|
-
"""
|
|
50
|
-
return list(value)
|
|
51
|
-
|
|
52
45
|
|
|
53
46
|
def count_graph(graph_iri: str) -> int:
|
|
54
47
|
"""Count triples in a graph and return integer."""
|
|
@@ -142,63 +135,6 @@ def _get_export_names(
|
|
|
142
135
|
return _names
|
|
143
136
|
|
|
144
137
|
|
|
145
|
-
def _create_node_label(iri: str, graphs: dict) -> str:
|
|
146
|
-
"""Create a label for a node in the tree."""
|
|
147
|
-
if iri not in graphs:
|
|
148
|
-
return "[missing: " + iri + "]"
|
|
149
|
-
title = graphs[iri]["label"]["title"]
|
|
150
|
-
return f"{title} -- {iri}"
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
def _add_tree_nodes_recursive(tree: Tree, structure: dict, iri: str, graphs: dict) -> Tree:
|
|
154
|
-
"""Add all child nodes of iri from structure to tree.
|
|
155
|
-
|
|
156
|
-
Call recursively until no child node can be used as parent anymore.
|
|
157
|
-
|
|
158
|
-
Args:
|
|
159
|
-
----
|
|
160
|
-
tree: the graph where to add the nodes
|
|
161
|
-
structure: the result dict of get_graph_import_tree()
|
|
162
|
-
iri: The IRI of the parent
|
|
163
|
-
graphs: the result of get_graphs()
|
|
164
|
-
|
|
165
|
-
Returns:
|
|
166
|
-
-------
|
|
167
|
-
the new treelib.Tree object with the additional nodes
|
|
168
|
-
|
|
169
|
-
"""
|
|
170
|
-
if not tree.contains(iri):
|
|
171
|
-
tree.create_node(tag=_create_node_label(iri, graphs), identifier=iri)
|
|
172
|
-
if iri not in structure:
|
|
173
|
-
return tree
|
|
174
|
-
for child in structure[iri]:
|
|
175
|
-
tree.create_node(tag=_create_node_label(child, graphs), identifier=child, parent=iri)
|
|
176
|
-
for child in structure[iri]:
|
|
177
|
-
if child in structure:
|
|
178
|
-
tree = _add_tree_nodes_recursive(tree, structure, child, graphs)
|
|
179
|
-
return tree
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
def _add_ignored_nodes(tree: Tree, structure: dict) -> Tree:
|
|
183
|
-
"""Add all child nodes as ignored nodes.
|
|
184
|
-
|
|
185
|
-
Args:
|
|
186
|
-
----
|
|
187
|
-
tree: the graph where to add the nodes
|
|
188
|
-
structure: the result dict of get_graph_import_tree()
|
|
189
|
-
|
|
190
|
-
Returns:
|
|
191
|
-
-------
|
|
192
|
-
the new treelib.Tree object with the additional nodes
|
|
193
|
-
|
|
194
|
-
"""
|
|
195
|
-
if len(structure.keys()) > 0:
|
|
196
|
-
for parent in structure:
|
|
197
|
-
for children in structure[parent]:
|
|
198
|
-
tree.create_node(tag="[ignored: " + children + "]", parent=parent)
|
|
199
|
-
return tree
|
|
200
|
-
|
|
201
|
-
|
|
202
138
|
def _get_graphs_filtered(filter_name: str, filter_value: str) -> list[dict]:
|
|
203
139
|
"""Get graphs but filtered according to name and value."""
|
|
204
140
|
# not filtered means all graphs
|
|
@@ -322,29 +258,7 @@ def _create_xml_catalog_file(app: ApplicationContext, names: dict, output_dir: s
|
|
|
322
258
|
app.echo_success(f"XML catalog file created as {file_name}.")
|
|
323
259
|
|
|
324
260
|
|
|
325
|
-
|
|
326
|
-
"""Prepare a sorted, de-duplicated IRI list of graph imports."""
|
|
327
|
-
output_iris = []
|
|
328
|
-
for iri in iris:
|
|
329
|
-
# get response for one requested graph
|
|
330
|
-
api_response = get_graph_import_tree(iri)
|
|
331
|
-
|
|
332
|
-
# add all imported IRIs to the IRI list
|
|
333
|
-
# add the requested graph as well
|
|
334
|
-
output_iris.append(iri)
|
|
335
|
-
for top_graph in api_response["tree"]:
|
|
336
|
-
output_iris.append(top_graph)
|
|
337
|
-
for sub_graph in api_response["tree"][top_graph]:
|
|
338
|
-
output_iris.append(sub_graph) # noqa: PERF402
|
|
339
|
-
|
|
340
|
-
# prepare a sorted, de-duplicated IRI list of existing graphs
|
|
341
|
-
# and create a line-by-line output of it
|
|
342
|
-
output_iris = sorted(set(output_iris), key=lambda x: x.lower())
|
|
343
|
-
filtered_iris = [iri for iri in output_iris if iri in graphs]
|
|
344
|
-
return "\n".join(filtered_iris[0:]) + "\n"
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
@click.command(cls=CmemcCommand, name="tree")
|
|
261
|
+
@click.command(cls=CmemcCommand, name="tree", hidden=True)
|
|
348
262
|
@click.option("-a", "--all", "all_", is_flag=True, help="Show tree of all (readable) graphs.")
|
|
349
263
|
@click.option("--raw", is_flag=True, help="Outputs raw JSON of the graph importTree API response.")
|
|
350
264
|
@click.option(
|
|
@@ -362,64 +276,17 @@ def _prepare_tree_output_id_only(iris: list[str], graphs: dict) -> str:
|
|
|
362
276
|
shell_complete=completion.graph_uris,
|
|
363
277
|
callback=tuple_to_list,
|
|
364
278
|
)
|
|
365
|
-
@click.
|
|
366
|
-
def tree_command(
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
Each graph is shown with label and IRI.
|
|
377
|
-
"""
|
|
378
|
-
graphs = get_graphs_as_dict()
|
|
379
|
-
if not iris and not all_:
|
|
380
|
-
raise UsageError(
|
|
381
|
-
"Either specify at least one graph IRI or use the "
|
|
382
|
-
"--all option to show the owl:imports tree of all graphs."
|
|
383
|
-
)
|
|
384
|
-
if all_:
|
|
385
|
-
iris = [str(_) for _ in graphs]
|
|
386
|
-
|
|
387
|
-
for iri in iris:
|
|
388
|
-
if iri not in graphs:
|
|
389
|
-
raise ClickException(UNKNOWN_GRAPH_ERROR.format(iri))
|
|
390
|
-
|
|
391
|
-
iris = sorted(iris, key=lambda x: graphs[x]["label"]["title"].lower())
|
|
392
|
-
|
|
393
|
-
if raw:
|
|
394
|
-
for iri in iris:
|
|
395
|
-
# direct output of the response for one requested graph
|
|
396
|
-
app.echo_info_json(get_graph_import_tree(iri))
|
|
397
|
-
return
|
|
398
|
-
|
|
399
|
-
if id_only:
|
|
400
|
-
app.echo_result(_prepare_tree_output_id_only(iris, graphs), nl=False)
|
|
401
|
-
return
|
|
402
|
-
|
|
403
|
-
# normal execution
|
|
404
|
-
output = ""
|
|
405
|
-
for iri in iris:
|
|
406
|
-
# get response for on requested graph
|
|
407
|
-
api_response = get_graph_import_tree(iri)
|
|
408
|
-
|
|
409
|
-
tree = _add_tree_nodes_recursive(Tree(), api_response["tree"], iri, graphs)
|
|
410
|
-
tree = _add_ignored_nodes(tree, api_response["ignored"])
|
|
411
|
-
|
|
412
|
-
# strip empty lines from the tree.show output
|
|
413
|
-
output += os.linesep.join(
|
|
414
|
-
[
|
|
415
|
-
line
|
|
416
|
-
for line in tree.show(key=lambda x: x.tag.lower(), stdout=False).splitlines() # type: ignore[arg-type, return-value]
|
|
417
|
-
if line.strip()
|
|
418
|
-
]
|
|
419
|
-
)
|
|
420
|
-
output += "\n"
|
|
421
|
-
# result output
|
|
422
|
-
app.echo_result(output, nl=False)
|
|
279
|
+
@click.pass_context
|
|
280
|
+
def tree_command(ctx: Context, all_: bool, raw: bool, id_only: bool, iris: list[str]) -> None:
|
|
281
|
+
"""(Hidden) Deprecated: use 'graph imports tree' instead."""
|
|
282
|
+
app: ApplicationContext = ctx.obj
|
|
283
|
+
app.echo_warning(
|
|
284
|
+
"The 'tree' command is deprecated and will be removed with the next major release."
|
|
285
|
+
" Please use the 'graph imports tree' command instead."
|
|
286
|
+
)
|
|
287
|
+
imports_cmd = graph.commands["imports"]
|
|
288
|
+
tree_cmd = imports_cmd.commands["tree"] # type: ignore[attr-defined]
|
|
289
|
+
ctx.invoke(tree_cmd, all_=all_, raw=raw, id_only=id_only, iris=iris)
|
|
423
290
|
|
|
424
291
|
|
|
425
292
|
@click.command(cls=CmemcCommand, name="list")
|
|
@@ -494,6 +361,15 @@ def _validate_export_command_input_parameters(
|
|
|
494
361
|
raise UsageError("Cannot output a binary file to terminal. Use --output-file option.")
|
|
495
362
|
|
|
496
363
|
|
|
364
|
+
def _write_graph_imports(ctx: Context, filename: str, iri: str) -> None:
|
|
365
|
+
imports = graph_imports_list.apply_filters(ctx=ctx, filter_=[("to-graph", iri)])
|
|
366
|
+
if imports:
|
|
367
|
+
imports_file = click.open_file(filename, "w")
|
|
368
|
+
for _ in imports:
|
|
369
|
+
imports_file.write(_["from_graph"] + "\n")
|
|
370
|
+
imports_file.close()
|
|
371
|
+
|
|
372
|
+
|
|
497
373
|
# pylint: disable=too-many-arguments,too-many-locals
|
|
498
374
|
@click.command(cls=CmemcCommand, name="export")
|
|
499
375
|
@click.option("-a", "--all", "all_", is_flag=True, help="Export all readable graphs.")
|
|
@@ -503,6 +379,12 @@ def _validate_export_command_input_parameters(
|
|
|
503
379
|
help="Export selected graph(s) and all graphs which are imported from "
|
|
504
380
|
"these selected graph(s).",
|
|
505
381
|
)
|
|
382
|
+
@click.option(
|
|
383
|
+
"--include-import-statements",
|
|
384
|
+
is_flag=True,
|
|
385
|
+
help="Save graph imports information from other graphs to the exported graphs "
|
|
386
|
+
"and write *.imports files.",
|
|
387
|
+
)
|
|
506
388
|
@click.option(
|
|
507
389
|
"--create-catalog",
|
|
508
390
|
is_flag=True,
|
|
@@ -560,11 +442,12 @@ def _validate_export_command_input_parameters(
|
|
|
560
442
|
shell_complete=completion.graph_uris,
|
|
561
443
|
callback=tuple_to_list,
|
|
562
444
|
)
|
|
563
|
-
@click.
|
|
564
|
-
def export_command( # noqa: PLR0913
|
|
565
|
-
|
|
445
|
+
@click.pass_context
|
|
446
|
+
def export_command( # noqa: C901, PLR0913
|
|
447
|
+
ctx: Context,
|
|
566
448
|
all_: bool,
|
|
567
449
|
include_imports: bool,
|
|
450
|
+
include_import_statements: bool,
|
|
568
451
|
create_catalog: bool,
|
|
569
452
|
output_dir: str,
|
|
570
453
|
output_file: str,
|
|
@@ -580,6 +463,7 @@ def export_command( # noqa: PLR0913
|
|
|
580
463
|
In case of directory export, .graph and .ttl files will be created
|
|
581
464
|
for each graph.
|
|
582
465
|
"""
|
|
466
|
+
app: ApplicationContext = ctx.obj
|
|
583
467
|
_validate_export_command_input_parameters(output_dir, output_file, compress, create_catalog)
|
|
584
468
|
iris = _check_and_extend_exported_graphs(iris, all_, include_imports, get_graphs_as_dict())
|
|
585
469
|
|
|
@@ -595,6 +479,8 @@ def export_command( # noqa: PLR0913
|
|
|
595
479
|
app, iris, template, f"{extension}.gz" if compress else f"{extension}"
|
|
596
480
|
)
|
|
597
481
|
_graph_file_names = _get_export_names(app, iris, template, f"{extension}.graph")
|
|
482
|
+
_graph_imports_file_names = _get_export_names(app, iris, template, f"{extension}.imports")
|
|
483
|
+
|
|
598
484
|
# create directory
|
|
599
485
|
if not SmartPath(output_dir).exists():
|
|
600
486
|
app.echo_warning("Output directory does not exist: " + "will create it.")
|
|
@@ -604,14 +490,21 @@ def export_command( # noqa: PLR0913
|
|
|
604
490
|
# join with given output directory and normalize full path
|
|
605
491
|
triple_file_name = os.path.normpath(SmartPath(output_dir) / _names[iri])
|
|
606
492
|
graph_file_name = os.path.normpath(SmartPath(output_dir) / _graph_file_names[iri])
|
|
493
|
+
imports_file_name = os.path.normpath(
|
|
494
|
+
SmartPath(output_dir) / _graph_imports_file_names[iri]
|
|
495
|
+
)
|
|
607
496
|
# output directory is created lazy
|
|
608
497
|
SmartPath(triple_file_name).parent.mkdir(parents=True, exist_ok=True)
|
|
609
498
|
# create and write the .ttl.graph metadata file
|
|
610
499
|
graph_file = click.open_file(graph_file_name, "w")
|
|
611
500
|
graph_file.write(iri + "\n")
|
|
501
|
+
graph_file.close()
|
|
502
|
+
if include_import_statements:
|
|
503
|
+
_write_graph_imports(ctx=ctx, filename=imports_file_name, iri=iri)
|
|
612
504
|
_get_graph_to_file(
|
|
613
505
|
iri, triple_file_name, app, numbers=(current, count), mime_type=mime_type
|
|
614
506
|
)
|
|
507
|
+
|
|
615
508
|
if create_catalog:
|
|
616
509
|
_create_xml_catalog_file(app, _names, output_dir)
|
|
617
510
|
return
|
|
@@ -706,6 +599,64 @@ def _get_buffer_and_content_type(
|
|
|
706
599
|
return buffer, content_type
|
|
707
600
|
|
|
708
601
|
|
|
602
|
+
def _create_graph_imports(ctx: Context, graphs: list[RdfGraphData]) -> None:
|
|
603
|
+
# Locate and invoke the 'create' subcommand under 'graph imports' command
|
|
604
|
+
app: ApplicationContext = ctx.obj
|
|
605
|
+
imports_cmd = graph.commands["imports"]
|
|
606
|
+
create_cmd = imports_cmd.commands["create"] # type: ignore[attr-defined]
|
|
607
|
+
for _ in graphs:
|
|
608
|
+
if not _.graph_imports:
|
|
609
|
+
continue
|
|
610
|
+
for _import in _.graph_imports:
|
|
611
|
+
from_graph = _import
|
|
612
|
+
to_graph = _.graph_iri
|
|
613
|
+
imports = graph_imports_list.apply_filters(
|
|
614
|
+
ctx=ctx, filter_=[("from-graph", from_graph), ("to-graph", to_graph)]
|
|
615
|
+
)
|
|
616
|
+
if imports:
|
|
617
|
+
app.echo_info(
|
|
618
|
+
f"Creating graph import from {from_graph} to {to_graph} ... ", nl=False
|
|
619
|
+
)
|
|
620
|
+
app.echo_warning("exists")
|
|
621
|
+
else:
|
|
622
|
+
ctx.invoke(create_cmd, from_graph=from_graph, to_graph=to_graph)
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
def _process_input_directory(input_path: str, iri: str) -> list[RdfGraphData]:
|
|
626
|
+
if iri is None:
|
|
627
|
+
# in case a directory is the source (and no IRI is given),
|
|
628
|
+
# the graph/nt file structure is crawled
|
|
629
|
+
graphs = read_rdf_graph_files(input_path)
|
|
630
|
+
else:
|
|
631
|
+
# in case a directory is the source AND IRI is given
|
|
632
|
+
graphs = []
|
|
633
|
+
for _ in _get_graph_supported_formats():
|
|
634
|
+
extension = mimetypes.guess_extension(_)
|
|
635
|
+
graphs += [
|
|
636
|
+
RdfGraphData(str(file), iri, [])
|
|
637
|
+
for file in SmartPath(input_path).glob(f"*{extension}")
|
|
638
|
+
]
|
|
639
|
+
graphs += [
|
|
640
|
+
RdfGraphData(str(file), iri, [])
|
|
641
|
+
for file in SmartPath(input_path).glob(f"*{extension}.gz")
|
|
642
|
+
]
|
|
643
|
+
return graphs
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
def _validate_graph_imports(graphs_to_import: list[RdfGraphData]) -> None:
|
|
647
|
+
graphs = {_["iri"] for _ in get_graphs()}
|
|
648
|
+
graphs.update({_.graph_iri for _ in graphs_to_import})
|
|
649
|
+
graph_imports: set[str] = set()
|
|
650
|
+
for graph_data in graphs_to_import:
|
|
651
|
+
for item in graph_data.graph_imports:
|
|
652
|
+
graph_imports.add(str(item))
|
|
653
|
+
if graph_imports - graphs:
|
|
654
|
+
raise click.UsageError(
|
|
655
|
+
f"The following graphs are not available, "
|
|
656
|
+
f"so you can not add import statements to them: {','.join(graph_imports - graphs)}"
|
|
657
|
+
)
|
|
658
|
+
|
|
659
|
+
|
|
709
660
|
@click.command(cls=CmemcCommand, name="import")
|
|
710
661
|
@click.option(
|
|
711
662
|
"--replace",
|
|
@@ -720,6 +671,11 @@ def _get_buffer_and_content_type(
|
|
|
720
671
|
"beginning of the process, so that you can still add multiple "
|
|
721
672
|
"files to one single graph (if it does not exist).",
|
|
722
673
|
)
|
|
674
|
+
@click.option(
|
|
675
|
+
"--include-import-statements",
|
|
676
|
+
is_flag=True,
|
|
677
|
+
help="Use *.imports files to re-apply the graph imports of the imported graphs.",
|
|
678
|
+
)
|
|
723
679
|
@click.argument(
|
|
724
680
|
"input_path",
|
|
725
681
|
required=True,
|
|
@@ -727,12 +683,13 @@ def _get_buffer_and_content_type(
|
|
|
727
683
|
type=ClickSmartPath(allow_dash=False, readable=True, remote_okay=True),
|
|
728
684
|
)
|
|
729
685
|
@click.argument("iri", type=click.STRING, required=False, shell_complete=completion.graph_uris)
|
|
730
|
-
@click.
|
|
731
|
-
def import_command(
|
|
732
|
-
|
|
686
|
+
@click.pass_context
|
|
687
|
+
def import_command( # noqa: PLR0913
|
|
688
|
+
ctx: Context,
|
|
733
689
|
input_path: str,
|
|
734
690
|
replace: bool,
|
|
735
691
|
skip_existing: bool,
|
|
692
|
+
include_import_statements: bool,
|
|
736
693
|
iri: str,
|
|
737
694
|
) -> None:
|
|
738
695
|
"""Import graph(s) to the store.
|
|
@@ -752,35 +709,25 @@ def import_command(
|
|
|
752
709
|
|
|
753
710
|
Note: Directories are scanned on the first level only (not recursively).
|
|
754
711
|
"""
|
|
712
|
+
app: ApplicationContext = ctx.obj
|
|
755
713
|
if replace and skip_existing:
|
|
756
714
|
raise UsageError(
|
|
757
715
|
"The options --replace and --skip-existing are mutually "
|
|
758
716
|
"exclusive, so please remove one of them."
|
|
759
717
|
)
|
|
760
718
|
# is an array of tuples like this [('path/to/triple.file', 'graph IRI')]
|
|
761
|
-
graphs: list[
|
|
719
|
+
graphs: list[RdfGraphData]
|
|
762
720
|
if SmartPath(input_path).is_dir():
|
|
763
721
|
validate_input_path(input_path)
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
graphs = read_rdf_graph_files(input_path)
|
|
768
|
-
else:
|
|
769
|
-
# in case a directory is the source AND IRI is given
|
|
770
|
-
graphs = []
|
|
771
|
-
for _ in _get_graph_supported_formats():
|
|
772
|
-
extension = mimetypes.guess_extension(_)
|
|
773
|
-
graphs += [(str(file), iri) for file in SmartPath(input_path).glob(f"*{extension}")]
|
|
774
|
-
graphs += [
|
|
775
|
-
(str(file), iri) for file in SmartPath(input_path).glob(f"*{extension}.gz")
|
|
776
|
-
]
|
|
777
|
-
|
|
722
|
+
graphs = _process_input_directory(input_path, iri)
|
|
723
|
+
if include_import_statements:
|
|
724
|
+
_validate_graph_imports(graphs)
|
|
778
725
|
elif SmartPath(input_path).is_file():
|
|
779
726
|
if iri is None:
|
|
780
727
|
raise UsageError(
|
|
781
728
|
"Either specify an input file AND a graph IRI or an input directory ONLY."
|
|
782
729
|
)
|
|
783
|
-
graphs = [(input_path, iri)]
|
|
730
|
+
graphs = [RdfGraphData(input_path, iri, [])]
|
|
784
731
|
else:
|
|
785
732
|
raise NotImplementedError(
|
|
786
733
|
"Input from special files (socket, FIFO, device file) is not supported."
|
|
@@ -790,7 +737,9 @@ def import_command(
|
|
|
790
737
|
processed_graphs: set = set()
|
|
791
738
|
count: int = len(graphs)
|
|
792
739
|
current: int = 1
|
|
793
|
-
for
|
|
740
|
+
for _ in graphs:
|
|
741
|
+
triple_file = _.file_path
|
|
742
|
+
graph_iri = _.graph_iri
|
|
794
743
|
app.echo_info(
|
|
795
744
|
f"Import file {current}/{count}: " f"{graph_iri} from {triple_file} ... ", nl=False
|
|
796
745
|
)
|
|
@@ -814,6 +763,9 @@ def import_command(
|
|
|
814
763
|
app.echo_debug("Access conditions refreshed.")
|
|
815
764
|
processed_graphs.add(graph_iri)
|
|
816
765
|
current += 1
|
|
766
|
+
# create graph imports
|
|
767
|
+
if include_import_statements:
|
|
768
|
+
_create_graph_imports(ctx, graphs)
|
|
817
769
|
|
|
818
770
|
|
|
819
771
|
@click.command(cls=CmemcCommand, name="delete")
|
|
@@ -824,6 +776,9 @@ def import_command(
|
|
|
824
776
|
help="Delete selected graph(s) and all writeable graphs which are "
|
|
825
777
|
"imported from these selected graph(s).",
|
|
826
778
|
)
|
|
779
|
+
@click.option(
|
|
780
|
+
"--include-import-statements", is_flag=True, help="Delete import reference of deleted graphs"
|
|
781
|
+
)
|
|
827
782
|
@click.argument(
|
|
828
783
|
"iris",
|
|
829
784
|
nargs=-1,
|
|
@@ -831,16 +786,25 @@ def import_command(
|
|
|
831
786
|
shell_complete=completion.writable_graph_uris,
|
|
832
787
|
callback=tuple_to_list,
|
|
833
788
|
)
|
|
834
|
-
@click.
|
|
789
|
+
@click.pass_context
|
|
835
790
|
def delete_command(
|
|
836
|
-
|
|
791
|
+
ctx: Context,
|
|
792
|
+
all_: bool,
|
|
793
|
+
include_imports: bool,
|
|
794
|
+
include_import_statements: bool,
|
|
795
|
+
iris: list[str],
|
|
837
796
|
) -> None:
|
|
838
797
|
"""Delete graph(s) from the store."""
|
|
798
|
+
app: ApplicationContext = ctx.obj
|
|
839
799
|
graphs = get_graphs_as_dict(writeable=True, readonly=False)
|
|
840
800
|
iris = _check_and_extend_exported_graphs(iris, all_, include_imports, graphs)
|
|
841
|
-
|
|
801
|
+
imports_to_be_deleted = []
|
|
842
802
|
count: int = len(iris)
|
|
843
803
|
for current, iri in enumerate(iris, start=1):
|
|
804
|
+
imports_to_be_deleted += graph_imports_list.apply_filters(
|
|
805
|
+
ctx=ctx, filter_=[("to-graph", iri)]
|
|
806
|
+
)
|
|
807
|
+
|
|
844
808
|
app.echo_info(f"Drop graph {current}/{count}: {iri} ... ", nl=False)
|
|
845
809
|
graph_api.delete(iri)
|
|
846
810
|
app.echo_success("done")
|
|
@@ -848,6 +812,13 @@ def delete_command(
|
|
|
848
812
|
if iri == refresh.AUTHORIZATION_GRAPH_URI:
|
|
849
813
|
refresh.get()
|
|
850
814
|
app.echo_debug("Access conditions refreshed.")
|
|
815
|
+
if include_import_statements:
|
|
816
|
+
# Locate and invoke the 'delete' subcommand under 'graph imports' command
|
|
817
|
+
imports_cmd = graph.commands["imports"]
|
|
818
|
+
delete_cmd = imports_cmd.commands["delete"] # type: ignore[attr-defined]
|
|
819
|
+
for _ in imports_to_be_deleted:
|
|
820
|
+
if _["from_graph"] not in iris:
|
|
821
|
+
ctx.invoke(delete_cmd, from_graph=_["from_graph"], to_graph=_["to_graph"])
|
|
851
822
|
|
|
852
823
|
|
|
853
824
|
@click.command(cls=CmemcCommand, name="open")
|
|
@@ -914,3 +885,4 @@ graph.add_command(delete_command)
|
|
|
914
885
|
graph.add_command(import_command)
|
|
915
886
|
graph.add_command(open_command)
|
|
916
887
|
graph.add_command(validation_group)
|
|
888
|
+
graph.add_command(imports_group)
|