relationalai 0.12.12__py3-none-any.whl → 0.13.0__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 (49) hide show
  1. relationalai/__init__.py +69 -22
  2. relationalai/clients/__init__.py +15 -2
  3. relationalai/clients/client.py +4 -4
  4. relationalai/clients/local.py +5 -5
  5. relationalai/clients/resources/__init__.py +8 -0
  6. relationalai/clients/{azure.py → resources/azure/azure.py} +12 -12
  7. relationalai/clients/resources/snowflake/__init__.py +20 -0
  8. relationalai/clients/resources/snowflake/cli_resources.py +87 -0
  9. relationalai/clients/resources/snowflake/direct_access_resources.py +711 -0
  10. relationalai/clients/resources/snowflake/engine_state_handlers.py +309 -0
  11. relationalai/clients/resources/snowflake/error_handlers.py +199 -0
  12. relationalai/clients/{export_procedure.py.jinja → resources/snowflake/export_procedure.py.jinja} +1 -1
  13. relationalai/clients/resources/snowflake/resources_factory.py +99 -0
  14. relationalai/clients/{snowflake.py → resources/snowflake/snowflake.py} +635 -1380
  15. relationalai/clients/{use_index_poller.py → resources/snowflake/use_index_poller.py} +43 -12
  16. relationalai/clients/resources/snowflake/use_index_resources.py +188 -0
  17. relationalai/clients/resources/snowflake/util.py +387 -0
  18. relationalai/early_access/dsl/ir/executor.py +4 -4
  19. relationalai/early_access/dsl/snow/api.py +2 -1
  20. relationalai/errors.py +23 -0
  21. relationalai/experimental/solvers.py +7 -7
  22. relationalai/semantics/devtools/benchmark_lqp.py +4 -5
  23. relationalai/semantics/devtools/extract_lqp.py +1 -1
  24. relationalai/semantics/internal/internal.py +4 -4
  25. relationalai/semantics/internal/snowflake.py +3 -2
  26. relationalai/semantics/lqp/executor.py +22 -22
  27. relationalai/semantics/lqp/model2lqp.py +42 -4
  28. relationalai/semantics/lqp/passes.py +1 -1
  29. relationalai/semantics/lqp/rewrite/cdc.py +1 -1
  30. relationalai/semantics/lqp/rewrite/extract_keys.py +72 -15
  31. relationalai/semantics/metamodel/builtins.py +8 -6
  32. relationalai/semantics/metamodel/rewrite/flatten.py +9 -4
  33. relationalai/semantics/metamodel/util.py +6 -5
  34. relationalai/semantics/reasoners/graph/core.py +8 -9
  35. relationalai/semantics/rel/executor.py +14 -11
  36. relationalai/semantics/sql/compiler.py +2 -2
  37. relationalai/semantics/sql/executor/snowflake.py +9 -5
  38. relationalai/semantics/tests/test_snapshot_abstract.py +1 -1
  39. relationalai/tools/cli.py +26 -30
  40. relationalai/tools/cli_helpers.py +10 -2
  41. relationalai/util/otel_configuration.py +2 -1
  42. relationalai/util/otel_handler.py +1 -1
  43. {relationalai-0.12.12.dist-info → relationalai-0.13.0.dist-info}/METADATA +1 -1
  44. {relationalai-0.12.12.dist-info → relationalai-0.13.0.dist-info}/RECORD +49 -40
  45. relationalai_test_util/fixtures.py +2 -1
  46. /relationalai/clients/{cache_store.py → resources/snowflake/cache_store.py} +0 -0
  47. {relationalai-0.12.12.dist-info → relationalai-0.13.0.dist-info}/WHEEL +0 -0
  48. {relationalai-0.12.12.dist-info → relationalai-0.13.0.dist-info}/entry_points.txt +0 -0
  49. {relationalai-0.12.12.dist-info → relationalai-0.13.0.dist-info}/licenses/LICENSE +0 -0
relationalai/__init__.py CHANGED
@@ -7,8 +7,8 @@ import importlib.metadata
7
7
 
8
8
  from typing import cast
9
9
 
10
- from .clients import config as cfg
11
10
  from .clients.config import Config, save_config
11
+ from . import clients
12
12
  from . import dsl
