ds-resource-plugin-py-lib 0.1.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.
- ds_resource_plugin_py_lib/__init__.py +28 -0
- ds_resource_plugin_py_lib/common/__init__.py +15 -0
- ds_resource_plugin_py_lib/common/resource/__init__.py +17 -0
- ds_resource_plugin_py_lib/common/resource/client.py +265 -0
- ds_resource_plugin_py_lib/common/resource/dataset/__init__.py +25 -0
- ds_resource_plugin_py_lib/common/resource/dataset/base.py +322 -0
- ds_resource_plugin_py_lib/common/resource/dataset/decorators.py +73 -0
- ds_resource_plugin_py_lib/common/resource/dataset/enums.py +36 -0
- ds_resource_plugin_py_lib/common/resource/dataset/errors.py +181 -0
- ds_resource_plugin_py_lib/common/resource/dataset/result.py +64 -0
- ds_resource_plugin_py_lib/common/resource/dataset/storage_format.py +80 -0
- ds_resource_plugin_py_lib/common/resource/errors.py +53 -0
- ds_resource_plugin_py_lib/common/resource/linked_service/__init__.py +18 -0
- ds_resource_plugin_py_lib/common/resource/linked_service/base.py +186 -0
- ds_resource_plugin_py_lib/common/resource/linked_service/enums.py +19 -0
- ds_resource_plugin_py_lib/common/resource/linked_service/errors.py +103 -0
- ds_resource_plugin_py_lib/common/serde/__init__.py +15 -0
- ds_resource_plugin_py_lib/common/serde/binary.py +129 -0
- ds_resource_plugin_py_lib/common/serde/deserialize/__init__.py +18 -0
- ds_resource_plugin_py_lib/common/serde/deserialize/awswrangler.py +142 -0
- ds_resource_plugin_py_lib/common/serde/deserialize/base.py +31 -0
- ds_resource_plugin_py_lib/common/serde/deserialize/pandas.py +107 -0
- ds_resource_plugin_py_lib/common/serde/serialize/__init__.py +18 -0
- ds_resource_plugin_py_lib/common/serde/serialize/awswrangler.py +115 -0
- ds_resource_plugin_py_lib/common/serde/serialize/base.py +27 -0
- ds_resource_plugin_py_lib/common/serde/serialize/pandas.py +102 -0
- ds_resource_plugin_py_lib/libs/__init__.py +14 -0
- ds_resource_plugin_py_lib/libs/utils/__init__.py +14 -0
- ds_resource_plugin_py_lib/libs/utils/import_string.py +50 -0
- ds_resource_plugin_py_lib/libs/utils/json_default.py +66 -0
- ds_resource_plugin_py_lib/libs/utils/sanitize.py +35 -0
- ds_resource_plugin_py_lib/py.typed +0 -0
- ds_resource_plugin_py_lib-0.1.0.dist-info/METADATA +140 -0
- ds_resource_plugin_py_lib-0.1.0.dist-info/RECORD +37 -0
- ds_resource_plugin_py_lib-0.1.0.dist-info/WHEEL +5 -0
- ds_resource_plugin_py_lib-0.1.0.dist-info/licenses/LICENSE-APACHE +201 -0
- ds_resource_plugin_py_lib-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
**File:** ``__init__.py``
|
|
3
|
+
**Region:** ``ds_resource_plugin_py_lib``
|
|
4
|
+
|
|
5
|
+
Description
|
|
6
|
+
-----------
|
|
7
|
+
A Python package from the ds-resource-plugin library.
|
|
8
|
+
|
|
9
|
+
Example
|
|
10
|
+
-------
|
|
11
|
+
.. code-block:: python
|
|
12
|
+
|
|
13
|
+
from ds_resource_plugin_py_lib import __version__
|
|
14
|
+
|
|
15
|
+
print(f"Package version: {__version__}")
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from importlib.metadata import version
|
|
19
|
+
|
|
20
|
+
from . import common, libs
|
|
21
|
+
|
|
22
|
+
__version__ = version("ds-resource-plugin-py-lib")
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"__version__",
|
|
26
|
+
"common",
|
|
27
|
+
"libs",
|
|
28
|
+
]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
**File:** ``__init__.py``
|
|
3
|
+
**Region:** ``ds_resource_plugin_py_lib/common``
|
|
4
|
+
|
|
5
|
+
Description
|
|
6
|
+
-----------
|
|
7
|
+
Common utilities for resources and (de)serialization.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from . import resource, serde
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"resource",
|
|
14
|
+
"serde",
|
|
15
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
**File:** ``__init__.py``
|
|
3
|
+
**Region:** ``ds_resource_plugin_py_lib/common/resource``
|
|
4
|
+
|
|
5
|
+
Description
|
|
6
|
+
-----------
|
|
7
|
+
Public resource APIs: datasets, linked services, and the resource client.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from . import dataset, linked_service
|
|
11
|
+
from .client import ResourceClient
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"ResourceClient",
|
|
15
|
+
"dataset",
|
|
16
|
+
"linked_service",
|
|
17
|
+
]
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"""
|
|
2
|
+
**File:** ``client.py``
|
|
3
|
+
**Region:** ``ds_resource_plugin_py_lib/common/resource``
|
|
4
|
+
|
|
5
|
+
Description
|
|
6
|
+
-----------
|
|
7
|
+
Resource client for discovering and managing datasets and linked services using entry points.
|
|
8
|
+
|
|
9
|
+
Example
|
|
10
|
+
-------
|
|
11
|
+
.. code-block:: python
|
|
12
|
+
|
|
13
|
+
from ds_resource_plugin_py_lib.common.resource.client import ResourceClient
|
|
14
|
+
|
|
15
|
+
client = ResourceClient()
|
|
16
|
+
|
|
17
|
+
# Inspect discovered resources (populated via Python entry points).
|
|
18
|
+
print(client.resources.keys())
|
|
19
|
+
print(client.linked_services.keys())
|
|
20
|
+
print(client.datasets.keys())
|
|
21
|
+
|
|
22
|
+
dataset = client.dataset(config={"type": "dataset.example", "version": "1.0.0"})
|
|
23
|
+
linked_service = client.linked_service(config={"type": "linked_service.example", "version": "1.0.0"})
|
|
24
|
+
|
|
25
|
+
linked_service.connect()
|
|
26
|
+
print(linked_service.connection)
|
|
27
|
+
print(dataset.read())
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from functools import lru_cache
|
|
31
|
+
from importlib import import_module
|
|
32
|
+
from importlib.metadata import entry_points
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
from typing import Any, cast
|
|
35
|
+
|
|
36
|
+
import yaml
|
|
37
|
+
from ds_common_logger_py_lib import Logger
|
|
38
|
+
from ds_common_serde_py_lib.errors import DeserializationError
|
|
39
|
+
|
|
40
|
+
from ...libs.utils import import_string, sanitize_version
|
|
41
|
+
from ..resource.dataset.base import Dataset, DatasetInfo
|
|
42
|
+
from ..resource.linked_service.base import LinkedService, LinkedServiceInfo
|
|
43
|
+
|
|
44
|
+
logger = Logger.get_logger(__name__, package=True)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ResourceClient:
|
|
48
|
+
PROTOCOL_GROUP = "ds.protocols"
|
|
49
|
+
PROVIDER_GROUP = "ds.providers"
|
|
50
|
+
|
|
51
|
+
def __init__(self) -> None:
|
|
52
|
+
super().__init__()
|
|
53
|
+
self._resource_dict: dict[str, dict[str, Any]] = {}
|
|
54
|
+
self._linked_services: dict[tuple[str, str], LinkedServiceInfo] = {}
|
|
55
|
+
self._datasets: dict[tuple[str, str], DatasetInfo] = {}
|
|
56
|
+
self._discover_resources(self.PROTOCOL_GROUP)
|
|
57
|
+
self._discover_resources(self.PROVIDER_GROUP)
|
|
58
|
+
logger.debug(f"Loaded {len(self._resource_dict)} resources")
|
|
59
|
+
logger.debug(f"Loaded {len(self._linked_services)} linked services")
|
|
60
|
+
logger.debug(f"Loaded {len(self._datasets)} datasets")
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
@lru_cache(maxsize=1)
|
|
64
|
+
def get_instance(cls) -> "ResourceClient":
|
|
65
|
+
"""Get the singleton instance of ResourceClient."""
|
|
66
|
+
return cls()
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def resources(self) -> dict[str, dict[str, Any]]:
|
|
70
|
+
return self._resource_dict
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def linked_services(self) -> dict[tuple[str, str], LinkedServiceInfo]:
|
|
74
|
+
return self._linked_services
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def datasets(self) -> dict[tuple[str, str], DatasetInfo]:
|
|
78
|
+
return self._datasets
|
|
79
|
+
|
|
80
|
+
def _discover_resources(self, group: str) -> None:
|
|
81
|
+
"""
|
|
82
|
+
Discover protocol/provider packages via entry points.
|
|
83
|
+
Each entry point must target a Python package that contains resource.yaml in its root.
|
|
84
|
+
"""
|
|
85
|
+
try:
|
|
86
|
+
eps = entry_points(group=group)
|
|
87
|
+
except Exception as exc:
|
|
88
|
+
logger.warning(f"Failed to read entry points for {group}: {exc}")
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
for ep in eps:
|
|
92
|
+
try:
|
|
93
|
+
module = import_module(ep.module)
|
|
94
|
+
module_path = getattr(module, "__file__", None)
|
|
95
|
+
if not module_path:
|
|
96
|
+
logger.warning(f"Entry point {ep.name} has no __file__; skipping.")
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
real_path = str(Path(module_path).parent.resolve())
|
|
100
|
+
self._scan_resource_directory(real_path)
|
|
101
|
+
|
|
102
|
+
except Exception as exc:
|
|
103
|
+
logger.error(f"Error when loading entry point {ep.name} ({group}): {exc}")
|
|
104
|
+
|
|
105
|
+
def _scan_resource_directory(self, resource_dir: str) -> None:
|
|
106
|
+
"""
|
|
107
|
+
Scan a resource directory for resource.yaml.
|
|
108
|
+
Checks root first (new behavior), then subdirectories (old behavior).
|
|
109
|
+
Args:
|
|
110
|
+
resource_dir: Path to the resource directory.
|
|
111
|
+
"""
|
|
112
|
+
resource_path = Path(resource_dir)
|
|
113
|
+
if not resource_path.exists():
|
|
114
|
+
logger.debug(f"Resource directory {resource_dir} does not exist")
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
self._load_resource_from_path(str(resource_path))
|
|
118
|
+
|
|
119
|
+
def _load_resource_from_path(self, path: str) -> None:
|
|
120
|
+
"""
|
|
121
|
+
Load resource configuration from a directory path.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
path: Path to the resource directory.
|
|
125
|
+
"""
|
|
126
|
+
resource_dir = Path(path)
|
|
127
|
+
resource_yaml = resource_dir / "resource.yaml"
|
|
128
|
+
if not resource_yaml.exists():
|
|
129
|
+
logger.debug(f"No resource.yaml found in {path}")
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
with Path(resource_yaml).open() as f:
|
|
134
|
+
resource_config = yaml.safe_load(f)
|
|
135
|
+
if not resource_config:
|
|
136
|
+
logger.warning(f"Empty resource configuration in {resource_yaml}")
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
resource_name = resource_config.get("name", resource_dir.name)
|
|
140
|
+
self._resource_dict[resource_name] = resource_config
|
|
141
|
+
self._parse_linked_services(resource_config)
|
|
142
|
+
self._parse_datasets(resource_config)
|
|
143
|
+
except Exception as exc:
|
|
144
|
+
logger.error(f"Error loading resource configuration from {resource_yaml}: {exc}")
|
|
145
|
+
|
|
146
|
+
def _parse_linked_services(self, config: dict[str, Any]) -> None:
|
|
147
|
+
"""
|
|
148
|
+
Parse linked services from resource configuration.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
config: Resource configuration dictionary.
|
|
152
|
+
"""
|
|
153
|
+
linked_services = config.get("linked_service", [])
|
|
154
|
+
for service in linked_services:
|
|
155
|
+
service_name = service.get("name")
|
|
156
|
+
if service_name:
|
|
157
|
+
type = service.get("type")
|
|
158
|
+
version = service.get("version", "1.0.0")
|
|
159
|
+
service_info = LinkedServiceInfo(
|
|
160
|
+
type=type,
|
|
161
|
+
name=service_name,
|
|
162
|
+
class_name=service.get("class_name"),
|
|
163
|
+
version=version,
|
|
164
|
+
description=service.get("description"),
|
|
165
|
+
)
|
|
166
|
+
# Store by composite key (type, version) to support multiple versions
|
|
167
|
+
self._linked_services[service_info.key] = service_info
|
|
168
|
+
|
|
169
|
+
def _parse_datasets(self, config: dict[str, Any]) -> None:
|
|
170
|
+
"""
|
|
171
|
+
Parse datasets from resource configuration.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
config: Resource configuration dictionary.
|
|
175
|
+
"""
|
|
176
|
+
datasets = config.get("dataset", [])
|
|
177
|
+
for dataset in datasets:
|
|
178
|
+
dataset_name = dataset.get("name")
|
|
179
|
+
if dataset_name:
|
|
180
|
+
type = dataset.get("type")
|
|
181
|
+
version = dataset.get("version", "1.0.0")
|
|
182
|
+
dataset_info = DatasetInfo(
|
|
183
|
+
type=type,
|
|
184
|
+
name=dataset_name,
|
|
185
|
+
class_name=dataset.get("class_name"),
|
|
186
|
+
version=version,
|
|
187
|
+
description=dataset.get("description"),
|
|
188
|
+
)
|
|
189
|
+
self._datasets[dataset_info.key] = dataset_info
|
|
190
|
+
|
|
191
|
+
def _get_dataset_model_cls(self, _type: str, version: str) -> type[Dataset[Any, Any, Any, Any]]:
|
|
192
|
+
"""
|
|
193
|
+
Get a dataset model class by type and optionally version.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
_type: str
|
|
197
|
+
version: str
|
|
198
|
+
Returns:
|
|
199
|
+
Type[Dataset]
|
|
200
|
+
"""
|
|
201
|
+
cls_name = self.datasets[(_type, version)].class_name
|
|
202
|
+
logger.debug("Dataset Class Name: %s", cls_name)
|
|
203
|
+
return cast("type[Dataset[Any, Any, Any, Any]]", import_string(cls_name))
|
|
204
|
+
|
|
205
|
+
def _get_linked_service_model_cls(self, _type: str, version: str) -> type[LinkedService[Any]]:
|
|
206
|
+
"""
|
|
207
|
+
Get a linked service model class by type and version.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
_type: The type of the linked service.
|
|
211
|
+
version: str version of the linked service.
|
|
212
|
+
Returns:
|
|
213
|
+
Type[LinkedService]
|
|
214
|
+
"""
|
|
215
|
+
cls_name = self.linked_services[(_type, version)].class_name
|
|
216
|
+
logger.debug("Linked Service Class Name: %s", cls_name)
|
|
217
|
+
return cast("type[LinkedService[Any]]", import_string(cls_name))
|
|
218
|
+
|
|
219
|
+
def linked_service(self, config: dict[str, Any]) -> LinkedService[Any]:
|
|
220
|
+
"""
|
|
221
|
+
Get a linked service instance by configuration.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
config: dict containing at least 'type' and 'version'
|
|
225
|
+
Returns:
|
|
226
|
+
LinkedService
|
|
227
|
+
Raises:
|
|
228
|
+
DeserializationError: If the linked service cannot be deserialized.
|
|
229
|
+
"""
|
|
230
|
+
try:
|
|
231
|
+
type = config["type"]
|
|
232
|
+
version = sanitize_version(config["version"])
|
|
233
|
+
model_cls = self._get_linked_service_model_cls(type, version)
|
|
234
|
+
linked_service: LinkedService[Any] = model_cls.deserialize(config)
|
|
235
|
+
return linked_service
|
|
236
|
+
except (TypeError, KeyError) as exc:
|
|
237
|
+
logger.error(f"Error deserializing linked service: {exc}")
|
|
238
|
+
raise DeserializationError(
|
|
239
|
+
message=f"Error deserializing linked service: {exc}",
|
|
240
|
+
details={"config": config, "error": str(exc)},
|
|
241
|
+
) from exc
|
|
242
|
+
|
|
243
|
+
def dataset(self, config: dict[str, Any]) -> Dataset[Any, Any, Any, Any]:
|
|
244
|
+
"""
|
|
245
|
+
Get a dataset instance by configuration.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
config: dict containing at least 'type' and 'version'
|
|
249
|
+
Returns:
|
|
250
|
+
Dataset
|
|
251
|
+
Raises:
|
|
252
|
+
DeserializationError: If the dataset cannot be deserialized.
|
|
253
|
+
"""
|
|
254
|
+
try:
|
|
255
|
+
type = config["type"]
|
|
256
|
+
version = sanitize_version(config["version"])
|
|
257
|
+
dataset_cls = self._get_dataset_model_cls(type, version)
|
|
258
|
+
dataset: Dataset[Any, Any, Any, Any] = dataset_cls.deserialize(config)
|
|
259
|
+
return dataset
|
|
260
|
+
except (TypeError, KeyError) as exc:
|
|
261
|
+
logger.error(f"Error deserializing dataset: {exc}")
|
|
262
|
+
raise DeserializationError(
|
|
263
|
+
message=f"Error deserializing dataset: {exc}",
|
|
264
|
+
details={"config": config, "error": str(exc)},
|
|
265
|
+
) from exc
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
**File:** ``__init__.py``
|
|
3
|
+
**Region:** ``ds_resource_plugin_py_lib/common/resource/dataset``
|
|
4
|
+
|
|
5
|
+
Description
|
|
6
|
+
-----------
|
|
7
|
+
Dataset models, typed properties, and storage format helpers.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .base import Dataset, DatasetInfo, DatasetSettings, TabularDataset
|
|
11
|
+
from .enums import DatasetMethod
|
|
12
|
+
from .result import OperationError, OperationInfo
|
|
13
|
+
from .storage_format import DatasetStorageFormat, DatasetStorageFormatType
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"Dataset",
|
|
17
|
+
"DatasetInfo",
|
|
18
|
+
"DatasetMethod",
|
|
19
|
+
"DatasetSettings",
|
|
20
|
+
"DatasetStorageFormat",
|
|
21
|
+
"DatasetStorageFormatType",
|
|
22
|
+
"OperationError",
|
|
23
|
+
"OperationInfo",
|
|
24
|
+
"TabularDataset",
|
|
25
|
+
]
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"""
|
|
2
|
+
**File:** ``base.py``
|
|
3
|
+
**Region:** ``ds_resource_plugin_py_lib/common/resource/dataset``
|
|
4
|
+
|
|
5
|
+
Description
|
|
6
|
+
-----------
|
|
7
|
+
Base dataset models and typed properties.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import io
|
|
11
|
+
import uuid
|
|
12
|
+
from abc import ABC, abstractmethod
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from enum import StrEnum
|
|
15
|
+
from types import TracebackType
|
|
16
|
+
from typing import Any, Generic, NamedTuple, Self, TypeVar
|
|
17
|
+
|
|
18
|
+
import pandas as pd
|
|
19
|
+
from ds_common_serde_py_lib import Serializable
|
|
20
|
+
|
|
21
|
+
from ...resource.linked_service.base import LinkedService
|
|
22
|
+
from ...serde.deserialize.base import DataDeserializer
|
|
23
|
+
from ...serde.serialize.base import DataSerializer
|
|
24
|
+
from .decorators import track_result
|
|
25
|
+
from .enums import DatasetMethod
|
|
26
|
+
from .result import OperationInfo
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class DatasetInfo(NamedTuple):
|
|
30
|
+
"""
|
|
31
|
+
NamedTuple that represents the dataset information.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
type: str
|
|
35
|
+
name: str
|
|
36
|
+
class_name: str
|
|
37
|
+
version: str
|
|
38
|
+
description: str | None = None
|
|
39
|
+
|
|
40
|
+
def __str__(self) -> str:
|
|
41
|
+
"""
|
|
42
|
+
Return a string representation of the dataset info.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
A string representation of the dataset info.
|
|
46
|
+
"""
|
|
47
|
+
return f"{self.type}:v{self.version}"
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def key(self) -> tuple[str, str]:
|
|
51
|
+
"""
|
|
52
|
+
Return the composite key (type, version) for dictionary lookups.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
A tuple containing the type and version.
|
|
56
|
+
"""
|
|
57
|
+
return (self.type, self.version)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass(kw_only=True)
|
|
61
|
+
class DatasetSettings(Serializable):
|
|
62
|
+
"""
|
|
63
|
+
The object containing the settings of the dataset.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
DatasetSettingsType = TypeVar("DatasetSettingsType", bound=DatasetSettings)
|
|
70
|
+
LinkedServiceType = TypeVar("LinkedServiceType", bound=LinkedService[Any])
|
|
71
|
+
SerializerType = TypeVar("SerializerType", bound=DataSerializer)
|
|
72
|
+
DeserializerType = TypeVar("DeserializerType", bound=DataDeserializer)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass(kw_only=True)
|
|
76
|
+
class Dataset(
|
|
77
|
+
ABC,
|
|
78
|
+
Serializable,
|
|
79
|
+
Generic[LinkedServiceType, DatasetSettingsType, SerializerType, DeserializerType],
|
|
80
|
+
):
|
|
81
|
+
"""
|
|
82
|
+
The ds workflow nested object which identifies data within a data store,
|
|
83
|
+
such as table, files, folders and documents.
|
|
84
|
+
|
|
85
|
+
You probably want to use the subclasses and not this class directly.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
id: uuid.UUID
|
|
89
|
+
name: str
|
|
90
|
+
description: str | None = None
|
|
91
|
+
version: str
|
|
92
|
+
|
|
93
|
+
settings: DatasetSettingsType
|
|
94
|
+
linked_service: LinkedServiceType
|
|
95
|
+
|
|
96
|
+
serializer: SerializerType | None = None
|
|
97
|
+
deserializer: DeserializerType | None = None
|
|
98
|
+
|
|
99
|
+
input: Any | None = field(default=None, metadata={"serialize": False})
|
|
100
|
+
output: Any | None = field(default=None, metadata={"serialize": False})
|
|
101
|
+
|
|
102
|
+
checkpoint: dict[str, Any] = field(default_factory=dict)
|
|
103
|
+
operation: OperationInfo = field(default_factory=OperationInfo)
|
|
104
|
+
|
|
105
|
+
def __init_subclass__(cls, **kwargs: Any) -> None:
|
|
106
|
+
"""
|
|
107
|
+
Initialize the subclass.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
kwargs: The keyword arguments.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
The subclass.
|
|
114
|
+
"""
|
|
115
|
+
super().__init_subclass__(**kwargs)
|
|
116
|
+
for name in DatasetMethod.all_values():
|
|
117
|
+
method = cls.__dict__.get(name)
|
|
118
|
+
if method is not None and not getattr(method, "_tracked", False):
|
|
119
|
+
setattr(cls, name, track_result(method))
|
|
120
|
+
|
|
121
|
+
def __enter__(self) -> Self:
|
|
122
|
+
"""
|
|
123
|
+
Context manager enter.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
The dataset.
|
|
127
|
+
"""
|
|
128
|
+
return self
|
|
129
|
+
|
|
130
|
+
def __exit__(
|
|
131
|
+
self,
|
|
132
|
+
exc_type: type[BaseException] | None,
|
|
133
|
+
exc_value: BaseException | None,
|
|
134
|
+
traceback: TracebackType | None,
|
|
135
|
+
) -> None:
|
|
136
|
+
"""
|
|
137
|
+
Context manager exit.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
exc_type: The type of the exception.
|
|
141
|
+
exc_value: The value of the exception.
|
|
142
|
+
traceback: The traceback of the exception.
|
|
143
|
+
"""
|
|
144
|
+
self.close()
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def supports_checkpoint(self) -> bool:
|
|
148
|
+
"""Whether this provider supports incremental loads via ``self.checkpoint``."""
|
|
149
|
+
return False
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
@abstractmethod
|
|
153
|
+
def type(self) -> StrEnum:
|
|
154
|
+
"""
|
|
155
|
+
Get the type of the dataset.
|
|
156
|
+
"""
|
|
157
|
+
...
|
|
158
|
+
|
|
159
|
+
@abstractmethod
|
|
160
|
+
def create(self) -> None:
|
|
161
|
+
"""
|
|
162
|
+
Insert all rows in ``self.input`` into the target as a single atomic
|
|
163
|
+
transaction. Must not delete, update, or overwrite existing data.
|
|
164
|
+
|
|
165
|
+
Raises:
|
|
166
|
+
CreateError: If the operation fails.
|
|
167
|
+
NotSupportedError: If the provider does not support create.
|
|
168
|
+
|
|
169
|
+
See Also:
|
|
170
|
+
Full contract: ``docs/DATASET_CONTRACT.md`` -- ``create()``
|
|
171
|
+
"""
|
|
172
|
+
...
|
|
173
|
+
|
|
174
|
+
@abstractmethod
|
|
175
|
+
def read(self) -> None:
|
|
176
|
+
"""
|
|
177
|
+
Read data from the source and assign it to ``self.output``.
|
|
178
|
+
Pagination within a single call is handled internally.
|
|
179
|
+
Supports incremental loads via ``self.checkpoint``.
|
|
180
|
+
|
|
181
|
+
Raises:
|
|
182
|
+
ReadError: If the operation fails.
|
|
183
|
+
NotSupportedError: If the provider does not support read.
|
|
184
|
+
|
|
185
|
+
See Also:
|
|
186
|
+
Full contract: ``docs/DATASET_CONTRACT.md`` -- ``read()``
|
|
187
|
+
"""
|
|
188
|
+
...
|
|
189
|
+
|
|
190
|
+
@abstractmethod
|
|
191
|
+
def update(self) -> None:
|
|
192
|
+
"""
|
|
193
|
+
Update existing rows in the target matched by identity columns
|
|
194
|
+
defined in ``self.settings``. Atomic. Must not insert new rows.
|
|
195
|
+
|
|
196
|
+
Raises:
|
|
197
|
+
UpdateError: If the operation fails.
|
|
198
|
+
NotSupportedError: If the provider does not support update.
|
|
199
|
+
|
|
200
|
+
See Also:
|
|
201
|
+
Full contract: ``docs/DATASET_CONTRACT.md`` -- ``update()``
|
|
202
|
+
"""
|
|
203
|
+
...
|
|
204
|
+
|
|
205
|
+
@abstractmethod
|
|
206
|
+
def upsert(self) -> None:
|
|
207
|
+
"""
|
|
208
|
+
Insert rows that do not exist, update rows that do, matched by
|
|
209
|
+
identity columns defined in ``self.settings``. Atomic.
|
|
210
|
+
|
|
211
|
+
Raises:
|
|
212
|
+
UpsertError: If the operation fails.
|
|
213
|
+
NotSupportedError: If the provider does not support upsert.
|
|
214
|
+
|
|
215
|
+
See Also:
|
|
216
|
+
Full contract: ``docs/DATASET_CONTRACT.md`` -- ``upsert()``
|
|
217
|
+
"""
|
|
218
|
+
...
|
|
219
|
+
|
|
220
|
+
@abstractmethod
|
|
221
|
+
def delete(self) -> None:
|
|
222
|
+
"""
|
|
223
|
+
Remove specific rows from the target matched by identity columns
|
|
224
|
+
defined in ``self.settings``. Atomic. Idempotent.
|
|
225
|
+
|
|
226
|
+
Raises:
|
|
227
|
+
DeleteError: If the operation fails.
|
|
228
|
+
NotSupportedError: If the provider does not support delete.
|
|
229
|
+
|
|
230
|
+
See Also:
|
|
231
|
+
Full contract: ``docs/DATASET_CONTRACT.md`` -- ``delete()``
|
|
232
|
+
"""
|
|
233
|
+
...
|
|
234
|
+
|
|
235
|
+
@abstractmethod
|
|
236
|
+
def purge(self) -> None:
|
|
237
|
+
"""
|
|
238
|
+
Remove all content from the target. ``self.input`` is not used.
|
|
239
|
+
Atomic. Idempotent.
|
|
240
|
+
|
|
241
|
+
Raises:
|
|
242
|
+
PurgeError: If the operation fails.
|
|
243
|
+
NotSupportedError: If the provider does not support purge.
|
|
244
|
+
|
|
245
|
+
See Also:
|
|
246
|
+
Full contract: ``docs/DATASET_CONTRACT.md`` -- ``purge()``
|
|
247
|
+
"""
|
|
248
|
+
...
|
|
249
|
+
|
|
250
|
+
@abstractmethod
|
|
251
|
+
def list(self) -> None:
|
|
252
|
+
"""
|
|
253
|
+
Discover available resources and populate ``self.output`` with a
|
|
254
|
+
DataFrame of resources and their metadata. Idempotent.
|
|
255
|
+
|
|
256
|
+
Raises:
|
|
257
|
+
ListError: If the operation fails.
|
|
258
|
+
NotSupportedError: If the provider does not support listing.
|
|
259
|
+
|
|
260
|
+
See Also:
|
|
261
|
+
Full contract: ``docs/DATASET_CONTRACT.md`` -- ``list()``
|
|
262
|
+
"""
|
|
263
|
+
...
|
|
264
|
+
|
|
265
|
+
@abstractmethod
|
|
266
|
+
def rename(self) -> None:
|
|
267
|
+
"""
|
|
268
|
+
Rename the resource in the backend. Atomic. Not idempotent.
|
|
269
|
+
|
|
270
|
+
Raises:
|
|
271
|
+
RenameError: If the operation fails.
|
|
272
|
+
NotSupportedError: If the provider does not support renaming.
|
|
273
|
+
|
|
274
|
+
See Also:
|
|
275
|
+
Full contract: ``docs/DATASET_CONTRACT.md`` -- ``rename()``
|
|
276
|
+
"""
|
|
277
|
+
...
|
|
278
|
+
|
|
279
|
+
@abstractmethod
|
|
280
|
+
def close(self) -> None:
|
|
281
|
+
"""
|
|
282
|
+
Release any connections, sessions, or handles held by the linked
|
|
283
|
+
service. Must not raise if already closed. Idempotent.
|
|
284
|
+
|
|
285
|
+
See Also:
|
|
286
|
+
Full contract: ``docs/DATASET_CONTRACT.md`` -- ``close()``
|
|
287
|
+
"""
|
|
288
|
+
...
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
@dataclass(kw_only=True)
|
|
292
|
+
class BinaryDataset(
|
|
293
|
+
Dataset[LinkedServiceType, DatasetSettingsType, SerializerType, DeserializerType],
|
|
294
|
+
Generic[LinkedServiceType, DatasetSettingsType, SerializerType, DeserializerType],
|
|
295
|
+
):
|
|
296
|
+
"""
|
|
297
|
+
Binary dataset object which identifies data within a data store,
|
|
298
|
+
such as files, folders and documents.
|
|
299
|
+
|
|
300
|
+
The input of the dataset is a binary file.
|
|
301
|
+
The output of the dataset is a binary file.
|
|
302
|
+
"""
|
|
303
|
+
|
|
304
|
+
input: io.BytesIO = field(default_factory=io.BytesIO, metadata={"serialize": False})
|
|
305
|
+
output: io.BytesIO = field(default_factory=io.BytesIO, metadata={"serialize": False})
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
@dataclass(kw_only=True)
|
|
309
|
+
class TabularDataset(
|
|
310
|
+
Dataset[LinkedServiceType, DatasetSettingsType, SerializerType, DeserializerType],
|
|
311
|
+
Generic[LinkedServiceType, DatasetSettingsType, SerializerType, DeserializerType],
|
|
312
|
+
):
|
|
313
|
+
"""
|
|
314
|
+
Tabular dataset object which identifies data within a data store,
|
|
315
|
+
such as table/csv/json/parquet/parquetdataset/ and other documents.
|
|
316
|
+
|
|
317
|
+
The input of the dataset is a pandas DataFrame.
|
|
318
|
+
The output of the dataset is a pandas DataFrame.
|
|
319
|
+
"""
|
|
320
|
+
|
|
321
|
+
input: pd.DataFrame = field(default_factory=pd.DataFrame, metadata={"serialize": False})
|
|
322
|
+
output: pd.DataFrame = field(default_factory=pd.DataFrame, metadata={"serialize": False})
|