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.
Files changed (52) hide show
  1. cmem_client/__init__.py +13 -0
  2. cmem_client/auth_provider/__init__.py +14 -0
  3. cmem_client/auth_provider/abc.py +124 -0
  4. cmem_client/auth_provider/client_credentials.py +207 -0
  5. cmem_client/auth_provider/password.py +252 -0
  6. cmem_client/auth_provider/prefetched_token.py +153 -0
  7. cmem_client/client.py +485 -0
  8. cmem_client/components/__init__.py +10 -0
  9. cmem_client/components/graph_store.py +316 -0
  10. cmem_client/components/marketplace.py +179 -0
  11. cmem_client/components/sparql_wrapper.py +53 -0
  12. cmem_client/components/workspace.py +194 -0
  13. cmem_client/config.py +364 -0
  14. cmem_client/exceptions.py +82 -0
  15. cmem_client/logging_utils.py +49 -0
  16. cmem_client/models/__init__.py +16 -0
  17. cmem_client/models/access_condition.py +147 -0
  18. cmem_client/models/base.py +30 -0
  19. cmem_client/models/dataset.py +32 -0
  20. cmem_client/models/error.py +67 -0
  21. cmem_client/models/graph.py +26 -0
  22. cmem_client/models/item.py +143 -0
  23. cmem_client/models/logging_config.py +51 -0
  24. cmem_client/models/package.py +35 -0
  25. cmem_client/models/project.py +46 -0
  26. cmem_client/models/python_package.py +26 -0
  27. cmem_client/models/token.py +40 -0
  28. cmem_client/models/url.py +34 -0
  29. cmem_client/models/workflow.py +80 -0
  30. cmem_client/repositories/__init__.py +15 -0
  31. cmem_client/repositories/access_conditions.py +62 -0
  32. cmem_client/repositories/base/__init__.py +12 -0
  33. cmem_client/repositories/base/abc.py +138 -0
  34. cmem_client/repositories/base/paged_list.py +63 -0
  35. cmem_client/repositories/base/plain_list.py +39 -0
  36. cmem_client/repositories/base/task_search.py +70 -0
  37. cmem_client/repositories/datasets.py +36 -0
  38. cmem_client/repositories/graph_imports.py +93 -0
  39. cmem_client/repositories/graphs.py +458 -0
  40. cmem_client/repositories/marketplace_packages.py +486 -0
  41. cmem_client/repositories/projects.py +214 -0
  42. cmem_client/repositories/protocols/__init__.py +15 -0
  43. cmem_client/repositories/protocols/create_item.py +125 -0
  44. cmem_client/repositories/protocols/delete_item.py +95 -0
  45. cmem_client/repositories/protocols/export_item.py +114 -0
  46. cmem_client/repositories/protocols/import_item.py +141 -0
  47. cmem_client/repositories/python_packages.py +58 -0
  48. cmem_client/repositories/workflows.py +143 -0
  49. cmem_client-0.5.0.dist-info/METADATA +64 -0
  50. cmem_client-0.5.0.dist-info/RECORD +52 -0
  51. cmem_client-0.5.0.dist-info/WHEEL +4 -0
  52. cmem_client-0.5.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,34 @@
