relationalai 0.13.5__py3-none-any.whl → 1.0.0a2__py3-none-any.whl

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