cmem-client 0.5.0__tar.gz → 0.6.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.
Files changed (54) hide show
  1. {cmem_client-0.5.0 → cmem_client-0.6.0}/PKG-INFO +2 -2
  2. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/client.py +16 -0
  3. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/exceptions.py +12 -0
  4. cmem_client-0.6.0/cmem_client/models/resource.py +25 -0
  5. cmem_client-0.6.0/cmem_client/repositories/files.py +181 -0
  6. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/repositories/marketplace_packages.py +68 -7
  7. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/repositories/protocols/delete_item.py +1 -2
  8. {cmem_client-0.5.0 → cmem_client-0.6.0}/pyproject.toml +2 -2
  9. {cmem_client-0.5.0 → cmem_client-0.6.0}/LICENSE +0 -0
  10. {cmem_client-0.5.0 → cmem_client-0.6.0}/README.md +0 -0
  11. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/__init__.py +0 -0
  12. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/auth_provider/__init__.py +0 -0
  13. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/auth_provider/abc.py +0 -0
  14. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/auth_provider/client_credentials.py +0 -0
  15. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/auth_provider/password.py +0 -0
  16. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/auth_provider/prefetched_token.py +0 -0
  17. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/components/__init__.py +0 -0
  18. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/components/graph_store.py +0 -0
  19. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/components/marketplace.py +0 -0
  20. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/components/sparql_wrapper.py +0 -0
  21. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/components/workspace.py +0 -0
  22. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/config.py +0 -0
  23. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/logging_utils.py +0 -0
  24. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/models/__init__.py +0 -0
  25. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/models/access_condition.py +0 -0
  26. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/models/base.py +0 -0
  27. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/models/dataset.py +0 -0
  28. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/models/error.py +0 -0
  29. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/models/graph.py +0 -0
  30. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/models/item.py +0 -0
  31. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/models/logging_config.py +0 -0
  32. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/models/package.py +0 -0
  33. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/models/project.py +0 -0
  34. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/models/python_package.py +0 -0
  35. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/models/token.py +0 -0
  36. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/models/url.py +0 -0
  37. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/models/workflow.py +0 -0
  38. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/repositories/__init__.py +0 -0
  39. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/repositories/access_conditions.py +0 -0
  40. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/repositories/base/__init__.py +0 -0
  41. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/repositories/base/abc.py +0 -0
  42. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/repositories/base/paged_list.py +0 -0
  43. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/repositories/base/plain_list.py +0 -0
  44. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/repositories/base/task_search.py +0 -0
  45. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/repositories/datasets.py +0 -0
  46. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/repositories/graph_imports.py +0 -0
  47. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/repositories/graphs.py +0 -0
  48. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/repositories/projects.py +0 -0
  49. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/repositories/protocols/__init__.py +0 -0
  50. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/repositories/protocols/create_item.py +0 -0
  51. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/repositories/protocols/export_item.py +0 -0
  52. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/repositories/protocols/import_item.py +0 -0
  53. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/repositories/python_packages.py +0 -0
  54. {cmem_client-0.5.0 → cmem_client-0.6.0}/cmem_client/repositories/workflows.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cmem-client
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: Next generation eccenca Corporate Memory client library.
5
5
  License: Apache-2.0
6
6
  License-File: LICENSE
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3
14
14
  Classifier: Programming Language :: Python :: 3.13
15
15
  Classifier: Programming Language :: Python :: 3.14
16
16
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
- Requires-Dist: eccenca-marketplace-client (>=0.5.0,<0.6.0)
17
+ Requires-Dist: eccenca-marketplace-client (>=0.6.0,<0.7.0)
18
18
  Requires-Dist: httpx (>=0.27.0,<0.28.0)
19
19
  Requires-Dist: pydantic (>=2.8.2,<3.0.0)
20
20
  Requires-Dist: pyjwt (>=2.8.0,<3.0.0)
@@ -40,6 +40,7 @@ from cmem_client.config import Config
40
40
  from cmem_client.exceptions import ClientNoAuthProviderError
41
41
  from cmem_client.logging_utils import install_trace_logger
42
42
  from cmem_client.models.logging_config import LoggingConfig
43
+ from cmem_client.repositories.files import FilesRepository
43
44
  from cmem_client.repositories.graph_imports import GraphImportsRepository