1
+ """HTTP URL validation and manipulation utilities.
2
+
3
+ This module provides the HttpUrl class, which extends httpx.URL with additional
4
+ validation and path manipulation capabilities. It ensures URLs are well-formed
5
+ and provides convenient methods for building API endpoints.
6
+
7
+ The HttpUrl class is used throughout the configuration system to construct
8
+ various Corporate Memory API endpoints from base URLs.
9
+ """
10
+
11
+ from httpx import URL
12
+
13
+
14
+ class HttpUrl(URL):
15
+ """A http(s) URL"""
16
+
17
+ def __init__(self, url: str) -> None:
18
+ if str(URL(url)) != url:
19
+ raise ValueError(f"URL '{url}' not well formed. Use '{URL(url)!s}' instead.")
20
+ super().__init__(url)
21
+
22
+ def __truediv__(self, other: str) -> "HttpUrl":
23
+ """Add or extend a Path in the url
24
+
25
+ Examples:
26
+ >>> HttpUrl("https://example.com/") / "/path/to/file.txt"
27
+ HttpUrl('https://example.com/path/to/file.txt')
28
+ """
29
+ path = self.path
30
+ if path.endswith("/") and other.startswith("/"):
31
+ other = other[1:]
32
+ if not path.endswith("/") and not other.startswith("/"):
33
+ other = f"/{other}"
34
+ return HttpUrl(f"{self!s}{other}")
@@ -0,0 +1,80 @@
1
+ """Workflow models"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime # noqa: TC003 # Pydantic needs this at runtime
6
+ from typing import TYPE_CHECKING, Literal
7
+
8
+ from pydantic import Field, PrivateAttr
9
+
10
+ from cmem_client.exceptions import BaseError
11
+
12
+ if TYPE_CHECKING:
13
+ from cmem_client.client import Client
14
+
15
+ from cmem_client.models.base import Model, ReadRepositoryItem
16
+
17
+ ACTIVITY_NAME = Literal["ExecuteDefaultWorkflow", "ExecuteLocalWorkflow", "ExecuteWorkflowWithPayload"]
18
+
19
+
20
+ class WorkflowStatus(Model):
21
+ """Workflow execution status"""
22
+
23
+ status_name: str = Field(description="Status name (Idle, Running, Finished)", alias="statusName")
24
+ concrete_status: str = Field(description="Concrete status (Successful, Failed, Cancelled)", alias="concreteStatus")
25
+ progress: int | None = Field(description="Progress percentage (0-100)")
26
+ failed: bool = Field(description="Whether the workflow has failed")
27
+ message: str = Field(description="Status message")
28
+ last_update_time: int = Field(description="Last update time", alias="lastUpdateTime")
29
+ project: str = Field(description="Project ID")
30
+ task: str = Field(description="Task")
31
+ activity: str = Field(description="Activity")
32
+ activity_label: str = Field(description="Activity label", alias="activityLabel")
33
+ queue_time: datetime | None = Field(default=None, description="Queue time", alias="queueTime")
34
+ start_time: datetime | None = Field(default=None, description="Start time", alias="startTime")
35
+ is_running: bool = Field(description="Whether the workflow is currently running", alias="isRunning")
36
+ runtime: int | None = Field(default=None, description="Runtime")
37
+ cancelled: bool | None = Field(default=None, description="Cancelled")
38
+ exception_message: str | None = Field(default=None, description="Exception message", alias="exceptionMessage")
39
+
40
+
41
+ class Workflow(Model, ReadRepositoryItem):
42
+ """A workflow"""
43
+
44
+ _client: Client | None = PrivateAttr(default=None)
45
+
46
+ id: str = Field(description="Workflow ID", alias="id")
47
+ label: str = Field(description="Workflow label")
48
+ project_id: str = Field(description="Project ID", alias="projectId")
49
+ project_label: str = Field(description="Project label", alias="projectLabel")
50
+ variable_inputs: list[str] = Field(description="Workflow variable inputs", alias="variableInputs")
51
+ variable_outputs: list[str] = Field(description="Workflow variable outputs", alias="variableOutputs")
52
+ warnings: list[str] = Field(description="Workflow warnings")
53
+
54
+ def get_id(self) -> str:
55
+ """Get the workflow ID"""
56
+ return f"{self.project_id}:{self.id}"
57
+
58
+ def set_client(self, client: Client) -> None:
59
+ """Set the client for this workflow"""
60
+ self._client = client
61
+
62
+ def execute(self, activity_name: ACTIVITY_NAME = "ExecuteDefaultWorkflow") -> None:
63
+ """Execute the workflow"""
64
+ if self._client is None:
65
+ raise BaseError("Client is not set")
66
+ self._client.workflows.execute(self.get_id(), activity_name)
67
+
68
+ def execute_wait_for_completion(
69
+ self, activity_name: ACTIVITY_NAME = "ExecuteDefaultWorkflow", sleep_time: int = 1
70
+ ) -> None:
71
+ """Execute the workflow waiting for completion"""
72
+ if self._client is None:
73
+ raise BaseError("Client is not set")
74
+ self._client.workflows.execute_wait_for_completion(self.get_id(), activity_name, sleep_time)
75
+
76
+ def get_status(self, activity_name: ACTIVITY_NAME = "ExecuteDefaultWorkflow") -> WorkflowStatus:
77
+ """Get the status of the workflow execution."""
78
+ if self._client is None:
79
+ raise BaseError("Client is not set")
80
+ return self._client.workflows.get_status(self.get_id(), activity_name)
@@ -0,0 +1,15 @@
1
+ """Data access layer implementing the repository pattern for Corporate Memory resources.
2
+
3
+ This package provides repository classes for managing different types of Corporate Memory
4
+ entities through a consistent interface. Repositories handle data fetching, caching,
5
+ CRUD operations, and API communication for various resource types.
6
+
7
+ Key repository categories:
8
+ - Resource repositories: Projects, datasets, graphs, access conditions
9
+ - Base classes: Common functionality and abstract base classes
10
+ - Protocols: Interface definitions for repository operations
11
+
12
+ Repositories follow the repository pattern, providing an abstraction layer between
13
+ the business logic and the Corporate Memory APIs, with support for both DataIntegration
14
+ (build) and DataPlatform (explore) endpoints.
15
+ """
@@ -0,0 +1,62 @@
1
+ """Repository for managing access conditions in Corporate Memory.
2
+
3
+ Provides AccessConditionRepository class for managing authorization access conditions
4
+ with CRUD operations including create and delete functionality.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import TYPE_CHECKING
10
+
11
+ from pydantic import TypeAdapter
12
+
13
+ from cmem_client.models.access_condition import AccessCondition, AccessConditionResultSet
14
+ from cmem_client.repositories.base.abc import RepositoryConfig
15
+ from cmem_client.repositories.base.paged_list import PagedListRepository
16
+ from cmem_client.repositories.protocols.create_item import CreateConfig, CreateItemProtocol
17
+ from cmem_client.repositories.protocols.delete_item import DeleteConfig, DeleteItemProtocol
18
+
19
+ if TYPE_CHECKING:
20
+ from httpx import Response
21
+
22
+ from cmem_client.client import Client
23
+
24
+
25
+ class AccessConditionsCreateConfig(CreateConfig):
26
+ """Access condition creation config."""
27
+
28
+
29
+ class AccessConditionsDeleteConfig(DeleteConfig):
30
+ """Access conditions delete config."""
31
+
32
+
33
+ class AccessConditionsRepository(PagedListRepository, DeleteItemProtocol, CreateItemProtocol):
34
+ """Repository for managing authorization access conditions.
35
+
36
+ This repository manages access conditions that control authorization for resources
37
+ in Corporate Memory. Access conditions are described with the
38
+ [AccessCondition model][cmem_client.models.access_condition.AccessCondition].
39
+
40
+ The repository extends PagedListRepository and implements protocols for creating
41
+ and deleting access conditions.
42
+ """
43
+
44
+ _dict: dict[str, AccessCondition]
45
+ _client: Client
46
+ _config = RepositoryConfig(
47
+ component="explore",
48
+ fetch_data_path="/api/authorization", # used for all requests, not just fetch
49
+ fetch_data_adapter=TypeAdapter(AccessConditionResultSet),
50
+ )
51
+
52
+ def _delete_item(self, key: str, configuration: AccessConditionsDeleteConfig | None = None) -> None:
53
+ _ = configuration
54
+ response = self._client.http.delete(url=self._url_fetch_data, params={"resource": key})
55
+ response.raise_for_status()
56
+
57
+ def _create_item(
58
+ self, item: AccessCondition, configuration: AccessConditionsCreateConfig | None = None
59
+ ) -> Response:
60
+ # Send a CreateAccessConditionRequest instead of an AccessCondition
61
+ _ = configuration
62
+ return self._client.http.post(url=self._url_fetch_data, json=item.get_create_request())
@@ -0,0 +1,12 @@
1
+ """Base classes and infrastructure for repository implementations.
2
+
3
+ This package provides the foundational classes and utilities for building
4
+ repository implementations in the cmem_client library. It includes:
5
+
6
+ - Abstract base classes defining repository interfaces
7
+ - Configuration classes for different repository types
8
+ - Concrete implementations for common data access patterns
9
+
10
+ Repositories abstract the details of API communication and provide a
11
+ consistent, dictionary-like interface for accessing Corporate Memory resources.
12
+ """
@@ -0,0 +1,138 @@
1
+ """Abstract base classes and configuration for CMEM repositories.
2
+
3
+ This module provides the foundational classes for building repositories in the CMEM client:
4
+
5
+ - RepositoryConfig: Configuration class that defines component type, fetch paths, and data adapters
6
+ - Repository: Abstract base class implementing a lazy-loading, read-only, dictionary-like interface
7
+ for accessing CMEM resources with automatic data fetching and caching capabilities
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import logging
13
+ from abc import ABC, abstractmethod
14
+ from collections.abc import ItemsView, Iterator, KeysView, Mapping, ValuesView
15
+ from typing import TYPE_CHECKING, Generic, Literal, TypeVar
16
+
17
+ from cmem_client.exceptions import RepositoryConfigError
18
+ from cmem_client.models.base import ReadRepositoryItem
19
+
20
+ if TYPE_CHECKING:
21
+ from pydantic import TypeAdapter
22
+
23
+ from cmem_client.client import Client
24
+ from cmem_client.models.url import HttpUrl
25
+
26
+ ItemType = TypeVar("ItemType", bound=ReadRepositoryItem)
27
+ KeysViewType = KeysView[str]
28
+ ItemsViewType = ItemsView[str, ItemType]
29
+ ValuesViewType = ValuesView[ItemType]
30
+
31
+
32
+ class RepositoryConfig:
33
+ """Configuration class for a read repository.
34
+
35
+ This class defines the essential configuration parameters needed to set up
36
+ a repository that can fetch data from CMEM components:
37
+
38
+ - component: Specifies whether to use the "build" or "explore" API endpoint
39
+ - fetch_data_path: The specific API path for retrieving repository data
40
+ - fetch_data_adapter: Pydantic TypeAdapter for deserializing the API response
41
+ """
42
+
43
+ component: Literal["build", "explore"]
44
+ fetch_data_path: str
45
+ fetch_data_adapter: TypeAdapter
46
+
47
+ def __init__(
48
+ self,
49
+ component: Literal["build", "explore"],
50
+ fetch_data_path: str,
51
+ fetch_data_adapter: TypeAdapter,
52
+ ) -> None:
53
+ self.component = component
54
+ self.fetch_data_path = fetch_data_path
55
+ self.fetch_data_adapter = fetch_data_adapter
56
+
57
+
58
+ class Repository(ABC, Mapping, Generic[ItemType]):
59
+ """ABC of a lazy loading, read-only, dictionary-mimicking repository"""
60
+
61
+ _dict: dict[str, ItemType]
62
+ """the dict"""
63
+
64
+ _client: Client
65
+ _config: RepositoryConfig
66
+
67
+ _logger: logging.Logger
68
+
69
+ @property
70
+ def logger(self) -> logging.Logger:
71
+ """Gets the client logger"""
72
+ if not hasattr(self, "_logger"):
73
+ self._logger = logging.getLogger(f"{self._client.logger.name}.{self.__class__.__name__}")
74
+ return self._logger
75
+
76
+ @abstractmethod
77
+ def fetch_data(self) -> None:
78
+ """Fetch new data and update the repository"""
79
+
80
+ def __init__(self, client: Client) -> None:
81
+ self._client = client
82
+ self.fetch_data()
83
+ self._post_init()
84
+
85
+ def _post_init(self) -> None:
86
+ """Additional code to initialize the repository."""
87
+
88
+ @property
89
+ def _url_fetch_data(self) -> HttpUrl:
90
+ """Get the endpoint url for fetching the repository data"""
91
+ return self._url_base / self._config.fetch_data_path
92
+
93
+ @property
94
+ def _url_base(self) -> HttpUrl:
95
+ """Get the base url for the repository"""
96
+ match self._config.component:
97
+ case "build":
98
+ return self._client.config.url_build_api
99
+ case "explore":
100
+ return self._client.config.url_explore_api
101
+ case _:
102
+ raise RepositoryConfigError(f"Unknown component ID: {self._config.component}")
103
+
104
+ def _url(self, path: str) -> HttpUrl:
105
+ """Get the url for a path (based on the component)"""
106
+ return self._url_base / path
107
+
108
+ def __len__(self) -> int:
109
+ """Get the number of items in the repository"""
110
+ return len(self._dict)
111
+
112
+ def __getitem__(self, key: str) -> ItemType:
113
+ """Get an item by key (ID or IRI, depending on the type)"""
114
+ return self._dict[key]
115
+
116
+ def __iter__(self) -> Iterator[ItemType]:
117
+ """Get the iterator over the items in the repository"""
118
+ return iter(self._dict) # type: ignore[arg-type]
119
+
120
+ def __repr__(self) -> str:
121
+ """Get a string representation of the repository"""
122
+ return repr(self._dict)
123
+
124
+ def __contains__(self, key: object) -> bool:
125
+ """Check if an item is in the repository"""
126
+ return key in self.keys()
127
+
128
+ def keys(self) -> KeysViewType:
129
+ """Get the keys of the repository"""
130
+ return self._dict.keys()
131
+
132
+ def values(self) -> ValuesViewType:
133
+ """Get the values of the repository"""
134
+ return self._dict.values()
135
+
136
+ def items(self) -> ItemsViewType:
137
+ """Get the items of the repository"""
138
+ return self._dict.items()
@@ -0,0 +1,63 @@
1
+ """Repository implementation for paginated API endpoints.
2
+
3
+ This module provides PagedListRepository, a repository implementation that
4
+ handles paginated API responses commonly used in Corporate Memory's DataPlatform
5
+ (explore) APIs. It automatically fetches all pages of results and provides
6
+ a unified dictionary-like interface.
7
+
8
+ The PagedListRepository is typically used for endpoints that return results
9
+ in a paginated format with metadata about page size, number, and totals.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from typing import TYPE_CHECKING, Generic
15
+
16
+ from pydantic import Field
17
+
18
+ from cmem_client.logging_utils import log_method
19
+ from cmem_client.models.base import Model
20
+ from cmem_client.repositories.base.abc import ItemType, Repository, RepositoryConfig
21
+
22
+ if TYPE_CHECKING:
23
+ from cmem_client.client import Client
24
+
25
+
26
+ class PageDescription(Model):
27
+ """A description of a paged list.
28
+
29
+ {"size":10,"number":0,"totalElements":0,"totalPages":0}
30
+ """
31
+
32
+ size: int
33
+ number: int
34
+ total_elements: int = Field(alias="totalElements")
35
+ total_pages: int = Field(alias="totalPages")
36
+
37
+
38
+ class PagedListRepository(Repository, Generic[ItemType]):
39
+ """Repository that uses a paged list endpoint."""
40
+
41
+ _dict: dict[str, ItemType]
42
+ _client: Client
43
+ _config: RepositoryConfig
44
+
45
+ @log_method
46
+ def fetch_data(self) -> None:
47
+ """Fetch a paged list from a JSON endpoint via a type adapter.
48
+
49
+ Use this method to fetch data if your result set is a pageable spring endpoint.
50
+ """
51
+ items = {}
52
+ page = 0
53
+ while True:
54
+ response = self._client.http.get(self._url_fetch_data, params={"page": page})
55
+ response.raise_for_status()
56
+ content = response.content.decode(encoding="utf-8")
57
+ result_set = self._config.fetch_data_adapter.validate_json(content)
58
+ for item in result_set.content:
59
+ items[item.get_id()] = item
60
+ if len(result_set.content) < result_set.page.size:
61
+ break
62
+ page += 1
63
+ self._dict = items
@@ -0,0 +1,39 @@
1
+ """Repository implementation for simple list API endpoints.
2
+
3
+ This module provides PlainListRepository, a repository implementation for
4
+ API endpoints that return a simple array of objects without pagination.
5
+ It's commonly used with Corporate Memory's DataIntegration (build) APIs
6
+ that provide straightforward list responses.
7
+
8
+ The PlainListRepository fetches the entire list in a single request and
9
+ provides dictionary-like access to the items by their ID.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from typing import TYPE_CHECKING, Generic
15
+
16
+ from cmem_client.logging_utils import log_method
17
+ from cmem_client.repositories.base.abc import ItemType, Repository, RepositoryConfig
18
+
19
+ if TYPE_CHECKING:
20
+ from cmem_client.client import Client
21
+
22
+
23
+ class PlainListRepository(Repository, Generic[ItemType]):
24
+ """Subclass of a ReadRepository that uses a plain list endpoint."""
25
+
26
+ _dict: dict[str, ItemType]
27
+ _client: Client
28
+ _config: RepositoryConfig
29
+
30
+ @log_method
31
+ def fetch_data(self) -> None:
32
+ """Fetch simple list from a JSON endpoint via a type adapter
33
+
34
+ Use this method to fetch data when your result set is an array of objects.
35
+ """
36
+ response = self._client.http.get(self._url_fetch_data)
37
+ response.raise_for_status()
38
+ content = response.content.decode(encoding="utf-8")
39
+ self._dict = {item.get_id(): item for item in self._config.fetch_data_adapter.validate_json(content)}
@@ -0,0 +1,70 @@
1
+ """Repository implementation for Corporate Memory task search endpoints.
2
+
3
+ This module provides TaskSearchRepository, a specialized repository that uses
4
+ Corporate Memory's DataIntegration task search API to find and retrieve items.
5
+ The search functionality allows for flexible querying with filters, facets,
6
+ and text search capabilities.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ from typing import TYPE_CHECKING, Generic, Literal
13
+
14
+ from cmem_client.repositories.base.abc import ItemType, Repository, RepositoryConfig
15
+
16
+ if TYPE_CHECKING:
17
+ from pydantic import TypeAdapter
18
+
19
+ from cmem_client.client import Client
20
+
21
+
22
+ class TaskSearchRepositoryConfig(RepositoryConfig):
23
+ """Configuration class for a Task Search read repository"""
24
+
25
+ component: Literal["build", "explore"]
26
+ fetch_data_path: str
27
+ fetch_data_adapter: TypeAdapter
28
+ item_type: str
29
+
30
+ def __init__(
31
+ self,
32
+ fetch_data_adapter: TypeAdapter,
33
+ item_type: str,
34
+ component: Literal["build", "explore"] = "build",
35
+ fetch_data_path: str = "/api/workspace/searchItems",
36
+ ) -> None:
37
+ self.item_type = item_type
38
+ super().__init__(component, fetch_data_path, fetch_data_adapter)
39
+
40
+
41
+ class TaskSearchRepository(Repository, Generic[ItemType]):
42
+ """Subclass of a ReadRepository that uses the task search endpoint."""
43
+
44
+ _dict: dict[str, ItemType]
45
+ _client: Client
46
+ _config: TaskSearchRepositoryConfig
47
+
48
+ def fetch_data(self) -> None:
49
+ """Fetch a list from the DI task search endpoint via a type adapter."""
50
+ search_config = {
51
+ "limit": 1000000,
52
+ "offset": 0,
53
+ "itemType": self._config.item_type,
54
+ "project": None,
55
+ "textQuery": "",
56
+ "facets": None,
57
+ "addTaskParameters": True,
58
+ }
59
+ response = self._client.http.post(
60
+ self._url_fetch_data,
61
+ content=json.dumps(search_config),
62
+ headers={"Content-Type": "application/json"},
63
+ )
64
+ response.raise_for_status()
65
+ content = response.content.decode(encoding="utf-8")
66
+ items = {}
67
+ result_set = self._config.fetch_data_adapter.validate_json(content)
68
+ for item in result_set.results:
69
+ items[item.get_id()] = item
70
+ self._dict = items
@@ -0,0 +1,36 @@
1
+ """Repository for managing Corporate Memory datasets within projects.
2
+
3
+ This module provides the DatasetRepository class for managing datasets in
4
+ Corporate Memory's DataIntegration (build) environment. Datasets represent
5
+ data sources and sinks used in integration workflows, configured through
6
+ various plugins.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from pydantic import TypeAdapter
12
+
13
+ from cmem_client.models.dataset import Dataset, DatasetSearchResultSet
14
+ from cmem_client.repositories.base.task_search import (
15
+ TaskSearchRepository,
16
+ TaskSearchRepositoryConfig,
17
+ )
18
+ from cmem_client.repositories.protocols.delete_item import DeleteConfig, DeleteItemProtocol
19
+
20
+
21
+ class DatasetDeleteConfig(DeleteConfig):
22
+ """Dataset deletion configuration."""
23
+
24
+
25
+ class DatasetsRepository(TaskSearchRepository, DeleteItemProtocol):
26
+ """Repository for datasets."""
27
+
28
+ _dict: dict[str, Dataset]
29
+ _config = TaskSearchRepositoryConfig(fetch_data_adapter=TypeAdapter(DatasetSearchResultSet), item_type="dataset")
30
+
31
+ def _delete_item(self, key: str, configuration: DatasetDeleteConfig | None = None) -> None:
32
+ _ = configuration
33
+ to_delete: Dataset = self._dict[key]
34
+ url = self._url(f"/workspace/projects/{to_delete.project_id}/datasets/{to_delete.id}")
35
+ response = self._client.http.delete(url)
36
+ response.raise_for_status()
@@ -0,0 +1,93 @@
1
+ """Repository for managing Graph Imports"""
2
+
3
+ from cmem_client.exceptions import RepositoryModificationError
4
+ from cmem_client.models.base import ReadRepositoryItem
5
+ from cmem_client.repositories.base.abc import Repository
6
+ from cmem_client.repositories.protocols.create_item import CreateConfig, CreateItemProtocol
7
+ from cmem_client.repositories.protocols.delete_item import DeleteConfig, DeleteItemProtocol
8
+
9
+ GRAPH_IMPORTS_LIST_SPARQL = """
10
+ PREFIX owl: <http://www.w3.org/2002/07/owl#>
11
+
12
+ SELECT ?from_graph ?to_graph
13
+ WHERE
14
+ {
15
+ GRAPH ?from_graph {
16
+ ?from_graph owl:imports ?to_graph
17
+ }
18
+ }
19
+ """
20
+
21
+ GRAPH_IMPORTS_CREATE_SPARQL = """
22
+ PREFIX owl: <http://www.w3.org/2002/07/owl#>
23
+
24
+ INSERT DATA {{
25
+ GRAPH <{from_graph}> {{
26
+ <{from_graph}> owl:imports <{to_graph}> .
27
+ }}
28
+ }}
29
+ """
30
+
31
+ GRAPH_IMPORTS_DELETE_SPARQL = """
32
+ PREFIX owl: <http://www.w3.org/2002/07/owl#>
33
+
34
+ DELETE DATA {{
35
+ GRAPH <{from_graph}> {{
36
+ <{from_graph}> owl:imports <{to_graph}> .
37
+ }}
38
+ }}
39
+ """
40
+
41
+
42
+ class GraphImportsCreateConfig(CreateConfig):
43
+ """Graph Imports creation configuration"""
44
+
45
+
46
+ class GraphImportsDeleteConfig(DeleteConfig):
47
+ """Graph Imports deletion configuration."""
48
+
49
+
50
+ class GraphImport(ReadRepositoryItem):
51
+ """Graph Import Repository Item"""
52
+
53
+ from_graph: str
54
+ to_graph: str
55
+
56
+ def get_id(self) -> str:
57
+ """Get the id of the item."""
58
+ return f"{self.from_graph}::::{self.to_graph}"
59
+
60
+
61
+ class GraphImportsRepository(Repository, CreateItemProtocol, DeleteItemProtocol):
62
+ """Repository for managing Graph Imports"""
63
+
64
+ _dict: dict[str, GraphImport]
65
+
66
+ def fetch_data(self) -> None:
67
+ """Fetch new data and update the repository"""
68
+ items = {}
69
+ results = self._client.store.sparql.query(GRAPH_IMPORTS_LIST_SPARQL)
70
+ for result in results:
71
+ graph_import = GraphImport(
72
+ from_graph=str(result.from_graph), # type: ignore[union-attr]
73
+ to_graph=str(result.to_graph), # type: ignore[union-attr]
74
+ )
75
+ items[graph_import.get_id()] = graph_import
76
+ self._dict = items
77
+
78
+ def _create_item(self, item: GraphImport, configuration: GraphImportsCreateConfig | None = None) -> None:
79
+ """Create (add) a new graph import"""
80
+ _ = configuration
81
+ if item.from_graph not in self._client.graphs:
82
+ raise RepositoryModificationError(f"Graph does not exist: {item.from_graph} (from_graph).")
83
+ if item.to_graph not in self._client.graphs:
84
+ raise RepositoryModificationError(f"Graph does not exist: {item.to_graph} (to_graph).")
85
+ query = GRAPH_IMPORTS_CREATE_SPARQL.format(from_graph=item.from_graph, to_graph=item.to_graph)
86
+ self._client.store.sparql.update(query)
87
+
88
+ def _delete_item(self, key: str, configuration: GraphImportsDeleteConfig | None = None) -> None:
89
+ """Delete a graph import"""
90
+ _ = configuration
91
+ from_graph, to_graph = key.split("::::", maxsplit=2)
92
+ query = GRAPH_IMPORTS_DELETE_SPARQL.format(from_graph=from_graph, to_graph=to_graph)
93
+ self._client.store.sparql.update(query)