relationalai 0.12.12__py3-none-any.whl → 0.12.13__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.
@@ -441,7 +441,8 @@ class Resources(ResourcesBase):
441
441
  code: str,
442
442
  params: List[Any] | Any | None = None,
443
443
  raw: bool = False,
444
- help: bool = True
444
+ help: bool = True,
445
+ skip_engine_db_error_retry: bool = False
445
446
  ) -> Any:
446
447
  # print(f"\n--- sql---\n{code}\n--- end sql---\n")
447
448
  if not self._session:
@@ -467,7 +468,8 @@ class Resources(ResourcesBase):
467
468
  if re.search(f"database '{rai_app}' does not exist or not authorized.".lower(), orig_message):
468
469
  exception = SnowflakeAppMissingException(rai_app, current_role)
469
470
  raise exception from None
470
- if _is_engine_issue(orig_message) or _is_database_issue(orig_message):
471
+ # skip initializing the index if the query is a user transaction. exec_raw/exec_lqp will handle that case with the correct request headers.
472
+ if (_is_engine_issue(orig_message) or _is_database_issue(orig_message)) and not skip_engine_db_error_retry:
471
473
  try:
472
474
  self._poll_use_index(
473
475
  app_name=self.get_app_name(),
@@ -1609,9 +1611,11 @@ Otherwise, remove it from your '{profile}' configuration profile.
1609
1611
  sql_string = f"CALL {APP_NAME}.api.exec_async_v2('{database}','{engine}', ?, {inputs}, {readonly}, {nowait_durable}, '{language}', {query_timeout_mins}, {request_headers});"
1610
1612
  else:
1611
1613
  sql_string = f"CALL {APP_NAME}.api.exec_async_v2('{database}','{engine}', ?, {inputs}, {readonly}, {nowait_durable}, '{language}', {request_headers});"
1614
+ # Don't let exec setup GI on failure, exec_raw and exec_lqp will do that and add the correct headers.
1612
1615
  response = self._exec(
1613
1616
  sql_string,
1614
1617
  raw_code,
1618
+ skip_engine_db_error_retry=True,
1615
1619
  )
1616
1620
  if not response:
1617
1621
  raise Exception("Failed to create transaction")
@@ -1629,6 +1633,7 @@ Otherwise, remove it from your '{profile}' configuration profile.
1629
1633
  bypass_index=False,
1630
1634
  language: str = "rel",
1631
1635
  query_timeout_mins: int | None = None,
1636
+ gi_setup_skipped: bool = False,
1632
1637
  ):
1633
1638
  if inputs is None:
1634
1639
  inputs = {}
@@ -1638,6 +1643,8 @@ Otherwise, remove it from your '{profile}' configuration profile.
1638
1643
  with debugging.span("transaction", **query_attrs_dict) as txn_span:
1639
1644
  with debugging.span("create_v2", **query_attrs_dict) as create_span:
1640
1645
  request_headers['user-agent'] = get_pyrel_version(self.generation)
