relationalai 0.13.5__py3-none-any.whl → 1.0.0a2__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 (856) hide show
  1. relationalai/__init__.py +1 -256
  2. relationalai/config/__init__.py +56 -0
  3. relationalai/config/config.py +289 -0
  4. relationalai/config/config_fields.py +86 -0
  5. relationalai/config/connections/__init__.py +46 -0
  6. relationalai/config/connections/base.py +23 -0
  7. relationalai/config/connections/duckdb.py +29 -0
  8. relationalai/config/connections/snowflake.py +243 -0
  9. relationalai/config/external/__init__.py +17 -0
  10. relationalai/config/external/dbt_converter.py +101 -0
  11. relationalai/config/external/dbt_models.py +93 -0
  12. relationalai/config/external/snowflake_converter.py +41 -0
  13. relationalai/config/external/snowflake_models.py +85 -0
  14. relationalai/config/external/utils.py +19 -0
  15. relationalai/config/shims.py +1 -0
  16. relationalai/semantics/__init__.py +146 -22
  17. relationalai/semantics/backends/lqp/annotations.py +11 -0
  18. relationalai/semantics/backends/sql/sql_compiler.py +327 -0
  19. relationalai/semantics/frontend/base.py +1719 -0
  20. relationalai/semantics/frontend/core.py +179 -0
  21. relationalai/semantics/frontend/front_compiler.py +1316 -0
  22. relationalai/semantics/frontend/pprint.py +408 -0
  23. relationalai/semantics/metamodel/__init__.py +6 -40
  24. relationalai/semantics/metamodel/builtins.py +206 -772
  25. relationalai/semantics/metamodel/metamodel.py +465 -0
  26. relationalai/semantics/metamodel/metamodel_analyzer.py +519 -0
  27. relationalai/semantics/metamodel/pprint.py +414 -0
  28. relationalai/semantics/metamodel/rewriter.py +266 -0
  29. relationalai/semantics/metamodel/typer.py +1213 -0
  30. relationalai/semantics/std/__init__.py +60 -40
  31. relationalai/semantics/std/aggregates.py +148 -0
  32. relationalai/semantics/std/common.py +44 -0
  33. relationalai/semantics/std/constraints.py +37 -43
  34. relationalai/semantics/std/datetime.py +249 -135
  35. relationalai/semantics/std/decimals.py +45 -52
  36. relationalai/semantics/std/floats.py +13 -5
  37. relationalai/semantics/std/integers.py +26 -11
  38. relationalai/semantics/std/math.py +183 -112
  39. relationalai/semantics/std/numbers.py +86 -0
  40. relationalai/semantics/std/re.py +80 -62
  41. relationalai/semantics/std/strings.py +101 -46
  42. relationalai/shims/executor.py +179 -0
  43. relationalai/shims/helpers.py +126 -0
  44. relationalai/shims/hoister.py +221 -0
  45. relationalai/shims/mm2v0.py +1394 -0
  46. relationalai/tools/cli/__init__.py +6 -0
  47. relationalai/tools/cli/cli.py +90 -0
  48. relationalai/tools/cli/components/__init__.py +5 -0
  49. relationalai/tools/cli/components/progress_reader.py +1524 -0
  50. relationalai/tools/cli/components/utils.py +58 -0
  51. relationalai/tools/cli/config_template.py +45 -0
  52. relationalai/tools/cli/dev.py +19 -0
  53. relationalai/tools/debugger.py +289 -183
  54. relationalai/tools/typer_debugger.py +93 -0
  55. relationalai/util/dataclasses.py +43 -0
  56. relationalai/util/docutils.py +40 -0
  57. relationalai/util/error.py +199 -0
  58. relationalai/util/format.py +48 -109
  59. relationalai/util/naming.py +145 -0
  60. relationalai/util/python.py +35 -0
  61. relationalai/util/runtime.py +156 -0
  62. relationalai/util/schema.py +197 -0
  63. relationalai/util/source.py +185 -0
  64. relationalai/util/structures.py +163 -0
  65. relationalai/util/tracing.py +261 -0
  66. relationalai-1.0.0a2.dist-info/METADATA +44 -0
  67. relationalai-1.0.0a2.dist-info/RECORD +489 -0
  68. relationalai-1.0.0a2.dist-info/WHEEL +5 -0
  69. relationalai-1.0.0a2.dist-info/entry_points.txt +3 -0
  70. relationalai-1.0.0a2.dist-info/top_level.txt +2 -0
  71. v0/relationalai/__init__.py +216 -0
  72. v0/relationalai/clients/__init__.py +5 -0
  73. v0/relationalai/clients/azure.py +477 -0
  74. v0/relationalai/clients/client.py +912 -0
  75. v0/relationalai/clients/config.py +673 -0
  76. v0/relationalai/clients/direct_access_client.py +118 -0
  77. v0/relationalai/clients/hash_util.py +31 -0
  78. v0/relationalai/clients/local.py +571 -0
  79. v0/relationalai/clients/profile_polling.py +73 -0
  80. v0/relationalai/clients/result_helpers.py +420 -0
  81. v0/relationalai/clients/snowflake.py +3869 -0
  82. v0/relationalai/clients/types.py +113 -0
  83. v0/relationalai/clients/use_index_poller.py +980 -0
  84. v0/relationalai/clients/util.py +356 -0
  85. v0/relationalai/debugging.py +389 -0
  86. v0/relationalai/dsl.py +1749 -0
  87. v0/relationalai/early_access/builder/__init__.py +30 -0
  88. v0/relationalai/early_access/builder/builder/__init__.py +35 -0
  89. v0/relationalai/early_access/builder/snowflake/__init__.py +12 -0
  90. v0/relationalai/early_access/builder/std/__init__.py +25 -0
  91. v0/relationalai/early_access/builder/std/decimals/__init__.py +12 -0
  92. v0/relationalai/early_access/builder/std/integers/__init__.py +12 -0
  93. v0/relationalai/early_access/builder/std/math/__init__.py +12 -0
  94. v0/relationalai/early_access/builder/std/strings/__init__.py +14 -0
  95. v0/relationalai/early_access/devtools/__init__.py +12 -0
  96. v0/relationalai/early_access/devtools/benchmark_lqp/__init__.py +12 -0
  97. v0/relationalai/early_access/devtools/extract_lqp/__init__.py +12 -0
  98. v0/relationalai/early_access/dsl/adapters/orm/adapter_qb.py +427 -0
  99. v0/relationalai/early_access/dsl/adapters/orm/parser.py +636 -0
  100. v0/relationalai/early_access/dsl/adapters/owl/adapter.py +176 -0
  101. v0/relationalai/early_access/dsl/adapters/owl/parser.py +160 -0
  102. v0/relationalai/early_access/dsl/bindings/common.py +402 -0
  103. v0/relationalai/early_access/dsl/bindings/csv.py +170 -0
  104. v0/relationalai/early_access/dsl/bindings/legacy/binding_models.py +143 -0
  105. v0/relationalai/early_access/dsl/bindings/snowflake.py +64 -0
  106. v0/relationalai/early_access/dsl/codegen/binder.py +411 -0
  107. v0/relationalai/early_access/dsl/codegen/common.py +79 -0
  108. v0/relationalai/early_access/dsl/codegen/helpers.py +23 -0
  109. v0/relationalai/early_access/dsl/codegen/relations.py +700 -0
  110. v0/relationalai/early_access/dsl/codegen/weaver.py +417 -0
  111. v0/relationalai/early_access/dsl/core/builders/__init__.py +47 -0
  112. v0/relationalai/early_access/dsl/core/builders/logic.py +19 -0
  113. v0/relationalai/early_access/dsl/core/builders/scalar_constraint.py +11 -0
  114. v0/relationalai/early_access/dsl/core/constraints/predicate/atomic.py +455 -0
  115. v0/relationalai/early_access/dsl/core/constraints/predicate/universal.py +73 -0
  116. v0/relationalai/early_access/dsl/core/constraints/scalar.py +310 -0
  117. v0/relationalai/early_access/dsl/core/context.py +13 -0
  118. v0/relationalai/early_access/dsl/core/cset.py +132 -0
  119. v0/relationalai/early_access/dsl/core/exprs/__init__.py +116 -0
  120. v0/relationalai/early_access/dsl/core/exprs/relational.py +18 -0
  121. v0/relationalai/early_access/dsl/core/exprs/scalar.py +412 -0
  122. v0/relationalai/early_access/dsl/core/instances.py +44 -0
  123. v0/relationalai/early_access/dsl/core/logic/__init__.py +193 -0
  124. v0/relationalai/early_access/dsl/core/logic/aggregation.py +98 -0
  125. v0/relationalai/early_access/dsl/core/logic/exists.py +223 -0
  126. v0/relationalai/early_access/dsl/core/logic/helper.py +163 -0
  127. v0/relationalai/early_access/dsl/core/namespaces.py +32 -0
  128. v0/relationalai/early_access/dsl/core/relations.py +276 -0
  129. v0/relationalai/early_access/dsl/core/rules.py +112 -0
  130. v0/relationalai/early_access/dsl/core/std/__init__.py +45 -0
  131. v0/relationalai/early_access/dsl/core/temporal/recall.py +6 -0
  132. v0/relationalai/early_access/dsl/core/types/__init__.py +270 -0
  133. v0/relationalai/early_access/dsl/core/types/concepts.py +128 -0
  134. v0/relationalai/early_access/dsl/core/types/constrained/__init__.py +267 -0
  135. v0/relationalai/early_access/dsl/core/types/constrained/nominal.py +143 -0
  136. v0/relationalai/early_access/dsl/core/types/constrained/subtype.py +124 -0
  137. v0/relationalai/early_access/dsl/core/types/standard.py +92 -0
  138. v0/relationalai/early_access/dsl/core/types/unconstrained.py +50 -0
  139. v0/relationalai/early_access/dsl/core/types/variables.py +203 -0
  140. v0/relationalai/early_access/dsl/ir/compiler.py +318 -0
  141. v0/relationalai/early_access/dsl/ir/executor.py +260 -0
  142. v0/relationalai/early_access/dsl/ontologies/constraints.py +88 -0
  143. v0/relationalai/early_access/dsl/ontologies/export.py +30 -0
  144. v0/relationalai/early_access/dsl/ontologies/models.py +453 -0
  145. v0/relationalai/early_access/dsl/ontologies/python_printer.py +303 -0
  146. v0/relationalai/early_access/dsl/ontologies/readings.py +60 -0
  147. v0/relationalai/early_access/dsl/ontologies/relationships.py +322 -0
  148. v0/relationalai/early_access/dsl/ontologies/roles.py +87 -0
  149. v0/relationalai/early_access/dsl/ontologies/subtyping.py +55 -0
  150. v0/relationalai/early_access/dsl/orm/constraints.py +438 -0
  151. v0/relationalai/early_access/dsl/orm/measures/dimensions.py +200 -0
  152. v0/relationalai/early_access/dsl/orm/measures/initializer.py +16 -0
  153. v0/relationalai/early_access/dsl/orm/measures/measure_rules.py +275 -0
  154. v0/relationalai/early_access/dsl/orm/measures/measures.py +299 -0
  155. v0/relationalai/early_access/dsl/orm/measures/role_exprs.py +268 -0
  156. v0/relationalai/early_access/dsl/orm/models.py +256 -0
  157. v0/relationalai/early_access/dsl/orm/object_oriented_printer.py +344 -0
  158. v0/relationalai/early_access/dsl/orm/printer.py +469 -0
  159. v0/relationalai/early_access/dsl/orm/reasoners.py +480 -0
  160. v0/relationalai/early_access/dsl/orm/relations.py +19 -0
  161. v0/relationalai/early_access/dsl/orm/relationships.py +251 -0
  162. v0/relationalai/early_access/dsl/orm/types.py +42 -0
  163. v0/relationalai/early_access/dsl/orm/utils.py +79 -0
  164. v0/relationalai/early_access/dsl/orm/verb.py +204 -0
  165. v0/relationalai/early_access/dsl/physical_metadata/tables.py +133 -0
  166. v0/relationalai/early_access/dsl/relations.py +170 -0
  167. v0/relationalai/early_access/dsl/rulesets.py +69 -0
  168. v0/relationalai/early_access/dsl/schemas/__init__.py +450 -0
  169. v0/relationalai/early_access/dsl/schemas/builder.py +48 -0
  170. v0/relationalai/early_access/dsl/schemas/comp_names.py +51 -0
  171. v0/relationalai/early_access/dsl/schemas/components.py +203 -0
  172. v0/relationalai/early_access/dsl/schemas/contexts.py +156 -0
  173. v0/relationalai/early_access/dsl/schemas/exprs.py +89 -0
  174. v0/relationalai/early_access/dsl/schemas/fragments.py +464 -0
  175. v0/relationalai/early_access/dsl/serialization.py +79 -0
  176. v0/relationalai/early_access/dsl/serialize/exporter.py +163 -0
  177. v0/relationalai/early_access/dsl/snow/api.py +104 -0
  178. v0/relationalai/early_access/dsl/snow/common.py +76 -0
  179. v0/relationalai/early_access/dsl/state_mgmt/__init__.py +129 -0
  180. v0/relationalai/early_access/dsl/state_mgmt/state_charts.py +125 -0
  181. v0/relationalai/early_access/dsl/state_mgmt/transitions.py +130 -0
  182. v0/relationalai/early_access/dsl/types/__init__.py +40 -0
  183. v0/relationalai/early_access/dsl/types/concepts.py +12 -0
  184. v0/relationalai/early_access/dsl/types/entities.py +135 -0
  185. v0/relationalai/early_access/dsl/types/values.py +17 -0
  186. v0/relationalai/early_access/dsl/utils.py +102 -0
  187. v0/relationalai/early_access/graphs/__init__.py +13 -0
  188. v0/relationalai/early_access/lqp/__init__.py +12 -0
  189. v0/relationalai/early_access/lqp/compiler/__init__.py +12 -0
  190. v0/relationalai/early_access/lqp/constructors/__init__.py +18 -0
  191. v0/relationalai/early_access/lqp/executor/__init__.py +12 -0
  192. v0/relationalai/early_access/lqp/ir/__init__.py +12 -0
  193. v0/relationalai/early_access/lqp/passes/__init__.py +12 -0
  194. v0/relationalai/early_access/lqp/pragmas/__init__.py +12 -0
  195. v0/relationalai/early_access/lqp/primitives/__init__.py +12 -0
  196. v0/relationalai/early_access/lqp/types/__init__.py +12 -0
  197. v0/relationalai/early_access/lqp/utils/__init__.py +12 -0
  198. v0/relationalai/early_access/lqp/validators/__init__.py +12 -0
  199. v0/relationalai/early_access/metamodel/__init__.py +58 -0
  200. v0/relationalai/early_access/metamodel/builtins/__init__.py +12 -0
  201. v0/relationalai/early_access/metamodel/compiler/__init__.py +12 -0
  202. v0/relationalai/early_access/metamodel/dependency/__init__.py +12 -0
  203. v0/relationalai/early_access/metamodel/factory/__init__.py +17 -0
  204. v0/relationalai/early_access/metamodel/helpers/__init__.py +12 -0
  205. v0/relationalai/early_access/metamodel/ir/__init__.py +14 -0
  206. v0/relationalai/early_access/metamodel/rewrite/__init__.py +7 -0
  207. v0/relationalai/early_access/metamodel/typer/__init__.py +3 -0
  208. v0/relationalai/early_access/metamodel/typer/typer/__init__.py +12 -0
  209. v0/relationalai/early_access/metamodel/types/__init__.py +15 -0
  210. v0/relationalai/early_access/metamodel/util/__init__.py +15 -0
  211. v0/relationalai/early_access/metamodel/visitor/__init__.py +12 -0
  212. v0/relationalai/early_access/rel/__init__.py +12 -0
  213. v0/relationalai/early_access/rel/executor/__init__.py +12 -0
  214. v0/relationalai/early_access/rel/rel_utils/__init__.py +12 -0
  215. v0/relationalai/early_access/rel/rewrite/__init__.py +7 -0
  216. v0/relationalai/early_access/solvers/__init__.py +19 -0
  217. v0/relationalai/early_access/sql/__init__.py +11 -0
  218. v0/relationalai/early_access/sql/executor/__init__.py +3 -0
  219. v0/relationalai/early_access/sql/rewrite/__init__.py +3 -0
  220. v0/relationalai/early_access/tests/logging/__init__.py +12 -0
  221. v0/relationalai/early_access/tests/test_snapshot_base/__init__.py +12 -0
  222. v0/relationalai/early_access/tests/utils/__init__.py +12 -0
  223. v0/relationalai/environments/__init__.py +35 -0
  224. v0/relationalai/environments/base.py +381 -0
  225. v0/relationalai/environments/colab.py +14 -0
  226. v0/relationalai/environments/generic.py +71 -0
  227. v0/relationalai/environments/ipython.py +68 -0
  228. v0/relationalai/environments/jupyter.py +9 -0
  229. v0/relationalai/environments/snowbook.py +169 -0
  230. v0/relationalai/errors.py +2478 -0
  231. v0/relationalai/experimental/SF.py +38 -0
  232. v0/relationalai/experimental/inspect.py +47 -0
  233. v0/relationalai/experimental/pathfinder/__init__.py +158 -0
  234. v0/relationalai/experimental/pathfinder/api.py +160 -0
  235. v0/relationalai/experimental/pathfinder/automaton.py +584 -0
  236. v0/relationalai/experimental/pathfinder/bridge.py +226 -0
  237. v0/relationalai/experimental/pathfinder/compiler.py +416 -0
  238. v0/relationalai/experimental/pathfinder/datalog.py +214 -0
  239. v0/relationalai/experimental/pathfinder/diagnostics.py +56 -0
  240. v0/relationalai/experimental/pathfinder/filter.py +236 -0
  241. v0/relationalai/experimental/pathfinder/glushkov.py +439 -0
  242. v0/relationalai/experimental/pathfinder/options.py +265 -0
  243. v0/relationalai/experimental/pathfinder/rpq.py +344 -0
  244. v0/relationalai/experimental/pathfinder/transition.py +200 -0
  245. v0/relationalai/experimental/pathfinder/utils.py +26 -0
  246. v0/relationalai/experimental/paths/api.py +143 -0
  247. v0/relationalai/experimental/paths/benchmarks/grid_graph.py +37 -0
  248. v0/relationalai/experimental/paths/examples/basic_example.py +40 -0
  249. v0/relationalai/experimental/paths/examples/minimal_engine_warmup.py +3 -0
  250. v0/relationalai/experimental/paths/examples/movie_example.py +77 -0
  251. v0/relationalai/experimental/paths/examples/paths_benchmark.py +115 -0
  252. v0/relationalai/experimental/paths/examples/paths_example.py +116 -0
  253. v0/relationalai/experimental/paths/examples/pattern_to_automaton.py +28 -0
  254. v0/relationalai/experimental/paths/find_paths_via_automaton.py +85 -0
  255. v0/relationalai/experimental/paths/graph.py +185 -0
  256. v0/relationalai/experimental/paths/path_algorithms/find_paths.py +280 -0
  257. v0/relationalai/experimental/paths/path_algorithms/one_sided_ball_repetition.py +26 -0
  258. v0/relationalai/experimental/paths/path_algorithms/one_sided_ball_upto.py +111 -0
  259. v0/relationalai/experimental/paths/path_algorithms/single.py +59 -0
  260. v0/relationalai/experimental/paths/path_algorithms/two_sided_balls_repetition.py +39 -0
  261. v0/relationalai/experimental/paths/path_algorithms/two_sided_balls_upto.py +103 -0
  262. v0/relationalai/experimental/paths/path_algorithms/usp-old.py +130 -0
  263. v0/relationalai/experimental/paths/path_algorithms/usp-tuple.py +183 -0
  264. v0/relationalai/experimental/paths/path_algorithms/usp.py +150 -0
  265. v0/relationalai/experimental/paths/product_graph.py +93 -0
  266. v0/relationalai/experimental/paths/rpq/automaton.py +584 -0
  267. v0/relationalai/experimental/paths/rpq/diagnostics.py +56 -0
  268. v0/relationalai/experimental/paths/rpq/rpq.py +378 -0
  269. v0/relationalai/experimental/paths/tests/tests_limit_sp_max_length.py +90 -0
  270. v0/relationalai/experimental/paths/tests/tests_limit_sp_multiple.py +119 -0
  271. v0/relationalai/experimental/paths/tests/tests_limit_sp_single.py +104 -0
  272. v0/relationalai/experimental/paths/tests/tests_limit_walks_multiple.py +113 -0
  273. v0/relationalai/experimental/paths/tests/tests_limit_walks_single.py +149 -0
  274. v0/relationalai/experimental/paths/tests/tests_one_sided_ball_repetition_multiple.py +70 -0
  275. v0/relationalai/experimental/paths/tests/tests_one_sided_ball_repetition_single.py +64 -0
  276. v0/relationalai/experimental/paths/tests/tests_one_sided_ball_upto_multiple.py +115 -0
  277. v0/relationalai/experimental/paths/tests/tests_one_sided_ball_upto_single.py +75 -0
  278. v0/relationalai/experimental/paths/tests/tests_single_paths.py +152 -0
  279. v0/relationalai/experimental/paths/tests/tests_single_walks.py +208 -0
  280. v0/relationalai/experimental/paths/tests/tests_single_walks_undirected.py +297 -0
  281. v0/relationalai/experimental/paths/tests/tests_two_sided_balls_repetition_multiple.py +107 -0
  282. v0/relationalai/experimental/paths/tests/tests_two_sided_balls_repetition_single.py +76 -0
  283. v0/relationalai/experimental/paths/tests/tests_two_sided_balls_upto_multiple.py +76 -0
  284. v0/relationalai/experimental/paths/tests/tests_two_sided_balls_upto_single.py +110 -0
  285. v0/relationalai/experimental/paths/tests/tests_usp_nsp_multiple.py +229 -0
  286. v0/relationalai/experimental/paths/tests/tests_usp_nsp_single.py +108 -0
  287. v0/relationalai/experimental/paths/tree_agg.py +168 -0
  288. v0/relationalai/experimental/paths/utilities/iterators.py +27 -0
  289. v0/relationalai/experimental/paths/utilities/prefix_sum.py +91 -0
  290. v0/relationalai/experimental/solvers.py +1087 -0
  291. v0/relationalai/loaders/csv.py +195 -0
  292. v0/relationalai/loaders/loader.py +177 -0
  293. v0/relationalai/loaders/types.py +23 -0
  294. v0/relationalai/rel_emitter.py +373 -0
  295. v0/relationalai/rel_utils.py +185 -0
  296. v0/relationalai/semantics/__init__.py +29 -0
  297. v0/relationalai/semantics/devtools/benchmark_lqp.py +536 -0
  298. v0/relationalai/semantics/devtools/compilation_manager.py +294 -0
  299. v0/relationalai/semantics/devtools/extract_lqp.py +110 -0
  300. v0/relationalai/semantics/internal/internal.py +3785 -0
  301. v0/relationalai/semantics/internal/snowflake.py +325 -0
  302. v0/relationalai/semantics/lqp/builtins.py +16 -0
  303. v0/relationalai/semantics/lqp/compiler.py +22 -0
  304. v0/relationalai/semantics/lqp/constructors.py +68 -0
  305. v0/relationalai/semantics/lqp/executor.py +474 -0
  306. v0/relationalai/semantics/lqp/intrinsics.py +24 -0
  307. v0/relationalai/semantics/lqp/ir.py +124 -0
  308. v0/relationalai/semantics/lqp/model2lqp.py +877 -0
  309. v0/relationalai/semantics/lqp/passes.py +680 -0
  310. v0/relationalai/semantics/lqp/primitives.py +252 -0
  311. v0/relationalai/semantics/lqp/result_helpers.py +202 -0
  312. v0/relationalai/semantics/lqp/rewrite/__init__.py +18 -0
  313. v0/relationalai/semantics/lqp/rewrite/annotate_constraints.py +57 -0
  314. v0/relationalai/semantics/lqp/rewrite/cdc.py +216 -0
  315. v0/relationalai/semantics/lqp/rewrite/extract_common.py +338 -0
  316. v0/relationalai/semantics/lqp/rewrite/extract_keys.py +490 -0
  317. v0/relationalai/semantics/lqp/rewrite/function_annotations.py +114 -0
  318. v0/relationalai/semantics/lqp/rewrite/functional_dependencies.py +314 -0
  319. v0/relationalai/semantics/lqp/rewrite/quantify_vars.py +296 -0
  320. v0/relationalai/semantics/lqp/rewrite/splinter.py +76 -0
  321. v0/relationalai/semantics/lqp/types.py +101 -0
  322. v0/relationalai/semantics/lqp/utils.py +160 -0
  323. v0/relationalai/semantics/lqp/validators.py +57 -0
  324. v0/relationalai/semantics/metamodel/__init__.py +40 -0
  325. v0/relationalai/semantics/metamodel/builtins.py +776 -0
  326. v0/relationalai/semantics/metamodel/compiler.py +133 -0
  327. v0/relationalai/semantics/metamodel/dependency.py +862 -0
  328. v0/relationalai/semantics/metamodel/executor.py +61 -0
  329. v0/relationalai/semantics/metamodel/factory.py +287 -0
  330. v0/relationalai/semantics/metamodel/helpers.py +361 -0
  331. v0/relationalai/semantics/metamodel/ir.py +923 -0
  332. v0/relationalai/semantics/metamodel/rewrite/__init__.py +7 -0
  333. v0/relationalai/semantics/metamodel/rewrite/discharge_constraints.py +39 -0
  334. v0/relationalai/semantics/metamodel/rewrite/dnf_union_splitter.py +210 -0
  335. v0/relationalai/semantics/metamodel/rewrite/extract_nested_logicals.py +78 -0
  336. v0/relationalai/semantics/metamodel/rewrite/flatten.py +554 -0
  337. v0/relationalai/semantics/metamodel/rewrite/format_outputs.py +165 -0
  338. v0/relationalai/semantics/metamodel/typer/checker.py +353 -0
  339. v0/relationalai/semantics/metamodel/typer/typer.py +1395 -0
  340. v0/relationalai/semantics/metamodel/util.py +505 -0
  341. v0/relationalai/semantics/metamodel/visitor.py +944 -0
  342. v0/relationalai/semantics/reasoners/__init__.py +10 -0
  343. v0/relationalai/semantics/reasoners/graph/__init__.py +37 -0
  344. v0/relationalai/semantics/reasoners/graph/core.py +9019 -0
  345. v0/relationalai/semantics/reasoners/optimization/__init__.py +68 -0
  346. v0/relationalai/semantics/reasoners/optimization/common.py +88 -0
  347. v0/relationalai/semantics/reasoners/optimization/solvers_dev.py +568 -0
  348. v0/relationalai/semantics/reasoners/optimization/solvers_pb.py +1163 -0
  349. v0/relationalai/semantics/rel/builtins.py +40 -0
  350. v0/relationalai/semantics/rel/compiler.py +989 -0
  351. v0/relationalai/semantics/rel/executor.py +359 -0
  352. v0/relationalai/semantics/rel/rel.py +482 -0
  353. v0/relationalai/semantics/rel/rel_utils.py +276 -0
  354. v0/relationalai/semantics/snowflake/__init__.py +3 -0
  355. v0/relationalai/semantics/sql/compiler.py +2503 -0
  356. v0/relationalai/semantics/sql/executor/duck_db.py +52 -0
  357. v0/relationalai/semantics/sql/executor/result_helpers.py +64 -0
  358. v0/relationalai/semantics/sql/executor/snowflake.py +145 -0
  359. v0/relationalai/semantics/sql/rewrite/denormalize.py +222 -0
  360. v0/relationalai/semantics/sql/rewrite/double_negation.py +49 -0
  361. v0/relationalai/semantics/sql/rewrite/recursive_union.py +127 -0
  362. v0/relationalai/semantics/sql/rewrite/sort_output_query.py +246 -0
  363. v0/relationalai/semantics/sql/sql.py +504 -0
  364. v0/relationalai/semantics/std/__init__.py +54 -0
  365. v0/relationalai/semantics/std/constraints.py +43 -0
  366. v0/relationalai/semantics/std/datetime.py +363 -0
  367. v0/relationalai/semantics/std/decimals.py +62 -0
  368. v0/relationalai/semantics/std/floats.py +7 -0
  369. v0/relationalai/semantics/std/integers.py +22 -0
  370. v0/relationalai/semantics/std/math.py +141 -0
  371. v0/relationalai/semantics/std/pragmas.py +11 -0
  372. v0/relationalai/semantics/std/re.py +83 -0
  373. v0/relationalai/semantics/std/std.py +14 -0
  374. v0/relationalai/semantics/std/strings.py +63 -0
  375. v0/relationalai/semantics/tests/__init__.py +0 -0
  376. v0/relationalai/semantics/tests/test_snapshot_abstract.py +143 -0
  377. v0/relationalai/semantics/tests/test_snapshot_base.py +9 -0
  378. v0/relationalai/semantics/tests/utils.py +46 -0
  379. v0/relationalai/std/__init__.py +70 -0
  380. v0/relationalai/tools/__init__.py +0 -0
  381. v0/relationalai/tools/cli.py +1940 -0
  382. v0/relationalai/tools/cli_controls.py +1826 -0
  383. v0/relationalai/tools/cli_helpers.py +390 -0
  384. v0/relationalai/tools/debugger.py +183 -0
  385. v0/relationalai/tools/debugger_client.py +109 -0
  386. v0/relationalai/tools/debugger_server.py +302 -0
  387. v0/relationalai/tools/dev.py +685 -0
  388. v0/relationalai/tools/qb_debugger.py +425 -0
  389. v0/relationalai/util/clean_up_databases.py +95 -0
  390. v0/relationalai/util/format.py +123 -0
  391. v0/relationalai/util/list_databases.py +9 -0
  392. v0/relationalai/util/otel_configuration.py +25 -0
  393. v0/relationalai/util/otel_handler.py +484 -0
  394. v0/relationalai/util/snowflake_handler.py +88 -0
  395. v0/relationalai/util/span_format_test.py +43 -0
  396. v0/relationalai/util/span_tracker.py +207 -0
  397. v0/relationalai/util/spans_file_handler.py +72 -0
  398. v0/relationalai/util/tracing_handler.py +34 -0
  399. frontend/debugger/dist/.gitignore +0 -2
  400. frontend/debugger/dist/assets/favicon-Dy0ZgA6N.png +0 -0
  401. frontend/debugger/dist/assets/index-Cssla-O7.js +0 -208
  402. frontend/debugger/dist/assets/index-DlHsYx1V.css +0 -9
  403. frontend/debugger/dist/index.html +0 -17
  404. relationalai/clients/__init__.py +0 -18
  405. relationalai/clients/client.py +0 -946
  406. relationalai/clients/config.py +0 -673
  407. relationalai/clients/direct_access_client.py +0 -118
  408. relationalai/clients/exec_txn_poller.py +0 -153
  409. relationalai/clients/hash_util.py +0 -31
  410. relationalai/clients/local.py +0 -594
  411. relationalai/clients/profile_polling.py +0 -73
  412. relationalai/clients/resources/__init__.py +0 -8
  413. relationalai/clients/resources/azure/azure.py +0 -502
  414. relationalai/clients/resources/snowflake/__init__.py +0 -20
  415. relationalai/clients/resources/snowflake/cli_resources.py +0 -98
  416. relationalai/clients/resources/snowflake/direct_access_resources.py +0 -739
  417. relationalai/clients/resources/snowflake/engine_service.py +0 -381
  418. relationalai/clients/resources/snowflake/engine_state_handlers.py +0 -315
  419. relationalai/clients/resources/snowflake/error_handlers.py +0 -240
  420. relationalai/clients/resources/snowflake/export_procedure.py.jinja +0 -249
  421. relationalai/clients/resources/snowflake/resources_factory.py +0 -99
  422. relationalai/clients/resources/snowflake/snowflake.py +0 -3193
  423. relationalai/clients/resources/snowflake/use_index_poller.py +0 -1019
  424. relationalai/clients/resources/snowflake/use_index_resources.py +0 -188
  425. relationalai/clients/resources/snowflake/util.py +0 -387
  426. relationalai/clients/result_helpers.py +0 -420
  427. relationalai/clients/types.py +0 -118
  428. relationalai/clients/util.py +0 -356
  429. relationalai/debugging.py +0 -389
  430. relationalai/dsl.py +0 -1749
  431. relationalai/early_access/builder/__init__.py +0 -30
  432. relationalai/early_access/builder/builder/__init__.py +0 -35
  433. relationalai/early_access/builder/snowflake/__init__.py +0 -12
  434. relationalai/early_access/builder/std/__init__.py +0 -25
  435. relationalai/early_access/builder/std/decimals/__init__.py +0 -12
  436. relationalai/early_access/builder/std/integers/__init__.py +0 -12
  437. relationalai/early_access/builder/std/math/__init__.py +0 -12
  438. relationalai/early_access/builder/std/strings/__init__.py +0 -14
  439. relationalai/early_access/devtools/__init__.py +0 -12
  440. relationalai/early_access/devtools/benchmark_lqp/__init__.py +0 -12
  441. relationalai/early_access/devtools/extract_lqp/__init__.py +0 -12
  442. relationalai/early_access/dsl/adapters/orm/adapter_qb.py +0 -427
  443. relationalai/early_access/dsl/adapters/orm/parser.py +0 -636
  444. relationalai/early_access/dsl/adapters/owl/adapter.py +0 -176
  445. relationalai/early_access/dsl/adapters/owl/parser.py +0 -160
  446. relationalai/early_access/dsl/bindings/common.py +0 -402
  447. relationalai/early_access/dsl/bindings/csv.py +0 -170
  448. relationalai/early_access/dsl/bindings/legacy/binding_models.py +0 -143
  449. relationalai/early_access/dsl/bindings/snowflake.py +0 -64
  450. relationalai/early_access/dsl/codegen/binder.py +0 -411
  451. relationalai/early_access/dsl/codegen/common.py +0 -79
  452. relationalai/early_access/dsl/codegen/helpers.py +0 -23
  453. relationalai/early_access/dsl/codegen/relations.py +0 -700
  454. relationalai/early_access/dsl/codegen/weaver.py +0 -417
  455. relationalai/early_access/dsl/core/builders/__init__.py +0 -47
  456. relationalai/early_access/dsl/core/builders/logic.py +0 -19
  457. relationalai/early_access/dsl/core/builders/scalar_constraint.py +0 -11
  458. relationalai/early_access/dsl/core/constraints/predicate/atomic.py +0 -455
  459. relationalai/early_access/dsl/core/constraints/predicate/universal.py +0 -73
  460. relationalai/early_access/dsl/core/constraints/scalar.py +0 -310
  461. relationalai/early_access/dsl/core/context.py +0 -13
  462. relationalai/early_access/dsl/core/cset.py +0 -132
  463. relationalai/early_access/dsl/core/exprs/__init__.py +0 -116
  464. relationalai/early_access/dsl/core/exprs/relational.py +0 -18
  465. relationalai/early_access/dsl/core/exprs/scalar.py +0 -412
  466. relationalai/early_access/dsl/core/instances.py +0 -44
  467. relationalai/early_access/dsl/core/logic/__init__.py +0 -193
  468. relationalai/early_access/dsl/core/logic/aggregation.py +0 -98
  469. relationalai/early_access/dsl/core/logic/exists.py +0 -223
  470. relationalai/early_access/dsl/core/logic/helper.py +0 -163
  471. relationalai/early_access/dsl/core/namespaces.py +0 -32
  472. relationalai/early_access/dsl/core/relations.py +0 -276
  473. relationalai/early_access/dsl/core/rules.py +0 -112
  474. relationalai/early_access/dsl/core/std/__init__.py +0 -45
  475. relationalai/early_access/dsl/core/temporal/recall.py +0 -6
  476. relationalai/early_access/dsl/core/types/__init__.py +0 -270
  477. relationalai/early_access/dsl/core/types/concepts.py +0 -128
  478. relationalai/early_access/dsl/core/types/constrained/__init__.py +0 -267
  479. relationalai/early_access/dsl/core/types/constrained/nominal.py +0 -143
  480. relationalai/early_access/dsl/core/types/constrained/subtype.py +0 -124
  481. relationalai/early_access/dsl/core/types/standard.py +0 -92
  482. relationalai/early_access/dsl/core/types/unconstrained.py +0 -50
  483. relationalai/early_access/dsl/core/types/variables.py +0 -203
  484. relationalai/early_access/dsl/ir/compiler.py +0 -318
  485. relationalai/early_access/dsl/ir/executor.py +0 -260
  486. relationalai/early_access/dsl/ontologies/constraints.py +0 -88
  487. relationalai/early_access/dsl/ontologies/export.py +0 -30
  488. relationalai/early_access/dsl/ontologies/models.py +0 -453
  489. relationalai/early_access/dsl/ontologies/python_printer.py +0 -303
  490. relationalai/early_access/dsl/ontologies/readings.py +0 -60
  491. relationalai/early_access/dsl/ontologies/relationships.py +0 -322
  492. relationalai/early_access/dsl/ontologies/roles.py +0 -87
  493. relationalai/early_access/dsl/ontologies/subtyping.py +0 -55
  494. relationalai/early_access/dsl/orm/constraints.py +0 -438
  495. relationalai/early_access/dsl/orm/measures/dimensions.py +0 -200
  496. relationalai/early_access/dsl/orm/measures/initializer.py +0 -16
  497. relationalai/early_access/dsl/orm/measures/measure_rules.py +0 -275
  498. relationalai/early_access/dsl/orm/measures/measures.py +0 -299
  499. relationalai/early_access/dsl/orm/measures/role_exprs.py +0 -268
  500. relationalai/early_access/dsl/orm/models.py +0 -256
  501. relationalai/early_access/dsl/orm/object_oriented_printer.py +0 -344
  502. relationalai/early_access/dsl/orm/printer.py +0 -469
  503. relationalai/early_access/dsl/orm/reasoners.py +0 -480
  504. relationalai/early_access/dsl/orm/relations.py +0 -19
  505. relationalai/early_access/dsl/orm/relationships.py +0 -251
  506. relationalai/early_access/dsl/orm/types.py +0 -42
  507. relationalai/early_access/dsl/orm/utils.py +0 -79
  508. relationalai/early_access/dsl/orm/verb.py +0 -204
  509. relationalai/early_access/dsl/physical_metadata/tables.py +0 -133
  510. relationalai/early_access/dsl/relations.py +0 -170
  511. relationalai/early_access/dsl/rulesets.py +0 -69
  512. relationalai/early_access/dsl/schemas/__init__.py +0 -450
  513. relationalai/early_access/dsl/schemas/builder.py +0 -48
  514. relationalai/early_access/dsl/schemas/comp_names.py +0 -51
  515. relationalai/early_access/dsl/schemas/components.py +0 -203
  516. relationalai/early_access/dsl/schemas/contexts.py +0 -156
  517. relationalai/early_access/dsl/schemas/exprs.py +0 -89
  518. relationalai/early_access/dsl/schemas/fragments.py +0 -464
  519. relationalai/early_access/dsl/serialization.py +0 -79
  520. relationalai/early_access/dsl/serialize/exporter.py +0 -163
  521. relationalai/early_access/dsl/snow/api.py +0 -105
  522. relationalai/early_access/dsl/snow/common.py +0 -76
  523. relationalai/early_access/dsl/state_mgmt/__init__.py +0 -129
  524. relationalai/early_access/dsl/state_mgmt/state_charts.py +0 -125
  525. relationalai/early_access/dsl/state_mgmt/transitions.py +0 -130
  526. relationalai/early_access/dsl/types/__init__.py +0 -40
  527. relationalai/early_access/dsl/types/concepts.py +0 -12
  528. relationalai/early_access/dsl/types/entities.py +0 -135
  529. relationalai/early_access/dsl/types/values.py +0 -17
  530. relationalai/early_access/dsl/utils.py +0 -102
  531. relationalai/early_access/graphs/__init__.py +0 -13
  532. relationalai/early_access/lqp/__init__.py +0 -12
  533. relationalai/early_access/lqp/compiler/__init__.py +0 -12
  534. relationalai/early_access/lqp/constructors/__init__.py +0 -18
  535. relationalai/early_access/lqp/executor/__init__.py +0 -12
  536. relationalai/early_access/lqp/ir/__init__.py +0 -12
  537. relationalai/early_access/lqp/passes/__init__.py +0 -12
  538. relationalai/early_access/lqp/pragmas/__init__.py +0 -12
  539. relationalai/early_access/lqp/primitives/__init__.py +0 -12
  540. relationalai/early_access/lqp/types/__init__.py +0 -12
  541. relationalai/early_access/lqp/utils/__init__.py +0 -12
  542. relationalai/early_access/lqp/validators/__init__.py +0 -12
  543. relationalai/early_access/metamodel/__init__.py +0 -58
  544. relationalai/early_access/metamodel/builtins/__init__.py +0 -12
  545. relationalai/early_access/metamodel/compiler/__init__.py +0 -12
  546. relationalai/early_access/metamodel/dependency/__init__.py +0 -12
  547. relationalai/early_access/metamodel/factory/__init__.py +0 -17
  548. relationalai/early_access/metamodel/helpers/__init__.py +0 -12
  549. relationalai/early_access/metamodel/ir/__init__.py +0 -14
  550. relationalai/early_access/metamodel/rewrite/__init__.py +0 -7
  551. relationalai/early_access/metamodel/typer/__init__.py +0 -3
  552. relationalai/early_access/metamodel/typer/typer/__init__.py +0 -12
  553. relationalai/early_access/metamodel/types/__init__.py +0 -15
  554. relationalai/early_access/metamodel/util/__init__.py +0 -15
  555. relationalai/early_access/metamodel/visitor/__init__.py +0 -12
  556. relationalai/early_access/rel/__init__.py +0 -12
  557. relationalai/early_access/rel/executor/__init__.py +0 -12
  558. relationalai/early_access/rel/rel_utils/__init__.py +0 -12
  559. relationalai/early_access/rel/rewrite/__init__.py +0 -7
  560. relationalai/early_access/solvers/__init__.py +0 -19
  561. relationalai/early_access/sql/__init__.py +0 -11
  562. relationalai/early_access/sql/executor/__init__.py +0 -3
  563. relationalai/early_access/sql/rewrite/__init__.py +0 -3
  564. relationalai/early_access/tests/logging/__init__.py +0 -12
  565. relationalai/early_access/tests/test_snapshot_base/__init__.py +0 -12
  566. relationalai/early_access/tests/utils/__init__.py +0 -12
  567. relationalai/environments/__init__.py +0 -35
  568. relationalai/environments/base.py +0 -381
  569. relationalai/environments/colab.py +0 -14
  570. relationalai/environments/generic.py +0 -71
  571. relationalai/environments/ipython.py +0 -68
  572. relationalai/environments/jupyter.py +0 -9
  573. relationalai/environments/snowbook.py +0 -169
  574. relationalai/errors.py +0 -2496
  575. relationalai/experimental/SF.py +0 -38
  576. relationalai/experimental/inspect.py +0 -47
  577. relationalai/experimental/pathfinder/__init__.py +0 -158
  578. relationalai/experimental/pathfinder/api.py +0 -160
  579. relationalai/experimental/pathfinder/automaton.py +0 -584
  580. relationalai/experimental/pathfinder/bridge.py +0 -226
  581. relationalai/experimental/pathfinder/compiler.py +0 -416
  582. relationalai/experimental/pathfinder/datalog.py +0 -214
  583. relationalai/experimental/pathfinder/diagnostics.py +0 -56
  584. relationalai/experimental/pathfinder/filter.py +0 -236
  585. relationalai/experimental/pathfinder/glushkov.py +0 -439
  586. relationalai/experimental/pathfinder/options.py +0 -265
  587. relationalai/experimental/pathfinder/pathfinder-v0.7.0.rel +0 -1951
  588. relationalai/experimental/pathfinder/rpq.py +0 -344
  589. relationalai/experimental/pathfinder/transition.py +0 -200
  590. relationalai/experimental/pathfinder/utils.py +0 -26
  591. relationalai/experimental/paths/README.md +0 -107
  592. relationalai/experimental/paths/api.py +0 -143
  593. relationalai/experimental/paths/benchmarks/grid_graph.py +0 -37
  594. relationalai/experimental/paths/code_organization.md +0 -2
  595. relationalai/experimental/paths/examples/Movies.ipynb +0 -16328
  596. relationalai/experimental/paths/examples/basic_example.py +0 -40
  597. relationalai/experimental/paths/examples/minimal_engine_warmup.py +0 -3
  598. relationalai/experimental/paths/examples/movie_example.py +0 -77
  599. relationalai/experimental/paths/examples/movies_data/actedin.csv +0 -193
  600. relationalai/experimental/paths/examples/movies_data/directed.csv +0 -45
  601. relationalai/experimental/paths/examples/movies_data/follows.csv +0 -7
  602. relationalai/experimental/paths/examples/movies_data/movies.csv +0 -39
  603. relationalai/experimental/paths/examples/movies_data/person.csv +0 -134
  604. relationalai/experimental/paths/examples/movies_data/produced.csv +0 -16
  605. relationalai/experimental/paths/examples/movies_data/ratings.csv +0 -10
  606. relationalai/experimental/paths/examples/movies_data/wrote.csv +0 -11
  607. relationalai/experimental/paths/examples/paths_benchmark.py +0 -115
  608. relationalai/experimental/paths/examples/paths_example.py +0 -116
  609. relationalai/experimental/paths/examples/pattern_to_automaton.py +0 -28
  610. relationalai/experimental/paths/find_paths_via_automaton.py +0 -85
  611. relationalai/experimental/paths/graph.py +0 -185
  612. relationalai/experimental/paths/path_algorithms/find_paths.py +0 -280
  613. relationalai/experimental/paths/path_algorithms/one_sided_ball_repetition.py +0 -26
  614. relationalai/experimental/paths/path_algorithms/one_sided_ball_upto.py +0 -111
  615. relationalai/experimental/paths/path_algorithms/single.py +0 -59
  616. relationalai/experimental/paths/path_algorithms/two_sided_balls_repetition.py +0 -39
  617. relationalai/experimental/paths/path_algorithms/two_sided_balls_upto.py +0 -103
  618. relationalai/experimental/paths/path_algorithms/usp-old.py +0 -130
  619. relationalai/experimental/paths/path_algorithms/usp-tuple.py +0 -183
  620. relationalai/experimental/paths/path_algorithms/usp.py +0 -150
  621. relationalai/experimental/paths/product_graph.py +0 -93
  622. relationalai/experimental/paths/rpq/automaton.py +0 -584
  623. relationalai/experimental/paths/rpq/diagnostics.py +0 -56
  624. relationalai/experimental/paths/rpq/rpq.py +0 -378
  625. relationalai/experimental/paths/tests/tests_limit_sp_max_length.py +0 -90
  626. relationalai/experimental/paths/tests/tests_limit_sp_multiple.py +0 -119
  627. relationalai/experimental/paths/tests/tests_limit_sp_single.py +0 -104
  628. relationalai/experimental/paths/tests/tests_limit_walks_multiple.py +0 -113
  629. relationalai/experimental/paths/tests/tests_limit_walks_single.py +0 -149
  630. relationalai/experimental/paths/tests/tests_one_sided_ball_repetition_multiple.py +0 -70
  631. relationalai/experimental/paths/tests/tests_one_sided_ball_repetition_single.py +0 -64
  632. relationalai/experimental/paths/tests/tests_one_sided_ball_upto_multiple.py +0 -115
  633. relationalai/experimental/paths/tests/tests_one_sided_ball_upto_single.py +0 -75
  634. relationalai/experimental/paths/tests/tests_single_paths.py +0 -152
  635. relationalai/experimental/paths/tests/tests_single_walks.py +0 -208
  636. relationalai/experimental/paths/tests/tests_single_walks_undirected.py +0 -297
  637. relationalai/experimental/paths/tests/tests_two_sided_balls_repetition_multiple.py +0 -107
  638. relationalai/experimental/paths/tests/tests_two_sided_balls_repetition_single.py +0 -76
  639. relationalai/experimental/paths/tests/tests_two_sided_balls_upto_multiple.py +0 -76
  640. relationalai/experimental/paths/tests/tests_two_sided_balls_upto_single.py +0 -110
  641. relationalai/experimental/paths/tests/tests_usp_nsp_multiple.py +0 -229
  642. relationalai/experimental/paths/tests/tests_usp_nsp_single.py +0 -108
  643. relationalai/experimental/paths/tree_agg.py +0 -168
  644. relationalai/experimental/paths/utilities/iterators.py +0 -27
  645. relationalai/experimental/paths/utilities/prefix_sum.py +0 -91
  646. relationalai/experimental/solvers.py +0 -1095
  647. relationalai/loaders/csv.py +0 -195
  648. relationalai/loaders/loader.py +0 -177
  649. relationalai/loaders/types.py +0 -23
  650. relationalai/rel_emitter.py +0 -373
  651. relationalai/rel_utils.py +0 -185
  652. relationalai/semantics/designs/query_builder/identify_by.md +0 -106
  653. relationalai/semantics/devtools/benchmark_lqp.py +0 -535
  654. relationalai/semantics/devtools/compilation_manager.py +0 -294
  655. relationalai/semantics/devtools/extract_lqp.py +0 -110
  656. relationalai/semantics/internal/internal.py +0 -3785
  657. relationalai/semantics/internal/snowflake.py +0 -329
  658. relationalai/semantics/lqp/README.md +0 -34
  659. relationalai/semantics/lqp/algorithms.py +0 -173
  660. relationalai/semantics/lqp/builtins.py +0 -213
  661. relationalai/semantics/lqp/compiler.py +0 -22
  662. relationalai/semantics/lqp/constructors.py +0 -68
  663. relationalai/semantics/lqp/executor.py +0 -518
  664. relationalai/semantics/lqp/export_rewriter.py +0 -40
  665. relationalai/semantics/lqp/intrinsics.py +0 -24
  666. relationalai/semantics/lqp/ir.py +0 -150
  667. relationalai/semantics/lqp/model2lqp.py +0 -1056
  668. relationalai/semantics/lqp/passes.py +0 -38
  669. relationalai/semantics/lqp/primitives.py +0 -252
  670. relationalai/semantics/lqp/result_helpers.py +0 -266
  671. relationalai/semantics/lqp/rewrite/__init__.py +0 -32
  672. relationalai/semantics/lqp/rewrite/algorithm.py +0 -385
  673. relationalai/semantics/lqp/rewrite/annotate_constraints.py +0 -69
  674. relationalai/semantics/lqp/rewrite/cdc.py +0 -216
  675. relationalai/semantics/lqp/rewrite/constants_to_vars.py +0 -70
  676. relationalai/semantics/lqp/rewrite/deduplicate_vars.py +0 -104
  677. relationalai/semantics/lqp/rewrite/eliminate_data.py +0 -108
  678. relationalai/semantics/lqp/rewrite/extract_common.py +0 -340
  679. relationalai/semantics/lqp/rewrite/extract_keys.py +0 -577
  680. relationalai/semantics/lqp/rewrite/flatten_script.py +0 -301
  681. relationalai/semantics/lqp/rewrite/function_annotations.py +0 -114
  682. relationalai/semantics/lqp/rewrite/functional_dependencies.py +0 -348
  683. relationalai/semantics/lqp/rewrite/period_math.py +0 -77
  684. relationalai/semantics/lqp/rewrite/quantify_vars.py +0 -339
  685. relationalai/semantics/lqp/rewrite/splinter.py +0 -76
  686. relationalai/semantics/lqp/rewrite/unify_definitions.py +0 -323
  687. relationalai/semantics/lqp/types.py +0 -101
  688. relationalai/semantics/lqp/utils.py +0 -170
  689. relationalai/semantics/lqp/validators.py +0 -70
  690. relationalai/semantics/metamodel/compiler.py +0 -134
  691. relationalai/semantics/metamodel/dependency.py +0 -880
  692. relationalai/semantics/metamodel/executor.py +0 -78
  693. relationalai/semantics/metamodel/factory.py +0 -287
  694. relationalai/semantics/metamodel/helpers.py +0 -368
  695. relationalai/semantics/metamodel/ir.py +0 -924
  696. relationalai/semantics/metamodel/rewrite/__init__.py +0 -8
  697. relationalai/semantics/metamodel/rewrite/discharge_constraints.py +0 -39
  698. relationalai/semantics/metamodel/rewrite/dnf_union_splitter.py +0 -220
  699. relationalai/semantics/metamodel/rewrite/extract_nested_logicals.py +0 -78
  700. relationalai/semantics/metamodel/rewrite/flatten.py +0 -590
  701. relationalai/semantics/metamodel/rewrite/format_outputs.py +0 -256
  702. relationalai/semantics/metamodel/rewrite/handle_aggregations_and_ranks.py +0 -237
  703. relationalai/semantics/metamodel/typer/checker.py +0 -355
  704. relationalai/semantics/metamodel/typer/typer.py +0 -1396
  705. relationalai/semantics/metamodel/util.py +0 -506
  706. relationalai/semantics/metamodel/visitor.py +0 -945
  707. relationalai/semantics/reasoners/__init__.py +0 -10
  708. relationalai/semantics/reasoners/graph/README.md +0 -620
  709. relationalai/semantics/reasoners/graph/__init__.py +0 -37
  710. relationalai/semantics/reasoners/graph/core.py +0 -9019
  711. relationalai/semantics/reasoners/graph/design/beyond_demand_transform.md +0 -797
  712. relationalai/semantics/reasoners/graph/tests/README.md +0 -21
  713. relationalai/semantics/reasoners/optimization/__init__.py +0 -68
  714. relationalai/semantics/reasoners/optimization/common.py +0 -88
  715. relationalai/semantics/reasoners/optimization/solvers_dev.py +0 -568
  716. relationalai/semantics/reasoners/optimization/solvers_pb.py +0 -1407
  717. relationalai/semantics/rel/builtins.py +0 -40
  718. relationalai/semantics/rel/compiler.py +0 -994
  719. relationalai/semantics/rel/executor.py +0 -363
  720. relationalai/semantics/rel/rel.py +0 -482
  721. relationalai/semantics/rel/rel_utils.py +0 -276
  722. relationalai/semantics/snowflake/__init__.py +0 -3
  723. relationalai/semantics/sql/compiler.py +0 -2503
  724. relationalai/semantics/sql/executor/duck_db.py +0 -52
  725. relationalai/semantics/sql/executor/result_helpers.py +0 -64
  726. relationalai/semantics/sql/executor/snowflake.py +0 -149
  727. relationalai/semantics/sql/rewrite/denormalize.py +0 -222
  728. relationalai/semantics/sql/rewrite/double_negation.py +0 -49
  729. relationalai/semantics/sql/rewrite/recursive_union.py +0 -127
  730. relationalai/semantics/sql/rewrite/sort_output_query.py +0 -246
  731. relationalai/semantics/sql/sql.py +0 -504
  732. relationalai/semantics/std/pragmas.py +0 -11
  733. relationalai/semantics/std/std.py +0 -14
  734. relationalai/semantics/tests/lqp/algorithms.py +0 -345
  735. relationalai/semantics/tests/test_snapshot_abstract.py +0 -144
  736. relationalai/semantics/tests/test_snapshot_base.py +0 -9
  737. relationalai/semantics/tests/utils.py +0 -46
  738. relationalai/std/__init__.py +0 -70
  739. relationalai/tools/cli.py +0 -2089
  740. relationalai/tools/cli_controls.py +0 -1975
  741. relationalai/tools/cli_helpers.py +0 -802
  742. relationalai/tools/debugger_client.py +0 -109
  743. relationalai/tools/debugger_server.py +0 -302
  744. relationalai/tools/dev.py +0 -685
  745. relationalai/tools/notes +0 -7
  746. relationalai/tools/qb_debugger.py +0 -425
  747. relationalai/tools/txn_progress.py +0 -188
  748. relationalai/util/clean_up_databases.py +0 -95
  749. relationalai/util/list_databases.py +0 -9
  750. relationalai/util/otel_configuration.py +0 -26
  751. relationalai/util/otel_handler.py +0 -484
  752. relationalai/util/snowflake_handler.py +0 -88
  753. relationalai/util/span_format_test.py +0 -43
  754. relationalai/util/span_tracker.py +0 -207
  755. relationalai/util/spans_file_handler.py +0 -72
  756. relationalai/util/tracing_handler.py +0 -34
  757. relationalai-0.13.5.dist-info/METADATA +0 -74
  758. relationalai-0.13.5.dist-info/RECORD +0 -473
  759. relationalai-0.13.5.dist-info/WHEEL +0 -4
  760. relationalai-0.13.5.dist-info/entry_points.txt +0 -3
  761. relationalai-0.13.5.dist-info/licenses/LICENSE +0 -202
  762. relationalai_test_util/__init__.py +0 -4
  763. relationalai_test_util/fixtures.py +0 -233
  764. relationalai_test_util/snapshot.py +0 -252
  765. relationalai_test_util/traceback.py +0 -118
  766. /relationalai/{analysis → semantics/frontend}/__init__.py +0 -0
  767. /relationalai/{auth/__init__.py → semantics/metamodel/metamodel_compiler.py} +0 -0
  768. /relationalai/{early_access → shims}/__init__.py +0 -0
  769. {relationalai/early_access/dsl/adapters → v0/relationalai/analysis}/__init__.py +0 -0
  770. {relationalai → v0/relationalai}/analysis/mechanistic.py +0 -0
  771. {relationalai → v0/relationalai}/analysis/whynot.py +0 -0
  772. {relationalai/early_access/dsl/adapters/orm → v0/relationalai/auth}/__init__.py +0 -0
  773. {relationalai → v0/relationalai}/auth/jwt_generator.py +0 -0
  774. {relationalai → v0/relationalai}/auth/oauth_callback_server.py +0 -0
  775. {relationalai → v0/relationalai}/auth/token_handler.py +0 -0
  776. {relationalai → v0/relationalai}/auth/util.py +0 -0
  777. {relationalai/clients/resources/snowflake → v0/relationalai/clients}/cache_store.py +0 -0
  778. {relationalai → v0/relationalai}/compiler.py +0 -0
  779. {relationalai → v0/relationalai}/dependencies.py +0 -0
  780. {relationalai → v0/relationalai}/docutils.py +0 -0
  781. {relationalai/early_access/dsl/adapters/owl → v0/relationalai/early_access}/__init__.py +0 -0
  782. {relationalai → v0/relationalai}/early_access/dsl/__init__.py +0 -0
  783. {relationalai/early_access/dsl/bindings → v0/relationalai/early_access/dsl/adapters}/__init__.py +0 -0
  784. {relationalai/early_access/dsl/bindings/legacy → v0/relationalai/early_access/dsl/adapters/orm}/__init__.py +0 -0
  785. {relationalai → v0/relationalai}/early_access/dsl/adapters/orm/model.py +0 -0
  786. {relationalai/early_access/dsl/codegen → v0/relationalai/early_access/dsl/adapters/owl}/__init__.py +0 -0
  787. {relationalai → v0/relationalai}/early_access/dsl/adapters/owl/model.py +0 -0
  788. {relationalai/early_access/dsl/core/temporal → v0/relationalai/early_access/dsl/bindings}/__init__.py +0 -0
  789. {relationalai/early_access/dsl/ir → v0/relationalai/early_access/dsl/bindings/legacy}/__init__.py +0 -0
  790. {relationalai/early_access/dsl/ontologies → v0/relationalai/early_access/dsl/codegen}/__init__.py +0 -0
  791. {relationalai → v0/relationalai}/early_access/dsl/constants.py +0 -0
  792. {relationalai → v0/relationalai}/early_access/dsl/core/__init__.py +0 -0
  793. {relationalai → v0/relationalai}/early_access/dsl/core/constraints/__init__.py +0 -0
  794. {relationalai → v0/relationalai}/early_access/dsl/core/constraints/predicate/__init__.py +0 -0
  795. {relationalai → v0/relationalai}/early_access/dsl/core/stack.py +0 -0
  796. {relationalai/early_access/dsl/orm → v0/relationalai/early_access/dsl/core/temporal}/__init__.py +0 -0
  797. {relationalai → v0/relationalai}/early_access/dsl/core/utils.py +0 -0
  798. {relationalai/early_access/dsl/orm/measures → v0/relationalai/early_access/dsl/ir}/__init__.py +0 -0
  799. {relationalai/early_access/dsl/physical_metadata → v0/relationalai/early_access/dsl/ontologies}/__init__.py +0 -0
  800. {relationalai → v0/relationalai}/early_access/dsl/ontologies/raw_source.py +0 -0
  801. {relationalai/early_access/dsl/serialize → v0/relationalai/early_access/dsl/orm}/__init__.py +0 -0
  802. {relationalai/early_access/dsl/snow → v0/relationalai/early_access/dsl/orm/measures}/__init__.py +0 -0
  803. {relationalai → v0/relationalai}/early_access/dsl/orm/reasoner_errors.py +0 -0
  804. {relationalai/loaders → v0/relationalai/early_access/dsl/physical_metadata}/__init__.py +0 -0
  805. {relationalai/semantics/tests → v0/relationalai/early_access/dsl/serialize}/__init__.py +0 -0
  806. {relationalai → v0/relationalai}/early_access/dsl/serialize/binding_model.py +0 -0
  807. {relationalai → v0/relationalai}/early_access/dsl/serialize/model.py +0 -0
  808. {relationalai/semantics/tests/lqp → v0/relationalai/early_access/dsl/snow}/__init__.py +0 -0
  809. {relationalai → v0/relationalai}/early_access/tests/__init__.py +0 -0
  810. {relationalai → v0/relationalai}/environments/ci.py +0 -0
  811. {relationalai → v0/relationalai}/environments/hex.py +0 -0
  812. {relationalai → v0/relationalai}/environments/terminal.py +0 -0
  813. {relationalai → v0/relationalai}/experimental/__init__.py +0 -0
  814. {relationalai → v0/relationalai}/experimental/graphs.py +0 -0
  815. {relationalai → v0/relationalai}/experimental/paths/__init__.py +0 -0
  816. {relationalai → v0/relationalai}/experimental/paths/benchmarks/__init__.py +0 -0
  817. {relationalai → v0/relationalai}/experimental/paths/path_algorithms/__init__.py +0 -0
  818. {relationalai → v0/relationalai}/experimental/paths/rpq/__init__.py +0 -0
  819. {relationalai → v0/relationalai}/experimental/paths/rpq/filter.py +0 -0
  820. {relationalai → v0/relationalai}/experimental/paths/rpq/glushkov.py +0 -0
  821. {relationalai → v0/relationalai}/experimental/paths/rpq/transition.py +0 -0
  822. {relationalai → v0/relationalai}/experimental/paths/utilities/__init__.py +0 -0
  823. {relationalai → v0/relationalai}/experimental/paths/utilities/utilities.py +0 -0
  824. {relationalai/tools → v0/relationalai/loaders}/__init__.py +0 -0
  825. {relationalai → v0/relationalai}/metagen.py +0 -0
  826. {relationalai → v0/relationalai}/metamodel.py +0 -0
  827. {relationalai → v0/relationalai}/rel.py +0 -0
  828. {relationalai → v0/relationalai}/semantics/devtools/__init__.py +0 -0
  829. {relationalai → v0/relationalai}/semantics/internal/__init__.py +0 -0
  830. {relationalai → v0/relationalai}/semantics/internal/annotations.py +0 -0
  831. {relationalai → v0/relationalai}/semantics/lqp/__init__.py +0 -0
  832. {relationalai → v0/relationalai}/semantics/lqp/pragmas.py +0 -0
  833. {relationalai → v0/relationalai}/semantics/metamodel/dataflow.py +0 -0
  834. {relationalai → v0/relationalai}/semantics/metamodel/typer/__init__.py +0 -0
  835. {relationalai → v0/relationalai}/semantics/metamodel/types.py +0 -0
  836. {relationalai → v0/relationalai}/semantics/reasoners/experimental/__init__.py +0 -0
  837. {relationalai → v0/relationalai}/semantics/rel/__init__.py +0 -0
  838. {relationalai → v0/relationalai}/semantics/sql/__init__.py +0 -0
  839. {relationalai → v0/relationalai}/semantics/sql/executor/__init__.py +0 -0
  840. {relationalai → v0/relationalai}/semantics/sql/rewrite/__init__.py +0 -0
  841. {relationalai → v0/relationalai}/semantics/tests/logging.py +0 -0
  842. {relationalai → v0/relationalai}/std/aggregates.py +0 -0
  843. {relationalai → v0/relationalai}/std/dates.py +0 -0
  844. {relationalai → v0/relationalai}/std/graphs.py +0 -0
  845. {relationalai → v0/relationalai}/std/inspect.py +0 -0
  846. {relationalai → v0/relationalai}/std/math.py +0 -0
  847. {relationalai → v0/relationalai}/std/re.py +0 -0
  848. {relationalai → v0/relationalai}/std/strings.py +0 -0
  849. {relationalai → v0/relationalai}/tools/cleanup_snapshots.py +0 -0
  850. {relationalai → v0/relationalai}/tools/constants.py +0 -0
  851. {relationalai → v0/relationalai}/tools/query_utils.py +0 -0
  852. {relationalai → v0/relationalai}/tools/snapshot_viewer.py +0 -0
  853. {relationalai → v0/relationalai}/util/__init__.py +0 -0
  854. {relationalai → v0/relationalai}/util/constants.py +0 -0
  855. {relationalai → v0/relationalai}/util/graph.py +0 -0
  856. {relationalai → v0/relationalai}/util/timeout.py +0 -0
