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
relationalai/tools/cli.py
CHANGED
|
@@ -30,10 +30,9 @@ from ..tools import debugger as deb, qb_debugger as qb_deb
|
|
|
30
30
|
from ..clients import config
|
|
31
31
|
from relationalai.tools.constants import RAI_APP_NAME
|
|
32
32
|
from relationalai.clients.resources.snowflake.cli_resources import CLIResources
|
|
33
|
+
from relationalai.clients.resources.snowflake import EngineType
|
|
33
34
|
from relationalai.tools.cli_helpers import (
|
|
34
35
|
EMPTY_STRING_REGEX,
|
|
35
|
-
ENGINE_NAME_ERROR,
|
|
36
|
-
ENGINE_NAME_REGEX,
|
|
37
36
|
PASSCODE_REGEX,
|
|
38
37
|
UUID,
|
|
39
38
|
RichGroup,
|
|
@@ -42,14 +41,25 @@ from relationalai.tools.cli_helpers import (
|
|
|
42
41
|
ensure_config,
|
|
43
42
|
exit_with_divider,
|
|
44
43
|
exit_with_error,
|
|
44
|
+
exit_with_handled_exception,
|
|
45
45
|
filter_profiles_by_platform,
|
|
46
46
|
format_row,
|
|
47
47
|
get_config, get_resource_provider,
|
|
48
48
|
is_latest_cli_version,
|
|
49
49
|
issue_top_level_profile_warning,
|
|
50
50
|
latest_version,
|
|
51
|
+
select_engine_interactive,
|
|
52
|
+
select_engine_with_state_filter,
|
|
53
|
+
ensure_engine_type_for_snowflake,
|
|
54
|
+
build_engine_operation_messages,
|
|
55
|
+
prompt_and_validate_engine_name,
|
|
56
|
+
validate_auto_suspend_mins,
|
|
57
|
+
get_engine_type_for_creation,
|
|
58
|
+
get_and_validate_engine_size,
|
|
59
|
+
create_engine_with_spinner,
|
|
51
60
|
show_dictionary_table,
|
|
52
61
|
show_engines,
|
|
62
|
+
show_engine_details,
|
|
53
63
|
show_imports,
|
|
54
64
|
show_transactions,
|
|
55
65
|
supports_platform,
|
|
@@ -113,6 +123,50 @@ def cli(profile):
|
|
|
113
123
|
rich.print(f"[yellow]RelationalAI version ({latest_ver}) is the latest. Please consider upgrading.[/yellow]")
|
|
114
124
|
GlobalProfile.set(profile)
|
|
115
125
|
|
|
126
|
+
|
|
127
|
+
#--------------------------------------------------
|
|
128
|
+
# Engines helpers
|
|
129
|
+
#--------------------------------------------------
|
|
130
|
+
|
|
131
|
+
def _exit_engine_requires_type(name: str, available_types: list[str], cmd: str) -> None:
|
|
132
|
+
"""Exit with a consistent 'available types' hint and example command."""
|
|
133
|
+
types_display = ", ".join(available_types) if available_types else "<unknown>"
|
|
134
|
+
example_type = available_types[0] if available_types else "<ENGINE TYPE>"
|
|
135
|
+
exit_with_error(
|
|
136
|
+
f"[yellow]Engine '{name}' has no LOGIC type engine. Available types: {types_display}. "
|
|
137
|
+
f"Please re-run with [cyan]--type[/cyan]. Example: \n\n"
|
|
138
|
+
f"[green]rai {cmd} --name {name} --type {example_type}[/green]"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _get_engine_types_for_name(provider: ResourcesBase, name: str) -> list[str] | None:
|
|
143
|
+
"""Return available engine types for a given name, or None if name not found.
|
|
144
|
+
|
|
145
|
+
Errors are handled via exit_with_handled_exception, since this is used for user-facing
|
|
146
|
+
CLI diagnostics.
|
|
147
|
+
"""
|
|
148
|
+
try:
|
|
149
|
+
engines_with_name = provider.list_engines(name=name)
|
|
150
|
+
except Exception as e:
|
|
151
|
+
exit_with_handled_exception("Error fetching engines", e)
|
|
152
|
+
raise Exception("unreachable")
|
|
153
|
+
if not engines_with_name:
|
|
154
|
+
return None
|
|
155
|
+
return sorted({(e.get("type") or "").upper() for e in engines_with_name if e.get("type")})
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _require_type_if_no_logic(provider: ResourcesBase, name: str, cmd: str) -> str:
|
|
159
|
+
"""Return LOGIC if present, otherwise exit with a helpful types message."""
|
|
160
|
+
available_types = _get_engine_types_for_name(provider, name)
|
|
161
|
+
if not available_types:
|
|
162
|
+
exit_with_error(f"[yellow]No engine found with name '{name}'.")
|
|
163
|
+
raise Exception("unreachable")
|
|
164
|
+
has_logic = any(t == EngineType.LOGIC for t in available_types)
|
|
165
|
+
if has_logic:
|
|
166
|
+
return EngineType.LOGIC
|
|
167
|
+
_exit_engine_requires_type(name, available_types, cmd)
|
|
168
|
+
raise Exception("unreachable")
|
|
169
|
+
|
|
116
170
|
#--------------------------------------------------
|
|
117
171
|
# Init
|
|
118
172
|
#--------------------------------------------------
|
|
@@ -716,7 +770,7 @@ def config_check(all_profiles:bool=False):
|
|
|
716
770
|
engine_name = cfg.get("engine")
|
|
717
771
|
assert isinstance(engine_name, str), f"Engine name must be a string, not {type(engine_name)}"
|
|
718
772
|
# This essentially checks if the profile is valid since we are connecting to get the engine
|
|
719
|
-
engine = provider.get_engine(engine_name)
|
|
773
|
+
engine = provider.get_engine(engine_name, EngineType.LOGIC)
|
|
720
774
|
if not engine or (engine and not provider.is_valid_engine_state(engine.get("state"))):
|
|
721
775
|
provider.auto_create_engine_async(engine_name)
|
|
722
776
|
except Exception as e:
|
|
@@ -823,16 +877,20 @@ def debugger(host, port, old, qb, profile):
|
|
|
823
877
|
|
|
824
878
|
@cli.command(name="engines:list", help="List all engines")
|
|
825
879
|
@click.option("--state", help="Filter by engine state")
|
|
826
|
-
|
|
880
|
+
@click.option("--name", help="Filter by engine name (case-insensitive partial match)")
|
|
881
|
+
@click.option("--type", help="Filter by engine type")
|
|
882
|
+
@click.option("--size", help="Filter by engine size")
|
|
883
|
+
@click.option("--created-by", help="Filter by creator (case-insensitive partial match)")
|
|
884
|
+
def engines_list(state:str|None=None, name:str|None=None, type:str|None=None, size:str|None=None, created_by:str|None=None):
|
|
827
885
|
divider(flush=True)
|
|
828
886
|
ensure_config()
|
|
829
887
|
rich.print("Note: [cyan]Engine names are case sensitive")
|
|
830
888
|
rich.print("")
|
|
831
889
|
with Spinner("Fetching engines"):
|
|
832
890
|
try:
|
|
833
|
-
engines = get_resource_provider().list_engines(state)
|
|
891
|
+
engines = get_resource_provider().list_engines(state=state, name=name, type=type, size=size, created_by=created_by)
|
|
834
892
|
except Exception as e:
|
|
835
|
-
return
|
|
893
|
+
return exit_with_handled_exception("Error fetching engines", e)
|
|
836
894
|
|
|
837
895
|
if len(engines):
|
|
838
896
|
show_engines(engines)
|
|
@@ -842,31 +900,64 @@ def engines_list(state:str|None=None):
|
|
|
842
900
|
|
|
843
901
|
@cli.command(name="engines:get", help="Get engine details")
|
|
844
902
|
@click.option("--name", help="Name of the engine")
|
|
845
|
-
|
|
903
|
+
@click.option("--type", help="Type of the engine")
|
|
904
|
+
def engines_get(name:str|None=None, type:str|None=None):
|
|
846
905
|
divider(flush=True)
|
|
847
906
|
ensure_config()
|
|
907
|
+
provider = get_resource_provider()
|
|
908
|
+
|
|
909
|
+
# Default to LOGIC for backwards compatibility when --type is not provided but --name is.
|
|
910
|
+
# If LOGIC doesn't exist but other types do, we'll show a targeted hint after probing.
|
|
911
|
+
if name and type is None:
|
|
912
|
+
type = EngineType.LOGIC
|
|
848
913
|
|
|
849
914
|
rich.print("Note: [cyan]Engine names are case sensitive")
|
|
850
915
|
rich.print("")
|
|
851
916
|
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
917
|
+
engine = None
|
|
918
|
+
if not name or (not type or not EngineType.is_valid(type)):
|
|
919
|
+
if type and not EngineType.is_valid(type):
|
|
920
|
+
rich.print(f"[yellow]Invalid engine type '{type}'.")
|
|
855
921
|
|
|
856
|
-
with Spinner("Fetching engine"):
|
|
857
922
|
try:
|
|
858
|
-
|
|
923
|
+
engines_list = provider.list_engines(name=name if name else None, type=type if type else None)
|
|
859
924
|
except Exception as e:
|
|
860
|
-
return
|
|
925
|
+
return exit_with_handled_exception("Error fetching engines", e)
|
|
926
|
+
result = select_engine_interactive(
|
|
927
|
+
provider,
|
|
928
|
+
"Select an engine:",
|
|
929
|
+
engine_name=name,
|
|
930
|
+
engines=engines_list,
|
|
931
|
+
)
|
|
932
|
+
if result is None:
|
|
933
|
+
return
|
|
934
|
+
name, type = result
|
|
935
|
+
|
|
936
|
+
for eng in engines_list:
|
|
937
|
+
if eng.get("name", "").upper() == name.upper() and (type is None or eng.get("type", "").upper() == type.upper()):
|
|
938
|
+
engine = eng
|
|
939
|
+
break
|
|
940
|
+
|
|
941
|
+
if engine is None:
|
|
942
|
+
with Spinner("Fetching engine"):
|
|
943
|
+
try:
|
|
944
|
+
engine_type = type or EngineType.LOGIC
|
|
945
|
+
engine = provider.get_engine(name, engine_type)
|
|
946
|
+
except Exception as e:
|
|
947
|
+
return exit_with_handled_exception("Error fetching engine", e)
|
|
861
948
|
|
|
862
949
|
if engine:
|
|
863
|
-
|
|
864
|
-
table.add_column("Name")
|
|
865
|
-
table.add_column("Size")
|
|
866
|
-
table.add_column("State")
|
|
867
|
-
table.add_row(engine.get("name"), engine.get("size"), engine.get("state"))
|
|
868
|
-
rich.print(table)
|
|
950
|
+
show_engine_details(cast(dict[str, Any], engine))
|
|
869
951
|
else:
|
|
952
|
+
# If the user didn't specify --type, try to detect whether the engine exists
|
|
953
|
+
# under a non-LOGIC type and provide a helpful hint.
|
|
954
|
+
if name and type == EngineType.LOGIC:
|
|
955
|
+
try:
|
|
956
|
+
available_types = _get_engine_types_for_name(provider, name)
|
|
957
|
+
except Exception:
|
|
958
|
+
available_types = None
|
|
959
|
+
if available_types and EngineType.LOGIC not in available_types:
|
|
960
|
+
_exit_engine_requires_type(name, available_types, "engines:get")
|
|
870
961
|
exit_with_error(f'[yellow]Engine "{name}" not found')
|
|
871
962
|
divider()
|
|
872
963
|
|
|
@@ -874,33 +965,18 @@ def engines_get(name:str|None=None):
|
|
|
874
965
|
# Engine create
|
|
875
966
|
#--------------------------------------------------
|
|
876
967
|
|
|
877
|
-
def create_engine_flow(cfg:config.Config, name=None, size=None, auto_suspend_mins=None):
|
|
968
|
+
def create_engine_flow(cfg:config.Config, name=None, engine_type=None, size=None, auto_suspend_mins=None):
|
|
969
|
+
"""Main flow for creating an engine interactively or programmatically."""
|
|
878
970
|
provider = get_resource_provider(None, cfg)
|
|
879
|
-
engine = None
|
|
880
|
-
is_engine_present = False
|
|
881
|
-
is_engine_suspended = False
|
|
882
971
|
is_interactive = name is None or size is None
|
|
883
|
-
auto_suspend_mins = cfg.get("auto_suspend_mins", None) if auto_suspend_mins is None else auto_suspend_mins
|
|
884
972
|
if is_interactive:
|
|
885
973
|
rich.print("Note: [cyan]Engine names are case sensitive")
|
|
886
974
|
rich.print("")
|
|
887
975
|
|
|
888
|
-
if
|
|
889
|
-
|
|
890
|
-
"Engine name:",
|
|
891
|
-
name,
|
|
892
|
-
validator=ENGINE_NAME_REGEX.match,
|
|
893
|
-
invalid_message=ENGINE_NAME_ERROR,
|
|
894
|
-
newline=True
|
|
895
|
-
)
|
|
976
|
+
auto_suspend_mins = cfg.get("auto_suspend_mins", None) if auto_suspend_mins is None else auto_suspend_mins
|
|
977
|
+
auto_suspend_mins = validate_auto_suspend_mins(auto_suspend_mins)
|
|
896
978
|
|
|
897
|
-
|
|
898
|
-
error_msg = f"[yellow]Error: auto_suspend_mins must be an integer instead of {type(auto_suspend_mins)}"
|
|
899
|
-
try:
|
|
900
|
-
auto_suspend_mins = int(auto_suspend_mins)
|
|
901
|
-
except ValueError:
|
|
902
|
-
exit_with_error(error_msg)
|
|
903
|
-
assert isinstance(auto_suspend_mins, int), error_msg
|
|
979
|
+
name = prompt_and_validate_engine_name(name)
|
|
904
980
|
|
|
905
981
|
is_name_valid, msg = validate_engine_name(name)
|
|
906
982
|
if not is_name_valid:
|
|
@@ -912,66 +988,49 @@ def create_engine_flow(cfg:config.Config, name=None, size=None, auto_suspend_min
|
|
|
912
988
|
else:
|
|
913
989
|
exit_with_divider(1)
|
|
914
990
|
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
is_engine_present = True
|
|
923
|
-
elif "Not Found" in f"{e}":
|
|
924
|
-
is_engine_present = False
|
|
925
|
-
else:
|
|
926
|
-
raise e
|
|
927
|
-
if is_engine_suspended:
|
|
928
|
-
rich.print("")
|
|
929
|
-
with Spinner(f"Resuming engine '{name}'", f"Engine '{name}' resumed", "Error:"):
|
|
930
|
-
provider.resume_engine(name)
|
|
931
|
-
return name
|
|
932
|
-
if is_engine_present:
|
|
933
|
-
rich.print("")
|
|
934
|
-
if is_interactive:
|
|
935
|
-
rich.print(f"Engine '{name}' already exists")
|
|
936
|
-
rich.print("")
|
|
937
|
-
return create_engine_flow(cfg)
|
|
938
|
-
else:
|
|
939
|
-
exit_with_error(f"[yellow]Engine '{name}' already exists")
|
|
991
|
+
# Backwards-compatible behavior:
|
|
992
|
+
# - If --type is omitted, default to LOGIC (script-friendly; avoids interactive prompts).
|
|
993
|
+
# Interactive behavior:
|
|
994
|
+
# - Only prompt for engine type when the user didn't provide --name (fully interactive flow).
|
|
995
|
+
if name is None and engine_type is None:
|
|
996
|
+
engine_type = ""
|
|
997
|
+
engine_type = get_engine_type_for_creation(provider, cfg, engine_type)
|
|
940
998
|
|
|
941
|
-
#
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
if size is None:
|
|
951
|
-
size = cfg.get("engine_size", None)
|
|
952
|
-
valid_sizes = provider.get_engine_sizes()
|
|
953
|
-
if not isinstance(size, str) or size not in valid_sizes:
|
|
954
|
-
exit_with_error(f"\nInvalid engine size [yellow]{size}[/yellow] provided. Please check your config.\n\nValid sizes: [green]{valid_sizes}[/green]")
|
|
955
|
-
assert isinstance(size, str), "engine_size must be a string"
|
|
999
|
+
# Simple existence check using the new get_engine API
|
|
1000
|
+
try:
|
|
1001
|
+
existing = provider.get_engine(name, engine_type or EngineType.LOGIC)
|
|
1002
|
+
if existing:
|
|
1003
|
+
engine_type_label = EngineType.get_label(engine_type or EngineType.LOGIC)
|
|
1004
|
+
exit_with_error(f"[yellow]Engine '{name}' with type '{engine_type_label} ({engine_type})' already exists.")
|
|
1005
|
+
except Exception:
|
|
1006
|
+
# If get_engine fails, proceed to creation path; real errors will be surfaced by create_engine
|
|
1007
|
+
pass
|
|
956
1008
|
|
|
957
|
-
|
|
1009
|
+
size = get_and_validate_engine_size(provider, cfg, size, engine_type)
|
|
1010
|
+
|
|
1011
|
+
if is_interactive:
|
|
1012
|
+
rich.print("")
|
|
958
1013
|
|
|
959
|
-
|
|
960
|
-
f"Creating '{name}' engine with size {size}... (this may take several minutes)",
|
|
961
|
-
f"Engine '{name}' created!",
|
|
962
|
-
failed_message="Error:"
|
|
963
|
-
):
|
|
964
|
-
provider.create_engine(name, size, auto_suspend_mins)
|
|
1014
|
+
create_engine_with_spinner(provider, name, size, engine_type, auto_suspend_mins)
|
|
965
1015
|
return name
|
|
966
1016
|
|
|
967
1017
|
@cli.command(name="engines:create", help="Create a new engine")
|
|
968
1018
|
@click.option("--name", help="Name of the engine")
|
|
1019
|
+
@click.option("--type", help="Type of the engine")
|
|
969
1020
|
@click.option("--size", help="Size of the engine")
|
|
970
|
-
@click.option(
|
|
971
|
-
|
|
1021
|
+
@click.option(
|
|
1022
|
+
"--auto-suspend-mins",
|
|
1023
|
+
"--auto_suspend_mins",
|
|
1024
|
+
help="Suspend the engine after this many minutes of inactivity",
|
|
1025
|
+
default=None,
|
|
1026
|
+
)
|
|
1027
|
+
def engines_create(name, type, size, auto_suspend_mins):
|
|
972
1028
|
divider(flush=True)
|
|
973
1029
|
cfg = ensure_config()
|
|
974
|
-
|
|
1030
|
+
try:
|
|
1031
|
+
create_engine_flow(cfg, name, type, size, auto_suspend_mins)
|
|
1032
|
+
except Exception as e:
|
|
1033
|
+
return exit_with_handled_exception("Error creating engine", e)
|
|
975
1034
|
divider()
|
|
976
1035
|
|
|
977
1036
|
#--------------------------------------------------
|
|
@@ -980,40 +1039,62 @@ def engines_create(name, size, auto_suspend_mins):
|
|
|
980
1039
|
|
|
981
1040
|
@cli.command(name="engines:delete", help="Delete an engine")
|
|
982
1041
|
@click.option("--name", help="Name of the engine")
|
|
983
|
-
@click.option("--
|
|
984
|
-
def engines_delete(name,
|
|
1042
|
+
@click.option("--type", help="Type of the engine")
|
|
1043
|
+
def engines_delete(name, type):
|
|
985
1044
|
divider(flush=True)
|
|
986
1045
|
ensure_config()
|
|
987
1046
|
provider = get_resource_provider()
|
|
988
|
-
|
|
989
|
-
name
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
except Exception as e:
|
|
1002
|
-
exit_with_error(f"[yellow]Error fetching engine: {e}")
|
|
1047
|
+
try:
|
|
1048
|
+
_engines_delete(provider, name, type)
|
|
1049
|
+
except Exception as e:
|
|
1050
|
+
return exit_with_handled_exception("Error deleting engine", e)
|
|
1051
|
+
divider()
|
|
1052
|
+
|
|
1053
|
+
def _engines_delete(provider: ResourcesBase, name, type) -> None:
|
|
1054
|
+
# If --type is omitted but --name is provided:
|
|
1055
|
+
# - prefer LOGIC for backwards compatibility if that engine exists
|
|
1056
|
+
# - otherwise, require explicit --type to avoid accidentally deleting the wrong engine type
|
|
1057
|
+
if name and type is None:
|
|
1058
|
+
# We only auto-select LOGIC; otherwise require explicit --type (avoid accidental deletes).
|
|
1059
|
+
type = _require_type_if_no_logic(provider, name, "engines:delete")
|
|
1003
1060
|
|
|
1004
|
-
|
|
1005
|
-
|
|
1061
|
+
# Select engine if name or type missing
|
|
1062
|
+
if not name or not type:
|
|
1006
1063
|
try:
|
|
1007
|
-
provider
|
|
1064
|
+
result = select_engine_interactive(provider, "Select an engine to delete:", engine_name=name)
|
|
1008
1065
|
except Exception as e:
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1066
|
+
return exit_with_handled_exception("Error fetching engines", e)
|
|
1067
|
+
if result is None:
|
|
1068
|
+
if name:
|
|
1069
|
+
exit_with_error(f"[yellow]No engine found with name '{name}'.")
|
|
1070
|
+
return
|
|
1071
|
+
name, type = result
|
|
1072
|
+
|
|
1073
|
+
engine_type = ensure_engine_type_for_snowflake(
|
|
1074
|
+
provider,
|
|
1075
|
+
name,
|
|
1076
|
+
type,
|
|
1077
|
+
f"[yellow]Engine type is required for engine '{name}'. Please specify --type or select from the list.",
|
|
1078
|
+
)
|
|
1079
|
+
|
|
1080
|
+
operation_msg, success_msg = build_engine_operation_messages(provider, name, engine_type, "Deleting", "Deleted")
|
|
1081
|
+
|
|
1082
|
+
try:
|
|
1083
|
+
with Spinner(operation_msg, success_msg):
|
|
1084
|
+
provider.delete_engine(name, engine_type)
|
|
1085
|
+
except Exception as e:
|
|
1086
|
+
error_str = str(e).lower()
|
|
1087
|
+
if "setup_cdc" in str(e):
|
|
1088
|
+
exc = Exception(
|
|
1089
|
+
"Imports are setup to utilize this engine.\n"
|
|
1090
|
+
"Use 'rai engines:delete --force' to force delete engines."
|
|
1091
|
+
)
|
|
1092
|
+
elif "engine not found" in error_str or ("not found" in error_str and "engine" in error_str):
|
|
1093
|
+
engine_type_label = EngineType.get_label(engine_type) if EngineType.is_valid(engine_type) else engine_type
|
|
1094
|
+
exc = Exception(f"Engine '{name}' with type '{engine_type_label} ({engine_type})' not found.")
|
|
1095
|
+
else:
|
|
1096
|
+
exc = e
|
|
1097
|
+
exit_with_handled_exception("Error deleting engine", exc)
|
|
1017
1098
|
|
|
1018
1099
|
#--------------------------------------------------
|
|
1019
1100
|
# Engine resume
|
|
@@ -1021,35 +1102,67 @@ def engines_delete(name, force=False):
|
|
|
1021
1102
|
|
|
1022
1103
|
@cli.command(name="engines:resume", help="Resume an engine")
|
|
1023
1104
|
@click.option("--name", help="Name of the engine")
|
|
1024
|
-
|
|
1105
|
+
@click.option("--type", help="Type of the engine")
|
|
1106
|
+
def engines_resume(name, type):
|
|
1025
1107
|
divider(flush=True)
|
|
1026
1108
|
ensure_config()
|
|
1027
1109
|
provider = get_resource_provider()
|
|
1028
|
-
|
|
1029
|
-
name
|
|
1110
|
+
try:
|
|
1111
|
+
_engines_resume(provider, name, type)
|
|
1112
|
+
except Exception as e:
|
|
1113
|
+
return exit_with_handled_exception("Error resuming engine", e)
|
|
1114
|
+
divider()
|
|
1115
|
+
|
|
1116
|
+
def _engines_resume(provider: ResourcesBase, name, type) -> None:
|
|
1117
|
+
type_was_omitted = type is None
|
|
1118
|
+
if name and type is None:
|
|
1119
|
+
type = EngineType.LOGIC
|
|
1120
|
+
|
|
1121
|
+
# Validate type early if provided
|
|
1122
|
+
if type and not EngineType.is_valid(type):
|
|
1123
|
+
exit_with_error(f"[yellow]Invalid engine type '{type}'. Valid types: LOGIC, SOLVER, ML")
|
|
1124
|
+
|
|
1125
|
+
try:
|
|
1126
|
+
result = select_engine_with_state_filter(
|
|
1127
|
+
provider,
|
|
1128
|
+
name,
|
|
1129
|
+
type,
|
|
1130
|
+
"SUSPENDED",
|
|
1131
|
+
"Select a suspended engine to resume:",
|
|
1030
1132
|
"Select a suspended engine to resume:",
|
|
1031
|
-
"engines",
|
|
1032
|
-
|
|
1133
|
+
"[yellow]No suspended engines found",
|
|
1134
|
+
f"[yellow]No suspended engines found with name '{name}'" if name else "[yellow]No suspended engines found",
|
|
1033
1135
|
)
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
if not engine:
|
|
1040
|
-
exit_with_error(f"[yellow]Engine '{name}' not found")
|
|
1041
|
-
if engine and engine.get("state") != "SUSPENDED":
|
|
1042
|
-
exit_with_error(f"[yellow]Engine '{name}' not in 'SUSPENDED' state")
|
|
1043
|
-
except Exception as e:
|
|
1044
|
-
exit_with_error(f"[yellow]Error fetching engine: {e}")
|
|
1136
|
+
except Exception as e:
|
|
1137
|
+
return exit_with_handled_exception("Error fetching engines", e)
|
|
1138
|
+
if result is None:
|
|
1139
|
+
return
|
|
1140
|
+
name, type = result
|
|
1045
1141
|
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1142
|
+
engine_type = ensure_engine_type_for_snowflake(
|
|
1143
|
+
provider,
|
|
1144
|
+
name,
|
|
1145
|
+
type,
|
|
1146
|
+
f"[yellow]Engine type is required for engine '{name}'. Please specify --type or select from the list.",
|
|
1147
|
+
)
|
|
1148
|
+
|
|
1149
|
+
operation_msg, success_msg = build_engine_operation_messages(provider, name, engine_type, "Resuming", "Resumed")
|
|
1150
|
+
try:
|
|
1151
|
+
with Spinner(operation_msg, success_msg):
|
|
1152
|
+
provider.resume_engine(name, engine_type)
|
|
1153
|
+
except Exception as e:
|
|
1154
|
+
error_str = str(e).lower()
|
|
1155
|
+
if "engine not found" in error_str or ("not found" in error_str and "engine" in error_str):
|
|
1156
|
+
# If the user omitted --type and we defaulted to LOGIC, try to hint at other types.
|
|
1157
|
+
if type_was_omitted and engine_type == EngineType.LOGIC:
|
|
1158
|
+
available_types = _get_engine_types_for_name(provider, name)
|
|
1159
|
+
if available_types and EngineType.LOGIC not in available_types:
|
|
1160
|
+
_exit_engine_requires_type(name, available_types, "engines:resume")
|
|
1161
|
+
engine_type_label = EngineType.get_label(engine_type) if EngineType.is_valid(engine_type) else engine_type
|
|
1162
|
+
exc = Exception(f"Engine '{name}' with type '{engine_type_label} ({engine_type})' not found.")
|
|
1163
|
+
else:
|
|
1164
|
+
exc = e
|
|
1165
|
+
exit_with_handled_exception("Error resuming engine", exc)
|
|
1053
1166
|
|
|
1054
1167
|
#--------------------------------------------------
|
|
1055
1168
|
# Engine suspend
|
|
@@ -1057,35 +1170,66 @@ def engines_resume(name):
|
|
|
1057
1170
|
|
|
1058
1171
|
@cli.command(name="engines:suspend", help="Suspend an engine")
|
|
1059
1172
|
@click.option("--name", help="Name of the engine")
|
|
1060
|
-
|
|
1173
|
+
@click.option("--type", help="Type of the engine")
|
|
1174
|
+
def engines_suspend(name, type):
|
|
1061
1175
|
divider(flush=True)
|
|
1062
1176
|
ensure_config()
|
|
1063
1177
|
provider = get_resource_provider()
|
|
1064
|
-
|
|
1065
|
-
name
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1178
|
+
try:
|
|
1179
|
+
_engines_suspend(provider, name, type)
|
|
1180
|
+
except Exception as e:
|
|
1181
|
+
return exit_with_handled_exception("Error suspending engine", e)
|
|
1182
|
+
divider()
|
|
1183
|
+
|
|
1184
|
+
def _engines_suspend(provider: ResourcesBase, name, type) -> None:
|
|
1185
|
+
type_was_omitted = type is None
|
|
1186
|
+
if name and type is None:
|
|
1187
|
+
type = EngineType.LOGIC
|
|
1188
|
+
|
|
1189
|
+
if type and not EngineType.is_valid(type):
|
|
1190
|
+
exit_with_error(f"[yellow]Invalid engine type '{type}'. Valid types: LOGIC, SOLVER, ML")
|
|
1191
|
+
|
|
1192
|
+
try:
|
|
1193
|
+
result = select_engine_with_state_filter(
|
|
1194
|
+
provider,
|
|
1195
|
+
name,
|
|
1196
|
+
type,
|
|
1197
|
+
"READY",
|
|
1198
|
+
"Select a ready engine to suspend:",
|
|
1199
|
+
"Select a ready engine to suspend:",
|
|
1200
|
+
"[yellow]No ready engines found",
|
|
1201
|
+
f"[yellow]No ready engines found with name '{name}'" if name else "[yellow]No ready engines found",
|
|
1069
1202
|
)
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
if not engine:
|
|
1076
|
-
exit_with_error(f"[yellow]Engine '{name}' not found")
|
|
1077
|
-
if engine and engine.get("state") != "READY":
|
|
1078
|
-
exit_with_error(f"[yellow]Engine '{name}' must be in 'READY' state to be suspended")
|
|
1079
|
-
except Exception as e:
|
|
1080
|
-
exit_with_error(f"[yellow]Error fetching engine: {e}")
|
|
1203
|
+
except Exception as e:
|
|
1204
|
+
return exit_with_handled_exception("Error fetching engines", e)
|
|
1205
|
+
if result is None:
|
|
1206
|
+
return
|
|
1207
|
+
name, type = result
|
|
1081
1208
|
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1209
|
+
engine_type = ensure_engine_type_for_snowflake(
|
|
1210
|
+
provider,
|
|
1211
|
+
name,
|
|
1212
|
+
type,
|
|
1213
|
+
f"[yellow]Engine type is required for engine '{name}'. Please specify --type or select from the list.",
|
|
1214
|
+
)
|
|
1215
|
+
|
|
1216
|
+
operation_msg, success_msg = build_engine_operation_messages(provider, name, engine_type, "Suspending", "Suspended")
|
|
1217
|
+
try:
|
|
1218
|
+
with Spinner(operation_msg, success_msg):
|
|
1219
|
+
provider.suspend_engine(name, engine_type)
|
|
1220
|
+
except Exception as e:
|
|
1221
|
+
error_str = str(e).lower()
|
|
1222
|
+
if "engine not found" in error_str or ("not found" in error_str and "engine" in error_str):
|
|
1223
|
+
# If the user omitted --type and we defaulted to LOGIC, try to hint at other types.
|
|
1224
|
+
if type_was_omitted and engine_type == EngineType.LOGIC:
|
|
1225
|
+
available_types = _get_engine_types_for_name(provider, name)
|
|
1226
|
+
if available_types and EngineType.LOGIC not in available_types:
|
|
1227
|
+
_exit_engine_requires_type(name, available_types, "engines:suspend")
|
|
1228
|
+
engine_type_label = EngineType.get_label(engine_type) if EngineType.is_valid(engine_type) else engine_type
|
|
1229
|
+
exc = Exception(f"Engine '{name}' with type '{engine_type_label} ({engine_type})' not found.")
|
|
1230
|
+
else:
|
|
1231
|
+
exc = e
|
|
1232
|
+
exit_with_handled_exception("Error suspending engine", exc)
|
|
1089
1233
|
|
|
1090
1234
|
#--------------------------------------------------
|
|
1091
1235
|
# Engine alter engine pool
|
|
@@ -1105,7 +1249,10 @@ def engines_alter_pool(size:str|None=None, min:int|None=None, max:int|None=None)
|
|
|
1105
1249
|
|
|
1106
1250
|
# Ask for engine size if not provided
|
|
1107
1251
|
if not size:
|
|
1108
|
-
|
|
1252
|
+
try:
|
|
1253
|
+
valid_sizes = provider.get_engine_sizes()
|
|
1254
|
+
except Exception as e:
|
|
1255
|
+
return exit_with_handled_exception("Error fetching engine sizes", e)
|
|
1109
1256
|
size = controls.fuzzy(
|
|
1110
1257
|
"Select engine size:",
|
|
1111
1258
|
choices=valid_sizes,
|
|
@@ -1113,7 +1260,10 @@ def engines_alter_pool(size:str|None=None, min:int|None=None, max:int|None=None)
|
|
|
1113
1260
|
)
|
|
1114
1261
|
|
|
1115
1262
|
# Validate engine size
|
|
1116
|
-
|
|
1263
|
+
try:
|
|
1264
|
+
valid_sizes = provider.get_engine_sizes()
|
|
1265
|
+
except Exception as e:
|
|
1266
|
+
return exit_with_handled_exception("Error fetching engine sizes", e)
|
|
1117
1267
|
if size not in valid_sizes:
|
|
1118
1268
|
exit_with_error(f"Invalid engine size '{size}'. Valid sizes: {valid_sizes}")
|
|
1119
1269
|
|
|
@@ -1148,9 +1298,12 @@ def engines_alter_pool(size:str|None=None, min:int|None=None, max:int|None=None)
|
|
|
1148
1298
|
rich.print()
|
|
1149
1299
|
|
|
1150
1300
|
# Call the API method
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1301
|
+
try:
|
|
1302
|
+
with Spinner("Altering engine pool", "Engine pool altered"):
|
|
1303
|
+
# Type cast to ensure type checker recognizes the method
|
|
1304
|
+
cast(ResourcesBase, provider).alter_engine_pool(size, min, max)
|
|
1305
|
+
except Exception as e:
|
|
1306
|
+
return exit_with_handled_exception("Error altering engine pool", e)
|
|
1154
1307
|
divider()
|
|
1155
1308
|
|
|
1156
1309
|
#--------------------------------------------------
|
|
@@ -1319,7 +1472,7 @@ def parse_source(provider: ResourcesBase, raw: str) -> ImportSource:
|
|
|
1319
1472
|
|
|
1320
1473
|
@supports_platform("snowflake")
|
|
1321
1474
|
@cli.command(name="imports:setup", help="Modify and view imports setup")
|
|
1322
|
-
@click.option("--engine_size", help="Engine size")
|
|
1475
|
+
@click.option("--engine-size", "--engine_size", help="Engine size")
|
|
1323
1476
|
@click.option("--resume", help="Resume imports", is_flag=True)
|
|
1324
1477
|
@click.option("--suspend", help="Suspend imports", is_flag=True)
|
|
1325
1478
|
def imports_setup(engine_size:str|None=None, resume:bool=False, suspend:bool=False):
|
|
@@ -1380,7 +1533,7 @@ def imports_setup(engine_size:str|None=None, resume:bool=False, suspend:bool=Fal
|
|
|
1380
1533
|
lambda k, v: {k: str(v), "style": "red"} if k == "enabled" and not v else format_row(k, v)
|
|
1381
1534
|
)
|
|
1382
1535
|
except Exception as e:
|
|
1383
|
-
|
|
1536
|
+
exit_with_handled_exception("Error fetching imports setup", e)
|
|
1384
1537
|
divider()
|
|
1385
1538
|
|
|
1386
1539
|
|
|
@@ -1496,7 +1649,7 @@ def imports_wait(source: List[str], model: str):
|
|
|
1496
1649
|
try:
|
|
1497
1650
|
models = [model["name"] for model in provider.list_graphs()]
|
|
1498
1651
|
except Exception as e:
|
|
1499
|
-
return
|
|
1652
|
+
return exit_with_handled_exception("Error fetching models", e)
|
|
1500
1653
|
if not models:
|
|
1501
1654
|
return exit_with_error("[yellow]No models found")
|
|
1502
1655
|
rich.print()
|
|
@@ -1592,7 +1745,7 @@ def imports_stream(
|
|
|
1592
1745
|
try:
|
|
1593
1746
|
models = ["[CREATE MODEL]"] + [model["name"] for model in provider.list_graphs()]
|
|
1594
1747
|
except Exception as e:
|
|
1595
|
-
return
|
|
1748
|
+
return exit_with_handled_exception("Error fetching models", e)
|
|
1596
1749
|
|
|
1597
1750
|
rich.print()
|
|
1598
1751
|
model = controls.fuzzy("Select a model:", models)
|
|
@@ -1614,7 +1767,7 @@ def imports_stream(
|
|
|
1614
1767
|
else:
|
|
1615
1768
|
sources = [parse_source(provider, source_) for source_ in source]
|
|
1616
1769
|
except Exception as e:
|
|
1617
|
-
return
|
|
1770
|
+
return exit_with_handled_exception("Error", e)
|
|
1618
1771
|
|
|
1619
1772
|
for import_source in sources:
|
|
1620
1773
|
try:
|
|
@@ -1640,7 +1793,7 @@ def imports_stream(
|
|
|
1640
1793
|
exit_with_error("\n\n[yellow]Stream engine not found. Please use '[cyan]rai imports:setup[/cyan]' to set up imports.")
|
|
1641
1794
|
else:
|
|
1642
1795
|
rich.print()
|
|
1643
|
-
|
|
1796
|
+
exit_with_handled_exception("Error creating stream", e)
|
|
1644
1797
|
wait = not no_wait
|
|
1645
1798
|
if wait:
|
|
1646
1799
|
poll_imports(provider, [source.name for source in sources], model, no_wait_notice=True)
|
|
@@ -1673,7 +1826,7 @@ def imports_snapshot(source:str|None, model:str|None, name:str|None, type:str|No
|
|
|
1673
1826
|
try:
|
|
1674
1827
|
models = [model["name"] for model in provider.list_graphs()]
|
|
1675
1828
|
except Exception as e:
|
|
1676
|
-
return
|
|
1829
|
+
return exit_with_handled_exception("Error fetching models", e)
|
|
1677
1830
|
if len(models) == 0:
|
|
1678
1831
|
exit_with_error("[yellow]No models found")
|
|
1679
1832
|
rich.print()
|
|
@@ -1695,7 +1848,7 @@ def imports_snapshot(source:str|None, model:str|None, name:str|None, type:str|No
|
|
|
1695
1848
|
exit_with_error("\n[yellow]Error creating snapshot, aborting.")
|
|
1696
1849
|
|
|
1697
1850
|
except Exception as e:
|
|
1698
|
-
|
|
1851
|
+
exit_with_handled_exception("Error creating snapshot", e)
|
|
1699
1852
|
divider()
|
|
1700
1853
|
|
|
1701
1854
|
#--------------------------------------------------
|
|
@@ -1715,7 +1868,7 @@ def imports_delete(object, model, force):
|
|
|
1715
1868
|
try:
|
|
1716
1869
|
models = [model["name"] for model in provider.list_graphs()]
|
|
1717
1870
|
except Exception as e:
|
|
1718
|
-
return
|
|
1871
|
+
return exit_with_handled_exception("Error fetching models", e)
|
|
1719
1872
|
if len(models) == 0:
|
|
1720
1873
|
rich.print()
|
|
1721
1874
|
exit_with_error("[yellow]No models found")
|
|
@@ -1727,7 +1880,7 @@ def imports_delete(object, model, force):
|
|
|
1727
1880
|
try:
|
|
1728
1881
|
imports = provider.list_imports(model=model)
|
|
1729
1882
|
except Exception as e:
|
|
1730
|
-
return
|
|
1883
|
+
return exit_with_handled_exception("Error fetching imports", e)
|
|
1731
1884
|
|
|
1732
1885
|
if not imports and not force:
|
|
1733
1886
|
rich.print()
|
|
@@ -1751,7 +1904,7 @@ def imports_delete(object, model, force):
|
|
|
1751
1904
|
try:
|
|
1752
1905
|
provider.delete_import(object, model, force)
|
|
1753
1906
|
except Exception as e:
|
|
1754
|
-
|
|
1907
|
+
exit_with_handled_exception("Error deleting import", e)
|
|
1755
1908
|
divider()
|
|
1756
1909
|
|
|
1757
1910
|
#--------------------------------------------------
|
|
@@ -1771,7 +1924,7 @@ def exports_list(model):
|
|
|
1771
1924
|
try:
|
|
1772
1925
|
models = [model["name"] for model in provider.list_graphs()]
|
|
1773
1926
|
except Exception as e:
|
|
1774
|
-
return
|
|
1927
|
+
return exit_with_handled_exception("Error fetching models", e)
|
|
1775
1928
|
if len(models) == 0:
|
|
1776
1929
|
return exit_with_error("[yellow]No models found")
|
|
1777
1930
|
rich.print()
|
|
@@ -1782,7 +1935,7 @@ def exports_list(model):
|
|
|
1782
1935
|
try:
|
|
1783
1936
|
exports = provider.list_exports(model, "")
|
|
1784
1937
|
except Exception as e:
|
|
1785
|
-
return
|
|
1938
|
+
return exit_with_handled_exception("Error fetching exports", e)
|
|
1786
1939
|
|
|
1787
1940
|
rich.print()
|
|
1788
1941
|
if len(exports):
|
|
@@ -1813,7 +1966,7 @@ def exports_delete(export, model):
|
|
|
1813
1966
|
try:
|
|
1814
1967
|
models = [model["name"] for model in provider.list_graphs()]
|
|
1815
1968
|
except Exception as e:
|
|
1816
|
-
return
|
|
1969
|
+
return exit_with_handled_exception("Error fetching models", e)
|
|
1817
1970
|
if len(models) == 0:
|
|
1818
1971
|
exit_with_error("[yellow]No models found")
|
|
1819
1972
|
rich.print()
|
|
@@ -1849,7 +2002,7 @@ def transactions_get(id):
|
|
|
1849
2002
|
try:
|
|
1850
2003
|
transaction = provider.get_transaction(id)
|
|
1851
2004
|
except Exception as e:
|
|
1852
|
-
|
|
2005
|
+
exit_with_handled_exception("Error fetching transaction", e)
|
|
1853
2006
|
rich.print()
|
|
1854
2007
|
if transaction:
|
|
1855
2008
|
show_dictionary_table(transaction, format_row)
|
|
@@ -1881,7 +2034,7 @@ def transactions_list(id, state, engine, limit, all_users):
|
|
|
1881
2034
|
)
|
|
1882
2035
|
except Exception as e:
|
|
1883
2036
|
rich.print()
|
|
1884
|
-
return
|
|
2037
|
+
return exit_with_handled_exception("Error fetching transactions", e)
|
|
1885
2038
|
|
|
1886
2039
|
if len(transactions) == 0:
|
|
1887
2040
|
rich.print()
|
|
@@ -1912,7 +2065,7 @@ def transactions_cancel(id, all_users):
|
|
|
1912
2065
|
created_by=cfg.get("user", None),
|
|
1913
2066
|
)
|
|
1914
2067
|
except Exception as e:
|
|
1915
|
-
return
|
|
2068
|
+
return exit_with_handled_exception("Error fetching transactions", e)
|
|
1916
2069
|
|
|
1917
2070
|
if not transactions:
|
|
1918
2071
|
exit_with_error("\n[yellow]No active transactions found")
|