1646
+ request_headers['gi_setup_skipped'] = str(gi_setup_skipped)
1647
+ request_headers['pyrel_program_id'] = debugging.get_program_span_id() or ""
1641
1648
  response = self._exec_rai_app(
1642
1649
  database=database,
1643
1650
  engine=engine,
@@ -1897,26 +1904,29 @@ Otherwise, remove it from your '{profile}' configuration profile.
1897
1904
  # Exec
1898
1905
  #--------------------------------------------------
1899
1906
 
1900
- def exec_lqp(
1907
+ def _exec_with_gi_retry(
1901
1908
  self,
1902
1909
  database: str,
1903
1910
  engine: str | None,
1904
- raw_code: bytes,
1905
- readonly=True,
1906
- *,
1907
- inputs: Dict | None = None,
1908
- nowait_durable=False,
1909
- headers: Dict | None = None,
1910
- bypass_index=False,
1911
- query_timeout_mins: int | None = None,
1911
+ raw_code: str,
1912
+ inputs: Dict | None,
1913
+ readonly: bool,
1914
+ nowait_durable: bool,
1915
+ headers: Dict | None,
1916
+ bypass_index: bool,
1917
+ language: str,
1918
+ query_timeout_mins: int | None,
1912
1919
  ):
1913
- raw_code_b64 = base64.b64encode(raw_code).decode("utf-8")
1920
+ """Execute with graph index retry logic.
1914
1921
 
1922
+ Attempts execution with gi_setup_skipped=True first. If an engine or database
1923
+ issue occurs, polls use_index and retries with gi_setup_skipped=False.
1924
+ """
1915
1925
  try:
1916
1926
  return self._exec_async_v2(
1917
- database, engine, raw_code_b64, inputs, readonly, nowait_durable,
1918
- headers=headers, bypass_index=bypass_index, language='lqp',
1919
- query_timeout_mins=query_timeout_mins,
1927
+ database, engine, raw_code, inputs, readonly, nowait_durable,
1928
+ headers=headers, bypass_index=bypass_index, language=language,
1929
+ query_timeout_mins=query_timeout_mins, gi_setup_skipped=True,
1920
1930
  )
1921
1931
  except Exception as e:
1922
1932
  err_message = str(e).lower()
@@ -1933,13 +1943,32 @@ Otherwise, remove it from your '{profile}' configuration profile.
1933
1943
  )
1934
1944
 
1935
1945
  return self._exec_async_v2(
1936
- database, engine, raw_code_b64, inputs, readonly, nowait_durable,
1937
- headers=headers, bypass_index=bypass_index, language='lqp',
1938
- query_timeout_mins=query_timeout_mins,
1946
+ database, engine, raw_code, inputs, readonly, nowait_durable,
1947
+ headers=headers, bypass_index=bypass_index, language=language,
1948
+ query_timeout_mins=query_timeout_mins, gi_setup_skipped=False,
1939
1949
  )
1940
1950
  else:
1941
1951
  raise e
1942
1952
 
1953
+ def exec_lqp(
1954
+ self,
1955
+ database: str,
1956
+ engine: str | None,
1957
+ raw_code: bytes,
1958
+ readonly=True,
1959
+ *,
1960
+ inputs: Dict | None = None,
1961
+ nowait_durable=False,
1962
+ headers: Dict | None = None,
1963
+ bypass_index=False,
1964
+ query_timeout_mins: int | None = None,
1965
+ ):
1966
+ raw_code_b64 = base64.b64encode(raw_code).decode("utf-8")
1967
+ return self._exec_with_gi_retry(
1968
+ database, engine, raw_code_b64, inputs, readonly, nowait_durable,
1969
+ headers, bypass_index, 'lqp', query_timeout_mins
1970
+ )
1971
+
1943
1972
 
1944
1973
  def exec_raw(
1945
1974
  self,
@@ -1955,45 +1984,10 @@ Otherwise, remove it from your '{profile}' configuration profile.
1955
1984
  query_timeout_mins: int | None = None,
1956
1985
  ):
1957
1986
  raw_code = raw_code.replace("'", "\\'")
1958
-
1959
- try:
1960
- return self._exec_async_v2(
1961
- database,
1962
- engine,
1963
- raw_code,
1964
- inputs,
1965
- readonly,
1966
- nowait_durable,
1967
- headers=headers,
1968
- bypass_index=bypass_index,
1969
- query_timeout_mins=query_timeout_mins,
1970
- )
1971
- except Exception as e:
1972
- err_message = str(e).lower()
1973
- if _is_engine_issue(err_message) or _is_database_issue(err_message):
1974
- engine_name = engine or self.get_default_engine_name()
1975
- engine_size = self.config.get_default_engine_size()
1976
- self._poll_use_index(
1977
- app_name=self.get_app_name(),
1978
- sources=self.sources,
1979
- model=database,
1980
- engine_name=engine_name,
1981
- engine_size=engine_size,
1982
- headers=headers,
1983
- )
1984
- return self._exec_async_v2(
1985
- database,
1986
- engine,
1987
- raw_code,
1988
- inputs,
1989
- readonly,
1990
- nowait_durable,
1991
- headers=headers,
1992
- bypass_index=bypass_index,
1993
- query_timeout_mins=query_timeout_mins,
1994
- )
1995
- else:
1996
- raise e
1987
+ return self._exec_with_gi_retry(
1988
+ database, engine, raw_code, inputs, readonly, nowait_durable,
1989
+ headers, bypass_index, 'rel', query_timeout_mins
1990
+ )
1997
1991
 
1998
1992
 
1999
1993
  def format_results(self, results, task:m.Task|None=None) -> Tuple[DataFrame, List[Any]]:
@@ -3281,6 +3275,7 @@ class DirectAccessResources(Resources):
3281
3275
  path_params: Dict[str, str] | None = None,
3282
3276
  query_params: Dict[str, str] | None = None,
3283
3277
  skip_auto_create: bool = False,
3278
+ skip_engine_db_error_retry: bool = False,
3284
3279
  ) -> requests.Response:
