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.
Files changed (46) hide show
  1. {ygg-0.1.57.dist-info → ygg-0.1.64.dist-info}/METADATA +2 -2
  2. ygg-0.1.64.dist-info/RECORD +74 -0
  3. yggdrasil/ai/__init__.py +2 -0
  4. yggdrasil/ai/session.py +87 -0
  5. yggdrasil/ai/sql_session.py +310 -0
  6. yggdrasil/databricks/__init__.py +0 -3
  7. yggdrasil/databricks/compute/cluster.py +68 -113
  8. yggdrasil/databricks/compute/command_execution.py +674 -0
  9. yggdrasil/databricks/compute/exceptions.py +19 -0
  10. yggdrasil/databricks/compute/execution_context.py +491 -282
  11. yggdrasil/databricks/compute/remote.py +4 -14
  12. yggdrasil/databricks/exceptions.py +10 -0
  13. yggdrasil/databricks/sql/__init__.py +0 -4
  14. yggdrasil/databricks/sql/engine.py +178 -178
  15. yggdrasil/databricks/sql/exceptions.py +9 -1
  16. yggdrasil/databricks/sql/statement_result.py +108 -120
  17. yggdrasil/databricks/sql/warehouse.py +339 -92
  18. yggdrasil/databricks/workspaces/io.py +185 -40
  19. yggdrasil/databricks/workspaces/path.py +114 -100
  20. yggdrasil/databricks/workspaces/workspace.py +210 -61
  21. yggdrasil/exceptions.py +7 -0
  22. yggdrasil/libs/databrickslib.py +22 -18
  23. yggdrasil/libs/extensions/spark_extensions.py +1 -1
  24. yggdrasil/libs/pandaslib.py +15 -6
  25. yggdrasil/libs/polarslib.py +49 -13
  26. yggdrasil/pyutils/__init__.py +1 -2
  27. yggdrasil/pyutils/callable_serde.py +12 -19
  28. yggdrasil/pyutils/exceptions.py +16 -0
  29. yggdrasil/pyutils/modules.py +6 -7
  30. yggdrasil/pyutils/python_env.py +16 -21
  31. yggdrasil/pyutils/waiting_config.py +171 -0
  32. yggdrasil/requests/msal.py +9 -96
  33. yggdrasil/types/cast/arrow_cast.py +3 -0
  34. yggdrasil/types/cast/pandas_cast.py +157 -169
  35. yggdrasil/types/cast/polars_cast.py +11 -43
  36. yggdrasil/types/dummy_class.py +81 -0
  37. yggdrasil/types/file_format.py +6 -2
  38. yggdrasil/types/python_defaults.py +92 -76
  39. yggdrasil/version.py +1 -1
  40. ygg-0.1.57.dist-info/RECORD +0 -66
  41. yggdrasil/databricks/ai/loki.py +0 -53
  42. {ygg-0.1.57.dist-info → ygg-0.1.64.dist-info}/WHEEL +0 -0
  43. {ygg-0.1.57.dist-info → ygg-0.1.64.dist-info}/entry_points.txt +0 -0
  44. {ygg-0.1.57.dist-info → ygg-0.1.64.dist-info}/licenses/LICENSE +0 -0
  45. {ygg-0.1.57.dist-info → ygg-0.1.64.dist-info}/top_level.txt +0 -0
  46. /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
- from typing import Optional, Sequence
4
+ import time
5
+ from typing import Optional, Sequence, Any, Type, TypeVar, Union, List
5
6
 
6
- from ..workspaces import WorkspaceService, Workspace
7
- from ...pyutils.equality import dicts_equal, dict_diff
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, EndpointTags, EndpointTagPair, EndpointInfoWarehouseType
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
- class WarehousesAPI:
20
- pass
21
-
22
- class State:
23
- pass
24
-
25
- class EndpointInfo:
26
- pass
27
-
28
- class EndpointTags:
29
- pass
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.warehouse_id = value.id
107
- self.warehouse_name = value.name
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 running(self):
115
- return self.state in {State.RUNNING}
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 pending(self):
119
- return self.state in {State.DELETING, State.STARTING, State.STOPPING}
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.running:
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.running:
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
- if self.warehouse_id:
256
+ elif self.warehouse_id:
145
257
  return self
146
258
 
147
- warehouse_name = warehouse_name or self.warehouse_name
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
- warehouse_id = get_cached_warehouse_id(host=self.workspace.host, warehouse_name=warehouse_name)
262
+ if warehouse_name:
263
+ if warehouse_name == self.warehouse_name:
264
+ return self
150
265
 
151
- if warehouse_id:
152
- return SQLWarehouse(
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
- for warehouse in self.list_warehouses():
159
- if warehouse.warehouse_name == warehouse_name:
160
- set_cached_warehouse_name(host=self.workspace.host, warehouse_name=warehouse_name, warehouse_id=warehouse.warehouse_id)
161
- return warehouse
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 {warehouse_name!r} not found"
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.name is None:
210
- details.name = "YGG-%s" % details.cluster_size.upper()
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
- default_tags = self.workspace.default_tags()
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(warehouse_id=warehouse_id, warehouse_name=name, raise_error=False)
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
- return self.create(name=name, **warehouse_specs)
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
- info = self.warehouse_client().create_and_wait(**{
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(details=self.details, keys=_EDIT_ARG_NAMES, **warehouse_specs)
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, diff
493
+ self, update_details
320
494
  )
321
495
 
322
- self.warehouse_client().edit_and_wait(**update_details)
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 sql(
513
+ def execute(
332
514
  self,
333
- workspace: Optional[Workspace] = None,
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
- """Return a SQLEngine configured for this workspace.
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
- workspace: Optional workspace override.
342
- warehouse_id: Optional SQL warehouse id.
343
- catalog_name: Optional catalog name.
344
- schema_name: Optional schema name.
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
- A SQLEngine instance.
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 self.workspace.sql(
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