ds-resource-plugin-py-lib 0.1.0a1__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.
- ds_resource_plugin_py_lib/__init__.py +27 -0
- ds_resource_plugin_py_lib/common/__init__.py +6 -0
- ds_resource_plugin_py_lib/common/resource/__init__.py +8 -0
- ds_resource_plugin_py_lib/common/resource/client.py +239 -0
- ds_resource_plugin_py_lib/common/resource/dataset/__init__.py +11 -0
- ds_resource_plugin_py_lib/common/resource/dataset/base.py +158 -0
- ds_resource_plugin_py_lib/common/resource/dataset/errors.py +150 -0
- ds_resource_plugin_py_lib/common/resource/dataset/storage_format.py +52 -0
- ds_resource_plugin_py_lib/common/resource/errors.py +35 -0
- ds_resource_plugin_py_lib/common/resource/linked_service/__init__.py +7 -0
- ds_resource_plugin_py_lib/common/resource/linked_service/base.py +80 -0
- ds_resource_plugin_py_lib/common/resource/linked_service/errors.py +98 -0
- ds_resource_plugin_py_lib/common/serde/__init__.py +6 -0
- ds_resource_plugin_py_lib/common/serde/deserialize/__init__.py +9 -0
- ds_resource_plugin_py_lib/common/serde/deserialize/awswrangler.py +80 -0
- ds_resource_plugin_py_lib/common/serde/deserialize/base.py +23 -0
- ds_resource_plugin_py_lib/common/serde/deserialize/pandas.py +56 -0
- ds_resource_plugin_py_lib/common/serde/serialize/__init__.py +9 -0
- ds_resource_plugin_py_lib/common/serde/serialize/awswrangler.py +52 -0
- ds_resource_plugin_py_lib/common/serde/serialize/base.py +19 -0
- ds_resource_plugin_py_lib/common/serde/serialize/pandas.py +45 -0
- ds_resource_plugin_py_lib/libs/__init__.py +5 -0
- ds_resource_plugin_py_lib/libs/models/__init__.py +5 -0
- ds_resource_plugin_py_lib/libs/models/serializable.py +544 -0
- ds_resource_plugin_py_lib/libs/utils/import_string.py +26 -0
- ds_resource_plugin_py_lib/py.typed +0 -0
- ds_resource_plugin_py_lib-0.1.0a1.dist-info/METADATA +137 -0
- ds_resource_plugin_py_lib-0.1.0a1.dist-info/RECORD +31 -0
- ds_resource_plugin_py_lib-0.1.0a1.dist-info/WHEEL +5 -0
- ds_resource_plugin_py_lib-0.1.0a1.dist-info/licenses/LICENSE-APACHE +201 -0
- ds_resource_plugin_py_lib-0.1.0a1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A Python package from the ds-resource-plugin library.
|
|
3
|
+
|
|
4
|
+
**File:** ``__init__.py``
|
|
5
|
+
**Region:** ``ds-resource-plugin-py-lib``
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
|
|
9
|
+
.. code-block:: python
|
|
10
|
+
|
|
11
|
+
from ds_resource_plugin_py_lib import __version__
|
|
12
|
+
|
|
13
|
+
print(f"Package version: {__version__}")
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from . import common, libs
|
|
19
|
+
|
|
20
|
+
_VERSION_FILE = Path(__file__).parent.parent.parent / "VERSION.txt"
|
|
21
|
+
__version__ = _VERSION_FILE.read_text().strip() if _VERSION_FILE.exists() else "0.0.0"
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"__version__",
|
|
25
|
+
"common",
|
|
26
|
+
"libs",
|
|
27
|
+
]
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""
|
|
2
|
+
File: client.py
|
|
3
|
+
Description: Resource client for discovering and managing datasets and linked services using entry points
|
|
4
|
+
Region: packages/shared
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from functools import lru_cache
|
|
9
|
+
from importlib import import_module
|
|
10
|
+
from importlib.metadata import entry_points
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, cast
|
|
13
|
+
|
|
14
|
+
import yaml
|
|
15
|
+
from ds_common_logger_py_lib.mixin import LoggingMixin
|
|
16
|
+
|
|
17
|
+
from ...libs.utils.import_string import import_string
|
|
18
|
+
from ..resource.dataset.base import Dataset, DatasetInfo
|
|
19
|
+
from ..resource.errors import DeserializationException
|
|
20
|
+
from ..resource.linked_service.base import LinkedService, LinkedServiceInfo
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ResourceClient(LoggingMixin):
|
|
24
|
+
log_level = logging.DEBUG
|
|
25
|
+
|
|
26
|
+
PROTOCOL_GROUP = "ds.protocols"
|
|
27
|
+
PROVIDER_GROUP = "ds.providers"
|
|
28
|
+
|
|
29
|
+
def __init__(self) -> None:
|
|
30
|
+
super().__init__()
|
|
31
|
+
self._resource_dict: dict[str, dict[str, Any]] = {}
|
|
32
|
+
self._linked_services: dict[tuple[str, str], LinkedServiceInfo] = {}
|
|
33
|
+
self._datasets: dict[tuple[str, str], DatasetInfo] = {}
|
|
34
|
+
self._discover_resources(self.PROTOCOL_GROUP)
|
|
35
|
+
self._discover_resources(self.PROVIDER_GROUP)
|
|
36
|
+
self.log.debug(f"Loaded {len(self._resource_dict)} resources")
|
|
37
|
+
self.log.debug(f"Loaded {len(self._linked_services)} linked services")
|
|
38
|
+
self.log.debug(f"Loaded {len(self._datasets)} datasets")
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
@lru_cache(maxsize=1)
|
|
42
|
+
def get_instance(cls) -> "ResourceClient":
|
|
43
|
+
"""Get the singleton instance of ResourceClient."""
|
|
44
|
+
return cls()
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def resources(self) -> dict[str, dict[str, Any]]:
|
|
48
|
+
return self._resource_dict
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def linked_services(self) -> dict[tuple[str, str], LinkedServiceInfo]:
|
|
52
|
+
return self._linked_services
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def datasets(self) -> dict[tuple[str, str], DatasetInfo]:
|
|
56
|
+
return self._datasets
|
|
57
|
+
|
|
58
|
+
def _discover_resources(self, group: str) -> None:
|
|
59
|
+
"""
|
|
60
|
+
Discover protocol/provider packages via entry points.
|
|
61
|
+
Each entry point must target a Python package that contains resource.yaml in its root.
|
|
62
|
+
"""
|
|
63
|
+
try:
|
|
64
|
+
eps = entry_points(group=group)
|
|
65
|
+
except Exception as exc:
|
|
66
|
+
self.log.warning(f"Failed to read entry points for {group}: {exc}")
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
for ep in eps:
|
|
70
|
+
try:
|
|
71
|
+
module = import_module(ep.module)
|
|
72
|
+
module_path = getattr(module, "__file__", None)
|
|
73
|
+
if not module_path:
|
|
74
|
+
self.log.warning(f"Entry point {ep.name} has no __file__; skipping.")
|
|
75
|
+
continue
|
|
76
|
+
|
|
77
|
+
real_path = str(Path(module_path).parent.resolve())
|
|
78
|
+
self._scan_resource_directory(real_path)
|
|
79
|
+
|
|
80
|
+
except Exception as exc:
|
|
81
|
+
self.log.error(f"Error when loading entry point {ep.name} ({group}): {exc}")
|
|
82
|
+
|
|
83
|
+
def _scan_resource_directory(self, resource_dir: str) -> None:
|
|
84
|
+
"""
|
|
85
|
+
Scan a resource directory for resource.yaml.
|
|
86
|
+
Checks root first (new behavior), then subdirectories (old behavior).
|
|
87
|
+
Args:
|
|
88
|
+
resource_dir: Path to the resource directory.
|
|
89
|
+
"""
|
|
90
|
+
resource_path = Path(resource_dir)
|
|
91
|
+
if not resource_path.exists():
|
|
92
|
+
self.log.debug(f"Resource directory {resource_dir} does not exist")
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
self._load_resource_from_path(str(resource_path))
|
|
96
|
+
|
|
97
|
+
def _load_resource_from_path(self, path: str) -> None:
|
|
98
|
+
"""
|
|
99
|
+
Load resource configuration from a directory path.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
path: Path to the resource directory.
|
|
103
|
+
"""
|
|
104
|
+
resource_dir = Path(path)
|
|
105
|
+
resource_yaml = resource_dir / "resource.yaml"
|
|
106
|
+
if not resource_yaml.exists():
|
|
107
|
+
self.log.debug(f"No resource.yaml found in {path}")
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
with Path(resource_yaml).open() as f:
|
|
112
|
+
resource_config = yaml.safe_load(f)
|
|
113
|
+
if not resource_config:
|
|
114
|
+
self.log.warning(f"Empty resource configuration in {resource_yaml}")
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
resource_name = resource_config.get("name", resource_dir.name)
|
|
118
|
+
self._resource_dict[resource_name] = resource_config
|
|
119
|
+
self._parse_linked_services(resource_config)
|
|
120
|
+
self._parse_datasets(resource_config)
|
|
121
|
+
except Exception as exc:
|
|
122
|
+
self.log.error(f"Error loading resource configuration from {resource_yaml}: {exc}")
|
|
123
|
+
|
|
124
|
+
def _parse_linked_services(self, config: dict[str, Any]) -> None:
|
|
125
|
+
"""
|
|
126
|
+
Parse linked services from resource configuration.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
config: Resource configuration dictionary.
|
|
130
|
+
"""
|
|
131
|
+
linked_services = config.get("linked_service", [])
|
|
132
|
+
for service in linked_services:
|
|
133
|
+
service_name = service.get("name")
|
|
134
|
+
if service_name:
|
|
135
|
+
kind = service.get("kind")
|
|
136
|
+
version = service.get("version", "1.0.0")
|
|
137
|
+
service_info = LinkedServiceInfo(
|
|
138
|
+
kind=kind,
|
|
139
|
+
name=service_name,
|
|
140
|
+
class_name=service.get("class_name"),
|
|
141
|
+
version=version,
|
|
142
|
+
description=service.get("description"),
|
|
143
|
+
)
|
|
144
|
+
# Store by composite key (kind, version) to support multiple versions
|
|
145
|
+
self._linked_services[service_info.key] = service_info
|
|
146
|
+
|
|
147
|
+
def _parse_datasets(self, config: dict[str, Any]) -> None:
|
|
148
|
+
"""
|
|
149
|
+
Parse datasets from resource configuration.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
config: Resource configuration dictionary.
|
|
153
|
+
"""
|
|
154
|
+
datasets = config.get("dataset", [])
|
|
155
|
+
for dataset in datasets:
|
|
156
|
+
dataset_name = dataset.get("name")
|
|
157
|
+
if dataset_name:
|
|
158
|
+
kind = dataset.get("kind")
|
|
159
|
+
version = dataset.get("version", "1.0.0")
|
|
160
|
+
dataset_info = DatasetInfo(
|
|
161
|
+
kind=kind,
|
|
162
|
+
name=dataset_name,
|
|
163
|
+
class_name=dataset.get("class_name"),
|
|
164
|
+
version=version,
|
|
165
|
+
description=dataset.get("description"),
|
|
166
|
+
)
|
|
167
|
+
self._datasets[dataset_info.key] = dataset_info
|
|
168
|
+
|
|
169
|
+
def _get_dataset_model_cls(self, kind: str, version: str) -> type[Dataset[Any, Any]]:
|
|
170
|
+
"""
|
|
171
|
+
Get a dataset model class by kind and optionally version.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
kind: str
|
|
175
|
+
version: str
|
|
176
|
+
Returns:
|
|
177
|
+
Type[Dataset]
|
|
178
|
+
"""
|
|
179
|
+
cls_name = self.datasets[(kind, version)].class_name
|
|
180
|
+
self.log.debug("Dataset Class Name: %s", cls_name)
|
|
181
|
+
return cast("type[Dataset[Any, Any]]", import_string(cls_name))
|
|
182
|
+
|
|
183
|
+
def _get_linked_service_model_cls(self, kind: str, version: str) -> type[LinkedService[Any]]:
|
|
184
|
+
"""
|
|
185
|
+
Get a linked service model class by kind and version.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
kind: The kind of the linked service.
|
|
189
|
+
version: str version of the linked service.
|
|
190
|
+
Returns:
|
|
191
|
+
Type[LinkedService]
|
|
192
|
+
"""
|
|
193
|
+
cls_name = self.linked_services[(kind, version)].class_name
|
|
194
|
+
self.log.debug("Linked Service Class Name: %s", cls_name)
|
|
195
|
+
return cast("type[LinkedService[Any]]", import_string(cls_name))
|
|
196
|
+
|
|
197
|
+
def linked_service(self, config: dict[str, Any]) -> LinkedService[Any]:
|
|
198
|
+
"""
|
|
199
|
+
Get a linked service instance by configuration.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
config: dict containing at least 'kind' and 'version'
|
|
203
|
+
Returns:
|
|
204
|
+
LinkedService
|
|
205
|
+
"""
|
|
206
|
+
try:
|
|
207
|
+
kind = config["kind"]
|
|
208
|
+
version = config["version"]
|
|
209
|
+
model_cls = self._get_linked_service_model_cls(kind, version)
|
|
210
|
+
linked_service: LinkedService[Any] = model_cls.deserialize(config)
|
|
211
|
+
return linked_service
|
|
212
|
+
except (TypeError, KeyError) as exc:
|
|
213
|
+
self.log.exception(f"Error deserializing linked service: {exc}")
|
|
214
|
+
raise DeserializationException(
|
|
215
|
+
message=f"Error deserializing linked service: {exc}",
|
|
216
|
+
details={"config": config, "error": str(exc)},
|
|
217
|
+
) from exc
|
|
218
|
+
|
|
219
|
+
def dataset(self, config: dict[str, Any]) -> Dataset[Any, Any]:
|
|
220
|
+
"""
|
|
221
|
+
Get a dataset instance by configuration.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
config: dict containing at least 'kind' and 'version'
|
|
225
|
+
Returns:
|
|
226
|
+
Dataset
|
|
227
|
+
"""
|
|
228
|
+
try:
|
|
229
|
+
kind = config["kind"]
|
|
230
|
+
version = config["version"]
|
|
231
|
+
dataset_cls = self._get_dataset_model_cls(kind, version)
|
|
232
|
+
dataset: Dataset[Any, Any] = dataset_cls.deserialize(config)
|
|
233
|
+
return dataset
|
|
234
|
+
except (TypeError, KeyError) as exc:
|
|
235
|
+
self.log.exception(f"Error deserializing dataset: {exc}")
|
|
236
|
+
raise DeserializationException(
|
|
237
|
+
message=f"Error deserializing dataset: {exc}",
|
|
238
|
+
details={"config": config, "error": str(exc)},
|
|
239
|
+
) from exc
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .base import Dataset, DatasetInfo, DatasetTypedProperties, TabularDataset
|
|
2
|
+
from .storage_format import DatasetStorageFormat, DatasetStorageFormatType
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"Dataset",
|
|
6
|
+
"DatasetInfo",
|
|
7
|
+
"DatasetStorageFormat",
|
|
8
|
+
"DatasetStorageFormatType",
|
|
9
|
+
"DatasetTypedProperties",
|
|
10
|
+
"TabularDataset",
|
|
11
|
+
]
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import io
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import Any, Generic, NamedTuple, TypeVar
|
|
6
|
+
|
|
7
|
+
import pandas as pd
|
|
8
|
+
|
|
9
|
+
from ....libs.models.serializable import Serializable
|
|
10
|
+
from ...resource.linked_service.base import LinkedService
|
|
11
|
+
from ...serde.deserialize.base import DataDeserializer
|
|
12
|
+
from ...serde.serialize.base import DataSerializer
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DatasetInfo(NamedTuple):
|
|
16
|
+
kind: str
|
|
17
|
+
name: str
|
|
18
|
+
class_name: str
|
|
19
|
+
version: str
|
|
20
|
+
description: str | None = None
|
|
21
|
+
|
|
22
|
+
def __str__(self) -> str:
|
|
23
|
+
"""Return a string representation of the dataset info."""
|
|
24
|
+
return f"{self.kind}:v{self.version}"
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def key(self) -> tuple[str, str]:
|
|
28
|
+
"""Return the composite key (kind, version) for dictionary lookups."""
|
|
29
|
+
return (self.kind, self.version)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(kw_only=True)
|
|
33
|
+
class DatasetTypedProperties(Serializable):
|
|
34
|
+
"""
|
|
35
|
+
The object containing the typed properties of the dataset.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
DatasetTypedPropertiesType = TypeVar("DatasetTypedPropertiesType", bound=DatasetTypedProperties)
|
|
42
|
+
LinkedServiceType = TypeVar("LinkedServiceType", bound=LinkedService[Any])
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass(kw_only=True)
|
|
46
|
+
class Dataset(
|
|
47
|
+
ABC,
|
|
48
|
+
Serializable,
|
|
49
|
+
Generic[LinkedServiceType, DatasetTypedPropertiesType],
|
|
50
|
+
):
|
|
51
|
+
"""
|
|
52
|
+
The ds workflow nested object which identifies data within a data store,
|
|
53
|
+
such as table, files, folders and documents.
|
|
54
|
+
|
|
55
|
+
You probably want to use the subclasses and not this class directly.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
typed_properties: DatasetTypedPropertiesType
|
|
59
|
+
linked_service: LinkedServiceType
|
|
60
|
+
|
|
61
|
+
serializer: DataSerializer | None = None
|
|
62
|
+
deserializer: DataDeserializer | None = None
|
|
63
|
+
|
|
64
|
+
post_fetch_callback: Callable[..., Any] | None = None
|
|
65
|
+
prepare_write_callback: Callable[..., Any] | None = None
|
|
66
|
+
|
|
67
|
+
content: Any | None = None
|
|
68
|
+
schema: dict[str, Any] | None = None
|
|
69
|
+
next: bool | None = True
|
|
70
|
+
cursor: str | None = None
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
@abstractmethod
|
|
74
|
+
def kind(self) -> str:
|
|
75
|
+
"""
|
|
76
|
+
Get the kind of the dataset.
|
|
77
|
+
"""
|
|
78
|
+
raise NotImplementedError("Method (kind) not implemented")
|
|
79
|
+
|
|
80
|
+
@abstractmethod
|
|
81
|
+
def create(self, **kwargs: Any) -> Any:
|
|
82
|
+
"""
|
|
83
|
+
Create the dataset.
|
|
84
|
+
:param kwargs: dict
|
|
85
|
+
:return: None
|
|
86
|
+
"""
|
|
87
|
+
raise NotImplementedError("Method (create) not implemented")
|
|
88
|
+
|
|
89
|
+
@abstractmethod
|
|
90
|
+
def read(self, **kwargs: Any) -> Any:
|
|
91
|
+
"""
|
|
92
|
+
Read the dataset.
|
|
93
|
+
:param kwargs: dict
|
|
94
|
+
:return: None
|
|
95
|
+
"""
|
|
96
|
+
raise NotImplementedError("Method (read) not implemented")
|
|
97
|
+
|
|
98
|
+
@abstractmethod
|
|
99
|
+
def delete(self, **kwargs: Any) -> Any:
|
|
100
|
+
"""
|
|
101
|
+
Delete the dataset.
|
|
102
|
+
:param kwargs: dict
|
|
103
|
+
:return: None
|
|
104
|
+
"""
|
|
105
|
+
raise NotImplementedError("Method (delete) not implemented")
|
|
106
|
+
|
|
107
|
+
@abstractmethod
|
|
108
|
+
def update(self, **kwargs: Any) -> Any:
|
|
109
|
+
"""
|
|
110
|
+
Update the dataset.
|
|
111
|
+
:param kwargs: dict
|
|
112
|
+
:return: None
|
|
113
|
+
"""
|
|
114
|
+
raise NotImplementedError("Method (update) not implemented")
|
|
115
|
+
|
|
116
|
+
@abstractmethod
|
|
117
|
+
def rename(self, **kwargs: Any) -> Any:
|
|
118
|
+
"""
|
|
119
|
+
Move the dataset.
|
|
120
|
+
:param kwargs: dict
|
|
121
|
+
:return: None
|
|
122
|
+
"""
|
|
123
|
+
raise NotImplementedError("Method (move) not implemented")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@dataclass(kw_only=True)
|
|
127
|
+
class BinaryDataset(
|
|
128
|
+
Dataset[LinkedServiceType, DatasetTypedPropertiesType],
|
|
129
|
+
Generic[LinkedServiceType, DatasetTypedPropertiesType],
|
|
130
|
+
):
|
|
131
|
+
"""
|
|
132
|
+
Binary dataset object which identifies data within a data store,
|
|
133
|
+
such as files, folders and documents.
|
|
134
|
+
|
|
135
|
+
The content of the dataset is a binary file.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
content: io.BytesIO = field(default_factory=io.BytesIO)
|
|
139
|
+
next: bool | None = True
|
|
140
|
+
cursor: str | None = None
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@dataclass(kw_only=True)
|
|
144
|
+
class TabularDataset(
|
|
145
|
+
Dataset[LinkedServiceType, DatasetTypedPropertiesType],
|
|
146
|
+
Generic[LinkedServiceType, DatasetTypedPropertiesType],
|
|
147
|
+
):
|
|
148
|
+
"""
|
|
149
|
+
Tabular dataset object which identifies data within a data store,
|
|
150
|
+
such as table/csv/json/parquet/parquetdataset/ and other documents.
|
|
151
|
+
|
|
152
|
+
The content of the dataset is a pandas DataFrame.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
schema: dict[str, Any] | None = None
|
|
156
|
+
content: pd.DataFrame = field(default_factory=pd.DataFrame)
|
|
157
|
+
next: bool | None = True
|
|
158
|
+
cursor: str | None = None
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Exceptions for Datasets.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ..errors import ResourceException
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DatasetException(ResourceException):
|
|
11
|
+
"""Base exception for all dataset-related errors."""
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
message: str = "Dataset operation failed",
|
|
16
|
+
code: str = "DATASET_ERROR",
|
|
17
|
+
status_code: int = 500,
|
|
18
|
+
details: dict[str, Any] | None = None,
|
|
19
|
+
) -> None:
|
|
20
|
+
super().__init__(message, code, status_code, details)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class MismatchedLinkedServiceException(DatasetException):
|
|
24
|
+
"""Raised when a linked service does not match the dataset type."""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
message: str = "Mismatched linked service",
|
|
29
|
+
code: str = "MISMATCHED_LINKED_SERVICE",
|
|
30
|
+
status_code: int = 400,
|
|
31
|
+
details: dict[str, Any] | None = None,
|
|
32
|
+
) -> None:
|
|
33
|
+
super().__init__(message, code, status_code, details)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class UnsupportedDatasetType(DatasetException):
|
|
37
|
+
"""Raised when a dataset type is not supported."""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
message: str = "Dataset type is not supported",
|
|
42
|
+
code: str = "UNSUPPORTED_DATASET_TYPE",
|
|
43
|
+
status_code: int = 400,
|
|
44
|
+
details: dict[str, Any] | None = None,
|
|
45
|
+
) -> None:
|
|
46
|
+
super().__init__(message, code, status_code, details)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class InvalidDatasetClass(DatasetException):
|
|
50
|
+
"""Raised when a dataset type is invalid."""
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
message: str = "Invalid dataset type",
|
|
55
|
+
code: str = "INVALID_DATASET_TYPE",
|
|
56
|
+
status_code: int = 400,
|
|
57
|
+
details: dict[str, Any] | None = None,
|
|
58
|
+
) -> None:
|
|
59
|
+
super().__init__(message, code, status_code, details)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class FileNotFound(DatasetException):
|
|
63
|
+
"""Raised when file not found."""
|
|
64
|
+
|
|
65
|
+
def __init__(
|
|
66
|
+
self,
|
|
67
|
+
message: str = "File not found",
|
|
68
|
+
code: str = "NOT_FOUND",
|
|
69
|
+
status_code: int = 404,
|
|
70
|
+
details: dict[str, Any] | None = None,
|
|
71
|
+
) -> None:
|
|
72
|
+
super().__init__(message, code, status_code, details)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class WriteException(DatasetException):
|
|
76
|
+
"""Raised when a write operation fails."""
|
|
77
|
+
|
|
78
|
+
def __init__(
|
|
79
|
+
self,
|
|
80
|
+
message: str = "Write operation failed",
|
|
81
|
+
code: str = "WRITE_ERROR",
|
|
82
|
+
status_code: int = 500,
|
|
83
|
+
details: dict[str, Any] | None = None,
|
|
84
|
+
) -> None:
|
|
85
|
+
super().__init__(message, code, status_code, details)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class UpdateException(DatasetException):
|
|
89
|
+
"""Raised when a update operation fails."""
|
|
90
|
+
|
|
91
|
+
def __init__(
|
|
92
|
+
self,
|
|
93
|
+
message: str = "Update operation failed",
|
|
94
|
+
code: str = "UPDATE_ERROR",
|
|
95
|
+
status_code: int = 500,
|
|
96
|
+
details: dict[str, Any] | None = None,
|
|
97
|
+
) -> None:
|
|
98
|
+
super().__init__(message, code, status_code, details)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class ReadException(DatasetException):
|
|
102
|
+
"""Raised when a read operation fails."""
|
|
103
|
+
|
|
104
|
+
def __init__(
|
|
105
|
+
self,
|
|
106
|
+
message: str = "Read operation failed",
|
|
107
|
+
code: str = "READ_ERROR",
|
|
108
|
+
status_code: int = 500,
|
|
109
|
+
details: dict[str, Any] | None = None,
|
|
110
|
+
) -> None:
|
|
111
|
+
super().__init__(message, code, status_code, details)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class DeleteException(DatasetException):
|
|
115
|
+
"""Raised when a delete operation fails."""
|
|
116
|
+
|
|
117
|
+
def __init__(
|
|
118
|
+
self,
|
|
119
|
+
message: str = "Delete operation failed",
|
|
120
|
+
code: str = "DELETE_ERROR",
|
|
121
|
+
status_code: int = 500,
|
|
122
|
+
details: dict[str, Any] | None = None,
|
|
123
|
+
) -> None:
|
|
124
|
+
super().__init__(message, code, status_code, details)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class JsonDecodeException(DatasetException):
|
|
128
|
+
"""Raised when a operation fails due to JSONDecodeError."""
|
|
129
|
+
|
|
130
|
+
def __init__(
|
|
131
|
+
self,
|
|
132
|
+
message: str = "Failed to decode JSON.",
|
|
133
|
+
code: str = "JSON_DECODE_ERROR",
|
|
134
|
+
status_code: int = 400,
|
|
135
|
+
details: dict[str, Any] | None = None,
|
|
136
|
+
) -> None:
|
|
137
|
+
super().__init__(message, code, status_code, details)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class BadRequestException(DatasetException):
|
|
141
|
+
"""Raised when an operation fails due to invalid input."""
|
|
142
|
+
|
|
143
|
+
def __init__(
|
|
144
|
+
self,
|
|
145
|
+
message: str = "Bad Request.",
|
|
146
|
+
code: str = "BAD_REQUEST",
|
|
147
|
+
status_code: int = 400,
|
|
148
|
+
details: dict[str, Any] | None = None,
|
|
149
|
+
) -> None:
|
|
150
|
+
super().__init__(message, code, status_code, details)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from enum import StrEnum
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from ....libs.models.serializable import Serializable
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DatasetStorageFormatType(StrEnum):
|
|
9
|
+
"""
|
|
10
|
+
Enum to define the storage format types.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
PARQUET = "PARQUET"
|
|
14
|
+
CSV = "CSV"
|
|
15
|
+
JSON = "JSON"
|
|
16
|
+
EXCEL = "EXCEL"
|
|
17
|
+
SEMI_STRUCTURED_JSON = "SEMI_STRUCTURED_JSON"
|
|
18
|
+
XML = "XML"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(kw_only=True)
|
|
22
|
+
class DatasetStorageFormat(Serializable):
|
|
23
|
+
"""
|
|
24
|
+
The object containing the storage format of the dataset.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
type: DatasetStorageFormatType
|
|
28
|
+
args: dict[str, Any] = field(default_factory=dict)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(kw_only=True)
|
|
32
|
+
class ParquetFormat(DatasetStorageFormat):
|
|
33
|
+
type: DatasetStorageFormatType = DatasetStorageFormatType.PARQUET
|
|
34
|
+
args: dict[str, Any] = field(default_factory=dict)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(kw_only=True)
|
|
38
|
+
class CsvFormat(DatasetStorageFormat):
|
|
39
|
+
type: DatasetStorageFormatType = DatasetStorageFormatType.CSV
|
|
40
|
+
args: dict[str, Any] = field(default_factory=lambda: {"delimiter": ","})
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(kw_only=True)
|
|
44
|
+
class SemiStructuredJsonFormat(DatasetStorageFormat):
|
|
45
|
+
type: DatasetStorageFormatType = DatasetStorageFormatType.SEMI_STRUCTURED_JSON
|
|
46
|
+
args: dict[str, Any] = field(default_factory=lambda: {"record_path": None})
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(kw_only=True)
|
|
50
|
+
class XMLFormat(DatasetStorageFormat):
|
|
51
|
+
type: DatasetStorageFormatType = DatasetStorageFormatType.XML
|
|
52
|
+
args: dict[str, Any] = field(default_factory=dict)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Exceptions for Resources.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ResourceException(Exception):
|
|
9
|
+
"""Base exception for all resource-related errors."""
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
message: str = "Resource operation failed",
|
|
14
|
+
code: str = "RESOURCE_ERROR",
|
|
15
|
+
status_code: int = 500,
|
|
16
|
+
details: dict[str, Any] | None = None,
|
|
17
|
+
) -> None:
|
|
18
|
+
self.code = code
|
|
19
|
+
self.status_code = status_code
|
|
20
|
+
self.message = message
|
|
21
|
+
self.details = details or {}
|
|
22
|
+
super().__init__(self.message)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class DeserializationException(ResourceException):
|
|
26
|
+
"""Raised when a deserialization operation fails."""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
message: str = "Deserialization operation failed",
|
|
31
|
+
code: str = "DESERIALIZATION_ERROR",
|
|
32
|
+
status_code: int = 400,
|
|
33
|
+
details: dict[str, Any] | None = None,
|
|
34
|
+
) -> None:
|
|
35
|
+
super().__init__(message, code, status_code, details)
|