3285
3280
  with debugging.span("direct_access_request"):
3286
3281
  def _send_request():
@@ -3314,8 +3309,8 @@ class DirectAccessResources(Resources):
3314
3309
  message = "" # Not used when we check status_code directly
3315
3310
 
3316
3311
  # fix engine on engine error and retry
3317
- # Skip auto-retry if skip_auto_create is True to avoid recursion
3318
- if (_is_engine_issue(message) and not skip_auto_create) or _is_database_issue(message):
3312
+ # Skip setting up GI if skip_auto_create is True to avoid recursion or skip_engine_db_error_retry is true to let _exec_async_v2 perform the retry with the correct headers.
3313
+ if ((_is_engine_issue(message) and not skip_auto_create) or _is_database_issue(message)) and not skip_engine_db_error_retry:
3319
3314
  engine_name = payload.get("caller_engine_name", "") if payload else ""
3320
3315
  engine_name = engine_name or self.get_default_engine_name()
3321
3316
  engine_size = self.config.get_default_engine_size()
@@ -3340,6 +3335,48 @@ class DirectAccessResources(Resources):
3340
3335
  raise e
3341
3336
  return response
3342
3337
 
3338
+ def _txn_request_with_gi_retry(
3339
+ self,
3340
+ payload: Dict,
3341
+ headers: Dict[str, str],
3342
+ query_params: Dict,
3343
+ engine: Union[str, None],
3344
+ ):
3345
+ """Make request with graph index retry logic.
3346
+
3347
+ Attempts request with gi_setup_skipped=True first. If an engine or database
3348
+ issue occurs, polls use_index and retries with gi_setup_skipped=False.
3349
+ """
3350
+ response = self.request(
3351
+ "create_txn", payload=payload, headers=headers, query_params=query_params, skip_auto_create=True, skip_engine_db_error_retry=True
3352
+ )
3353
+
3354
+ if response.status_code != 200:
3355
+ try:
3356
+ message = response.json().get("message", "")
3357
+ except requests.exceptions.JSONDecodeError:
3358
+ message = ""
3359
+
3360
+ if _is_engine_issue(message) or _is_database_issue(message):
3361
+ engine_name = engine or self.get_default_engine_name()
3362
+ engine_size = self.config.get_default_engine_size()
3363
+ self._poll_use_index(
3364
+ app_name=self.get_app_name(),
3365
+ sources=self.sources,
3366
+ model=self.database,
3367
+ engine_name=engine_name,
3368
+ engine_size=engine_size,
3369
+ headers=headers,
3370
+ )
3371
+ headers['gi_setup_skipped'] = 'False'
3372
+ response = self.request(
3373
+ "create_txn", payload=payload, headers=headers, query_params=query_params, skip_auto_create=True, skip_engine_db_error_retry=True
3374
+ )
3375
+ else:
3376
+ raise ResponseStatusException("Failed to create transaction.", response)
3377
+
3378
+ return response
3379
+
3343
3380
  def _exec_async_v2(
3344
3381
  self,
3345
3382
  database: str,
@@ -3352,6 +3389,7 @@ class DirectAccessResources(Resources):
3352
3389
  bypass_index=False,
3353
3390
  language: str = "rel",
3354
3391
  query_timeout_mins: int | None = None,
3392
+ gi_setup_skipped: bool = False,
3355
3393
  ):
3356
3394
 
3357
3395
  with debugging.span("transaction") as txn_span:
@@ -3374,12 +3412,15 @@ class DirectAccessResources(Resources):
3374
3412
  payload["timeout_mins"] = query_timeout_mins
3375
3413
  query_params={"use_graph_index": str(use_graph_index and not bypass_index)}
3376
3414
 
3377
- response = self.request(
3378
- "create_txn", payload=payload, headers=headers, query_params=query_params,
3379
- )
3415
+ # Add gi_setup_skipped to headers
3416
+ if headers is None:
3417
+ headers = {}
3418
+ headers["gi_setup_skipped"] = str(gi_setup_skipped)
3419
+ headers['pyrel_program_id'] = debugging.get_program_span_id() or ""
3380
3420
 
