relationalai 0.11.4__py3-none-any.whl → 0.12.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. relationalai/clients/config.py +7 -0
  2. relationalai/clients/direct_access_client.py +113 -0
  3. relationalai/clients/snowflake.py +263 -189
  4. relationalai/clients/types.py +4 -1
  5. relationalai/clients/use_index_poller.py +72 -48
  6. relationalai/clients/util.py +9 -0
  7. relationalai/dsl.py +1 -2
  8. relationalai/early_access/metamodel/rewrite/__init__.py +5 -3
  9. relationalai/early_access/rel/rewrite/__init__.py +1 -1
  10. relationalai/environments/snowbook.py +10 -1
  11. relationalai/errors.py +24 -3
  12. relationalai/semantics/internal/annotations.py +1 -0
  13. relationalai/semantics/internal/internal.py +22 -3
  14. relationalai/semantics/lqp/builtins.py +1 -0
  15. relationalai/semantics/lqp/executor.py +12 -4
  16. relationalai/semantics/lqp/model2lqp.py +1 -0
  17. relationalai/semantics/lqp/passes.py +3 -4
  18. relationalai/semantics/{rel → lqp}/rewrite/__init__.py +6 -0
  19. relationalai/semantics/metamodel/builtins.py +12 -1
  20. relationalai/semantics/metamodel/executor.py +2 -1
  21. relationalai/semantics/metamodel/rewrite/__init__.py +3 -9
  22. relationalai/semantics/metamodel/rewrite/flatten.py +8 -7
  23. relationalai/semantics/reasoners/graph/core.py +1356 -258
  24. relationalai/semantics/rel/builtins.py +5 -1
  25. relationalai/semantics/rel/compiler.py +3 -3
  26. relationalai/semantics/rel/executor.py +20 -11
  27. relationalai/semantics/sql/compiler.py +2 -3
  28. relationalai/semantics/sql/executor/duck_db.py +8 -4
  29. relationalai/semantics/sql/executor/snowflake.py +1 -1
  30. relationalai/tools/cli.py +17 -6
  31. relationalai/tools/cli_controls.py +334 -352
  32. relationalai/tools/constants.py +1 -0
  33. relationalai/tools/query_utils.py +27 -0
  34. relationalai/util/otel_configuration.py +1 -1
  35. {relationalai-0.11.4.dist-info → relationalai-0.12.1.dist-info}/METADATA +5 -4
  36. {relationalai-0.11.4.dist-info → relationalai-0.12.1.dist-info}/RECORD +45 -45
  37. relationalai/semantics/metamodel/rewrite/gc_nodes.py +0 -58
  38. relationalai/semantics/metamodel/rewrite/list_types.py +0 -109
  39. /relationalai/semantics/{rel → lqp}/rewrite/cdc.py +0 -0
  40. /relationalai/semantics/{rel → lqp}/rewrite/extract_common.py +0 -0
  41. /relationalai/semantics/{metamodel → lqp}/rewrite/extract_keys.py +0 -0
  42. /relationalai/semantics/{metamodel → lqp}/rewrite/fd_constraints.py +0 -0
  43. /relationalai/semantics/{rel → lqp}/rewrite/quantify_vars.py +0 -0
  44. /relationalai/semantics/{metamodel → lqp}/rewrite/splinter.py +0 -0
  45. {relationalai-0.11.4.dist-info → relationalai-0.12.1.dist-info}/WHEEL +0 -0
  46. {relationalai-0.11.4.dist-info → relationalai-0.12.1.dist-info}/entry_points.txt +0 -0
  47. {relationalai-0.11.4.dist-info → relationalai-0.12.1.dist-info}/licenses/LICENSE +0 -0
@@ -2,6 +2,7 @@
2
2
  from __future__ import annotations
3
3
  from relationalai.semantics.metamodel import types, factory as f
4
4
  from relationalai.semantics.metamodel.util import OrderedSet
5
+ from relationalai.semantics.metamodel import builtins
5
6
 
6
7
  # Rel Annotations as IR Relations (to be used in IR Annotations)
7
8
 
