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,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)
|