ygg 0.1.57__py3-none-any.whl → 0.1.64__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.
- {ygg-0.1.57.dist-info → ygg-0.1.64.dist-info}/METADATA +2 -2
- ygg-0.1.64.dist-info/RECORD +74 -0
- yggdrasil/ai/__init__.py +2 -0
- yggdrasil/ai/session.py +87 -0
- yggdrasil/ai/sql_session.py +310 -0
- yggdrasil/databricks/__init__.py +0 -3
- yggdrasil/databricks/compute/cluster.py +68 -113
- yggdrasil/databricks/compute/command_execution.py +674 -0
- yggdrasil/databricks/compute/exceptions.py +19 -0
- yggdrasil/databricks/compute/execution_context.py +491 -282
- yggdrasil/databricks/compute/remote.py +4 -14
- yggdrasil/databricks/exceptions.py +10 -0
- yggdrasil/databricks/sql/__init__.py +0 -4
- yggdrasil/databricks/sql/engine.py +178 -178
- yggdrasil/databricks/sql/exceptions.py +9 -1
- yggdrasil/databricks/sql/statement_result.py +108 -120
- yggdrasil/databricks/sql/warehouse.py +339 -92
- yggdrasil/databricks/workspaces/io.py +185 -40
- yggdrasil/databricks/workspaces/path.py +114 -100
- yggdrasil/databricks/workspaces/workspace.py +210 -61
- yggdrasil/exceptions.py +7 -0
- yggdrasil/libs/databrickslib.py +22 -18
- yggdrasil/libs/extensions/spark_extensions.py +1 -1
- yggdrasil/libs/pandaslib.py +15 -6
- yggdrasil/libs/polarslib.py +49 -13
- yggdrasil/pyutils/__init__.py +1 -2
- yggdrasil/pyutils/callable_serde.py +12 -19
- yggdrasil/pyutils/exceptions.py +16 -0
- yggdrasil/pyutils/modules.py +6 -7
- yggdrasil/pyutils/python_env.py +16 -21
- yggdrasil/pyutils/waiting_config.py +171 -0
- yggdrasil/requests/msal.py +9 -96
- yggdrasil/types/cast/arrow_cast.py +3 -0
- yggdrasil/types/cast/pandas_cast.py +157 -169
- yggdrasil/types/cast/polars_cast.py +11 -43
- yggdrasil/types/dummy_class.py +81 -0
- yggdrasil/types/file_format.py +6 -2
- yggdrasil/types/python_defaults.py +92 -76
- yggdrasil/version.py +1 -1
- ygg-0.1.57.dist-info/RECORD +0 -66
- yggdrasil/databricks/ai/loki.py +0 -53
- {ygg-0.1.57.dist-info → ygg-0.1.64.dist-info}/WHEEL +0 -0
- {ygg-0.1.57.dist-info → ygg-0.1.64.dist-info}/entry_points.txt +0 -0
- {ygg-0.1.57.dist-info → ygg-0.1.64.dist-info}/licenses/LICENSE +0 -0
- {ygg-0.1.57.dist-info → ygg-0.1.64.dist-info}/top_level.txt +0 -0
- /yggdrasil/{databricks/ai/__init__.py → pyutils/mimetypes.py} +0 -0
|
@@ -1,38 +1,41 @@
|
|
|
1
1
|
import dataclasses as dc
|
|
2
2
|
import inspect
|
|
3
3
|
import logging
|
|
4
|
-
|
|
4
|
+
import time
|
|
5
|
+
from typing import Optional, Sequence, Any, Type, TypeVar, Union, List
|
|
5
6
|
|
|
6
|
-
from
|
|
7
|
-
from
|
|
7
|
+
from .statement_result import StatementResult
|
|
8
|
+
from ..workspaces import WorkspaceService
|
|
9
|
+
from ...libs.databrickslib import DatabricksDummyClass
|
|
10
|
+
from ...pyutils.equality import dicts_equal
|
|
8
11
|
from ...pyutils.expiring_dict import ExpiringDict
|
|
12
|
+
from ...pyutils.waiting_config import WaitingConfig, WaitingConfigArg
|
|
9
13
|
|
|
10
14
|
try:
|
|
11
15
|
from databricks.sdk import WarehousesAPI
|
|
12
16
|
from databricks.sdk.service.sql import (
|
|
13
|
-
State, EndpointInfo,
|
|
14
|
-
|
|
17
|
+
State, EndpointInfo,
|
|
18
|
+
EndpointTags, EndpointTagPair, EndpointInfoWarehouseType,
|
|
19
|
+
GetWarehouseResponse, GetWarehouseResponseWarehouseType,
|
|
20
|
+
Disposition, Format,
|
|
21
|
+
ExecuteStatementRequestOnWaitTimeout, StatementParameterListItem
|
|
22
|
+
)
|
|
15
23
|
|
|
16
24
|
_CREATE_ARG_NAMES = {_ for _ in inspect.signature(WarehousesAPI.create).parameters.keys()}
|
|
17
25
|
_EDIT_ARG_NAMES = {_ for _ in inspect.signature(WarehousesAPI.edit).parameters.keys()}
|
|
18
26
|
except ImportError:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class EndpointTagPair:
|
|
32
|
-
pass
|
|
33
|
-
|
|
34
|
-
class EndpointInfoWarehouseType:
|
|
35
|
-
pass
|
|
27
|
+
WarehousesAPI = DatabricksDummyClass
|
|
28
|
+
State = DatabricksDummyClass
|
|
29
|
+
EndpointInfo = DatabricksDummyClass
|
|
30
|
+
EndpointTags = DatabricksDummyClass
|
|
31
|
+
EndpointTagPair = DatabricksDummyClass
|
|
32
|
+
EndpointInfoWarehouseType = DatabricksDummyClass
|
|
33
|
+
GetWarehouseResponse = DatabricksDummyClass
|
|
34
|
+
GetWarehouseResponseWarehouseType = DatabricksDummyClass
|
|
35
|
+
Disposition = DatabricksDummyClass
|
|
36
|
+
Format = DatabricksDummyClass
|
|
37
|
+
ExecuteStatementRequestOnWaitTimeout = DatabricksDummyClass
|
|
38
|
+
StatementParameterListItem = DatabricksDummyClass
|
|
36
39
|
|
|
37
40
|
|
|
38
41
|
__all__ = [
|
|
@@ -42,6 +45,35 @@ __all__ = [
|
|
|
42
45
|
|
|
43
46
|
LOGGER = logging.getLogger(__name__)
|
|
44
47
|
NAME_ID_CACHE: dict[str, ExpiringDict] = {}
|
|
48
|
+
T = TypeVar("T")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def safeGetWarehouseResponse(src: Union[GetWarehouseResponse, EndpointInfo]) -> GetWarehouseResponse:
|
|
52
|
+
if isinstance(src, GetWarehouseResponse):
|
|
53
|
+
return src
|
|
54
|
+
|
|
55
|
+
payload = _copy_common_fields(src, GetWarehouseResponse, skip={"warehouse_type"})
|
|
56
|
+
|
|
57
|
+
payload["warehouse_type"] = _safe_map_enum(
|
|
58
|
+
GetWarehouseResponseWarehouseType,
|
|
59
|
+
getattr(src, "warehouse_type", None),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
return GetWarehouseResponse(**payload)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def safeEndpointInfo(src: Union[GetWarehouseResponse, EndpointInfo]) -> EndpointInfo:
|
|
66
|
+
if isinstance(src, EndpointInfo):
|
|
67
|
+
return src
|
|
68
|
+
|
|
69
|
+
payload = _copy_common_fields(src, EndpointInfo, skip={"warehouse_type"})
|
|
70
|
+
|
|
71
|
+
payload["warehouse_type"] = _safe_map_enum(
|
|
72
|
+
EndpointInfoWarehouseType,
|
|
73
|
+
getattr(src, "warehouse_type", None),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
return EndpointInfo(**payload)
|
|
45
77
|
|
|
46
78
|
|
|
47
79
|
def set_cached_warehouse_name(
|
|
@@ -66,26 +98,69 @@ def get_cached_warehouse_id(
|
|
|
66
98
|
return existing.get(warehouse_name) if existing else None
|
|
67
99
|
|
|
68
100
|
|
|
101
|
+
|
|
102
|
+
def _safe_map_enum(dst_enum: Type[T], src_val: Any) -> Optional[T]:
|
|
103
|
+
"""
|
|
104
|
+
Best-effort mapping:
|
|
105
|
+
- if src_val is already a dst_enum member -> return it
|
|
106
|
+
- try dst_enum(src_val) for value-based enums
|
|
107
|
+
- try dst_enum(src_val.value) if src is enum-like
|
|
108
|
+
- try dst_enum[src_val.name] if src is enum-like
|
|
109
|
+
- try dst_enum[str(src_val)] as a last resort
|
|
110
|
+
If nothing works -> None
|
|
111
|
+
"""
|
|
112
|
+
if src_val is None:
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
# already the right type
|
|
116
|
+
if isinstance(src_val, dst_enum):
|
|
117
|
+
return src_val
|
|
118
|
+
|
|
119
|
+
# direct constructor (works if src_val is value-compatible)
|
|
120
|
+
try:
|
|
121
|
+
return dst_enum(src_val) # type: ignore[misc]
|
|
122
|
+
except Exception:
|
|
123
|
+
pass
|
|
124
|
+
|
|
125
|
+
# enum-like: .value
|
|
126
|
+
try:
|
|
127
|
+
return dst_enum(src_val.value) # type: ignore[misc]
|
|
128
|
+
except Exception:
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
# enum-like: .name
|
|
132
|
+
try:
|
|
133
|
+
return dst_enum[src_val.name] # type: ignore[index]
|
|
134
|
+
except Exception:
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
# string fallback
|
|
138
|
+
try:
|
|
139
|
+
return dst_enum(str(src_val)) # type: ignore[misc]
|
|
140
|
+
except Exception:
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _copy_common_fields(src: Any, dst_cls: Type[T], *, skip: set[str] = frozenset()) -> dict:
|
|
145
|
+
dst_field_names = {f.name for f in dc.fields(dst_cls)}
|
|
146
|
+
payload = {}
|
|
147
|
+
for name in dst_field_names:
|
|
148
|
+
if name in skip:
|
|
149
|
+
continue
|
|
150
|
+
payload[name] = getattr(src, name, None)
|
|
151
|
+
return payload
|
|
152
|
+
|
|
153
|
+
|
|
69
154
|
@dc.dataclass
|
|
70
155
|
class SQLWarehouse(WorkspaceService):
|
|
71
156
|
warehouse_id: Optional[str] = None
|
|
72
157
|
warehouse_name: Optional[str] = None
|
|
73
158
|
|
|
74
|
-
_details: Optional[EndpointInfo] = dc.field(default=None, repr=False)
|
|
159
|
+
_details: Optional[EndpointInfo] = dc.field(default=None, repr=False, hash=False, compare=False)
|
|
75
160
|
|
|
76
161
|
def warehouse_client(self):
|
|
77
162
|
return self.workspace.sdk().warehouses
|
|
78
163
|
|
|
79
|
-
def default(
|
|
80
|
-
self,
|
|
81
|
-
name: str = "YGG-DEFAULT",
|
|
82
|
-
**kwargs
|
|
83
|
-
):
|
|
84
|
-
return self.create_or_update(
|
|
85
|
-
name=name,
|
|
86
|
-
**kwargs
|
|
87
|
-
)
|
|
88
|
-
|
|
89
164
|
@property
|
|
90
165
|
def details(self) -> EndpointInfo:
|
|
91
166
|
if self._details is None:
|
|
@@ -100,31 +175,65 @@ class SQLWarehouse(WorkspaceService):
|
|
|
100
175
|
return self
|
|
101
176
|
|
|
102
177
|
@details.setter
|
|
103
|
-
def details(self, value: EndpointInfo):
|
|
104
|
-
self._details = value
|
|
178
|
+
def details(self, value: Union[GetWarehouseResponse, EndpointInfo]):
|
|
179
|
+
self._details = safeEndpointInfo(value)
|
|
105
180
|
|
|
106
|
-
self.
|
|
107
|
-
|
|
181
|
+
if self._details is not None:
|
|
182
|
+
self.warehouse_id = self._details.id
|
|
183
|
+
self.warehouse_name = self._details.name
|
|
108
184
|
|
|
109
185
|
@property
|
|
110
186
|
def state(self):
|
|
111
187
|
return self.latest_details().state
|
|
112
188
|
|
|
113
189
|
@property
|
|
114
|
-
def
|
|
115
|
-
return self.
|
|
190
|
+
def is_serverless(self):
|
|
191
|
+
return self.details.enable_serverless_compute
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def is_running(self):
|
|
195
|
+
return self.state == State.RUNNING
|
|
116
196
|
|
|
117
197
|
@property
|
|
118
|
-
def
|
|
119
|
-
return self.state in {
|
|
198
|
+
def is_pending(self):
|
|
199
|
+
return self.state in {
|
|
200
|
+
State.DELETING, State.STARTING, State.STOPPING
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
def wait_for_status(
|
|
204
|
+
self,
|
|
205
|
+
wait: Optional[WaitingConfigArg] = None
|
|
206
|
+
):
|
|
207
|
+
"""
|
|
208
|
+
Polls until not pending, using wait.sleep(iteration, start).
|
|
209
|
+
|
|
210
|
+
WaitingConfig:
|
|
211
|
+
- timeout: total wall-clock seconds (0 => no timeout)
|
|
212
|
+
- interval: base sleep seconds (0 => busy/no-sleep)
|
|
213
|
+
- backoff: exponential factor (>= 1)
|
|
214
|
+
- max_interval: cap for sleep seconds (0 => no cap)
|
|
215
|
+
|
|
216
|
+
Returns self.
|
|
217
|
+
"""
|
|
218
|
+
wait = WaitingConfig.default() if wait is None else WaitingConfig.check_arg(wait)
|
|
219
|
+
|
|
220
|
+
start = time.time()
|
|
221
|
+
iteration = 0
|
|
222
|
+
|
|
223
|
+
if wait.timeout:
|
|
224
|
+
while self.is_pending:
|
|
225
|
+
wait.sleep(iteration=iteration, start=start)
|
|
226
|
+
iteration += 1
|
|
227
|
+
|
|
228
|
+
return self
|
|
120
229
|
|
|
121
230
|
def start(self):
|
|
122
|
-
if not self.
|
|
231
|
+
if not self.is_running:
|
|
123
232
|
self.warehouse_client().start(id=self.warehouse_id)
|
|
124
233
|
return self
|
|
125
234
|
|
|
126
235
|
def stop(self):
|
|
127
|
-
if self.
|
|
236
|
+
if self.is_running:
|
|
128
237
|
return self.warehouse_client().stop(id=self.warehouse_id)
|
|
129
238
|
return self
|
|
130
239
|
|
|
@@ -132,38 +241,66 @@ class SQLWarehouse(WorkspaceService):
|
|
|
132
241
|
self,
|
|
133
242
|
warehouse_id: Optional[str] = None,
|
|
134
243
|
warehouse_name: Optional[str] = None,
|
|
135
|
-
raise_error: bool = True
|
|
244
|
+
raise_error: bool = True,
|
|
136
245
|
):
|
|
137
246
|
if warehouse_id:
|
|
247
|
+
if warehouse_id == self.warehouse_id:
|
|
248
|
+
return self
|
|
249
|
+
|
|
138
250
|
return SQLWarehouse(
|
|
139
251
|
workspace=self.workspace,
|
|
140
252
|
warehouse_id=warehouse_id,
|
|
141
253
|
warehouse_name=warehouse_name
|
|
142
254
|
)
|
|
143
255
|
|
|
144
|
-
|
|
256
|
+
elif self.warehouse_id:
|
|
145
257
|
return self
|
|
146
258
|
|
|
147
|
-
|
|
259
|
+
starter_warehouse, starter_name = None, "Serverless Starter Warehouse"
|
|
260
|
+
warehouse_name = warehouse_name or self.warehouse_name or self._make_default_name(enable_serverless_compute=True)
|
|
148
261
|
|
|
149
|
-
|
|
262
|
+
if warehouse_name:
|
|
263
|
+
if warehouse_name == self.warehouse_name:
|
|
264
|
+
return self
|
|
150
265
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
workspace=self.workspace,
|
|
154
|
-
warehouse_id=warehouse_id,
|
|
266
|
+
warehouse_id = get_cached_warehouse_id(
|
|
267
|
+
host=self.workspace.safe_host,
|
|
155
268
|
warehouse_name=warehouse_name
|
|
156
269
|
)
|
|
157
270
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
271
|
+
if warehouse_id:
|
|
272
|
+
if warehouse_id == self.warehouse_id:
|
|
273
|
+
return self
|
|
274
|
+
|
|
275
|
+
return SQLWarehouse(
|
|
276
|
+
workspace=self.workspace,
|
|
277
|
+
warehouse_id=warehouse_id,
|
|
278
|
+
warehouse_name=warehouse_name
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
for warehouse in self.list_warehouses():
|
|
282
|
+
if warehouse.warehouse_name == warehouse_name:
|
|
283
|
+
set_cached_warehouse_name(
|
|
284
|
+
host=self.workspace.safe_host,
|
|
285
|
+
warehouse_name=warehouse_name,
|
|
286
|
+
warehouse_id=warehouse.warehouse_id
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
return warehouse
|
|
290
|
+
|
|
291
|
+
elif warehouse.warehouse_name == starter_warehouse:
|
|
292
|
+
starter_warehouse = warehouse
|
|
293
|
+
|
|
294
|
+
if starter_warehouse is not None:
|
|
295
|
+
return starter_warehouse
|
|
162
296
|
|
|
163
297
|
if raise_error:
|
|
298
|
+
v = warehouse_name or warehouse_id
|
|
299
|
+
|
|
164
300
|
raise ValueError(
|
|
165
|
-
f"SQL Warehouse {
|
|
301
|
+
f"SQL Warehouse {v!r} not found"
|
|
166
302
|
)
|
|
303
|
+
|
|
167
304
|
return None
|
|
168
305
|
|
|
169
306
|
def list_warehouses(self):
|
|
@@ -177,9 +314,16 @@ class SQLWarehouse(WorkspaceService):
|
|
|
177
314
|
|
|
178
315
|
yield warehouse
|
|
179
316
|
|
|
317
|
+
def _make_default_name(self, enable_serverless_compute: bool = True):
|
|
318
|
+
return "%s%s" % (
|
|
319
|
+
self.workspace.product or "yggdrasil",
|
|
320
|
+
" serverless" if enable_serverless_compute else ""
|
|
321
|
+
)
|
|
322
|
+
|
|
180
323
|
def _check_details(
|
|
181
324
|
self,
|
|
182
325
|
keys: Sequence[str],
|
|
326
|
+
update: bool,
|
|
183
327
|
details: Optional[EndpointInfo] = None,
|
|
184
328
|
**warehouse_specs
|
|
185
329
|
):
|
|
@@ -206,10 +350,20 @@ class SQLWarehouse(WorkspaceService):
|
|
|
206
350
|
if details.cluster_size is None:
|
|
207
351
|
details.cluster_size = "Small"
|
|
208
352
|
|
|
209
|
-
if details.
|
|
210
|
-
details.
|
|
353
|
+
if details.warehouse_type is None:
|
|
354
|
+
details.warehouse_type = EndpointInfoWarehouseType.PRO
|
|
355
|
+
|
|
356
|
+
if details.enable_serverless_compute is None:
|
|
357
|
+
details.enable_serverless_compute = details.warehouse_type.value == EndpointInfoWarehouseType.PRO.value
|
|
358
|
+
elif details.enable_serverless_compute:
|
|
359
|
+
details.warehouse_type = EndpointInfoWarehouseType.PRO
|
|
211
360
|
|
|
212
|
-
|
|
361
|
+
if not details.name:
|
|
362
|
+
details.name = self._make_default_name(
|
|
363
|
+
enable_serverless_compute=details.enable_serverless_compute
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
default_tags = self.workspace.default_tags(update=update)
|
|
213
367
|
|
|
214
368
|
if details.tags is None:
|
|
215
369
|
details.tags = EndpointTags(custom_tags=[
|
|
@@ -233,42 +387,61 @@ class SQLWarehouse(WorkspaceService):
|
|
|
233
387
|
if not details.max_num_clusters:
|
|
234
388
|
details.max_num_clusters = 4
|
|
235
389
|
|
|
236
|
-
if details.warehouse_type is None:
|
|
237
|
-
details.warehouse_type = EndpointInfoWarehouseType.CLASSIC
|
|
238
|
-
|
|
239
390
|
return details
|
|
240
391
|
|
|
241
392
|
def create_or_update(
|
|
242
393
|
self,
|
|
243
394
|
warehouse_id: Optional[str] = None,
|
|
244
395
|
name: Optional[str] = None,
|
|
396
|
+
wait: Optional[WaitingConfig] = None,
|
|
245
397
|
**warehouse_specs
|
|
246
398
|
):
|
|
247
399
|
name = name or self.warehouse_name
|
|
248
|
-
found = self.find_warehouse(
|
|
400
|
+
found = self.find_warehouse(
|
|
401
|
+
warehouse_id=warehouse_id,
|
|
402
|
+
warehouse_name=name,
|
|
403
|
+
raise_error=False,
|
|
404
|
+
)
|
|
249
405
|
|
|
250
406
|
if found is not None:
|
|
251
|
-
return found.update(name=name, **warehouse_specs)
|
|
252
|
-
|
|
407
|
+
return found.update(name=name, wait=wait, **warehouse_specs)
|
|
408
|
+
|
|
409
|
+
return self.create(name=name, wait=wait, **warehouse_specs)
|
|
253
410
|
|
|
254
411
|
def create(
|
|
255
412
|
self,
|
|
256
413
|
name: Optional[str] = None,
|
|
414
|
+
wait: Optional[WaitingConfigArg] = None,
|
|
257
415
|
**warehouse_specs
|
|
258
416
|
):
|
|
259
417
|
name = name or self.warehouse_name
|
|
260
418
|
|
|
419
|
+
checked_wait = WaitingConfig.check_arg(wait)
|
|
420
|
+
|
|
261
421
|
details = self._check_details(
|
|
262
422
|
keys=_CREATE_ARG_NAMES,
|
|
423
|
+
update=False,
|
|
263
424
|
name=name,
|
|
264
425
|
**warehouse_specs
|
|
265
426
|
)
|
|
266
427
|
|
|
267
|
-
|
|
428
|
+
update_details = {
|
|
268
429
|
k: v
|
|
269
430
|
for k, v in details.as_shallow_dict().items()
|
|
270
431
|
if k in _CREATE_ARG_NAMES
|
|
271
|
-
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if checked_wait.timeout:
|
|
435
|
+
info = self.warehouse_client().create_and_wait(
|
|
436
|
+
timeout=checked_wait.timeout_timedelta,
|
|
437
|
+
**update_details
|
|
438
|
+
)
|
|
439
|
+
else:
|
|
440
|
+
info = self.warehouse_client().create(**update_details)
|
|
441
|
+
|
|
442
|
+
update_details["id"] = info.response.id
|
|
443
|
+
|
|
444
|
+
info = EndpointInfo(**update_details)
|
|
272
445
|
|
|
273
446
|
return SQLWarehouse(
|
|
274
447
|
workspace=self.workspace,
|
|
@@ -279,11 +452,14 @@ class SQLWarehouse(WorkspaceService):
|
|
|
279
452
|
|
|
280
453
|
def update(
|
|
281
454
|
self,
|
|
455
|
+
wait: Optional[WaitingConfigArg] = None,
|
|
282
456
|
**warehouse_specs
|
|
283
457
|
):
|
|
284
458
|
if not warehouse_specs:
|
|
285
459
|
return self
|
|
286
460
|
|
|
461
|
+
wait = WaitingConfig.check_arg(wait)
|
|
462
|
+
|
|
287
463
|
existing_details = {
|
|
288
464
|
k: v
|
|
289
465
|
for k, v in self.details.as_shallow_dict().items()
|
|
@@ -293,7 +469,12 @@ class SQLWarehouse(WorkspaceService):
|
|
|
293
469
|
update_details = {
|
|
294
470
|
k: v
|
|
295
471
|
for k, v in (
|
|
296
|
-
self._check_details(
|
|
472
|
+
self._check_details(
|
|
473
|
+
details=self.details,
|
|
474
|
+
update=True,
|
|
475
|
+
keys=_EDIT_ARG_NAMES,
|
|
476
|
+
**warehouse_specs
|
|
477
|
+
)
|
|
297
478
|
.as_shallow_dict()
|
|
298
479
|
.items()
|
|
299
480
|
)
|
|
@@ -304,22 +485,23 @@ class SQLWarehouse(WorkspaceService):
|
|
|
304
485
|
existing_details,
|
|
305
486
|
update_details,
|
|
306
487
|
keys=_EDIT_ARG_NAMES,
|
|
307
|
-
treat_missing_as_none=True,
|
|
308
|
-
float_tol=0.0, # set e.g. 1e-6 if you have float-y stuff
|
|
309
488
|
)
|
|
310
489
|
|
|
311
490
|
if not same:
|
|
312
|
-
diff = {
|
|
313
|
-
k: v[1]
|
|
314
|
-
for k, v in dict_diff(existing_details, update_details, keys=_EDIT_ARG_NAMES).items()
|
|
315
|
-
}
|
|
316
|
-
|
|
317
491
|
LOGGER.debug(
|
|
318
492
|
"Updating %s with %s",
|
|
319
|
-
self,
|
|
493
|
+
self, update_details
|
|
320
494
|
)
|
|
321
495
|
|
|
322
|
-
|
|
496
|
+
if wait.timeout:
|
|
497
|
+
self.details = self.warehouse_client().edit_and_wait(
|
|
498
|
+
timeout=wait.timeout_timedelta,
|
|
499
|
+
**update_details
|
|
500
|
+
)
|
|
501
|
+
else:
|
|
502
|
+
_ = self.warehouse_client().edit(**update_details)
|
|
503
|
+
|
|
504
|
+
self.details = EndpointInfo(**update_details)
|
|
323
505
|
|
|
324
506
|
LOGGER.info(
|
|
325
507
|
"Updated %s",
|
|
@@ -328,28 +510,93 @@ class SQLWarehouse(WorkspaceService):
|
|
|
328
510
|
|
|
329
511
|
return self
|
|
330
512
|
|
|
331
|
-
def
|
|
513
|
+
def execute(
|
|
332
514
|
self,
|
|
333
|
-
|
|
515
|
+
statement: Optional[str] = None,
|
|
516
|
+
*,
|
|
334
517
|
warehouse_id: Optional[str] = None,
|
|
518
|
+
warehouse_name: Optional[str] = None,
|
|
519
|
+
byte_limit: Optional[int] = None,
|
|
520
|
+
disposition: Optional[Disposition] = None,
|
|
521
|
+
format: Optional[Format] = None,
|
|
522
|
+
on_wait_timeout: Optional[ExecuteStatementRequestOnWaitTimeout] = None,
|
|
523
|
+
parameters: Optional[List[StatementParameterListItem]] = None,
|
|
524
|
+
row_limit: Optional[int] = None,
|
|
525
|
+
wait_timeout: Optional[str] = None,
|
|
335
526
|
catalog_name: Optional[str] = None,
|
|
336
527
|
schema_name: Optional[str] = None,
|
|
337
|
-
|
|
338
|
-
|
|
528
|
+
wait: Optional[WaitingConfigArg] = True
|
|
529
|
+
) -> StatementResult:
|
|
530
|
+
"""Execute a SQL statement via Spark or Databricks SQL Statement Execution API.
|
|
531
|
+
|
|
532
|
+
Engine resolution:
|
|
533
|
+
- If `engine` is not provided and a Spark session is active -> uses Spark.
|
|
534
|
+
- Otherwise uses Databricks SQL API (warehouse).
|
|
535
|
+
|
|
536
|
+
Waiting behavior (`wait_result`):
|
|
537
|
+
- If True (default): returns a StatementResult in terminal state (SUCCEEDED/FAILED/CANCELED).
|
|
538
|
+
- If False: returns immediately with the initial handle (caller can `.wait()` later).
|
|
339
539
|
|
|
340
540
|
Args:
|
|
341
|
-
|
|
342
|
-
warehouse_id:
|
|
343
|
-
|
|
344
|
-
|
|
541
|
+
statement: SQL statement to execute. If None, a `SELECT *` is generated from the table params.
|
|
542
|
+
warehouse_id: Warehouse override (for API engine).
|
|
543
|
+
warehouse_name: Warehouse name override (for API engine).
|
|
544
|
+
byte_limit: Optional byte limit for results.
|
|
545
|
+
disposition: Result disposition mode (API engine).
|
|
546
|
+
format: Result format (API engine).
|
|
547
|
+
on_wait_timeout: Timeout behavior for waiting (API engine).
|
|
548
|
+
parameters: Optional statement parameters (API engine).
|
|
549
|
+
row_limit: Optional row limit for results (API engine).
|
|
550
|
+
wait_timeout: API wait timeout value.
|
|
551
|
+
catalog_name: Optional catalog override for API engine.
|
|
552
|
+
schema_name: Optional schema override for API engine.
|
|
553
|
+
wait: Whether to block until completion (API engine).
|
|
345
554
|
|
|
346
555
|
Returns:
|
|
347
|
-
|
|
556
|
+
StatementResult
|
|
348
557
|
"""
|
|
558
|
+
if format is None:
|
|
559
|
+
format = Format.ARROW_STREAM
|
|
560
|
+
|
|
561
|
+
if disposition is None:
|
|
562
|
+
disposition = Disposition.EXTERNAL_LINKS
|
|
563
|
+
elif format in (Format.CSV, Format.ARROW_STREAM):
|
|
564
|
+
disposition = Disposition.EXTERNAL_LINKS
|
|
565
|
+
|
|
566
|
+
instance = self.find_warehouse(warehouse_id=warehouse_id, warehouse_name=warehouse_name)
|
|
567
|
+
warehouse_id = warehouse_id or instance.warehouse_id
|
|
568
|
+
workspace_client = instance.workspace.sdk()
|
|
569
|
+
|
|
570
|
+
LOGGER.debug(
|
|
571
|
+
"API SQL executing query:\n%s",
|
|
572
|
+
statement
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
response = workspace_client.statement_execution.execute_statement(
|
|
576
|
+
statement=statement,
|
|
577
|
+
warehouse_id=warehouse_id,
|
|
578
|
+
byte_limit=byte_limit,
|
|
579
|
+
disposition=disposition,
|
|
580
|
+
format=format,
|
|
581
|
+
on_wait_timeout=on_wait_timeout,
|
|
582
|
+
parameters=parameters,
|
|
583
|
+
row_limit=row_limit,
|
|
584
|
+
wait_timeout=wait_timeout,
|
|
585
|
+
catalog=catalog_name,
|
|
586
|
+
schema=schema_name,
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
execution = StatementResult(
|
|
590
|
+
workspace_client=workspace_client,
|
|
591
|
+
warehouse_id=warehouse_id,
|
|
592
|
+
statement_id=response.statement_id,
|
|
593
|
+
disposition=disposition,
|
|
594
|
+
_response=response,
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
LOGGER.info(
|
|
598
|
+
"API SQL executed %s",
|
|
599
|
+
execution
|
|
600
|
+
)
|
|
349
601
|
|
|
350
|
-
return
|
|
351
|
-
workspace=workspace,
|
|
352
|
-
warehouse_id=warehouse_id or self.warehouse_id,
|
|
353
|
-
catalog_name=catalog_name,
|
|
354
|
-
schema_name=schema_name
|
|
355
|
-
)
|
|
602
|
+
return execution.wait() if wait is not None else execution
|