relationalai 0.13.1__py3-none-any.whl → 0.13.2__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.
- relationalai/clients/client.py +49 -14
- relationalai/clients/local.py +23 -8
- relationalai/clients/resources/azure/azure.py +36 -11
- relationalai/clients/resources/snowflake/__init__.py +3 -3
- relationalai/clients/resources/snowflake/cli_resources.py +12 -1
- relationalai/clients/resources/snowflake/direct_access_resources.py +57 -17
- relationalai/clients/resources/snowflake/engine_service.py +381 -0
- relationalai/clients/resources/snowflake/engine_state_handlers.py +35 -29
- relationalai/clients/resources/snowflake/error_handlers.py +43 -2
- relationalai/clients/resources/snowflake/snowflake.py +116 -121
- relationalai/clients/types.py +5 -0
- relationalai/errors.py +1 -1
- relationalai/semantics/metamodel/typer/typer.py +6 -2
- relationalai/tools/cli.py +339 -186
- relationalai/tools/cli_helpers.py +410 -6
- {relationalai-0.13.1.dist-info → relationalai-0.13.2.dist-info}/METADATA +1 -1
- {relationalai-0.13.1.dist-info → relationalai-0.13.2.dist-info}/RECORD +21 -20
- relationalai_test_util/fixtures.py +2 -2
- {relationalai-0.13.1.dist-info → relationalai-0.13.2.dist-info}/WHEEL +0 -0
- {relationalai-0.13.1.dist-info → relationalai-0.13.2.dist-info}/entry_points.txt +0 -0
- {relationalai-0.13.1.dist-info → relationalai-0.13.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#pyright: reportPrivateImportUsage=false
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
import io
|
|
4
|
+
import json
|
|
4
5
|
import os
|
|
5
6
|
import re
|
|
6
7
|
import sys
|
|
@@ -9,10 +10,12 @@ import rich
|
|
|
9
10
|
import click
|
|
10
11
|
import functools
|
|
11
12
|
import pytz
|
|
13
|
+
from typing import NoReturn
|
|
12
14
|
|
|
13
15
|
from relationalai.util.constants import TOP_LEVEL_PROFILE_NAME
|
|
16
|
+
from relationalai.errors import RAIException
|
|
14
17
|
from rich.table import Table
|
|
15
|
-
from typing import Callable, Dict, Any
|
|
18
|
+
from typing import Callable, Dict, Any, List, cast
|
|
16
19
|
from ..clients import config
|
|
17
20
|
from click.core import Context
|
|
18
21
|
from rich.console import Console
|
|
@@ -23,9 +26,10 @@ from ..clients.config import ConfigFile
|
|
|
23
26
|
from datetime import datetime, timedelta
|
|
24
27
|
from click.formatting import HelpFormatter
|
|
25
28
|
from ..clients.client import ResourcesBase
|
|
26
|
-
from relationalai.tools.constants import GlobalProfile
|
|
29
|
+
from relationalai.tools.constants import GlobalProfile, SHOW_FULL_TRACES
|
|
27
30
|
from relationalai.tools.cli_controls import divider
|
|
28
31
|
from relationalai.util.format import humanized_bytes, humanized_duration
|
|
32
|
+
from InquirerPy.base.control import Choice
|
|
29
33
|
|
|
30
34
|
#--------------------------------------------------
|
|
31
35
|
# Helpers
|
|
@@ -152,6 +156,291 @@ def validate_engine_name(name:str) -> tuple[bool, str|None]:
|
|
|
152
156
|
return False, ENGINE_NAME_ERROR
|
|
153
157
|
return True, None
|
|
154
158
|
|
|
159
|
+
#--------------------------------------------------
|
|
160
|
+
# Engine types & selection helpers (Snowflake)
|
|
161
|
+
#--------------------------------------------------
|
|
162
|
+
|
|
163
|
+
def format_state_with_color(state: str) -> str:
|
|
164
|
+
"""Format engine state with colors for display."""
|
|
165
|
+
if not state:
|
|
166
|
+
return ""
|
|
167
|
+
state_upper = state.upper()
|
|
168
|
+
if state_upper == "READY":
|
|
169
|
+
return f"[green]{state_upper}[/green]"
|
|
170
|
+
if state_upper == "SUSPENDED":
|
|
171
|
+
return f"[yellow]{state_upper}[/yellow]"
|
|
172
|
+
if state_upper in ("PENDING", "SYNCING", "PROCESSING"):
|
|
173
|
+
return f"[bold yellow]{state_upper}[/bold yellow]"
|
|
174
|
+
if state_upper in ("ABORTED", "QUARANTINED", "GONE"):
|
|
175
|
+
return f"[red]{state_upper}[/red]"
|
|
176
|
+
return state_upper
|
|
177
|
+
|
|
178
|
+
def _get_engine_type_api():
|
|
179
|
+
# Local import to avoid importing snowflake modules for non-snowflake usage
|
|
180
|
+
from relationalai.clients.resources.snowflake import EngineType
|
|
181
|
+
return EngineType
|
|
182
|
+
|
|
183
|
+
def _get_internal_engine_sizes() -> list[str]:
|
|
184
|
+
# Local import to avoid snowflake imports for non-snowflake usage
|
|
185
|
+
from relationalai.clients.resources.snowflake import INTERNAL_ENGINE_SIZES
|
|
186
|
+
return list(INTERNAL_ENGINE_SIZES)
|
|
187
|
+
|
|
188
|
+
def get_engine_type_choices(cfg: config.Config, exclude_types: List[str] | None = None) -> List[Choice]:
|
|
189
|
+
"""Get sorted list of engine type choices for interactive selection."""
|
|
190
|
+
EngineType = _get_engine_type_api()
|
|
191
|
+
if exclude_types is None:
|
|
192
|
+
exclude_types = []
|
|
193
|
+
exclude_types_upper = [et.upper() for et in exclude_types]
|
|
194
|
+
engine_types_list = [EngineType.LOGIC, EngineType.ML, EngineType.SOLVER]
|
|
195
|
+
engine_types_list = [et for et in engine_types_list if et.upper() not in exclude_types_upper]
|
|
196
|
+
engine_types_list.sort(key=lambda et: EngineType.get_label(et))
|
|
197
|
+
return [
|
|
198
|
+
Choice(value=et, name=f"{EngineType.get_label(et)}: {EngineType.get_description(et)}")
|
|
199
|
+
for et in engine_types_list
|
|
200
|
+
]
|
|
201
|
+
|
|
202
|
+
def select_engine_type_interactive(cfg: config.Config) -> str:
|
|
203
|
+
"""Show interactive engine type selection and return the selected type."""
|
|
204
|
+
from . import cli_controls as controls
|
|
205
|
+
rich.print("")
|
|
206
|
+
engine_type_choices = get_engine_type_choices(cfg)
|
|
207
|
+
return controls.select("Engine type:", cast("list[str | Choice]", engine_type_choices), None, newline=True)
|
|
208
|
+
|
|
209
|
+
def select_engine_interactive(
|
|
210
|
+
provider: ResourcesBase,
|
|
211
|
+
prompt: str = "Select an engine:",
|
|
212
|
+
engine_name: str | None = None,
|
|
213
|
+
engines: List[Dict[str, Any]] | None = None,
|
|
214
|
+
) -> tuple[str, str | None] | None:
|
|
215
|
+
"""Interactive engine picker returning (name, type)."""
|
|
216
|
+
from . import cli_controls as controls
|
|
217
|
+
|
|
218
|
+
engine_map: Dict[str, tuple[str, str | None]] = {}
|
|
219
|
+
|
|
220
|
+
def get_engines():
|
|
221
|
+
engine_list = engines if engines is not None else provider.list_engines()
|
|
222
|
+
engine_map.clear()
|
|
223
|
+
items: List[str] = []
|
|
224
|
+
EngineType = _get_engine_type_api()
|
|
225
|
+
for engine in engine_list:
|
|
226
|
+
eng_name = engine.get("name", "")
|
|
227
|
+
if engine_name and eng_name.upper() != engine_name.upper():
|
|
228
|
+
continue
|
|
229
|
+
eng_type = engine.get("type", "")
|
|
230
|
+
eng_size = engine.get("size", "")
|
|
231
|
+
if eng_type:
|
|
232
|
+
label = f"{EngineType.get_label(eng_type)} ({eng_type})" if EngineType.is_valid(eng_type) else f"{eng_type} ({eng_type})"
|
|
233
|
+
display = f"{eng_name}, {label}, {eng_size}"
|
|
234
|
+
else:
|
|
235
|
+
display = f"{eng_name}, {eng_size}" if eng_size else eng_name
|
|
236
|
+
engine_map[display] = (eng_name, eng_type or None)
|
|
237
|
+
items.append(display)
|
|
238
|
+
return items
|
|
239
|
+
|
|
240
|
+
# Auto-select when engine_name uniquely identifies an engine
|
|
241
|
+
if engine_name:
|
|
242
|
+
engine_list = engines if engines is not None else provider.list_engines()
|
|
243
|
+
matches = [e for e in engine_list if e.get("name", "").upper() == engine_name.upper()]
|
|
244
|
+
if len(matches) == 1:
|
|
245
|
+
e = matches[0]
|
|
246
|
+
return (e.get("name", ""), e.get("type"))
|
|
247
|
+
if len(matches) == 0:
|
|
248
|
+
return None
|
|
249
|
+
|
|
250
|
+
selected = controls.fuzzy_with_refetch(prompt, "engines", get_engines)
|
|
251
|
+
if not selected or isinstance(selected, Exception):
|
|
252
|
+
return None
|
|
253
|
+
return engine_map.get(selected)
|
|
254
|
+
|
|
255
|
+
def select_engine_with_state_filter(
|
|
256
|
+
provider: ResourcesBase,
|
|
257
|
+
engine_name: str | None,
|
|
258
|
+
engine_type: str | None,
|
|
259
|
+
state_filter: str,
|
|
260
|
+
prompt_no_name: str,
|
|
261
|
+
prompt_with_name: str,
|
|
262
|
+
error_no_engines: str,
|
|
263
|
+
error_no_matching: str,
|
|
264
|
+
) -> tuple[str, str | None] | None:
|
|
265
|
+
"""Select an engine with optional state filtering + optional name filtering."""
|
|
266
|
+
EngineType = _get_engine_type_api()
|
|
267
|
+
|
|
268
|
+
if not engine_name:
|
|
269
|
+
filtered = provider.list_engines(state_filter)
|
|
270
|
+
if not filtered:
|
|
271
|
+
exit_with_error(error_no_engines)
|
|
272
|
+
return select_engine_interactive(provider, prompt_no_name, engines=filtered)
|
|
273
|
+
|
|
274
|
+
# If type provided and valid, return directly
|
|
275
|
+
if engine_type and EngineType.is_valid(engine_type):
|
|
276
|
+
return (engine_name, engine_type)
|
|
277
|
+
|
|
278
|
+
# Filter by name + state; selection handles (name,type)
|
|
279
|
+
filtered = provider.list_engines(state_filter, name=engine_name)
|
|
280
|
+
if not filtered:
|
|
281
|
+
exit_with_error(error_no_matching)
|
|
282
|
+
return select_engine_interactive(provider, prompt_with_name, engine_name=engine_name, engines=filtered)
|
|
283
|
+
|
|
284
|
+
def ensure_engine_type_for_snowflake(
|
|
285
|
+
provider: ResourcesBase,
|
|
286
|
+
engine_name: str,
|
|
287
|
+
engine_type: str | None,
|
|
288
|
+
error_message: str,
|
|
289
|
+
) -> str:
|
|
290
|
+
"""Ensure engine_type is provided and valid; default to LOGIC if omitted."""
|
|
291
|
+
EngineType = _get_engine_type_api()
|
|
292
|
+
# If --type was omitted, default to LOGIC for backwards compatibility
|
|
293
|
+
if engine_type is None:
|
|
294
|
+
return EngineType.LOGIC
|
|
295
|
+
assert isinstance(engine_type, str)
|
|
296
|
+
if engine_type == "" or not EngineType.is_valid(engine_type):
|
|
297
|
+
cfg = get_config()
|
|
298
|
+
if engine_type == "":
|
|
299
|
+
rich.print(f"[yellow]Empty engine type provided for engine '{engine_name}'.")
|
|
300
|
+
else:
|
|
301
|
+
rich.print(f"[yellow]Invalid engine type '{engine_type}' for engine '{engine_name}'.")
|
|
302
|
+
return select_engine_type_interactive(cfg)
|
|
303
|
+
return engine_type
|
|
304
|
+
|
|
305
|
+
def build_engine_operation_messages(
|
|
306
|
+
provider: ResourcesBase,
|
|
307
|
+
engine_name: str,
|
|
308
|
+
engine_type: str | None,
|
|
309
|
+
action: str,
|
|
310
|
+
action_past: str,
|
|
311
|
+
) -> tuple[str, str]:
|
|
312
|
+
EngineType = _get_engine_type_api()
|
|
313
|
+
if engine_type:
|
|
314
|
+
label = EngineType.get_label(engine_type) if EngineType.is_valid(engine_type) else engine_type
|
|
315
|
+
return (f"{action} {label} engine '{engine_name}'", f"{label} Engine '{engine_name}' {action_past.lower()}")
|
|
316
|
+
return (f"{action} '{engine_name}' engine", f"Engine '{engine_name}' {action_past.lower()}")
|
|
317
|
+
|
|
318
|
+
def prompt_and_validate_engine_name(name: str | None) -> str:
|
|
319
|
+
"""Prompt for engine name if missing; validate using ENGINE_NAME_REGEX."""
|
|
320
|
+
from . import cli_controls as controls
|
|
321
|
+
if not name:
|
|
322
|
+
name = controls.prompt(
|
|
323
|
+
"Engine name:",
|
|
324
|
+
name,
|
|
325
|
+
validator=ENGINE_NAME_REGEX.match,
|
|
326
|
+
invalid_message=ENGINE_NAME_ERROR,
|
|
327
|
+
newline=True,
|
|
328
|
+
)
|
|
329
|
+
assert isinstance(name, str)
|
|
330
|
+
return name
|
|
331
|
+
|
|
332
|
+
def validate_auto_suspend_mins(auto_suspend_mins: int | str | None) -> int | None:
|
|
333
|
+
if auto_suspend_mins is None:
|
|
334
|
+
return None
|
|
335
|
+
if isinstance(auto_suspend_mins, int):
|
|
336
|
+
return auto_suspend_mins
|
|
337
|
+
error_msg = f"[yellow]Error: auto_suspend_mins must be an integer instead of {type(auto_suspend_mins)}"
|
|
338
|
+
try:
|
|
339
|
+
return int(auto_suspend_mins)
|
|
340
|
+
except ValueError:
|
|
341
|
+
exit_with_error(error_msg)
|
|
342
|
+
return None
|
|
343
|
+
|
|
344
|
+
def get_engine_type_for_creation(provider: ResourcesBase, cfg: config.Config, engine_type: str | None) -> str | None:
|
|
345
|
+
"""Get engine type for engine creation; defaults to LOGIC when omitted."""
|
|
346
|
+
EngineType = _get_engine_type_api()
|
|
347
|
+
if engine_type is None:
|
|
348
|
+
return EngineType.LOGIC
|
|
349
|
+
if engine_type == "" or not EngineType.is_valid(engine_type):
|
|
350
|
+
if engine_type == "":
|
|
351
|
+
rich.print("[yellow]Empty engine type provided.")
|
|
352
|
+
else:
|
|
353
|
+
valid_types_display = ", ".join(EngineType.get_all_types())
|
|
354
|
+
rich.print(f"[yellow]Invalid engine type '{engine_type}'. Valid types: {valid_types_display}")
|
|
355
|
+
return select_engine_type_interactive(cfg)
|
|
356
|
+
return engine_type
|
|
357
|
+
|
|
358
|
+
def get_and_validate_engine_size(
|
|
359
|
+
provider: ResourcesBase,
|
|
360
|
+
cfg: config.Config,
|
|
361
|
+
size: str | None,
|
|
362
|
+
engine_type: str | None = None,
|
|
363
|
+
) -> str:
|
|
364
|
+
from . import cli_controls as controls
|
|
365
|
+
EngineType = _get_engine_type_api()
|
|
366
|
+
internal_sizes = set(_get_internal_engine_sizes())
|
|
367
|
+
|
|
368
|
+
cloud_provider = provider.get_cloud_provider()
|
|
369
|
+
valid_sizes = provider.get_engine_sizes(cloud_provider)
|
|
370
|
+
|
|
371
|
+
# Engine-type-aware filtering:
|
|
372
|
+
# Internal sizes (XS/S/M/L) are only valid for LOGIC engines (and only on some accounts).
|
|
373
|
+
# For ML/SOLVER, hide them to avoid presenting invalid options.
|
|
374
|
+
if engine_type and EngineType.is_valid(engine_type) and engine_type != EngineType.LOGIC:
|
|
375
|
+
valid_sizes = [s for s in valid_sizes if s not in internal_sizes]
|
|
376
|
+
|
|
377
|
+
# Ask if missing and not in config
|
|
378
|
+
if not size and not cfg.get("engine_size", None):
|
|
379
|
+
rich.print("")
|
|
380
|
+
# This refers to the cloud backing your Snowflake account (AWS/Azure), not the engine "platform".
|
|
381
|
+
size = controls.fuzzy(f"Engine size (Snowflake cloud: {cloud_provider.upper()}):", choices=valid_sizes)
|
|
382
|
+
elif size is None and cfg.get("engine_size", None):
|
|
383
|
+
size = cfg.get("engine_size", None)
|
|
384
|
+
if not isinstance(size, str) or size not in valid_sizes:
|
|
385
|
+
exit_with_error(f"\nInvalid engine size [yellow]{size}[/yellow] provided. Please check your config.\n\nValid sizes: [green]{valid_sizes}[/green]")
|
|
386
|
+
return size
|
|
387
|
+
|
|
388
|
+
def create_engine_with_spinner(
|
|
389
|
+
provider: ResourcesBase,
|
|
390
|
+
engine_name: str,
|
|
391
|
+
engine_size: str,
|
|
392
|
+
engine_type: str | None,
|
|
393
|
+
auto_suspend_mins: int | None,
|
|
394
|
+
) -> None:
|
|
395
|
+
"""Create an engine with appropriate spinner messages and error handling."""
|
|
396
|
+
from .cli_controls import Spinner
|
|
397
|
+
|
|
398
|
+
EngineType = _get_engine_type_api()
|
|
399
|
+
# Build creation message with engine type when available
|
|
400
|
+
if engine_type:
|
|
401
|
+
creation_message = (
|
|
402
|
+
f"Creating {EngineType.get_label(engine_type)} engine '{engine_name}' with size {engine_size}... "
|
|
403
|
+
f"(this may take several minutes)"
|
|
404
|
+
)
|
|
405
|
+
else:
|
|
406
|
+
creation_message = (
|
|
407
|
+
f"Creating engine '{engine_name}' with size {engine_size}... "
|
|
408
|
+
f"(this may take several minutes)"
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
with Spinner(
|
|
412
|
+
creation_message,
|
|
413
|
+
f"Engine '{engine_name}' created!",
|
|
414
|
+
failed_message=None, # We handle error display ourselves below
|
|
415
|
+
):
|
|
416
|
+
try:
|
|
417
|
+
provider.create_engine(
|
|
418
|
+
engine_name,
|
|
419
|
+
type=engine_type,
|
|
420
|
+
size=engine_size,
|
|
421
|
+
auto_suspend_mins=auto_suspend_mins,
|
|
422
|
+
)
|
|
423
|
+
except Exception as e:
|
|
424
|
+
# Prefer richer error messages when available
|
|
425
|
+
error_msg = None
|
|
426
|
+
|
|
427
|
+
# EngineProvisioningFailed has a format_message() method that provides better error details
|
|
428
|
+
if hasattr(e, "format_message"):
|
|
429
|
+
try:
|
|
430
|
+
error_msg = getattr(e, "format_message")()
|
|
431
|
+
except Exception:
|
|
432
|
+
pass
|
|
433
|
+
|
|
434
|
+
# Try content/message fallbacks
|
|
435
|
+
if not error_msg and hasattr(e, "content"):
|
|
436
|
+
error_msg = getattr(e, "content", None)
|
|
437
|
+
if not error_msg and hasattr(e, "message"):
|
|
438
|
+
error_msg = str(getattr(e, "message", ""))
|
|
439
|
+
if not error_msg:
|
|
440
|
+
error_msg = str(e)
|
|
441
|
+
|
|
442
|
+
raise Exception(error_msg)
|
|
443
|
+
|
|
155
444
|
#--------------------------------------------------
|
|
156
445
|
# Tables
|
|
157
446
|
#--------------------------------------------------
|
|
@@ -190,7 +479,7 @@ def format_value(value) -> str:
|
|
|
190
479
|
def format_row(key: str, value) -> dict:
|
|
191
480
|
result = {}
|
|
192
481
|
result[key] = value
|
|
193
|
-
if "status" or "state" in key.lower():
|
|
482
|
+
if "status" in key.lower() or "state" in key.lower():
|
|
194
483
|
result["style"] = get_color_by_state(value)
|
|
195
484
|
if key == "query_size" and isinstance(value, int):
|
|
196
485
|
result[key] = humanized_bytes(value)
|
|
@@ -216,6 +505,28 @@ def show_dictionary_table(dict, format_fn:Callable|None=None):
|
|
|
216
505
|
|
|
217
506
|
|
|
218
507
|
class RichGroup(click.Group):
|
|
508
|
+
def invoke(self, ctx: Context) -> Any:
|
|
509
|
+
"""Invoke the CLI command, suppressing tracebacks for handled RAIExceptions.
|
|
510
|
+
|
|
511
|
+
Any `RAIException` is expected to already know how to render itself nicely via
|
|
512
|
+
`pprint()`. When such an exception bubbles up to the top-level Click runner,
|
|
513
|
+
Click will otherwise print a full Python traceback, which is noisy for users.
|
|
514
|
+
"""
|
|
515
|
+
try:
|
|
516
|
+
return super().invoke(ctx)
|
|
517
|
+
except RAIException as exc:
|
|
518
|
+
# Respect config-based full-trace setting when available.
|
|
519
|
+
try:
|
|
520
|
+
show_full_traces = get_config().get("show_full_traces", SHOW_FULL_TRACES)
|
|
521
|
+
except Exception:
|
|
522
|
+
show_full_traces = SHOW_FULL_TRACES
|
|
523
|
+
|
|
524
|
+
if show_full_traces:
|
|
525
|
+
raise
|
|
526
|
+
|
|
527
|
+
exc.pprint()
|
|
528
|
+
raise click.exceptions.Exit(1) from None
|
|
529
|
+
|
|
219
530
|
def format_help(self, ctx: Context, formatter: HelpFormatter) -> None:
|
|
220
531
|
is_latest, current_ver, latest_ver = is_latest_cli_version()
|
|
221
532
|
|
|
@@ -383,16 +694,109 @@ def show_engines(engines):
|
|
|
383
694
|
table = Table(show_header=True, border_style="dim", header_style="bold", box=rich_box.SIMPLE_HEAD)
|
|
384
695
|
table.add_column("#")
|
|
385
696
|
table.add_column("Name")
|
|
697
|
+
# Show type column if present
|
|
698
|
+
table.add_column("Type")
|
|
386
699
|
table.add_column("Size")
|
|
387
700
|
table.add_column("State")
|
|
701
|
+
table.add_column("Created By")
|
|
702
|
+
table.add_column("Created On")
|
|
703
|
+
EngineType = _get_engine_type_api()
|
|
388
704
|
for index, engine in enumerate(engines):
|
|
389
|
-
|
|
705
|
+
engine_type = engine.get("type", "")
|
|
706
|
+
type_display = EngineType.get_label_with_value(engine_type) if engine_type and EngineType.is_valid(engine_type) else (engine_type or "")
|
|
707
|
+
created_on = format_value(engine.get("created_on"))
|
|
708
|
+
state_display = format_state_with_color(engine.get("state", ""))
|
|
709
|
+
table.add_row(
|
|
710
|
+
f"{index+1}",
|
|
711
|
+
engine.get("name"),
|
|
712
|
+
type_display,
|
|
713
|
+
engine.get("size"),
|
|
714
|
+
state_display,
|
|
715
|
+
engine.get("created_by", ""),
|
|
716
|
+
created_on,
|
|
717
|
+
)
|
|
390
718
|
rich.print(table)
|
|
391
719
|
|
|
392
|
-
|
|
720
|
+
|
|
721
|
+
def show_engine_details(engine: dict[str, Any]) -> None:
|
|
722
|
+
"""Print a vertical table of engine details (one field per row)."""
|
|
723
|
+
from relationalai.clients.resources.snowflake import EngineType as _EngineType
|
|
724
|
+
|
|
725
|
+
table = Table(
|
|
726
|
+
show_header=True,
|
|
727
|
+
border_style="dim",
|
|
728
|
+
header_style="bold",
|
|
729
|
+
box=rich_box.SIMPLE_HEAD,
|
|
730
|
+
)
|
|
731
|
+
table.add_column("Field")
|
|
732
|
+
table.add_column("Value", overflow="fold")
|
|
733
|
+
|
|
734
|
+
engine_type_from_db = engine.get("type", "")
|
|
735
|
+
type_display = (
|
|
736
|
+
_EngineType.get_label_with_value(engine_type_from_db)
|
|
737
|
+
if engine_type_from_db and _EngineType.is_valid(engine_type_from_db)
|
|
738
|
+
else engine_type_from_db
|
|
739
|
+
)
|
|
740
|
+
|
|
741
|
+
rows: list[tuple[str, str]] = [
|
|
742
|
+
("Name", str(engine.get("name", ""))),
|
|
743
|
+
("Type", str(type_display)),
|
|
744
|
+
("Size", str(engine.get("size", ""))),
|
|
745
|
+
("State", str(format_state_with_color(engine.get("state", "")))),
|
|
746
|
+
("Created By", str(engine.get("created_by", ""))),
|
|
747
|
+
("Created On", str(format_value(engine.get("created_on")))),
|
|
748
|
+
]
|
|
749
|
+
|
|
750
|
+
# Optional fields (may not exist on older backends).
|
|
751
|
+
for key, label, formatter in [
|
|
752
|
+
("version", "Version", lambda v: "" if v is None else str(v)),
|
|
753
|
+
("updated_on", "Updated On", format_value),
|
|
754
|
+
("suspends_at", "Suspends At", format_value),
|
|
755
|
+
]:
|
|
756
|
+
if key in engine:
|
|
757
|
+
try:
|
|
758
|
+
val = engine.get(key)
|
|
759
|
+
rows.append((label, str(formatter(val))))
|
|
760
|
+
except Exception:
|
|
761
|
+
rows.append((label, str(engine.get(key))))
|
|
762
|
+
|
|
763
|
+
# Auto-suspend minutes is represented differently across backends:
|
|
764
|
+
# - list_engines tends to return auto_suspend_mins
|
|
765
|
+
# - get_engine (Snowflake EngineServiceSQL) returns auto_suspend
|
|
766
|
+
if "auto_suspend_mins" in engine or "auto_suspend" in engine:
|
|
767
|
+
auto_suspend_val = engine.get("auto_suspend_mins", engine.get("auto_suspend"))
|
|
768
|
+
rows.append(("Auto Suspend (mins)", "" if auto_suspend_val is None else str(auto_suspend_val)))
|
|
769
|
+
|
|
770
|
+
settings = engine.get("settings")
|
|
771
|
+
if settings in (None, {}, ""):
|
|
772
|
+
settings_str = ""
|
|
773
|
+
elif isinstance(settings, dict):
|
|
774
|
+
settings_str = json.dumps(settings, indent=2, sort_keys=True)
|
|
775
|
+
else:
|
|
776
|
+
settings_str = str(settings)
|
|
777
|
+
rows.append(("Settings", settings_str))
|
|
778
|
+
|
|
779
|
+
for field, value in rows:
|
|
780
|
+
table.add_row(field, value)
|
|
781
|
+
|
|
782
|
+
rich.print(table)
|
|
783
|
+
|
|
784
|
+
def exit_with_error(message: str) -> NoReturn:
|
|
393
785
|
rich.print(message, file=sys.stderr)
|
|
394
786
|
exit_with_divider(1)
|
|
395
787
|
|
|
396
|
-
def
|
|
788
|
+
def exit_with_handled_exception(context: str, exc: Exception) -> NoReturn:
|
|
789
|
+
"""Exit with a nicely formatted message for handled RAIExceptions.
|
|
790
|
+
|
|
791
|
+
- If `exc` is a RAIException (i.e. produced by an error handler), print its rich
|
|
792
|
+
formatted content via `exc.pprint()`.
|
|
793
|
+
- Otherwise, print a raw one-line error including the context and exception string.
|
|
794
|
+
"""
|
|
795
|
+
if isinstance(exc, RAIException):
|
|
796
|
+
exc.pprint()
|
|
797
|
+
sys.exit(1)
|
|
798
|
+
exit_with_error(f"\n\n[yellow]{context}: {exc}")
|
|
799
|
+
|
|
800
|
+
def exit_with_divider(exit_code: int = 0) -> NoReturn:
|
|
397
801
|
divider()
|
|
398
802
|
sys.exit(exit_code)
|
|
@@ -4,7 +4,7 @@ relationalai/debugging.py,sha256=wqGly2Yji4ErdV9F_S1Bny35SiGqI3C7S-hap7B8yUs,119
|
|
|
4
4
|
relationalai/dependencies.py,sha256=tL113efcISkJUiDXYHmRdU_usdD7gmee-VRHA7N4EFA,16574
|
|
5
5
|
relationalai/docutils.py,sha256=1gVv9mk0ytdMB2W7_NvslJefmSQtTOg8LHTCDcGCjyE,1554
|
|
6
6
|
relationalai/dsl.py,sha256=UJr93X8kwnnyUY-kjPzp_jhsp2pYBUnDfu8mhNXPNII,66116
|
|
7
|
-
relationalai/errors.py,sha256=
|
|
7
|
+
relationalai/errors.py,sha256=LNMUXoe7e19c4azqx3WDYpQs-59IEwCH_i3iXxGptE8,96830
|
|
8
8
|
relationalai/metagen.py,sha256=o10PNvR_myr_61DC8g6lkB093bFo9qXGUkZKgKyfXiE,26821
|
|
9
9
|
relationalai/metamodel.py,sha256=P1hliwHd1nYxbXON4LZeaYZD6T6pZm97HgmFBFrWyCk,32886
|
|
10
10
|
relationalai/rel.py,sha256=ePmAXx4NxOdsPcHNHyGH3Jkp_cB3QzfKu5p_EQSHPh0,38293
|
|
@@ -19,27 +19,28 @@ relationalai/auth/oauth_callback_server.py,sha256=vbcpz77n_WKMDZ4sac6IYyrpxScR0D
|
|
|
19
19
|
relationalai/auth/token_handler.py,sha256=d5aueGEiK6BmzkxSab3reCdkhtjdphb0CeecyvvNaQU,19443
|
|
20
20
|
relationalai/auth/util.py,sha256=oXOUwW5gaBhEV5v5A-q2ME1VnfjfRWswvRlXW4oBYpk,1116
|
|
21
21
|
relationalai/clients/__init__.py,sha256=LQ_yHsutRMpoW2mOTmOPGF8mrbP0OiV5E68t8uVwDyQ,833
|
|
22
|
-
relationalai/clients/client.py,sha256=
|
|
22
|
+
relationalai/clients/client.py,sha256=3yK1mCXYSECAS7vBa9uovTeRbfOvnBAJ9USWzhtr_bk,36715
|
|
23
23
|
relationalai/clients/config.py,sha256=hERaKjc3l4kd-kf0l-NUOHrWunCn8gmFWpuE0j3ScJg,24457
|
|
24
24
|
relationalai/clients/direct_access_client.py,sha256=VGjQ7wzduxCo04BkxSZjlPAgqK-aBc32zIXcMfAzzSU,6436
|
|
25
25
|
relationalai/clients/exec_txn_poller.py,sha256=JbmrTvsWGwxM5fcZVjeJihQDqhMf-ySfHN3Jn0HGwG0,3108
|
|
26
26
|
relationalai/clients/hash_util.py,sha256=pZVR1FX3q4G_19p_r6wpIR2tIM8_WUlfAR7AVZJjIYM,1495
|
|
27
|
-
relationalai/clients/local.py,sha256=
|
|
27
|
+
relationalai/clients/local.py,sha256=X_v4h8HNyZEYeyQuoDBfavLuedjCVKg5GvnEtOUn5WM,23952
|
|
28
28
|
relationalai/clients/profile_polling.py,sha256=pUH7WKH4nYDD0SlQtg3wsWdj0K7qt6nZqUw8jTthCBs,2565
|
|
29
29
|
relationalai/clients/result_helpers.py,sha256=wDSD02Ngx6W-YQqBIGKnpXD4Ju3pA1e9Nz6ORRI6SRI,17808
|
|
30
|
-
relationalai/clients/types.py,sha256=
|
|
30
|
+
relationalai/clients/types.py,sha256=xHkmrbqOfxxhBrTiQDUQqN0Lqnsv8pkAzstffYNdqqg,3038
|
|
31
31
|
relationalai/clients/util.py,sha256=NJC8fnrWHR01NydwESPSetIHRWf7jQJURYpaWJjmDyE,12311
|
|
32
32
|
relationalai/clients/resources/__init__.py,sha256=pymn8gB86Q3C2bVoFei0KAL8pX_U04uDY9TE4TKzTBs,260
|
|
33
|
-
relationalai/clients/resources/azure/azure.py,sha256=
|
|
34
|
-
relationalai/clients/resources/snowflake/__init__.py,sha256=
|
|
33
|
+
relationalai/clients/resources/azure/azure.py,sha256=3RQucsot4M8sU4yqeCi5IIeC4BgVFMQm1q9uhWY54W0,21676
|
|
34
|
+
relationalai/clients/resources/snowflake/__init__.py,sha256=kHjuNWr37viTd-n8tNMVo0FGhlKlrStb8_V9yJx_CNw,975
|
|
35
35
|
relationalai/clients/resources/snowflake/cache_store.py,sha256=A-qd11wcwN3TkIqvlN0_iFUU3aEjJal3T2pqFBwkkzQ,3966
|
|
36
|
-
relationalai/clients/resources/snowflake/cli_resources.py,sha256=
|
|
37
|
-
relationalai/clients/resources/snowflake/direct_access_resources.py,sha256=
|
|
38
|
-
relationalai/clients/resources/snowflake/
|
|
39
|
-
relationalai/clients/resources/snowflake/
|
|
36
|
+
relationalai/clients/resources/snowflake/cli_resources.py,sha256=DjvGyxYLwfCA57FgxpmyBfAyyHIHsbpa-UsoYziEowk,3689
|
|
37
|
+
relationalai/clients/resources/snowflake/direct_access_resources.py,sha256=HF9rowNOBrjimtBGJlDSRsS0jGEHu9FdQhRxImdCM0M,30624
|
|
38
|
+
relationalai/clients/resources/snowflake/engine_service.py,sha256=CYW0RoQjIlEr2w1FeUXwl2gj5o0HoNg9qLmiLo_7rQY,13492
|
|
39
|
+
relationalai/clients/resources/snowflake/engine_state_handlers.py,sha256=gHdqotbNkfAsXQq9XR7aRcsW4QBk7sKAxdra9YS5bsM,12079
|
|
40
|
+
relationalai/clients/resources/snowflake/error_handlers.py,sha256=R5f-3WhOBOFN373BjhkCNLBKVWBIMNPEm2VbAvyvELo,10733
|
|
40
41
|
relationalai/clients/resources/snowflake/export_procedure.py.jinja,sha256=00iLO2qmvJoqAeJUWt3bAsFDDnof7Ab2spggzelbwK4,10566
|
|
41
42
|
relationalai/clients/resources/snowflake/resources_factory.py,sha256=4LGd4IQ6z8hGeGlO1TIjSFJEeUNHutaB7j9q1a9rYfQ,3385
|
|
42
|
-
relationalai/clients/resources/snowflake/snowflake.py,sha256=
|
|
43
|
+
relationalai/clients/resources/snowflake/snowflake.py,sha256=wlGd0AA2fWo2ew681l3Q-N7It0PmcAmiJ4SekzOLcf4,135366
|
|
43
44
|
relationalai/clients/resources/snowflake/use_index_poller.py,sha256=xF_9XAymO82YZcZOQqz0r9oZZTqvbWW-WjDTZm4-tFM,48859
|
|
44
45
|
relationalai/clients/resources/snowflake/use_index_resources.py,sha256=69PNWHI_uf-Aw_evfwC6j8HLVdjhp84vs8hLkjnhwbg,6462
|
|
45
46
|
relationalai/clients/resources/snowflake/util.py,sha256=BEnm1B1-nqqHdm41RNxblbb-zqXbtqEGGZmTdAYeN_M,13841
|
|
@@ -360,7 +361,7 @@ relationalai/semantics/metamodel/rewrite/flatten.py,sha256=oyCYyJrq84YpNC6UpLDfS
|
|
|
360
361
|
relationalai/semantics/metamodel/rewrite/format_outputs.py,sha256=n0IxC3RL3UMly6MWsq342EGfL2yGj3vOgVG_wg7kt-o,6225
|
|
361
362
|
relationalai/semantics/metamodel/typer/__init__.py,sha256=E3ydmhWRdm-cAqWsNR24_Qd3NcwiHx8ElO2tzNysAXc,143
|
|
362
363
|
relationalai/semantics/metamodel/typer/checker.py,sha256=frY0gilDO6skbDiYFiIpDUOWyt9s9jAJsRBs848DcG0,19184
|
|
363
|
-
relationalai/semantics/metamodel/typer/typer.py,sha256=
|
|
364
|
+
relationalai/semantics/metamodel/typer/typer.py,sha256=cgb8ckAIQSIzkS2VLvj9tl2UdGALKZXrsn_io35ojNk,62906
|
|
364
365
|
relationalai/semantics/reasoners/__init__.py,sha256=cLrGNKFX859EdPjk2n6MdYLvueaFtTEnfVYnBriQMfI,303
|
|
365
366
|
relationalai/semantics/reasoners/experimental/__init__.py,sha256=ZWXb3Oun7m_G2c3ijKnqxbEsAzTMVa7ciBXjdi4dCNI,144
|
|
366
367
|
relationalai/semantics/reasoners/graph/README.md,sha256=QgKEXTllp5PO-yK8oDfMx1PNTYF2uVoneMRKsWTY5GU,23953
|
|
@@ -417,9 +418,9 @@ relationalai/std/re.py,sha256=7B0dPaYyEdIlEgZfDzs7HJ_MTqccYTfIZieqst6MHbk,4457
|
|
|
417
418
|
relationalai/std/strings.py,sha256=vHvex_W5GHhhsVws6Dfyl4w1EHdbDE3nPjT09DnpvSE,4260
|
|
418
419
|
relationalai/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
419
420
|
relationalai/tools/cleanup_snapshots.py,sha256=s1q1ophi1RmiTZ9QQolQ6q1K9ZwseCaOuDtI1Rm99MQ,3434
|
|
420
|
-
relationalai/tools/cli.py,sha256=
|
|
421
|
+
relationalai/tools/cli.py,sha256=KM-_4KCNkW6loi3LjfLNRcApKTcusd_8gFS8166ZEAs,84147
|
|
421
422
|
relationalai/tools/cli_controls.py,sha256=84K0sjfsVwqi9C8ZlpvPi6XZPhjyW-R06Pir9Ex_Xtg,70468
|
|
422
|
-
relationalai/tools/cli_helpers.py,sha256=
|
|
423
|
+
relationalai/tools/cli_helpers.py,sha256=z7D4-9hX8DMm_WrG_5JYpsRhe9WzznsbKh2kN4OvGuw,30846
|
|
423
424
|
relationalai/tools/constants.py,sha256=A8Qz0CPs2cRUs5xaZ-Y7BCyvqmI6RLvipzwLDYZ3_PU,4505
|
|
424
425
|
relationalai/tools/debugger.py,sha256=iWO1tMJud3dXHIkrjdZ5Temp0MR2AwN5_QGDt-ddmcs,6934
|
|
425
426
|
relationalai/tools/debugger_client.py,sha256=g9vBaeJLKVq6vW63W-KKSaPJ--g5Wuf4LcIuYRA_pk4,3729
|
|
@@ -444,7 +445,7 @@ relationalai/util/spans_file_handler.py,sha256=a0sDwDPBBvGsM6be2En3mId9sXpuJlXia
|
|
|
444
445
|
relationalai/util/timeout.py,sha256=2o6BVNFnFc-B2j-i1pEkZcQbMRto9ps2emci0XwiA4I,783
|
|
445
446
|
relationalai/util/tracing_handler.py,sha256=H919ETAxh7Z1tRz9x8m90qP51_264UunHAPw8Sr6x2g,1729
|
|
446
447
|
relationalai_test_util/__init__.py,sha256=Io_9_IQXXnrUlaL7S1Ndv-4YHilNxy36LrL723MI7lw,118
|
|
447
|
-
relationalai_test_util/fixtures.py,sha256=
|
|
448
|
+
relationalai_test_util/fixtures.py,sha256=1fCU-45pUMuWstkOfQ1u_37lJnPPqC43ks2nYW-UA7A,9478
|
|
448
449
|
relationalai_test_util/snapshot.py,sha256=FeH2qYBzLxr2-9qs0yElPIgWUjm_SrzawB3Jgn-aSuE,9291
|
|
449
450
|
relationalai_test_util/traceback.py,sha256=lD0qaEmCyO-7xg9CNf6IzwS-Q-sTS8N9YIv8RroAE50,3298
|
|
450
451
|
frontend/debugger/dist/.gitignore,sha256=JAo-DTfS6GthQGP1NH6wLU-ZymwlTea4KHH_jZVTKn0,14
|
|
@@ -452,8 +453,8 @@ frontend/debugger/dist/index.html,sha256=0wIQ1Pm7BclVV1wna6Mj8OmgU73B9rSEGPVX-Wo
|
|
|
452
453
|
frontend/debugger/dist/assets/favicon-Dy0ZgA6N.png,sha256=tPXOEhOrM4tJyZVJQVBO_yFgNAlgooY38ZsjyrFstgg,620
|
|
453
454
|
frontend/debugger/dist/assets/index-Cssla-O7.js,sha256=MxgIGfdKQyBWgufck1xYggQNhW5nj6BPjCF6Wleo-f0,298886
|
|
454
455
|
frontend/debugger/dist/assets/index-DlHsYx1V.css,sha256=21pZtAjKCcHLFjbjfBQTF6y7QmOic-4FYaKNmwdNZVE,60141
|
|
455
|
-
relationalai-0.13.
|
|
456
|
-
relationalai-0.13.
|
|
457
|
-
relationalai-0.13.
|
|
458
|
-
relationalai-0.13.
|
|
459
|
-
relationalai-0.13.
|
|
456
|
+
relationalai-0.13.2.dist-info/METADATA,sha256=C_6NBEgmM09pft0zVQWwdSXVuUDJG6IGrY_C0WKuVCE,2561
|
|
457
|
+
relationalai-0.13.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
458
|
+
relationalai-0.13.2.dist-info/entry_points.txt,sha256=fo_oLFJih3PUgYuHXsk7RnCjBm9cqRNR--ab6DgI6-0,88
|
|
459
|
+
relationalai-0.13.2.dist-info/licenses/LICENSE,sha256=pPyTVXFYhirkEW9VsnHIgUjT0Vg8_xsE6olrF5SIgpc,11343
|
|
460
|
+
relationalai-0.13.2.dist-info/RECORD,,
|
|
@@ -78,14 +78,14 @@ def create_engine(engine_name: str, size: str, use_direct_access=False):
|
|
|
78
78
|
|
|
79
79
|
provider = rai.Resources(config=config)
|
|
80
80
|
print(f"Creating engine {engine_name}")
|
|
81
|
-
provider.create_engine(name=engine_name, size=size)
|
|
81
|
+
provider.create_engine(name=engine_name, type="LOGIC", size=size)
|
|
82
82
|
print(f"Engine {engine_name} created")
|
|
83
83
|
|
|
84
84
|
def delete_engine(engine_name: str, use_direct_access=False):
|
|
85
85
|
print(f"Deleting engine {engine_name}")
|
|
86
86
|
config = make_config(engine_name, use_direct_access=use_direct_access)
|
|
87
87
|
provider = rai.Resources(config=config)
|
|
88
|
-
provider.delete_engine(engine_name)
|
|
88
|
+
provider.delete_engine(engine_name, "LOGIC")
|
|
89
89
|
print(f"Engine {engine_name} deleted")
|
|
90
90
|
|
|
91
91
|
def make_config(engine_name: str | None = None, fetch_profile: bool = True, use_package_manager = USE_PACKAGE_MANAGER, use_direct_access = False, show_full_traces = True, show_debug_logs = True, reuse_model = False) -> cfg.Config:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|