44
45
  from cmem_client.repositories.graphs import GraphsRepository
45
46
  from cmem_client.repositories.marketplace_packages import MarketplacePackagesRepository
@@ -113,6 +114,9 @@ class Client:
113
114
  _workflows: WorkflowsRepository
114
115
  """WorkflowsRepository object for workflow operations."""
115
116
 
117
+ _files: FilesRepository
118
+ """FilesRepository object for file operations."""
119
+
116
120
  def __init__(
117
121
  self,
118
122
  config: Config,
@@ -483,3 +487,15 @@ class Client:
483
487
  except AttributeError:
484
488
  self._workflows = WorkflowsRepository(client=self)
485
489
  return self._workflows
490
+
491
+ @property
492
+ def files(self) -> FilesRepository:
493
+ """Get the files repository for managing files
494
+
495
+ Returns: The files repository instance, created lazy on first access.
496
+ """
497
+ try:
498
+ return self._files
499
+ except AttributeError:
500
+ self._files = FilesRepository(client=self)
501
+ return self._files
@@ -80,3 +80,15 @@ class MarketplacePackagesDeleteError(RepositoryModificationError):
80
80
 
81
81
  class MarketplacePackagesExportError(BaseError):
82
82
  """Exception raised when a marketplace packages export fails."""
83
+
84
+
85
+ class FilesImportError(RepositoryModificationError):
86
+ """Exception raised when a file import fails."""
87
+
88
+
89
+ class FilesDeleteError(RepositoryModificationError):
90
+ """Exception raised when a file import fails."""
91
+
92
+
93
+ class FilesExportError(RepositoryModificationError):
94
+ """Exception raised when a file export fails."""
@@ -0,0 +1,25 @@
1
+ """A file resource model"""
2
+
3
+ from pydantic import Field
4
+
5
+ from cmem_client.models.base import Model, ReadRepositoryItem
6
+
7
+
8
+ class ResourceResponse(Model):
9
+ """API response model for a file resource"""
10
+
11
+ name: str = Field(description="Resource name")
12
+ full_path: str = Field(description="Path of the resource", alias="fullPath")
13
+ modified: str = Field(description="Resource last modified time")
14
+ size: int = Field(description="Resource size")
15
+
16
+
17
+ class Resource(Model, ReadRepositoryItem):
18
+ """A file resource."""
19
+
20
+ file_id: str
21
+ project_id: str
22
+
23
+ def get_id(self) -> str:
24
+ """Get the resource ID in format 'project_id:file_id'"""
25
+ return f"{self.project_id}:{self.file_id}"
@@ -0,0 +1,181 @@
1
+ """Files Repository."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import TYPE_CHECKING, ClassVar
7
+
8
+ from httpx import HTTPError
9
+ from pydantic import TypeAdapter
10
+
11
+ from cmem_client.repositories.protocols.export_item import ExportConfig, ExportItemProtocol
12
+
13
+ if TYPE_CHECKING:
14
+ from collections.abc import Sequence
15
+
16
+ from cmem_client.client import Client
17
+
18
+ from cmem_client.exceptions import FilesDeleteError, FilesExportError, FilesImportError
19
+ from cmem_client.models.item import FileImportItem, ImportItem
20
+ from cmem_client.models.resource import Resource, ResourceResponse
21
+ from cmem_client.repositories.base.plain_list import PlainListRepository
22
+ from cmem_client.repositories.protocols.delete_item import DeleteConfig, DeleteItemProtocol
23
+ from cmem_client.repositories.protocols.import_item import ImportConfig, ImportItemProtocol
24
+
25
+
26
+ class FilesImportConfig(ImportConfig):
27
+ """Files Import Configuration."""
28
+
29
+
30
+ class FilesExportConfig(ExportConfig):
31
+ """Files Export Configuration."""
32
+
33
+
34
+ class FilesDeleteConfig(DeleteConfig):
35
+ """Files Delete Configuration."""
36
+
37
+
38
+ class FilesRepository(PlainListRepository, ImportItemProtocol, DeleteItemProtocol, ExportItemProtocol):
39
+ """Repository for files"""
40
+
41
+ _client: Client
42
+ _dict: dict[str, Resource]
43
+ _allowed_import_items: ClassVar[Sequence[type[ImportItem]]] = [FileImportItem]
44
+
45
+ def fetch_data(self) -> None:
46
+ """Fetch all file resources from all projects."""
47
+ self._dict = {}
48
+ for project_id in self._client.projects:
49
+ for resource_data in self._get_resources(project_id):
50
+ resource = Resource(file_id=resource_data.full_path, project_id=project_id)
51
+ self._dict[resource.get_id()] = resource
52
+
53
+ def _get_resources(self, project_id: str) -> list[ResourceResponse]:
54
+ """GET retrieve list of resources."""
55
+ url = self._client.config.url_build_api / "workspace/projects" / project_id / "resources"
56
+ response = self._client.http.get(url=url)
57
+ response.raise_for_status()
58
+ adapter = TypeAdapter(list[ResourceResponse])
59
+ return adapter.validate_json(response.content)
60
+
61
+ def _import_item(
62
+ self,
63
+ path: Path | None = None,
64
+ replace: bool = False,
65
+ key: str | None = None,
66
+ configuration: FilesImportConfig | None = None,
67
+ ) -> str:
68
+ """Import a file to a specific project.
69
+
70
+ Args:
71
+ path: Local file path to upload
72
+ replace: Whether to replace existing file
73
+ key: Composite key in format 'project_id:file_path'
74
+ configuration: Import configuration
75
+
76
+ Returns:
77
+ Composite key in format 'project_id:file_path'
78
+ """
79
+ _ = configuration
80
+
81
+ if path is None:
82
+ raise FilesImportError("Path to file necessary.")
83
+
84
+ if key is None:
85
+ raise FilesImportError("Key in format 'project_id:file_path' is required.")
86
+
87
+ if ":" not in key:
88
+ raise FilesImportError(f"Invalid key format. Expected 'project_id:file_path', got '{key}'")
89
+
90
+ project_id, resource_name = key.split(":", 1)
91
+
92
+ if not replace and key in self._dict:
93
+ raise FilesImportError(f"File {key} already exists. Try replace.")
94
+
95
+ url = self._client.config.url_build_api / "workspace/projects" / project_id / "files"
96
+
97
+ with path.open("rb") as file:
98
+ try:
99
+ response = self._client.http.put(url, params={"path": resource_name}, content=file)
100
+ response.raise_for_status()
101
+ except HTTPError as e:
102
+ raise FilesImportError(f"Could not upload file '{resource_name}' to project '{project_id}'.") from e
103
+
104
+ self.fetch_data()
105
+ return key
106
+
107
+ def _delete_item(self, key: str, configuration: FilesDeleteConfig | None = None) -> None:
108
+ """Delete a file.
109
+
110
+ Args:
111
+ key: Composite key in format 'project_id:file_path'
112
+ configuration: Delete configuration
113
+ """
114
+ _ = configuration
115
+
116
+ if ":" not in key:
117
+ raise FilesDeleteError(f"Invalid key format. Expected 'project_id:file_path', got '{key}'")
118
+
119
+ project_id, file_path = key.split(":", 1)
120
+
121
+ url = self._client.config.url_build_api / "workspace/projects" / project_id / "files"
122
+ try:
123
+ response = self._client.http.delete(url=url, params={"path": file_path})
124
+ response.raise_for_status()
125
+ except HTTPError as e:
126
+ raise FilesDeleteError(f"Could not delete file '{file_path}' from project '{project_id}'.") from e
127
+
128
+ def _export_item(
129
+ self,
130
+ key: str,
131
+ path: Path | None,
132
+ replace: bool = False,
133
+ configuration: FilesExportConfig | None = None,
134
+ ) -> Path:
135
+ """Export a file from a specific project.
136
+
137
+ Args:
138
+ key: Composite key in format 'project_id:file_path'
139
+ path: Target export path
140
+ replace: Whether to replace existing file
141
+ configuration: Export configuration
142
+ """
143
+ _ = configuration
144
+
145
+ if key is None:
146
+ raise FilesExportError("No resource key specified.")
147
+
148
+ if ":" not in key:
149
+ raise FilesExportError(f"Invalid key format. Expected 'project_id:file_path', got '{key}'")
150
+
151
+ project_id, file_path = key.split(":", 1)
152
+
153
+ if path is None:
154
+ target_path = Path(file_path)
155
+ elif path.is_dir():
156
+ target_path = path / file_path
157
+ else:
158
+ target_path = path
159
+
160
+ if target_path.exists() and not replace:
161
+ raise FilesExportError(f"File '{target_path}' already exists. Try replace=True.")
162
+
163
+ url = self._client.config.url_build_api / "workspace/projects" / project_id / "files"
164
+
165
+ try:
166
+ with self._client.http.stream(
167
+ "GET",
168
+ url,
169
+ params={"path": file_path},
170
+ ) as response:
171
+ response.raise_for_status()
172
+
173
+ target_path.parent.mkdir(parents=True, exist_ok=True)
174
+ with target_path.open("wb") as file:
175
+ for chunk in response.iter_bytes():
176
+ file.write(chunk)
177
+
178
+ except HTTPError as e:
179
+ raise FilesExportError(f"Could not export file '{file_path}' from project '{project_id}'.") from e
180
+
181
+ return target_path
@@ -12,8 +12,8 @@ from pathlib import Path
12
12
  from typing import TYPE_CHECKING, ClassVar
13
13
  from zipfile import BadZipFile, ZipFile
14
14
 
15
- from eccenca_marketplace_client.dependencies import PythonPackageDependency, VocabularyDependency
16
- from eccenca_marketplace_client.file_specs import GraphFileSpec, ProjectFileSpec
15
+ from eccenca_marketplace_client.models.dependencies import MarketplacePackageDependency, PythonPackageDependency
16
+ from eccenca_marketplace_client.models.files import GraphFileSpec, ImageFileSpec, ProjectFileSpec, TextFileSpec
17
17
  from eccenca_marketplace_client.ontology import (
18
18
  NS_IRI,
19
19
  get_data_graph_iri,
@@ -29,7 +29,9 @@ from cmem_client.exceptions import (
29
29
  MarketplacePackagesExportError,
30
30
  MarketplacePackagesImportError,
31
31
  )
32
+ from cmem_client.models.project import Project, ProjectMetaData
32
33
  from cmem_client.models.python_package import PythonPackage
34
+ from cmem_client.repositories.files import FilesImportConfig
33
35
  from cmem_client.repositories.graph_imports import GraphImport
34
36
 
35
37
  if TYPE_CHECKING:
@@ -46,6 +48,7 @@ from cmem_client.repositories.protocols.export_item import ExportConfig, ExportI
46
48
  from cmem_client.repositories.protocols.import_item import ImportConfig, ImportItemProtocol
47
49
 
48
50
  MAX_DEPENDENCY_DEPTH = 5
51
+ MARKETPLACE_PROJECT_ID = "marketplace-packages"
49
52
 
50
53
 
51
54
  class MarketplacePackagesImportConfig(ImportConfig):
@@ -123,13 +126,16 @@ class MarketplacePackagesRepository(Repository, ImportItemProtocol, ExportItemPr
123
126
  tmp_file.write(ontology_graph.serialize(format="turtle").encode("utf-8"))
124
127
 
125
128
  try:
129
+ get_ontology_graph()
126
130
  self._client.graphs.import_item(
127
- path=tmp_path, replace=False, key=None, configuration=GraphImportConfig(register_as_vocabulary=True)
131
+ path=tmp_path,
132
+ replace=False,
133
+ key=NS_IRI,
128
134
  )
129
135
  finally:
130
136
  tmp_path.unlink(missing_ok=True)
131
137
 
132
- def _import_item( # noqa: C901, PLR0912
138
+ def _import_item( # noqa: C901, PLR0912, PLR0915
133
139
  self,
134
140
  path: Path | None = None,
135
141
  replace: bool = False,
@@ -176,6 +182,7 @@ class MarketplacePackagesRepository(Repository, ImportItemProtocol, ExportItemPr
176
182
  imported_imports: list[str] = []
177
183
  imported_python_packages: list[str] = []
178
184
  imported_vocabulary_packages: list[str] = []
185
+ imported_files: list[str] = []
179
186
 
180
187
  if manifest.package_id in self._dict:
181
188
  if replace:
@@ -183,6 +190,8 @@ class MarketplacePackagesRepository(Repository, ImportItemProtocol, ExportItemPr
183
190
  else:
184
191
  raise MarketplacePackagesImportError("Package already imported. Try replace.")
185
192
 
193
+ self._create_assets_project()
194
+
186
195
  if not configuration.ignore_dependencies:
187
196
  # import python package dependencies first
188
197
  for dependency in manifest.dependencies:
@@ -193,7 +202,7 @@ class MarketplacePackagesRepository(Repository, ImportItemProtocol, ExportItemPr
193
202
  imported_python_packages.append(dependency.pypi_id)
194
203
 
195
204
  for dependency in manifest.dependencies:
196
- if isinstance(dependency, VocabularyDependency) and dependency.package_id not in self._dict:
205
+ if isinstance(dependency, MarketplacePackageDependency) and dependency.package_id not in self._dict:
197
206
  if configuration.dependency_level >= MAX_DEPENDENCY_DEPTH:
198
207
  self.logger.warning(
199
208
  "Skipping dependency '%s' because the max depth of '%s' was reached.",
@@ -238,6 +247,17 @@ class MarketplacePackagesRepository(Repository, ImportItemProtocol, ExportItemPr
238
247
  self._client.graph_imports.create_item(item=new_import, skip_if_existing=True)
239
248
  imported_imports.append(new_import.get_id())
240
249
 
250
+ for file in manifest.files:
251
+ file_resource_path = f"{manifest.package_id}/{file.file_path}"
252
+ composite_key = f"{MARKETPLACE_PROJECT_ID}:{file_resource_path}"
253
+ self._client.files.import_item(
254
+ path=package_version.get_file_path(file.file_path),
255
+ key=composite_key,
256
+ replace=True,
257
+ configuration=FilesImportConfig(use_archive_handler=False),
258
+ )
259
+ imported_files.append(composite_key)
260
+
241
261
  self._add_package_triples(package_version)
242
262
 
243
263
  # Rollback graphs + imports, projects, python packages
@@ -254,6 +274,8 @@ class MarketplacePackagesRepository(Repository, ImportItemProtocol, ExportItemPr
254
274
  self._client.python_packages.delete_item(key=python_package_id, skip_if_missing=True)
255
275
  for vocabulary_id in imported_vocabulary_packages:
256
276
  self._client.marketplace_packages.delete_item(key=vocabulary_id, skip_if_missing=True)
277
+ for file in imported_files:
278
+ self._client.files.delete_item(key=file, skip_if_missing=True)
257
279
  raise MarketplacePackagesImportError(f"Failed to import package ({error!s})") from error
258
280
 
259
281
  self.fetch_data()
@@ -311,6 +333,14 @@ class MarketplacePackagesRepository(Repository, ImportItemProtocol, ExportItemPr
311
333
  )
312
334
  exported_files.append((exported_path, file_spec.file_path))
313
335
 
336
+ if isinstance(file_spec, (ImageFileSpec, TextFileSpec)):
337
+ file_path = tmp_path / file_spec.file_path
338
+ file_path.parent.mkdir(parents=True, exist_ok=True)
339
+ file_resource_path = f"{manifest.package_id}/{file_spec.file_path}"
340
+ composite_key = f"{MARKETPLACE_PROJECT_ID}:{file_resource_path}"
341
+ self._client.files.export_item(key=composite_key, path=file_path)
342
+ exported_files.append((file_path, file_spec.file_path))
343
+
314
344
  manifest_path = tmp_path / "manifest.json"
315
345
  manifest_path.write_text(manifest_json_str, encoding="utf-8")
316
346
 
@@ -338,7 +368,7 @@ class MarketplacePackagesRepository(Repository, ImportItemProtocol, ExportItemPr
338
368
 
339
369
  return path
340
370
 
341
- def _delete_item(self, key: str, configuration: MarketplacePackagesDeleteConfig | None = None) -> None:
371
+ def _delete_item(self, key: str, configuration: MarketplacePackagesDeleteConfig | None = None) -> None: # noqa: C901
342
372
  """Delete a package by its package_id.
343
373
 
344
374
  This method need be extended for new FileSpecs.
@@ -369,7 +399,7 @@ class MarketplacePackagesRepository(Repository, ImportItemProtocol, ExportItemPr
369
399
  key=dependency.pypi_id, skip_if_missing=configuration.skip_missing_dependencies
370
400
  )
371
401
 
372
- if isinstance(dependency, VocabularyDependency):
402
+ if isinstance(dependency, MarketplacePackageDependency):
373
403
  dependants = self._graph.get_package_dependants(dependency.package_id)
374
404
  dependants.remove(package_id)
375
405
  if len(dependants) > 0:
@@ -393,8 +423,18 @@ class MarketplacePackagesRepository(Repository, ImportItemProtocol, ExportItemPr
393
423
  deleted_import = GraphImport(from_graph=str(from_graph), to_graph=str(graph.graph_iri))
394
424
  self._client.graph_imports.delete_item(key=deleted_import.get_id(), skip_if_missing=True)
395
425
 
426
+ for file in manifest.files:
427
+ file_resource_path = f"{manifest.package_id}/{file.file_path}"
428
+ composite_key = f"{MARKETPLACE_PROJECT_ID}:{file_resource_path}"
429
+ self._client.files.delete_item(key=composite_key, skip_if_missing=True)
430
+
396
431
  self._client.store.sparql.update(get_delete_query(package_iri))
397
432
 
433
+ # Remaining package is removed by the protocol, marketplace vocabulary and project can be deleted safely
434
+ if len(self._dict) == 1 and key in self._dict:
435
+ self._client.graphs.delete_item(NS_IRI, skip_if_missing=True)
436
+ self._client.projects.delete_item(MARKETPLACE_PROJECT_ID, skip_if_missing=True)
437
+
398
438
  def _add_package_triples(self, package: PackageVersion) -> None:
399
439
  """Add a package to the data config graph in the Corporate Memory instance.
400
440
 
@@ -484,3 +524,24 @@ class MarketplacePackagesRepository(Repository, ImportItemProtocol, ExportItemPr
484
524
  )
485
525
 
486
526
  return package_version
527
+
528
+ def _create_assets_project(self) -> None:
529
+ """Create the assets project for the marketplace."""
530
+ if MARKETPLACE_PROJECT_ID in self._client.projects:
531
+ return
532
+ mp_assets = Project(
533
+ name=MARKETPLACE_PROJECT_ID,
534
+ metaData=ProjectMetaData(
535
+ label="Marketplace Packages",
536
+ description="""This project contains all files that were installed via Marketplace packages.
537
+
538
+ This project was created when you installed your first package.
539
+ It will be deleted after the last package is uninstalled.
540
+
541
+ For more information about marketplace packages, have a look at
542
+ [documentation.eccenca.com](https://go.eccenca.com/feature/marketplace-packages).
543
+ """,
544
+ ),
545
+ )
546
+ self._client.projects.create_item(mp_assets)
547
+ self._client.projects.fetch_data()
@@ -67,14 +67,13 @@ class DeleteItemProtocol(Protocol[ItemType, DeleteItemConfig_contra]):
67
67
  RepositoryModificationError: if an error occurs while creating the item
68
68
  HTTPError: for any other http error
69
69
  """
70
- _ = configuration
71
70
  if key not in self._dict:
72
71
  if not skip_if_missing:
73
72
  raise RepositoryModificationError(f"Repository item '{key}' does not exists.")
74
73
  self.logger.info("Item '%s' does not exists, therefore not deleting.", key)
75
74
  return
76
75
  try:
77
- self._delete_item(key=key)
76
+ self._delete_item(key=key, configuration=configuration)
78
77
  except HTTPError as error:
79
78
  raise RepositoryModificationError(f"Error on deleting repository item '{key}'.") from error
80
79
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "cmem-client"
3
- version = "0.5.0"
3
+ version = "0.6.0"
4
4
  license = "Apache-2.0"
5
5
  description = "Next generation eccenca Corporate Memory client library."
6
6
  authors = ["eccenca GmbH <cmempy-developer@eccenca.com>"]
@@ -19,7 +19,7 @@ pydantic = "^2.8.2"
19
19
  httpx = "^0.27.0"
20
20
  pyjwt = "^2.8.0"
21
21
  rdflib = "^7.2.1"
22
- eccenca-marketplace-client = "^0.5.0"
22
+ eccenca-marketplace-client = "^0.6.0"
23
23
  # eccenca-marketplace-client = {path = "eccenca-marketplace-client", develop = true}
24
24
  xdg-base-dirs = "^6.0.2"
25
25
 
File without changes
File without changes