ygg 0.1.57__py3-none-any.whl → 0.1.60__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.60.dist-info}/METADATA +1 -1
- ygg-0.1.60.dist-info/RECORD +74 -0
- yggdrasil/ai/__init__.py +2 -0
- yggdrasil/ai/session.py +89 -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 +161 -173
- yggdrasil/databricks/sql/exceptions.py +9 -1
- yggdrasil/databricks/sql/statement_result.py +108 -120
- yggdrasil/databricks/sql/warehouse.py +331 -92
- yggdrasil/databricks/workspaces/io.py +89 -9
- yggdrasil/databricks/workspaces/path.py +120 -72
- yggdrasil/databricks/workspaces/workspace.py +214 -61
- yggdrasil/exceptions.py +7 -0
- yggdrasil/libs/databrickslib.py +23 -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/python_env.py +14 -13
- yggdrasil/pyutils/waiting_config.py +171 -0
- 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/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.60.dist-info}/WHEEL +0 -0
- {ygg-0.1.57.dist-info → ygg-0.1.60.dist-info}/entry_points.txt +0 -0
- {ygg-0.1.57.dist-info → ygg-0.1.60.dist-info}/licenses/LICENSE +0 -0
- {ygg-0.1.57.dist-info → ygg-0.1.60.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,58 @@ 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
|
-
warehouse_name = warehouse_name or self.warehouse_name
|
|
259
|
+
warehouse_name = warehouse_name or self.warehouse_name or self._make_default_name(enable_serverless_compute=True)
|
|
148
260
|
|
|
149
|
-
|
|
261
|
+
if warehouse_name:
|
|
262
|
+
if warehouse_name == self.warehouse_name:
|
|
263
|
+
return self
|
|
150
264
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
workspace=self.workspace,
|
|
154
|
-
warehouse_id=warehouse_id,
|
|
265
|
+
warehouse_id = get_cached_warehouse_id(
|
|
266
|
+
host=self.workspace.safe_host,
|
|
155
267
|
warehouse_name=warehouse_name
|
|
156
268
|
)
|
|
157
269
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
270
|
+
if warehouse_id:
|
|
271
|
+
if warehouse_id == self.warehouse_id:
|
|
272
|
+
return self
|
|
273
|
+
|
|
274
|
+
return SQLWarehouse(
|
|
275
|
+
workspace=self.workspace,
|
|
276
|
+
warehouse_id=warehouse_id,
|
|
277
|
+
warehouse_name=warehouse_name
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
for warehouse in self.list_warehouses():
|
|
281
|
+
if warehouse.warehouse_name == warehouse_name:
|
|
282
|
+
set_cached_warehouse_name(
|
|
283
|
+
host=self.workspace.safe_host,
|
|
284
|
+
warehouse_name=warehouse_name,
|
|
285
|
+
warehouse_id=warehouse.warehouse_id
|
|
286
|
+
)
|
|
287
|
+
return warehouse
|
|
162
288
|
|
|
163
289
|
if raise_error:
|
|
290
|
+
v = warehouse_name or warehouse_id
|
|
291
|
+
|
|
164
292
|
raise ValueError(
|
|
165
|
-
f"SQL Warehouse {
|
|
293
|
+
f"SQL Warehouse {v!r} not found"
|
|
166
294
|
)
|
|
295
|
+
|
|
167
296
|
return None
|
|
168
297
|
|
|
169
298
|
def list_warehouses(self):
|
|
@@ -177,9 +306,16 @@ class SQLWarehouse(WorkspaceService):
|
|
|
177
306
|
|
|
178
307
|
yield warehouse
|
|
179
308
|
|
|
309
|
+
def _make_default_name(self, enable_serverless_compute: bool = True):
|
|
310
|
+
return "%s%s" % (
|
|
311
|
+
self.workspace.product or "yggdrasil",
|
|
312
|
+
" serverless" if enable_serverless_compute else ""
|
|
313
|
+
)
|
|
314
|
+
|
|
180
315
|
def _check_details(
|
|
181
316
|
self,
|
|
182
317
|
keys: Sequence[str],
|
|
318
|
+
update: bool,
|
|
183
319
|
details: Optional[EndpointInfo] = None,
|
|
184
320
|
**warehouse_specs
|
|
185
321
|
):
|
|
@@ -206,10 +342,20 @@ class SQLWarehouse(WorkspaceService):
|
|
|
206
342
|
if details.cluster_size is None:
|
|
207
343
|
details.cluster_size = "Small"
|
|
208
344
|
|
|
209
|
-
if details.
|
|
210
|
-
details.
|
|
345
|
+
if details.warehouse_type is None:
|
|
346
|
+
details.warehouse_type = EndpointInfoWarehouseType.PRO
|
|
347
|
+
|
|
348
|
+
if details.enable_serverless_compute is None:
|
|
349
|
+
details.enable_serverless_compute = details.warehouse_type.value == EndpointInfoWarehouseType.PRO.value
|
|
350
|
+
elif details.enable_serverless_compute:
|
|
351
|
+
details.warehouse_type = EndpointInfoWarehouseType.PRO
|
|
352
|
+
|
|
353
|
+
if not details.name:
|
|
354
|
+
details.name = self._make_default_name(
|
|
355
|
+
enable_serverless_compute=details.enable_serverless_compute
|
|
356
|
+
)
|
|
211
357
|
|
|
212
|
-
default_tags = self.workspace.default_tags()
|
|
358
|
+
default_tags = self.workspace.default_tags(update=update)
|
|
213
359
|
|
|
214
360
|
if details.tags is None:
|
|
215
361
|
details.tags = EndpointTags(custom_tags=[
|
|
@@ -233,42 +379,61 @@ class SQLWarehouse(WorkspaceService):
|
|
|
233
379
|
if not details.max_num_clusters:
|
|
234
380
|
details.max_num_clusters = 4
|
|
235
381
|
|
|
236
|
-
if details.warehouse_type is None:
|
|
237
|
-
details.warehouse_type = EndpointInfoWarehouseType.CLASSIC
|
|
238
|
-
|
|
239
382
|
return details
|
|
240
383
|
|
|
241
384
|
def create_or_update(
|
|
242
385
|
self,
|
|
243
386
|
warehouse_id: Optional[str] = None,
|
|
244
387
|
name: Optional[str] = None,
|
|
388
|
+
wait: Optional[WaitingConfig] = None,
|
|
245
389
|
**warehouse_specs
|
|
246
390
|
):
|
|
247
391
|
name = name or self.warehouse_name
|
|
248
|
-
found = self.find_warehouse(
|
|
392
|
+
found = self.find_warehouse(
|
|
393
|
+
warehouse_id=warehouse_id,
|
|
394
|
+
warehouse_name=name,
|
|
395
|
+
raise_error=False,
|
|
396
|
+
)
|
|
249
397
|
|
|
250
398
|
if found is not None:
|
|
251
|
-
return found.update(name=name, **warehouse_specs)
|
|
252
|
-
|
|
399
|
+
return found.update(name=name, wait=wait, **warehouse_specs)
|
|
400
|
+
|
|
401
|
+
return self.create(name=name, wait=wait, **warehouse_specs)
|
|
253
402
|
|
|
254
403
|
def create(
|
|
255
404
|
self,
|
|
256
405
|
name: Optional[str] = None,
|
|
406
|
+
wait: Optional[WaitingConfigArg] = None,
|
|
257
407
|
**warehouse_specs
|
|
258
408
|
):
|
|
259
409
|
name = name or self.warehouse_name
|
|
260
410
|
|
|
411
|
+
checked_wait = WaitingConfig.check_arg(wait)
|
|
412
|
+
|
|
261
413
|
details = self._check_details(
|
|
262
414
|
keys=_CREATE_ARG_NAMES,
|
|
415
|
+
update=False,
|
|
263
416
|
name=name,
|
|
264
417
|
**warehouse_specs
|
|
265
418
|
)
|
|
266
419
|
|
|
267
|
-
|
|
420
|
+
update_details = {
|
|
268
421
|
k: v
|
|
269
422
|
for k, v in details.as_shallow_dict().items()
|
|
270
423
|
if k in _CREATE_ARG_NAMES
|
|
271
|
-
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if checked_wait.timeout:
|
|
427
|
+
info = self.warehouse_client().create_and_wait(
|
|
428
|
+
timeout=checked_wait.timeout_timedelta,
|
|
429
|
+
**update_details
|
|
430
|
+
)
|
|
431
|
+
else:
|
|
432
|
+
info = self.warehouse_client().create(**update_details)
|
|
433
|
+
|
|
434
|
+
update_details["id"] = info.response.id
|
|
435
|
+
|
|
436
|
+
info = EndpointInfo(**update_details)
|
|
272
437
|
|
|
273
438
|
return SQLWarehouse(
|
|
274
439
|
workspace=self.workspace,
|
|
@@ -279,11 +444,14 @@ class SQLWarehouse(WorkspaceService):
|
|
|
279
444
|
|
|
280
445
|
def update(
|
|
281
446
|
self,
|
|
447
|
+
wait: Optional[WaitingConfigArg] = None,
|
|
282
448
|
**warehouse_specs
|
|
283
449
|
):
|
|
284
450
|
if not warehouse_specs:
|
|
285
451
|
return self
|
|
286
452
|
|
|
453
|
+
wait = WaitingConfig.check_arg(wait)
|
|
454
|
+
|
|
287
455
|
existing_details = {
|
|
288
456
|
k: v
|
|
289
457
|
for k, v in self.details.as_shallow_dict().items()
|
|
@@ -293,7 +461,12 @@ class SQLWarehouse(WorkspaceService):
|
|
|
293
461
|
update_details = {
|
|
294
462
|
k: v
|
|
295
463
|
for k, v in (
|
|
296
|
-
self._check_details(
|
|
464
|
+
self._check_details(
|
|
465
|
+
details=self.details,
|
|
466
|
+
update=True,
|
|
467
|
+
keys=_EDIT_ARG_NAMES,
|
|
468
|
+
**warehouse_specs
|
|
469
|
+
)
|
|
297
470
|
.as_shallow_dict()
|
|
298
471
|
.items()
|
|
299
472
|
)
|
|
@@ -304,22 +477,23 @@ class SQLWarehouse(WorkspaceService):
|
|
|
304
477
|
existing_details,
|
|
305
478
|
update_details,
|
|
306
479
|
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
480
|
)
|
|
310
481
|
|
|
311
482
|
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
483
|
LOGGER.debug(
|
|
318
484
|
"Updating %s with %s",
|
|
319
|
-
self,
|
|
485
|
+
self, update_details
|
|
320
486
|
)
|
|
321
487
|
|
|
322
|
-
|
|
488
|
+
if wait.timeout:
|
|
489
|
+
self.details = self.warehouse_client().edit_and_wait(
|
|
490
|
+
timeout=wait.timeout_timedelta,
|
|
491
|
+
**update_details
|
|
492
|
+
)
|
|
493
|
+
else:
|
|
494
|
+
_ = self.warehouse_client().edit(**update_details)
|
|
495
|
+
|
|
496
|
+
self.details = EndpointInfo(**update_details)
|
|
323
497
|
|
|
324
498
|
LOGGER.info(
|
|
325
499
|
"Updated %s",
|
|
@@ -328,28 +502,93 @@ class SQLWarehouse(WorkspaceService):
|
|
|
328
502
|
|
|
329
503
|
return self
|
|
330
504
|
|
|
331
|
-
def
|
|
505
|
+
def execute(
|
|
332
506
|
self,
|
|
333
|
-
|
|
507
|
+
statement: Optional[str] = None,
|
|
508
|
+
*,
|
|
334
509
|
warehouse_id: Optional[str] = None,
|
|
510
|
+
warehouse_name: Optional[str] = None,
|
|
511
|
+
byte_limit: Optional[int] = None,
|
|
512
|
+
disposition: Optional[Disposition] = None,
|
|
513
|
+
format: Optional[Format] = None,
|
|
514
|
+
on_wait_timeout: Optional[ExecuteStatementRequestOnWaitTimeout] = None,
|
|
515
|
+
parameters: Optional[List[StatementParameterListItem]] = None,
|
|
516
|
+
row_limit: Optional[int] = None,
|
|
517
|
+
wait_timeout: Optional[str] = None,
|
|
335
518
|
catalog_name: Optional[str] = None,
|
|
336
519
|
schema_name: Optional[str] = None,
|
|
337
|
-
|
|
338
|
-
|
|
520
|
+
wait: Optional[WaitingConfigArg] = True
|
|
521
|
+
) -> StatementResult:
|
|
522
|
+
"""Execute a SQL statement via Spark or Databricks SQL Statement Execution API.
|
|
523
|
+
|
|
524
|
+
Engine resolution:
|
|
525
|
+
- If `engine` is not provided and a Spark session is active -> uses Spark.
|
|
526
|
+
- Otherwise uses Databricks SQL API (warehouse).
|
|
527
|
+
|
|
528
|
+
Waiting behavior (`wait_result`):
|
|
529
|
+
- If True (default): returns a StatementResult in terminal state (SUCCEEDED/FAILED/CANCELED).
|
|
530
|
+
- If False: returns immediately with the initial handle (caller can `.wait()` later).
|
|
339
531
|
|
|
340
532
|
Args:
|
|
341
|
-
|
|
342
|
-
warehouse_id:
|
|
343
|
-
|
|
344
|
-
|
|
533
|
+
statement: SQL statement to execute. If None, a `SELECT *` is generated from the table params.
|
|
534
|
+
warehouse_id: Warehouse override (for API engine).
|
|
535
|
+
warehouse_name: Warehouse name override (for API engine).
|
|
536
|
+
byte_limit: Optional byte limit for results.
|
|
537
|
+
disposition: Result disposition mode (API engine).
|
|
538
|
+
format: Result format (API engine).
|
|
539
|
+
on_wait_timeout: Timeout behavior for waiting (API engine).
|
|
540
|
+
parameters: Optional statement parameters (API engine).
|
|
541
|
+
row_limit: Optional row limit for results (API engine).
|
|
542
|
+
wait_timeout: API wait timeout value.
|
|
543
|
+
catalog_name: Optional catalog override for API engine.
|
|
544
|
+
schema_name: Optional schema override for API engine.
|
|
545
|
+
wait: Whether to block until completion (API engine).
|
|
345
546
|
|
|
346
547
|
Returns:
|
|
347
|
-
|
|
548
|
+
StatementResult
|
|
348
549
|
"""
|
|
550
|
+
if format is None:
|
|
551
|
+
format = Format.ARROW_STREAM
|
|
552
|
+
|
|
553
|
+
if disposition is None:
|
|
554
|
+
disposition = Disposition.EXTERNAL_LINKS
|
|
555
|
+
elif format in (Format.CSV, Format.ARROW_STREAM):
|
|
556
|
+
disposition = Disposition.EXTERNAL_LINKS
|
|
557
|
+
|
|
558
|
+
instance = self.find_warehouse(warehouse_id=warehouse_id, warehouse_name=warehouse_name)
|
|
559
|
+
warehouse_id = warehouse_id or instance.warehouse_id
|
|
560
|
+
workspace_client = instance.workspace.sdk()
|
|
561
|
+
|
|
562
|
+
LOGGER.debug(
|
|
563
|
+
"API SQL executing query:\n%s",
|
|
564
|
+
statement
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
response = workspace_client.statement_execution.execute_statement(
|
|
568
|
+
statement=statement,
|
|
569
|
+
warehouse_id=warehouse_id,
|
|
570
|
+
byte_limit=byte_limit,
|
|
571
|
+
disposition=disposition,
|
|
572
|
+
format=format,
|
|
573
|
+
on_wait_timeout=on_wait_timeout,
|
|
574
|
+
parameters=parameters,
|
|
575
|
+
row_limit=row_limit,
|
|
576
|
+
wait_timeout=wait_timeout,
|
|
577
|
+
catalog=catalog_name,
|
|
578
|
+
schema=schema_name,
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
execution = StatementResult(
|
|
582
|
+
workspace_client=workspace_client,
|
|
583
|
+
warehouse_id=warehouse_id,
|
|
584
|
+
statement_id=response.statement_id,
|
|
585
|
+
disposition=disposition,
|
|
586
|
+
_response=response,
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
LOGGER.info(
|
|
590
|
+
"API SQL executed %s",
|
|
591
|
+
execution
|
|
592
|
+
)
|
|
349
593
|
|
|
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
|
-
)
|
|
594
|
+
return execution.wait() if wait is not None else execution
|