13
13
  from . import debugging
14
14
  from . import metamodel
@@ -19,11 +19,14 @@ from . import tools
19
19
  from .util.otel_configuration import configure_otel
20
20
  from snowflake.snowpark import Session
21
21
  from .errors import RAIException, handle_missing_integration
22
- from .environments import runtime_env, SessionEnvironment
23
- from relationalai.tools.constants import USE_DIRECT_ACCESS, Generation
22
+ from .environments import runtime_env, SessionEnvironment, SnowbookEnvironment
23
+ from relationalai.tools.constants import Generation
24
24
 
25
25
  import __main__
26
26
 
27
+ # Define cfg after all imports since it depends on clients being imported
28
+ cfg = clients.config
29
+
27
30
  __version__ = importlib.metadata.version(__package__ or __name__)
28
31
 
29
32
  def Model(
@@ -113,9 +116,9 @@ def Model(
113
116
 
114
117
  try:
115
118
  if platform == "azure":
116
- import relationalai.clients.azure as azure
119
+ from relationalai.clients.resources.azure.azure import Graph
117
120
  from .util.otel_handler import disable_otel_handling, is_otel_initialized
118
- model = azure.Graph(
121
+ model = Graph(
119
122
  name,
120
123
  profile=profile,
121
124
  config=config,
@@ -127,8 +130,18 @@ def Model(
127
130
  if is_otel_initialized:
128
131
  disable_otel_handling()
129
132
  elif platform == "snowflake":
130
- from relationalai.clients import snowflake
131
- model = snowflake.Graph(
133
+ try:
134
+ from relationalai.clients.resources.snowflake import Graph
135
+ except ImportError as e:
136
+ # Provide a helpful error message for Snowflake notebook environments
137
+ if isinstance(runtime_env, SnowbookEnvironment):
138
+ raise ImportError(
139
+ "Failed to import relationalai.clients.resources.snowflake. "
140
+ "This may indicate that the relationalai.zip package structure is incomplete. "
141
+ "Please ensure the zip file includes the full clients/resources/snowflake directory structure."
142
+ ) from e
143
+ raise
144
+ model = Graph(
132
145
  name,
133
146
  profile=profile,
134
147
  config=config,
@@ -163,18 +176,41 @@ def Resources(
163
176
  config = config or Config(profile)
164
177
  platform = config.get("platform", "snowflake")
165
178
  if platform == "azure":
166
- from relationalai.clients.azure import Resources
179
+ from relationalai.clients.resources.azure.azure import Resources
167
180
  return Resources(config=config)
168
181
  elif platform == "snowflake":
169
- from relationalai.clients.snowflake import Resources, DirectAccessResources
170
- use_direct_access = config.get("use_direct_access", USE_DIRECT_ACCESS)
171
- if use_direct_access:
172
- return DirectAccessResources(config=config, connection=connection, reset_session=reset_session, generation=generation)
173
- else:
174
- return Resources(config=config, connection=connection, reset_session=reset_session, generation=generation)
182
+ try:
183
+ from relationalai.clients.resources.snowflake.resources_factory import create_resources_instance
184
+ except ImportError as e:
185
+ # Provide a helpful error message for Snowflake notebook environments
186
+ from relationalai.environments import runtime_env, SnowbookEnvironment
187
+ if isinstance(runtime_env, SnowbookEnvironment):
188
+ raise ImportError(
189
+ "Failed to import relationalai.clients.resources.snowflake. "
190
+ "This may indicate that the relationalai.zip package structure is incomplete. "
191
+ "Please ensure the zip file includes the full clients/resources/snowflake directory structure."
192
+ ) from e
193
+ raise
194
+ return create_resources_instance(
195
+ config=config,
196
+ profile=profile,
197
+ connection=connection,
198
+ reset_session=reset_session,
199
+ generation=generation or Generation.V0,
200
+ dry_run=False,
201
+ language="rel",
202
+ )
175
203
  elif platform == "local":
176
- from relationalai.clients.local import LocalResources
177
- return LocalResources(config=config, connection=connection, reset_session=reset_session, generation=generation)
204
+ from relationalai.clients.resources.snowflake.resources_factory import create_resources_instance
205
+ return create_resources_instance(
206
+ config=config,
207
+ profile=profile,
208
+ connection=connection,
209
+ reset_session=reset_session,
210
+ generation=generation or Generation.V0,
211
+ dry_run=False,
212
+ language="rel",
213
+ )
178
214
  else:
179
215
  raise Exception(f"Unknown platform: {platform}")
180
216
 
@@ -187,15 +223,26 @@ def Provider(
187
223
  resources = Resources(profile, config, connection, generation=generation)
188
224
  platform = resources.config.get("platform", "snowflake")
189
225
  if platform == "azure":
190
- import relationalai.clients.azure
191
- resources = cast(relationalai.clients.azure.Resources, resources)
192
- return relationalai.clients.azure.Provider(
226
+ from relationalai.clients.resources.azure.azure import Resources as AzureResources, Provider as AzureProvider
227
+ resources = cast(AzureResources, resources)
228
+ return AzureProvider(
193
229
  resources=resources
194
230
  )
195
231
  elif platform == "snowflake":
196
- import relationalai.clients.snowflake
197
- resources = cast(relationalai.clients.snowflake.Resources, resources)
198
- return relationalai.clients.snowflake.Provider(
232
+ try:
233
+ from relationalai.clients.resources.snowflake import Resources as SnowflakeResources, Provider as SnowflakeProvider
234
+ except ImportError as e:
235
+ # Provide a helpful error message for Snowflake notebook environments
236
+ from relationalai.environments import runtime_env, SnowbookEnvironment
237
+ if isinstance(runtime_env, SnowbookEnvironment):
238
+ raise ImportError(
239
+ "Failed to import relationalai.clients.resources.snowflake. "
240
+ "This may indicate that the relationalai.zip package structure is incomplete. "
241
+ "Please ensure the zip file includes the full clients/resources/snowflake directory structure."
242
+ ) from e
243
+ raise
244
+ resources = cast(SnowflakeResources, resources)
245
+ return SnowflakeProvider(
199
246
  resources=resources,
200
247
  generation=generation
201
248
  )
@@ -1,5 +1,18 @@
1
1
  # Azure import is dropped because we need to to do runtime import of azure module to support our package running on Snowflake Notebook
2
- from . import config, snowflake, client, local
2
+ from . import config
3
+
4
+ # Lazy imports for client and local to avoid circular import issues
5
+ # These modules import from relationalai which imports from clients, creating a cycle
6
+ def __getattr__(name: str):
7
+ if name == 'client':
8
+ from . import client
9
+ return client
10
+ elif name == 'local':
11
+ from . import local
12
+ return local
13
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
3
14
 
4
15
  # note: user must do `import relationalai.clients.azure` to get `azure` submodule
5
- __all__ = ['snowflake', 'config', 'client', 'local']
16
+ # note: snowflake module is now at relationalai.clients.resources.snowflake
17
+ # note: 'client' and 'local' are lazy-loaded via __getattr__, so they're not in __all__
18
+ __all__ = ['config']
@@ -10,12 +10,12 @@ from dataclasses import dataclass
10
10
  from pandas import DataFrame
11
11
  import time
12
12
 
13
- from relationalai.clients.hash_util import database_name_from_sproc_name
14
- from relationalai.tools.constants import USE_GRAPH_INDEX, USE_PACKAGE_MANAGER
13
+ from .hash_util import database_name_from_sproc_name
14
+ from ..tools.constants import USE_GRAPH_INDEX, USE_PACKAGE_MANAGER
15
15
 
16
16
 
17
17
  from .types import AvailableModel, EngineState, Import, ImportSource, ImportsStatus, SourceMapEntry
18
- from ..clients.config import Config
18
+ from .config import Config
19
19
  from ..compiler import Compiler
20
20
  from ..environments import runtime_env, NotebookRuntimeEnvironment
21
21
  from .. import dsl, debugging, metamodel as m
@@ -210,7 +210,7 @@ class ResourcesBase(ABC):
210
210
  return engine
211
211
 
212
212
  @abstractmethod
213
- def auto_create_engine_async(self, name: str | None = None):
213
+ def auto_create_engine_async(self, name: str | None = None) -> str:
214
214
  pass
215
215
 
216
216
  _active_engine: EngineState|None = None
@@ -13,10 +13,10 @@ from typing import Any, Dict, List, Iterable, Literal, Optional, Tuple, Union
13
13
  from requests.adapters import HTTPAdapter
14
14
  from urllib3.util.retry import Retry
15
15
 
16
- from relationalai.clients.client import ResourcesBase, ProviderBase
17
- from relationalai.clients.config import Config
18
- from relationalai.clients.types import TransactionAsyncResponse
19
- from relationalai.clients.util import get_pyrel_version
16
+ from .client import ResourcesBase, ProviderBase
17
+ from .config import Config
18
+ from .types import TransactionAsyncResponse
19
+ from .util import get_pyrel_version
20
20
  from ..errors import ResponseStatusException
21
21
  from .. import debugging
22
22
 
@@ -202,7 +202,7 @@ class LocalResources(ResourcesBase):
202
202
  def alter_engine_pool(self, size: str | None = None, mins: int | None = None, maxs: int | None = None):
203
203
  raise NotImplementedError("alter_engine_pool not supported in local mode")
204
204
 
205
- def auto_create_engine_async(self, name: str | None = None):
205
+ def auto_create_engine_async(self, name: str | None = None) -> str:
206
206
  raise NotImplementedError("auto_create_engine_async not supported in local mode")
207
207
 
208
208
  #--------------------------------------------------
@@ -0,0 +1,8 @@
1
+ """
2
+ Resources module for RelationalAI clients.
3
+ """
4
+
5
+ # This package contains resource implementations for different platforms:
6
+ # - snowflake: Snowflake-specific resources
7
+ # - azure: Azure-specific resources (optional, may not be available in all environments)
8
+
@@ -9,18 +9,18 @@ from urllib.error import HTTPError
9
9
 
10
10
  from pandas import DataFrame
11
11
 
12
- from relationalai import debugging
13
- from relationalai.clients.util import poll_with_specified_overhead
14
-
15
- from ..errors import EngineNotFoundException, RAIException, AzureUnsupportedQueryTimeoutException
16
- from ..rel_utils import assert_no_problems
17
- from ..loaders.loader import emit_delete_import, import_file, list_available_resources
18
- from .config import Config
19
- from .types import EngineState, ImportSource, ImportSourceFile, TransactionAsyncResponse
20
- from .client import Client, ExportParams, ProviderBase, ResourcesBase
21
- from .. import dsl, rel, metamodel as m
12
+ from .... import debugging
13
+ from ...util import poll_with_specified_overhead
14
+
15
+ from ....errors import EngineNotFoundException, RAIException, AzureUnsupportedQueryTimeoutException
16
+ from ....rel_utils import assert_no_problems
17
+ from ....loaders.loader import emit_delete_import, import_file, list_available_resources
18
+ from ...config import Config
19
+ from ...types import EngineState, ImportSource, ImportSourceFile, TransactionAsyncResponse
20
+ from ...client import Client, ExportParams, ProviderBase, ResourcesBase
21
+ from .... import dsl, rel, metamodel as m
22
22
  from railib import api
23
- from . import result_helpers
23
+ from ... import result_helpers
24
24
 
25
25
  #--------------------------------------------------
26
26
  # Constants
@@ -105,7 +105,7 @@ class Resources(ResourcesBase):
105
105
  def resume_engine_async(self, name:str):
106
106
  return api.resume_engine(self._api_ctx(), name)
107
107
 
108
- def auto_create_engine_async(self, name: str | None = None):
108
+ def auto_create_engine_async(self, name: str | None = None) -> str:
109
109
  raise Exception("Azure doesn't support auto_create_engine_async")
110
110
 
111
111
  def alter_engine_pool(self, size: str | None = None, mins: int | None = None, maxs: int | None = None):
@@ -0,0 +1,20 @@
1
+ """
2
+ Snowflake resources module.
3
+ """
4
+ # Import order matters - Resources must be imported first since other classes depend on it
5
+ from .snowflake import Resources, Provider, Graph, SnowflakeClient, APP_NAME, PYREL_ROOT_DB, ExecContext, INTERNAL_ENGINE_SIZES, ENGINE_SIZES_AWS, ENGINE_SIZES_AZURE, PrimaryKey
6
+
7
+ # These imports depend on Resources, so they come after
8
+ from .cli_resources import CLIResources
9
+ from .use_index_resources import UseIndexResources
10
+ from .direct_access_resources import DirectAccessResources
11
+ from .resources_factory import create_resources_instance
12
+
13
+ __all__ = [
14
+ 'Resources', 'DirectAccessResources', 'Provider', 'Graph', 'SnowflakeClient',
15
+ 'APP_NAME', 'PYREL_ROOT_DB', 'CLIResources', 'UseIndexResources', 'ExecContext',
16
+ 'INTERNAL_ENGINE_SIZES', 'ENGINE_SIZES_AWS', 'ENGINE_SIZES_AZURE', 'PrimaryKey',
17
+ 'create_resources_instance',
18
+ ]
19
+
20
+
@@ -0,0 +1,87 @@
1
+ """
2
+ CLI Resources - Resources class for CLI operations.
3
+ Re-raises exceptions without transformation and provides CLI utility methods.
4
+ """
5
+ from __future__ import annotations
6
+ import json
7
+ from typing import Any
8
+
9
+ from ....tools.constants import RAI_APP_NAME
10
+
11
+ # Import Resources from snowflake - this creates a dependency but no circular import
12
+ # since snowflake.py doesn't import from this file
13
+ from .snowflake import Resources, ExecContext
14
+
15
+
16
+ class CLIResources(Resources):
17
+ """
18
+ Resources class for CLI operations.
19
+ Re-raises exceptions without transformation and provides CLI utility methods.
20
+ """
21
+
22
+ def _handle_standard_exec_errors(self, e: Exception, ctx: ExecContext) -> Any | None:
23
+ """For CLI resources, re-raise exceptions without transformation."""
24
+ raise e
25
+
26
+ def list_warehouses(self):
27
+ """List all warehouses in the Snowflake account."""
28
+ results = self._exec("SHOW WAREHOUSES")
29
+ if not results:
30
+ return []
31
+ return [{"name": name}
32
+ for (name, *rest) in results]
33
+
34
+ def list_compute_pools(self):
35
+ """List all compute pools in the Snowflake account."""
36
+ results = self._exec("SHOW COMPUTE POOLS")
37
+ if not results:
38
+ return []
39
+ return [{"name": name, "status": status, "min_nodes": min_nodes, "max_nodes": max_nodes, "instance_family": instance_family}
40
+ for (name, status, min_nodes, max_nodes, instance_family, *rest) in results]
41
+
42
+ def list_roles(self):
43
+ """List all available roles in the Snowflake account."""
44
+ results = self._exec("SELECT CURRENT_AVAILABLE_ROLES()")
45
+ if not results:
46
+ return []
47
+ # the response is a single row with a single column containing
48
+ # a stringified JSON array of role names:
49
+ row = results[0]
50
+ if not row:
51
+ return []
52
+ return [{"name": name} for name in json.loads(row[0])]
53
+
54
+ def list_apps(self):
55
+ """List all applications in the Snowflake account."""
56
+ all_apps = self._exec(f"SHOW APPLICATIONS LIKE '{RAI_APP_NAME}'")
57
+ if not all_apps:
58
+ all_apps = self._exec("SHOW APPLICATIONS")
59
+ if not all_apps:
60
+ return []
61
+ return [{"name": name}
62
+ for (time, name, *rest) in all_apps]
63
+
64
+ def list_databases(self):
65
+ """List all databases in the Snowflake account."""
66
+ results = self._exec("SHOW DATABASES")
67
+ if not results:
68
+ return []
69
+ return [{"name": name}
70
+ for (time, name, *rest) in results]
71
+
72
+ def list_sf_schemas(self, database: str):
73
+ """List all schemas in a given database."""
74
+ results = self._exec(f"SHOW SCHEMAS IN {database}")
75
+ if not results:
76
+ return []
77
+ return [{"name": name}
78
+ for (time, name, *rest) in results]
79
+
80
+ def list_tables(self, database: str, schema: str):
81
+ """List all tables and views in a given schema."""
82
+ results = self._exec(f"SHOW OBJECTS IN {database}.{schema}")
83
+ items = []
84
+ if results:
85
+ for (time, name, db_name, schema_name, kind, *rest) in results:
86
+ items.append({"name": name, "kind": kind.lower()})
87
+ return items