3381
- if response.status_code != 200:
3382
- raise ResponseStatusException("Failed to create transaction.", response)
3421
+ response = self._txn_request_with_gi_retry(
3422
+ payload, headers, query_params, engine
3423
+ )
3383
3424
 
3384
3425
  artifact_info = {}
3385
3426
  response_content = response.json()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: relationalai
3
- Version: 0.12.12
3
+ Version: 0.12.13
4
4
  Summary: RelationalAI Library and CLI
5
5
  Author-email: RelationalAI <support@relational.ai>
6
6
  License-File: LICENSE
@@ -29,7 +29,7 @@ relationalai/clients/hash_util.py,sha256=pZVR1FX3q4G_19p_r6wpIR2tIM8_WUlfAR7AVZJ
29
29
  relationalai/clients/local.py,sha256=uX1Or2WO0juDuqa6TCCvm3G2ieP6p0PtYp_jfrCMlVc,23577
30
30
  relationalai/clients/profile_polling.py,sha256=pUH7WKH4nYDD0SlQtg3wsWdj0K7qt6nZqUw8jTthCBs,2565
31
31
  relationalai/clients/result_helpers.py,sha256=wDSD02Ngx6W-YQqBIGKnpXD4Ju3pA1e9Nz6ORRI6SRI,17808
32
- relationalai/clients/snowflake.py,sha256=QPJrRolINDkjZb8xyuvaLJIWOEYXdeALLSNqIAAXUEY,166006
32
+ relationalai/clients/snowflake.py,sha256=pB_4t70vTmfGPUJnJpbrrsNH5pojPJyqodsMtUL6b9c,168261
33
33
  relationalai/clients/types.py,sha256=eNo6akcMTbnBFbBbHd5IgVeY-zuAgtXlOs8Bo1SWmVU,2890
34
34
  relationalai/clients/use_index_poller.py,sha256=rrkg35xiHqY0-2dZlPkgixEGENrIrl7bf_2TboX_qew,46794
35
35
  relationalai/clients/util.py,sha256=NJC8fnrWHR01NydwESPSetIHRWf7jQJURYpaWJjmDyE,12311
@@ -442,8 +442,8 @@ frontend/debugger/dist/index.html,sha256=0wIQ1Pm7BclVV1wna6Mj8OmgU73B9rSEGPVX-Wo
442
442
  frontend/debugger/dist/assets/favicon-Dy0ZgA6N.png,sha256=tPXOEhOrM4tJyZVJQVBO_yFgNAlgooY38ZsjyrFstgg,620
443
443
  frontend/debugger/dist/assets/index-Cssla-O7.js,sha256=MxgIGfdKQyBWgufck1xYggQNhW5nj6BPjCF6Wleo-f0,298886
444
444
  frontend/debugger/dist/assets/index-DlHsYx1V.css,sha256=21pZtAjKCcHLFjbjfBQTF6y7QmOic-4FYaKNmwdNZVE,60141
445
- relationalai-0.12.12.dist-info/METADATA,sha256=FraqrzfrL1kptZq9GjRHxJ0y0sfT2bpTuozGwYh7_7k,2563
446
- relationalai-0.12.12.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
447
- relationalai-0.12.12.dist-info/entry_points.txt,sha256=fo_oLFJih3PUgYuHXsk7RnCjBm9cqRNR--ab6DgI6-0,88
448
- relationalai-0.12.12.dist-info/licenses/LICENSE,sha256=pPyTVXFYhirkEW9VsnHIgUjT0Vg8_xsE6olrF5SIgpc,11343
449
- relationalai-0.12.12.dist-info/RECORD,,
445
+ relationalai-0.12.13.dist-info/METADATA,sha256=qiiOC2n_SXtlfEHafFp8hVqY1M2VSzGOl5fqOPt1_ZQ,2563
446
+ relationalai-0.12.13.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
447
+ relationalai-0.12.13.dist-info/entry_points.txt,sha256=fo_oLFJih3PUgYuHXsk7RnCjBm9cqRNR--ab6DgI6-0,88
448
+ relationalai-0.12.13.dist-info/licenses/LICENSE,sha256=pPyTVXFYhirkEW9VsnHIgUjT0Vg8_xsE6olrF5SIgpc,11343
449
+ relationalai-0.12.13.dist-info/RECORD,,