cmem-client 0.5.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_client/__init__.py +13 -0
- cmem_client/auth_provider/__init__.py +14 -0
- cmem_client/auth_provider/abc.py +124 -0
- cmem_client/auth_provider/client_credentials.py +207 -0
- cmem_client/auth_provider/password.py +252 -0
- cmem_client/auth_provider/prefetched_token.py +153 -0
- cmem_client/client.py +485 -0
- cmem_client/components/__init__.py +10 -0
- cmem_client/components/graph_store.py +316 -0
- cmem_client/components/marketplace.py +179 -0
- cmem_client/components/sparql_wrapper.py +53 -0
- cmem_client/components/workspace.py +194 -0
- cmem_client/config.py +364 -0
- cmem_client/exceptions.py +82 -0
- cmem_client/logging_utils.py +49 -0
- cmem_client/models/__init__.py +16 -0
- cmem_client/models/access_condition.py +147 -0
- cmem_client/models/base.py +30 -0
- cmem_client/models/dataset.py +32 -0
- cmem_client/models/error.py +67 -0
- cmem_client/models/graph.py +26 -0
- cmem_client/models/item.py +143 -0
- cmem_client/models/logging_config.py +51 -0
- cmem_client/models/package.py +35 -0
- cmem_client/models/project.py +46 -0
- cmem_client/models/python_package.py +26 -0
- cmem_client/models/token.py +40 -0
- cmem_client/models/url.py +34 -0
- cmem_client/models/workflow.py +80 -0
- cmem_client/repositories/__init__.py +15 -0
- cmem_client/repositories/access_conditions.py +62 -0
- cmem_client/repositories/base/__init__.py +12 -0
- cmem_client/repositories/base/abc.py +138 -0
- cmem_client/repositories/base/paged_list.py +63 -0
- cmem_client/repositories/base/plain_list.py +39 -0
- cmem_client/repositories/base/task_search.py +70 -0
- cmem_client/repositories/datasets.py +36 -0
- cmem_client/repositories/graph_imports.py +93 -0
- cmem_client/repositories/graphs.py +458 -0
- cmem_client/repositories/marketplace_packages.py +486 -0
- cmem_client/repositories/projects.py +214 -0
- cmem_client/repositories/protocols/__init__.py +15 -0
- cmem_client/repositories/protocols/create_item.py +125 -0
- cmem_client/repositories/protocols/delete_item.py +95 -0
- cmem_client/repositories/protocols/export_item.py +114 -0
- cmem_client/repositories/protocols/import_item.py +141 -0
- cmem_client/repositories/python_packages.py +58 -0
- cmem_client/repositories/workflows.py +143 -0
- cmem_client-0.5.0.dist-info/METADATA +64 -0
- cmem_client-0.5.0.dist-info/RECORD +52 -0
- cmem_client-0.5.0.dist-info/WHEEL +4 -0
- cmem_client-0.5.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
"""Repository for eccenca marketplace package operations.
|
|
2
|
+
|
|
3
|
+
This module provides the PackagesRepository class for managing marketplace packages
|
|
4
|
+
in Corporate Memory.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import shutil
|
|
10
|
+
import tempfile
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import TYPE_CHECKING, ClassVar
|
|
13
|
+
from zipfile import BadZipFile, ZipFile
|
|
14
|
+
|
|
15
|
+
from eccenca_marketplace_client.dependencies import PythonPackageDependency, VocabularyDependency
|
|
16
|
+
from eccenca_marketplace_client.file_specs import GraphFileSpec, ProjectFileSpec
|
|
17
|
+
from eccenca_marketplace_client.ontology import (
|
|
18
|
+
NS_IRI,
|
|
19
|
+
get_data_graph_iri,
|
|
20
|
+
get_delete_query,
|
|
21
|
+
get_fetch_query,
|
|
22
|
+
get_ontology_graph,
|
|
23
|
+
)
|
|
24
|
+
from eccenca_marketplace_client.package_graph import PackageGraph
|
|
25
|
+
from eccenca_marketplace_client.package_version import PackageVersion
|
|
26
|
+
|
|
27
|
+
from cmem_client.exceptions import (
|
|
28
|
+
BaseError,
|
|
29
|
+
MarketplacePackagesExportError,
|
|
30
|
+
MarketplacePackagesImportError,
|
|
31
|
+
)
|
|
32
|
+
from cmem_client.models.python_package import PythonPackage
|
|
33
|
+
from cmem_client.repositories.graph_imports import GraphImport
|
|
34
|
+
|
|
35
|
+
if TYPE_CHECKING:
|
|
36
|
+
from collections.abc import Sequence
|
|
37
|
+
|
|
38
|
+
from eccenca_marketplace_client.fields import PackageVersionIdentifier # noqa: TC002 # Pydantic needs this at runtime
|
|
39
|
+
|
|
40
|
+
from cmem_client.models.item import DirectoryImportItem, FileImportItem, ImportItem, ZipImportItem
|
|
41
|
+
from cmem_client.models.package import Package
|
|
42
|
+
from cmem_client.repositories.base.abc import Repository
|
|
43
|
+
from cmem_client.repositories.graphs import GraphImportConfig
|
|
44
|
+
from cmem_client.repositories.protocols.delete_item import DeleteConfig, DeleteItemProtocol
|
|
45
|
+
from cmem_client.repositories.protocols.export_item import ExportConfig, ExportItemProtocol
|
|
46
|
+
from cmem_client.repositories.protocols.import_item import ImportConfig, ImportItemProtocol
|
|
47
|
+
|
|
48
|
+
MAX_DEPENDENCY_DEPTH = 5
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class MarketplacePackagesImportConfig(ImportConfig):
|
|
52
|
+
"""Configuration for marketplace package import operations.
|
|
53
|
+
|
|
54
|
+
Attributes:
|
|
55
|
+
ignore_dependencies: If True, skips installation of package dependencies.
|
|
56
|
+
install_from_marketplace: If True, downloads packages from the marketplace server.
|
|
57
|
+
If False, loads packages from local filesystem.
|
|
58
|
+
package_version: Specific version to install. If None, installs the latest version.
|
|
59
|
+
dependency_level: Current recursion depth for dependency resolution. Used internally
|
|
60
|
+
to prevent infinite recursion. Should not be set manually.
|
|
61
|
+
use_cache: Weather to use the cache directory to look packages up which have already been downloaded.
|
|
62
|
+
To prevent the cache entirely, set this up in the marketplace component.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
ignore_dependencies: bool = False
|
|
66
|
+
install_from_marketplace: bool = True
|
|
67
|
+
package_version: PackageVersionIdentifier | None = None
|
|
68
|
+
dependency_level: int = 0
|
|
69
|
+
use_cache: bool = True
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class MarketplacePackagesExportConfig(ExportConfig):
|
|
73
|
+
"""Package export configuration"""
|
|
74
|
+
|
|
75
|
+
export_as_zip: bool = True
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class MarketplacePackagesDeleteConfig(DeleteConfig):
|
|
79
|
+
"""Package deletion configuration"""
|
|
80
|
+
|
|
81
|
+
skip_missing_dependencies: bool = True
|
|
82
|
+
skip_missing_graphs: bool = True
|
|
83
|
+
skip_missing_projects: bool = True
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class MarketplacePackagesRepository(Repository, ImportItemProtocol, ExportItemProtocol, DeleteItemProtocol):
|
|
87
|
+
"""Repository for marketplace package operations."""
|
|
88
|
+
|
|
89
|
+
_dict: dict[str, Package]
|
|
90
|
+
_allowed_import_items: ClassVar[Sequence[type[ImportItem]]] = [FileImportItem, ZipImportItem, DirectoryImportItem]
|
|
91
|
+
_graph: PackageGraph
|
|
92
|
+
|
|
93
|
+
def fetch_data(self) -> None:
|
|
94
|
+
"""Fetch installed packages from the config graph via SPARQL query.
|
|
95
|
+
|
|
96
|
+
Queries the config graph for all installed packages and their metadata.
|
|
97
|
+
"""
|
|
98
|
+
result = self._client.store.sparql.query(get_fetch_query())
|
|
99
|
+
|
|
100
|
+
self._dict = {}
|
|
101
|
+
self._graph = PackageGraph()
|
|
102
|
+
for row in result:
|
|
103
|
+
manifest_json = str(row[0]) # type: ignore[index]
|
|
104
|
+
package_version = PackageVersion.from_json(manifest_json)
|
|
105
|
+
package_id = package_version.manifest.package_id
|
|
106
|
+
package = Package(package_version=package_version)
|
|
107
|
+
self._dict[package_id] = package
|
|
108
|
+
self._graph.add_package(package.package_version)
|
|
109
|
+
|
|
110
|
+
def _add_marketplace_vocabulary(self) -> None:
|
|
111
|
+
"""Ensure the package vocabulary/ontology is imported into CMEM.
|
|
112
|
+
|
|
113
|
+
Checks if the marketplace ontology graph exists. If not, generates the
|
|
114
|
+
ontology from the marketplace client models and imports it as a vocabulary.
|
|
115
|
+
"""
|
|
116
|
+
ontology_graph = get_ontology_graph()
|
|
117
|
+
|
|
118
|
+
self._client.graphs.fetch_data()
|
|
119
|
+
|
|
120
|
+
if NS_IRI not in self._client.graphs:
|
|
121
|
+
with tempfile.NamedTemporaryFile(mode="wb", suffix=".ttl", delete=False) as tmp_file:
|
|
122
|
+
tmp_path = Path(tmp_file.name)
|
|
123
|
+
tmp_file.write(ontology_graph.serialize(format="turtle").encode("utf-8"))
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
self._client.graphs.import_item(
|
|
127
|
+
path=tmp_path, replace=False, key=None, configuration=GraphImportConfig(register_as_vocabulary=True)
|
|
128
|
+
)
|
|
129
|
+
finally:
|
|
130
|
+
tmp_path.unlink(missing_ok=True)
|
|
131
|
+
|
|
132
|
+
def _import_item( # noqa: C901, PLR0912
|
|
133
|
+
self,
|
|
134
|
+
path: Path | None = None,
|
|
135
|
+
replace: bool = False,
|
|
136
|
+
key: str | None = None,
|
|
137
|
+
configuration: MarketplacePackagesImportConfig | None = None,
|
|
138
|
+
) -> str:
|
|
139
|
+
"""Import a marketplace package from archive or marketplace server.
|
|
140
|
+
|
|
141
|
+
Extracts the package manifest from the archive, adds package metadata to the
|
|
142
|
+
config graph as RDF triples, then delegates to the appropriate repositories
|
|
143
|
+
based on the package.
|
|
144
|
+
|
|
145
|
+
If the import fails, all imported resources (graphs, projects, imports, packages)
|
|
146
|
+
are automatically rolled back to maintain consistency.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
path: Path to the package archive file (.cpa) or directory. Required when
|
|
150
|
+
configuration.install_from_marketplace is False.
|
|
151
|
+
replace: Whether to replace an existing package with the same ID.
|
|
152
|
+
key: Package identifier for marketplace installation. Required when
|
|
153
|
+
configuration.install_from_marketplace is True.
|
|
154
|
+
configuration: Import configuration controlling source (marketplace vs. local),
|
|
155
|
+
dependency resolution, and version selection.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
The package_id of the successfully imported package.
|
|
159
|
+
|
|
160
|
+
Raises:
|
|
161
|
+
MarketplacePackagesImportError: If required parameters are missing, the package
|
|
162
|
+
already exists (when replace=False), or the import fails.
|
|
163
|
+
"""
|
|
164
|
+
if configuration is None:
|
|
165
|
+
configuration = MarketplacePackagesImportConfig()
|
|
166
|
+
|
|
167
|
+
if path is None and not configuration.install_from_marketplace:
|
|
168
|
+
raise MarketplacePackagesImportError("No import path specified.")
|
|
169
|
+
|
|
170
|
+
package_version = self._get_package_version(configuration, key, path)
|
|
171
|
+
|
|
172
|
+
manifest = package_version.manifest
|
|
173
|
+
|
|
174
|
+
imported_graphs: list[str] = []
|
|
175
|
+
imported_projects: list[str] = []
|
|
176
|
+
imported_imports: list[str] = []
|
|
177
|
+
imported_python_packages: list[str] = []
|
|
178
|
+
imported_vocabulary_packages: list[str] = []
|
|
179
|
+
|
|
180
|
+
if manifest.package_id in self._dict:
|
|
181
|
+
if replace:
|
|
182
|
+
self.delete_item(manifest.package_id)
|
|
183
|
+
else:
|
|
184
|
+
raise MarketplacePackagesImportError("Package already imported. Try replace.")
|
|
185
|
+
|
|
186
|
+
if not configuration.ignore_dependencies:
|
|
187
|
+
# import python package dependencies first
|
|
188
|
+
for dependency in manifest.dependencies:
|
|
189
|
+
if isinstance(dependency, PythonPackageDependency):
|
|
190
|
+
self._client.python_packages.create_item(
|
|
191
|
+
item=PythonPackage(name=dependency.pypi_id), skip_if_existing=True
|
|
192
|
+
)
|
|
193
|
+
imported_python_packages.append(dependency.pypi_id)
|
|
194
|
+
|
|
195
|
+
for dependency in manifest.dependencies:
|
|
196
|
+
if isinstance(dependency, VocabularyDependency) and dependency.package_id not in self._dict:
|
|
197
|
+
if configuration.dependency_level >= MAX_DEPENDENCY_DEPTH:
|
|
198
|
+
self.logger.warning(
|
|
199
|
+
"Skipping dependency '%s' because the max depth of '%s' was reached.",
|
|
200
|
+
dependency,
|
|
201
|
+
MAX_DEPENDENCY_DEPTH,
|
|
202
|
+
)
|
|
203
|
+
continue
|
|
204
|
+
self._client.marketplace_packages.import_item(
|
|
205
|
+
key=dependency.package_id,
|
|
206
|
+
configuration=MarketplacePackagesImportConfig(
|
|
207
|
+
dependency_level=configuration.dependency_level + 1,
|
|
208
|
+
),
|
|
209
|
+
skip_if_existing=True,
|
|
210
|
+
)
|
|
211
|
+
imported_vocabulary_packages.append(dependency.package_id)
|
|
212
|
+
|
|
213
|
+
try:
|
|
214
|
+
# import graphs first
|
|
215
|
+
for graph in manifest.get_graphs():
|
|
216
|
+
graph_iri = self._client.graphs.import_item(
|
|
217
|
+
key=str(graph.graph_iri),
|
|
218
|
+
path=package_version.get_file_path(graph.file_path),
|
|
219
|
+
replace=replace,
|
|
220
|
+
configuration=GraphImportConfig(register_as_vocabulary=graph.register_as_vocabulary),
|
|
221
|
+
)
|
|
222
|
+
imported_graphs.append(graph_iri)
|
|
223
|
+
|
|
224
|
+
# import projects after graphs
|
|
225
|
+
for project in manifest.get_projects():
|
|
226
|
+
project_id = self._client.projects.import_item(
|
|
227
|
+
key=str(project.project_id),
|
|
228
|
+
path=package_version.get_file_path(project.file_path),
|
|
229
|
+
replace=replace,
|
|
230
|
+
)
|
|
231
|
+
imported_projects.append(project_id)
|
|
232
|
+
|
|
233
|
+
# add graph imports
|
|
234
|
+
for graph in manifest.get_graphs():
|
|
235
|
+
to_graph = str(graph.graph_iri)
|
|
236
|
+
for from_graph in graph.import_into:
|
|
237
|
+
new_import = GraphImport(from_graph=str(from_graph), to_graph=to_graph)
|
|
238
|
+
self._client.graph_imports.create_item(item=new_import, skip_if_existing=True)
|
|
239
|
+
imported_imports.append(new_import.get_id())
|
|
240
|
+
|
|
241
|
+
self._add_package_triples(package_version)
|
|
242
|
+
|
|
243
|
+
# Rollback graphs + imports, projects, python packages
|
|
244
|
+
except (BaseError, BadZipFile) as error:
|
|
245
|
+
self._client.logger.exception("Failed to import package '%s'", package_version.manifest.package_id)
|
|
246
|
+
self._client.logger.warning("Deleting all imports from this package.")
|
|
247
|
+
for graph_import in imported_imports:
|
|
248
|
+
self._client.graph_imports.delete_item(key=graph_import, skip_if_missing=True)
|
|
249
|
+
for graph_iri in imported_graphs:
|
|
250
|
+
self._client.graphs.delete_item(key=graph_iri, skip_if_missing=True)
|
|
251
|
+
for project_id in imported_projects:
|
|
252
|
+
self._client.projects.delete_item(key=project_id, skip_if_missing=True)
|
|
253
|
+
for python_package_id in imported_python_packages:
|
|
254
|
+
self._client.python_packages.delete_item(key=python_package_id, skip_if_missing=True)
|
|
255
|
+
for vocabulary_id in imported_vocabulary_packages:
|
|
256
|
+
self._client.marketplace_packages.delete_item(key=vocabulary_id, skip_if_missing=True)
|
|
257
|
+
raise MarketplacePackagesImportError(f"Failed to import package ({error!s})") from error
|
|
258
|
+
|
|
259
|
+
self.fetch_data()
|
|
260
|
+
return str(manifest.package_id)
|
|
261
|
+
|
|
262
|
+
def _export_item( # noqa: C901
|
|
263
|
+
self,
|
|
264
|
+
key: str,
|
|
265
|
+
path: Path | None,
|
|
266
|
+
replace: bool = False,
|
|
267
|
+
configuration: MarketplacePackagesExportConfig | None = None,
|
|
268
|
+
) -> Path:
|
|
269
|
+
"""Export a marketplace package from eccenca Corporate Memory.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
key: The package identifier.
|
|
273
|
+
path: The file path where the item should be exported. If None, a path
|
|
274
|
+
should be generated by the implementation.
|
|
275
|
+
replace: Whether to replace existing files at the target path.
|
|
276
|
+
configuration: Optional configuration for export behavior.
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
The exported file path.
|
|
280
|
+
"""
|
|
281
|
+
if configuration is None:
|
|
282
|
+
configuration = MarketplacePackagesExportConfig()
|
|
283
|
+
|
|
284
|
+
if key not in self._dict:
|
|
285
|
+
raise MarketplacePackagesExportError("Key is not a valid package identifier")
|
|
286
|
+
|
|
287
|
+
package = self._dict[key]
|
|
288
|
+
manifest_json_str = self._get_manifest_json(package.package_version.manifest.package_id)
|
|
289
|
+
manifest = PackageVersion.from_json(manifest_json_str).manifest
|
|
290
|
+
|
|
291
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
292
|
+
tmp_path = Path(tmpdir)
|
|
293
|
+
exported_files: list[tuple[Path, str]] = []
|
|
294
|
+
|
|
295
|
+
for file_spec in manifest.files:
|
|
296
|
+
if isinstance(file_spec, GraphFileSpec):
|
|
297
|
+
file_path = tmp_path / file_spec.file_path
|
|
298
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
299
|
+
exported_path = self._client.graphs.export_item(
|
|
300
|
+
key=str(file_spec.graph_iri),
|
|
301
|
+
path=file_path,
|
|
302
|
+
)
|
|
303
|
+
exported_files.append((exported_path, file_spec.file_path))
|
|
304
|
+
|
|
305
|
+
if isinstance(file_spec, ProjectFileSpec):
|
|
306
|
+
file_path = tmp_path / file_spec.file_path
|
|
307
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
308
|
+
exported_path = self._client.projects.export_item(
|
|
309
|
+
key=file_spec.project_id,
|
|
310
|
+
path=file_path,
|
|
311
|
+
)
|
|
312
|
+
exported_files.append((exported_path, file_spec.file_path))
|
|
313
|
+
|
|
314
|
+
manifest_path = tmp_path / "manifest.json"
|
|
315
|
+
manifest_path.write_text(manifest_json_str, encoding="utf-8")
|
|
316
|
+
|
|
317
|
+
if path is None:
|
|
318
|
+
path = Path.cwd() / (
|
|
319
|
+
f"{manifest.package_id}.zip" if configuration.export_as_zip else manifest.package_id
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
if path.exists() and not replace:
|
|
323
|
+
item_type = "File" if configuration.export_as_zip else "Directory"
|
|
324
|
+
raise MarketplacePackagesExportError(f"{item_type} {path} already exists and replace is False")
|
|
325
|
+
|
|
326
|
+
if configuration.export_as_zip:
|
|
327
|
+
with ZipFile(path, mode="w") as zipf:
|
|
328
|
+
for file_path, file_name in exported_files:
|
|
329
|
+
zipf.write(file_path, file_name)
|
|
330
|
+
zipf.write(manifest_path, "manifest.json")
|
|
331
|
+
else:
|
|
332
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
333
|
+
for file_path, file_name in exported_files:
|
|
334
|
+
file_dest = path / file_name
|
|
335
|
+
file_dest.parent.mkdir(parents=True, exist_ok=True)
|
|
336
|
+
shutil.copy(file_path, file_dest)
|
|
337
|
+
shutil.copy(manifest_path, path / "manifest.json")
|
|
338
|
+
|
|
339
|
+
return path
|
|
340
|
+
|
|
341
|
+
def _delete_item(self, key: str, configuration: MarketplacePackagesDeleteConfig | None = None) -> None:
|
|
342
|
+
"""Delete a package by its package_id.
|
|
343
|
+
|
|
344
|
+
This method need be extended for new FileSpecs.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
key: The package_id of the package to delete.
|
|
348
|
+
configuration: Optional configuration to delete.
|
|
349
|
+
"""
|
|
350
|
+
if not configuration:
|
|
351
|
+
# apply default configuration
|
|
352
|
+
configuration = MarketplacePackagesDeleteConfig()
|
|
353
|
+
package = self._dict[key]
|
|
354
|
+
manifest = package.package_version.manifest
|
|
355
|
+
package_id = manifest.package_id
|
|
356
|
+
package_iri = package.package_version.iri()
|
|
357
|
+
|
|
358
|
+
for dependency in manifest.dependencies:
|
|
359
|
+
if isinstance(dependency, PythonPackageDependency):
|
|
360
|
+
dependants = self._graph.get_python_dependants(dependency.pypi_id)
|
|
361
|
+
dependants.remove(package_id)
|
|
362
|
+
if len(dependants) > 0:
|
|
363
|
+
self._logger.warning(
|
|
364
|
+
f"Python plugin '{dependency.pypi_id}' can not be removed since it is in"
|
|
365
|
+
f"use by other packages: {', '.join(dependants)}"
|
|
366
|
+
)
|
|
367
|
+
continue
|
|
368
|
+
self._client.python_packages.delete_item(
|
|
369
|
+
key=dependency.pypi_id, skip_if_missing=configuration.skip_missing_dependencies
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
if isinstance(dependency, VocabularyDependency):
|
|
373
|
+
dependants = self._graph.get_package_dependants(dependency.package_id)
|
|
374
|
+
dependants.remove(package_id)
|
|
375
|
+
if len(dependants) > 0:
|
|
376
|
+
self._logger.warning(
|
|
377
|
+
f"Package '{dependency.package_id}' can not be removed since it is in"
|
|
378
|
+
f"use by other packages: {', '.join(dependants)}"
|
|
379
|
+
)
|
|
380
|
+
continue
|
|
381
|
+
self._client.marketplace_packages.delete_item(
|
|
382
|
+
key=dependency.package_id, skip_if_missing=configuration.skip_missing_dependencies
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
for project in manifest.get_projects():
|
|
386
|
+
self._client.projects.delete_item(
|
|
387
|
+
key=project.project_id, skip_if_missing=configuration.skip_missing_projects
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
for graph in manifest.get_graphs():
|
|
391
|
+
self._client.graphs.delete_item(key=str(graph.graph_iri), skip_if_missing=configuration.skip_missing_graphs)
|
|
392
|
+
for from_graph in graph.import_into:
|
|
393
|
+
deleted_import = GraphImport(from_graph=str(from_graph), to_graph=str(graph.graph_iri))
|
|
394
|
+
self._client.graph_imports.delete_item(key=deleted_import.get_id(), skip_if_missing=True)
|
|
395
|
+
|
|
396
|
+
self._client.store.sparql.update(get_delete_query(package_iri))
|
|
397
|
+
|
|
398
|
+
def _add_package_triples(self, package: PackageVersion) -> None:
|
|
399
|
+
"""Add a package to the data config graph in the Corporate Memory instance.
|
|
400
|
+
|
|
401
|
+
Converts the package metadata to RDF triples and inserts them into the
|
|
402
|
+
config data graph using SPARQL UPDATE. Uses rdflib to programmatically
|
|
403
|
+
construct the RDF graph, avoiding manual string escaping.
|
|
404
|
+
|
|
405
|
+
Args:
|
|
406
|
+
package: The package metadata to add to the catalog.
|
|
407
|
+
"""
|
|
408
|
+
self._add_marketplace_vocabulary()
|
|
409
|
+
g = package.to_rdf_graph()
|
|
410
|
+
|
|
411
|
+
triples = g.serialize(format="nt")
|
|
412
|
+
|
|
413
|
+
sparql_update = f"""
|
|
414
|
+
INSERT DATA {{
|
|
415
|
+
GRAPH <{get_data_graph_iri()}> {{
|
|
416
|
+
{triples}
|
|
417
|
+
}}
|
|
418
|
+
}}
|
|
419
|
+
"""
|
|
420
|
+
|
|
421
|
+
self._client.store.sparql.update(sparql_update)
|
|
422
|
+
|
|
423
|
+
def _get_manifest_json(self, package_id: str) -> str:
|
|
424
|
+
"""Fetch manifest JSON from config graph via SPARQL for a specific package_id."""
|
|
425
|
+
package_iri = f"{get_data_graph_iri()}{package_id}"
|
|
426
|
+
query = f"""
|
|
427
|
+
PREFIX eccm: <{NS_IRI}>
|
|
428
|
+
SELECT ?manifest_json
|
|
429
|
+
WHERE {{
|
|
430
|
+
GRAPH <{get_data_graph_iri()}> {{
|
|
431
|
+
<{package_iri}> eccm:property_manifest_json ?manifest_json .
|
|
432
|
+
}}
|
|
433
|
+
}}
|
|
434
|
+
"""
|
|
435
|
+
response = self._client.http.get(
|
|
436
|
+
url=self._client.config.url_explore_api / "/proxy/default/sparql",
|
|
437
|
+
headers={"Accept": "application/sparql-results+json"},
|
|
438
|
+
params={"query": query},
|
|
439
|
+
)
|
|
440
|
+
response.raise_for_status()
|
|
441
|
+
bindings = response.json()["results"]["bindings"]
|
|
442
|
+
return str(bindings[0]["manifest_json"]["value"])
|
|
443
|
+
|
|
444
|
+
def _get_package_version(
|
|
445
|
+
self, configuration: MarketplacePackagesImportConfig, key: str | None, path: Path | None
|
|
446
|
+
) -> PackageVersion:
|
|
447
|
+
"""Load package version from marketplace server or local filesystem.
|
|
448
|
+
|
|
449
|
+
When installing from the marketplace server, the package is downloaded into the cache directory if path is
|
|
450
|
+
set to None. The method then returns the path to the cached file so it can be reused later.
|
|
451
|
+
If path is provided, the package is downloaded to that location instead.
|
|
452
|
+
|
|
453
|
+
When installing from the local filesystem, path must point directly to the package to be installed.
|
|
454
|
+
|
|
455
|
+
Args:
|
|
456
|
+
configuration: Import configuration specifying the package source and version.
|
|
457
|
+
key: Package identifier for marketplace downloads. Required when
|
|
458
|
+
configuration.install_from_marketplace is True.
|
|
459
|
+
path: Local filesystem path for non-marketplace installations. Required when
|
|
460
|
+
configuration.install_from_marketplace is False.
|
|
461
|
+
|
|
462
|
+
Returns:
|
|
463
|
+
The package version from marketplace server or local filesystem.
|
|
464
|
+
|
|
465
|
+
Raises:
|
|
466
|
+
MarketplacePackagesImportError: If path is None when loading from local filesystem.
|
|
467
|
+
"""
|
|
468
|
+
if configuration.install_from_marketplace:
|
|
469
|
+
if key is None:
|
|
470
|
+
raise MarketplacePackagesImportError("No key was provided to download from marketplace.")
|
|
471
|
+
|
|
472
|
+
downloaded_package_path = self._client.marketplace.download_package(
|
|
473
|
+
path=path,
|
|
474
|
+
package_id=key,
|
|
475
|
+
package_version=configuration.package_version,
|
|
476
|
+
use_cache=configuration.use_cache,
|
|
477
|
+
)
|
|
478
|
+
package_version = PackageVersion.from_archive(downloaded_package_path)
|
|
479
|
+
else:
|
|
480
|
+
if path is None:
|
|
481
|
+
raise MarketplacePackagesImportError("No package path specified")
|
|
482
|
+
package_version = (
|
|
483
|
+
PackageVersion.from_directory(path) if path.is_dir() else PackageVersion.from_archive(path)
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
return package_version
|