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.
@@ -39,6 +39,7 @@ from ...types import AvailableModel, EngineState, Import, ImportSource, ImportSo
39
39
  from ...config import Config
40
40
  from ...client import Client, ExportParams, ProviderBase, ResourcesBase
41
41
  from ...util import IdentityParser, escape_for_f_string, get_pyrel_version, get_with_retries, poll_with_specified_overhead, safe_json_loads, sanitize_module_name, scrub_exception, wrap_with_request_id, normalize_datetime
42
+ from .engine_service import EngineServiceSQL, EngineType
42
43
  from .util import (
43
44
  collect_error_messages,
44
45
  process_jinja_template,
@@ -64,6 +65,7 @@ from .error_handlers import (
64
65
  ErrorHandler,
65
66
  DuoSecurityErrorHandler,
66
67
  AppMissingErrorHandler,
68
+ AppFunctionMissingErrorHandler,
67
69
  DatabaseErrorsHandler,
68
70
  EngineErrorsHandler,
69
71
  ServiceNotStartedErrorHandler,
@@ -90,16 +92,10 @@ from .engine_state_handlers import (
90
92
  # Constants
91
93
  #--------------------------------------------------
92
94
 
93
- VALID_POOL_STATUS = ["ACTIVE", "IDLE", "SUSPENDED"]
94
95
  # transaction list and get return different fields (duration vs timings)
95
96
  LIST_TXN_SQL_FIELDS = ["id", "database_name", "engine_name", "state", "abort_reason", "read_only","created_by", "created_on", "finished_at", "duration"]
96
97
  GET_TXN_SQL_FIELDS = ["id", "database", "engine", "state", "abort_reason", "read_only","created_by", "created_on", "finished_at", "timings"]
97
98
  VALID_ENGINE_STATES = ["READY", "PENDING"]
98
-
99
- # Cloud-specific engine sizes
100
- INTERNAL_ENGINE_SIZES = ["XS", "S", "M", "L"]
101
- ENGINE_SIZES_AWS = ["HIGHMEM_X64_S", "HIGHMEM_X64_M", "HIGHMEM_X64_L"]
102
- ENGINE_SIZES_AZURE = ["HIGHMEM_X64_S", "HIGHMEM_X64_M", "HIGHMEM_X64_SL"]
103
99
  # Note: ENGINE_ERRORS, ENGINE_NOT_READY_MSGS, DATABASE_ERRORS moved to util.py
104
100
  PYREL_ROOT_DB = 'pyrel_root_db'
105
101
 
@@ -184,11 +180,17 @@ class Resources(ResourcesBase):
184
180
  self._sproc_models = None
185
181
  # Store language for backward compatibility (used by child classes for use_index polling)
186
182
  self.language = language
183
+ # Engine subsystem (composition: keeps engine CRUD isolated from the core Resources class)
184
+ self._engines = EngineServiceSQL(self)
187
185
  # Register error and state handlers
188
186
  self._register_handlers()
189
187
  # Register atexit callback to cancel pending transactions
190
188
  atexit.register(self.cancel_pending_transactions)
191
189
 
190
+ @property
191
+ def engines(self) -> EngineServiceSQL:
192
+ return self._engines
193
+
192
194
  #--------------------------------------------------
193
195
  # Initialization & Properties
194
196
  #--------------------------------------------------
@@ -217,11 +219,12 @@ class Resources(ResourcesBase):
217
219
  return handlers
218
220
  """
219
221
  return [
220
- DuoSecurityErrorHandler(),
221
222
  AppMissingErrorHandler(),
223
+ AppFunctionMissingErrorHandler(),
224
+ ServiceNotStartedErrorHandler(),
225
+ DuoSecurityErrorHandler(),
222
226
  DatabaseErrorsHandler(),
223
227
  EngineErrorsHandler(),
224
- ServiceNotStartedErrorHandler(),
225
228
  TransactionAbortedErrorHandler(),
226
229
  ]
227
230
 
@@ -613,7 +616,7 @@ class Resources(ResourcesBase):
613
616
 
614
617
  # Validate engine size
615
618
  if engine_size:
616
- is_size_valid, sizes = self.validate_engine_size(engine_size)
619
+ is_size_valid, sizes = self._engines.validate_engine_size(engine_size)
617
620
  if not is_size_valid:
618
621
  error_msg = f"Invalid engine size '{engine_size}'. Valid sizes are: {', '.join(sizes)}"
619
622
  if use_default_size:
@@ -703,67 +706,27 @@ class Resources(ResourcesBase):
703
706
  if not handled:
704
707
  raise EngineProvisioningFailed(engine_name, error) from error
705
708
 
706
- def validate_engine_size(self, size: str) -> Tuple[bool, List[str]]:
707
- if size is not None:
708
- sizes = self.get_engine_sizes()
709
- if size not in sizes:
710
- return False, sizes
711
- return True, []
712
-
713
709
  def get_engine_sizes(self, cloud_provider: str|None=None):
714
- sizes = []
715
- if cloud_provider is None:
716
- cloud_provider = self.get_cloud_provider()
717
- if cloud_provider == 'azure':
718
- sizes = ENGINE_SIZES_AZURE
719
- else:
720
- sizes = ENGINE_SIZES_AWS
721
- if self.config.show_all_engine_sizes():
722
- return INTERNAL_ENGINE_SIZES + sizes
723
- else:
724
- return sizes
710
+ return self._engines.get_engine_sizes(cloud_provider=cloud_provider)
725
711
 
726
- def list_engines(self, state: str | None = None):
727
- where_clause = f"WHERE STATUS = '{state.upper()}'" if state else ""
728
- statement = f"SELECT NAME, ID, SIZE, STATUS, CREATED_BY, CREATED_ON, UPDATED_ON FROM {APP_NAME}.api.engines {where_clause} ORDER BY NAME ASC;"
729
- results = self._exec(statement)
730
- if not results:
731
- return []
732
- return [
733
- {
734
- "name": row["NAME"],
735
- "id": row["ID"],
736
- "size": row["SIZE"],
737
- "state": row["STATUS"], # callers are expecting 'state'
738
- "created_by": row["CREATED_BY"],
739
- "created_on": row["CREATED_ON"],
740
- "updated_on": row["UPDATED_ON"],
741
- }
742
- for row in results
743
- ]
744
-
745
- def get_engine(self, name: str):
746
- results = self._exec(
747
- f"SELECT NAME, ID, SIZE, STATUS, CREATED_BY, CREATED_ON, UPDATED_ON, VERSION, AUTO_SUSPEND_MINS, SUSPENDS_AT FROM {APP_NAME}.api.engines WHERE NAME='{name}';"
712
+ def list_engines(
713
+ self,
714
+ state: str | None = None,
715
+ name: str | None = None,
716
+ type: str | None = None,
717
+ size: str | None = None,
718
+ created_by: str | None = None,
719
+ ):
720
+ return self._engines.list_engines(
721
+ state=state,
722
+ name=name,
723
+ type=type,
724
+ size=size,
725
+ created_by=created_by,
748
726
  )
749
- if not results:
750
- return None
751
- engine = results[0]
752
- if not engine:
753
- return None
754
- engine_state: EngineState = {
755
- "name": engine["NAME"],
756
- "id": engine["ID"],
757
- "size": engine["SIZE"],
758
- "state": engine["STATUS"], # callers are expecting 'state'
759
- "created_by": engine["CREATED_BY"],
760
- "created_on": engine["CREATED_ON"],
761
- "updated_on": engine["UPDATED_ON"],
762
- "version": engine["VERSION"],
763
- "auto_suspend": engine["AUTO_SUSPEND_MINS"],
764
- "suspends_at": engine["SUSPENDS_AT"]
765
- }
766
- return engine_state
727
+
728
+ def get_engine(self, name: str, type: str):
729
+ return self._engines.get_engine(name, type)
767
730
 
768
731
  def get_default_engine_name(self) -> str:
769
732
  if self.config.get("engine_name", None) is not None:
@@ -784,60 +747,82 @@ Otherwise, remove it from your '{profile}' configuration profile.
784
747
  def is_valid_engine_state(self, name:str):
785
748
  return name in VALID_ENGINE_STATES
786
749
 
750
+ # Can be overridden by subclasses (e.g. DirectAccessResources)
787
751
  def _create_engine(
788
752
  self,
789
753
  name: str,
754
+ type: str = EngineType.LOGIC,
790
755
  size: str | None = None,
791
756
  auto_suspend_mins: int | None= None,
792
757
  is_async: bool = False,
793
758
  headers: Dict | None = None,
759
+ settings: Dict[str, Any] | None = None,
794
760
  ):
795
- api = "create_engine_async" if is_async else "create_engine"
796
- if size is None:
797
- size = self.config.get_default_engine_size()
798
- # if auto_suspend_mins is None, get the default value from the config
799
- if auto_suspend_mins is None:
800
- auto_suspend_mins = self.config.get_default_auto_suspend_mins()
801
- try:
802
- headers = debugging.gen_current_propagation_headers()
803
- with debugging.span(api, name=name, size=size, auto_suspend_mins=auto_suspend_mins):
804
- # check in case the config default is missing
805
- if auto_suspend_mins is None:
806
- self._exec(f"call {APP_NAME}.api.{api}('{name}', '{size}', null, {headers});")
807
- else:
808
- self._exec(f"call {APP_NAME}.api.{api}('{name}', '{size}', PARSE_JSON('{{\"auto_suspend_mins\": {auto_suspend_mins}}}'), {headers});")
809
- except Exception as e:
810
- raise EngineProvisioningFailed(name, e) from e
761
+ return self._engines._create_engine(
762
+ name=name,
763
+ type=type,
764
+ size=size,
765
+ auto_suspend_mins=auto_suspend_mins,
766
+ is_async=is_async,
767
+ headers=headers,
768
+ settings=settings,
769
+ )
811
770
 
812
- def create_engine(self, name:str, size:str|None=None, auto_suspend_mins:int|None=None, headers: Dict | None = None):
813
- self._create_engine(name, size, auto_suspend_mins, headers=headers)
771
+ def create_engine(
772
+ self,
773
+ name: str,
774
+ type: str | None = None,
775
+ size: str | None = None,
776
+ auto_suspend_mins: int | None = None,
777
+ headers: Dict | None = None,
778
+ settings: Dict[str, Any] | None = None,
779
+ ):
780
+ if type is None:
781
+ type = EngineType.LOGIC
782
+ # Route through _create_engine so subclasses (e.g. DirectAccessResources)
783
+ # can override engine creation behavior.
784
+ return self._create_engine(
785
+ name=name,
786
+ type=type,
787
+ size=size,
788
+ auto_suspend_mins=auto_suspend_mins,
789
+ is_async=False,
790
+ headers=headers,
791
+ settings=settings,
792
+ )
814
793
 
815
- def create_engine_async(self, name:str, size:str|None=None, auto_suspend_mins:int|None=None):
816
- self._create_engine(name, size, auto_suspend_mins, True)
794
+ def create_engine_async(
795
+ self,
796
+ name: str,
797
+ type: str = EngineType.LOGIC,
798
+ size: str | None = None,
799
+ auto_suspend_mins: int | None = None,
800
+ ):
801
+ # Route through _create_engine so subclasses (e.g. DirectAccessResources)
802
+ # can override async engine creation behavior.
803
+ return self._create_engine(
804
+ name=name,
805
+ type=type,
806
+ size=size,
807
+ auto_suspend_mins=auto_suspend_mins,
808
+ is_async=True,
809
+ )
817
810
 
818
- def delete_engine(self, name:str, force:bool = False, headers: Dict | None = None):
819
- request_headers = debugging.add_current_propagation_headers(headers)
820
- self._exec(f"call {APP_NAME}.api.delete_engine('{name}', {force},{request_headers});")
811
+ def delete_engine(self, name: str, type: str):
812
+ return self._engines.delete_engine(name, type)
821
813
 
822
- def suspend_engine(self, name:str):
823
- self._exec(f"call {APP_NAME}.api.suspend_engine('{name}');")
814
+ def suspend_engine(self, name: str, type: str | None = None):
815
+ return self._engines.suspend_engine(name, type)
824
816
 
825
- def resume_engine(self, name:str, headers: Dict | None = None) -> Dict:
826
- request_headers = debugging.add_current_propagation_headers(headers)
827
- self._exec(f"call {APP_NAME}.api.resume_engine('{name}',{request_headers});")
828
- # returning empty dict to match the expected return type
829
- return {}
817
+ def resume_engine(self, name: str, type: str | None = None, headers: Dict | None = None) -> Dict:
818
+ return self._engines.resume_engine(name, type=type, headers=headers)
830
819
 
831
- def resume_engine_async(self, name:str, headers: Dict | None = None) -> Dict:
832
- if headers is None:
833
- headers = {}
834
- self._exec(f"call {APP_NAME}.api.resume_engine_async('{name}',{headers});")
835
- # returning empty dict to match the expected return type
836
- return {}
820
+ def resume_engine_async(self, name: str, type: str | None = None, headers: Dict | None = None) -> Dict:
821
+ return self._engines.resume_engine_async(name, type=type, headers=headers)
837
822
 
838
823
  def alter_engine_pool(self, size:str|None=None, mins:int|None=None, maxs:int|None=None):
839
824
  """Alter engine pool node limits for Snowflake."""
840
- self._exec(f"call {APP_NAME}.api.alter_engine_pool_node_limits('{size}', {mins}, {maxs});")
825
+ return self._engines.alter_engine_pool(size=size, mins=mins, maxs=maxs)
841
826
 
842
827
  #--------------------------------------------------
843
828
  # Graphs
@@ -1871,11 +1856,17 @@ Otherwise, remove it from your '{profile}' configuration profile.
1871
1856
  assert isinstance(user, str), f"current_user() must return a string, not {type(user)}"
1872
1857
  return _sanitize_user_name(user)
1873
1858
 
1874
- def is_engine_ready(self, engine_name: str):
1875
- engine = self.get_engine(engine_name)
1859
+ def is_engine_ready(self, engine_name: str, type: str = EngineType.LOGIC):
1860
+ engine = self.get_engine(engine_name, type)
1876
1861
  return engine and engine["state"] == "READY"
1877
1862
 
1878
- def auto_create_engine(self, name: str | None = None, size: str | None = None, headers: Dict | None = None):
1863
+ def auto_create_engine(
1864
+ self,
1865
+ name: str | None = None,
1866
+ type: str = EngineType.LOGIC,
1867
+ size: str | None = None,
1868
+ headers: Dict | None = None,
1869
+ ):
1879
1870
  """Synchronously create/ensure an engine is ready, blocking until ready."""
1880
1871
  with debugging.span("auto_create_engine", active=self._active_engine) as span:
1881
1872
  active = self._get_active_engine()
@@ -1883,18 +1874,19 @@ Otherwise, remove it from your '{profile}' configuration profile.
1883
1874
  return active
1884
1875
 
1885
1876
  # Resolve and validate parameters
1886
- engine_name, engine_size = self._prepare_engine_params(name, size)
1877
+ name, size = self._prepare_engine_params(name, size)
1887
1878
 
1888
1879
  try:
1889
1880
  # Get current engine state
1890
- engine = self.get_engine(engine_name)
1881
+ engine = self.get_engine(name, type)
1891
1882
  if engine:
1892
1883
  span.update(cast(dict, engine))
1893
1884
 
1894
1885
  # Create context for state handling
1895
1886
  context = EngineContext(
1896
- engine_name=engine_name,
1897
- engine_size=engine_size,
1887
+ name=name,
1888
+ size=size,
1889
+ type=type,
1898
1890
  headers=headers,
1899
1891
  requested_size=size,
1900
1892
  span=span,
@@ -1904,12 +1896,14 @@ Otherwise, remove it from your '{profile}' configuration profile.
1904
1896
  self._process_engine_state(engine, context, self._sync_engine_state_handlers)
1905
1897
 
1906
1898
  except Exception as e:
1907
- self._handle_engine_creation_errors(e, engine_name)
1899
+ self._handle_engine_creation_errors(e, name)
1908
1900
 
1909
- return engine_name
1901
+ return name
1910
1902
 
1911
- def auto_create_engine_async(self, name: str | None = None):
1903
+ def auto_create_engine_async(self, name: str | None = None, type: str | None = None):
1912
1904
  """Asynchronously create/ensure an engine, returns immediately."""
1905
+ if type is None:
1906
+ type = EngineType.LOGIC
1913
1907
  active = self._get_active_engine()
1914
1908
  if active and (active == name or name is None):
1915
1909
  return active
@@ -1920,16 +1914,17 @@ Otherwise, remove it from your '{profile}' configuration profile.
1920
1914
  ) as spinner:
1921
1915
  with debugging.span("auto_create_engine_async", active=self._active_engine):
1922
1916
  # Resolve and validate parameters (use_default_size=True for async)
1923
- engine_name, engine_size = self._prepare_engine_params(name, None, use_default_size=True)
1917
+ name, size = self._prepare_engine_params(name, None, use_default_size=True)
1924
1918
 
1925
1919
  try:
1926
1920
  # Get current engine state
1927
- engine = self.get_engine(engine_name)
1921
+ engine = self.get_engine(name, type)
1928
1922
 
1929
1923
  # Create context for state handling
1930
1924
  context = EngineContext(
1931
- engine_name=engine_name,
1932
- engine_size=engine_size,
1925
+ name=name,
1926
+ size=size,
1927
+ type=type,
1933
1928
  headers=None,
1934
1929
  requested_size=None,
1935
1930
  spinner=spinner,
@@ -1940,11 +1935,11 @@ Otherwise, remove it from your '{profile}' configuration profile.
1940
1935
 
1941
1936
  except Exception as e:
1942
1937
  spinner.update_messages({
1943
- "finished_message": f"Failed to create engine {engine_name}",
1938
+ "finished_message": f"Failed to create engine {name}",
1944
1939
  })
1945
- self._handle_engine_creation_errors(e, engine_name, preserve_rai_exception=True)
1940
+ self._handle_engine_creation_errors(e, name, preserve_rai_exception=True)
1946
1941
 
1947
- return engine_name
1942
+ return name
1948
1943
 
1949
1944
  #--------------------------------------------------
1950
1945
  # Exec
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
  from abc import ABC
3
3
  from datetime import datetime
4
4
  from typing import Any, Optional, TypedDict
5
+ from typing_extensions import NotRequired
5
6
  from pathlib import Path
6
7
  from urllib.parse import urlparse
7
8
 
@@ -27,6 +28,7 @@ class AvailableModel(TypedDict):
27
28
 
28
29
  class EngineState(TypedDict):
29
30
  name: str
31
+ type: str
30
32
  id: str
31
33
  size: str
32
34
  state: str
@@ -38,6 +40,9 @@ class EngineState(TypedDict):
38
40
  auto_suspend: int|None
39
41
  suspends_at: datetime|None
40
42
 
43
+ # Optional JSON settings (engine configuration)
44
+ settings: NotRequired[dict | None]
45
+
41
46
  class SourceInfo(TypedDict, total=False):
42
47
  type: str|None
43
48
  state: str
relationalai/errors.py CHANGED
@@ -1780,7 +1780,7 @@ class SnowflakeRaiAppNotStarted(RAIException):
1780
1780
  def __init__(self, app_name: str):
1781
1781
  self.app_name = app_name
1782
1782
  self.message = f"The RelationalAI app '{app_name}' isn't started."
1783
- self.name = "The RelationalAI application not started"
1783
+ self.name = "The RelationalAI application isn't running"
1784
1784
  self.content = self.format_message()
1785
1785
 
1786
1786
  super().__init__(self.message, self.name, self.content)
@@ -1361,6 +1361,9 @@ class Replacer(visitor.Rewriter):
1361
1361
  # Typer pass
1362
1362
  #--------------------------------------------------
1363
1363
 
1364
+ # global flag to suppress type errors from being printed
1365
+ SUPPRESS_TYPE_ERRORS = False
1366
+
1364
1367
  class InferTypes(compiler.Pass):
1365
1368
  def __init__(self):
1366
1369
  super().__init__()
@@ -1389,7 +1392,8 @@ class InferTypes(compiler.Pass):
1389
1392
  with debugging.span("type.replace"):
1390
1393
  final = Replacer(w.net).walk(model)
1391
1394
 
1392
- for err in w.net.errors:
1393
- rich.print(str(err), file=sys.stderr)
1395
+ if not SUPPRESS_TYPE_ERRORS:
1396
+ for err in w.net.errors:
1397
+ rich.print(str(err), file=sys.stderr)
1394
1398
 
1395
1399
  return final