@@ -0,0 +1,2478 @@
1
+ from __future__ import annotations
2
+ import sys
3
+ from types import TracebackType
4
+ import ast
5
+ import io
6
+ import json
7
+ import re
8
+ import textwrap
9
+ from typing import Any, List, Literal, NamedTuple, Tuple
10
+
11
+ import rich.markup
12
+ from rich.console import Console
13
+ from rich.text import Text
14
+ from rich.table import Table
15
+ from rich import box
16
+ from enum import Enum
17
+ import contextlib
18
+ import contextvars
19
+
20
+ from .metamodel import Action, Task
21
+ from .environments import runtime_env, IPythonEnvironment, SnowbookEnvironment
22
+ from . import debugging
23
+ from .tools.constants import SHOW_FULL_TRACES
24
+
25
+
26
+ #--------------------------------------------------
27
+ # Print helpers
28
+ #--------------------------------------------------
29
+
30
+
31
+ _current_console = contextvars.ContextVar('current_console', default=None)
32
+
33
+ def get_console(*args, **kwargs):
34
+ current = _current_console.get()
35
+ if current is not None:
36
+ return current
37
+ return Console(*args, **kwargs)
38
+
39
+ @contextlib.contextmanager
40
+ def using_console(console):
41
+ token = _current_console.set(console)
42
+ try:
43
+ yield
44
+ finally:
45
+ _current_console.reset(token)
46
+
47
+ @contextlib.contextmanager
48
+ def record(retain_buffer = False):
49
+ buffer = io.StringIO()
50
+ console = Console(record=True, file=buffer)
51
+ with using_console(console):
52
+ try:
53
+ yield (console, buffer)
54
+ finally:
55
+ if not retain_buffer:
56
+ buffer.close()
57
+
58
+ def rich_str(item:Any, style:str|None = None) -> str:
59
+ output = io.StringIO()
60
+ console = Console(file=output, force_terminal=True)
61
+ console.print(item, style=style)
62
+ return output.getvalue()
63
+
64
+ def body_text(console, body:str):
65
+ body = textwrap.dedent(body)
66
+ for line in body.splitlines():
67
+ if not line.startswith(" "):
68
+ console.print(line)
69
+ else:
70
+ console.print(line, soft_wrap=True)
71
+
72
+ def mark_source(source: debugging.SourceInfo|None, start_line:int|None=None, end_line:int|None=None, indent=8, highlight="yellow", highlight_lines = []):
73
+ if source is None:
74
+ return ""
75
+
76
+ final_lines = []
77
+ all_lines = source.source.splitlines()
78
+ source_start = source.source_start_line or source.line or start_line or 0
79
+ start_line = start_line if start_line is not None else source.line
80
+ max_line = source.line + len(all_lines)
81
+ end_line = end_line if end_line is not None else max_line
82
+ line_number_len = len(str(max_line))
83
+ for ix, line in enumerate(source.source.splitlines()):
84
+ cur = line
85
+ cur_indent = indent if ix > 0 else 0
86
+ line_number = source_start + ix
87
+ color = "dim white"
88
+ if (line_number >= start_line and line_number <= end_line) or line_number in highlight_lines:
89
+ color = highlight + " bold"
90
+ cur = f"{' '*cur_indent}[{color}] {line_number :>{line_number_len}} | {cur}[/{color}]"
91
+ final_lines.append(cur)
92
+ return "\n".join(final_lines)
93
+
94
+ def print_source_error(source, name:str, content:str, color="red"):
95
+ fixed_content_length = len(name) + len(source.file) + len(str(source.line)) + 2 # 2 for the spaces around the dash
96
+ num_dashes = 74 - fixed_content_length
97
+ dashes = '-' * num_dashes
98
+ console = get_console(width=80, force_jupyter=False, stderr=True)
99
+ console.print("\n")
100
+ console.print(f"[{color}]--- {name} {dashes} {source.file}: {source.line}")
101
+ console.print()
102
+ body_text(console, content)
103
+ console.print()
104
+ console.print(f'[{color}]{"-" * 80}')
105
+ console.print()
106
+
107
+ def print_error(name:str, content:str, color="red"):
108
+ fixed_content_length = len(name) + 2 # 2 for the spaces around the dash
109
+ num_dashes = 76 - fixed_content_length
110
+ dashes = '-' * num_dashes
111
+ console = get_console(width=80, force_jupyter=False, stderr=True)
112
+ console.print("\n")
113
+ console.print(f"[{color}]--- {name} {dashes}")
114
+ console.print()
115
+ body_text(console, content)
116
+ console.print()
117
+ console.print(f'[{color}]{"-" * 80}')
118
+ console.print()
119
+
120
+ def print_error_name(name:str, color="red"):
121
+ console = get_console(width=80, force_jupyter=False, stderr=True)
122
+ console.print("\n")
123
+ console.print(f'[{color}]{"-" * 80}')
124
+ console.print()
125
+ body_text(console, name)
126
+ console.print()
127
+ console.print(f'[{color}]{"-" * 80}')
128
+ console.print()
129
+
130
+ SuggestionEnv = Literal["cli", "notebook", "python"]
131
+ class Suggestion(NamedTuple):
132
+ env: SuggestionEnv
133
+ cmd: str
134
+ style: str|None = "green"
135
+
136
+ def suggest(suggestions: list[Suggestion], prefix = "with the following command", prefix_multi: str|None = None):
137
+ """Format a list of suggestions, adapted to the current runtime environment."""
138
+ if prefix_multi is None:
139
+ prefix_multi = prefix + "s"
140
+
141
+ relevant: list[Suggestion] = []
142
+ for suggestion in suggestions:
143
+ relevant += adapt_to_env(suggestion)
144
+
145
+ if len(relevant) == 1:
146
+ return "\n".join([
147
+ prefix + ":",
148
+ "",
149
+ fmt_suggestion(relevant[0]),
150
+ ])
151
+ else:
152
+ return "\n".join([
153
+ prefix_multi + ":",
154
+ "",
155
+ "\n\n".join(fmt_suggestion(suggestion) for suggestion in relevant)
156
+ ])
157
+
158
+ def fmt_suggestion(suggestion: Suggestion) -> str:
159
+ """Format a single styled suggestion."""
160
+ cmd = textwrap.dedent(suggestion.cmd)
161
+ return "\n".join(f"[{suggestion.style}]{line}[/{suggestion.style}]" for line in cmd.splitlines()) if suggestion.style else cmd
162
+
163
+ def adapt_to_env(suggestion: Suggestion) -> list[Suggestion]:
164
+ """Adapt a suggestion to work in the current runtime environment if possible, or exclude it if not."""
165
+ if suggestion.env == "cli":
166
+ if isinstance(runtime_env, SnowbookEnvironment) and runtime_env.runner == "warehouse":
167
+ return []
168
+ if isinstance(runtime_env, (IPythonEnvironment, SnowbookEnvironment)):
169
+ return [Suggestion("notebook", "\n".join(f"! {subcmd}" for subcmd in partition_by_indent(textwrap.dedent(suggestion.cmd))), suggestion.style)]
170
+ return [suggestion]
171
+ else:
172
+ return [suggestion]
173
+
174
+ def partition_by_indent(text):
175
+ # Split on newlines followed by non-whitespace
176
+ groups = re.split(r'\n(?=\S)', text)
177
+ # Strip any trailing whitespace from each group
178
+ return [group.rstrip() for group in groups]
179
+
180
+ #--------------------------------------------------
181
+ # Transformers
182
+ #--------------------------------------------------
183
+
184
+ class IfToWithTransformer(ast.NodeTransformer):
185
+ def visit_If(self, node):
186
+ with_node = ast.With(
187
+ items=[ast.withitem(context_expr=node.test, optional_vars=None)],
188
+ body=node.body,
189
+ lineno=node.lineno,
190
+ type_comment=None)
191
+ return with_node
192
+
193
+ class WithDynamic(ast.NodeTransformer):
194
+ def visit_With(self, node):
195
+ content = ast.unparse(node.items[0].context_expr).replace(")", "dynamic=True)")
196
+ with_node = ast.With(
197
+ items=[ast.withitem(context_expr=ast.Name(id=content), optional_vars=node.items[0].optional_vars)],
198
+ body=[],
199
+ lineno=node.lineno,
200
+ type_comment=None)
201
+ return with_node
202
+
203
+ class SetToMethod(ast.NodeTransformer):
204
+ def visit_Assign(self, node):
205
+ if isinstance(node.targets[0], ast.Attribute):
206
+ keyword = ast.keyword(arg=node.targets[0].attr, value=node.value)
207
+ return ast.Expr(value=ast.Call(
208
+ func=ast.Attribute(value=node.targets[0].value, attr="set", ctx=ast.Load()),
209
+ args=[],
210
+ keywords=[keyword],
211
+ lineno=node.lineno
212
+ ))
213
+ return node
214
+
215
+ class AssignToCompare(ast.NodeTransformer):
216
+ def visit_Assign(self, node):
217
+ if isinstance(node.targets[0], ast.Attribute) and len(node.targets) == 1:
218
+ compare_node = ast.Compare(
219
+ left=node.targets[0],
220
+ ops=[ast.Eq()],
221
+ comparators=[node.value]
222
+ )
223
+ expr_node = ast.Expr(value=compare_node)
224
+ ast.copy_location(expr_node, node)
225
+
226
+ return expr_node
227
+ return node
228
+
229
+ class PropertyNameReplacer(ast.NodeTransformer):
230
+ def __init__(self, old_name, new_name):
231
+ self.old_name = old_name
232
+ self.new_name = new_name
233
+
234
+ def visit_Attribute(self, node):
235
+ # Check if the attribute name matches the old name
236
+ if isinstance(node.attr, str) and node.attr == self.old_name:
237
+ # Replace the attribute name with the new name
238
+ node.attr = self.new_name
239
+ return node
240
+
241
+ def visit_Name(self, node):
242
+ # Check if the variable name matches the old name
243
+ if isinstance(node.id, str) and node.id == self.old_name:
244
+ # Replace the variable name with the new name
245
+ node.id = self.new_name
246
+ return node
247
+
248
+ #--------------------------------------------------
249
+ # Finders
250
+ #--------------------------------------------------
251
+
252
+ class PropertyFinder(ast.NodeVisitor):
253
+ def __init__(self, start_line, properties):
254
+ self.errors = []
255
+ self.start_line = start_line
256
+ self.properties = properties
257
+ self.found_properties_lines = [] # To store lines where properties are found
258
+ self.dynamic_properties = [] # To store dynamic properties
259
+
260
+ def to_line_numbers(self, node):
261
+ return (node.lineno, node.end_lineno)
262
+
263
+ def visit_Attribute(self, node):
264
+ if node.attr in self.properties:
265
+ line_numbers = self.to_line_numbers(node)
266
+ if line_numbers[0] >= self.start_line:
267
+ self.found_properties_lines.append(node.lineno)
268
+ self.generic_visit(node)
269
+
270
+ def visit_Call(self, node):
271
+ # Check if this is a call to 'getattr'
272
+ if (isinstance(node.func, ast.Name) and node.func.id == 'getattr' and
273
+ len(node.args) >= 2):
274
+ if isinstance(node.args[1], ast.Str):
275
+ property_name = node.args[1].s
276
+ if property_name in self.properties:
277
+ line_numbers = self.to_line_numbers(node)
278
+ if line_numbers[0] >= self.start_line:
279
+ self.found_properties_lines.append(node.lineno)
280
+ else:
281
+ line_numbers = self.to_line_numbers(node)
282
+ if line_numbers[0] >= self.start_line:
283
+ self.dynamic_properties.append(node.lineno)
284
+ self.generic_visit(node)
285
+
286
+ #--------------------------------------------------
287
+ # Metaclass
288
+ #--------------------------------------------------
289
+
290
+ class RAIPostInitMeta(type):
291
+ def __call__(cls, *args, **kwargs):
292
+ instance = super().__call__(*args, **kwargs)
293
+ instance.__post_init__()
294
+ return instance
295
+
296
+ # --------------------------------------------------
297
+ # RAIException
298
+ # --------------------------------------------------
299
+
300
+ class RAIException(Exception, metaclass=RAIPostInitMeta):
301
+ def __init__(self, message, name=None, content=None, source: debugging.SourceInfo|None=None, **kwargs):
302
+ super().__init__(message)
303
+ self.name = name
304
+ self.message = message
305
+ self.content = content
306
+ self.source = source
307
+
308
+ # Store any additional keyword arguments as attributes
309
+ for key, value in kwargs.items():
310
+ setattr(self, key, value)
311
+
312
+ def __str__(self):
313
+ return self.message
314
+
315
+ def __post_init__(self):
316
+ self.raw_content = self.strip_rich_tags()
317
+
318
+ def pprint(self):
319
+ if self.source and self.content:
320
+ print_source_error(self.source, self.name or self.message, self.content.strip())
321
+ elif self.content:
322
+ print_error(self.name or self.message, self.content.strip())
323
+ else:
324
+ print_error_name(self.name or self.message)
325
+
326
+ def __eq__(self, other):
327
+ if isinstance(other, RAIException):
328
+ return self.message == other.message and self.__dict__ == other.__dict__
329
+ return False
330
+
331
+ def strip_rich_tags(self):
332
+ if self.content:
333
+ # Use Text.from_markup to remove rich tags
334
+ plain_text = Text.from_markup(self.content).plain
335
+ # Split the content into lines
336
+ lines = plain_text.split('\n')
337
+ # Remove leading empty lines or lines with only whitespace
338
+ while lines and not lines[0].strip():
339
+ lines.pop(0)
340
+ # Strip leading whitespace from the first non-empty line
341
+ if lines:
342
+ lines[0] = lines[0].lstrip()
343
+ # Join the lines back together
344
+ cleaned_content = '\n'.join(lines)
345
+ return cleaned_content
346
+ else:
347
+ return self.message
348
+
349
+ def clone(self, config=None):
350
+ show_full_traces = SHOW_FULL_TRACES
351
+ if config is not None:
352
+ show_full_traces = config.get("show_full_traces", SHOW_FULL_TRACES)
353
+
354
+ if show_full_traces:
355
+ return self
356
+
357
+ try:
358
+ raise AssertionError
359
+ except AssertionError:
360
+ traceback = sys.exc_info()[2]
361
+ back_frame = traceback.tb_frame.f_back if traceback else None
362
+
363
+ if back_frame is None:
364
+ return self.with_traceback(traceback)
365
+
366
+ back_tb = TracebackType(tb_next=None,
367
+ tb_frame=back_frame,
368
+ tb_lasti=back_frame.f_lasti,
369
+ tb_lineno=back_frame.f_lineno)
370
+ return self.with_traceback(back_tb)
371
+
372
+ def message_for_environment(self, message, cli_command, provider_call):
373
+ if isinstance(runtime_env, SnowbookEnvironment):
374
+ return textwrap.dedent(f"""
375
+ {message} with the following call:
376
+
377
+ [green]{provider_call}[/green]
378
+ """)
379
+ else:
380
+ return textwrap.dedent(f"""
381
+ {message} with either of the following commands:
382
+
383
+ [green]{cli_command}[/green]
384
+
385
+ [green]{provider_call}[/green]
386
+ """)
387
+
388
+ class NonExistentConfigFileError(RAIException):
389
+ def __init__(self, file_name: str):
390
+ self.file_name = file_name
391
+ self.message = f"The specified configuration file '{file_name}' does not exist."
392
+ self.name = "Configuration file not found"
393
+
394
+ # Format the content and raw_content
395
+ self.content = self.format_message()
396
+
397
+ super().__init__(self.message, self.name, self.content)
398
+
399
+ def format_message(self):
400
+ return textwrap.dedent(f"""
401
+ Configuration file '{self.file_name}' does not exist.
402
+
403
+ To initialize one, use:
404
+
405
+ [green]rai init[/green]
406
+ """)
407
+
408
+ class InvalidActiveProfileError(RAIException):
409
+ def __init__(self, active_profile_name: str):
410
+ self.active_profile_name = active_profile_name
411
+ self.message = f"The specified active profile '{self.active_profile_name}' set in the config is not found."
412
+ self.name = f"Active profile '{active_profile_name}' not found"
413
+
414
+ # Format the content and raw_content
415
+ self.content = self.format_message()
416
+
417
+ super().__init__(self.message, self.name, self.content)
418
+
419
+ def format_message(self):
420
+ return textwrap.dedent(f"""
421
+ The specified active profile '{self.active_profile_name}' is not found.
422
+ Please check your configuration and ensure that the profile exists.
423
+
424
+ To switch to a different profile, use the following command:
425
+
426
+ [green]rai profile:switch[/green]
427
+
428
+ To create a new profile, use:
429
+
430
+ [green]rai init[/green]
431
+ """)
432
+
433
+ class ModelError(RAIException):
434
+ error_locations = {}
435
+
436
+ def __init__(self, problems):
437
+ super().__init__("Error object added")
438
+ problem = problems[0]
439
+ self.problems = problems
440
+ self.source = ModelError.error_locations.get(problem["props"]["pyrel_id"], debugging.SourceInfo())
441
+ self.message = problem["message"]
442
+ self.name = "Model error"
443
+ self.content = self.format_message()
444
+
445
+ def get_formatted_props_string(self):
446
+ # Create a table
447
+ table = Table(show_header=True, box=box.ROUNDED, padding=(0, 1))
448
+ ks = list([k for k in self.problems[0]["props"].keys() if not k.startswith("pyrel_")])
449
+ for k in ks:
450
+ table.add_column(k)
451
+
452
+ for problem in self.problems:
453
+ row = []
454
+ for k in ks:
455
+ row.append(str(problem["props"].get(k, "")))
456
+ table.add_row(*row)
457
+
458
+ with io.StringIO() as string_io:
459
+ console = Console(file=string_io)
460
+ console.print(table)
461
+ table_string = string_io.getvalue()
462
+
463
+ return table_string
464
+
465
+ def format_message(self):
466
+ row_str = self.get_formatted_props_string()
467
+
468
+ marked = mark_source(self.source, self.source.line, self.source.line, indent=2) if self.source else ""
469
+ return "\n\n".join([self.message, " " + marked, row_str])
470
+
471
+
472
+ class RelQueryError(RAIException):
473
+ def __init__(self, problem, source: debugging.SourceInfo|None = None):
474
+ super().__init__("Rel query error")
475
+ self.problem = problem
476
+ self.source = source
477
+ self.message = "Query error"
478
+ self.name = "Query error"
479
+ self.content = self.format_message()
480
+
481
+ def format_message(self):
482
+ problem = self.problem
483
+ marked = mark_source(self.source, -1, -1) if self.source else ""
484
+ return textwrap.dedent(f"""
485
+ {rich.markup.escape(problem["report"])}
486
+ {rich.markup.escape(problem["message"])}
487
+
488
+ {marked}
489
+ """)
490
+
491
+ class RAIExceptionSet(RAIException):
492
+ def __init__(self, exceptions: List[RAIException]):
493
+ super().__init__("Multiple Errors, see above")
494
+ self.exceptions = exceptions
495
+
496
+ def pprint(self):
497
+ for exception in self.exceptions:
498
+ exception.pprint()
499
+
500
+ def clone(self, config=None):
501
+ return RAIExceptionSet([exception.clone(config) for exception in self.exceptions])
502
+
503
+ # --------------------------------------------------
504
+ # Warning
505
+ # --------------------------------------------------
506
+
507
+ class RAIWarning(Warning, metaclass=RAIPostInitMeta):
508
+ name = ""
509
+ message: str|None = None
510
+ content = None
511
+ source = None
512
+
513
+ def __init__(self, message="", name=None, content=None, source=None):
514
+ self.name = name
515
+ self.message = message
516
+ self.content = content
517
+ self.source = source
518
+
519
+ def pprint(self):
520
+ msg = self.name or self.message or ""
521
+ if self.source and self.content:
522
+ print_source_error(self.source, msg, self.content.strip(), color="yellow")
523
+ elif self.content:
524
+ print_error(msg, self.content.strip(), color="yellow")
525
+ else:
526
+ print_error_name(msg, color="yellow")
527
+
528
+ def __post_init__(self):
529
+ self.raw_content = self.strip_rich_tags()
530
+ debugging.warn(self)
531
+
532
+ def strip_rich_tags(self):
533
+ if self.content:
534
+ # Use Text.from_markup to remove rich tags
535
+ plain_text = Text.from_markup(self.content).plain
536
+ # Split the content into lines
537
+ lines = plain_text.split('\n')
538
+ # Remove leading empty lines or lines with only whitespace
539
+ while lines and not lines[0].strip():
540
+ lines.pop(0)
541
+ # Strip leading whitespace from the first non-empty line
542
+ if lines:
543
+ lines[0] = lines[0].lstrip()
544
+ # Join the lines back together
545
+ cleaned_content = '\n'.join(lines)
546
+
547
+ return cleaned_content
548
+ else:
549
+ return self.name
550
+
551
+ def __str__(self):
552
+ return f"{self.name}: {self.raw_content}"
553
+
554
+ class RelQueryWarning(RAIWarning):
555
+ def __init__(self, problem, source):
556
+ super().__init__("Query warning")
557
+ self.name = "Query warning"
558
+ self.problem = problem
559
+ self.source = source
560
+ self.message = "Query warning"
561
+ self.content = self.format_message()
562
+
563
+ def format_message(self):
564
+ problem = self.problem
565
+ marked = mark_source(self.source, -1, -1)
566
+ return textwrap.dedent(f"""
567
+ {problem["report"]}
568
+ {problem["message"]}
569
+
570
+ {marked}
571
+ """)
572
+
573
+ class UnknownSourceWarning(RAIWarning):
574
+ def __init__(self, source_fqns: list[str], role: str | None = None):
575
+ name = "Unknown Source"
576
+
577
+ if len(source_fqns) == 1:
578
+ msg = textwrap.fill(
579
+ f"Unable to access source '{source_fqns[0]}'. Ensure the fully qualified name is correct "
580
+ f"and that your active role(s) have the required privileges.",
581
+ 78,
582
+ )
583
+ else:
584
+ prefix = "The following sources could not be accessed:"
585
+ suffix = textwrap.fill(
586
+ "Ensure the fully qualified names are correct and that your active role(s) have the required privileges.",
587
+ 78,
588
+ )
589
+ msg = "\n".join(
590
+ [prefix, "", *[f"- {fqn}" for fqn in source_fqns], "", suffix]
591
+ )
592
+
593
+ if role:
594
+ msg += f"\n\nYour current primary role is '{role}', and by default all assigned roles are used as secondary roles."
595
+
596
+ msg += "\n\n" + textwrap.fill(
597
+ "While access via secondary roles is supported, we recommend granting the necessary privileges "
598
+ "directly to your primary role to ensure consistent access.",
599
+ 78,
600
+ )
601
+
602
+ super().__init__(name, name, msg)
603
+ self.source_fqns = source_fqns
604
+
605
+ class InvalidSourceTypeWarning(RAIWarning):
606
+ def __init__(self, source_types: dict[str, str]):
607
+ if len(source_types) == 1:
608
+ [(fqn, type)] = source_types.items()
609
+ msg = f"RAI currently only supports tables and views.\n The source '{fqn}' has type '{type}'"
610
+ else:
611
+ prefix = "RAI currently only supports tables and views. These have other types:"
612
+ msg = "\n".join([
613
+ prefix,
614
+ "",
615
+ *[f"- {fqn} ({type})" for fqn, type in source_types.items()]
616
+ ])
617
+
618
+ name = "Invalid source type"
619
+ super().__init__(name, name, msg)
620
+ self.source_types = source_types
621
+
622
+ class IntegrityConstraintViolation(RAIException):
623
+ def __init__(self, violation, source=None):
624
+ self.violation = violation
625
+ self.source = source
626
+
627
+ body = violation["message"]
628
+ self.name = "Integrity constraint violation"
629
+ self.message = "Integrity constraint violation"
630
+ self.content = self.format_message(body)
631
+ super().__init__(self.message, self.name, self.content, self.source)
632
+
633
+ def format_message(self, body):
634
+ marked = mark_source(self.source, -1, -1)
635
+
636
+ f"Integrity constraint violation\n{body}"
637
+
638
+ return textwrap.dedent(f"""
639
+ {body}
640
+
641
+ {marked}
642
+ """)
643
+
644
+ def clone(self, config=None):
645
+ return IntegrityConstraintViolation(self.violation, self.source)
646
+
647
+ class ModelWarning(RAIWarning):
648
+ error_locations = {}
649
+
650
+ def __init__(self, problems):
651
+ super().__init__("Error object added")
652
+ problem = problems[0]
653
+ self.problems = problems
654
+ self.source = ModelError.error_locations.get(problem["props"]["pyrel_id"], debugging.SourceInfo())
655
+ self.message = problem["message"]
656
+ self.name = "Model warning"
657
+ self.content = self.format_message()
658
+
659
+ def get_formatted_props_string(self):
660
+ # Create a table
661
+ table = Table(show_header=True, box=box.ROUNDED, padding=(0, 1))
662
+ ks = list([k for k in self.problems[0]["props"].keys() if not k.startswith("pyrel_")])
663
+ for k in ks:
664
+ table.add_column(k)
665
+
666
+ for problem in self.problems:
667
+ row = []
668
+ for k in ks:
669
+ row.append(str(problem["props"].get(k, "")))
670
+ table.add_row(*row)
671
+
672
+ with io.StringIO() as string_io:
673
+ console = Console(file=string_io)
674
+ console.print(table)
675
+ table_string = string_io.getvalue()
676
+
677
+ return table_string
678
+
679
+ def format_message(self):
680
+ row_str = self.get_formatted_props_string()
681
+
682
+ marked = mark_source(self.source, self.source.line, self.source.line, indent=2) if self.source else ""
683
+ return "\n\n".join([self.message or "", " " + marked, row_str])
684
+
685
+ class RowsDroppedFromTargetTableWarning(RAIWarning):
686
+ def __init__(self, rejected_rows: list[dict], rejected_rows_count: int, col_names_map: dict):
687
+ """
688
+ Warning raised when loading data into a table (exec_into_table API) and some rows are rejected due to erroneous data.
689
+ """
690
+ self.parsing_errors = self.replace_column_names(rejected_rows, col_names_map)
691
+ self.name = "Rejected rows"
692
+ self.content = self.format_message(rejected_rows, rejected_rows_count, self.parsing_errors)
693
+ self.rejected_rows = rejected_rows
694
+ self.rejected_rows_count = rejected_rows_count
695
+ self.col_names_map = col_names_map
696
+ super().__init__(self.name, self.name, self.content)
697
+
698
+ def format_message(self, rejected_rows: list[dict], rejected_rows_count: int, parsing_errors: list[dict]) -> str:
699
+ grouped_errors = {}
700
+
701
+ # Group errors by ROW_NUMBER
702
+ for i, row in enumerate(rejected_rows):
703
+ error_indices = {e["index"] for e in parsing_errors}
704
+ if i in error_indices:
705
+ continue
706
+ try:
707
+ row_number = row['ROW_NUMBER']
708
+ if row_number not in grouped_errors:
709
+ grouped_errors[row_number] = {'rejected_record': row['REJECTED_RECORD'], 'errors': []}
710
+ grouped_errors[row_number]['errors'].append(f"Erroneous column: {row['COLUMN_NAME']}\nError message: {row['ERROR']}")
711
+ except Exception as e:
712
+ parsing_errors.append({"index": i, "message": str(e), "row": str(row)})
713
+
714
+ msg = f"Your data has been loaded but {rejected_rows_count} rows were skipped due to erroneous data. Here are the first {len(grouped_errors)} rejected rows:\n"
715
+ for row_number, data in grouped_errors.items():
716
+ msg += f"""Rejected record: {data['rejected_record']}"""
717
+ for error in data['errors']:
718
+ msg += f"- {error}\n"
719
+ msg += "\n"
720
+
721
+ return msg
722
+
723
+ """Replaces the autogenerated pyrel COLUMN_NAME in rejected_rows with the actual column names of the target table according to the mapping in col_names_map."""
724
+ def replace_column_names(self, rejected_rows: list[dict], col_names_map: dict) -> list[dict]:
725
+ # col_names_map looks like this: {col000: TIMESTAMPS, col002: id}
726
+ # COLUMN_NAME in rejected_rows looks like this: "OUT23A38988_10B1_4BCB_B566_89F7F3C8D1FB_INTERNAL"["COL000":1]
727
+
728
+ parsing_errors = []
729
+
730
+ # Convert col_names_map keys to lowercase for case-insensitive matching
731
+ col_names_map = {key.lower(): value for key, value in col_names_map.items()}
732
+ for i, row in enumerate(rejected_rows):
733
+ try:
734
+ col_name = row['COLUMN_NAME'].split('[')[1].split(':')[0].replace('"', '').strip().lower()
735
+ if col_name in col_names_map:
736
+ row['COLUMN_NAME'] = col_names_map[col_name]
737
+ except Exception as e:
738
+ parsing_errors.append({"index": i, "message": str(e), "row": str(row)})
739
+ return parsing_errors
740
+
741
+ # --------------------------------------------------
742
+ # ERP Exceptions
743
+ # --------------------------------------------------
744
+
745
+ class ERPNotRunningError(RAIException):
746
+ def __init__(self):
747
+ super().__init__("ERP service state")
748
+ self.name = "ERP service state"
749
+ self.content = self.format_message()
750
+
751
+ def format_message(self):
752
+ return textwrap.dedent("""
753
+ The ERP service is not available. Check your internet connection.
754
+ If this issue persists, please contact support.
755
+ """)
756
+
757
+ # --------------------------------------------------
758
+ # Rel Exceptions/warnings
759
+ # --------------------------------------------------
760
+
761
+ class NumericOverflow(RAIException):
762
+ def __init__(self, problem, source):
763
+ super().__init__("Numeric Overflow")
764
+ self.problem = problem
765
+ self.source = source
766
+ self.message = "Numeric Overflow"
767
+ self.name = "Numeric Overflow"
768
+ self.content = self.format_message()
769
+
770
+ def format_message(self):
771
+ problem = self.problem
772
+ marked = mark_source(self.source, -1, -1)
773
+ info = problem["report"].split("\n--------------")[0].strip()
774
+ return textwrap.dedent(f"""
775
+ {info}
776
+
777
+ {marked}
778
+ """)
779
+
780
+ class ArityMismatch(RAIWarning):
781
+ def __init__(self, problem, source):
782
+ self.name = "Arity mismatch"
783
+ self.message = problem["message"]
784
+ self.source = source
785
+ self.content = self.format_message()
786
+
787
+ def format_message(self):
788
+ source = self.source
789
+ match = re.search(r"`(.+?)` expects (.*)", self.message or "")
790
+ if match is None:
791
+ return self.message
792
+ fq_name = match.group(1)
793
+ rel_name = fq_name.split("::")[-1]
794
+ arity_sentence = match.group(2)
795
+ found = PropertyFinder(source.line, [rel_name])
796
+ if source.block:
797
+ found.visit(source.block)
798
+ found_lines = found.found_properties_lines or found.dynamic_properties
799
+ marked = mark_source(self.source, -1, -1, highlight_lines=found_lines)
800
+ return textwrap.dedent(f"""
801
+ The relation [yellow]{rel_name}[/yellow] expects {arity_sentence}
802
+
803
+ {marked}
804
+ """)
805
+
806
+ class UninitializedPropertyException(RAIException):
807
+ def __init__(self, undefined_list: List[Tuple[str, Any]]):
808
+ self.content = ""
809
+ self.raw_content = ""
810
+ self.source = None
811
+ message_chunks = []
812
+ chunks: list[str] = []
813
+ underscore_prefixed_list = list[Tuple[str, Any]]()
814
+
815
+ # Deduplicate items in undefined_list
816
+ seen = set()
817
+ undefined_list = [item for item in undefined_list if item[0] not in seen and not seen.add(item[0])]
818
+
819
+ # Check for properties that start with an underscore
820
+ for item in undefined_list:
821
+ if item[0].startswith("_"):
822
+ underscore_prefixed_list.append(item)
823
+
824
+ # Remove the underscore-prefixed items from the undefined_list
825
+ if len(underscore_prefixed_list) > 0:
826
+ for item in underscore_prefixed_list:
827
+ undefined_list.remove(item)
828
+
829
+ len_undefined = len(undefined_list)
830
+ len_underscore = len(underscore_prefixed_list)
831
+
832
+ if len_undefined > 0:
833
+ if len_undefined == 1:
834
+ message_chunks.append("Uninitialized property: " + undefined_list[0][0])
835
+ else:
836
+ message_chunks.append("Uninitialized properties: " + ", ".join([item[0] for item in undefined_list]))
837
+
838
+ if len_underscore > 0:
839
+ prefix = "renamed" if len_undefined > 0 else "Renamed"
840
+ suffix = " property:" if len_undefined == 0 else ":"
841
+ if len_underscore == 1:
842
+ message_chunks.append(f"{prefix}{suffix} " + underscore_prefixed_list[0][0])
843
+ else:
844
+ suffix = " properties:" if len_undefined == 0 else ":"
845
+ message_chunks.append(f"{prefix}{suffix} " + ", ".join([item[0] for item in underscore_prefixed_list]))
846
+
847
+ self.message = ", ".join(message_chunks)
848
+ self.name = self.message
849
+
850
+ if len(undefined_list) > 0:
851
+ source_map_undefined: dict[str, tuple[debugging.SourceInfo, list[str]]] = {}
852
+ for name, source in undefined_list:
853
+ if source.source not in source_map_undefined:
854
+ source_map_undefined[source.source] = (source, [name])
855
+ else:
856
+ source_map_undefined[source.source][1].append(name)
857
+
858
+ for (source, names) in source_map_undefined.values():
859
+ self.source = source
860
+ unique_names = list(set(names))
861
+ props = ", ".join([f"[yellow]{name}[/yellow]" for name in unique_names])
862
+ prop_line = (
863
+ f"property {props} has"
864
+ if len(unique_names) == 1
865
+ else f"properties {props} have"
866
+ )
867
+ found = PropertyFinder(source.line, unique_names)
868
+ if source.block:
869
+ found.visit(source.block)
870
+ found_lines = found.found_properties_lines or found.dynamic_properties
871
+ marked = mark_source(source, -1, -1, indent=0, highlight_lines=found_lines)
872
+ chunks.append(textwrap.dedent(f"""
873
+ The {prop_line} never been set or added to and so will always cause the rule or query to fail.
874
+ """))
875
+ chunks.append(marked)
876
+
877
+ if len(underscore_prefixed_list) > 0:
878
+ source_map_underscore: dict[str, tuple[debugging.SourceInfo, list[str]]] = {}
879
+ for name, source in underscore_prefixed_list:
880
+ if source.source not in source_map_underscore:
881
+ source_map_underscore[source.source] = (source, [name])
882
+ else:
883
+ source_map_underscore[source.source][1].append(name)
884
+
885
+ for (source, names) in source_map_underscore.values():
886
+ self.source = source
887
+ unique_names = list(set(names))
888
+ found = PropertyFinder(source.line, unique_names)
889
+ if source.block:
890
+ found.visit(source.block)
891
+ found_lines = found.found_properties_lines or found.dynamic_properties
892
+ marked = mark_source(source, -1, -1, indent=0, highlight_lines=found_lines)
893
+ chunks.append(textwrap.dedent("""
894
+ Column names prefixed with '_' are clashing with internal properties.
895
+ In your code replace the column name with the alias shown below:
896
+ """))
897
+ chunks.append("\n".join((f"[yellow]{name}[/yellow] \trename to:\t [green]{'col' + name}[/green]" for name, _ in underscore_prefixed_list)))
898
+ chunks.append(marked)
899
+
900
+ self.content = "\n\n".join(chunks)
901
+
902
+ super().__init__(self.name, self.message, self.content, self.source)
903
+
904
+ class RAITypeError(RAIException, TypeError):
905
+ def __init__(self, param: str, message: str):
906
+ self.param = param
907
+ self.message = message
908
+ self.name = "Invalid type error"
909
+ self.source = Errors.call_source(4)
910
+
911
+ # Mark the source if available
912
+ if self.source:
913
+ self.marked = mark_source(self.source, self.source.line, self.source.line)
914
+ else:
915
+ self.marked = ""
916
+
917
+ # Format the content and raw_content
918
+ self.content = self.format_message()
919
+ self.raw_content = self.strip_rich_tags()
920
+
921
+ super().__init__(self.message, self.name, self.content, self.source)
922
+
923
+ def format_message(self):
924
+ if self.source:
925
+ return textwrap.dedent(f"""
926
+ The parameter [yellow]{self.param}[/yellow] is of the wrong type.
927
+
928
+ {self.marked}
929
+ """)
930
+ else:
931
+ return textwrap.dedent(f"""
932
+ The parameter [yellow]{self.param}[/yellow] is of the wrong type.
933
+ """)
934
+
935
+ class RAIValueError(RAIException, ValueError):
936
+ def __init__(self, message: str):
937
+ self.message = message
938
+ self.name = "Invalid value error"
939
+ self.source = Errors.call_source(4)
940
+
941
+ # Mark the source if available
942
+ if self.source:
943
+ self.marked = mark_source(self.source, self.source.line, self.source.line)
944
+ else:
945
+ self.marked = ""
946
+
947
+ # Format the content and raw_content
948
+ self.content = self.format_message()
949
+ self.raw_content = self.strip_rich_tags()
950
+
951
+ super().__init__(self.message, self.name, self.content, self.source)
952
+
953
+ def format_message(self):
954
+ if self.source:
955
+ return textwrap.dedent(f"""
956
+ {self.message}
957
+
958
+ {self.marked}
959
+ """)
960
+ else:
961
+ return textwrap.dedent(f"""
962
+ {self.message}
963
+ """)
964
+
965
+
966
+
967
+ class RAIAbortedTransactionError(RAIException):
968
+ def __init__(self, type, message, report):
969
+ self.type = type
970
+ self.message = message
971
+ self.report = report
972
+ self.name = "Transaction aborted"
973
+
974
+ self.content = self.format_message()
975
+ self.raw_content = self.strip_rich_tags()
976
+
977
+ super().__init__(self.message, self.name, self.content)
978
+
979
+ def format_message(self):
980
+ clean_report = "\n".join(line.lstrip() for line in self.report.splitlines())
981
+
982
+ formatted = textwrap.dedent(f"""
983
+ [yellow]The transaction was aborted due to the following error:[/yellow]
984
+
985
+ Type: [yellow]{self.type}[/yellow]
986
+
987
+ Message: [yellow]{self.message}[/yellow]
988
+
989
+ Report: {clean_report}
990
+ """)
991
+
992
+ final_formatted = "\n".join(line.lstrip() for line in formatted.splitlines())
993
+ return final_formatted
994
+
995
+
996
+ # --------------------------------------------------
997
+ # DSL scope errors
998
+ # --------------------------------------------------
999
+ class OutOfContextException(RAIException):
1000
+ def __init__(self, source=None):
1001
+ self.name = "Outside of context"
1002
+ self.message = (
1003
+ "Looks like this object is being used outside of a rule or query."
1004
+ )
1005
+ self.source = source or Errors.call_source() or debugging.SourceInfo()
1006
+ self.content = self.format_message()
1007
+
1008
+ super().__init__(self.message, self.name, self.content, source=source)
1009
+
1010
+ def format_message(self):
1011
+ marked = mark_source(self.source, self.source.line, self.source.line)
1012
+ return textwrap.dedent(f"""
1013
+ Looks like this [yellow]object[/yellow] is being used outside of a rule or query.
1014
+
1015
+ {marked}
1016
+ """)
1017
+
1018
+
1019
+ class VariableOutOfContextException(RAIException):
1020
+ def __init__(self, source, name: str|None, is_property=False):
1021
+ self.name = "Variable out of context"
1022
+ if name is None:
1023
+ self.message = "Looks like a variable is being used outside of the rule or query it was defined in."
1024
+ else:
1025
+ self.message = f"Looks like a variable representing '{name}' is being used outside of the rule or query it was defined in."
1026
+ self.source = source
1027
+ self.is_property = is_property
1028
+ self.content = self.format_message(name)
1029
+
1030
+ super().__init__(self.message, self.name, self.content, source=source)
1031
+
1032
+ def format_message(self, name):
1033
+ marked = mark_source(self.source, self.source.line, self.source.line)
1034
+ return textwrap.dedent(f"""
1035
+ Looks like a variable representing [yellow bold]{name}[/yellow bold] is being used outside of the rule or query it was defined in.
1036
+
1037
+ {marked}
1038
+ """)
1039
+
1040
+ class SelectOutOfContext(RAIException):
1041
+ def __init__(self):
1042
+ self.name = "Outside of context"
1043
+ self.message = (
1044
+ "Looks like this select is being used outside of the query it was originally from."
1045
+ )
1046
+ self.source = Errors.call_source() or debugging.SourceInfo()
1047
+ self.content = self.format_message()
1048
+
1049
+ super().__init__(self.message, self.name, self.content, source=self.source)
1050
+
1051
+ def format_message(self):
1052
+ marked = mark_source(self.source, self.source.line, self.source.line)
1053
+ return textwrap.dedent(f"""
1054
+ Looks like this [yellow]select[/yellow] is being used outside of the query it is from.
1055
+
1056
+ {marked}
1057
+ """)
1058
+
1059
+ #--------------------------------------------------
1060
+ # DSL Type errors
1061
+ #--------------------------------------------------
1062
+
1063
+ class FilterAsValue(RAIException):
1064
+ def __init__(self, source=None):
1065
+ self.name = "Filter used as a value"
1066
+ self.message = "Boolean expressions are filters and can't be used as values directly, use std.as_bool to cast them into True/False."
1067
+ self.source = source or Errors.call_source()
1068
+ self.content = self.format_message()
1069
+
1070
+ super().__init__(self.message, self.name, self.content, source=self.source)
1071
+
1072
+ def format_message(self):
1073
+ marked = mark_source(self.source, self.source.line, self.source.line) if self.source else ""
1074
+ return textwrap.dedent(f"""
1075
+ Boolean expressions are filters and can't be used as values directly. You can use relationalai.std.as_bool(..) to cast them into True/False.
1076
+
1077
+ {marked}
1078
+ """)
1079
+
1080
+ class AsBoolForNonFilter(RAIException):
1081
+ def __init__(self, source=None):
1082
+ self.name = "as_bool used on non-filter"
1083
+ self.message = "as_bool can only be used on boolean expressions."
1084
+ self.source = source or Errors.call_source()
1085
+ self.content = self.format_message()
1086
+
1087
+ super().__init__(self.message, self.name, self.content, source=self.source)
1088
+
1089
+ def format_message(self):
1090
+ marked = mark_source(self.source, self.source.line, self.source.line) if self.source else ""
1091
+ return textwrap.dedent(f"""
1092
+ as_bool can only be used on boolean expressions.
1093
+
1094
+ {marked}
1095
+ """)
1096
+
1097
+ class PropertyCaseMismatch(RAIWarning):
1098
+ def __init__(self, name, found_name):
1099
+ self.name = "Similar property name"
1100
+ self.message = ".{name} doesn't exist, but .{found_name} does"
1101
+ self.source = Errors.call_source()
1102
+ self.content = self.format_message(name, found_name)
1103
+
1104
+ def format_message(self, name, found_name):
1105
+ source = self.source or debugging.SourceInfo()
1106
+ found = PropertyFinder(source.line, [name])
1107
+ if source.block:
1108
+ found.visit(source.block)
1109
+ found_lines = found.found_properties_lines or found.dynamic_properties
1110
+ marked = mark_source(source, -1, -1, indent=8, highlight_lines=found_lines)
1111
+
1112
+ updated = mark_source(
1113
+ source.modify(PropertyNameReplacer(name, found_name)),
1114
+ -1, -1, indent=8, highlight_lines=found_lines, highlight="green",
1115
+ )
1116
+
1117
+ return textwrap.dedent(f"""
1118
+ The property [yellow]{name}[/yellow] doesn't exist, but the very similar [green]{found_name}[/green] does.
1119
+
1120
+ {marked}
1121
+
1122
+ Did you mean [green]{found_name}[/green] instead?
1123
+
1124
+ {updated}
1125
+ """)
1126
+
1127
+
1128
+ class NonVarObject(RAIException, TypeError):
1129
+ def __init__(self, obj: Any, message: str):
1130
+ self.obj = obj
1131
+ self.message = message
1132
+ self.name = "non-var object used as variable"
1133
+ self.source = Errors.call_source(4)
1134
+
1135
+ # Mark the source if available
1136
+ if self.source:
1137
+ self.marked = mark_source(self.source, self.source.line, self.source.line)
1138
+ else:
1139
+ self.marked = ""
1140
+
1141
+ # Format the content and raw_content
1142
+ self.content = self.format_message()
1143
+ self.raw_content = self.strip_rich_tags()
1144
+
1145
+ super().__init__(self.message, self.name, self.content, self.source)
1146
+
1147
+ def format_message(self):
1148
+ if self.source:
1149
+ return textwrap.dedent(f"""
1150
+ [yellow]{self.obj.__class__.__name__}[/yellow] objects can't be used as variables, such as in a .add(), .set(), or select().
1151
+
1152
+ {self.marked}
1153
+ """)
1154
+ else:
1155
+ return textwrap.dedent(f"""
1156
+ [yellow]{self.obj.__class__.__name__}[/yellow] objects can't be used as variables, such as in a .add(), .set(), or select().
1157
+ """)
1158
+
1159
+ class KeyedCantBeExtended(RAIException):
1160
+ def __init__(self):
1161
+ self.name = "Keyed types can't be extended"
1162
+ self.message = "Keyed types can't be extended."
1163
+ self.source = Errors.call_source() or debugging.SourceInfo()
1164
+ self.content = self.format_message()
1165
+
1166
+ super().__init__(self.message, self.name, self.content, source=self.source)
1167
+
1168
+ def format_message(self):
1169
+ marked = mark_source(self.source, self.source.line, self.source.line)
1170
+ return textwrap.dedent(f"""
1171
+ Keyed types can't be extended, use .add() in a rule instead.
1172
+
1173
+ {marked}
1174
+ """)
1175
+
1176
+ class KeyedWrongArity(RAIException):
1177
+ def __init__(self, name:str, keys:List[str], given:int):
1178
+ self.name = "Incorrect number of keys"
1179
+ verb = "was" if given == 1 else "were"
1180
+ self.message = f"{name} expects {len(keys)} keys ({', '.join(keys)}), but only {given} {verb} given."
1181
+ self.source = Errors.call_source() or debugging.SourceInfo()
1182
+ self.content = self.format_message()
1183
+
1184
+ super().__init__(self.message, self.name, self.content, source=self.source)
1185
+
1186
+ def format_message(self):
1187
+ marked = mark_source(self.source, self.source.line, self.source.line)
1188
+ return textwrap.dedent(f"""
1189
+ {self.message}
1190
+
1191
+ {marked}
1192
+ """)
1193
+
1194
+ class RowLiteralTooLargeWarning(RAIWarning):
1195
+ def __init__(self, size, max_size=1000):
1196
+ self.name = "Large row literal"
1197
+ self.message = f"Rows with more than {max_size} items can be slow to compile."
1198
+ self.size = size
1199
+ self.max_size = max_size
1200
+ self.source = Errors.call_source() or debugging.SourceInfo()
1201
+ self.content = self.format_message()
1202
+
1203
+ def format_message(self):
1204
+ marked = mark_source(self.source, self.source.line, self.source.line)
1205
+ return textwrap.dedent(f"""
1206
+ Rows with more than {self.max_size} items can be slow to compile. This one has {self.size} items:
1207
+
1208
+ {marked}
1209
+ """)
1210
+
1211
+ class RowLiteralTooLarge(RAIException):
1212
+ def __init__(self, size, max_size=10000):
1213
+ self.name = "Row literal too large"
1214
+ self.message = f"Rows can't have more than {max_size} items."
1215
+ self.size = size
1216
+ self.max_size = max_size
1217
+ self.source = Errors.call_source() or debugging.SourceInfo()
1218
+ self.content = self.format_message()
1219
+
1220
+ def format_message(self):
1221
+ marked = mark_source(self.source, self.source.line, self.source.line)
1222
+ return textwrap.dedent(f"""
1223
+ Rows can't have more than {self.max_size} items. This one has {self.size} items:
1224
+
1225
+ {marked}
1226
+ """)
1227
+
1228
+ class RowLiteralMismatch(RAIException):
1229
+ def __init__(self, mismatched_thing:str):
1230
+ self.name = "Row literal mismatch"
1231
+ self.message = f"All rows in a row literal must have the same {mismatched_thing}"
1232
+ self.mismatched_thing = mismatched_thing
1233
+ self.source = Errors.call_source() or debugging.SourceInfo()
1234
+ self.content = self.format_message()
1235
+
1236
+ def format_message(self):
1237
+ marked = mark_source(self.source, self.source.line, self.source.line)
1238
+ return textwrap.dedent(f"""
1239
+ All rows in a row literal must have the same {self.mismatched_thing}
1240
+
1241
+ {marked}
1242
+ """)
1243
+
1244
+
1245
+ # --------------------------------------------------
1246
+ # DSL property exceptions
1247
+ # --------------------------------------------------
1248
+
1249
+
1250
+ class ReservedPropertyException(RAIException):
1251
+ def __init__(self, source, property_name: str):
1252
+ self.name = "Reserved property name"
1253
+ self.message = f"The property '{property_name}' is a reserved property name on RelationalAI types."
1254
+ self.source = source
1255
+ self.content = self.format_message(property_name)
1256
+
1257
+ super().__init__(self.message, self.name, self.content, source=self.source)
1258
+
1259
+ def format_message(self, property_name):
1260
+ marked = mark_source(self.source, self.source.line, self.source.line)
1261
+ return textwrap.dedent(f"""
1262
+ The property '{property_name}' is a reserved property name on RelationalAI types.
1263
+
1264
+ {marked}
1265
+ """)
1266
+
1267
+
1268
+ class NonCallablePropertyException(RAIException):
1269
+ def __init__(self, source, property_name: str):
1270
+ self.name = "Non-callable property"
1271
+ self.message = f"The property '{property_name}' is not callable."
1272
+ self.source = source
1273
+ self.content = self.format_message(property_name)
1274
+
1275
+ super().__init__(self.message, self.name, self.content, source=source)
1276
+
1277
+ def format_message(self, property_name):
1278
+ marked = mark_source(self.source, self.source.line, self.source.line)
1279
+ return textwrap.dedent(f"""
1280
+ The property '{property_name}' is not callable on RelationalAI types.
1281
+
1282
+ {marked}
1283
+ """)
1284
+
1285
+ class InvalidPropertySetException(RAIException):
1286
+ def __init__(self, source):
1287
+ self.name = "Invalid property set"
1288
+ self.message = "You can't set properties directly on a RAI object."
1289
+ self.source = source
1290
+ self.start_line = source.line
1291
+ self.end_line = source.line
1292
+ self.content = self.format_message()
1293
+ self.raw_content = self.strip_rich_tags()
1294
+ super().__init__(
1295
+ message=self.message, name=self.name, source=source, content=self.content
1296
+ )
1297
+
1298
+ def format_message(self):
1299
+ marked = self.mark_source()
1300
+ dynamic = mark_source(
1301
+ self.source.modify(SetToMethod()),
1302
+ self.start_line,
1303
+ self.end_line,
1304
+ highlight="green",
1305
+ )
1306
+ compare = mark_source(
1307
+ self.source.modify(AssignToCompare()),
1308
+ self.start_line,
1309
+ self.end_line,
1310
+ highlight="green",
1311
+ )
1312
+
1313
+ return textwrap.dedent(f"""
1314
+ You can't set properties directly on a RAI object.
1315
+
1316
+ {marked}
1317
+
1318
+ If you are trying to set the value of a property use [green]set()[/green]:
1319
+
1320
+ {dynamic}
1321
+
1322
+ Or maybe you meant [green]==[/green] instead?
1323
+
1324
+ {compare}
1325
+ """)
1326
+
1327
+ def mark_source(self):
1328
+ return mark_source(self.source, self.start_line, self.end_line)
1329
+
1330
+ class MultipleIdentities(RAIException):
1331
+ def __init__(self):
1332
+ source = Errors.call_source()
1333
+ self.name = "Multiple identities"
1334
+ self.message = "You can't pass multiple identity variables for a single object."
1335
+ self.source = source
1336
+ self.content = self.format_message()
1337
+
1338
+ super().__init__(self.message, self.name, self.content, source=self.source)
1339
+
1340
+ def format_message(self):
1341
+ marked = mark_source(self.source, self.source.line, self.source.line) if self.source else ""
1342
+ return textwrap.dedent(f"""
1343
+ You can't pass multiple identity variables for a single object.
1344
+
1345
+ {marked}
1346
+ """)
1347
+
1348
+ # --------------------------------------------------
1349
+ # Graph library exceptions
1350
+ # --------------------------------------------------
1351
+
1352
+ class DirectedGraphNotApplicable(RAIValueError):
1353
+ def __init__(self, name: str):
1354
+ super().__init__(name)
1355
+ self.name = f"algorithm `{name}` is not applicable to directed graphs"
1356
+
1357
+ class DirectedGraphNotSupported(RAIValueError):
1358
+ def __init__(self, name: str, message_addendum: str = ""):
1359
+ message = f"algorithm `{name}` does not currently support directed graphs{'' if not message_addendum else f'. {message_addendum}'}"
1360
+ super().__init__(message)
1361
+
1362
+ class ParameterTypeMismatch(RAIValueError):
1363
+ def __init__(self, name: str, type_, value):
1364
+ super().__init__(name)
1365
+ self.name = (
1366
+ f"parameter `{name}` must be of type {type_.__name__.lower()}, "
1367
+ f"but its value {value!r} is of type {type(value)}"
1368
+ )
1369
+
1370
+ class ParameterBoundBelowInclusive(RAIValueError):
1371
+ def __init__(self, name: str, value, minimum):
1372
+ super().__init__(name)
1373
+ self.name = f"parameter `{name}` must be greater than or equal to {minimum}, but is {value!r}"
1374
+
1375
+ class ParameterBoundAboveInclusive(RAIValueError):
1376
+ def __init__(self, name: str, value, maximum):
1377
+ super().__init__(name)
1378
+ self.name = f"parameter `{name}` must be less than or equal to {maximum}, but is {value!r}"
1379
+
1380
+ class ParameterBoundBelowExclusive(RAIValueError):
1381
+ def __init__(self, name: str, value, minimum):
1382
+ super().__init__(name)
1383
+ self.name = f"parameter `{name}` must be strictly greater than {minimum}, but is {value!r}"
1384
+
1385
+ class ParameterBoundAboveExclusive(RAIValueError):
1386
+ def __init__(self, name: str, value, maximum):
1387
+ super().__init__(name)
1388
+ self.name = f"parameter `{name}` must be strictly less than {maximum}, but is {value!r}"
1389
+
1390
+
1391
+ # --------------------------------------------------
1392
+ # Engine exceptions
1393
+ # --------------------------------------------------
1394
+
1395
+
1396
+ class EngineNotFoundException(RAIException):
1397
+ def __init__(self, engine_name: str, message: str):
1398
+ self.engine_name = engine_name
1399
+ self.message = message
1400
+ self.name = "Engine unavailable"
1401
+
1402
+ # Format the content and raw_content
1403
+ self.content = self.format_message()
1404
+ self.raw_content = self.strip_rich_tags()
1405
+
1406
+ super().__init__(self.message, self.name, self.content)
1407
+
1408
+ def format_message(self):
1409
+ message = "We were unable to detect an existing engine or provision a new engine for you. To manually create an engine:"
1410
+ return self.message_for_environment(
1411
+ message,
1412
+ f"rai engines:create --name {self.engine_name}",
1413
+ f"rai.Provider().create_engine('{self.engine_name}')"
1414
+ )
1415
+
1416
+ class EngineProvisioningFailed(RAIException):
1417
+ def __init__(self, engine_name: str, original_exception: Exception | None = None):
1418
+ self.engine_name = engine_name
1419
+ self.original_exception = original_exception
1420
+ self.message = "Engine provisioning failed"
1421
+ self.name = self.message
1422
+
1423
+ # Format the content and raw_content
1424
+ self.content = self.format_message()
1425
+ self.raw_content = self.strip_rich_tags()
1426
+
1427
+ super().__init__(self.message, self.name, self.content)
1428
+
1429
+ def format_message(self):
1430
+ base_message = "\n".join([
1431
+ f"We tried to provision the engine [yellow]{self.engine_name}[/yellow] but it failed."
1432
+ "\n\n",
1433
+ "To learn more about how to manually manage engines, see:"
1434
+ "\n",
1435
+ "https://docs.relational.ai/api/cli/engines"
1436
+ ])
1437
+
1438
+ # Add original exception details if available
1439
+ if self.original_exception:
1440
+ original_msg = str(self.original_exception).strip()
1441
+ if original_msg:
1442
+ base_message += f"\n\n[red]Error:[/red] {original_msg}"
1443
+
1444
+ return base_message
1445
+
1446
+ class DuoSecurityFailed(RAIException):
1447
+ def __init__(self, _: Exception):
1448
+ self.message = "Connection failed due to Duo security"
1449
+ self.name = "Connection failed"
1450
+
1451
+ # Format the content and raw_content
1452
+ self.content = self.format_message()
1453
+ self.raw_content = self.strip_rich_tags()
1454
+
1455
+ super().__init__(self.message, self.name, self.content)
1456
+
1457
+ def format_message(self):
1458
+ return "Establishing connection failed due to Duo security.\nPlease check your Duo security code/settings and try again."
1459
+ class EnginePending(RAIException):
1460
+ def __init__(self, engine_name: str):
1461
+ self.engine_name = engine_name
1462
+ self.message = "Engine is in a pending state"
1463
+ self.name = "Engine not ready"
1464
+
1465
+ # Format the content and raw_content
1466
+ self.content = self.format_message()
1467
+ self.raw_content = self.strip_rich_tags()
1468
+
1469
+ super().__init__(self.message, self.name, self.content)
1470
+
1471
+ def format_message(self):
1472
+ message = f"The engine [yellow]{self.engine_name}[/yellow] is in a pending state. You can see the status of engines"
1473
+ return self.message_for_environment(
1474
+ message,
1475
+ "rai engines:list",
1476
+ "rai.Provider().list_engines()"
1477
+ )
1478
+
1479
+
1480
+ class EngineResumeFailed(RAIException):
1481
+ def __init__(self, engine_name: str):
1482
+ self.engine_name = engine_name
1483
+ self.message = "Unable to resume engine"
1484
+ self.name = "Unable to resume engine"
1485
+ # Format the content and raw_content
1486
+ self.content = self.format_message()
1487
+ self.raw_content = self.strip_rich_tags()
1488
+ super().__init__(self.message, self.name, self.content)
1489
+
1490
+ def format_message(self):
1491
+ suggestions = [
1492
+ Suggestion("cli", f"""
1493
+ rai engines:delete --name {self.engine_name}
1494
+ rai engines:create --name {self.engine_name}
1495
+ """[1:-1]),
1496
+ Suggestion("python", f"""
1497
+ rai.Provider().delete_engine('{self.engine_name}')
1498
+ rai.Provider().create_engine('{self.engine_name}')
1499
+ """[1:-1])
1500
+ ]
1501
+ return "\n".join([
1502
+ f"The engine [yellow]{self.engine_name}[/yellow] could not be automatically resumed. Please ensure both your native app install and the `relationalai` package are up to date.",
1503
+ "",
1504
+ f"You can also manually recreate the engine {suggest(suggestions)}"
1505
+ ])
1506
+
1507
+
1508
+ class InvalidEngineSizeError(RAIException):
1509
+ def __init__(self, engine_size: str, valid_sizes: list[str]):
1510
+ msg = f"Invalid engine size '{engine_size}'"
1511
+ self.name = msg
1512
+ self.message = msg
1513
+
1514
+ # Format the content and raw_content
1515
+ self.content = "".join([
1516
+ f"Invalid engine size [yellow]{engine_size}[/yellow] provided. Please check your config."
1517
+ "\n",
1518
+ f"Valid engine sizes are: [green]{', '.join(valid_sizes)}[/green]"
1519
+ ])
1520
+
1521
+ super().__init__(self.message, self.name, self.content)
1522
+
1523
+ class EngineNameValidationException(RAIException):
1524
+ def __init__(self, engine_name: str):
1525
+ self.engine_name = engine_name
1526
+ msg = "Engine auto-creation failed due to invalid engine name"
1527
+ self.name = msg
1528
+ self.message = msg
1529
+
1530
+ # Format the content and raw_content
1531
+ self.content = self.format_message()
1532
+ self.raw_content = self.strip_rich_tags()
1533
+
1534
+ super().__init__(self.message, self.name, self.content)
1535
+
1536
+ def format_message(self):
1537
+ from v0.relationalai.tools.cli_helpers import ENGINE_NAME_ERROR
1538
+ return "\n".join([
1539
+ f"We tried to auto-create engine [yellow]{self.engine_name}[/yellow] but it failed."
1540
+ "\n",
1541
+ f"Engine name requirements are: [yellow]{ENGINE_NAME_ERROR}[/yellow]"
1542
+ ])
1543
+
1544
+ # --------------------------------------------------
1545
+ # Hex exceptions
1546
+ # --------------------------------------------------
1547
+
1548
+ class HexSessionException(RAIException):
1549
+ def __init__(self):
1550
+ self.name = "Hex session error"
1551
+ self.message = "Missing Snowflake session object in Hex"
1552
+ self.content = self.format_message()
1553
+ self.raw_content = self.strip_rich_tags()
1554
+
1555
+ super().__init__(self.message, self.name, self.content)
1556
+
1557
+ def format_message(self):
1558
+ return textwrap.dedent("""
1559
+ To use RelationalAI in Hex, get your Snowflake session and supply it as a connection parameter to `rai.Model` or `rai.Provider`:
1560
+
1561
+ [green]
1562
+ import hextoolkit
1563
+ hex_snowflake_conn = hextoolkit.get_data_connection('<Your Connection Name>')
1564
+ hex_snowpark_session = hex_snowflake_conn.get_snowpark_session()
1565
+ [/green]
1566
+ """)
1567
+
1568
+
1569
+ # --------------------------------------------------
1570
+ # Snowflake errors
1571
+ # --------------------------------------------------
1572
+
1573
+ def handle_missing_integration(e: Exception):
1574
+ in_warehouse = (
1575
+ isinstance(runtime_env, SnowbookEnvironment)
1576
+ and runtime_env.runner == "warehouse"
1577
+ )
1578
+ if in_warehouse and any((name in str(type(e)) for name in ("NameResolutionError", "ProgrammingError", "SnowflakeIntegrationMissingException"))):
1579
+ raise SnowflakeIntegrationMissingException() from None
1580
+
1581
+ access_integration_warning = textwrap.dedent("""
1582
+ Make sure that S3_RAI_INTERNAL_BUCKET_EGRESS_INTEGRATION is enabled. You can access the toggle from the vertical ellipsis button in the top-right corner of the Snowflake notebook window: ⋮ > Notebook settings > External access.
1583
+
1584
+ If you don't see S3_RAI_INTERNAL_BUCKET_EGRESS_INTEGRATION listed, get an account administrator to run Step 4 in the installation notebook here:
1585
+
1586
+ https://relational.ai/notebooks/relationalai-installation.ipynb
1587
+ """)
1588
+
1589
+ class SnowflakeMissingConfigValuesException(RAIException):
1590
+ def __init__(self, missing_keys: list[str], profile: str | None = None, config_file_path: str | None = None):
1591
+ sorted_missing_keys = sorted(missing_keys)
1592
+ self.name = f"Missing {len(missing_keys)} required configuration value{'s' if len(missing_keys) > 1 else ''}"
1593
+ config_info = f"File: {config_file_path}\n" if config_file_path else ""
1594
+ profile_info = f"Profile: '{profile}'\n" if profile else ""
1595
+ self.message = f"""
1596
+ {self.name}:
1597
+
1598
+ [green]{', '.join(sorted_missing_keys)}[/green]
1599
+
1600
+ {config_info}{profile_info}
1601
+ [yellow]Please update the configuration file above to include the missing values.
1602
+ """
1603
+ self.content = self.format_message()
1604
+
1605
+ super().__init__(self.message, self.name, self.content)
1606
+
1607
+ def format_message(self):
1608
+ return f"""{self.message}"""
1609
+
1610
+ class SnowflakeDatabaseException(RAIException):
1611
+ def __init__(self, exception: Exception):
1612
+ self.message = str(exception)
1613
+ self.name = "Snowflake Database Error"
1614
+ self.content = self.format_message()
1615
+
1616
+ super().__init__(self.message, self.name, self.content)
1617
+
1618
+ def format_message(self):
1619
+ return f"""{self.message}"""
1620
+
1621
+ class SnowflakeIntegrationMissingException(RAIException):
1622
+ def __init__(self):
1623
+ self.message = access_integration_warning
1624
+ self.name = "Integration not enabled"
1625
+ self.content = self.format_message()
1626
+
1627
+ super().__init__(self.message, self.name, self.content)
1628
+
1629
+ def format_message(self):
1630
+ return access_integration_warning
1631
+
1632
+ class SnowflakeAppMissingException(RAIException):
1633
+ def __init__(self, app_name: str, role: str | None = None):
1634
+ self.app_name = app_name
1635
+ self.role = role
1636
+ self.message = (
1637
+ f"Either the app '{app_name}' isn't installed in this Snowflake account "
1638
+ f"or you don't have permission to access it."
1639
+ )
1640
+ self.name = "Couldn't find RelationalAI Snowflake application"
1641
+ self.content = self.format_message()
1642
+ super().__init__(self.message, self.name, self.content)
1643
+
1644
+ def format_message(self):
1645
+ base = (
1646
+ f"Either the app '{self.app_name}' isn't installed in this Snowflake account, "
1647
+ f"or your active role(s) don't have permission to access it.\n\n"
1648
+ f"If it's installed under a different name, run 'rai init' on the command line to set the app name."
1649
+ )
1650
+
1651
+ if self.role:
1652
+ base += f"\n\nYour current primary role is '{self.role}', and by default all assigned roles are used as secondary roles."
1653
+
1654
+ base += "\n\nWhile access via secondary roles is supported, we recommend granting the necessary privileges directly to your primary role to ensure consistent access."
1655
+
1656
+ return base
1657
+
1658
+
1659
+ class SnowflakeImportMissingException(RAIException):
1660
+ def __init__(self, source: debugging.SourceInfo|None, import_name: str, model_name: str):
1661
+ self.source = source
1662
+ self.import_name = import_name
1663
+ self.model_name = model_name
1664
+ self.message = (
1665
+ f"The Snowflake object '{import_name}' hasn't been imported into RAI."
1666
+ )
1667
+ self.name = "Couldn't find import"
1668
+ self.content = self.format_message()
1669
+
1670
+ super().__init__(self.message, self.name, self.content)
1671
+
1672
+ def format_message(self):
1673
+ return textwrap.dedent(f"""
1674
+ The Snowflake object '{self.import_name}' hasn't been imported into RAI.
1675
+ To automatically handle imports please verify that `use_graph_index` is not set to `False` in your config.
1676
+
1677
+ You can also create an import for it using:
1678
+
1679
+ [green]rai imports:stream --source {self.import_name} --model {self.model_name}[/green]
1680
+
1681
+ [green]rai.Provider().create_streams(['{self.import_name}'], '{self.model_name}')[/green]
1682
+ """)
1683
+
1684
+
1685
+ class SnowflakeChangeTrackingNotEnabledException(RAIException):
1686
+ def __init__(self, obj: tuple[str, str] | list[tuple[str, str]]):
1687
+ self.obj = obj
1688
+ if isinstance(obj, tuple):
1689
+ fqn, type = obj
1690
+ self.sql = f"[green]ALTER {type.upper()} {fqn} SET CHANGE_TRACKING = TRUE;[/green]"
1691
+ self.message = f"Change tracking isn't enabled for '{obj}'."
1692
+ elif isinstance(obj, list):
1693
+ self.sql = "\n".join([f"[green]ALTER {type.upper()} {fqn} SET CHANGE_TRACKING = TRUE;[/green]" for fqn, type in obj])
1694
+ self.message = f"Change tracking isn't enabled for the following sources: {', '.join(fqn for fqn, _ in obj)}"
1695
+
1696
+ self.name = "Change tracking not enabled"
1697
+ self.content = self.format_message()
1698
+
1699
+ super().__init__(self.message, self.name, self.content)
1700
+
1701
+ def format_message(self):
1702
+ if isinstance(self.obj, tuple):
1703
+ fqn_str, _ = self.obj
1704
+ elif isinstance(self.obj, list):
1705
+ fqn_str = ', '.join(fqn for fqn, _ in self.obj)
1706
+ else:
1707
+ fqn_str = self.obj
1708
+ return (
1709
+ f"Change tracking isn't enabled for [yellow]{fqn_str}[/yellow].\n\n"
1710
+ f"To enable change tracking, you'll need to run the following SQL:\n\n"
1711
+ f"{self.sql}\n\n"
1712
+ f"If you want automatic enabling, please add [cyan]ensure_change_tracking = true[/cyan] to your config or Model"
1713
+ ).strip()
1714
+
1715
+ class ModelNotFoundException(RAIException):
1716
+ def __init__(self, model_name: str):
1717
+ self.model_name = model_name
1718
+ self.message = "Model not found"
1719
+ self.name = f"Model '{model_name}' not found"
1720
+
1721
+ # Format the content and raw_content
1722
+ self.content = self.format_message()
1723
+ self.raw_content = self.strip_rich_tags()
1724
+
1725
+ super().__init__(self.message, self.name, self.content)
1726
+
1727
+ def format_message(self):
1728
+ return textwrap.dedent(f"""
1729
+ The model '{self.model_name}' does not exist. You can create it by running a program containing the following:
1730
+
1731
+ model = relationalai.Model("{self.model_name}")
1732
+ """)
1733
+
1734
+
1735
+ class SnowflakeTableObject:
1736
+ def __init__(self, message: str, source: str):
1737
+ self.message = message
1738
+ self.source = source
1739
+
1740
+ class SnowflakeTableObjectsException(RAIException):
1741
+ def __init__(self, table_objects: List[SnowflakeTableObject]):
1742
+ self.table_objects = table_objects
1743
+ table_count = len(table_objects)
1744
+ table_word = "table" if table_count == 1 else "tables"
1745
+
1746
+ self.message = f"Getting the following {table_word} failed with the error in Snowflake."
1747
+ self.name = f"Snowflake {table_word.capitalize()} Streaming Error"
1748
+
1749
+ self.content = self.format_message()
1750
+
1751
+ super().__init__(self.message, self.name, self.content)
1752
+
1753
+ def format_message(self):
1754
+ formatted_objects = [
1755
+ f"Source: [yellow bold]{obj.source}[/yellow bold]\n\n"
1756
+ f"Message: {obj.message.strip()}"
1757
+ for obj in self.table_objects
1758
+ ]
1759
+ return "\n\n".join(formatted_objects)
1760
+
1761
+ class SnowflakeInvalidSource(RAIException):
1762
+ def __init__(self, source, type_source: str):
1763
+ self.source = source
1764
+ self.type_source = type_source
1765
+ self.message = "Invalid source"
1766
+ self.name = "Invalid source"
1767
+ self.content = self.format_message()
1768
+
1769
+ super().__init__(self.message, self.name, self.content, self.source)
1770
+
1771
+ def format_message(self):
1772
+ marked = mark_source(self.source, self.source.line, self.source.line)
1773
+ return textwrap.dedent(f"""
1774
+ The source provided isn't a fully qualified Snowflake table or view name:
1775
+
1776
+ {marked}
1777
+ """)
1778
+
1779
+ class SnowflakeRaiAppNotStarted(RAIException):
1780
+ def __init__(self, app_name: str):
1781
+ self.app_name = app_name
1782
+ self.message = f"The RelationalAI app '{app_name}' isn't started."
1783
+ self.name = "The RelationalAI application not started"
1784
+ self.content = self.format_message()
1785
+
1786
+ super().__init__(self.message, self.name, self.content)
1787
+
1788
+ def format_message(self):
1789
+ return textwrap.dedent(f"""
1790
+ Your app '{self.app_name}' has not been activated.
1791
+ You can activate it by running the following SQL:
1792
+
1793
+ [green]call {self.app_name}.app.activate();[/green]
1794
+ """)
1795
+
1796
+ # --------------------------------------------------
1797
+ # RAI Warnings
1798
+ # --------------------------------------------------
1799
+
1800
+ class InvalidIfWarning(RAIWarning):
1801
+ def __init__(self, task: Task | Action, start_line: int, end_line: int):
1802
+ self.name = "Invalid if"
1803
+ self.source = debugging.get_source(task)
1804
+ self.start_line = start_line
1805
+ self.end_line = end_line
1806
+ self.content = self.format_message()
1807
+
1808
+ def format_message(self):
1809
+ marked = self.mark_source()
1810
+ updated = mark_source(
1811
+ self.source.modify(IfToWithTransformer()),
1812
+ self.start_line,
1813
+ self.end_line,
1814
+ highlight="green",
1815
+ ) if self.source else ""
1816
+ dynamic = mark_source(
1817
+ self.source.modify(WithDynamic()),
1818
+ self.source.line,
1819
+ self.end_line,
1820
+ highlight="green",
1821
+ ) if self.source else ""
1822
+
1823
+ return textwrap.dedent(f"""
1824
+ In a RelationalAI query, using an if statement dynamically modifies the structure of the query itself, rather than adding a conditional.
1825
+
1826
+ {marked}
1827
+
1828
+ If you're trying to do an action based on a condition, use a [green]with[/green] statement instead.
1829
+
1830
+ {updated}
1831
+
1832
+ If you are trying to create a dynamic query where parts are conditional, add the [green]dynamic=True[/green] flag to the query like so:
1833
+
1834
+ {dynamic}
1835
+ """)
1836
+
1837
+ def mark_source(self):
1838
+ return mark_source(self.source, self.start_line, self.end_line)
1839
+
1840
+
1841
+ class InvalidLoopWarning(RAIWarning):
1842
+ def __init__(self, task: Task | Action, start_line: int, end_line: int):
1843
+ self.name = "Invalid loop"
1844
+ self.source = debugging.get_source(task)
1845
+ self.start_line = start_line
1846
+ self.end_line = end_line
1847
+ self.content = self.format_message()
1848
+
1849
+ def format_message(self):
1850
+ marked = self.mark_source() if self.source else ""
1851
+ dynamic = mark_source(
1852
+ self.source.modify(WithDynamic()),
1853
+ self.source.line,
1854
+ self.end_line,
1855
+ highlight="green",
1856
+ ) if self.source else ""
1857
+
1858
+ return textwrap.dedent(f"""
1859
+ In a RelationalAI query, using a loop statement would dynamically modify the query itself, like a macro.
1860
+
1861
+ {marked}
1862
+
1863
+ If that's the goal, you can add the [green]dynamic=True[/green] flag to the query:
1864
+
1865
+ {dynamic}
1866
+ """)
1867
+
1868
+ def mark_source(self):
1869
+ return mark_source(self.source, self.start_line, self.end_line)
1870
+
1871
+ class InvalidTryWarning(RAIWarning):
1872
+ def __init__(self, task: Task | Action, start_line: int, end_line: int):
1873
+ self.name = "Invalid try"
1874
+ self.source = debugging.get_source(task)
1875
+ self.start_line = start_line
1876
+ self.end_line = end_line
1877
+ self.content = self.format_message()
1878
+
1879
+ def format_message(self):
1880
+ marked = self.mark_source()
1881
+ dynamic = mark_source(
1882
+ self.source.modify(WithDynamic()),
1883
+ self.source.line,
1884
+ self.end_line,
1885
+ highlight="green",
1886
+ ) if self.source else ""
1887
+
1888
+ return textwrap.dedent(f"""
1889
+ In a RelationalAI query, using a try statement will have no effect unless a macro-like function is being called and can fail.
1890
+
1891
+ {marked}
1892
+
1893
+ If macro-like behavior is the goal, you can add the [green]dynamic=True[/green] flag to the query:
1894
+
1895
+ {dynamic}
1896
+ """)
1897
+
1898
+ def mark_source(self):
1899
+ return mark_source(self.source, self.start_line, self.end_line)
1900
+
1901
+
1902
+ class InvalidBoolWarning(RAIWarning):
1903
+ def __init__(self, source):
1904
+ self.name = "Invalid boolean expression with Producer"
1905
+ self.source = source
1906
+ self.start_line = source.line
1907
+ self.end_line = source.line
1908
+ self.content = self.format_message()
1909
+ self.raw_content = self.strip_rich_tags()
1910
+
1911
+ def format_message(self):
1912
+ marked = self.mark_source()
1913
+ return textwrap.dedent(f"""
1914
+ In a RelationalAI query, the truth values of Producer objects are unknown
1915
+ until the query has been evaluated. You may not use Producers in boolean
1916
+ expressions, such as those involving [green]if[/green], [green]while[/green], [green]and[/green], [green]or[/green], and [green]not[/green]:
1917
+
1918
+ {marked}
1919
+
1920
+ Producer objects include:
1921
+
1922
+ - [green]Instance[/green] objects, such as [green]person[/green].
1923
+ - [green]InstanceProperty[/green] objects, such as [green]person.age[/green].
1924
+ - [green]Expression[/green] objects, such as [green]person.age >= 18[/green].
1925
+ """)
1926
+
1927
+ def mark_source(self):
1928
+ return mark_source(self.source, self.start_line, self.end_line)
1929
+
1930
+ # whether the python library or the Rel schema is out of date (or both!)
1931
+ class InvalidVersionKind(Enum):
1932
+ LibraryOutOfDate = 'LibraryOutOfDate'
1933
+ SchemaOutOfDate = 'SchemaOutOfDate'
1934
+ Incompatible = 'Incompatible'
1935
+
1936
+ class RAIInvalidVersionWarning(RAIWarning):
1937
+ def __init__(
1938
+ self,
1939
+ kind: InvalidVersionKind,
1940
+ expected: List[Tuple[str, str, str]],
1941
+ lock: dict,
1942
+ platform: str,
1943
+ model_name: str,
1944
+ app_name: str,
1945
+ engine_name: str,
1946
+ errors: List[Tuple[InvalidVersionKind, str, str]] = []
1947
+ ):
1948
+ super().__init__()
1949
+ self.libraries = self.get_current_libs(errors)
1950
+ self.expected_lib_version = self.get_expected_lib_version(expected)
1951
+ self.current_lib_version = self.get_current_lib_version(lock)
1952
+ self.model_name = model_name
1953
+ self.app_name = app_name
1954
+ self.engine_name = engine_name
1955
+
1956
+ self.name = "Invalid version"
1957
+
1958
+ if kind == InvalidVersionKind.LibraryOutOfDate:
1959
+ msg = textwrap.dedent("""
1960
+ The RelationalAI Python library is out of date with respect to the schema. You can update the library using the following command:
1961
+
1962
+ [green]pip install relationalai --upgrade[/green]
1963
+ """).strip()
1964
+ elif kind == InvalidVersionKind.SchemaOutOfDate:
1965
+ # if the "schema" is out of date, we need to delete the engine and recreate it
1966
+ msg = textwrap.dedent(
1967
+ f"""The engine [yellow]{self.engine_name}[/yellow] is out of date with your version of the relationalai Python package.
1968
+ Please delete the engine using the CLI command below and re-run your program:
1969
+
1970
+ [green]rai engines:delete --name {self.engine_name}[/green]""").strip()
1971
+ else:
1972
+ command = "pip install relationalai --upgrade"
1973
+ warehouse_notebook = (
1974
+ isinstance(runtime_env, SnowbookEnvironment)
1975
+ and runtime_env.runner == "warehouse"
1976
+ )
1977
+ if isinstance(runtime_env, IPythonEnvironment) and not warehouse_notebook:
1978
+ command = "!" + command
1979
+ if warehouse_notebook:
1980
+ lib_msg = textwrap.dedent("""
1981
+ The RelationalAI Python library is incompatible with the current schema.
1982
+
1983
+ You can update the library by downloading the latest ZIP file from https://relational.ai/relationalai.zip and uploading it here.
1984
+ """).strip()
1985
+ else:
1986
+ lib_msg = textwrap.dedent(f"""
1987
+ The RelationalAI Python library is incompatible with the current schema.
1988
+ You can update the library using the following command:
1989
+
1990
+ [green]{command}[/green]
1991
+ """).strip()
1992
+
1993
+ msg = f"{lib_msg}\n\nIn addition, the schema must be recreated."
1994
+
1995
+ if len(self.libraries) > 0:
1996
+ library_info = self.generate_library_info()
1997
+ full_msg = f"{msg}\n\n{library_info}"
1998
+ else:
1999
+ full_msg = msg
2000
+
2001
+ self.content = full_msg
2002
+
2003
+ def extract_version(self, version_obj):
2004
+ match = re.search(r"\('([^']+)'\)", repr(version_obj))
2005
+ if match:
2006
+ return match.group(1)
2007
+ else:
2008
+ return ""
2009
+
2010
+ def get_expected_lib_version(self, expected):
2011
+ libraries = {}
2012
+ for name, _, version_range in expected:
2013
+ from_version = self.extract_version(version_range[0])
2014
+ to_version = self.extract_version(version_range[1])
2015
+ libraries[name] = {"from": from_version, "to": to_version}
2016
+ return libraries
2017
+
2018
+ def get_current_lib_version(self, lock):
2019
+ libraries = {}
2020
+ for k, v in lock.items():
2021
+ libraries[k[0]] = v
2022
+ return libraries
2023
+
2024
+ def get_current_libs(self, errors=[]):
2025
+ if len(errors) == 0:
2026
+ return []
2027
+
2028
+ libraries = []
2029
+ for _, library, _ in errors:
2030
+ libraries.append(library)
2031
+ return libraries
2032
+
2033
+ def generate_library_info(self):
2034
+ if len(self.libraries) == 1:
2035
+ lib = self.libraries[0]
2036
+ current_version = self.current_lib_version.get(lib, "unknown")
2037
+ expected_version = self.expected_lib_version.get(lib, {})
2038
+ from_version = expected_version.get("from", "unknown")
2039
+ to_version = expected_version.get("to", "unknown")
2040
+ return (
2041
+ f"Your current library '{lib}' has version {current_version}. "
2042
+ f"However, it is expected to have a version range between {from_version} and {to_version}."
2043
+ )
2044
+ else:
2045
+ info = "Your current libraries have the following issues:\n"
2046
+ for lib in self.libraries:
2047
+ current_version = self.current_lib_version.get(lib, "unknown")
2048
+ expected_version = self.expected_lib_version.get(lib, {})
2049
+ from_version = expected_version.get("from", "unknown")
2050
+ to_version = expected_version.get("to", "unknown")
2051
+ info += (
2052
+ f"- Library '{lib}' has version {current_version}. "
2053
+ f"Expected version range: {from_version} to {to_version}.\n"
2054
+ )
2055
+ return info.strip()
2056
+
2057
+ class UnsupportedVisualizationError(RAIException):
2058
+ def __init__(self):
2059
+ name = "Unsupported Visualization Error"
2060
+ self.source = Errors.call_source() or debugging.SourceInfo()
2061
+ super().__init__(name, name, self.format_message(), self.source)
2062
+
2063
+ def format_message(self):
2064
+ marked = mark_source(self.source)
2065
+ return textwrap.dedent(f"""
2066
+ This environment does not support embedding interactive visualizations via Graph.visualize().
2067
+
2068
+ {marked}
2069
+
2070
+ You can use graph.fetch() [1] to retrieve the necessary data to render your own visualization via whatever means the environment supports. Alternatively, consider running your program in another supported environment like a python script or jupyter.
2071
+
2072
+ [1]: https://relational.ai/docs/reference/python/std/graphs/Graph/fetch
2073
+ """)
2074
+
2075
+ class EngineSizeMismatchWarning(RAIWarning):
2076
+ def __init__(self, engine_name: str, existing_size: str, requested_size: str):
2077
+ self.engine_name = engine_name
2078
+ self.existing_size = existing_size
2079
+ self.requested_size = requested_size
2080
+ self.message = f"An engine named '{engine_name}' already exists with a different size ('{existing_size}'). The existing engine will be used."
2081
+ self.name = "Engine size mismatch"
2082
+
2083
+ # Format the content and raw_content
2084
+ self.content = self.format_message()
2085
+ self.raw_content = self.strip_rich_tags()
2086
+
2087
+ super().__init__(self.message, self.name, self.content)
2088
+
2089
+ def format_message(self):
2090
+ return textwrap.dedent(f"""
2091
+ An engine named '{self.engine_name}' already exists with a different size ('{self.existing_size}'). The existing engine will be used. You can change the size of '{self.engine_name}' by deleting the existing engine.
2092
+
2093
+ You can delete the engine with either of the following commands:
2094
+
2095
+ [green]rai engines:delete --name {self.engine_name}[/green]
2096
+
2097
+ [green]rai.Provider().delete_engine('{self.engine_name}')[/green]
2098
+ """)
2099
+
2100
+ #--------------------------------------------------
2101
+ # Deprecations + Migrations
2102
+ #--------------------------------------------------
2103
+
2104
+ class DeprecationWarning(RAIWarning):
2105
+ def __init__(self, api: str, fix: str|None = None):
2106
+ self.api = api
2107
+ self.fix = fix
2108
+
2109
+ self.name = "Deprecation Warning"
2110
+ self.prefix = f"'{api}' is deprecated and should no longer be used."
2111
+ self.message = f"{self.prefix} {fix}" if fix else self.prefix
2112
+ self.source = Errors.call_source() or debugging.SourceInfo()
2113
+ self.content = self.format_message()
2114
+
2115
+ super().__init__(self.message, self.name, self.content, source=self.source)
2116
+
2117
+ def format_message(self):
2118
+ marked = mark_source(self.source, self.source.line, self.source.line)
2119
+ return textwrap.dedent(f"""
2120
+ {self.prefix}
2121
+
2122
+ {marked}
2123
+
2124
+ {self.fix}
2125
+ """)
2126
+
2127
+ class SnowflakeProxyAPIDeprecationWarning(DeprecationWarning):
2128
+ def __init__(self):
2129
+ super().__init__("clients.snowflake.Snowflake", "Instead, use the `source` keyword argument when defining types. See https://relational.ai/docs/reference/python/Model/Type for details.")
2130
+
2131
+ class SnowflakeProxySourceError(RAIException):
2132
+ def __init__(self):
2133
+ self.name = "Snowflake Proxy Source Error"
2134
+ self.message = "Data sources derived from the deprecated snowflake proxy 'clients.snowflake.Snowflake' aren't supported with your current configuration."
2135
+ self.source = Errors.call_source() or debugging.SourceInfo()
2136
+ self.content = self.format_message()
2137
+
2138
+ super().__init__(self.message, self.name, self.content, source=self.source)
2139
+
2140
+ def format_message(self):
2141
+ marked = mark_source(self.source, self.source.line, self.source.line)
2142
+ return textwrap.dedent(f"""
2143
+ {self.message}
2144
+
2145
+ {marked}
2146
+
2147
+ Instead, use the `source` keyword argument when defining types. See https://relational.ai/docs/reference/python/Model/Type for details.
2148
+ """)
2149
+
2150
+ #--------------------------------------------------
2151
+ # Direct Access Warnings
2152
+ #--------------------------------------------------
2153
+
2154
+ class DirectAccessInvalidAuthWarning(RAIWarning):
2155
+ def __init__(self, authenticator, valid_authenticators):
2156
+ self.authenticator = authenticator
2157
+ self.valid_authenticators = valid_authenticators
2158
+ self.name = "Direct Access Invalid Auth"
2159
+ self.message = f"Due to security constraints in Snowflake, the selected authenticator '{authenticator}' is not supported when using direct access."
2160
+ self.content = self.format_message()
2161
+ super().__init__(self.message, self.name, self.content)
2162
+
2163
+ def format_message(self):
2164
+ valid_authenticators_str = ", ".join(self.valid_authenticators)
2165
+ return textwrap.dedent(f"""
2166
+ {self.message}
2167
+ Falling back to 'use_direct_access=False'. This comes with a potential negative latency impact.
2168
+ Direct access requires one of the following authenticators: {valid_authenticators_str}
2169
+ """)
2170
+
2171
+ class NonDefaultLQPSemanticsVersionWarning(RAIWarning):
2172
+ def __init__(self, current_version: str, default_version: str):
2173
+ self.current_version = current_version
2174
+ self.default_version = default_version
2175
+ self.name = "Non-default LQP Semantics Version"
2176
+ self.message = f"Using non-default LQP semantics version {current_version}. Default is {default_version}."
2177
+ self.content = self.format_message()
2178
+ super().__init__(self.message, self.name, self.content)
2179
+
2180
+ def format_message(self):
2181
+ return textwrap.dedent(f"""
2182
+ {self.message}
2183
+
2184
+ You are using a non-default LQP semantics version, likely to avoid a change in
2185
+ behaviour that broke one of your models. This is a reminder to ensure you switch
2186
+ back to the default version once any blocking issues have been resolved.
2187
+
2188
+ To do so you need to remove the following section from your raiconfig.toml:
2189
+
2190
+ [reasoner.rule]
2191
+ lqp.semantics_version = {self.current_version}
2192
+ """)
2193
+
2194
+ class InsecureKeychainWarning(RAIWarning):
2195
+ def __init__(self):
2196
+ self.message = "Insecure keyring detected. Please use a secure keyring backend."
2197
+ self.name = "Insecure Keyring Warning"
2198
+ self.content = self.format_message()
2199
+
2200
+ super().__init__(self.message, self.name, self.content)
2201
+
2202
+ def format_message(self):
2203
+ return textwrap.dedent(f"""
2204
+ {self.message}
2205
+
2206
+ A secure keychain is used to cache refresh tokens for oauth authentication. Without
2207
+ this caching mechanism a re-authentication will be required for each execution.
2208
+ """)
2209
+
2210
+ class KeychainFailureWarning(RAIWarning):
2211
+ def __init__(self, message):
2212
+ self.message = message
2213
+ self.name = "Keychain Failure Warning"
2214
+ self.content = self.format_message()
2215
+
2216
+ super().__init__(self.message, self.name, self.content)
2217
+
2218
+ def format_message(self):
2219
+ return textwrap.dedent(f"""
2220
+ {self.message}
2221
+
2222
+ This may be due to a misconfiguration or an issue with the keyring backend.
2223
+ A secure keychain is used to cache refresh tokens for oauth authentication. Without
2224
+ this caching mechanism a re-authentication will be required for each execution.
2225
+ """)
2226
+
2227
+ #--------------------------------------------------
2228
+ # Direct Access Exceptions
2229
+ #--------------------------------------------------
2230
+
2231
+ class DirectAccessInvalidAuthException(RAIException):
2232
+ def __init__(self, authenticator, valid_authenticators):
2233
+ self.authenticator = authenticator
2234
+ self.valid_authenticators = valid_authenticators
2235
+ self.name = "Direct Access Invalid Auth"
2236
+ self.message = f"Due to security constraints in Snowflake, the selected authenticator '{authenticator}' is not supported when using direct access."
2237
+ self.content = self.format_message()
2238
+ super().__init__(self.message, self.name, self.content)
2239
+
2240
+ def format_message(self):
2241
+ valid_authenticators_str = ", ".join(self.valid_authenticators)
2242
+ return textwrap.dedent(f"""
2243
+ {self.message}
2244
+ Use a valid authenticator or deactivate direct access via 'use_direct_access=False'.
2245
+ Direct access requires one of the following authenticators: {valid_authenticators_str}
2246
+ """)
2247
+
2248
+ class ResponseStatusException(RAIException):
2249
+ def __init__(self, message: str, response):
2250
+ self.message = message
2251
+ self.response = response
2252
+ self.response_message = self._extract_response_message()
2253
+ self.name = "Response Status Error"
2254
+ self.content = self.format_message()
2255
+
2256
+ super().__init__(self.message, self.name, self.content)
2257
+
2258
+ def format_message(self):
2259
+ return f"{self.message} Status Code: {self.response.status_code}, Reason: {self.response.reason} {self.response_message}"
2260
+
2261
+ def _extract_response_message(self):
2262
+ try:
2263
+ return self.response.json().get("message", self.response.text)
2264
+ except Exception:
2265
+ return self.response.text
2266
+
2267
+ class OAuthFailedPortBinding(RAIException):
2268
+ def __init__(self, redirect_port, exception):
2269
+ self.redirect_port = redirect_port
2270
+ self.message = f"Failed to bind to the OAuth redirect URI port {self.redirect_port}."
2271
+ self.name = "OAuth Failed Port Binding Error"
2272
+ self.exception = exception
2273
+ self.content = self.format_message()
2274
+ super().__init__(self.message, self.name, self.content)
2275
+
2276
+ def format_message(self):
2277
+ return textwrap.dedent(f"""
2278
+ {self.message}
2279
+
2280
+ This usually happens when the specified port for the OAuth redirect URI is not available.
2281
+ You can fix the issue by ensuring that the port {self.redirect_port} is open and accessible.
2282
+ You can check which processes are using this port with the command 'lsof -t -i :{self.redirect_port}' on Unix-based systems or 'netstat -ano | findstr :{self.redirect_port}' on Windows.
2283
+ If you are sure no process should be running on this port, you can try killing any processes that are using it.
2284
+
2285
+ The original exception was: {self.exception}
2286
+ """)
2287
+
2288
+ #TODO: Take out call_source from Errors class and put it in the main module
2289
+ class Errors:
2290
+ @staticmethod
2291
+ def call_source(steps=None):
2292
+ if steps is None:
2293
+ return runtime_env.get_source()
2294
+ return runtime_env.get_source(steps + 1)
2295
+
2296
+
2297
+ class InvalidAliasError(RAIException):
2298
+ def __init__(self, msg: str):
2299
+ self.message = msg
2300
+ self.name = "Invalid property alias found"
2301
+ self.content = self.format_message()
2302
+
2303
+ super().__init__(self.message, self.name, self.content)
2304
+
2305
+ def format_message(self):
2306
+ return self.message
2307
+
2308
+
2309
+ class LeftOverRelationException(RAIException):
2310
+ def __init__(self, import_name: str, model: str):
2311
+ self.import_name = import_name
2312
+ self.model = model
2313
+ self.message = f"Relations are not empty for import '{import_name}'"
2314
+ self.name = f"Stream relations for '{import_name}' are not empty"
2315
+ self.content = self.format_message()
2316
+
2317
+ super().__init__(self.message, self.name, self.content)
2318
+
2319
+ def format_message(self):
2320
+ return textwrap.dedent(f"""
2321
+ The import '{self.import_name}' cannot be created due to existing non-empty relationships.
2322
+
2323
+ You can use the [green]--force[/green] parameter to overwrite these:
2324
+
2325
+ [green]rai imports:stream --source {self.import_name.upper()} --model {self.model} --force[/green]
2326
+
2327
+ To delete the import and all its relationships, use the [green]--force[/green] parameter:
2328
+
2329
+ [green]rai imports:delete --object {self.import_name.upper()} --model {self.model} --force[/green]
2330
+ """)
2331
+
2332
+ class UnsupportedColumnTypesWarning(RAIWarning):
2333
+ def __init__(self, table_invalid_columns: dict[str, dict[str, str]]):
2334
+ self.table_invalid_columns = table_invalid_columns
2335
+ self.message = self._generate_message()
2336
+ self.name = self._generate_name()
2337
+ self.content = self.format_message()
2338
+
2339
+ super().__init__(self.message, self.name, self.content)
2340
+
2341
+ def _generate_message(self):
2342
+ total_tables = len(self.table_invalid_columns)
2343
+ total_columns = sum(len(invalid_columns) for invalid_columns in self.table_invalid_columns.values())
2344
+
2345
+ if total_tables == 1:
2346
+ table_name = next(iter(self.table_invalid_columns.keys()))
2347
+ return f"Found {total_columns} unsupported column type(s) in table '{table_name}'"
2348
+ else:
2349
+ return f"Found {total_columns} unsupported column type(s) across {total_tables} tables"
2350
+
2351
+ def _generate_name(self):
2352
+ total_tables = len(self.table_invalid_columns)
2353
+ total_columns = sum(len(invalid_columns) for invalid_columns in self.table_invalid_columns.values())
2354
+
2355
+ if total_tables == 1:
2356
+ table_name = next(iter(self.table_invalid_columns.keys()))
2357
+ return f"Unsupported Column Types - {total_columns} column(s) in {table_name}"
2358
+ else:
2359
+ return f"Unsupported Column Types - {total_columns} column(s) across {total_tables} tables"
2360
+
2361
+ def format_message(self):
2362
+ def extract_type(data_type):
2363
+ # Extract just the basic type from Snowflake data_type
2364
+ # data_type might be a JSON string like {"type":"VARIANT","nullable":true}
2365
+ # or a simple string like "VARIANT"
2366
+ try:
2367
+ parsed = json.loads(data_type)
2368
+ if isinstance(parsed, dict) and "type" in parsed:
2369
+ return parsed["type"]
2370
+ return str(parsed)
2371
+ except (json.JSONDecodeError, TypeError):
2372
+ # If it's not JSON, treat it as a simple string
2373
+ if '(' in data_type:
2374
+ return data_type.split('(')[0]
2375
+ return data_type
2376
+
2377
+ total_tables = len(self.table_invalid_columns)
2378
+ total_columns = sum(len(invalid_columns) for invalid_columns in self.table_invalid_columns.values())
2379
+
2380
+ # Build organized display by table
2381
+ table_sections = []
2382
+ columns_displayed = 0
2383
+ MAX_COLUMNS_TO_DISPLAY = 10
2384
+
2385
+ for table_name, invalid_columns in self.table_invalid_columns.items():
2386
+ table_columns = []
2387
+ for col_name, data_type in invalid_columns.items():
2388
+ if columns_displayed >= MAX_COLUMNS_TO_DISPLAY:
2389
+ break
2390
+ type_only = extract_type(data_type)
2391
+ table_columns.append(f" [yellow]{col_name}[/yellow]: [red]{type_only}[/red]")
2392
+ columns_displayed += 1
2393
+
2394
+ if table_columns:
2395
+ table_sections.append(f"[yellow]{table_name}[/yellow]:\n" + "\n".join(table_columns))
2396
+
2397
+ if columns_displayed >= MAX_COLUMNS_TO_DISPLAY:
2398
+ break
2399
+
2400
+ # Build the message
2401
+ if total_tables == 1:
2402
+ table_name = next(iter(self.table_invalid_columns.keys()))
2403
+ message = f"Found {total_columns} unsupported column type(s) in table '{table_name}':"
2404
+ else:
2405
+ message = f"Found {total_columns} unsupported column type(s) across {total_tables} tables:"
2406
+
2407
+ if total_columns > MAX_COLUMNS_TO_DISPLAY:
2408
+ message += f" (showing first {MAX_COLUMNS_TO_DISPLAY} for brevity)"
2409
+
2410
+ note = "These columns will not be accessible in your model.\n\nFor the list of supported column types see: https://docs.relational.ai/api/cli/imports/stream/#supported-column-types"
2411
+
2412
+ columns_text = "\n\n".join(table_sections)
2413
+
2414
+ return textwrap.dedent(f"""
2415
+ {message}
2416
+
2417
+ {columns_text}
2418
+
2419
+ {note}
2420
+ """)
2421
+
2422
+ class QueryTimeoutExceededException(RAIException):
2423
+ def __init__(self, timeout_mins: int, query_id: str | None = None, config_file_path: str | None = None):
2424
+ self.timeout_mins = timeout_mins
2425
+ self.name = "Query Timeout Exceeded"
2426
+ self.message = f"Query execution time exceeded the specified timeout of {self.timeout_mins} minutes."
2427
+ self.query_id = query_id or ""
2428
+ self.config_file_path = config_file_path or ""
2429
+ self.content = self.format_message()
2430
+ super().__init__(self.message, self.name, self.content)
2431
+
2432
+ def format_message(self):
2433
+ return textwrap.dedent(f"""
2434
+ Query execution time exceeded the specified timeout of {self.timeout_mins} minutes{f' for query with ID: {self.query_id}' if self.query_id else ''}.
2435
+
2436
+ Consider increasing the 'query_timeout_mins' parameter in your configuration file{f' (stored in {self.config_file_path})' if self.config_file_path else ''} to allow more time for query execution.
2437
+ """)
2438
+
2439
+
2440
+ #--------------------------------------------------
2441
+ # Azure Exceptions
2442
+ #--------------------------------------------------
2443
+
2444
+ class AzureUnsupportedQueryTimeoutException(RAIException):
2445
+ def __init__(self, config_file_path: str | None = None):
2446
+ self.message = "Query timeouts aren't supported on platform Azure."
2447
+ self.name = "Azure Unsupported Query Timeout Error"
2448
+ self.config_file_path = config_file_path or ""
2449
+ self.content = self.format_message()
2450
+ super().__init__(self.message, self.name, self.content)
2451
+
2452
+ def format_message(self):
2453
+ return textwrap.dedent(f"""
2454
+ {self.message}
2455
+
2456
+ Please remove the 'query_timeout_mins' from your configuration file{f' (stored in {self.config_file_path})' if self.config_file_path else ''} when running on platform Azure.
2457
+ """)
2458
+
2459
+ class AzureLegacyDependencyMissingException(RAIException):
2460
+ def __init__(self):
2461
+ self.message = "The Azure platform requires the 'legacy' extras to be installed."
2462
+ self.name = "Azure Legacy Dependency Missing"
2463
+ self.content = self.format_message()
2464
+ super().__init__(self.message, self.name, self.content)
2465
+
2466
+ def format_message(self):
2467
+ return textwrap.dedent("""
2468
+ The Azure platform requires the 'rai-sdk' package, which is not installed.
2469
+
2470
+ To use the Azure platform, please install the legacy extras:
2471
+
2472
+ pip install relationalai[legacy]
2473
+
2474
+ Or if upgrading an existing installation:
2475
+
2476
+ pip install --upgrade relationalai[legacy]
2477
+ """)
2478
+