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.
Files changed (42) hide show
  1. {ygg-0.1.57.dist-info → ygg-0.1.60.dist-info}/METADATA +1 -1
  2. ygg-0.1.60.dist-info/RECORD +74 -0
  3. yggdrasil/ai/__init__.py +2 -0
  4. yggdrasil/ai/session.py +89 -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 +161 -173
  15. yggdrasil/databricks/sql/exceptions.py +9 -1
  16. yggdrasil/databricks/sql/statement_result.py +108 -120
  17. yggdrasil/databricks/sql/warehouse.py +331 -92
  18. yggdrasil/databricks/workspaces/io.py +89 -9
  19. yggdrasil/databricks/workspaces/path.py +120 -72
  20. yggdrasil/databricks/workspaces/workspace.py +214 -61
  21. yggdrasil/exceptions.py +7 -0
  22. yggdrasil/libs/databrickslib.py +23 -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/python_env.py +14 -13
  30. yggdrasil/pyutils/waiting_config.py +171 -0
  31. yggdrasil/types/cast/arrow_cast.py +3 -0
  32. yggdrasil/types/cast/pandas_cast.py +157 -169
  33. yggdrasil/types/cast/polars_cast.py +11 -43
  34. yggdrasil/types/dummy_class.py +81 -0
  35. yggdrasil/version.py +1 -1
  36. ygg-0.1.57.dist-info/RECORD +0 -66
  37. yggdrasil/databricks/ai/loki.py +0 -53
  38. {ygg-0.1.57.dist-info → ygg-0.1.60.dist-info}/WHEEL +0 -0
  39. {ygg-0.1.57.dist-info → ygg-0.1.60.dist-info}/entry_points.txt +0 -0
  40. {ygg-0.1.57.dist-info → ygg-0.1.60.dist-info}/licenses/LICENSE +0 -0
  41. {ygg-0.1.57.dist-info → ygg-0.1.60.dist-info}/top_level.txt +0 -0
  42. /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,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
- 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
+ warehouse_name = warehouse_name or self.warehouse_name or self._make_default_name(enable_serverless_compute=True)
148
260
 
149
- warehouse_id = get_cached_warehouse_id(host=self.workspace.host, warehouse_name=warehouse_name)
261
+ if warehouse_name:
262
+ if warehouse_name == self.warehouse_name:
263
+ return self
150
264
 
151
- if warehouse_id:
152
- return SQLWarehouse(
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
- 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
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 {warehouse_name!r} not found"
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.name is None:
210
- details.name = "YGG-%s" % details.cluster_size.upper()
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(warehouse_id=warehouse_id, warehouse_name=name, raise_error=False)
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
- return self.create(name=name, **warehouse_specs)
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
- info = self.warehouse_client().create_and_wait(**{
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(details=self.details, keys=_EDIT_ARG_NAMES, **warehouse_specs)
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, diff
485
+ self, update_details
320
486
  )
321
487
 
322
- self.warehouse_client().edit_and_wait(**update_details)
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 sql(
505
+ def execute(
332
506
  self,
333
- workspace: Optional[Workspace] = None,
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
- """Return a SQLEngine configured for this workspace.
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
- workspace: Optional workspace override.
342
- warehouse_id: Optional SQL warehouse id.
343
- catalog_name: Optional catalog name.
344
- schema_name: Optional schema name.
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
- A SQLEngine instance.
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 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
- )
594
+ return execution.wait() if wait is not None else execution