@@ -28,7 +29,10 @@ inner_loop_non_stratified_annotation = f.annotation(inner_loop_non_stratified, [
28
29
 
29
30
  # collect all supported builtin rel annotations
30
31
  builtin_annotations = OrderedSet.from_iterable([
31
- arrow, no_diagnostics, no_inline, function, inner_loop, inner_loop_non_stratified
32
+ arrow, no_diagnostics, no_inline, function, inner_loop, inner_loop_non_stratified,
33
+ # track annotations on relations do not currently propagate into Rel
34
+ # TODO: from Thiago, ensure annotation goes from the Logical into the proper declaration
35
+ builtins.track,
32
36
  ])
33
37
 
34
38
  builtin_annotation_names = OrderedSet.from_iterable([a.name for a in builtin_annotations])
@@ -6,13 +6,13 @@ from decimal import Decimal as PyDecimal
6
6
  from relationalai.semantics.metamodel import ir, compiler as c, builtins as bt, types, visitor, helpers, factory as f
7
7
  from relationalai.semantics.metamodel.typer import Checker, InferTypes
8
8
  from relationalai.semantics.metamodel.typer.typer import to_base_primitive, to_type, _NON_PARAMETRIC_PRIMITIVES
9
- from relationalai.semantics.metamodel.rewrite import (Flatten, ExtractKeys, FDConstraints, Splinter,
10
- ExtractNestedLogicals, DNFUnionSplitter, DischargeConstraints)
11
9
  from relationalai.semantics.metamodel.visitor import ReadWriteVisitor
12
10
  from relationalai.semantics.metamodel.util import OrderedSet, group_by, NameCache, ordered_set
13
11
 
14
12
  from relationalai.semantics.rel import rel, rel_utils as u, builtins as rel_bt
15
- from relationalai.semantics.rel.rewrite import CDC, QuantifyVars, ExtractCommon
13
+
14
+ from ..metamodel.rewrite import (Flatten, ExtractNestedLogicals, DNFUnionSplitter, DischargeConstraints)
15
+ from ..lqp.rewrite import CDC, ExtractCommon, ExtractKeys, FDConstraints, QuantifyVars, Splinter
16
16
 
17
17
  import math
18
18
 
@@ -18,7 +18,8 @@ from relationalai.clients.snowflake import APP_NAME
18
18
  from relationalai.semantics.metamodel import ir, executor as e, factory as f
19
19
  from relationalai.semantics.rel import Compiler
20
20
  from relationalai.clients.config import Config
21
- from relationalai.tools.constants import USE_DIRECT_ACCESS, Generation
21
+ from relationalai.tools.constants import USE_DIRECT_ACCESS, Generation, QUERY_ATTRIBUTES_HEADER
22
+ from relationalai.tools.query_utils import prepare_metadata_for_headers
22
23
 
23
24
 
24
25
  class RelExecutor(e.Executor):
@@ -60,7 +61,7 @@ class RelExecutor(e.Executor):
60
61
  atexit.register(self._resources.delete_graph, self.database, True)
61
62
  return self._resources
62
63
 
63
- def check_graph_index(self):
64
+ def check_graph_index(self, headers: dict[str, Any] | None = None):
64
65
  # Has to happen first, so self.dry_run is populated.
65
66
  resources = self.resources
66
67
 
@@ -84,7 +85,7 @@ class RelExecutor(e.Executor):
84
85
  assert self.engine is not None
85
86
 
86
87
  with debugging.span("poll_use_index", sources=sources, model=model, engine=engine_name):
87
- resources.poll_use_index(app_name, sources, model, self.engine, engine_size, program_span_id)
88
+ resources.poll_use_index(app_name, sources, model, self.engine, engine_size, program_span_id, headers)
88
89
 
89
90
  def report_errors(self, problems: list[dict[str, Any]], abort_on_error=True):
90
91
  from relationalai import errors
@@ -143,7 +144,7 @@ class RelExecutor(e.Executor):
143
144
  elif len(all_errors) > 1:
144
145
  raise errors.RAIExceptionSet(all_errors)
145
146
 
146
- def _export(self, raw_code: str, dest_fqn: str, actual_cols: list[str], declared_cols: list[str], update:bool):
147
+ def _export(self, raw_code: str, dest_fqn: str, actual_cols: list[str], declared_cols: list[str], update:bool, headers: dict[str, Any] | None = None):
147
148
  _exec = self.resources._exec
148
149
  output_table = "out" + str(uuid.uuid4()).replace("-", "_")
149
150
  txn_id = None
@@ -153,7 +154,7 @@ class RelExecutor(e.Executor):
153
154
  with debugging.span("transaction"):
154
155
  try:
155
156
  with debugging.span("exec_format") as span:
156
- res = _exec(f"call {APP_NAME}.api.exec_into_table(?, ?, ?, ?, ?, ?);", [self.database, self.engine, raw_code, output_table, False, True])
157
+ res = _exec(f"call {APP_NAME}.api.exec_into_table(?, ?, ?, ?, ?, NULL, ?, {headers}, ?, ?);", [self.database, self.engine, raw_code, output_table, False, True, False, None])
157
158
  txn_id = json.loads(res[0]["EXEC_INTO_TABLE"])["rai_transaction_id"]
158
159
  span["txn_id"] = txn_id
159
160
 
@@ -229,14 +230,18 @@ class RelExecutor(e.Executor):
229
230
  else:
230
231
  raise e
231
232
  if txn_id:
232
- artifact_info = self.resources._list_exec_async_artifacts(txn_id)
233
+ artifact_info = self.resources._list_exec_async_artifacts(txn_id, headers=headers)
233
234
  with debugging.span("fetch"):
234
235
  artifacts = self.resources._download_results(artifact_info, txn_id, "ABORTED")
235
236
  return artifacts
236
237
 
237
238
  def execute(self, model: ir.Model, task: ir.Task, format: Literal["pandas", "snowpark"] = "pandas",
238
- result_cols: list[str] | None = None, export_to: Optional[str] = None, update: bool = False) -> Any:
239
- self.check_graph_index()
239
+ result_cols: list[str] | None = None, export_to: Optional[str] = None, update: bool = False, meta: dict[str, Any] | None = None) -> Any:
240
+ # Format meta as headers
241
+ json_meta = prepare_metadata_for_headers(meta)
242
+ headers = {QUERY_ATTRIBUTES_HEADER: json_meta} if json_meta else {}
243
+
244
+ self.check_graph_index(headers)
240
245
  resources= self.resources
241
246
 
242
247
  rules_code = ""
@@ -277,12 +282,16 @@ class RelExecutor(e.Executor):
277
282
 
278
283
  if not export_to:
279
284
  if format == "pandas":
280
- raw_results = resources.exec_raw(self.database, self.engine, full_code, False, nowait_durable=True)
285
+ raw_results = resources.exec_raw(self.database, self.engine, full_code, False, nowait_durable=True, headers=headers)
281
286
  df, errs = result_helpers.format_results(raw_results, None, cols, generation=Generation.QB) # Pass None for task parameter
282
287
  self.report_errors(errs)
288
+ # Rename columns if wide outputs is enabled
289
+ if self.wide_outputs and len(cols) - len(extra_cols) == len(df.columns):
290
+ df.columns = cols[: len(df.columns)]
291
+
283
292
  return self._postprocess_df(self.config, df, extra_cols)
284
293
  elif format == "snowpark":
285
- results, raw = resources.exec_format(self.database, self.engine, full_code, cols, format=format, readonly=False, nowait_durable=True)
294
+ results, raw = resources.exec_format(self.database, self.engine, full_code, cols, format=format, readonly=False, nowait_durable=True, headers=headers)
286
295
  if raw:
287
296
  df, errs = result_helpers.format_results(raw, None, cols, generation=Generation.QB) # Pass None for task parameter
288
297
  self.report_errors(errs)
@@ -296,7 +305,7 @@ class RelExecutor(e.Executor):
296
305
  else:
297
306
  result_cols = [col for col in cols if col not in extra_cols]
298
307
  assert result_cols
299
- raw = self._export(full_code, export_to, cols, result_cols, update)
308
+ raw = self._export(full_code, export_to, cols, result_cols, update, headers)
300
309
  errors = []
301
310
  if raw:
302
311
  dataframe, errors = result_helpers.format_results(raw, None, result_cols, generation=Generation.QB)
@@ -11,7 +11,7 @@ from decimal import Decimal as PyDecimal
11
11
 
12
12
  import math
13
13
 
14
- from relationalai.semantics.metamodel.rewrite import (Flatten, ExtractNestedLogicals, FDConstraints, DNFUnionSplitter,
14
+ from relationalai.semantics.metamodel.rewrite import (Flatten, ExtractNestedLogicals, DNFUnionSplitter,
15
15
  DischargeConstraints)
16
16
  from relationalai.semantics.metamodel.visitor import ReadWriteVisitor
17
17
  from relationalai.util.graph import topological_sort
@@ -28,7 +28,6 @@ from relationalai.semantics.sql import sql, rewrite
28
28
  class Compiler(c.Compiler):
29
29
  def __init__(self, skip_denormalization:bool=False):
30
30
  rewrites = [
31
- FDConstraints(),
32
31
  DischargeConstraints(),
33
32
  Checker(),
34
33
  ExtractNestedLogicals(), # before InferTypes to avoid extracting casts
@@ -2495,4 +2494,4 @@ class DerivedRelationsVisitor(v.Visitor):
2495
2494
 
2496
2495
  def visit_relation(self, node: ir.Relation, parent: Optional[ir.Node]):
2497
2496
  if self._is_derived and from_cdc_annotation in node.annotations:
2498
- self._is_derived = False
2497
+ self._is_derived = False
@@ -8,7 +8,7 @@ from scipy.special import erfinv as special_erfinv
8
8
 
9
9
  from relationalai.semantics.sql import Compiler
10
10
  from relationalai.semantics.sql.executor.result_helpers import format_duckdb_columns
11
- from relationalai.semantics.metamodel import ir, executor as e
11
+ from relationalai.semantics.metamodel import ir, executor as e, factory as f
12
12
 
13
13
  class DuckDBExecutor(e.Executor):
14
14
 
@@ -29,8 +29,12 @@ class DuckDBExecutor(e.Executor):
29
29
  connection.create_function("erfinv", self.erfinv)
30
30
 
31
31
  try:
32
- sql, _ = self.compiler.compile(model, {"is_duck_db": True})
33
- arrow_table = connection.query(sql).fetch_arrow_table()
32
+ model_sql, _ = self.compiler.compile(model, {"is_duck_db": True})
33
+ query_options = {"is_duck_db": True, "query_compilation": True}
34
+ query_sql, _ = self.compiler.compile(f.compute_model(f.logical([task])), query_options)
35
+
36
+ full_sql = model_sql + "\n" + query_sql
37
+ arrow_table = connection.query(full_sql).fetch_arrow_table()
34
38
  return format_duckdb_columns(arrow_table.to_pandas(), arrow_table.schema)
35
39
  finally:
36
40
  connection.close()
@@ -45,4 +49,4 @@ class DuckDBExecutor(e.Executor):
45
49
 
46
50
  @staticmethod
47
51
  def acot(x: float) -> float:
48
- return math.atan(1 / x) if x != 0 else math.copysign(math.pi / 2, x)
52
+ return math.atan(1 / x) if x != 0 else math.copysign(math.pi / 2, x)
@@ -62,7 +62,7 @@ class SnowflakeExecutor(e.Executor):
62
62
 
63
63
  def execute(self, model: ir.Model, task: ir.Task, format:Literal["pandas", "snowpark"]="pandas",
64
64
  result_cols: Optional[list[str]] = None, export_to: Optional[str] = None,
65
- update: bool = False) -> Union[pd.DataFrame, Any]:
65
+ update: bool = False, meta: dict[str, Any] | None = None) -> Union[pd.DataFrame, Any]:
66
66
  """ Execute the SQL query directly. """
67
67
 
68
68
  warehouse = self.resources.config.get("warehouse", None)
relationalai/tools/cli.py CHANGED
@@ -16,8 +16,10 @@ from InquirerPy.base.control import Choice
16
16
  from relationalai.clients.util import IdentityParser
17
17
  from .cli_controls import divider, Spinner
18
18
  from . import cli_controls as controls
19
- from relationalai.clients import azure
20
- from typing import Sequence, cast, Any, List
19
+ from typing import Sequence, cast, Any, List, TYPE_CHECKING
20
+
21
+ if TYPE_CHECKING:
22
+ from relationalai.clients import azure
21
23
  from relationalai.errors import RAIException
22
24
  from relationalai.loaders.types import LoadType, UnsupportedTypeError
23
25
  from relationalai.loaders.csv import CSVLoader
@@ -717,7 +719,10 @@ def config_check(all_profiles:bool=False):
717
719
  @cli.command(help="Print version info")
718
720
  def version():
719
721
  from .. import __version__
720
- from railib import __version__ as railib_version
722
+ try:
723
+ from railib import __version__ as railib_version
724
+ except Exception:
725
+ railib_version = None
721
726
 
722
727
  table = Table(show_header=False, border_style="dim", header_style="bold", box=rich_box.SIMPLE)
723
728
  def print_version(name, version, latest=None):
@@ -728,7 +733,8 @@ def version():
728
733
 
729
734
  divider()
730
735
  print_version("RelationalAI", __version__, latest_version("relationalai"))
731
- print_version("Rai-sdk", railib_version, latest_version("rai-sdk"))
736
+ if railib_version is not None:
737
+ print_version("Rai-sdk", railib_version, latest_version("rai-sdk"))
732
738
  print_version("Python", sys.version.split()[0])
733
739
 
734
740
  app_version = None
@@ -1150,9 +1156,14 @@ def import_source_flow(provider: ResourcesBase) -> Sequence[ImportSource]:
1150
1156
 
1151
1157
  if isinstance(provider, snowflake.Resources):
1152
1158
  return snowflake_import_source_flow(provider)
1153
- elif isinstance(provider, azure.Resources):
1154
- return azure_import_source_flow(provider)
1155
1159
  else:
1160
+ # Lazy import for azure to avoid optional dependency issues
1161
+ try:
1162
+ from relationalai.clients import azure
1163
+ if isinstance(provider, azure.Resources):
1164
+ return azure_import_source_flow(provider)
1165
+ except ImportError:
1166
+ pass
1156
1167
  raise Exception(f"No import source flow available for {provider_type.__module__}.{provider_type.__name__}")
1157
1168
 
1158
1169
  def snowflake_import_source_flow(provider: snowflake.Resources) -> Sequence[ImportSource]: