relationalai 0.13.0__py3-none-any.whl → 0.13.0.dev0__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 (836) 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/semantics/__init__.py +146 -22
  16. relationalai/semantics/backends/lqp/annotations.py +11 -0
  17. relationalai/semantics/backends/sql/sql_compiler.py +327 -0
  18. relationalai/semantics/frontend/base.py +1707 -0
  19. relationalai/semantics/frontend/core.py +179 -0
  20. relationalai/semantics/frontend/front_compiler.py +1313 -0
  21. relationalai/semantics/frontend/pprint.py +408 -0
  22. relationalai/semantics/metamodel/__init__.py +6 -40
  23. relationalai/semantics/metamodel/builtins.py +205 -771
  24. relationalai/semantics/metamodel/metamodel.py +437 -0
  25. relationalai/semantics/metamodel/metamodel_analyzer.py +519 -0
  26. relationalai/semantics/metamodel/pprint.py +412 -0
  27. relationalai/semantics/metamodel/rewriter.py +266 -0
  28. relationalai/semantics/metamodel/typer.py +1378 -0
  29. relationalai/semantics/std/__init__.py +60 -40
  30. relationalai/semantics/std/aggregates.py +149 -0
  31. relationalai/semantics/std/common.py +44 -0
  32. relationalai/semantics/std/constraints.py +37 -43
  33. relationalai/semantics/std/datetime.py +246 -135
  34. relationalai/semantics/std/decimals.py +45 -52
  35. relationalai/semantics/std/floats.py +13 -5
  36. relationalai/semantics/std/integers.py +26 -11
  37. relationalai/semantics/std/math.py +183 -112
  38. relationalai/semantics/std/numbers.py +86 -0
  39. relationalai/semantics/std/re.py +80 -62
  40. relationalai/semantics/std/strings.py +117 -60
  41. relationalai/shims/executor.py +147 -0
  42. relationalai/shims/helpers.py +126 -0
  43. relationalai/shims/hoister.py +221 -0
  44. relationalai/shims/mm2v0.py +1290 -0
  45. relationalai/tools/cli/__init__.py +6 -0
  46. relationalai/tools/cli/cli.py +90 -0
  47. relationalai/tools/cli/components/__init__.py +5 -0
  48. relationalai/tools/cli/components/progress_reader.py +1524 -0
  49. relationalai/tools/cli/components/utils.py +58 -0
  50. relationalai/tools/cli/config_template.py +45 -0
  51. relationalai/tools/cli/dev.py +19 -0
  52. relationalai/tools/debugger.py +289 -183
  53. relationalai/tools/typer_debugger.py +93 -0
  54. relationalai/util/dataclasses.py +43 -0
  55. relationalai/util/docutils.py +40 -0
  56. relationalai/util/error.py +199 -0
  57. relationalai/util/format.py +48 -106
  58. relationalai/util/naming.py +145 -0
  59. relationalai/util/python.py +35 -0
  60. relationalai/util/runtime.py +156 -0
  61. relationalai/util/schema.py +197 -0
  62. relationalai/util/source.py +185 -0
  63. relationalai/util/structures.py +163 -0
  64. relationalai/util/tracing.py +261 -0
  65. relationalai-0.13.0.dev0.dist-info/METADATA +46 -0
  66. relationalai-0.13.0.dev0.dist-info/RECORD +488 -0
  67. relationalai-0.13.0.dev0.dist-info/WHEEL +5 -0
  68. relationalai-0.13.0.dev0.dist-info/entry_points.txt +3 -0
  69. relationalai-0.13.0.dev0.dist-info/top_level.txt +2 -0
  70. v0/relationalai/__init__.py +216 -0
  71. v0/relationalai/clients/__init__.py +5 -0
  72. v0/relationalai/clients/azure.py +477 -0
  73. v0/relationalai/clients/client.py +912 -0
  74. v0/relationalai/clients/config.py +673 -0
  75. v0/relationalai/clients/direct_access_client.py +118 -0
  76. v0/relationalai/clients/hash_util.py +31 -0
  77. v0/relationalai/clients/local.py +571 -0
  78. v0/relationalai/clients/profile_polling.py +73 -0
  79. v0/relationalai/clients/result_helpers.py +420 -0
  80. v0/relationalai/clients/snowflake.py +3869 -0
  81. v0/relationalai/clients/types.py +113 -0
  82. v0/relationalai/clients/use_index_poller.py +980 -0
  83. v0/relationalai/clients/util.py +356 -0
  84. v0/relationalai/debugging.py +389 -0
  85. v0/relationalai/dsl.py +1749 -0
  86. v0/relationalai/early_access/builder/__init__.py +30 -0
  87. v0/relationalai/early_access/builder/builder/__init__.py +35 -0
  88. v0/relationalai/early_access/builder/snowflake/__init__.py +12 -0
  89. v0/relationalai/early_access/builder/std/__init__.py +25 -0
  90. v0/relationalai/early_access/builder/std/decimals/__init__.py +12 -0
  91. v0/relationalai/early_access/builder/std/integers/__init__.py +12 -0
  92. v0/relationalai/early_access/builder/std/math/__init__.py +12 -0
  93. v0/relationalai/early_access/builder/std/strings/__init__.py +14 -0
  94. v0/relationalai/early_access/devtools/__init__.py +12 -0
  95. v0/relationalai/early_access/devtools/benchmark_lqp/__init__.py +12 -0
  96. v0/relationalai/early_access/devtools/extract_lqp/__init__.py +12 -0
  97. v0/relationalai/early_access/dsl/adapters/orm/adapter_qb.py +427 -0
  98. v0/relationalai/early_access/dsl/adapters/orm/parser.py +636 -0
  99. v0/relationalai/early_access/dsl/adapters/owl/adapter.py +176 -0
  100. v0/relationalai/early_access/dsl/adapters/owl/parser.py +160 -0
  101. v0/relationalai/early_access/dsl/bindings/common.py +402 -0
  102. v0/relationalai/early_access/dsl/bindings/csv.py +170 -0
  103. v0/relationalai/early_access/dsl/bindings/legacy/binding_models.py +143 -0
  104. v0/relationalai/early_access/dsl/bindings/snowflake.py +64 -0
  105. v0/relationalai/early_access/dsl/codegen/binder.py +411 -0
  106. v0/relationalai/early_access/dsl/codegen/common.py +79 -0
  107. v0/relationalai/early_access/dsl/codegen/helpers.py +23 -0
  108. v0/relationalai/early_access/dsl/codegen/relations.py +700 -0
  109. v0/relationalai/early_access/dsl/codegen/weaver.py +417 -0
  110. v0/relationalai/early_access/dsl/core/builders/__init__.py +47 -0
  111. v0/relationalai/early_access/dsl/core/builders/logic.py +19 -0
  112. v0/relationalai/early_access/dsl/core/builders/scalar_constraint.py +11 -0
  113. v0/relationalai/early_access/dsl/core/constraints/predicate/atomic.py +455 -0
  114. v0/relationalai/early_access/dsl/core/constraints/predicate/universal.py +73 -0
  115. v0/relationalai/early_access/dsl/core/constraints/scalar.py +310 -0
  116. v0/relationalai/early_access/dsl/core/context.py +13 -0
  117. v0/relationalai/early_access/dsl/core/cset.py +132 -0
  118. v0/relationalai/early_access/dsl/core/exprs/__init__.py +116 -0
  119. v0/relationalai/early_access/dsl/core/exprs/relational.py +18 -0
  120. v0/relationalai/early_access/dsl/core/exprs/scalar.py +412 -0
  121. v0/relationalai/early_access/dsl/core/instances.py +44 -0
  122. v0/relationalai/early_access/dsl/core/logic/__init__.py +193 -0
  123. v0/relationalai/early_access/dsl/core/logic/aggregation.py +98 -0
  124. v0/relationalai/early_access/dsl/core/logic/exists.py +223 -0
  125. v0/relationalai/early_access/dsl/core/logic/helper.py +163 -0
  126. v0/relationalai/early_access/dsl/core/namespaces.py +32 -0
  127. v0/relationalai/early_access/dsl/core/relations.py +276 -0
  128. v0/relationalai/early_access/dsl/core/rules.py +112 -0
  129. v0/relationalai/early_access/dsl/core/std/__init__.py +45 -0
  130. v0/relationalai/early_access/dsl/core/temporal/recall.py +6 -0
  131. v0/relationalai/early_access/dsl/core/types/__init__.py +270 -0
  132. v0/relationalai/early_access/dsl/core/types/concepts.py +128 -0
  133. v0/relationalai/early_access/dsl/core/types/constrained/__init__.py +267 -0
  134. v0/relationalai/early_access/dsl/core/types/constrained/nominal.py +143 -0
  135. v0/relationalai/early_access/dsl/core/types/constrained/subtype.py +124 -0
  136. v0/relationalai/early_access/dsl/core/types/standard.py +92 -0
  137. v0/relationalai/early_access/dsl/core/types/unconstrained.py +50 -0
  138. v0/relationalai/early_access/dsl/core/types/variables.py +203 -0
  139. v0/relationalai/early_access/dsl/ir/compiler.py +318 -0
  140. v0/relationalai/early_access/dsl/ir/executor.py +260 -0
  141. v0/relationalai/early_access/dsl/ontologies/constraints.py +88 -0
  142. v0/relationalai/early_access/dsl/ontologies/export.py +30 -0
  143. v0/relationalai/early_access/dsl/ontologies/models.py +453 -0
  144. v0/relationalai/early_access/dsl/ontologies/python_printer.py +303 -0
  145. v0/relationalai/early_access/dsl/ontologies/readings.py +60 -0
  146. v0/relationalai/early_access/dsl/ontologies/relationships.py +322 -0
  147. v0/relationalai/early_access/dsl/ontologies/roles.py +87 -0
  148. v0/relationalai/early_access/dsl/ontologies/subtyping.py +55 -0
  149. v0/relationalai/early_access/dsl/orm/constraints.py +438 -0
  150. v0/relationalai/early_access/dsl/orm/measures/dimensions.py +200 -0
  151. v0/relationalai/early_access/dsl/orm/measures/initializer.py +16 -0
  152. v0/relationalai/early_access/dsl/orm/measures/measure_rules.py +275 -0
  153. v0/relationalai/early_access/dsl/orm/measures/measures.py +299 -0
  154. v0/relationalai/early_access/dsl/orm/measures/role_exprs.py +268 -0
  155. v0/relationalai/early_access/dsl/orm/models.py +256 -0
  156. v0/relationalai/early_access/dsl/orm/object_oriented_printer.py +344 -0
  157. v0/relationalai/early_access/dsl/orm/printer.py +469 -0
  158. v0/relationalai/early_access/dsl/orm/reasoners.py +480 -0
  159. v0/relationalai/early_access/dsl/orm/relations.py +19 -0
  160. v0/relationalai/early_access/dsl/orm/relationships.py +251 -0
  161. v0/relationalai/early_access/dsl/orm/types.py +42 -0
  162. v0/relationalai/early_access/dsl/orm/utils.py +79 -0
  163. v0/relationalai/early_access/dsl/orm/verb.py +204 -0
  164. v0/relationalai/early_access/dsl/physical_metadata/tables.py +133 -0
  165. v0/relationalai/early_access/dsl/relations.py +170 -0
  166. v0/relationalai/early_access/dsl/rulesets.py +69 -0
  167. v0/relationalai/early_access/dsl/schemas/__init__.py +450 -0
  168. v0/relationalai/early_access/dsl/schemas/builder.py +48 -0
  169. v0/relationalai/early_access/dsl/schemas/comp_names.py +51 -0
  170. v0/relationalai/early_access/dsl/schemas/components.py +203 -0
  171. v0/relationalai/early_access/dsl/schemas/contexts.py +156 -0
  172. v0/relationalai/early_access/dsl/schemas/exprs.py +89 -0
  173. v0/relationalai/early_access/dsl/schemas/fragments.py +464 -0
  174. v0/relationalai/early_access/dsl/serialization.py +79 -0
  175. v0/relationalai/early_access/dsl/serialize/exporter.py +163 -0
  176. v0/relationalai/early_access/dsl/snow/api.py +104 -0
  177. v0/relationalai/early_access/dsl/snow/common.py +76 -0
  178. v0/relationalai/early_access/dsl/state_mgmt/__init__.py +129 -0
  179. v0/relationalai/early_access/dsl/state_mgmt/state_charts.py +125 -0
  180. v0/relationalai/early_access/dsl/state_mgmt/transitions.py +130 -0
  181. v0/relationalai/early_access/dsl/types/__init__.py +40 -0
  182. v0/relationalai/early_access/dsl/types/concepts.py +12 -0
  183. v0/relationalai/early_access/dsl/types/entities.py +135 -0
  184. v0/relationalai/early_access/dsl/types/values.py +17 -0
  185. v0/relationalai/early_access/dsl/utils.py +102 -0
  186. v0/relationalai/early_access/graphs/__init__.py +13 -0
  187. v0/relationalai/early_access/lqp/__init__.py +12 -0
  188. v0/relationalai/early_access/lqp/compiler/__init__.py +12 -0
  189. v0/relationalai/early_access/lqp/constructors/__init__.py +18 -0
  190. v0/relationalai/early_access/lqp/executor/__init__.py +12 -0
  191. v0/relationalai/early_access/lqp/ir/__init__.py +12 -0
  192. v0/relationalai/early_access/lqp/passes/__init__.py +12 -0
  193. v0/relationalai/early_access/lqp/pragmas/__init__.py +12 -0
  194. v0/relationalai/early_access/lqp/primitives/__init__.py +12 -0
  195. v0/relationalai/early_access/lqp/types/__init__.py +12 -0
  196. v0/relationalai/early_access/lqp/utils/__init__.py +12 -0
  197. v0/relationalai/early_access/lqp/validators/__init__.py +12 -0
  198. v0/relationalai/early_access/metamodel/__init__.py +58 -0
  199. v0/relationalai/early_access/metamodel/builtins/__init__.py +12 -0
  200. v0/relationalai/early_access/metamodel/compiler/__init__.py +12 -0
  201. v0/relationalai/early_access/metamodel/dependency/__init__.py +12 -0
  202. v0/relationalai/early_access/metamodel/factory/__init__.py +17 -0
  203. v0/relationalai/early_access/metamodel/helpers/__init__.py +12 -0
  204. v0/relationalai/early_access/metamodel/ir/__init__.py +14 -0
  205. v0/relationalai/early_access/metamodel/rewrite/__init__.py +7 -0
  206. v0/relationalai/early_access/metamodel/typer/__init__.py +3 -0
  207. v0/relationalai/early_access/metamodel/typer/typer/__init__.py +12 -0
  208. v0/relationalai/early_access/metamodel/types/__init__.py +15 -0
  209. v0/relationalai/early_access/metamodel/util/__init__.py +15 -0
  210. v0/relationalai/early_access/metamodel/visitor/__init__.py +12 -0
  211. v0/relationalai/early_access/rel/__init__.py +12 -0
  212. v0/relationalai/early_access/rel/executor/__init__.py +12 -0
  213. v0/relationalai/early_access/rel/rel_utils/__init__.py +12 -0
  214. v0/relationalai/early_access/rel/rewrite/__init__.py +7 -0
  215. v0/relationalai/early_access/solvers/__init__.py +19 -0
  216. v0/relationalai/early_access/sql/__init__.py +11 -0
  217. v0/relationalai/early_access/sql/executor/__init__.py +3 -0
  218. v0/relationalai/early_access/sql/rewrite/__init__.py +3 -0
  219. v0/relationalai/early_access/tests/logging/__init__.py +12 -0
  220. v0/relationalai/early_access/tests/test_snapshot_base/__init__.py +12 -0
  221. v0/relationalai/early_access/tests/utils/__init__.py +12 -0
  222. v0/relationalai/environments/__init__.py +35 -0
  223. v0/relationalai/environments/base.py +381 -0
  224. v0/relationalai/environments/colab.py +14 -0
  225. v0/relationalai/environments/generic.py +71 -0
  226. v0/relationalai/environments/ipython.py +68 -0
  227. v0/relationalai/environments/jupyter.py +9 -0
  228. v0/relationalai/environments/snowbook.py +169 -0
  229. v0/relationalai/errors.py +2455 -0
  230. v0/relationalai/experimental/SF.py +38 -0
  231. v0/relationalai/experimental/inspect.py +47 -0
  232. v0/relationalai/experimental/pathfinder/__init__.py +158 -0
  233. v0/relationalai/experimental/pathfinder/api.py +160 -0
  234. v0/relationalai/experimental/pathfinder/automaton.py +584 -0
  235. v0/relationalai/experimental/pathfinder/bridge.py +226 -0
  236. v0/relationalai/experimental/pathfinder/compiler.py +416 -0
  237. v0/relationalai/experimental/pathfinder/datalog.py +214 -0
  238. v0/relationalai/experimental/pathfinder/diagnostics.py +56 -0
  239. v0/relationalai/experimental/pathfinder/filter.py +236 -0
  240. v0/relationalai/experimental/pathfinder/glushkov.py +439 -0
  241. v0/relationalai/experimental/pathfinder/options.py +265 -0
  242. v0/relationalai/experimental/pathfinder/rpq.py +344 -0
  243. v0/relationalai/experimental/pathfinder/transition.py +200 -0
  244. v0/relationalai/experimental/pathfinder/utils.py +26 -0
  245. v0/relationalai/experimental/paths/api.py +143 -0
  246. v0/relationalai/experimental/paths/benchmarks/grid_graph.py +37 -0
  247. v0/relationalai/experimental/paths/examples/basic_example.py +40 -0
  248. v0/relationalai/experimental/paths/examples/minimal_engine_warmup.py +3 -0
  249. v0/relationalai/experimental/paths/examples/movie_example.py +77 -0
  250. v0/relationalai/experimental/paths/examples/paths_benchmark.py +115 -0
  251. v0/relationalai/experimental/paths/examples/paths_example.py +116 -0
  252. v0/relationalai/experimental/paths/examples/pattern_to_automaton.py +28 -0
  253. v0/relationalai/experimental/paths/find_paths_via_automaton.py +85 -0
  254. v0/relationalai/experimental/paths/graph.py +185 -0
  255. v0/relationalai/experimental/paths/path_algorithms/find_paths.py +280 -0
  256. v0/relationalai/experimental/paths/path_algorithms/one_sided_ball_repetition.py +26 -0
  257. v0/relationalai/experimental/paths/path_algorithms/one_sided_ball_upto.py +111 -0
  258. v0/relationalai/experimental/paths/path_algorithms/single.py +59 -0
  259. v0/relationalai/experimental/paths/path_algorithms/two_sided_balls_repetition.py +39 -0
  260. v0/relationalai/experimental/paths/path_algorithms/two_sided_balls_upto.py +103 -0
  261. v0/relationalai/experimental/paths/path_algorithms/usp-old.py +130 -0
  262. v0/relationalai/experimental/paths/path_algorithms/usp-tuple.py +183 -0
  263. v0/relationalai/experimental/paths/path_algorithms/usp.py +150 -0
  264. v0/relationalai/experimental/paths/product_graph.py +93 -0
  265. v0/relationalai/experimental/paths/rpq/automaton.py +584 -0
  266. v0/relationalai/experimental/paths/rpq/diagnostics.py +56 -0
  267. v0/relationalai/experimental/paths/rpq/rpq.py +378 -0
  268. v0/relationalai/experimental/paths/tests/tests_limit_sp_max_length.py +90 -0
  269. v0/relationalai/experimental/paths/tests/tests_limit_sp_multiple.py +119 -0
  270. v0/relationalai/experimental/paths/tests/tests_limit_sp_single.py +104 -0
  271. v0/relationalai/experimental/paths/tests/tests_limit_walks_multiple.py +113 -0
  272. v0/relationalai/experimental/paths/tests/tests_limit_walks_single.py +149 -0
  273. v0/relationalai/experimental/paths/tests/tests_one_sided_ball_repetition_multiple.py +70 -0
  274. v0/relationalai/experimental/paths/tests/tests_one_sided_ball_repetition_single.py +64 -0
  275. v0/relationalai/experimental/paths/tests/tests_one_sided_ball_upto_multiple.py +115 -0
  276. v0/relationalai/experimental/paths/tests/tests_one_sided_ball_upto_single.py +75 -0
  277. v0/relationalai/experimental/paths/tests/tests_single_paths.py +152 -0
  278. v0/relationalai/experimental/paths/tests/tests_single_walks.py +208 -0
  279. v0/relationalai/experimental/paths/tests/tests_single_walks_undirected.py +297 -0
  280. v0/relationalai/experimental/paths/tests/tests_two_sided_balls_repetition_multiple.py +107 -0
  281. v0/relationalai/experimental/paths/tests/tests_two_sided_balls_repetition_single.py +76 -0
  282. v0/relationalai/experimental/paths/tests/tests_two_sided_balls_upto_multiple.py +76 -0
  283. v0/relationalai/experimental/paths/tests/tests_two_sided_balls_upto_single.py +110 -0
  284. v0/relationalai/experimental/paths/tests/tests_usp_nsp_multiple.py +229 -0
  285. v0/relationalai/experimental/paths/tests/tests_usp_nsp_single.py +108 -0
  286. v0/relationalai/experimental/paths/tree_agg.py +168 -0
  287. v0/relationalai/experimental/paths/utilities/iterators.py +27 -0
  288. v0/relationalai/experimental/paths/utilities/prefix_sum.py +91 -0
  289. v0/relationalai/experimental/solvers.py +1087 -0
  290. v0/relationalai/loaders/__init__.py +0 -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 +324 -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 +469 -0
  306. v0/relationalai/semantics/lqp/intrinsics.py +24 -0
  307. v0/relationalai/semantics/lqp/model2lqp.py +839 -0
  308. v0/relationalai/semantics/lqp/passes.py +680 -0
  309. v0/relationalai/semantics/lqp/primitives.py +252 -0
  310. v0/relationalai/semantics/lqp/result_helpers.py +202 -0
  311. v0/relationalai/semantics/lqp/rewrite/annotate_constraints.py +57 -0
  312. v0/relationalai/semantics/lqp/rewrite/cdc.py +216 -0
  313. v0/relationalai/semantics/lqp/rewrite/extract_common.py +338 -0
  314. v0/relationalai/semantics/lqp/rewrite/extract_keys.py +449 -0
  315. v0/relationalai/semantics/lqp/rewrite/function_annotations.py +114 -0
  316. v0/relationalai/semantics/lqp/rewrite/functional_dependencies.py +314 -0
  317. v0/relationalai/semantics/lqp/rewrite/quantify_vars.py +296 -0
  318. v0/relationalai/semantics/lqp/rewrite/splinter.py +76 -0
  319. v0/relationalai/semantics/lqp/types.py +101 -0
  320. v0/relationalai/semantics/lqp/utils.py +160 -0
  321. v0/relationalai/semantics/lqp/validators.py +57 -0
  322. v0/relationalai/semantics/metamodel/__init__.py +40 -0
  323. v0/relationalai/semantics/metamodel/builtins.py +774 -0
  324. v0/relationalai/semantics/metamodel/compiler.py +133 -0
  325. v0/relationalai/semantics/metamodel/dependency.py +862 -0
  326. v0/relationalai/semantics/metamodel/executor.py +61 -0
  327. v0/relationalai/semantics/metamodel/factory.py +287 -0
  328. v0/relationalai/semantics/metamodel/helpers.py +361 -0
  329. v0/relationalai/semantics/metamodel/rewrite/discharge_constraints.py +39 -0
  330. v0/relationalai/semantics/metamodel/rewrite/dnf_union_splitter.py +210 -0
  331. v0/relationalai/semantics/metamodel/rewrite/extract_nested_logicals.py +78 -0
  332. v0/relationalai/semantics/metamodel/rewrite/flatten.py +549 -0
  333. v0/relationalai/semantics/metamodel/rewrite/format_outputs.py +165 -0
  334. v0/relationalai/semantics/metamodel/typer/checker.py +353 -0
  335. v0/relationalai/semantics/metamodel/typer/typer.py +1395 -0
  336. v0/relationalai/semantics/metamodel/util.py +505 -0
  337. v0/relationalai/semantics/reasoners/__init__.py +10 -0
  338. v0/relationalai/semantics/reasoners/graph/__init__.py +37 -0
  339. v0/relationalai/semantics/reasoners/graph/core.py +9020 -0
  340. v0/relationalai/semantics/reasoners/optimization/__init__.py +68 -0
  341. v0/relationalai/semantics/reasoners/optimization/common.py +88 -0
  342. v0/relationalai/semantics/reasoners/optimization/solvers_dev.py +568 -0
  343. v0/relationalai/semantics/reasoners/optimization/solvers_pb.py +1163 -0
  344. v0/relationalai/semantics/rel/builtins.py +40 -0
  345. v0/relationalai/semantics/rel/compiler.py +989 -0
  346. v0/relationalai/semantics/rel/executor.py +359 -0
  347. v0/relationalai/semantics/rel/rel.py +482 -0
  348. v0/relationalai/semantics/rel/rel_utils.py +276 -0
  349. v0/relationalai/semantics/snowflake/__init__.py +3 -0
  350. v0/relationalai/semantics/sql/compiler.py +2503 -0
  351. v0/relationalai/semantics/sql/executor/duck_db.py +52 -0
  352. v0/relationalai/semantics/sql/executor/result_helpers.py +64 -0
  353. v0/relationalai/semantics/sql/executor/snowflake.py +145 -0
  354. v0/relationalai/semantics/sql/rewrite/denormalize.py +222 -0
  355. v0/relationalai/semantics/sql/rewrite/double_negation.py +49 -0
  356. v0/relationalai/semantics/sql/rewrite/recursive_union.py +127 -0
  357. v0/relationalai/semantics/sql/rewrite/sort_output_query.py +246 -0
  358. v0/relationalai/semantics/sql/sql.py +504 -0
  359. v0/relationalai/semantics/std/__init__.py +54 -0
  360. v0/relationalai/semantics/std/constraints.py +43 -0
  361. v0/relationalai/semantics/std/datetime.py +363 -0
  362. v0/relationalai/semantics/std/decimals.py +62 -0
  363. v0/relationalai/semantics/std/floats.py +7 -0
  364. v0/relationalai/semantics/std/integers.py +22 -0
  365. v0/relationalai/semantics/std/math.py +141 -0
  366. v0/relationalai/semantics/std/pragmas.py +11 -0
  367. v0/relationalai/semantics/std/re.py +83 -0
  368. v0/relationalai/semantics/std/std.py +14 -0
  369. v0/relationalai/semantics/std/strings.py +63 -0
  370. v0/relationalai/semantics/tests/__init__.py +0 -0
  371. v0/relationalai/semantics/tests/test_snapshot_abstract.py +143 -0
  372. v0/relationalai/semantics/tests/test_snapshot_base.py +9 -0
  373. v0/relationalai/semantics/tests/utils.py +46 -0
  374. v0/relationalai/std/__init__.py +70 -0
  375. v0/relationalai/tools/__init__.py +0 -0
  376. v0/relationalai/tools/cli.py +1940 -0
  377. v0/relationalai/tools/cli_controls.py +1826 -0
  378. v0/relationalai/tools/cli_helpers.py +390 -0
  379. v0/relationalai/tools/debugger.py +183 -0
  380. v0/relationalai/tools/debugger_client.py +109 -0
  381. v0/relationalai/tools/debugger_server.py +302 -0
  382. v0/relationalai/tools/dev.py +685 -0
  383. v0/relationalai/tools/qb_debugger.py +425 -0
  384. v0/relationalai/util/clean_up_databases.py +95 -0
  385. v0/relationalai/util/format.py +123 -0
  386. v0/relationalai/util/list_databases.py +9 -0
  387. v0/relationalai/util/otel_configuration.py +25 -0
  388. v0/relationalai/util/otel_handler.py +484 -0
  389. v0/relationalai/util/snowflake_handler.py +88 -0
  390. v0/relationalai/util/span_format_test.py +43 -0
  391. v0/relationalai/util/span_tracker.py +207 -0
  392. v0/relationalai/util/spans_file_handler.py +72 -0
  393. v0/relationalai/util/tracing_handler.py +34 -0
  394. frontend/debugger/dist/.gitignore +0 -2
  395. frontend/debugger/dist/assets/favicon-Dy0ZgA6N.png +0 -0
  396. frontend/debugger/dist/assets/index-Cssla-O7.js +0 -208
  397. frontend/debugger/dist/assets/index-DlHsYx1V.css +0 -9
  398. frontend/debugger/dist/index.html +0 -17
  399. relationalai/clients/__init__.py +0 -18
  400. relationalai/clients/client.py +0 -912
  401. relationalai/clients/config.py +0 -673
  402. relationalai/clients/direct_access_client.py +0 -118
  403. relationalai/clients/hash_util.py +0 -31
  404. relationalai/clients/local.py +0 -571
  405. relationalai/clients/profile_polling.py +0 -73
  406. relationalai/clients/resources/__init__.py +0 -8
  407. relationalai/clients/resources/azure/azure.py +0 -477
  408. relationalai/clients/resources/snowflake/__init__.py +0 -20
  409. relationalai/clients/resources/snowflake/cli_resources.py +0 -87
  410. relationalai/clients/resources/snowflake/direct_access_resources.py +0 -711
  411. relationalai/clients/resources/snowflake/engine_state_handlers.py +0 -309
  412. relationalai/clients/resources/snowflake/error_handlers.py +0 -199
  413. relationalai/clients/resources/snowflake/export_procedure.py.jinja +0 -249
  414. relationalai/clients/resources/snowflake/resources_factory.py +0 -99
  415. relationalai/clients/resources/snowflake/snowflake.py +0 -3083
  416. relationalai/clients/resources/snowflake/use_index_poller.py +0 -1011
  417. relationalai/clients/resources/snowflake/use_index_resources.py +0 -188
  418. relationalai/clients/resources/snowflake/util.py +0 -387
  419. relationalai/clients/result_helpers.py +0 -420
  420. relationalai/clients/types.py +0 -113
  421. relationalai/clients/util.py +0 -356
  422. relationalai/debugging.py +0 -389
  423. relationalai/dsl.py +0 -1749
  424. relationalai/early_access/builder/__init__.py +0 -30
  425. relationalai/early_access/builder/builder/__init__.py +0 -35
  426. relationalai/early_access/builder/snowflake/__init__.py +0 -12
  427. relationalai/early_access/builder/std/__init__.py +0 -25
  428. relationalai/early_access/builder/std/decimals/__init__.py +0 -12
  429. relationalai/early_access/builder/std/integers/__init__.py +0 -12
  430. relationalai/early_access/builder/std/math/__init__.py +0 -12
  431. relationalai/early_access/builder/std/strings/__init__.py +0 -14
  432. relationalai/early_access/devtools/__init__.py +0 -12
  433. relationalai/early_access/devtools/benchmark_lqp/__init__.py +0 -12
  434. relationalai/early_access/devtools/extract_lqp/__init__.py +0 -12
  435. relationalai/early_access/dsl/adapters/orm/adapter_qb.py +0 -427
  436. relationalai/early_access/dsl/adapters/orm/parser.py +0 -636
  437. relationalai/early_access/dsl/adapters/owl/adapter.py +0 -176
  438. relationalai/early_access/dsl/adapters/owl/parser.py +0 -160
  439. relationalai/early_access/dsl/bindings/common.py +0 -402
  440. relationalai/early_access/dsl/bindings/csv.py +0 -170
  441. relationalai/early_access/dsl/bindings/legacy/binding_models.py +0 -143
  442. relationalai/early_access/dsl/bindings/snowflake.py +0 -64
  443. relationalai/early_access/dsl/codegen/binder.py +0 -411
  444. relationalai/early_access/dsl/codegen/common.py +0 -79
  445. relationalai/early_access/dsl/codegen/helpers.py +0 -23
  446. relationalai/early_access/dsl/codegen/relations.py +0 -700
  447. relationalai/early_access/dsl/codegen/weaver.py +0 -417
  448. relationalai/early_access/dsl/core/builders/__init__.py +0 -47
  449. relationalai/early_access/dsl/core/builders/logic.py +0 -19
  450. relationalai/early_access/dsl/core/builders/scalar_constraint.py +0 -11
  451. relationalai/early_access/dsl/core/constraints/predicate/atomic.py +0 -455
  452. relationalai/early_access/dsl/core/constraints/predicate/universal.py +0 -73
  453. relationalai/early_access/dsl/core/constraints/scalar.py +0 -310
  454. relationalai/early_access/dsl/core/context.py +0 -13
  455. relationalai/early_access/dsl/core/cset.py +0 -132
  456. relationalai/early_access/dsl/core/exprs/__init__.py +0 -116
  457. relationalai/early_access/dsl/core/exprs/relational.py +0 -18
  458. relationalai/early_access/dsl/core/exprs/scalar.py +0 -412
  459. relationalai/early_access/dsl/core/instances.py +0 -44
  460. relationalai/early_access/dsl/core/logic/__init__.py +0 -193
  461. relationalai/early_access/dsl/core/logic/aggregation.py +0 -98
  462. relationalai/early_access/dsl/core/logic/exists.py +0 -223
  463. relationalai/early_access/dsl/core/logic/helper.py +0 -163
  464. relationalai/early_access/dsl/core/namespaces.py +0 -32
  465. relationalai/early_access/dsl/core/relations.py +0 -276
  466. relationalai/early_access/dsl/core/rules.py +0 -112
  467. relationalai/early_access/dsl/core/std/__init__.py +0 -45
  468. relationalai/early_access/dsl/core/temporal/recall.py +0 -6
  469. relationalai/early_access/dsl/core/types/__init__.py +0 -270
  470. relationalai/early_access/dsl/core/types/concepts.py +0 -128
  471. relationalai/early_access/dsl/core/types/constrained/__init__.py +0 -267
  472. relationalai/early_access/dsl/core/types/constrained/nominal.py +0 -143
  473. relationalai/early_access/dsl/core/types/constrained/subtype.py +0 -124
  474. relationalai/early_access/dsl/core/types/standard.py +0 -92
  475. relationalai/early_access/dsl/core/types/unconstrained.py +0 -50
  476. relationalai/early_access/dsl/core/types/variables.py +0 -203
  477. relationalai/early_access/dsl/ir/compiler.py +0 -318
  478. relationalai/early_access/dsl/ir/executor.py +0 -260
  479. relationalai/early_access/dsl/ontologies/constraints.py +0 -88
  480. relationalai/early_access/dsl/ontologies/export.py +0 -30
  481. relationalai/early_access/dsl/ontologies/models.py +0 -453
  482. relationalai/early_access/dsl/ontologies/python_printer.py +0 -303
  483. relationalai/early_access/dsl/ontologies/readings.py +0 -60
  484. relationalai/early_access/dsl/ontologies/relationships.py +0 -322
  485. relationalai/early_access/dsl/ontologies/roles.py +0 -87
  486. relationalai/early_access/dsl/ontologies/subtyping.py +0 -55
  487. relationalai/early_access/dsl/orm/constraints.py +0 -438
  488. relationalai/early_access/dsl/orm/measures/dimensions.py +0 -200
  489. relationalai/early_access/dsl/orm/measures/initializer.py +0 -16
  490. relationalai/early_access/dsl/orm/measures/measure_rules.py +0 -275
  491. relationalai/early_access/dsl/orm/measures/measures.py +0 -299
  492. relationalai/early_access/dsl/orm/measures/role_exprs.py +0 -268
  493. relationalai/early_access/dsl/orm/models.py +0 -256
  494. relationalai/early_access/dsl/orm/object_oriented_printer.py +0 -344
  495. relationalai/early_access/dsl/orm/printer.py +0 -469
  496. relationalai/early_access/dsl/orm/reasoners.py +0 -480
  497. relationalai/early_access/dsl/orm/relations.py +0 -19
  498. relationalai/early_access/dsl/orm/relationships.py +0 -251
  499. relationalai/early_access/dsl/orm/types.py +0 -42
  500. relationalai/early_access/dsl/orm/utils.py +0 -79
  501. relationalai/early_access/dsl/orm/verb.py +0 -204
  502. relationalai/early_access/dsl/physical_metadata/tables.py +0 -133
  503. relationalai/early_access/dsl/relations.py +0 -170
  504. relationalai/early_access/dsl/rulesets.py +0 -69
  505. relationalai/early_access/dsl/schemas/__init__.py +0 -450
  506. relationalai/early_access/dsl/schemas/builder.py +0 -48
  507. relationalai/early_access/dsl/schemas/comp_names.py +0 -51
  508. relationalai/early_access/dsl/schemas/components.py +0 -203
  509. relationalai/early_access/dsl/schemas/contexts.py +0 -156
  510. relationalai/early_access/dsl/schemas/exprs.py +0 -89
  511. relationalai/early_access/dsl/schemas/fragments.py +0 -464
  512. relationalai/early_access/dsl/serialization.py +0 -79
  513. relationalai/early_access/dsl/serialize/exporter.py +0 -163
  514. relationalai/early_access/dsl/snow/api.py +0 -105
  515. relationalai/early_access/dsl/snow/common.py +0 -76
  516. relationalai/early_access/dsl/state_mgmt/__init__.py +0 -129
  517. relationalai/early_access/dsl/state_mgmt/state_charts.py +0 -125
  518. relationalai/early_access/dsl/state_mgmt/transitions.py +0 -130
  519. relationalai/early_access/dsl/types/__init__.py +0 -40
  520. relationalai/early_access/dsl/types/concepts.py +0 -12
  521. relationalai/early_access/dsl/types/entities.py +0 -135
  522. relationalai/early_access/dsl/types/values.py +0 -17
  523. relationalai/early_access/dsl/utils.py +0 -102
  524. relationalai/early_access/graphs/__init__.py +0 -13
  525. relationalai/early_access/lqp/__init__.py +0 -12
  526. relationalai/early_access/lqp/compiler/__init__.py +0 -12
  527. relationalai/early_access/lqp/constructors/__init__.py +0 -18
  528. relationalai/early_access/lqp/executor/__init__.py +0 -12
  529. relationalai/early_access/lqp/ir/__init__.py +0 -12
  530. relationalai/early_access/lqp/passes/__init__.py +0 -12
  531. relationalai/early_access/lqp/pragmas/__init__.py +0 -12
  532. relationalai/early_access/lqp/primitives/__init__.py +0 -12
  533. relationalai/early_access/lqp/types/__init__.py +0 -12
  534. relationalai/early_access/lqp/utils/__init__.py +0 -12
  535. relationalai/early_access/lqp/validators/__init__.py +0 -12
  536. relationalai/early_access/metamodel/__init__.py +0 -58
  537. relationalai/early_access/metamodel/builtins/__init__.py +0 -12
  538. relationalai/early_access/metamodel/compiler/__init__.py +0 -12
  539. relationalai/early_access/metamodel/dependency/__init__.py +0 -12
  540. relationalai/early_access/metamodel/factory/__init__.py +0 -17
  541. relationalai/early_access/metamodel/helpers/__init__.py +0 -12
  542. relationalai/early_access/metamodel/ir/__init__.py +0 -14
  543. relationalai/early_access/metamodel/rewrite/__init__.py +0 -7
  544. relationalai/early_access/metamodel/typer/__init__.py +0 -3
  545. relationalai/early_access/metamodel/typer/typer/__init__.py +0 -12
  546. relationalai/early_access/metamodel/types/__init__.py +0 -15
  547. relationalai/early_access/metamodel/util/__init__.py +0 -15
  548. relationalai/early_access/metamodel/visitor/__init__.py +0 -12
  549. relationalai/early_access/rel/__init__.py +0 -12
  550. relationalai/early_access/rel/executor/__init__.py +0 -12
  551. relationalai/early_access/rel/rel_utils/__init__.py +0 -12
  552. relationalai/early_access/rel/rewrite/__init__.py +0 -7
  553. relationalai/early_access/solvers/__init__.py +0 -19
  554. relationalai/early_access/sql/__init__.py +0 -11
  555. relationalai/early_access/sql/executor/__init__.py +0 -3
  556. relationalai/early_access/sql/rewrite/__init__.py +0 -3
  557. relationalai/early_access/tests/logging/__init__.py +0 -12
  558. relationalai/early_access/tests/test_snapshot_base/__init__.py +0 -12
  559. relationalai/early_access/tests/utils/__init__.py +0 -12
  560. relationalai/environments/__init__.py +0 -35
  561. relationalai/environments/base.py +0 -381
  562. relationalai/environments/colab.py +0 -14
  563. relationalai/environments/generic.py +0 -71
  564. relationalai/environments/ipython.py +0 -68
  565. relationalai/environments/jupyter.py +0 -9
  566. relationalai/environments/snowbook.py +0 -169
  567. relationalai/errors.py +0 -2478
  568. relationalai/experimental/SF.py +0 -38
  569. relationalai/experimental/inspect.py +0 -47
  570. relationalai/experimental/pathfinder/__init__.py +0 -158
  571. relationalai/experimental/pathfinder/api.py +0 -160
  572. relationalai/experimental/pathfinder/automaton.py +0 -584
  573. relationalai/experimental/pathfinder/bridge.py +0 -226
  574. relationalai/experimental/pathfinder/compiler.py +0 -416
  575. relationalai/experimental/pathfinder/datalog.py +0 -214
  576. relationalai/experimental/pathfinder/diagnostics.py +0 -56
  577. relationalai/experimental/pathfinder/filter.py +0 -236
  578. relationalai/experimental/pathfinder/glushkov.py +0 -439
  579. relationalai/experimental/pathfinder/options.py +0 -265
  580. relationalai/experimental/pathfinder/pathfinder-v0.7.0.rel +0 -1951
  581. relationalai/experimental/pathfinder/rpq.py +0 -344
  582. relationalai/experimental/pathfinder/transition.py +0 -200
  583. relationalai/experimental/pathfinder/utils.py +0 -26
  584. relationalai/experimental/paths/README.md +0 -107
  585. relationalai/experimental/paths/api.py +0 -143
  586. relationalai/experimental/paths/benchmarks/grid_graph.py +0 -37
  587. relationalai/experimental/paths/code_organization.md +0 -2
  588. relationalai/experimental/paths/examples/Movies.ipynb +0 -16328
  589. relationalai/experimental/paths/examples/basic_example.py +0 -40
  590. relationalai/experimental/paths/examples/minimal_engine_warmup.py +0 -3
  591. relationalai/experimental/paths/examples/movie_example.py +0 -77
  592. relationalai/experimental/paths/examples/movies_data/actedin.csv +0 -193
  593. relationalai/experimental/paths/examples/movies_data/directed.csv +0 -45
  594. relationalai/experimental/paths/examples/movies_data/follows.csv +0 -7
  595. relationalai/experimental/paths/examples/movies_data/movies.csv +0 -39
  596. relationalai/experimental/paths/examples/movies_data/person.csv +0 -134
  597. relationalai/experimental/paths/examples/movies_data/produced.csv +0 -16
  598. relationalai/experimental/paths/examples/movies_data/ratings.csv +0 -10
  599. relationalai/experimental/paths/examples/movies_data/wrote.csv +0 -11
  600. relationalai/experimental/paths/examples/paths_benchmark.py +0 -115
  601. relationalai/experimental/paths/examples/paths_example.py +0 -116
  602. relationalai/experimental/paths/examples/pattern_to_automaton.py +0 -28
  603. relationalai/experimental/paths/find_paths_via_automaton.py +0 -85
  604. relationalai/experimental/paths/graph.py +0 -185
  605. relationalai/experimental/paths/path_algorithms/find_paths.py +0 -280
  606. relationalai/experimental/paths/path_algorithms/one_sided_ball_repetition.py +0 -26
  607. relationalai/experimental/paths/path_algorithms/one_sided_ball_upto.py +0 -111
  608. relationalai/experimental/paths/path_algorithms/single.py +0 -59
  609. relationalai/experimental/paths/path_algorithms/two_sided_balls_repetition.py +0 -39
  610. relationalai/experimental/paths/path_algorithms/two_sided_balls_upto.py +0 -103
  611. relationalai/experimental/paths/path_algorithms/usp-old.py +0 -130
  612. relationalai/experimental/paths/path_algorithms/usp-tuple.py +0 -183
  613. relationalai/experimental/paths/path_algorithms/usp.py +0 -150
  614. relationalai/experimental/paths/product_graph.py +0 -93
  615. relationalai/experimental/paths/rpq/automaton.py +0 -584
  616. relationalai/experimental/paths/rpq/diagnostics.py +0 -56
  617. relationalai/experimental/paths/rpq/rpq.py +0 -378
  618. relationalai/experimental/paths/tests/tests_limit_sp_max_length.py +0 -90
  619. relationalai/experimental/paths/tests/tests_limit_sp_multiple.py +0 -119
  620. relationalai/experimental/paths/tests/tests_limit_sp_single.py +0 -104
  621. relationalai/experimental/paths/tests/tests_limit_walks_multiple.py +0 -113
  622. relationalai/experimental/paths/tests/tests_limit_walks_single.py +0 -149
  623. relationalai/experimental/paths/tests/tests_one_sided_ball_repetition_multiple.py +0 -70
  624. relationalai/experimental/paths/tests/tests_one_sided_ball_repetition_single.py +0 -64
  625. relationalai/experimental/paths/tests/tests_one_sided_ball_upto_multiple.py +0 -115
  626. relationalai/experimental/paths/tests/tests_one_sided_ball_upto_single.py +0 -75
  627. relationalai/experimental/paths/tests/tests_single_paths.py +0 -152
  628. relationalai/experimental/paths/tests/tests_single_walks.py +0 -208
  629. relationalai/experimental/paths/tests/tests_single_walks_undirected.py +0 -297
  630. relationalai/experimental/paths/tests/tests_two_sided_balls_repetition_multiple.py +0 -107
  631. relationalai/experimental/paths/tests/tests_two_sided_balls_repetition_single.py +0 -76
  632. relationalai/experimental/paths/tests/tests_two_sided_balls_upto_multiple.py +0 -76
  633. relationalai/experimental/paths/tests/tests_two_sided_balls_upto_single.py +0 -110
  634. relationalai/experimental/paths/tests/tests_usp_nsp_multiple.py +0 -229
  635. relationalai/experimental/paths/tests/tests_usp_nsp_single.py +0 -108
  636. relationalai/experimental/paths/tree_agg.py +0 -168
  637. relationalai/experimental/paths/utilities/iterators.py +0 -27
  638. relationalai/experimental/paths/utilities/prefix_sum.py +0 -91
  639. relationalai/experimental/solvers.py +0 -1087
  640. relationalai/loaders/csv.py +0 -195
  641. relationalai/loaders/loader.py +0 -177
  642. relationalai/loaders/types.py +0 -23
  643. relationalai/rel_emitter.py +0 -373
  644. relationalai/rel_utils.py +0 -185
  645. relationalai/semantics/designs/query_builder/identify_by.md +0 -106
  646. relationalai/semantics/devtools/benchmark_lqp.py +0 -535
  647. relationalai/semantics/devtools/compilation_manager.py +0 -294
  648. relationalai/semantics/devtools/extract_lqp.py +0 -110
  649. relationalai/semantics/internal/internal.py +0 -3785
  650. relationalai/semantics/internal/snowflake.py +0 -325
  651. relationalai/semantics/lqp/README.md +0 -34
  652. relationalai/semantics/lqp/builtins.py +0 -16
  653. relationalai/semantics/lqp/compiler.py +0 -22
  654. relationalai/semantics/lqp/constructors.py +0 -68
  655. relationalai/semantics/lqp/executor.py +0 -469
  656. relationalai/semantics/lqp/intrinsics.py +0 -24
  657. relationalai/semantics/lqp/model2lqp.py +0 -877
  658. relationalai/semantics/lqp/passes.py +0 -680
  659. relationalai/semantics/lqp/primitives.py +0 -252
  660. relationalai/semantics/lqp/result_helpers.py +0 -202
  661. relationalai/semantics/lqp/rewrite/annotate_constraints.py +0 -57
  662. relationalai/semantics/lqp/rewrite/cdc.py +0 -216
  663. relationalai/semantics/lqp/rewrite/extract_common.py +0 -338
  664. relationalai/semantics/lqp/rewrite/extract_keys.py +0 -506
  665. relationalai/semantics/lqp/rewrite/function_annotations.py +0 -114
  666. relationalai/semantics/lqp/rewrite/functional_dependencies.py +0 -314
  667. relationalai/semantics/lqp/rewrite/quantify_vars.py +0 -296
  668. relationalai/semantics/lqp/rewrite/splinter.py +0 -76
  669. relationalai/semantics/lqp/types.py +0 -101
  670. relationalai/semantics/lqp/utils.py +0 -160
  671. relationalai/semantics/lqp/validators.py +0 -57
  672. relationalai/semantics/metamodel/compiler.py +0 -133
  673. relationalai/semantics/metamodel/dependency.py +0 -862
  674. relationalai/semantics/metamodel/executor.py +0 -61
  675. relationalai/semantics/metamodel/factory.py +0 -287
  676. relationalai/semantics/metamodel/helpers.py +0 -361
  677. relationalai/semantics/metamodel/rewrite/discharge_constraints.py +0 -39
  678. relationalai/semantics/metamodel/rewrite/dnf_union_splitter.py +0 -210
  679. relationalai/semantics/metamodel/rewrite/extract_nested_logicals.py +0 -78
  680. relationalai/semantics/metamodel/rewrite/flatten.py +0 -554
  681. relationalai/semantics/metamodel/rewrite/format_outputs.py +0 -165
  682. relationalai/semantics/metamodel/typer/checker.py +0 -353
  683. relationalai/semantics/metamodel/typer/typer.py +0 -1395
  684. relationalai/semantics/metamodel/util.py +0 -506
  685. relationalai/semantics/reasoners/__init__.py +0 -10
  686. relationalai/semantics/reasoners/graph/README.md +0 -620
  687. relationalai/semantics/reasoners/graph/__init__.py +0 -37
  688. relationalai/semantics/reasoners/graph/core.py +0 -9019
  689. relationalai/semantics/reasoners/graph/design/beyond_demand_transform.md +0 -797
  690. relationalai/semantics/reasoners/graph/tests/README.md +0 -21
  691. relationalai/semantics/reasoners/optimization/__init__.py +0 -68
  692. relationalai/semantics/reasoners/optimization/common.py +0 -88
  693. relationalai/semantics/reasoners/optimization/solvers_dev.py +0 -568
  694. relationalai/semantics/reasoners/optimization/solvers_pb.py +0 -1163
  695. relationalai/semantics/rel/builtins.py +0 -40
  696. relationalai/semantics/rel/compiler.py +0 -989
  697. relationalai/semantics/rel/executor.py +0 -362
  698. relationalai/semantics/rel/rel.py +0 -482
  699. relationalai/semantics/rel/rel_utils.py +0 -276
  700. relationalai/semantics/snowflake/__init__.py +0 -3
  701. relationalai/semantics/sql/compiler.py +0 -2503
  702. relationalai/semantics/sql/executor/duck_db.py +0 -52
  703. relationalai/semantics/sql/executor/result_helpers.py +0 -64
  704. relationalai/semantics/sql/executor/snowflake.py +0 -149
  705. relationalai/semantics/sql/rewrite/denormalize.py +0 -222
  706. relationalai/semantics/sql/rewrite/double_negation.py +0 -49
  707. relationalai/semantics/sql/rewrite/recursive_union.py +0 -127
  708. relationalai/semantics/sql/rewrite/sort_output_query.py +0 -246
  709. relationalai/semantics/sql/sql.py +0 -504
  710. relationalai/semantics/std/pragmas.py +0 -11
  711. relationalai/semantics/std/std.py +0 -14
  712. relationalai/semantics/tests/test_snapshot_abstract.py +0 -143
  713. relationalai/semantics/tests/test_snapshot_base.py +0 -9
  714. relationalai/semantics/tests/utils.py +0 -46
  715. relationalai/std/__init__.py +0 -70
  716. relationalai/tools/cli.py +0 -1936
  717. relationalai/tools/cli_controls.py +0 -1826
  718. relationalai/tools/cli_helpers.py +0 -398
  719. relationalai/tools/debugger_client.py +0 -109
  720. relationalai/tools/debugger_server.py +0 -302
  721. relationalai/tools/dev.py +0 -685
  722. relationalai/tools/notes +0 -7
  723. relationalai/tools/qb_debugger.py +0 -425
  724. relationalai/util/clean_up_databases.py +0 -95
  725. relationalai/util/list_databases.py +0 -9
  726. relationalai/util/otel_configuration.py +0 -26
  727. relationalai/util/otel_handler.py +0 -484
  728. relationalai/util/snowflake_handler.py +0 -88
  729. relationalai/util/span_format_test.py +0 -43
  730. relationalai/util/span_tracker.py +0 -207
  731. relationalai/util/spans_file_handler.py +0 -72
  732. relationalai/util/tracing_handler.py +0 -34
  733. relationalai-0.13.0.dist-info/METADATA +0 -74
  734. relationalai-0.13.0.dist-info/RECORD +0 -458
  735. relationalai-0.13.0.dist-info/WHEEL +0 -4
  736. relationalai-0.13.0.dist-info/entry_points.txt +0 -3
  737. relationalai-0.13.0.dist-info/licenses/LICENSE +0 -202
  738. relationalai_test_util/__init__.py +0 -4
  739. relationalai_test_util/fixtures.py +0 -229
  740. relationalai_test_util/snapshot.py +0 -252
  741. relationalai_test_util/traceback.py +0 -118
  742. /relationalai/{analysis → semantics/frontend}/__init__.py +0 -0
  743. /relationalai/{auth/__init__.py → semantics/metamodel/metamodel_compiler.py} +0 -0
  744. /relationalai/{early_access → shims}/__init__.py +0 -0
  745. {relationalai/early_access/dsl/adapters → v0/relationalai/analysis}/__init__.py +0 -0
  746. {relationalai → v0/relationalai}/analysis/mechanistic.py +0 -0
  747. {relationalai → v0/relationalai}/analysis/whynot.py +0 -0
  748. {relationalai/early_access/dsl/adapters/orm → v0/relationalai/auth}/__init__.py +0 -0
  749. {relationalai → v0/relationalai}/auth/jwt_generator.py +0 -0
  750. {relationalai → v0/relationalai}/auth/oauth_callback_server.py +0 -0
  751. {relationalai → v0/relationalai}/auth/token_handler.py +0 -0
  752. {relationalai → v0/relationalai}/auth/util.py +0 -0
  753. {relationalai/clients/resources/snowflake → v0/relationalai/clients}/cache_store.py +0 -0
  754. {relationalai → v0/relationalai}/compiler.py +0 -0
  755. {relationalai → v0/relationalai}/dependencies.py +0 -0
  756. {relationalai → v0/relationalai}/docutils.py +0 -0
  757. {relationalai/early_access/dsl/adapters/owl → v0/relationalai/early_access}/__init__.py +0 -0
  758. {relationalai → v0/relationalai}/early_access/dsl/__init__.py +0 -0
  759. {relationalai/early_access/dsl/bindings → v0/relationalai/early_access/dsl/adapters}/__init__.py +0 -0
  760. {relationalai/early_access/dsl/bindings/legacy → v0/relationalai/early_access/dsl/adapters/orm}/__init__.py +0 -0
  761. {relationalai → v0/relationalai}/early_access/dsl/adapters/orm/model.py +0 -0
  762. {relationalai/early_access/dsl/codegen → v0/relationalai/early_access/dsl/adapters/owl}/__init__.py +0 -0
  763. {relationalai → v0/relationalai}/early_access/dsl/adapters/owl/model.py +0 -0
  764. {relationalai/early_access/dsl/core/temporal → v0/relationalai/early_access/dsl/bindings}/__init__.py +0 -0
  765. {relationalai/early_access/dsl/ir → v0/relationalai/early_access/dsl/bindings/legacy}/__init__.py +0 -0
  766. {relationalai/early_access/dsl/ontologies → v0/relationalai/early_access/dsl/codegen}/__init__.py +0 -0
  767. {relationalai → v0/relationalai}/early_access/dsl/constants.py +0 -0
  768. {relationalai → v0/relationalai}/early_access/dsl/core/__init__.py +0 -0
  769. {relationalai → v0/relationalai}/early_access/dsl/core/constraints/__init__.py +0 -0
  770. {relationalai → v0/relationalai}/early_access/dsl/core/constraints/predicate/__init__.py +0 -0
  771. {relationalai → v0/relationalai}/early_access/dsl/core/stack.py +0 -0
  772. {relationalai/early_access/dsl/orm → v0/relationalai/early_access/dsl/core/temporal}/__init__.py +0 -0
  773. {relationalai → v0/relationalai}/early_access/dsl/core/utils.py +0 -0
  774. {relationalai/early_access/dsl/orm/measures → v0/relationalai/early_access/dsl/ir}/__init__.py +0 -0
  775. {relationalai/early_access/dsl/physical_metadata → v0/relationalai/early_access/dsl/ontologies}/__init__.py +0 -0
  776. {relationalai → v0/relationalai}/early_access/dsl/ontologies/raw_source.py +0 -0
  777. {relationalai/early_access/dsl/serialize → v0/relationalai/early_access/dsl/orm}/__init__.py +0 -0
  778. {relationalai/early_access/dsl/snow → v0/relationalai/early_access/dsl/orm/measures}/__init__.py +0 -0
  779. {relationalai → v0/relationalai}/early_access/dsl/orm/reasoner_errors.py +0 -0
  780. {relationalai/loaders → v0/relationalai/early_access/dsl/physical_metadata}/__init__.py +0 -0
  781. {relationalai/semantics/tests → v0/relationalai/early_access/dsl/serialize}/__init__.py +0 -0
  782. {relationalai → v0/relationalai}/early_access/dsl/serialize/binding_model.py +0 -0
  783. {relationalai → v0/relationalai}/early_access/dsl/serialize/model.py +0 -0
  784. {relationalai/tools → v0/relationalai/early_access/dsl/snow}/__init__.py +0 -0
  785. {relationalai → v0/relationalai}/early_access/tests/__init__.py +0 -0
  786. {relationalai → v0/relationalai}/environments/ci.py +0 -0
  787. {relationalai → v0/relationalai}/environments/hex.py +0 -0
  788. {relationalai → v0/relationalai}/environments/terminal.py +0 -0
  789. {relationalai → v0/relationalai}/experimental/__init__.py +0 -0
  790. {relationalai → v0/relationalai}/experimental/graphs.py +0 -0
  791. {relationalai → v0/relationalai}/experimental/paths/__init__.py +0 -0
  792. {relationalai → v0/relationalai}/experimental/paths/benchmarks/__init__.py +0 -0
  793. {relationalai → v0/relationalai}/experimental/paths/path_algorithms/__init__.py +0 -0
  794. {relationalai → v0/relationalai}/experimental/paths/rpq/__init__.py +0 -0
  795. {relationalai → v0/relationalai}/experimental/paths/rpq/filter.py +0 -0
  796. {relationalai → v0/relationalai}/experimental/paths/rpq/glushkov.py +0 -0
  797. {relationalai → v0/relationalai}/experimental/paths/rpq/transition.py +0 -0
  798. {relationalai → v0/relationalai}/experimental/paths/utilities/__init__.py +0 -0
  799. {relationalai → v0/relationalai}/experimental/paths/utilities/utilities.py +0 -0
  800. {relationalai → v0/relationalai}/metagen.py +0 -0
  801. {relationalai → v0/relationalai}/metamodel.py +0 -0
  802. {relationalai → v0/relationalai}/rel.py +0 -0
  803. {relationalai → v0/relationalai}/semantics/devtools/__init__.py +0 -0
  804. {relationalai → v0/relationalai}/semantics/internal/__init__.py +0 -0
  805. {relationalai → v0/relationalai}/semantics/internal/annotations.py +0 -0
  806. {relationalai → v0/relationalai}/semantics/lqp/__init__.py +0 -0
  807. {relationalai → v0/relationalai}/semantics/lqp/ir.py +0 -0
  808. {relationalai → v0/relationalai}/semantics/lqp/pragmas.py +0 -0
  809. {relationalai → v0/relationalai}/semantics/lqp/rewrite/__init__.py +0 -0
  810. {relationalai → v0/relationalai}/semantics/metamodel/dataflow.py +0 -0
  811. {relationalai → v0/relationalai}/semantics/metamodel/ir.py +0 -0
  812. {relationalai → v0/relationalai}/semantics/metamodel/rewrite/__init__.py +0 -0
  813. {relationalai → v0/relationalai}/semantics/metamodel/typer/__init__.py +0 -0
  814. {relationalai → v0/relationalai}/semantics/metamodel/types.py +0 -0
  815. {relationalai → v0/relationalai}/semantics/metamodel/visitor.py +0 -0
  816. {relationalai → v0/relationalai}/semantics/reasoners/experimental/__init__.py +0 -0
  817. {relationalai → v0/relationalai}/semantics/rel/__init__.py +0 -0
  818. {relationalai → v0/relationalai}/semantics/sql/__init__.py +0 -0
  819. {relationalai → v0/relationalai}/semantics/sql/executor/__init__.py +0 -0
  820. {relationalai → v0/relationalai}/semantics/sql/rewrite/__init__.py +0 -0
  821. {relationalai → v0/relationalai}/semantics/tests/logging.py +0 -0
  822. {relationalai → v0/relationalai}/std/aggregates.py +0 -0
  823. {relationalai → v0/relationalai}/std/dates.py +0 -0
  824. {relationalai → v0/relationalai}/std/graphs.py +0 -0
  825. {relationalai → v0/relationalai}/std/inspect.py +0 -0
  826. {relationalai → v0/relationalai}/std/math.py +0 -0
  827. {relationalai → v0/relationalai}/std/re.py +0 -0
  828. {relationalai → v0/relationalai}/std/strings.py +0 -0
  829. {relationalai → v0/relationalai}/tools/cleanup_snapshots.py +0 -0
  830. {relationalai → v0/relationalai}/tools/constants.py +0 -0
  831. {relationalai → v0/relationalai}/tools/query_utils.py +0 -0
  832. {relationalai → v0/relationalai}/tools/snapshot_viewer.py +0 -0
  833. {relationalai → v0/relationalai}/util/__init__.py +0 -0
  834. {relationalai → v0/relationalai}/util/constants.py +0 -0
  835. {relationalai → v0/relationalai}/util/graph.py +0 -0
  836. {relationalai → v0/relationalai}/util/timeout.py +0 -0
@@ -0,0 +1,1940 @@
1
+ #pyright: reportPrivateImportUsage=false
2
+ from __future__ import annotations
3
+ import os
4
+ import re
5
+ import sys
6
+ import rich
7
+ import json
8
+ import click
9
+ import shlex
10
+ from pathlib import Path
11
+ from rich.table import Table
12
+ from datetime import datetime
13
+ from rich import box as rich_box
14
+ from InquirerPy.base.control import Choice
15
+
16
+ from v0.relationalai.clients.util import IdentityParser
17
+ from .cli_controls import divider, Spinner
18
+ from . import cli_controls as controls
19
+ from typing import Sequence, cast, Any, List, TYPE_CHECKING
20
+
21
+ if TYPE_CHECKING:
22
+ from v0.relationalai.clients import azure
23
+ from v0.relationalai.errors import RAIException
24
+ from v0.relationalai.loaders.types import LoadType, UnsupportedTypeError
25
+ from v0.relationalai.loaders.csv import CSVLoader
26
+ from v0.relationalai.loaders.loader import Loader, rel_schema_to_type
27
+ from ..clients.types import ImportSource, ImportSourceFile, ImportSourceTable
28
+ from ..clients.client import ResourcesBase
29
+ from ..tools import debugger as deb, qb_debugger as qb_deb
30
+ from ..clients import config, snowflake
31
+ from v0.relationalai.tools.cli_helpers import (
32
+ EMPTY_STRING_REGEX,
33
+ ENGINE_NAME_ERROR,
34
+ ENGINE_NAME_REGEX,
35
+ PASSCODE_REGEX,
36
+ UUID,
37
+ RichGroup,
38
+ account_from_url,
39
+ coming_soon,
40
+ ensure_config,
41
+ exit_with_divider,
42
+ exit_with_error,
43
+ filter_profiles_by_platform,
44
+ format_row,
45
+ get_config, get_resource_provider,
46
+ is_latest_cli_version,
47
+ issue_top_level_profile_warning,
48
+ latest_version,
49
+ show_dictionary_table,
50
+ show_engines,
51
+ show_imports,
52
+ show_transactions,
53
+ supports_platform,
54
+ unexpand_user_path,
55
+ validate_engine_name
56
+ )
57
+ from ..clients.config import (
58
+ FIELD_PLACEHOLDER,
59
+ CONFIG_FILE,
60
+ ConfigStore,
61
+ all_configs_including_legacy,
62
+ get_from_snowflake_connections_toml,
63
+ azure_default_props,
64
+ map_toml_value,
65
+ SNOWFLAKE_AUTHS,
66
+ )
67
+ from v0.relationalai.tools.constants import AZURE, AZURE_ENVS, CONTEXT_SETTINGS, SNOWFLAKE, SNOWFLAKE_AUTHENTICATOR, GlobalProfile
68
+ from v0.relationalai.util.constants import DEFAULT_PROFILE_NAME, PARTIAL_PROFILE_NAME, TOP_LEVEL_PROFILE_NAME
69
+ from packaging.version import Version
70
+
71
+
72
+ #--------------------------------------------------
73
+ # Custom Click Option and Argument Types
74
+ #--------------------------------------------------
75
+
76
+ ImportOption = tuple[list[str], str]
77
+
78
+ class ImportOptionsType(click.ParamType):
79
+ def __init__(self):
80
+ self.options = {}
81
+
82
+ name = "import_options"
83
+ def convert(self, value: Any, param, ctx) -> ImportOption:
84
+ if ':' not in value or '=' not in value:
85
+ self.fail(f"'{value}' is not a valid import option.", param, ctx)
86
+ raw_key, val = value.split('=', 1)
87
+ if len(val) == 2 and val[0] == "\\":
88
+ val = val.encode().decode("unicode_escape")
89
+ return (raw_key.split(":"), val)
90
+
91
+ @classmethod
92
+ def reduce(cls, kvs:Sequence[ImportOption]):
93
+ options = {}
94
+ for (key_parts, val) in kvs:
95
+ cur = options
96
+ for part in key_parts[:-1]:
97
+ cur = cur.setdefault(part, {})
98
+ cur[key_parts[-1]] = val
99
+ return options
100
+
101
+ #--------------------------------------------------
102
+ # Main group
103
+ #--------------------------------------------------
104
+
105
+ @click.group(cls=RichGroup, context_settings=CONTEXT_SETTINGS)
106
+ @click.option("--profile", help="Which config profile to use")
107
+ def cli(profile):
108
+ is_latest, _, latest_ver = is_latest_cli_version()
109
+ if not is_latest:
110
+ rich.print()
111
+ rich.print(f"[yellow]RelationalAI version ({latest_ver}) is the latest. Please consider upgrading.[/yellow]")
112
+ GlobalProfile.set(profile)
113
+
114
+ #--------------------------------------------------
115
+ # Init
116
+ #--------------------------------------------------
117
+
118
+ @cli.command(help="Initialize a new project")
119
+ def init():
120
+ init_flow()
121
+
122
+ #--------------------------------------------------
123
+ # Init flow
124
+ #--------------------------------------------------
125
+
126
+ def azure_flow(cfg:config.Config):
127
+ option_selected = check_original_config_flow(cfg, "azure")
128
+ # get the client id and secret
129
+ client_id = controls.text("Client ID:", default=cfg.get("client_id", "") if option_selected else "")
130
+ client_secret = controls.password("Client Secret:", default=cfg.get("client_secret", "") if option_selected else "")
131
+ host = cfg.get("host", "")
132
+ client_credentials_url = cfg.get("client_credentials_url", "")
133
+ if not host or not client_credentials_url:
134
+ env = controls.fuzzy("Select environment:", [*AZURE_ENVS.keys(), "<custom>"])
135
+ if env == "<custom>":
136
+ host = controls.text("Host:")
137
+ client_credentials_url = controls.text("Client Credentials URL:")
138
+ else:
139
+ host = AZURE_ENVS[env]["host"]
140
+ client_credentials_url = AZURE_ENVS[env]["client_credentials_url"]
141
+ # setup the default config
142
+ cfg.fill_in_with_azure_defaults(
143
+ client_id=client_id,
144
+ client_secret=client_secret,
145
+ host=host if host else None,
146
+ client_credentials_url=client_credentials_url if client_credentials_url else None
147
+ )
148
+
149
+ def snowflake_flow(cfg:config.Config):
150
+ pyrel_config = check_original_config_flow(cfg, "snowflake")
151
+ if not pyrel_config:
152
+ check_snowflake_connections_flow(cfg)
153
+
154
+ provider = get_resource_provider("snowflake", cfg)
155
+
156
+ rich.print('\n Please select your Snowflake authentication method.')
157
+ rich.print('\n If the Snowflake user has Multi-Factor Authentication (MFA) enabled, select "USERNAME & PASSWORD (MFA ENABLED)" to enter the MFA passcode.')
158
+ rich.print(' If your Snowflake account supports Single Sign-On, please select "SINGLE SIGN-ON (SSO)".\n')
159
+
160
+ cfg_auth = cfg.get("authenticator", "snowflake")
161
+ auth_key = next((key for key, value in SNOWFLAKE_AUTHENTICATOR.items() if value == cfg_auth), None)
162
+
163
+ authenticator = SNOWFLAKE_AUTHENTICATOR[controls.fuzzy(
164
+ "Snowflake Authentication:",
165
+ choices=list(SNOWFLAKE_AUTHENTICATOR.keys()),
166
+ default=auth_key
167
+ )]
168
+ cfg.set("authenticator", authenticator)
169
+
170
+ # setup the authenticator default values in the config
171
+ cfg.fill_in_with_snowflake_defaults(authenticator=authenticator)
172
+
173
+ rich.print('\n Note: Account ID should look like: "<my_org_name>-<my_account_name>" e.g. "org-account123"')
174
+ rich.print(" Details: https://docs.snowflake.com/en/user-guide/admin-account-identifier\n")
175
+ rich.print(" Alternatively, you can log in to Snowsight, copy the URL, and paste it here.")
176
+ rich.print(" Example: https://app.snowflake.com/myorg/account123/worksheets\n")
177
+ account_or_url = controls.text(
178
+ "Snowflake account:",
179
+ default=cfg.get("account", ""),
180
+ validator=EMPTY_STRING_REGEX.match,
181
+ invalid_message="Account is required"
182
+ )
183
+ account = account_from_url(account_or_url)
184
+ if "." in account and "privatelink" not in account:
185
+ rich.print("\n[yellow] Your Account ID should not contain a period (.) character.")
186
+ corrected_account = account.replace(".", "-")
187
+ use_replacement = controls.confirm(f"Use '{corrected_account}' instead", default=True)
188
+ if use_replacement:
189
+ account = corrected_account
190
+ if account_or_url != account:
191
+ rich.print(f"\n[dim] Account ID: {account}")
192
+ cfg.set("account", account)
193
+
194
+ user_prompt = "Snowflake user:"
195
+ invalid_message = "User is required"
196
+ validator = EMPTY_STRING_REGEX.match
197
+
198
+ if authenticator == "externalbrowser":
199
+ rich.print('\n Note: For SSO, the user must match the one specified in the Identity Provider.\n')
200
+
201
+ # get account info
202
+ user = controls.text(
203
+ user_prompt,
204
+ default=cfg.get("user", ""),
205
+ validator = validator,
206
+ invalid_message=invalid_message
207
+ )
208
+ cfg.set("user", user)
209
+
210
+ if authenticator != "externalbrowser":
211
+ password = controls.password("SnowSQL password:", default=cfg.get("password", ""))
212
+ cfg.set("password", password)
213
+
214
+ if authenticator == "username_password_mfa":
215
+ rich.print('\n The MFA passcode can be found in your [cyan]Duo mobile[/cyan] app. Make sure you select the correct account passcode.')
216
+ rich.print(' To learn more about MFA: https://docs.snowflake.com/en/user-guide/security-mfa\n')
217
+ passcode = mfa_passcode_flow(cfg)
218
+ cfg.set("passcode", passcode)
219
+
220
+ if authenticator == "externalbrowser":
221
+ rich.print("")
222
+ try:
223
+ is_id_token = provider.is_account_flag_set("ALLOW_ID_TOKEN")
224
+ if not is_id_token:
225
+ rich.print("\nNote: [yellow2]Connection caching is not enabled in your Snowflake account.")
226
+ rich.print("You'll be prompted for SSO account selection for each request to Snowflake.")
227
+ rich.print("To learn more: https://docs.snowflake.com/en/user-guide/admin-security-fed-auth-use#label-browser-based-sso-connection-caching\n")
228
+ except Exception as e:
229
+ if "SAML Identity Provider account parameter" in f"{e}":
230
+ rich.print(f'[yellow2]Single Sign-On is not enabled on[/yellow2] "{account}" [yellow2]account.')
231
+ exit_with_error("Learn more: https://docs.snowflake.com/en/user-guide/admin-security-fed-auth-security-integration")
232
+ else:
233
+ raise e
234
+
235
+
236
+ # this has to be done here because the account ID must be available:
237
+ if not pyrel_config or cfg.get("engine_size", None) is None:
238
+ engine_size = controls.fuzzy(
239
+ "Select an engine size:",
240
+ choices=[size for size in provider.get_engine_sizes()],
241
+ default=cfg.get_default_engine_size()
242
+ )
243
+ cfg.set("engine_size", engine_size)
244
+
245
+ # init SF specific configuration
246
+ if not pyrel_config or cfg.get("data_freshness_mins", None) is None:
247
+ data_freshness_mins = controls.number(
248
+ "How often should data in RAI be refreshed (minutes)?\n(30 minutes recommended for development, otherwise 0 for data to refreshed on each program execution):",
249
+ default=30,
250
+ min_allowed=0,
251
+ float_allowed=False,
252
+ )
253
+ cfg.set("data_freshness_mins", int(data_freshness_mins))
254
+
255
+ return account
256
+
257
+ def mfa_passcode_flow(cfg:config.Config):
258
+ passcode = controls.text(
259
+ "MFA passcode:",
260
+ validator=PASSCODE_REGEX.match,
261
+ invalid_message="Invalid passcode. MFA passcode should be 6 digits.",
262
+ )
263
+ cfg.set("passcode", passcode)
264
+
265
+ provider = get_resource_provider("snowflake", cfg)
266
+
267
+ is_mfa_cached = False
268
+ try:
269
+ # Run a dummy query to check if the passcode is correct
270
+ is_mfa_cached = provider.is_account_flag_set("ALLOW_CLIENT_MFA_CACHING")
271
+ except Exception as e:
272
+ if "Incorrect passcode was specified" in f"{e}":
273
+ rich.print("")
274
+ rich.print("[yellow]Provided MFA passcode was incorrect")
275
+ rich.print("")
276
+ redo = controls.confirm("Enter fresh passcode from the Duo app?", default=True)
277
+ if redo:
278
+ rich.print("")
279
+ return mfa_passcode_flow(cfg)
280
+ else:
281
+ return ""
282
+ else:
283
+ raise e
284
+
285
+ if not is_mfa_cached:
286
+ rich.print("\nNote: [yellow2]MFA caching is not enabled in your Snowflake account.")
287
+ rich.print("You'll be prompted for approval in the Duo app for each request to Snowflake.")
288
+ rich.print("To learn more: https://docs.snowflake.com/en/user-guide/security-mfa#using-mfa-token-caching-to-minimize-the-number-of-prompts-during-authentication-optional")
289
+ # If MFA caching is not enabled, clear the passcode so that the user is prompted in the Duo app
290
+ passcode = ""
291
+
292
+ return passcode
293
+
294
+ def check_original_config_flow(cfg:config.Config, platform:str):
295
+ all_profiles = {}
296
+ for config_file in all_configs_including_legacy():
297
+ file_path = config_file.path
298
+ plt_config = filter_profiles_by_platform(config_file, platform)
299
+ for profile, props in plt_config.items():
300
+ profile_id = (profile, file_path)
301
+ all_profiles[profile_id] = props
302
+ if platform == "snowflake":
303
+ sf_config = get_from_snowflake_connections_toml()
304
+ if sf_config:
305
+ file_path = os.path.expanduser("~/.snowflake/connections.toml")
306
+ for profile, props in sf_config.items():
307
+ profile_id = (profile, file_path)
308
+ all_profiles[profile_id] = props
309
+ if len(all_profiles) == 0:
310
+ return
311
+ max_profile_name_len = max(len(profile) for profile, _ in all_profiles.keys())
312
+ profile_options: List[Choice] = []
313
+ for profile, props in all_profiles.items():
314
+ formatted_name = f"{profile[0]:<{max_profile_name_len}} {unexpand_user_path(profile[1])}"
315
+ profile_options.append(Choice(value=profile, name=formatted_name))
316
+ selected_profile = controls.select("Start from an existing profile", list(profile_options), None, mandatory=False)
317
+ if not selected_profile:
318
+ return
319
+ cfg.profile = selected_profile[0]
320
+ if cfg.profile == PARTIAL_PROFILE_NAME:
321
+ cfg.profile = TOP_LEVEL_PROFILE_NAME
322
+ cfg.update(all_profiles[selected_profile])
323
+ return True
324
+
325
+ def check_snowflake_connections_flow(cfg:config.Config):
326
+ sf_config = get_from_snowflake_connections_toml()
327
+ if not sf_config or len(sf_config) == 0:
328
+ return
329
+ profiles = list(sf_config.keys())
330
+ if len(profiles) == 0:
331
+ return
332
+ profile = controls.fuzzy("Import a profile from ~/.snowflake/connections.toml", profiles, mandatory=False)
333
+ if not profile:
334
+ return
335
+ cfg.profile = profile
336
+ cfg.update(sf_config[profile])
337
+ return True
338
+
339
+ def role_flow(provider:ResourcesBase, cfg:config.Config):
340
+ roles = cast(snowflake.Resources, provider).list_roles()
341
+ result = controls.fuzzy_with_refetch(
342
+ "Select a role:",
343
+ "roles",
344
+ lambda: [r["name"] for r in roles],
345
+ default=cfg.get("role", None),
346
+ )
347
+ if isinstance(result, Exception):
348
+ return
349
+ else:
350
+ role = result
351
+ cfg.set("role", role or FIELD_PLACEHOLDER)
352
+ provider.reset()
353
+
354
+ def warehouse_flow(provider:ResourcesBase, cfg:config.Config):
355
+ result = controls.fuzzy_with_refetch(
356
+ "Select a warehouse:",
357
+ "warehouses",
358
+ lambda: [w["name"] for w in cast(snowflake.Resources, provider).list_warehouses()],
359
+ default=cfg.get("warehouse", None),
360
+ )
361
+ if not result or isinstance(result, Exception):
362
+ return
363
+ else:
364
+ warehouse = result
365
+ cfg.set("warehouse", warehouse or FIELD_PLACEHOLDER)
366
+
367
+ def rai_app_flow(provider:ResourcesBase, cfg:config.Config):
368
+ auto_select = None
369
+ if provider.config.get("platform") == "snowflake":
370
+ auto_select=snowflake.RAI_APP_NAME
371
+
372
+ result = controls.fuzzy_with_refetch(
373
+ "Select RelationalAI app name:",
374
+ "apps",
375
+ lambda: [w["name"] for w in cast(snowflake.Resources, provider).list_apps()],
376
+ default=cfg.get("rai_app_name", None),
377
+ auto_select=auto_select
378
+ )
379
+ if not result or isinstance(result, Exception):
380
+ return
381
+ else:
382
+ app = result
383
+ cfg.set("rai_app_name", app or FIELD_PLACEHOLDER)
384
+ provider.reset()
385
+
386
+ def spcs_flow(provider: ResourcesBase, cfg: config.Config):
387
+ role_flow(provider, cfg)
388
+ warehouse_flow(provider, cfg)
389
+ rai_app_flow(provider, cfg)
390
+
391
+ def create_engine(cfg:config.Config, **kwargs):
392
+ provider = get_resource_provider(None, cfg)
393
+ prompt = kwargs.get("prompt", "Select an engine:")
394
+ result = controls.fuzzy_with_refetch(
395
+ prompt,
396
+ "engines",
397
+ lambda: ["[CREATE A NEW ENGINE]"] + [engine.get("name") for engine in provider.list_engines()],
398
+ default=cfg.get("engine", None),
399
+ )
400
+ if not result or isinstance(result, Exception):
401
+ raise Exception("Error fetching engines")
402
+ else:
403
+ engine = result
404
+ if result == "[CREATE A NEW ENGINE]":
405
+ engine = create_engine_flow(cfg)
406
+ rich.print("")
407
+ return engine
408
+
409
+ def engine_flow(provider:ResourcesBase, cfg:config.Config, **kwargs):
410
+ if provider.config.get("platform") == "snowflake":
411
+ app_name = cfg.get("rai_app_name", None)
412
+ if not app_name:
413
+ rich.print("[yellow]App name is required for engine selection. Skipping step.\n")
414
+ raise Exception("App name is required for engine selection")
415
+ warehouse = cfg.get("warehouse", None)
416
+ if not warehouse:
417
+ rich.print("[yellow]Warehouse is required for engine selection. Skipping step.\n")
418
+ raise Exception("Warehouse is required for engine selection")
419
+ prompt = kwargs.get("prompt", "Select an engine:")
420
+ engine = create_engine(cfg, prompt=prompt)
421
+ cfg.set("engine", engine or FIELD_PLACEHOLDER)
422
+
423
+ def gitignore_flow():
424
+ current_dir = Path.cwd()
425
+ prev_dir = None
426
+ while current_dir != prev_dir:
427
+ gitignore_path = current_dir / '.gitignore'
428
+ if gitignore_path.exists():
429
+ # if there is, check to see if raiconfig.toml is in it
430
+ with open(gitignore_path, 'r') as gitignore_file:
431
+ if CONFIG_FILE in gitignore_file.read():
432
+ return
433
+ else:
434
+ # if it's not, ask to add it
435
+ add_to_gitignore = controls.confirm(f"Add {CONFIG_FILE} to .gitignore?", default=True)
436
+ if add_to_gitignore:
437
+ with open(gitignore_path, 'a') as gitignore_file:
438
+ gitignore_file.write(f"\n{CONFIG_FILE}")
439
+ return
440
+ prev_dir = current_dir
441
+ current_dir = current_dir.parent
442
+
443
+ def is_valid_bare_toml_key(key):
444
+ pattern = re.compile(r'^[A-Za-z_][A-Za-z0-9_-]*$')
445
+ return bool(pattern.match(key))
446
+
447
+ def name_profile_flow(cfg: config.Config):
448
+ if cfg.profile != TOP_LEVEL_PROFILE_NAME:
449
+ return
450
+ profile = controls.text("New name for this profile:", default=DEFAULT_PROFILE_NAME)
451
+ if not is_valid_bare_toml_key(profile):
452
+ rich.print(
453
+ "[yellow]Invalid profile name: should contain only alphanumeric characters, dashes, and hyphens"
454
+ )
455
+ return name_profile_flow(cfg)
456
+ config_store = ConfigStore()
457
+ if profile in config_store.get_profiles():
458
+ overwrite = controls.confirm(f"[yellow]Overwrite existing {profile} profile?")
459
+ if overwrite:
460
+ return profile
461
+ else:
462
+ return name_profile_flow(cfg)
463
+ return profile
464
+
465
+ def save_flow(cfg:config.Config):
466
+ config_store = ConfigStore()
467
+ if cfg.profile != PARTIAL_PROFILE_NAME and cfg.profile in config_store.get_profiles():
468
+ if not controls.confirm(f"Overwrite existing {cfg.profile} profile"):
469
+ rich.print()
470
+ profile_name = controls.text("Profile name:")
471
+ if profile_name:
472
+ cfg.profile = profile_name
473
+ else:
474
+ save_flow(cfg)
475
+ return
476
+ config_store.add_profile(cfg)
477
+ if cfg.profile != PARTIAL_PROFILE_NAME:
478
+ rich.print()
479
+ if config_store.num_profiles() == 1 or controls.confirm("Activate this profile?"):
480
+ config_store.change_active_profile(cfg.profile)
481
+ config_store.save()
482
+
483
+ def init_flow():
484
+ cfg = config.Config(fetch=False)
485
+ account = None
486
+ try:
487
+ cfg.clone_profile()
488
+ rich.print("\n[dim]---------------------------------------------------\n")
489
+ rich.print("[bold]Welcome to [green]RelationalAI!\n")
490
+ rich.print("Note: [yellow2]To skip a non-mandatory prompt press [bold]Control-S[/bold]\n")
491
+
492
+ if ConfigStore().get("platform"):
493
+ issue_top_level_profile_warning()
494
+
495
+ platform = controls.fuzzy("Host platform:", choices=[SNOWFLAKE, AZURE])
496
+ cfg.set("platform", {
497
+ SNOWFLAKE: "snowflake",
498
+ AZURE: "azure"
499
+ }[platform])
500
+
501
+ if platform == SNOWFLAKE:
502
+ account = snowflake_flow(cfg)
503
+ elif platform == AZURE:
504
+ azure_flow(cfg)
505
+ elif platform:
506
+ rich.print("[yellow]Initialization aborted!")
507
+ rich.print(f"[yellow]Unknown platform: {platform}")
508
+ return
509
+
510
+ provider = get_resource_provider(None, cfg)
511
+
512
+ rich.print()
513
+ if platform == SNOWFLAKE:
514
+ spcs_flow(provider, cfg)
515
+ else: # We auto create engines in SPCS flow so no need to do it here
516
+ engine_flow(provider, cfg)
517
+ profile = name_profile_flow(cfg)
518
+ if profile:
519
+ cfg.profile = profile
520
+ save_flow(cfg)
521
+
522
+ gitignore_flow()
523
+ rich.print("")
524
+ rich.print(f"[green]✓ {CONFIG_FILE} saved!")
525
+ rich.print("\n[dim]---------------------------------------------------\n")
526
+ except Exception as e:
527
+ msg = "[yellow bold]Initialization aborted!\n"
528
+ rich.print("")
529
+ if ("Incorrect passcode was specified" in f"{e}"):
530
+ rich.print("[yellow]Incorrect MFA passcode specified. Please provide a fresh passcode from the Duo app.")
531
+ rich.print('Note: Check if "MFA caching" is enabled in your Snowflake account if this error occurs frequently.\n')
532
+ if ("250001: Could not connect to Snowflake backend" in f"{e}"):
533
+ rich.print("[yellow]Failed to establish connection to the provided account. Please verify the account name.")
534
+ if account and "." in account and "privatelink" not in account:
535
+ corrected_account = account.replace(".", "-")
536
+ rich.print(f"\n[yellow]Note: the account ID format that Snowflake expects in this context uses a dash (-) \nrather than a period (.) to separate the org name and account name. \n\nConsider using {corrected_account} instead.")
537
+ else:
538
+ rich.print(msg)
539
+ print(e.with_traceback(None))
540
+ rich.print("")
541
+
542
+ save = controls.confirm("Save partial config?")
543
+ if save:
544
+ cfg.profile = PARTIAL_PROFILE_NAME
545
+ rich.print("")
546
+ cfg.fill_in_with_defaults()
547
+ save_flow(cfg)
548
+ gitignore_flow()
549
+ rich.print(f"[yellow bold]✓ Saved partial {CONFIG_FILE} ({os.path.abspath(CONFIG_FILE)})")
550
+
551
+ divider()
552
+
553
+ #--------------------------------------------------
554
+ # Profile switcher
555
+ #--------------------------------------------------
556
+
557
+ @cli.command(
558
+ name="profile:switch",
559
+ help="Switch to a different profile",
560
+ )
561
+ @click.option("--profile", help="Profile to switch to")
562
+ def profile_switch(profile:str|None=None):
563
+ config_store = ConfigStore()
564
+ current_profile = None
565
+ try:
566
+ config = config_store.get_config()
567
+ if config.profile != TOP_LEVEL_PROFILE_NAME:
568
+ current_profile = config.profile
569
+ except Exception as e:
570
+ rich.print(f"\n[yellow]Error: {e}")
571
+ pass
572
+
573
+ profiles = list(config_store.get_profiles().keys())
574
+ divider()
575
+ if not profile:
576
+ if len(profiles) == 0:
577
+ exit_with_error("[yellow]No profiles found")
578
+ profile = controls.fuzzy("Select a profile:", profiles, default=current_profile)
579
+ divider()
580
+ else:
581
+ if profile not in profiles:
582
+ exit_with_error(f"[yellow]Profile '{profile}' not found")
583
+ config_store.change_active_profile(profile)
584
+ config_store.save()
585
+ rich.print(f"[green]✓ Switched to profile '{profile}'")
586
+ divider()
587
+
588
+ #--------------------------------------------------
589
+ # Print config file
590
+ #--------------------------------------------------
591
+ @cli.command(
592
+ name="config:print",
593
+ help="Print the config file contents with secrets masked",
594
+ )
595
+ def config_print():
596
+ divider()
597
+ cfg = ensure_config()
598
+ rich.print(f"[bold green]{cfg.file_path}\n")
599
+ if cfg.file_path is None:
600
+ rich.print("[yellow]No configuration file found. To create one, run: [green]rai init")
601
+ divider()
602
+ return
603
+ with open(cfg.file_path, "r") as f:
604
+ content = f.read()
605
+ for line in content.split("\n"):
606
+ if "client_secret" in line or "password" in line:
607
+ chars = []
608
+ equals_found = False
609
+ for char in line:
610
+ if char == "=":
611
+ equals_found = True
612
+ chars.append(char)
613
+ continue
614
+ if equals_found and char != "\"" and char != " ":
615
+ chars.append("*")
616
+ else:
617
+ chars.append(char)
618
+ line = "".join(chars)
619
+ print(line)
620
+ divider()
621
+
622
+ #--------------------------------------------------
623
+ # Explain config
624
+ #--------------------------------------------------
625
+
626
+ @cli.command(
627
+ name="config:explain",
628
+ help="Inspect config status",
629
+ )
630
+ @click.option(
631
+ "--profile",
632
+ help="Profile to inspect",
633
+ )
634
+ @click.option(
635
+ "--all-profiles",
636
+ help="Whether to show all profiles in config file",
637
+ is_flag=True,
638
+ )
639
+ def config_explain(profile:str|None=None, all_profiles:bool=False):
640
+ divider()
641
+ cfg = ensure_config(profile)
642
+ config_store = ConfigStore()
643
+
644
+ if config_store.get("platform"):
645
+ issue_top_level_profile_warning()
646
+
647
+ rich.print(f"[bold green]{cfg.file_path}")
648
+ if os.getenv("RAI_PROFILE"):
649
+ rich.print(f"[yellow]Environment variable [bold]RAI_PROFILE = {os.getenv('RAI_PROFILE')}[/bold]")
650
+ rich.print("")
651
+ if cfg.profile != TOP_LEVEL_PROFILE_NAME:
652
+ rich.print(f"[bold]\\[{cfg.profile}]")
653
+
654
+ for key, value in cfg.items_with_dots():
655
+ if key == "active_profile" and cfg.profile != TOP_LEVEL_PROFILE_NAME:
656
+ continue
657
+ rich.print(f"{key} = [cyan bold]{map_toml_value(mask(key, value))}")
658
+
659
+ platform = cfg.get("platform", "snowflake")
660
+ authenticator = cfg.get("authenticator", "snowflake")
661
+ assert isinstance(authenticator, str), f"authenticator should be a string, not {type(authenticator)}"
662
+ defaults = {}
663
+ if platform == "snowflake":
664
+ defaults = {k: v["value"] for k, v in SNOWFLAKE_AUTHS[authenticator].items()}
665
+ else:
666
+ defaults = azure_default_props
667
+
668
+ for key, value in defaults.items():
669
+ if key not in cfg:
670
+ rich.print(f"[yellow bold]{key}[/yellow bold] = ?" + (
671
+ f" (default: {value})" if value and value != FIELD_PLACEHOLDER else ""
672
+ ))
673
+
674
+ if all_profiles:
675
+ for profile, props in config_store.get_profiles().items():
676
+ if profile == cfg.profile:
677
+ continue
678
+ if len(props):
679
+ rich.print()
680
+ rich.print(f"[bold]\\[{profile}][/bold]")
681
+ for key, value in props.items():
682
+ rich.print(f"{key} = [cyan bold]{map_toml_value(mask(key, value))}")
683
+
684
+ divider()
685
+
686
+ def mask(key: str, value: Any):
687
+ if key in ["client_secret", "password"]:
688
+ return "*" * len(str(value))
689
+ return value
690
+
691
+ #--------------------------------------------------
692
+ # Check config
693
+ #--------------------------------------------------
694
+
695
+ @cli.command(
696
+ name="config:check",
697
+ help="Check whether config is valid",
698
+ )
699
+ def config_check(all_profiles:bool=False):
700
+ error = None
701
+ divider()
702
+ if ConfigStore().get("platform"):
703
+ issue_top_level_profile_warning()
704
+
705
+ cfg = ensure_config()
706
+
707
+ with Spinner("Connecting to platform...", "Connection successful!", "Error:"):
708
+ try:
709
+ provider = get_resource_provider(None, cfg)
710
+ # Engine is required by both clients if it is not configured check would fail already
711
+ engine_name = cfg.get("engine")
712
+ assert isinstance(engine_name, str), f"Engine name must be a string, not {type(engine_name)}"
713
+ # This essentially checks if the profile is valid since we are connecting to get the engine
714
+ engine = provider.get_engine(engine_name)
715
+ if not engine or (engine and not provider.is_valid_engine_state(engine.get("state"))):
716
+ provider.auto_create_engine_async(engine_name)
717
+ except Exception as e:
718
+ error = e
719
+ if error:
720
+ raise error
721
+ if error:
722
+ exit_with_divider(1)
723
+ divider()
724
+
725
+ #--------------------------------------------------
726
+ # Version
727
+ #--------------------------------------------------
728
+
729
+ @cli.command(help="Print version info")
730
+ def version():
731
+ from .. import __version__
732
+ try:
733
+ from railib import __version__ as railib_version
734
+ except Exception:
735
+ railib_version = None
736
+
737
+ table = Table(show_header=False, border_style="dim", header_style="bold", box=rich_box.SIMPLE)
738
+ def print_version(name, version, latest=None):
739
+ if latest is not None and Version(version) < Version(latest):
740
+ table.add_row(f"[bold]{name}[red]", f"[red bold]{version} (yours) → {latest} (latest)")
741
+ else:
742
+ table.add_row(f"[bold]{name}", f"[green]{version} (latest)")
743
+
744
+ divider()
745
+ print_version("RelationalAI", __version__, latest_version("relationalai"))
746
+ if railib_version is not None:
747
+ print_version("Rai-sdk", railib_version, latest_version("rai-sdk"))
748
+ print_version("Python", sys.version.split()[0])
749
+
750
+ app_version = None
751
+
752
+ try:
753
+ cfg = get_config()
754
+ if not cfg.file_path:
755
+ table.add_row("[bold]App", "No configuration file found. To create one, run: [green]rai init")
756
+ else:
757
+ platform = cfg.get("platform", None)
758
+ if platform == "snowflake":
759
+ with Spinner("Checking app version"):
760
+ try:
761
+ app_version = get_resource_provider().get_version()
762
+ except Exception as e:
763
+ error_str = str(e).lower()
764
+ prefix = "Unable to acquire app version.\n"
765
+ if "unknown user-defined function" in error_str:
766
+ app_version = Exception(f"{prefix}Application not found. Please make sure you have set a valid Snowflake native application name in your configuration and your app is up and running.")
767
+ elif "404 not found" in error_str:
768
+ app_version = Exception(f"{prefix}Account not found. Please make sure you have set a valid Snowflake account name in your configuration.")
769
+ elif "role" in error_str:
770
+ app_version = Exception(f"{prefix}Please make sure you have a valid role set in your configuration.")
771
+ elif "failed to connect" in error_str:
772
+ app_version = Exception(f"{prefix}Please check your Snowflake connection settings.")
773
+ elif "no active warehouse" in error_str:
774
+ app_version = Exception(f"{prefix}No active warehouse found. Please make sure you have a valid warehouse name set in your configuration.")
775
+ else:
776
+ app_version = e
777
+
778
+ if not isinstance(app_version, Exception):
779
+ print_version("App", app_version)
780
+
781
+ except Exception as e:
782
+ rich.print(f"[yellow]Error checking app version: {e}")
783
+ exit_with_divider(1)
784
+
785
+ rich.print(table)
786
+
787
+ if isinstance(app_version, Exception):
788
+ error_table = Table(show_header=False, border_style="dim", header_style="bold", box=rich_box.SIMPLE)
789
+ error_table.add_row("App", f"[yellow]{app_version}")
790
+ rich.print(error_table)
791
+
792
+ divider()
793
+
794
+ #--------------------------------------------------
795
+ # Debugger
796
+ #--------------------------------------------------
797
+
798
+ @cli.command(help="Open the RAI debugger")
799
+ @click.option("--host", help="Host to use", default="localhost")
800
+ @click.option("--port", type=int, help="Port to use", default=None)
801
+ @click.option("--old", help="Use the legacy debugger", is_flag=True, default=False)
802
+ @click.option("--qb", help="Query builder debugger", is_flag=True, default=None)
803
+ @click.option("--profile", help="Profile to use", default=None)
804
+
805
+ def debugger(host, port, old, qb, profile):
806
+ if old:
807
+ deb.main(host, port)
808
+ elif qb:
809
+ qb_deb.main(host, port)
810
+ else:
811
+ from v0.relationalai.tools import debugger_server
812
+ debugger_server.start_server(host, port, profile)
813
+
814
+
815
+ #--------------------------------------------------
816
+ # Engine list
817
+ #--------------------------------------------------
818
+
819
+ @cli.command(name="engines:list", help="List all engines")
820
+ @click.option("--state", help="Filter by engine state")
821
+ def engines_list(state:str|None=None):
822
+ divider(flush=True)
823
+ ensure_config()
824
+ rich.print("Note: [cyan]Engine names are case sensitive")
825
+ rich.print("")
826
+ with Spinner("Fetching engines"):
827
+ try:
828
+ engines = get_resource_provider().list_engines(state)
829
+ except Exception as e:
830
+ return exit_with_error(f"\n\n[yellow]Error fetching engines: {e}")
831
+
832
+ if len(engines):
833
+ show_engines(engines)
834
+ else:
835
+ exit_with_error("[yellow]No engines found")
836
+ divider()
837
+
838
+ @cli.command(name="engines:get", help="Get engine details")
839
+ @click.option("--name", help="Name of the engine")
840
+ def engines_get(name:str|None=None):
841
+ divider(flush=True)
842
+ ensure_config()
843
+
844
+ rich.print("Note: [cyan]Engine names are case sensitive")
845
+ rich.print("")
846
+
847
+ if not name:
848
+ name = controls.text("Engine name:", validator=ENGINE_NAME_REGEX.match, invalid_message=ENGINE_NAME_ERROR)
849
+ rich.print("")
850
+
851
+ with Spinner("Fetching engine"):
852
+ try:
853
+ engine = get_resource_provider().get_engine(name)
854
+ except Exception as e:
855
+ return exit_with_error(f"\n\n[yellow]Error fetching engine: {e}")
856
+
857
+ if engine:
858
+ table = Table(show_header=True, border_style="dim", header_style="bold", box=rich_box.SIMPLE_HEAD)
859
+ table.add_column("Name")
860
+ table.add_column("Size")
861
+ table.add_column("State")
862
+ table.add_row(engine.get("name"), engine.get("size"), engine.get("state"))
863
+ rich.print(table)
864
+ else:
865
+ exit_with_error(f'[yellow]Engine "{name}" not found')
866
+ divider()
867
+
868
+ #--------------------------------------------------
869
+ # Engine create
870
+ #--------------------------------------------------
871
+
872
+ def create_engine_flow(cfg:config.Config, name=None, size=None, auto_suspend_mins=None):
873
+ provider = get_resource_provider(None, cfg)
874
+ engine = None
875
+ create_exception = None
876
+ is_engine_present = False
877
+ is_engine_suspended = False
878
+ is_interactive = name is None or size is None
879
+ auto_suspend_mins = cfg.get("auto_suspend_mins", None) if auto_suspend_mins is None else auto_suspend_mins
880
+ if is_interactive:
881
+ rich.print("Note: [cyan]Engine names are case sensitive")
882
+ rich.print("")
883
+
884
+ if not name:
885
+ name = controls.prompt(
886
+ "Engine name:",
887
+ name,
888
+ validator=ENGINE_NAME_REGEX.match,
889
+ invalid_message=ENGINE_NAME_ERROR,
890
+ newline=True
891
+ )
892
+
893
+ if auto_suspend_mins is not None:
894
+ error_msg = f"[yellow]Error: auto_suspend_mins must be an integer instead of {type(auto_suspend_mins)}"
895
+ try:
896
+ auto_suspend_mins = int(auto_suspend_mins)
897
+ except ValueError:
898
+ exit_with_error(error_msg)
899
+ assert isinstance(auto_suspend_mins, int), error_msg
900
+
901
+ is_name_valid, msg = validate_engine_name(name)
902
+ if not is_name_valid:
903
+ rich.print("")
904
+ rich.print(f"[yellow]{msg}")
905
+ if is_interactive:
906
+ rich.print("")
907
+ return create_engine_flow(cfg)
908
+ else:
909
+ exit_with_divider(1)
910
+
911
+ with Spinner(f"Validating engine '{name}' name", "Engine name validated", "Error:"):
912
+ try:
913
+ engine = provider.get_engine(name)
914
+ is_engine_present = engine and engine is not None
915
+ is_engine_suspended = engine and engine.get("state", None) == "SUSPENDED"
916
+ except Exception as e:
917
+ if "already exists" in f"{e}":
918
+ is_engine_present = True
919
+ elif "Not Found" in f"{e}":
920
+ is_engine_present = False
921
+ else:
922
+ raise e
923
+ if is_engine_suspended:
924
+ rich.print("")
925
+ with Spinner(f"Resuming engine '{name}'", f"Engine '{name}' resumed", "Error:"):
926
+ provider.resume_engine(name)
927
+ return name
928
+ if is_engine_present:
929
+ rich.print("")
930
+ if is_interactive:
931
+ rich.print(f"Engine '{name}' already exists")
932
+ rich.print("")
933
+ return create_engine_flow(cfg)
934
+ else:
935
+ exit_with_error(f"[yellow]Engine '{name}' already exists")
936
+
937
+ # If no size is provided via the params and no size is set in the config,
938
+ if not size and not cfg.get("engine_size", None):
939
+ rich.print("")
940
+ cloud_provider = provider.get_cloud_provider()
941
+ choices = provider.get_engine_sizes(cloud_provider)
942
+ size = controls.fuzzy(f"Engine size ({cloud_provider.upper()}):", choices=choices)
943
+ # If a size is provided via the params or the config, validate it
944
+ elif size or cfg.get("engine_size", None):
945
+ # If size is None but config has engine_size, use config value
946
+ if size is None:
947
+ size = cfg.get("engine_size", None)
948
+ valid_sizes = provider.get_engine_sizes()
949
+ if not isinstance(size, str) or size not in valid_sizes:
950
+ exit_with_error(f"\nInvalid engine size [yellow]{size}[/yellow] provided. Please check your config.\n\nValid sizes: [green]{valid_sizes}[/green]")
951
+ assert isinstance(size, str), "engine_size must be a string"
952
+
953
+ rich.print("")
954
+
955
+ with Spinner(
956
+ f"Creating '{name}' engine with size {size}... (this may take several minutes)",
957
+ f"Engine '{name}' created!",
958
+ failed_message="Error:"
959
+ ):
960
+ try:
961
+ provider.create_engine(name, size, auto_suspend_mins)
962
+ except Exception as e:
963
+ create_exception = e
964
+ # We do not want to print the success context message if the engine creation fails
965
+ # Since we are raising here and passing a "fail_message", the spinner will print the actual error message
966
+ raise e
967
+ if isinstance(create_exception, Exception):
968
+ exit_with_error(f"[yellow]{create_exception}")
969
+ return name
970
+
971
+ @cli.command(name="engines:create", help="Create a new engine")
972
+ @click.option("--name", help="Name of the engine")
973
+ @click.option("--size", help="Size of the engine")
974
+ @click.option("--auto_suspend_mins", help="Suspend the engine after this many minutes of inactivity", default=None)
975
+ def engines_create(name, size, auto_suspend_mins):
976
+ divider(flush=True)
977
+ cfg = ensure_config()
978
+ create_engine_flow(cfg, name, size, auto_suspend_mins)
979
+ divider()
980
+
981
+ #--------------------------------------------------
982
+ # Engine delete
983
+ #--------------------------------------------------
984
+
985
+ @cli.command(name="engines:delete", help="Delete an engine")
986
+ @click.option("--name", help="Name of the engine")
987
+ @click.option("--force", help="Force delete the engine", is_flag=True)
988
+ def engines_delete(name, force=False):
989
+ divider(flush=True)
990
+ ensure_config()
991
+ provider = get_resource_provider()
992
+ if not name:
993
+ name = controls.fuzzy_with_refetch(
994
+ "Select an engine:",
995
+ "engines",
996
+ lambda: [engine["name"] for engine in provider.list_engines()],
997
+ )
998
+ if not name or isinstance(name, Exception):
999
+ return
1000
+ else:
1001
+ try:
1002
+ engine = provider.get_engine(name)
1003
+ if not engine:
1004
+ exit_with_error(f"[yellow]Engine '{name}' not found")
1005
+ except Exception as e:
1006
+ exit_with_error(f"[yellow]Error fetching engine: {e}")
1007
+
1008
+ del_err = None
1009
+ with Spinner(f"Deleting '{name}' engine", f"Engine '{name}' deleted!", "Error:"):
1010
+ try:
1011
+ provider.delete_engine(name, force)
1012
+ except Exception as e:
1013
+ if "SETUP_CDC" in f"{e}":
1014
+ del_err = Exception("[yellow]Imports are setup to utilize this engine.\nUse '[cyan]rai engines:delete --force[/cyan]' to force delete engines.")
1015
+ else:
1016
+ del_err = e
1017
+ raise del_err
1018
+ if isinstance(del_err, Exception):
1019
+ exit_with_divider(1)
1020
+ divider()
1021
+
1022
+ #--------------------------------------------------
1023
+ # Engine resume
1024
+ #--------------------------------------------------
1025
+
1026
+ @cli.command(name="engines:resume", help="Resume an engine")
1027
+ @click.option("--name", help="Name of the engine")
1028
+ def engines_resume(name):
1029
+ divider(flush=True)
1030
+ ensure_config()
1031
+ provider = get_resource_provider()
1032
+ if not name:
1033
+ name = controls.fuzzy_with_refetch(
1034
+ "Select a suspended engine to resume:",
1035
+ "engines",
1036
+ lambda: [engine["name"] for engine in provider.list_engines('SUSPENDED')],
1037
+ )
1038
+ if not name or isinstance(name, Exception):
1039
+ return
1040
+ else:
1041
+ try:
1042
+ engine = provider.get_engine(name)
1043
+ if not engine:
1044
+ exit_with_error(f"[yellow]Engine '{name}' not found")
1045
+ if engine and engine.get("state") != "SUSPENDED":
1046
+ exit_with_error(f"[yellow]Engine '{name}' not in 'SUSPENDED' state")
1047
+ except Exception as e:
1048
+ exit_with_error(f"[yellow]Error fetching engine: {e}")
1049
+
1050
+ with Spinner(f"Resuming '{name}' engine", f"Engine '{name}' resumed", "Error:"):
1051
+ try:
1052
+ provider.resume_engine(name)
1053
+ except Exception as e:
1054
+ rich.print(f"[yellow]Error resuming engine: {e}")
1055
+ exit_with_divider(1)
1056
+ divider()
1057
+
1058
+ #--------------------------------------------------
1059
+ # Engine suspend
1060
+ #--------------------------------------------------
1061
+
1062
+ @cli.command(name="engines:suspend", help="Suspend an engine")
1063
+ @click.option("--name", help="Name of the engine")
1064
+ def engines_suspend(name):
1065
+ divider(flush=True)
1066
+ ensure_config()
1067
+ provider = get_resource_provider()
1068
+ if not name:
1069
+ name = controls.fuzzy_with_refetch(
1070
+ "Select an active engine to suspend:",
1071
+ "engines",
1072
+ lambda: [engine["name"] for engine in provider.list_engines('READY')],
1073
+ )
1074
+ if not name or isinstance(name, Exception):
1075
+ return
1076
+ else:
1077
+ try:
1078
+ engine = provider.get_engine(name)
1079
+ if not engine:
1080
+ exit_with_error(f"[yellow]Engine '{name}' not found")
1081
+ if engine and engine.get("state") != "READY":
1082
+ exit_with_error(f"[yellow]Engine '{name}' must be in 'READY' state to be suspended")
1083
+ except Exception as e:
1084
+ exit_with_error(f"[yellow]Error fetching engine: {e}")
1085
+
1086
+ with Spinner(f"Suspending '{name}' engine", f"Engine '{name}' suspended", "Error:"):
1087
+ try:
1088
+ provider.suspend_engine(name)
1089
+ except Exception as e:
1090
+ rich.print(f"[yellow]Error suspending engine: {e}")
1091
+ exit_with_divider(1)
1092
+ divider()
1093
+
1094
+ #--------------------------------------------------
1095
+ # Engine alter engine pool
1096
+ #--------------------------------------------------
1097
+
1098
+ @cli.command(name="engines:alter_pool", help="Alter the engine pool size")
1099
+ @click.option("--size", help="Engine size")
1100
+ @click.option("--min", help="Minimum number of engines")
1101
+ @click.option("--max", help="Maximum number of engines")
1102
+ def engines_alter_pool(size:str|None=None, min:int|None=None, max:int|None=None):
1103
+ divider(flush=True)
1104
+ ensure_config()
1105
+ provider = get_resource_provider()
1106
+
1107
+ if provider.platform != "snowflake":
1108
+ exit_with_error("Engine pool alteration is only supported for Snowflake")
1109
+
1110
+ # Ask for engine size if not provided
1111
+ if not size:
1112
+ valid_sizes = provider.get_engine_sizes()
1113
+ size = controls.fuzzy(
1114
+ "Select engine size:",
1115
+ choices=valid_sizes,
1116
+ mandatory=True
1117
+ )
1118
+
1119
+ # Validate engine size
1120
+ valid_sizes = provider.get_engine_sizes()
1121
+ if size not in valid_sizes:
1122
+ exit_with_error(f"Invalid engine size '{size}'. Valid sizes: {valid_sizes}")
1123
+
1124
+ # Ask for minimum number of engines
1125
+ if min is None:
1126
+ min_str = controls.text(
1127
+ "Enter minimum number of engines:",
1128
+ validator=lambda x: x.isdigit() and int(x) > 0,
1129
+ invalid_message="Please enter a valid non-negative integer"
1130
+ )
1131
+ min = int(min_str)
1132
+ else:
1133
+ # Convert string to int if it came from command line
1134
+ min = int(min)
1135
+
1136
+ # Ask for maximum number of engines
1137
+ if max is None:
1138
+ max_str = controls.text(
1139
+ "Enter maximum number of engines:",
1140
+ validator=lambda x: x.isdigit() and int(x) >= min,
1141
+ invalid_message=f"Please enter a valid integer greater than or equal to {min}"
1142
+ )
1143
+ max = int(max_str)
1144
+ else:
1145
+ # Convert string to int if it came from command line
1146
+ max = int(max)
1147
+
1148
+ # Validate that range is valid
1149
+ if max < min:
1150
+ exit_with_error(f"Maximum number of engines ({max}) must be greater than or equal to minimum ({min})")
1151
+
1152
+ rich.print()
1153
+
1154
+ # Call the API method
1155
+ with Spinner("Altering engine pool", "Engine pool altered", "Error:"):
1156
+ # Type cast to ensure type checker recognizes the method
1157
+ cast(ResourcesBase, provider).alter_engine_pool(size, min, max)
1158
+ divider()
1159
+
1160
+ #--------------------------------------------------
1161
+ # Import Source flows
1162
+ #--------------------------------------------------
1163
+
1164
+ def import_source_flow(provider: ResourcesBase) -> Sequence[ImportSource]:
1165
+ provider_type = type(provider)
1166
+
1167
+ if isinstance(provider, snowflake.Resources):
1168
+ return snowflake_import_source_flow(provider)
1169
+ else:
1170
+ # Lazy import for azure to avoid optional dependency issues
1171
+ try:
1172
+ from v0.relationalai.clients import azure
1173
+ if isinstance(provider, azure.Resources):
1174
+ return azure_import_source_flow(provider)
1175
+ except ImportError:
1176
+ pass
1177
+ raise Exception(f"No import source flow available for {provider_type.__module__}.{provider_type.__name__}")
1178
+
1179
+ def snowflake_import_source_flow(provider: snowflake.Resources) -> Sequence[ImportSource]:
1180
+ with Spinner("Fetching databases", "Databases fetched"):
1181
+ try:
1182
+ dbs = provider.list_databases()
1183
+ except Exception as e:
1184
+ rich.print(f"\n\n[yellow]Error fetching databases: {e}", file=sys.stderr)
1185
+ dbs = []
1186
+ if len(dbs) == 0:
1187
+ exit_with_error("[yellow]No databases found")
1188
+ rich.print()
1189
+ db = controls.fuzzy("Select a database:", [db["name"] for db in dbs])
1190
+ rich.print()
1191
+
1192
+ with Spinner("Fetching schemas", "Schemas fetched"):
1193
+ try:
1194
+ schemas = provider.list_sf_schemas(db)
1195
+ except Exception as e:
1196
+ rich.print(f"\n\n[yellow]Error fetching schemas: {e}")
1197
+ schemas = []
1198
+ if len(schemas) == 0:
1199
+ exit_with_error("[yellow]No schemas found")
1200
+ rich.print()
1201
+ schema = controls.fuzzy("Select a schema:", [s["name"] for s in schemas])
1202
+ rich.print()
1203
+
1204
+ with Spinner("Fetching tables", "Tables fetched"):
1205
+ try:
1206
+ tables = provider.list_tables(db, schema)
1207
+ except Exception as e:
1208
+ rich.print(f"\n\n[yellow]Error fetching tables: {e}")
1209
+ tables = []
1210
+ if len(tables) == 0:
1211
+ exit_with_error("[yellow]No tables found")
1212
+ rich.print()
1213
+ if tables:
1214
+ tables = controls.fuzzy_multiselect("Select tables (tab for multiple):", [t["name"] for t in tables])
1215
+ else:
1216
+ rich.print("[yellow]No tables found")
1217
+ tables = ""
1218
+ rich.print()
1219
+ if isinstance(tables, list):
1220
+ return [ImportSourceTable(db, schema, table) for table in tables]
1221
+ else:
1222
+ return [ImportSourceTable(db, schema, tables)]
1223
+
1224
+ def azure_import_source_flow(provider: azure.Resources) -> Sequence[ImportSource]:
1225
+ result = controls.file("Select a file:", allow_freeform=True)
1226
+ return [ImportSourceFile(result)] if result else []
1227
+
1228
+ def import_source_options_flow(provider: ResourcesBase, source: ImportSource, default_options:dict) -> dict:
1229
+ if isinstance(source, ImportSourceFile):
1230
+ type: LoadType = default_options.get("type", None)
1231
+ if type is None or type == "auto":
1232
+ type = Loader.get_type_for(source)
1233
+ if type == "csv":
1234
+ return import_source_csv_options_flow(provider, source, default_options)
1235
+
1236
+ return default_options
1237
+
1238
+ def import_source_csv_options_flow(provider: ResourcesBase, source: ImportSourceFile, default_options:dict) -> dict:
1239
+ user_specified_schema = {k.strip(): rel_schema_to_type(v.lower()) for k, v in default_options.get("schema", {}).items()}
1240
+ user_specified_syntax = default_options.get("syntax", {})
1241
+
1242
+ if source.is_url():
1243
+ # @FIXME: Should maybe prompt user to provide a schema manually for urls?
1244
+ return {**default_options, "schema": user_specified_schema}
1245
+
1246
+ # Syntax inference + confirmation for local files ==========================
1247
+
1248
+ syntax = CSVLoader.guess_syntax(source.raw_path)
1249
+ syntax.update(user_specified_syntax)
1250
+
1251
+ syntax_table = Table(show_header=True, border_style="dim", header_style="bold", box=rich_box.SIMPLE_HEAD)
1252
+ for k in syntax.keys():
1253
+ syntax_table.add_column(k)
1254
+ syntax_table.add_row(*[
1255
+ repr(v)[1:-1] if isinstance(v, str) else
1256
+ "[dim]<default>[/dim]" if v is None else
1257
+ str(v)
1258
+ for v in syntax.values()])
1259
+
1260
+ rich.print(syntax_table)
1261
+
1262
+ if not controls.confirm(f"Use this dialect for {source.name}:", True):
1263
+ fail_import_options_flow(
1264
+ source,
1265
+ "You can manually specify the CSV dialectusing syntax arguments. For example, to set the [cyan]delimiter[/cyan] to [green]tab[/green], run:",
1266
+ 'syntax:[cyan]delim[/cyan]="[green]\\t[/green]"'
1267
+ )
1268
+
1269
+ # Schema inference + confirmation for local files ==========================
1270
+
1271
+ schema, csv_chunk = CSVLoader.guess_schema(source.raw_path, syntax)
1272
+ schema.update(user_specified_schema)
1273
+
1274
+ schema_table = Table(show_header=True, border_style="dim", header_style="bold", box=rich_box.SIMPLE_HEAD)
1275
+ schema_table.add_column("Field")
1276
+ schema_table.add_column("Type")
1277
+ schema_table.add_column("Ex.")
1278
+ for field, type in schema.items():
1279
+ schema_table.add_row(field, type.name, f"[dim]{csv_chunk[field][0]}")
1280
+
1281
+ rich.print(schema_table)
1282
+
1283
+ if not controls.confirm(f"Use this schema for {source.name}:", True):
1284
+ field = next(iter(schema.keys()))
1285
+ fail_import_options_flow(
1286
+ source,
1287
+ f"You can manually specify column types using schema arguments. For example, to load the column [cyan]{field}[/cyan] as a [green]string[/green], run:",
1288
+ f"schema:[cyan]{field}[/cyan]=[green]string[/green]"
1289
+ )
1290
+
1291
+ return {**default_options, "schema": schema, "syntax": syntax}
1292
+
1293
+ def fail_import_options_flow(source: ImportSourceFile, message: str, solution_args: str):
1294
+ prev_cmd_args = " ".join(shlex.quote(arg) for arg in sys.argv[1:])
1295
+ saved_args = []
1296
+ if "--source" not in sys.argv:
1297
+ saved_args.append(f"--source {shlex.quote(source.raw_path)}")
1298
+ if "--name" not in sys.argv:
1299
+ saved_args.append(f"--name {shlex.quote(source.name)}")
1300
+
1301
+ saved_args = " " + " ".join(saved_args) if saved_args else ""
1302
+ print()
1303
+ rich.print(message)
1304
+ print()
1305
+ rich.get_console().print(f"[dim] rai {prev_cmd_args}{saved_args}[/dim] {solution_args}", highlight=False)
1306
+ divider()
1307
+ exit(0)
1308
+
1309
+
1310
+ def parse_source(provider: ResourcesBase, raw: str) -> ImportSource:
1311
+ if provider.platform == "azure":
1312
+ return ImportSourceFile(raw)
1313
+ elif provider.platform == "snowflake":
1314
+ parser = IdentityParser(raw)
1315
+ assert parser.is_complete, "Snowflake table imports must be in `database.schema.table` format"
1316
+ return ImportSourceTable(*parser.to_list())
1317
+ else:
1318
+ raise Exception(f"Unsupported platform: {provider.platform}")
1319
+
1320
+ #--------------------------------------------------
1321
+ # Imports
1322
+ #--------------------------------------------------
1323
+
1324
+ @supports_platform("snowflake")
1325
+ @cli.command(name="imports:setup", help="Modify and view imports setup")
1326
+ @click.option("--engine_size", help="Engine size")
1327
+ @click.option("--resume", help="Resume imports", is_flag=True)
1328
+ @click.option("--suspend", help="Suspend imports", is_flag=True)
1329
+ def imports_setup(engine_size:str|None=None, resume:bool=False, suspend:bool=False):
1330
+ divider(flush=True)
1331
+ ensure_config()
1332
+ provider = get_resource_provider()
1333
+ data = None
1334
+
1335
+ if resume or suspend:
1336
+ if resume:
1337
+ with Spinner("Resuming imports", "Imports resumed", "Error:"):
1338
+ provider.change_imports_status(suspend=False)
1339
+ if suspend:
1340
+ with Spinner("Suspending imports", "Imports suspended", "Error:"):
1341
+ provider.change_imports_status(suspend=True)
1342
+ exit_with_divider()
1343
+
1344
+ if engine_size:
1345
+ if engine_size in provider.get_engine_sizes():
1346
+ with Spinner("Setting imports engine size", "Imports engine size set", "Error:"):
1347
+ provider.set_imports_engine_size(engine_size)
1348
+ exit_with_divider()
1349
+ else:
1350
+ rich.print("[yellow]Invalid engine size.\n")
1351
+ engine_size = controls.fuzzy("Select engine size for imports:", provider.get_engine_sizes())
1352
+ assert isinstance(engine_size, str), "selected_name should not be None"
1353
+ provider.set_imports_engine_size(engine_size)
1354
+ exit_with_divider()
1355
+
1356
+ # Verify imports setup
1357
+ with Spinner("Fetching imports setup", "Imports setup fetched", "Error:"):
1358
+ try:
1359
+ data = provider.get_imports_status()
1360
+ except Exception as e:
1361
+ raise e
1362
+
1363
+ # Engine is already set for imports
1364
+ if data:
1365
+ rich.print()
1366
+ if data["status"].lower() == "suspended":
1367
+ rich.print("To resume imports, use '[cyan]rai imports:setup --resume[/cyan]'")
1368
+ else:
1369
+ rich.print("To suspend imports, use '[cyan]rai imports:setup --suspend[/cyan]'")
1370
+ try:
1371
+ rich.print()
1372
+ data = {**data, **json.loads(data["info"])}
1373
+ data["status"] = data["status"].upper()
1374
+ data["created_on"] = datetime.strptime(data["createdOn"], '%Y-%m-%d %H:%M:%S.%f %z')
1375
+ data["last_suspended_on"] = datetime.strptime(data["lastSuspendedOn"], '%Y-%m-%d %H:%M:%S.%f %z') if data["lastSuspendedOn"] else "N/A"
1376
+ data["last_suspended_reason"] = data["lastSuspendedReason"] if data["lastSuspendedReason"] else "N/A"
1377
+ del data["info"]
1378
+ del data["state"]
1379
+ del data["createdOn"]
1380
+ del data["lastSuspendedOn"]
1381
+ del data["lastSuspendedReason"]
1382
+ show_dictionary_table(
1383
+ data,
1384
+ lambda k, v: {k: str(v), "style": "red"} if k == "enabled" and not v else format_row(k, v)
1385
+ )
1386
+ except Exception as e:
1387
+ exit_with_error(f"\n\n[yellow]Error fetching imports setup: {e}")
1388
+ divider()
1389
+
1390
+
1391
+ @supports_platform("snowflake")
1392
+ @cli.command(name="imports:get", help="Get specific import details")
1393
+ @click.option("--id", help="Filter by import id")
1394
+ def imports_get(id:str|None=None):
1395
+ divider(flush=True)
1396
+ ensure_config()
1397
+ provider = get_resource_provider()
1398
+ import_streams = []
1399
+ import_response = []
1400
+ with Spinner("Fetching imports", "Imports fetched", "Error:"):
1401
+ import_response = provider.list_imports()
1402
+ if not import_response:
1403
+ exit_with_error("[yellow]No imports found")
1404
+
1405
+ if not id:
1406
+ show_imports(import_response, showId=True)
1407
+ id = controls.fuzzy("Select an import id:", [i["id"] for i in import_response], default=id, show_index=True)
1408
+
1409
+ details_list = [{"name": item['name'], "model": item['model']} for item in import_response if item['id'] == id]
1410
+ if not details_list:
1411
+ rich.print()
1412
+ exit_with_error(f"[yellow]Import '{id}' not found")
1413
+
1414
+ details = details_list[0]
1415
+
1416
+ rich.print()
1417
+ with Spinner("Fetching import details ", "Import details fetched", "Error:"):
1418
+ import_streams = provider.get_import_stream(details.get("name"), details.get("model"))
1419
+ if import_streams and len(import_streams) > 0:
1420
+ rich.print()
1421
+ show_dictionary_table(import_streams[-1], format_row)
1422
+ divider()
1423
+
1424
+ #--------------------------------------------------
1425
+ # Imports list
1426
+ #--------------------------------------------------
1427
+
1428
+ @cli.command(name="imports:list", help="List objects imported into RAI")
1429
+ @click.option("--id", help="Filter by import id")
1430
+ @click.option("--name", help="Filter by import name")
1431
+ @click.option("--model", help="Filter by model")
1432
+ @click.option("--status", help="Filter by import status")
1433
+ @click.option("--creator", help="Filter by import creator")
1434
+ def imports_list(id:str|None=None, name:str|None=None, model:str|None=None, status:str|None=None, creator:str|None=None):
1435
+ divider(flush=True)
1436
+ ensure_config()
1437
+ provider = get_resource_provider()
1438
+ data = None
1439
+ error = None
1440
+ with Spinner("Fetching imports config", "Imports config fetched", "Error:"):
1441
+ try:
1442
+ data = provider.get_imports_status()
1443
+ except Exception as e:
1444
+ error = e
1445
+ raise error
1446
+ if isinstance(error, Exception):
1447
+ exit_with_divider(1)
1448
+
1449
+ if data:
1450
+ rich.print()
1451
+ if data['status'] is None:
1452
+ ds = "[yellow]Not available"
1453
+ elif data['status'].lower() == "suspended":
1454
+ ds = f"[red]{data['status'].upper()}[/red]"
1455
+ else:
1456
+ ds = data["status"].upper()
1457
+ rich.print(f"Imports status: {ds}")
1458
+
1459
+ imports = None
1460
+
1461
+ rich.print()
1462
+ with Spinner("Fetching imports", "Imports fetched", "Error:"):
1463
+ imports = provider.list_imports(id, name, model, status, creator)
1464
+ if len(imports) == 0:
1465
+ exit_with_error("\n[yellow]No imports found")
1466
+
1467
+ rich.print()
1468
+ show_imports(imports)
1469
+ divider()
1470
+
1471
+ #--------------------------------------------------
1472
+ # Imports waiting
1473
+ #--------------------------------------------------
1474
+
1475
+ def poll_imports(provider: ResourcesBase, source:list[str], model:str, no_wait_notice:bool=False):
1476
+ spinner = Spinner(
1477
+ "Waiting for imports to load "
1478
+ "(Ctrl-C to stop waiting - imports will continue loading in the background)",
1479
+ )
1480
+ with spinner:
1481
+ try:
1482
+ provider.poll_imports(source, model)
1483
+ rich.print("\n\n[green]All imports loaded")
1484
+ except KeyboardInterrupt:
1485
+ rich.print("\n\n[yellow]Imports will continue loading in the background")
1486
+ rich.print(f"[yellow]Use [cyan]rai imports:wait --model {model}[/cyan] to resume waiting")
1487
+ if no_wait_notice:
1488
+ rich.print("[yellow]Use [cyan]--no-wait[/cyan] to skip waiting")
1489
+
1490
+ @cli.command(name="imports:wait", help="Wait for a list of imports to load")
1491
+ @click.option("--source", help="Imports to wait for", multiple=True, type=str)
1492
+ @click.option("--model", help="Model", type=str)
1493
+ def imports_wait(source: List[str], model: str):
1494
+ divider(flush=True)
1495
+ ensure_config()
1496
+ provider = get_resource_provider()
1497
+
1498
+ if not model:
1499
+ with Spinner("Fetching models", "Models fetched"):
1500
+ try:
1501
+ models = [model["name"] for model in provider.list_graphs()]
1502
+ except Exception as e:
1503
+ return exit_with_error(f"\n\n[yellow]Error fetching models: {e}")
1504
+ if not models:
1505
+ return exit_with_error("[yellow]No models found")
1506
+ rich.print()
1507
+ model = controls.fuzzy("Select a model:", models)
1508
+ rich.print()
1509
+
1510
+ if not source:
1511
+ with Spinner("Fetching imports", "Imports fetched", "Error:"):
1512
+ imports = provider.list_imports(model=model)
1513
+ if not imports:
1514
+ exit_with_error("[yellow]No imports found")
1515
+ def is_loaded(import_):
1516
+ status = import_["status"]
1517
+ if status is None:
1518
+ return False
1519
+ return status.upper() == "LOADED"
1520
+ loaded_imports = [i for i in imports if is_loaded(i)]
1521
+ other_imports = [i for i in imports if not is_loaded(i)]
1522
+ if loaded_imports:
1523
+ rich.print()
1524
+ rich.print("[yellow]The following imports are already loaded:")
1525
+ show_imports(loaded_imports)
1526
+ if not other_imports:
1527
+ rich.print()
1528
+ exit_with_error("[yellow]No imports to wait for")
1529
+ if not other_imports:
1530
+ exit_with_error("[yellow]No imports found")
1531
+ rich.print()
1532
+ source = controls.fuzzy_multiselect(
1533
+ "Select imports (tab for multiple):",
1534
+ [i["name"] for i in other_imports]
1535
+ )
1536
+ if not source:
1537
+ exit_with_divider()
1538
+
1539
+ rich.print()
1540
+ poll_imports(provider, source, model, no_wait_notice=False)
1541
+
1542
+ divider()
1543
+
1544
+ #--------------------------------------------------
1545
+ # Imports stream
1546
+ #--------------------------------------------------
1547
+
1548
+ @supports_platform("snowflake")
1549
+ @cli.command(name="imports:stream", help="Stream objects into RAI")
1550
+ @click.option("--source", help="Source", multiple=True)
1551
+ @click.option("--model", help="Model")
1552
+ @click.option("--rate", help="Rate")
1553
+ @click.option("--resume", help="Name of the import to resume")
1554
+ @click.option("--suspend", help="Name of the import to suspend")
1555
+ @click.option("--no-wait", help="Don't wait for imports to load", is_flag=True)
1556
+ @click.option("--force", help="Overwrite any existing streams with the same name", is_flag=True)
1557
+ @click.argument('options', nargs=-1, type=ImportOptionsType())
1558
+ def imports_stream(
1559
+ source: Sequence[str],
1560
+ model: str|None,
1561
+ rate: int|None,
1562
+ resume: str|None,
1563
+ suspend: str|None,
1564
+ no_wait: bool|None,
1565
+ force: bool|None,
1566
+ options: Sequence[ImportOption],
1567
+ ):
1568
+ divider(flush=True)
1569
+ ensure_config()
1570
+ provider = get_resource_provider()
1571
+ default_options = ImportOptionsType.reduce(options)
1572
+
1573
+ # Resume or suspend import stream
1574
+ if resume or suspend:
1575
+ import_name = resume if resume else suspend
1576
+ assert import_name
1577
+ is_suspend = True if suspend else False
1578
+ with Spinner("Acquiring import", "Import stream fetched", "Error:"):
1579
+ stream = provider.list_imports(name=import_name)
1580
+ if not stream:
1581
+ rich.print()
1582
+ rich.print(f"[yellow]Import '{import_name}' not found")
1583
+ exit_with_divider()
1584
+ rich.print()
1585
+ with Spinner(
1586
+ f"{'Resume' if resume else 'Suspend'}ing import stream",
1587
+ f"Import stream {'resumed' if resume else 'suspended'}",
1588
+ "Error:"
1589
+ ):
1590
+ provider.change_stream_status(import_name, model=stream[0]["model"], suspend=is_suspend)
1591
+ exit_with_divider()
1592
+
1593
+ # Model/database selection & validation
1594
+ if not model:
1595
+ with Spinner("Fetching models", "Models fetched"):
1596
+ try:
1597
+ models = ["[CREATE MODEL]"] + [model["name"] for model in provider.list_graphs()]
1598
+ except Exception as e:
1599
+ return exit_with_error(f"\n\n[yellow]Error fetching models: {e}")
1600
+
1601
+ rich.print()
1602
+ model = controls.fuzzy("Select a model:", models)
1603
+ if model == "[CREATE MODEL]":
1604
+ model = controls.text("Model name:")
1605
+ rich.print()
1606
+ with Spinner("Creating model", "Model created"):
1607
+ provider.create_graph(model)
1608
+ rich.print()
1609
+ else:
1610
+ db = provider.get_database(model)
1611
+ if not db:
1612
+ rich.print()
1613
+ with Spinner("Creating model", "Model created"):
1614
+ provider.create_graph(model)
1615
+ try:
1616
+ if not source:
1617
+ sources = import_source_flow(provider)
1618
+ else:
1619
+ sources = [parse_source(provider, source_) for source_ in source]
1620
+ except Exception as e:
1621
+ return exit_with_error(f"[yellow]Error: {e}")
1622
+
1623
+ for import_source in sources:
1624
+ try:
1625
+ opts = import_source_options_flow(provider, import_source, default_options)
1626
+ with Spinner(f"Creating stream for {import_source.name}", f"Stream for {import_source.name} created successfully"):
1627
+ if force:
1628
+ provider.delete_import(import_source.name, model, True)
1629
+ provider.create_import_stream(import_source, model, rate, options=opts)
1630
+ except UnsupportedTypeError as err:
1631
+ exit_with_error(f"\n\n[yellow]The [bold]{provider.platform}[/bold] integration doesn't support streaming from [bold]'{err.type}'[/bold] sources.")
1632
+ except Exception as e:
1633
+ if "relations are not empty" in f"{e}":
1634
+ # Handle LeftOverRelationException directly here
1635
+ from v0.relationalai.errors import LeftOverRelationException
1636
+ exception = LeftOverRelationException(import_source.name, model)
1637
+ exception.pprint()
1638
+ sys.exit(1)
1639
+ elif "use setup_cdc()" in f"{e}":
1640
+ exit_with_error("\n\n[yellow]Imports are not configured.\n[yellow]To start use '[cyan]rai imports:setup[/cyan]' to set up imports.")
1641
+ elif "stream already exists" in f"{e}":
1642
+ exit_with_error(f"\n\n[yellow]Stream [cyan]'{import_source.name.upper()}'[/cyan] already exists.")
1643
+ elif "engine not found" in f"{e}":
1644
+ exit_with_error("\n\n[yellow]Stream engine not found. Please use '[cyan]rai imports:setup[/cyan]' to set up imports.")
1645
+ else:
1646
+ rich.print()
1647
+ exit_with_error(f"\n[yellow]Error creating stream: {e}")
1648
+ wait = not no_wait
1649
+ if wait:
1650
+ poll_imports(provider, [source.name for source in sources], model, no_wait_notice=True)
1651
+ else:
1652
+ rich.print(f"\nRun '[cyan]rai imports:list --model {model}[/cyan]' to check import status.")
1653
+ rich.print(f"\nRun '[cyan]rai imports:wait --model {model}[/cyan]' to poll until the imports are loaded.")
1654
+
1655
+ divider()
1656
+
1657
+ #--------------------------------------------------
1658
+ # Imports snapshot
1659
+ #--------------------------------------------------
1660
+
1661
+ @supports_platform("azure")
1662
+ @cli.command(name="imports:snapshot", help="Load an object once into RAI")
1663
+ @click.option("--source", help="Source")
1664
+ @click.option("--model", help="Model")
1665
+ @click.option("--name", help="Import name")
1666
+ @click.option("--type", help="Import as type", default="auto", type=click.Choice(["auto", *Loader.type_to_loader.keys()]))
1667
+ @click.argument('options', nargs=-1, type=ImportOptionsType())
1668
+ def imports_snapshot(source:str|None, model:str|None, name:str|None, type:str|None, options):
1669
+ divider(flush=True)
1670
+ ensure_config()
1671
+ provider = get_resource_provider()
1672
+ default_options = ImportOptionsType.reduce(options)
1673
+ default_options["type"] = type
1674
+
1675
+ if not model:
1676
+ with Spinner("Fetching models", "Models fetched"):
1677
+ try:
1678
+ models = [model["name"] for model in provider.list_graphs()]
1679
+ except Exception as e:
1680
+ return exit_with_error(f"\n\n[yellow]Error fetching models: {e}")
1681
+ if len(models) == 0:
1682
+ exit_with_error("[yellow]No models found")
1683
+ rich.print()
1684
+ model = controls.fuzzy("Select a model:", models)
1685
+ rich.print()
1686
+
1687
+ sources = [parse_source(provider, source)] if source else import_source_flow(provider)
1688
+ for import_source in sources:
1689
+ try:
1690
+ import_source.name = name if name else controls.text("name:", import_source.name)
1691
+ options = import_source_options_flow(provider, import_source, default_options)
1692
+ with Spinner(f"Creating snapshot for {import_source.name}", f"Snapshot for {import_source.name} created"):
1693
+ provider.create_import_snapshot(import_source, model, options=options)
1694
+ except UnsupportedTypeError as err:
1695
+ exit_with_error(f"\n\n[yellow]The [bold]{provider.platform}[/bold] integration doesn't support loading [bold]'{err.type}'[/bold] files.")
1696
+ except RAIException as e:
1697
+ print("\n\n")
1698
+ e.pprint()
1699
+ exit_with_error("\n[yellow]Error creating snapshot, aborting.")
1700
+
1701
+ except Exception as e:
1702
+ exit_with_error(f"\n\n[yellow]Error creating snapshot: {e}")
1703
+ divider()
1704
+
1705
+ #--------------------------------------------------
1706
+ # Imports delete
1707
+ #--------------------------------------------------
1708
+
1709
+ @cli.command(name="imports:delete", help="Delete an import from RAI")
1710
+ @click.option("--object", help="Object")
1711
+ @click.option("--model", help="Model")
1712
+ @click.option("--force", help="Force delete stream and relations", is_flag=True)
1713
+ def imports_delete(object, model, force):
1714
+ divider(flush=True)
1715
+ ensure_config()
1716
+ provider = cast(snowflake.Resources, get_resource_provider())
1717
+ if not model:
1718
+ with Spinner("Fetching models", "Models fetched"):
1719
+ try:
1720
+ models = [model["name"] for model in provider.list_graphs()]
1721
+ except Exception as e:
1722
+ return exit_with_error(f"\n\n[yellow]Error fetching models: {e}")
1723
+ if len(models) == 0:
1724
+ rich.print()
1725
+ exit_with_error("[yellow]No models found")
1726
+ rich.print()
1727
+ model = controls.fuzzy("Select a model:", models)
1728
+ rich.print()
1729
+
1730
+ with Spinner(f"Fetching imports for {model}", "Imports fetched"):
1731
+ try:
1732
+ imports = provider.list_imports(model=model)
1733
+ except Exception as e:
1734
+ return exit_with_error(f"\n\n[yellow]Error fetching imports: {e}")
1735
+
1736
+ if not imports and not force:
1737
+ rich.print()
1738
+ exit_with_error("[yellow]No imports to delete")
1739
+
1740
+ if object:
1741
+ parser = IdentityParser(object)
1742
+ assert parser.identity, "Invalid object provided for deletion"
1743
+ objects = [parser.identity]
1744
+ else:
1745
+ if len(imports) == 0:
1746
+ exit_with_error("[yellow]No imports found")
1747
+ rich.print()
1748
+ objects = controls.fuzzy_multiselect("Select objects (tab for multiple):", [t["name"] for t in imports])
1749
+ rich.print()
1750
+
1751
+ for object in objects:
1752
+ spinner_message = f"Removing {object}" + (" and relations" if force else "")
1753
+ success_message = f"{object}" + (" and relations" if force else "") + " removed successfully"
1754
+ with Spinner(spinner_message, success_message):
1755
+ try:
1756
+ provider.delete_import(object, model, force)
1757
+ except Exception as e:
1758
+ exit_with_error(f"\n\n[yellow]Error deleting import: {e}")
1759
+ divider()
1760
+
1761
+ #--------------------------------------------------
1762
+ # Exports list
1763
+ #--------------------------------------------------
1764
+
1765
+ @supports_platform("snowflake")
1766
+ @cli.command(name="exports:list", help="List objects exported out of RAI")
1767
+ @click.option("--model", help="Model")
1768
+ def exports_list(model):
1769
+ divider(flush=True)
1770
+ ensure_config()
1771
+ provider = cast(snowflake.Resources, get_resource_provider())
1772
+ coming_soon()
1773
+ if not model:
1774
+ with Spinner("Fetching models", "Models fetched"):
1775
+ try:
1776
+ models = [model["name"] for model in provider.list_graphs()]
1777
+ except Exception as e:
1778
+ return exit_with_error(f"\n\n[yellow]Error fetching models: {e}")
1779
+ if len(models) == 0:
1780
+ return exit_with_error("[yellow]No models found")
1781
+ rich.print()
1782
+ model = controls.fuzzy("Select a model:", models)
1783
+ rich.print()
1784
+
1785
+ with Spinner(f"Fetching exports for {model}", "Exports fetched"):
1786
+ try:
1787
+ exports = provider.list_exports(model, "")
1788
+ except Exception as e:
1789
+ return exit_with_error(f"\n\n[yellow]Error fetching exports: {e}")
1790
+
1791
+ rich.print()
1792
+ if len(exports):
1793
+ table = Table(show_header=True, border_style="dim", header_style="bold", box=rich_box.SIMPLE_HEAD)
1794
+ table.add_column("Object")
1795
+ for imp in exports:
1796
+ table.add_row(imp.get("name"))
1797
+ rich.print(table)
1798
+ else:
1799
+ rich.print("[yellow]No exports found")
1800
+ divider()
1801
+
1802
+ #--------------------------------------------------
1803
+ # Exports delete
1804
+ #--------------------------------------------------
1805
+
1806
+ @supports_platform("snowflake")
1807
+ @cli.command(name="exports:delete", help="Delete an export from RAI")
1808
+ @click.option("--export", help="export")
1809
+ @click.option("--model", help="Model")
1810
+ def exports_delete(export, model):
1811
+ divider(flush=True)
1812
+ ensure_config()
1813
+ provider = cast(snowflake.Resources, get_resource_provider())
1814
+ coming_soon()
1815
+ if not model:
1816
+ with Spinner("Fetching models", "Models fetched"):
1817
+ try:
1818
+ models = [model["name"] for model in provider.list_graphs()]
1819
+ except Exception as e:
1820
+ return exit_with_error(f"\n\n[yellow]Error fetching models: {e}")
1821
+ if len(models) == 0:
1822
+ exit_with_error("[yellow]No models found")
1823
+ rich.print()
1824
+ model = controls.fuzzy("Select a model:", models)
1825
+ rich.print()
1826
+
1827
+ # @FIXME It seems like we should just fuzzy list exports but this was the original behavior
1828
+ source_names = [export] if export else [source.name for source in import_source_flow(provider)]
1829
+ for source_name in source_names:
1830
+ with Spinner(f"Removing {source_name}", f"{source_name} removed"):
1831
+ try:
1832
+ provider.delete_export(model, "", source_name)
1833
+ except Exception as e:
1834
+ rich.print(f"\n\n[yellow]Error deleting export: {e}")
1835
+ divider()
1836
+
1837
+ #--------------------------------------------------
1838
+ # Transactions get
1839
+ #--------------------------------------------------
1840
+
1841
+ @cli.command(name="transactions:get", help="Get transaction details")
1842
+ @click.option("--id", help="Transaction id")
1843
+ def transactions_get(id):
1844
+ divider()
1845
+ ensure_config()
1846
+ provider = get_resource_provider()
1847
+ transaction = None
1848
+ if not id:
1849
+ id = controls.text("Transaction id:", mandatory=True, validator=UUID.match, invalid_message="Invalid transaction id")
1850
+ rich.print("")
1851
+
1852
+ with Spinner("Fetching transaction", "Transaction fetched"):
1853
+ try:
1854
+ transaction = provider.get_transaction(id)
1855
+ except Exception as e:
1856
+ exit_with_error(f"\n\n[yellow]Error fetching transaction: {e}")
1857
+ rich.print()
1858
+ if transaction:
1859
+ show_dictionary_table(transaction, format_row)
1860
+ divider()
1861
+
1862
+ #--------------------------------------------------
1863
+ # Transactions list
1864
+ #--------------------------------------------------
1865
+
1866
+ @cli.command(name="transactions:list", help="List transactions")
1867
+ @click.option("--id", help="Filter by transaction id", type=str)
1868
+ @click.option("--state", help="Filter by transaction state", type=str)
1869
+ @click.option("--engine", help="Filter by transaction engine", type=str)
1870
+ @click.option("--limit", default=20, help="Limit", type=int)
1871
+ @click.option("--all-users", is_flag=True, default=False, help="Show transactions from all users")
1872
+ def transactions_list(id, state, engine, limit, all_users):
1873
+ divider()
1874
+ cfg = ensure_config()
1875
+ provider = get_resource_provider()
1876
+ with Spinner("Fetching transactions", "Transactions fetched"):
1877
+ try:
1878
+ transactions = provider.list_transactions(
1879
+ id=id,
1880
+ state=state,
1881
+ engine=engine,
1882
+ limit=max(limit, 100),
1883
+ all_users=all_users,
1884
+ created_by=cfg.get("user", None),
1885
+ )
1886
+ except Exception as e:
1887
+ rich.print()
1888
+ return exit_with_error(f"\n\n[yellow]Error fetching transactions: {e}\n")
1889
+
1890
+ if len(transactions) == 0:
1891
+ rich.print()
1892
+ exit_with_error("[yellow]No transactions found")
1893
+
1894
+ rich.print()
1895
+ show_transactions(transactions, limit)
1896
+ divider()
1897
+
1898
+ #--------------------------------------------------
1899
+ # Transaction cancel
1900
+ #--------------------------------------------------
1901
+
1902
+ @cli.command(name="transactions:cancel", help="Cancel a transaction")
1903
+ @click.option("--id", help="Transaction ID")
1904
+ @click.option("--all-users", is_flag=True, help="Show transactions from all users")
1905
+ def transactions_cancel(id, all_users):
1906
+ divider()
1907
+ cfg = ensure_config()
1908
+ provider = get_resource_provider()
1909
+ if id is None:
1910
+ with Spinner("Fetching transactions", "Transactions fetched"):
1911
+ try:
1912
+ transactions = provider.list_transactions(
1913
+ limit=20,
1914
+ only_active=True,
1915
+ all_users=all_users,
1916
+ created_by=cfg.get("user", None),
1917
+ )
1918
+ except Exception as e:
1919
+ return exit_with_error(f"\n\n[yellow]Error fetching transactions: {e}")
1920
+
1921
+ if not transactions:
1922
+ exit_with_error("\n[yellow]No active transactions found")
1923
+
1924
+ show_transactions(transactions, 20)
1925
+
1926
+ id = controls.fuzzy("Select a transaction to cancel:", [t["id"] for t in transactions])
1927
+ print()
1928
+
1929
+ with Spinner("Cancelling transaction", "Transaction cancelled", "Error:"):
1930
+ provider.cancel_transaction(id)
1931
+ divider()
1932
+
1933
+ #--------------------------------------------------
1934
+ # Main
1935
+ #--------------------------------------------------
1936
+
1937
+ if __name__ == "__main__":
1938
+ # app = EventApp()
1939
+ # app.run()
1940
+ cli()