relationalai 0.13.0__py3-none-any.whl → 0.13.0.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (836) hide show
  1. relationalai/__init__.py +1 -256
  2. relationalai/config/__init__.py +56 -0
  3. relationalai/config/config.py +289 -0
  4. relationalai/config/config_fields.py +86 -0
  5. relationalai/config/connections/__init__.py +46 -0
  6. relationalai/config/connections/base.py +23 -0
  7. relationalai/config/connections/duckdb.py +29 -0
  8. relationalai/config/connections/snowflake.py +243 -0
  9. relationalai/config/external/__init__.py +17 -0
  10. relationalai/config/external/dbt_converter.py +101 -0
  11. relationalai/config/external/dbt_models.py +93 -0
  12. relationalai/config/external/snowflake_converter.py +41 -0
  13. relationalai/config/external/snowflake_models.py +85 -0
  14. relationalai/config/external/utils.py +19 -0
  15. relationalai/semantics/__init__.py +146 -22
  16. relationalai/semantics/backends/lqp/annotations.py +11 -0
  17. relationalai/semantics/backends/sql/sql_compiler.py +327 -0
  18. relationalai/semantics/frontend/base.py +1707 -0
  19. relationalai/semantics/frontend/core.py +179 -0
  20. relationalai/semantics/frontend/front_compiler.py +1313 -0
  21. relationalai/semantics/frontend/pprint.py +408 -0
  22. relationalai/semantics/metamodel/__init__.py +6 -40
  23. relationalai/semantics/metamodel/builtins.py +205 -771
  24. relationalai/semantics/metamodel/metamodel.py +437 -0
  25. relationalai/semantics/metamodel/metamodel_analyzer.py +519 -0
  26. relationalai/semantics/metamodel/pprint.py +412 -0
  27. relationalai/semantics/metamodel/rewriter.py +266 -0
  28. relationalai/semantics/metamodel/typer.py +1378 -0
  29. relationalai/semantics/std/__init__.py +60 -40
  30. relationalai/semantics/std/aggregates.py +149 -0
  31. relationalai/semantics/std/common.py +44 -0
  32. relationalai/semantics/std/constraints.py +37 -43
  33. relationalai/semantics/std/datetime.py +246 -135
  34. relationalai/semantics/std/decimals.py +45 -52
  35. relationalai/semantics/std/floats.py +13 -5
  36. relationalai/semantics/std/integers.py +26 -11
  37. relationalai/semantics/std/math.py +183 -112
  38. relationalai/semantics/std/numbers.py +86 -0
  39. relationalai/semantics/std/re.py +80 -62
  40. relationalai/semantics/std/strings.py +117 -60
  41. relationalai/shims/executor.py +147 -0
  42. relationalai/shims/helpers.py +126 -0
  43. relationalai/shims/hoister.py +221 -0
  44. relationalai/shims/mm2v0.py +1290 -0
  45. relationalai/tools/cli/__init__.py +6 -0
  46. relationalai/tools/cli/cli.py +90 -0
  47. relationalai/tools/cli/components/__init__.py +5 -0
  48. relationalai/tools/cli/components/progress_reader.py +1524 -0
  49. relationalai/tools/cli/components/utils.py +58 -0
  50. relationalai/tools/cli/config_template.py +45 -0
  51. relationalai/tools/cli/dev.py +19 -0
  52. relationalai/tools/debugger.py +289 -183
  53. relationalai/tools/typer_debugger.py +93 -0
  54. relationalai/util/dataclasses.py +43 -0
  55. relationalai/util/docutils.py +40 -0
  56. relationalai/util/error.py +199 -0
  57. relationalai/util/format.py +48 -106
  58. relationalai/util/naming.py +145 -0
  59. relationalai/util/python.py +35 -0
  60. relationalai/util/runtime.py +156 -0
  61. relationalai/util/schema.py +197 -0
  62. relationalai/util/source.py +185 -0
  63. relationalai/util/structures.py +163 -0
  64. relationalai/util/tracing.py +261 -0
  65. relationalai-0.13.0.dev0.dist-info/METADATA +46 -0
  66. relationalai-0.13.0.dev0.dist-info/RECORD +488 -0
  67. relationalai-0.13.0.dev0.dist-info/WHEEL +5 -0
  68. relationalai-0.13.0.dev0.dist-info/entry_points.txt +3 -0
  69. relationalai-0.13.0.dev0.dist-info/top_level.txt +2 -0
  70. v0/relationalai/__init__.py +216 -0
  71. v0/relationalai/clients/__init__.py +5 -0
  72. v0/relationalai/clients/azure.py +477 -0
  73. v0/relationalai/clients/client.py +912 -0
  74. v0/relationalai/clients/config.py +673 -0
  75. v0/relationalai/clients/direct_access_client.py +118 -0
  76. v0/relationalai/clients/hash_util.py +31 -0
  77. v0/relationalai/clients/local.py +571 -0
  78. v0/relationalai/clients/profile_polling.py +73 -0
  79. v0/relationalai/clients/result_helpers.py +420 -0
  80. v0/relationalai/clients/snowflake.py +3869 -0
  81. v0/relationalai/clients/types.py +113 -0
  82. v0/relationalai/clients/use_index_poller.py +980 -0
  83. v0/relationalai/clients/util.py +356 -0
  84. v0/relationalai/debugging.py +389 -0
  85. v0/relationalai/dsl.py +1749 -0
  86. v0/relationalai/early_access/builder/__init__.py +30 -0
  87. v0/relationalai/early_access/builder/builder/__init__.py +35 -0
  88. v0/relationalai/early_access/builder/snowflake/__init__.py +12 -0
  89. v0/relationalai/early_access/builder/std/__init__.py +25 -0
  90. v0/relationalai/early_access/builder/std/decimals/__init__.py +12 -0
  91. v0/relationalai/early_access/builder/std/integers/__init__.py +12 -0
  92. v0/relationalai/early_access/builder/std/math/__init__.py +12 -0
  93. v0/relationalai/early_access/builder/std/strings/__init__.py +14 -0
  94. v0/relationalai/early_access/devtools/__init__.py +12 -0
  95. v0/relationalai/early_access/devtools/benchmark_lqp/__init__.py +12 -0
  96. v0/relationalai/early_access/devtools/extract_lqp/__init__.py +12 -0
  97. v0/relationalai/early_access/dsl/adapters/orm/adapter_qb.py +427 -0
  98. v0/relationalai/early_access/dsl/adapters/orm/parser.py +636 -0
  99. v0/relationalai/early_access/dsl/adapters/owl/adapter.py +176 -0
  100. v0/relationalai/early_access/dsl/adapters/owl/parser.py +160 -0
  101. v0/relationalai/early_access/dsl/bindings/common.py +402 -0
  102. v0/relationalai/early_access/dsl/bindings/csv.py +170 -0
  103. v0/relationalai/early_access/dsl/bindings/legacy/binding_models.py +143 -0
  104. v0/relationalai/early_access/dsl/bindings/snowflake.py +64 -0
  105. v0/relationalai/early_access/dsl/codegen/binder.py +411 -0
  106. v0/relationalai/early_access/dsl/codegen/common.py +79 -0
  107. v0/relationalai/early_access/dsl/codegen/helpers.py +23 -0
  108. v0/relationalai/early_access/dsl/codegen/relations.py +700 -0
  109. v0/relationalai/early_access/dsl/codegen/weaver.py +417 -0
  110. v0/relationalai/early_access/dsl/core/builders/__init__.py +47 -0
  111. v0/relationalai/early_access/dsl/core/builders/logic.py +19 -0
  112. v0/relationalai/early_access/dsl/core/builders/scalar_constraint.py +11 -0
  113. v0/relationalai/early_access/dsl/core/constraints/predicate/atomic.py +455 -0
  114. v0/relationalai/early_access/dsl/core/constraints/predicate/universal.py +73 -0
  115. v0/relationalai/early_access/dsl/core/constraints/scalar.py +310 -0
  116. v0/relationalai/early_access/dsl/core/context.py +13 -0
  117. v0/relationalai/early_access/dsl/core/cset.py +132 -0
  118. v0/relationalai/early_access/dsl/core/exprs/__init__.py +116 -0
  119. v0/relationalai/early_access/dsl/core/exprs/relational.py +18 -0
  120. v0/relationalai/early_access/dsl/core/exprs/scalar.py +412 -0
  121. v0/relationalai/early_access/dsl/core/instances.py +44 -0
  122. v0/relationalai/early_access/dsl/core/logic/__init__.py +193 -0
  123. v0/relationalai/early_access/dsl/core/logic/aggregation.py +98 -0
  124. v0/relationalai/early_access/dsl/core/logic/exists.py +223 -0
  125. v0/relationalai/early_access/dsl/core/logic/helper.py +163 -0
  126. v0/relationalai/early_access/dsl/core/namespaces.py +32 -0
  127. v0/relationalai/early_access/dsl/core/relations.py +276 -0
  128. v0/relationalai/early_access/dsl/core/rules.py +112 -0
  129. v0/relationalai/early_access/dsl/core/std/__init__.py +45 -0
  130. v0/relationalai/early_access/dsl/core/temporal/recall.py +6 -0
  131. v0/relationalai/early_access/dsl/core/types/__init__.py +270 -0
  132. v0/relationalai/early_access/dsl/core/types/concepts.py +128 -0
  133. v0/relationalai/early_access/dsl/core/types/constrained/__init__.py +267 -0
  134. v0/relationalai/early_access/dsl/core/types/constrained/nominal.py +143 -0
  135. v0/relationalai/early_access/dsl/core/types/constrained/subtype.py +124 -0
  136. v0/relationalai/early_access/dsl/core/types/standard.py +92 -0
  137. v0/relationalai/early_access/dsl/core/types/unconstrained.py +50 -0
  138. v0/relationalai/early_access/dsl/core/types/variables.py +203 -0
  139. v0/relationalai/early_access/dsl/ir/compiler.py +318 -0
  140. v0/relationalai/early_access/dsl/ir/executor.py +260 -0
  141. v0/relationalai/early_access/dsl/ontologies/constraints.py +88 -0
  142. v0/relationalai/early_access/dsl/ontologies/export.py +30 -0
  143. v0/relationalai/early_access/dsl/ontologies/models.py +453 -0
  144. v0/relationalai/early_access/dsl/ontologies/python_printer.py +303 -0
  145. v0/relationalai/early_access/dsl/ontologies/readings.py +60 -0
  146. v0/relationalai/early_access/dsl/ontologies/relationships.py +322 -0
  147. v0/relationalai/early_access/dsl/ontologies/roles.py +87 -0
  148. v0/relationalai/early_access/dsl/ontologies/subtyping.py +55 -0
  149. v0/relationalai/early_access/dsl/orm/constraints.py +438 -0
  150. v0/relationalai/early_access/dsl/orm/measures/dimensions.py +200 -0
  151. v0/relationalai/early_access/dsl/orm/measures/initializer.py +16 -0
  152. v0/relationalai/early_access/dsl/orm/measures/measure_rules.py +275 -0
  153. v0/relationalai/early_access/dsl/orm/measures/measures.py +299 -0
  154. v0/relationalai/early_access/dsl/orm/measures/role_exprs.py +268 -0
  155. v0/relationalai/early_access/dsl/orm/models.py +256 -0
  156. v0/relationalai/early_access/dsl/orm/object_oriented_printer.py +344 -0
  157. v0/relationalai/early_access/dsl/orm/printer.py +469 -0
  158. v0/relationalai/early_access/dsl/orm/reasoners.py +480 -0
  159. v0/relationalai/early_access/dsl/orm/relations.py +19 -0
  160. v0/relationalai/early_access/dsl/orm/relationships.py +251 -0
  161. v0/relationalai/early_access/dsl/orm/types.py +42 -0
  162. v0/relationalai/early_access/dsl/orm/utils.py +79 -0
  163. v0/relationalai/early_access/dsl/orm/verb.py +204 -0
  164. v0/relationalai/early_access/dsl/physical_metadata/tables.py +133 -0
  165. v0/relationalai/early_access/dsl/relations.py +170 -0
  166. v0/relationalai/early_access/dsl/rulesets.py +69 -0
  167. v0/relationalai/early_access/dsl/schemas/__init__.py +450 -0
  168. v0/relationalai/early_access/dsl/schemas/builder.py +48 -0
  169. v0/relationalai/early_access/dsl/schemas/comp_names.py +51 -0
  170. v0/relationalai/early_access/dsl/schemas/components.py +203 -0
  171. v0/relationalai/early_access/dsl/schemas/contexts.py +156 -0
  172. v0/relationalai/early_access/dsl/schemas/exprs.py +89 -0
  173. v0/relationalai/early_access/dsl/schemas/fragments.py +464 -0
  174. v0/relationalai/early_access/dsl/serialization.py +79 -0
  175. v0/relationalai/early_access/dsl/serialize/exporter.py +163 -0
  176. v0/relationalai/early_access/dsl/snow/api.py +104 -0
  177. v0/relationalai/early_access/dsl/snow/common.py +76 -0
  178. v0/relationalai/early_access/dsl/state_mgmt/__init__.py +129 -0
  179. v0/relationalai/early_access/dsl/state_mgmt/state_charts.py +125 -0
  180. v0/relationalai/early_access/dsl/state_mgmt/transitions.py +130 -0
  181. v0/relationalai/early_access/dsl/types/__init__.py +40 -0
  182. v0/relationalai/early_access/dsl/types/concepts.py +12 -0
  183. v0/relationalai/early_access/dsl/types/entities.py +135 -0
  184. v0/relationalai/early_access/dsl/types/values.py +17 -0
  185. v0/relationalai/early_access/dsl/utils.py +102 -0
  186. v0/relationalai/early_access/graphs/__init__.py +13 -0
  187. v0/relationalai/early_access/lqp/__init__.py +12 -0
  188. v0/relationalai/early_access/lqp/compiler/__init__.py +12 -0
  189. v0/relationalai/early_access/lqp/constructors/__init__.py +18 -0
  190. v0/relationalai/early_access/lqp/executor/__init__.py +12 -0
  191. v0/relationalai/early_access/lqp/ir/__init__.py +12 -0
  192. v0/relationalai/early_access/lqp/passes/__init__.py +12 -0
  193. v0/relationalai/early_access/lqp/pragmas/__init__.py +12 -0
  194. v0/relationalai/early_access/lqp/primitives/__init__.py +12 -0
  195. v0/relationalai/early_access/lqp/types/__init__.py +12 -0
  196. v0/relationalai/early_access/lqp/utils/__init__.py +12 -0
  197. v0/relationalai/early_access/lqp/validators/__init__.py +12 -0
  198. v0/relationalai/early_access/metamodel/__init__.py +58 -0
  199. v0/relationalai/early_access/metamodel/builtins/__init__.py +12 -0
  200. v0/relationalai/early_access/metamodel/compiler/__init__.py +12 -0
  201. v0/relationalai/early_access/metamodel/dependency/__init__.py +12 -0
  202. v0/relationalai/early_access/metamodel/factory/__init__.py +17 -0
  203. v0/relationalai/early_access/metamodel/helpers/__init__.py +12 -0
  204. v0/relationalai/early_access/metamodel/ir/__init__.py +14 -0
  205. v0/relationalai/early_access/metamodel/rewrite/__init__.py +7 -0
  206. v0/relationalai/early_access/metamodel/typer/__init__.py +3 -0
  207. v0/relationalai/early_access/metamodel/typer/typer/__init__.py +12 -0
  208. v0/relationalai/early_access/metamodel/types/__init__.py +15 -0
  209. v0/relationalai/early_access/metamodel/util/__init__.py +15 -0
  210. v0/relationalai/early_access/metamodel/visitor/__init__.py +12 -0
  211. v0/relationalai/early_access/rel/__init__.py +12 -0
  212. v0/relationalai/early_access/rel/executor/__init__.py +12 -0
  213. v0/relationalai/early_access/rel/rel_utils/__init__.py +12 -0
  214. v0/relationalai/early_access/rel/rewrite/__init__.py +7 -0
  215. v0/relationalai/early_access/solvers/__init__.py +19 -0
  216. v0/relationalai/early_access/sql/__init__.py +11 -0
  217. v0/relationalai/early_access/sql/executor/__init__.py +3 -0
  218. v0/relationalai/early_access/sql/rewrite/__init__.py +3 -0
  219. v0/relationalai/early_access/tests/logging/__init__.py +12 -0
  220. v0/relationalai/early_access/tests/test_snapshot_base/__init__.py +12 -0
  221. v0/relationalai/early_access/tests/utils/__init__.py +12 -0
  222. v0/relationalai/environments/__init__.py +35 -0
  223. v0/relationalai/environments/base.py +381 -0
  224. v0/relationalai/environments/colab.py +14 -0
  225. v0/relationalai/environments/generic.py +71 -0
  226. v0/relationalai/environments/ipython.py +68 -0
  227. v0/relationalai/environments/jupyter.py +9 -0
  228. v0/relationalai/environments/snowbook.py +169 -0
  229. v0/relationalai/errors.py +2455 -0
  230. v0/relationalai/experimental/SF.py +38 -0
  231. v0/relationalai/experimental/inspect.py +47 -0
  232. v0/relationalai/experimental/pathfinder/__init__.py +158 -0
  233. v0/relationalai/experimental/pathfinder/api.py +160 -0
  234. v0/relationalai/experimental/pathfinder/automaton.py +584 -0
  235. v0/relationalai/experimental/pathfinder/bridge.py +226 -0
  236. v0/relationalai/experimental/pathfinder/compiler.py +416 -0
  237. v0/relationalai/experimental/pathfinder/datalog.py +214 -0
  238. v0/relationalai/experimental/pathfinder/diagnostics.py +56 -0
  239. v0/relationalai/experimental/pathfinder/filter.py +236 -0
  240. v0/relationalai/experimental/pathfinder/glushkov.py +439 -0
  241. v0/relationalai/experimental/pathfinder/options.py +265 -0
  242. v0/relationalai/experimental/pathfinder/rpq.py +344 -0
  243. v0/relationalai/experimental/pathfinder/transition.py +200 -0
  244. v0/relationalai/experimental/pathfinder/utils.py +26 -0
  245. v0/relationalai/experimental/paths/api.py +143 -0
  246. v0/relationalai/experimental/paths/benchmarks/grid_graph.py +37 -0
  247. v0/relationalai/experimental/paths/examples/basic_example.py +40 -0
  248. v0/relationalai/experimental/paths/examples/minimal_engine_warmup.py +3 -0
  249. v0/relationalai/experimental/paths/examples/movie_example.py +77 -0
  250. v0/relationalai/experimental/paths/examples/paths_benchmark.py +115 -0
  251. v0/relationalai/experimental/paths/examples/paths_example.py +116 -0
  252. v0/relationalai/experimental/paths/examples/pattern_to_automaton.py +28 -0
  253. v0/relationalai/experimental/paths/find_paths_via_automaton.py +85 -0
  254. v0/relationalai/experimental/paths/graph.py +185 -0
  255. v0/relationalai/experimental/paths/path_algorithms/find_paths.py +280 -0
  256. v0/relationalai/experimental/paths/path_algorithms/one_sided_ball_repetition.py +26 -0
  257. v0/relationalai/experimental/paths/path_algorithms/one_sided_ball_upto.py +111 -0
  258. v0/relationalai/experimental/paths/path_algorithms/single.py +59 -0
  259. v0/relationalai/experimental/paths/path_algorithms/two_sided_balls_repetition.py +39 -0
  260. v0/relationalai/experimental/paths/path_algorithms/two_sided_balls_upto.py +103 -0
  261. v0/relationalai/experimental/paths/path_algorithms/usp-old.py +130 -0
  262. v0/relationalai/experimental/paths/path_algorithms/usp-tuple.py +183 -0
  263. v0/relationalai/experimental/paths/path_algorithms/usp.py +150 -0
  264. v0/relationalai/experimental/paths/product_graph.py +93 -0
  265. v0/relationalai/experimental/paths/rpq/automaton.py +584 -0
  266. v0/relationalai/experimental/paths/rpq/diagnostics.py +56 -0
  267. v0/relationalai/experimental/paths/rpq/rpq.py +378 -0
  268. v0/relationalai/experimental/paths/tests/tests_limit_sp_max_length.py +90 -0
  269. v0/relationalai/experimental/paths/tests/tests_limit_sp_multiple.py +119 -0
  270. v0/relationalai/experimental/paths/tests/tests_limit_sp_single.py +104 -0
  271. v0/relationalai/experimental/paths/tests/tests_limit_walks_multiple.py +113 -0
  272. v0/relationalai/experimental/paths/tests/tests_limit_walks_single.py +149 -0
  273. v0/relationalai/experimental/paths/tests/tests_one_sided_ball_repetition_multiple.py +70 -0
  274. v0/relationalai/experimental/paths/tests/tests_one_sided_ball_repetition_single.py +64 -0
  275. v0/relationalai/experimental/paths/tests/tests_one_sided_ball_upto_multiple.py +115 -0
  276. v0/relationalai/experimental/paths/tests/tests_one_sided_ball_upto_single.py +75 -0
  277. v0/relationalai/experimental/paths/tests/tests_single_paths.py +152 -0
  278. v0/relationalai/experimental/paths/tests/tests_single_walks.py +208 -0
  279. v0/relationalai/experimental/paths/tests/tests_single_walks_undirected.py +297 -0
  280. v0/relationalai/experimental/paths/tests/tests_two_sided_balls_repetition_multiple.py +107 -0
  281. v0/relationalai/experimental/paths/tests/tests_two_sided_balls_repetition_single.py +76 -0
  282. v0/relationalai/experimental/paths/tests/tests_two_sided_balls_upto_multiple.py +76 -0
  283. v0/relationalai/experimental/paths/tests/tests_two_sided_balls_upto_single.py +110 -0
  284. v0/relationalai/experimental/paths/tests/tests_usp_nsp_multiple.py +229 -0
  285. v0/relationalai/experimental/paths/tests/tests_usp_nsp_single.py +108 -0
  286. v0/relationalai/experimental/paths/tree_agg.py +168 -0
  287. v0/relationalai/experimental/paths/utilities/iterators.py +27 -0
  288. v0/relationalai/experimental/paths/utilities/prefix_sum.py +91 -0
  289. v0/relationalai/experimental/solvers.py +1087 -0
  290. v0/relationalai/loaders/__init__.py +0 -0
  291. v0/relationalai/loaders/csv.py +195 -0
  292. v0/relationalai/loaders/loader.py +177 -0
  293. v0/relationalai/loaders/types.py +23 -0
  294. v0/relationalai/rel_emitter.py +373 -0
  295. v0/relationalai/rel_utils.py +185 -0
  296. v0/relationalai/semantics/__init__.py +29 -0
  297. v0/relationalai/semantics/devtools/benchmark_lqp.py +536 -0
  298. v0/relationalai/semantics/devtools/compilation_manager.py +294 -0
  299. v0/relationalai/semantics/devtools/extract_lqp.py +110 -0
  300. v0/relationalai/semantics/internal/internal.py +3785 -0
  301. v0/relationalai/semantics/internal/snowflake.py +324 -0
  302. v0/relationalai/semantics/lqp/builtins.py +16 -0
  303. v0/relationalai/semantics/lqp/compiler.py +22 -0
  304. v0/relationalai/semantics/lqp/constructors.py +68 -0
  305. v0/relationalai/semantics/lqp/executor.py +469 -0
  306. v0/relationalai/semantics/lqp/intrinsics.py +24 -0
  307. v0/relationalai/semantics/lqp/model2lqp.py +839 -0
  308. v0/relationalai/semantics/lqp/passes.py +680 -0
  309. v0/relationalai/semantics/lqp/primitives.py +252 -0
  310. v0/relationalai/semantics/lqp/result_helpers.py +202 -0
  311. v0/relationalai/semantics/lqp/rewrite/annotate_constraints.py +57 -0
  312. v0/relationalai/semantics/lqp/rewrite/cdc.py +216 -0
  313. v0/relationalai/semantics/lqp/rewrite/extract_common.py +338 -0
  314. v0/relationalai/semantics/lqp/rewrite/extract_keys.py +449 -0
  315. v0/relationalai/semantics/lqp/rewrite/function_annotations.py +114 -0
  316. v0/relationalai/semantics/lqp/rewrite/functional_dependencies.py +314 -0
  317. v0/relationalai/semantics/lqp/rewrite/quantify_vars.py +296 -0
  318. v0/relationalai/semantics/lqp/rewrite/splinter.py +76 -0
  319. v0/relationalai/semantics/lqp/types.py +101 -0
  320. v0/relationalai/semantics/lqp/utils.py +160 -0
  321. v0/relationalai/semantics/lqp/validators.py +57 -0
  322. v0/relationalai/semantics/metamodel/__init__.py +40 -0
  323. v0/relationalai/semantics/metamodel/builtins.py +774 -0
  324. v0/relationalai/semantics/metamodel/compiler.py +133 -0
  325. v0/relationalai/semantics/metamodel/dependency.py +862 -0
  326. v0/relationalai/semantics/metamodel/executor.py +61 -0
  327. v0/relationalai/semantics/metamodel/factory.py +287 -0
  328. v0/relationalai/semantics/metamodel/helpers.py +361 -0
  329. v0/relationalai/semantics/metamodel/rewrite/discharge_constraints.py +39 -0
  330. v0/relationalai/semantics/metamodel/rewrite/dnf_union_splitter.py +210 -0
  331. v0/relationalai/semantics/metamodel/rewrite/extract_nested_logicals.py +78 -0
  332. v0/relationalai/semantics/metamodel/rewrite/flatten.py +549 -0
  333. v0/relationalai/semantics/metamodel/rewrite/format_outputs.py +165 -0
  334. v0/relationalai/semantics/metamodel/typer/checker.py +353 -0
  335. v0/relationalai/semantics/metamodel/typer/typer.py +1395 -0
  336. v0/relationalai/semantics/metamodel/util.py +505 -0
  337. v0/relationalai/semantics/reasoners/__init__.py +10 -0
  338. v0/relationalai/semantics/reasoners/graph/__init__.py +37 -0
  339. v0/relationalai/semantics/reasoners/graph/core.py +9020 -0
  340. v0/relationalai/semantics/reasoners/optimization/__init__.py +68 -0
  341. v0/relationalai/semantics/reasoners/optimization/common.py +88 -0
  342. v0/relationalai/semantics/reasoners/optimization/solvers_dev.py +568 -0
  343. v0/relationalai/semantics/reasoners/optimization/solvers_pb.py +1163 -0
  344. v0/relationalai/semantics/rel/builtins.py +40 -0
  345. v0/relationalai/semantics/rel/compiler.py +989 -0
  346. v0/relationalai/semantics/rel/executor.py +359 -0
  347. v0/relationalai/semantics/rel/rel.py +482 -0
  348. v0/relationalai/semantics/rel/rel_utils.py +276 -0
  349. v0/relationalai/semantics/snowflake/__init__.py +3 -0
  350. v0/relationalai/semantics/sql/compiler.py +2503 -0
  351. v0/relationalai/semantics/sql/executor/duck_db.py +52 -0
  352. v0/relationalai/semantics/sql/executor/result_helpers.py +64 -0
  353. v0/relationalai/semantics/sql/executor/snowflake.py +145 -0
  354. v0/relationalai/semantics/sql/rewrite/denormalize.py +222 -0
  355. v0/relationalai/semantics/sql/rewrite/double_negation.py +49 -0
  356. v0/relationalai/semantics/sql/rewrite/recursive_union.py +127 -0
  357. v0/relationalai/semantics/sql/rewrite/sort_output_query.py +246 -0
  358. v0/relationalai/semantics/sql/sql.py +504 -0
  359. v0/relationalai/semantics/std/__init__.py +54 -0
  360. v0/relationalai/semantics/std/constraints.py +43 -0
  361. v0/relationalai/semantics/std/datetime.py +363 -0
  362. v0/relationalai/semantics/std/decimals.py +62 -0
  363. v0/relationalai/semantics/std/floats.py +7 -0
  364. v0/relationalai/semantics/std/integers.py +22 -0
  365. v0/relationalai/semantics/std/math.py +141 -0
  366. v0/relationalai/semantics/std/pragmas.py +11 -0
  367. v0/relationalai/semantics/std/re.py +83 -0
  368. v0/relationalai/semantics/std/std.py +14 -0
  369. v0/relationalai/semantics/std/strings.py +63 -0
  370. v0/relationalai/semantics/tests/__init__.py +0 -0
  371. v0/relationalai/semantics/tests/test_snapshot_abstract.py +143 -0
  372. v0/relationalai/semantics/tests/test_snapshot_base.py +9 -0
  373. v0/relationalai/semantics/tests/utils.py +46 -0
  374. v0/relationalai/std/__init__.py +70 -0
  375. v0/relationalai/tools/__init__.py +0 -0
  376. v0/relationalai/tools/cli.py +1940 -0
  377. v0/relationalai/tools/cli_controls.py +1826 -0
  378. v0/relationalai/tools/cli_helpers.py +390 -0
  379. v0/relationalai/tools/debugger.py +183 -0
  380. v0/relationalai/tools/debugger_client.py +109 -0
  381. v0/relationalai/tools/debugger_server.py +302 -0
  382. v0/relationalai/tools/dev.py +685 -0
  383. v0/relationalai/tools/qb_debugger.py +425 -0
  384. v0/relationalai/util/clean_up_databases.py +95 -0
  385. v0/relationalai/util/format.py +123 -0
  386. v0/relationalai/util/list_databases.py +9 -0
  387. v0/relationalai/util/otel_configuration.py +25 -0
  388. v0/relationalai/util/otel_handler.py +484 -0
  389. v0/relationalai/util/snowflake_handler.py +88 -0
  390. v0/relationalai/util/span_format_test.py +43 -0
  391. v0/relationalai/util/span_tracker.py +207 -0
  392. v0/relationalai/util/spans_file_handler.py +72 -0
  393. v0/relationalai/util/tracing_handler.py +34 -0
  394. frontend/debugger/dist/.gitignore +0 -2
  395. frontend/debugger/dist/assets/favicon-Dy0ZgA6N.png +0 -0
  396. frontend/debugger/dist/assets/index-Cssla-O7.js +0 -208
  397. frontend/debugger/dist/assets/index-DlHsYx1V.css +0 -9
  398. frontend/debugger/dist/index.html +0 -17
  399. relationalai/clients/__init__.py +0 -18
  400. relationalai/clients/client.py +0 -912
  401. relationalai/clients/config.py +0 -673
  402. relationalai/clients/direct_access_client.py +0 -118
  403. relationalai/clients/hash_util.py +0 -31
  404. relationalai/clients/local.py +0 -571
  405. relationalai/clients/profile_polling.py +0 -73
  406. relationalai/clients/resources/__init__.py +0 -8
  407. relationalai/clients/resources/azure/azure.py +0 -477
  408. relationalai/clients/resources/snowflake/__init__.py +0 -20
  409. relationalai/clients/resources/snowflake/cli_resources.py +0 -87
  410. relationalai/clients/resources/snowflake/direct_access_resources.py +0 -711
  411. relationalai/clients/resources/snowflake/engine_state_handlers.py +0 -309
  412. relationalai/clients/resources/snowflake/error_handlers.py +0 -199
  413. relationalai/clients/resources/snowflake/export_procedure.py.jinja +0 -249
  414. relationalai/clients/resources/snowflake/resources_factory.py +0 -99
  415. relationalai/clients/resources/snowflake/snowflake.py +0 -3083
  416. relationalai/clients/resources/snowflake/use_index_poller.py +0 -1011
  417. relationalai/clients/resources/snowflake/use_index_resources.py +0 -188
  418. relationalai/clients/resources/snowflake/util.py +0 -387
  419. relationalai/clients/result_helpers.py +0 -420
  420. relationalai/clients/types.py +0 -113
  421. relationalai/clients/util.py +0 -356
  422. relationalai/debugging.py +0 -389
  423. relationalai/dsl.py +0 -1749
  424. relationalai/early_access/builder/__init__.py +0 -30
  425. relationalai/early_access/builder/builder/__init__.py +0 -35
  426. relationalai/early_access/builder/snowflake/__init__.py +0 -12
  427. relationalai/early_access/builder/std/__init__.py +0 -25
  428. relationalai/early_access/builder/std/decimals/__init__.py +0 -12
  429. relationalai/early_access/builder/std/integers/__init__.py +0 -12
  430. relationalai/early_access/builder/std/math/__init__.py +0 -12
  431. relationalai/early_access/builder/std/strings/__init__.py +0 -14
  432. relationalai/early_access/devtools/__init__.py +0 -12
  433. relationalai/early_access/devtools/benchmark_lqp/__init__.py +0 -12
  434. relationalai/early_access/devtools/extract_lqp/__init__.py +0 -12
  435. relationalai/early_access/dsl/adapters/orm/adapter_qb.py +0 -427
  436. relationalai/early_access/dsl/adapters/orm/parser.py +0 -636
  437. relationalai/early_access/dsl/adapters/owl/adapter.py +0 -176
  438. relationalai/early_access/dsl/adapters/owl/parser.py +0 -160
  439. relationalai/early_access/dsl/bindings/common.py +0 -402
  440. relationalai/early_access/dsl/bindings/csv.py +0 -170
  441. relationalai/early_access/dsl/bindings/legacy/binding_models.py +0 -143
  442. relationalai/early_access/dsl/bindings/snowflake.py +0 -64
  443. relationalai/early_access/dsl/codegen/binder.py +0 -411
  444. relationalai/early_access/dsl/codegen/common.py +0 -79
  445. relationalai/early_access/dsl/codegen/helpers.py +0 -23
  446. relationalai/early_access/dsl/codegen/relations.py +0 -700
  447. relationalai/early_access/dsl/codegen/weaver.py +0 -417
  448. relationalai/early_access/dsl/core/builders/__init__.py +0 -47
  449. relationalai/early_access/dsl/core/builders/logic.py +0 -19
  450. relationalai/early_access/dsl/core/builders/scalar_constraint.py +0 -11
  451. relationalai/early_access/dsl/core/constraints/predicate/atomic.py +0 -455
  452. relationalai/early_access/dsl/core/constraints/predicate/universal.py +0 -73
  453. relationalai/early_access/dsl/core/constraints/scalar.py +0 -310
  454. relationalai/early_access/dsl/core/context.py +0 -13
  455. relationalai/early_access/dsl/core/cset.py +0 -132
  456. relationalai/early_access/dsl/core/exprs/__init__.py +0 -116
  457. relationalai/early_access/dsl/core/exprs/relational.py +0 -18
  458. relationalai/early_access/dsl/core/exprs/scalar.py +0 -412
  459. relationalai/early_access/dsl/core/instances.py +0 -44
  460. relationalai/early_access/dsl/core/logic/__init__.py +0 -193
  461. relationalai/early_access/dsl/core/logic/aggregation.py +0 -98
  462. relationalai/early_access/dsl/core/logic/exists.py +0 -223
  463. relationalai/early_access/dsl/core/logic/helper.py +0 -163
  464. relationalai/early_access/dsl/core/namespaces.py +0 -32
  465. relationalai/early_access/dsl/core/relations.py +0 -276
  466. relationalai/early_access/dsl/core/rules.py +0 -112
  467. relationalai/early_access/dsl/core/std/__init__.py +0 -45
  468. relationalai/early_access/dsl/core/temporal/recall.py +0 -6
  469. relationalai/early_access/dsl/core/types/__init__.py +0 -270
  470. relationalai/early_access/dsl/core/types/concepts.py +0 -128
  471. relationalai/early_access/dsl/core/types/constrained/__init__.py +0 -267
  472. relationalai/early_access/dsl/core/types/constrained/nominal.py +0 -143
  473. relationalai/early_access/dsl/core/types/constrained/subtype.py +0 -124
  474. relationalai/early_access/dsl/core/types/standard.py +0 -92
  475. relationalai/early_access/dsl/core/types/unconstrained.py +0 -50
  476. relationalai/early_access/dsl/core/types/variables.py +0 -203
  477. relationalai/early_access/dsl/ir/compiler.py +0 -318
  478. relationalai/early_access/dsl/ir/executor.py +0 -260
  479. relationalai/early_access/dsl/ontologies/constraints.py +0 -88
  480. relationalai/early_access/dsl/ontologies/export.py +0 -30
  481. relationalai/early_access/dsl/ontologies/models.py +0 -453
  482. relationalai/early_access/dsl/ontologies/python_printer.py +0 -303
  483. relationalai/early_access/dsl/ontologies/readings.py +0 -60
  484. relationalai/early_access/dsl/ontologies/relationships.py +0 -322
  485. relationalai/early_access/dsl/ontologies/roles.py +0 -87
  486. relationalai/early_access/dsl/ontologies/subtyping.py +0 -55
  487. relationalai/early_access/dsl/orm/constraints.py +0 -438
  488. relationalai/early_access/dsl/orm/measures/dimensions.py +0 -200
  489. relationalai/early_access/dsl/orm/measures/initializer.py +0 -16
  490. relationalai/early_access/dsl/orm/measures/measure_rules.py +0 -275
  491. relationalai/early_access/dsl/orm/measures/measures.py +0 -299
  492. relationalai/early_access/dsl/orm/measures/role_exprs.py +0 -268
  493. relationalai/early_access/dsl/orm/models.py +0 -256
  494. relationalai/early_access/dsl/orm/object_oriented_printer.py +0 -344
  495. relationalai/early_access/dsl/orm/printer.py +0 -469
  496. relationalai/early_access/dsl/orm/reasoners.py +0 -480
  497. relationalai/early_access/dsl/orm/relations.py +0 -19
  498. relationalai/early_access/dsl/orm/relationships.py +0 -251
  499. relationalai/early_access/dsl/orm/types.py +0 -42
  500. relationalai/early_access/dsl/orm/utils.py +0 -79
  501. relationalai/early_access/dsl/orm/verb.py +0 -204
  502. relationalai/early_access/dsl/physical_metadata/tables.py +0 -133
  503. relationalai/early_access/dsl/relations.py +0 -170
  504. relationalai/early_access/dsl/rulesets.py +0 -69
  505. relationalai/early_access/dsl/schemas/__init__.py +0 -450
  506. relationalai/early_access/dsl/schemas/builder.py +0 -48
  507. relationalai/early_access/dsl/schemas/comp_names.py +0 -51
  508. relationalai/early_access/dsl/schemas/components.py +0 -203
  509. relationalai/early_access/dsl/schemas/contexts.py +0 -156
  510. relationalai/early_access/dsl/schemas/exprs.py +0 -89
  511. relationalai/early_access/dsl/schemas/fragments.py +0 -464
  512. relationalai/early_access/dsl/serialization.py +0 -79
  513. relationalai/early_access/dsl/serialize/exporter.py +0 -163
  514. relationalai/early_access/dsl/snow/api.py +0 -105
  515. relationalai/early_access/dsl/snow/common.py +0 -76
  516. relationalai/early_access/dsl/state_mgmt/__init__.py +0 -129
  517. relationalai/early_access/dsl/state_mgmt/state_charts.py +0 -125
  518. relationalai/early_access/dsl/state_mgmt/transitions.py +0 -130
  519. relationalai/early_access/dsl/types/__init__.py +0 -40
  520. relationalai/early_access/dsl/types/concepts.py +0 -12
  521. relationalai/early_access/dsl/types/entities.py +0 -135
  522. relationalai/early_access/dsl/types/values.py +0 -17
  523. relationalai/early_access/dsl/utils.py +0 -102
  524. relationalai/early_access/graphs/__init__.py +0 -13
  525. relationalai/early_access/lqp/__init__.py +0 -12
  526. relationalai/early_access/lqp/compiler/__init__.py +0 -12
  527. relationalai/early_access/lqp/constructors/__init__.py +0 -18
  528. relationalai/early_access/lqp/executor/__init__.py +0 -12
  529. relationalai/early_access/lqp/ir/__init__.py +0 -12
  530. relationalai/early_access/lqp/passes/__init__.py +0 -12
  531. relationalai/early_access/lqp/pragmas/__init__.py +0 -12
  532. relationalai/early_access/lqp/primitives/__init__.py +0 -12
  533. relationalai/early_access/lqp/types/__init__.py +0 -12
  534. relationalai/early_access/lqp/utils/__init__.py +0 -12
  535. relationalai/early_access/lqp/validators/__init__.py +0 -12
  536. relationalai/early_access/metamodel/__init__.py +0 -58
  537. relationalai/early_access/metamodel/builtins/__init__.py +0 -12
  538. relationalai/early_access/metamodel/compiler/__init__.py +0 -12
  539. relationalai/early_access/metamodel/dependency/__init__.py +0 -12
  540. relationalai/early_access/metamodel/factory/__init__.py +0 -17
  541. relationalai/early_access/metamodel/helpers/__init__.py +0 -12
  542. relationalai/early_access/metamodel/ir/__init__.py +0 -14
  543. relationalai/early_access/metamodel/rewrite/__init__.py +0 -7
  544. relationalai/early_access/metamodel/typer/__init__.py +0 -3
  545. relationalai/early_access/metamodel/typer/typer/__init__.py +0 -12
  546. relationalai/early_access/metamodel/types/__init__.py +0 -15
  547. relationalai/early_access/metamodel/util/__init__.py +0 -15
  548. relationalai/early_access/metamodel/visitor/__init__.py +0 -12
  549. relationalai/early_access/rel/__init__.py +0 -12
  550. relationalai/early_access/rel/executor/__init__.py +0 -12
  551. relationalai/early_access/rel/rel_utils/__init__.py +0 -12
  552. relationalai/early_access/rel/rewrite/__init__.py +0 -7
  553. relationalai/early_access/solvers/__init__.py +0 -19
  554. relationalai/early_access/sql/__init__.py +0 -11
  555. relationalai/early_access/sql/executor/__init__.py +0 -3
  556. relationalai/early_access/sql/rewrite/__init__.py +0 -3
  557. relationalai/early_access/tests/logging/__init__.py +0 -12
  558. relationalai/early_access/tests/test_snapshot_base/__init__.py +0 -12
  559. relationalai/early_access/tests/utils/__init__.py +0 -12
  560. relationalai/environments/__init__.py +0 -35
  561. relationalai/environments/base.py +0 -381
  562. relationalai/environments/colab.py +0 -14
  563. relationalai/environments/generic.py +0 -71
  564. relationalai/environments/ipython.py +0 -68
  565. relationalai/environments/jupyter.py +0 -9
  566. relationalai/environments/snowbook.py +0 -169
  567. relationalai/errors.py +0 -2478
  568. relationalai/experimental/SF.py +0 -38
  569. relationalai/experimental/inspect.py +0 -47
  570. relationalai/experimental/pathfinder/__init__.py +0 -158
  571. relationalai/experimental/pathfinder/api.py +0 -160
  572. relationalai/experimental/pathfinder/automaton.py +0 -584
  573. relationalai/experimental/pathfinder/bridge.py +0 -226
  574. relationalai/experimental/pathfinder/compiler.py +0 -416
  575. relationalai/experimental/pathfinder/datalog.py +0 -214
  576. relationalai/experimental/pathfinder/diagnostics.py +0 -56
  577. relationalai/experimental/pathfinder/filter.py +0 -236
  578. relationalai/experimental/pathfinder/glushkov.py +0 -439
  579. relationalai/experimental/pathfinder/options.py +0 -265
  580. relationalai/experimental/pathfinder/pathfinder-v0.7.0.rel +0 -1951
  581. relationalai/experimental/pathfinder/rpq.py +0 -344
  582. relationalai/experimental/pathfinder/transition.py +0 -200
  583. relationalai/experimental/pathfinder/utils.py +0 -26
  584. relationalai/experimental/paths/README.md +0 -107
  585. relationalai/experimental/paths/api.py +0 -143
  586. relationalai/experimental/paths/benchmarks/grid_graph.py +0 -37
  587. relationalai/experimental/paths/code_organization.md +0 -2
  588. relationalai/experimental/paths/examples/Movies.ipynb +0 -16328
  589. relationalai/experimental/paths/examples/basic_example.py +0 -40
  590. relationalai/experimental/paths/examples/minimal_engine_warmup.py +0 -3
  591. relationalai/experimental/paths/examples/movie_example.py +0 -77
  592. relationalai/experimental/paths/examples/movies_data/actedin.csv +0 -193
  593. relationalai/experimental/paths/examples/movies_data/directed.csv +0 -45
  594. relationalai/experimental/paths/examples/movies_data/follows.csv +0 -7
  595. relationalai/experimental/paths/examples/movies_data/movies.csv +0 -39
  596. relationalai/experimental/paths/examples/movies_data/person.csv +0 -134
  597. relationalai/experimental/paths/examples/movies_data/produced.csv +0 -16
  598. relationalai/experimental/paths/examples/movies_data/ratings.csv +0 -10
  599. relationalai/experimental/paths/examples/movies_data/wrote.csv +0 -11
  600. relationalai/experimental/paths/examples/paths_benchmark.py +0 -115
  601. relationalai/experimental/paths/examples/paths_example.py +0 -116
  602. relationalai/experimental/paths/examples/pattern_to_automaton.py +0 -28
  603. relationalai/experimental/paths/find_paths_via_automaton.py +0 -85
  604. relationalai/experimental/paths/graph.py +0 -185
  605. relationalai/experimental/paths/path_algorithms/find_paths.py +0 -280
  606. relationalai/experimental/paths/path_algorithms/one_sided_ball_repetition.py +0 -26
  607. relationalai/experimental/paths/path_algorithms/one_sided_ball_upto.py +0 -111
  608. relationalai/experimental/paths/path_algorithms/single.py +0 -59
  609. relationalai/experimental/paths/path_algorithms/two_sided_balls_repetition.py +0 -39
  610. relationalai/experimental/paths/path_algorithms/two_sided_balls_upto.py +0 -103
  611. relationalai/experimental/paths/path_algorithms/usp-old.py +0 -130
  612. relationalai/experimental/paths/path_algorithms/usp-tuple.py +0 -183
  613. relationalai/experimental/paths/path_algorithms/usp.py +0 -150
  614. relationalai/experimental/paths/product_graph.py +0 -93
  615. relationalai/experimental/paths/rpq/automaton.py +0 -584
  616. relationalai/experimental/paths/rpq/diagnostics.py +0 -56
  617. relationalai/experimental/paths/rpq/rpq.py +0 -378
  618. relationalai/experimental/paths/tests/tests_limit_sp_max_length.py +0 -90
  619. relationalai/experimental/paths/tests/tests_limit_sp_multiple.py +0 -119
  620. relationalai/experimental/paths/tests/tests_limit_sp_single.py +0 -104
  621. relationalai/experimental/paths/tests/tests_limit_walks_multiple.py +0 -113
  622. relationalai/experimental/paths/tests/tests_limit_walks_single.py +0 -149
  623. relationalai/experimental/paths/tests/tests_one_sided_ball_repetition_multiple.py +0 -70
  624. relationalai/experimental/paths/tests/tests_one_sided_ball_repetition_single.py +0 -64
  625. relationalai/experimental/paths/tests/tests_one_sided_ball_upto_multiple.py +0 -115
  626. relationalai/experimental/paths/tests/tests_one_sided_ball_upto_single.py +0 -75
  627. relationalai/experimental/paths/tests/tests_single_paths.py +0 -152
  628. relationalai/experimental/paths/tests/tests_single_walks.py +0 -208
  629. relationalai/experimental/paths/tests/tests_single_walks_undirected.py +0 -297
  630. relationalai/experimental/paths/tests/tests_two_sided_balls_repetition_multiple.py +0 -107
  631. relationalai/experimental/paths/tests/tests_two_sided_balls_repetition_single.py +0 -76
  632. relationalai/experimental/paths/tests/tests_two_sided_balls_upto_multiple.py +0 -76
  633. relationalai/experimental/paths/tests/tests_two_sided_balls_upto_single.py +0 -110
  634. relationalai/experimental/paths/tests/tests_usp_nsp_multiple.py +0 -229
  635. relationalai/experimental/paths/tests/tests_usp_nsp_single.py +0 -108
  636. relationalai/experimental/paths/tree_agg.py +0 -168
  637. relationalai/experimental/paths/utilities/iterators.py +0 -27
  638. relationalai/experimental/paths/utilities/prefix_sum.py +0 -91
  639. relationalai/experimental/solvers.py +0 -1087
  640. relationalai/loaders/csv.py +0 -195
  641. relationalai/loaders/loader.py +0 -177
  642. relationalai/loaders/types.py +0 -23
  643. relationalai/rel_emitter.py +0 -373
  644. relationalai/rel_utils.py +0 -185
  645. relationalai/semantics/designs/query_builder/identify_by.md +0 -106
  646. relationalai/semantics/devtools/benchmark_lqp.py +0 -535
  647. relationalai/semantics/devtools/compilation_manager.py +0 -294
  648. relationalai/semantics/devtools/extract_lqp.py +0 -110
  649. relationalai/semantics/internal/internal.py +0 -3785
  650. relationalai/semantics/internal/snowflake.py +0 -325
  651. relationalai/semantics/lqp/README.md +0 -34
  652. relationalai/semantics/lqp/builtins.py +0 -16
  653. relationalai/semantics/lqp/compiler.py +0 -22
  654. relationalai/semantics/lqp/constructors.py +0 -68
  655. relationalai/semantics/lqp/executor.py +0 -469
  656. relationalai/semantics/lqp/intrinsics.py +0 -24
  657. relationalai/semantics/lqp/model2lqp.py +0 -877
  658. relationalai/semantics/lqp/passes.py +0 -680
  659. relationalai/semantics/lqp/primitives.py +0 -252
  660. relationalai/semantics/lqp/result_helpers.py +0 -202
  661. relationalai/semantics/lqp/rewrite/annotate_constraints.py +0 -57
  662. relationalai/semantics/lqp/rewrite/cdc.py +0 -216
  663. relationalai/semantics/lqp/rewrite/extract_common.py +0 -338
  664. relationalai/semantics/lqp/rewrite/extract_keys.py +0 -506
  665. relationalai/semantics/lqp/rewrite/function_annotations.py +0 -114
  666. relationalai/semantics/lqp/rewrite/functional_dependencies.py +0 -314
  667. relationalai/semantics/lqp/rewrite/quantify_vars.py +0 -296
  668. relationalai/semantics/lqp/rewrite/splinter.py +0 -76
  669. relationalai/semantics/lqp/types.py +0 -101
  670. relationalai/semantics/lqp/utils.py +0 -160
  671. relationalai/semantics/lqp/validators.py +0 -57
  672. relationalai/semantics/metamodel/compiler.py +0 -133
  673. relationalai/semantics/metamodel/dependency.py +0 -862
  674. relationalai/semantics/metamodel/executor.py +0 -61
  675. relationalai/semantics/metamodel/factory.py +0 -287
  676. relationalai/semantics/metamodel/helpers.py +0 -361
  677. relationalai/semantics/metamodel/rewrite/discharge_constraints.py +0 -39
  678. relationalai/semantics/metamodel/rewrite/dnf_union_splitter.py +0 -210
  679. relationalai/semantics/metamodel/rewrite/extract_nested_logicals.py +0 -78
  680. relationalai/semantics/metamodel/rewrite/flatten.py +0 -554
  681. relationalai/semantics/metamodel/rewrite/format_outputs.py +0 -165
  682. relationalai/semantics/metamodel/typer/checker.py +0 -353
  683. relationalai/semantics/metamodel/typer/typer.py +0 -1395
  684. relationalai/semantics/metamodel/util.py +0 -506
  685. relationalai/semantics/reasoners/__init__.py +0 -10
  686. relationalai/semantics/reasoners/graph/README.md +0 -620
  687. relationalai/semantics/reasoners/graph/__init__.py +0 -37
  688. relationalai/semantics/reasoners/graph/core.py +0 -9019
  689. relationalai/semantics/reasoners/graph/design/beyond_demand_transform.md +0 -797
  690. relationalai/semantics/reasoners/graph/tests/README.md +0 -21
  691. relationalai/semantics/reasoners/optimization/__init__.py +0 -68
  692. relationalai/semantics/reasoners/optimization/common.py +0 -88
  693. relationalai/semantics/reasoners/optimization/solvers_dev.py +0 -568
  694. relationalai/semantics/reasoners/optimization/solvers_pb.py +0 -1163
  695. relationalai/semantics/rel/builtins.py +0 -40
  696. relationalai/semantics/rel/compiler.py +0 -989
  697. relationalai/semantics/rel/executor.py +0 -362
  698. relationalai/semantics/rel/rel.py +0 -482
  699. relationalai/semantics/rel/rel_utils.py +0 -276
  700. relationalai/semantics/snowflake/__init__.py +0 -3
  701. relationalai/semantics/sql/compiler.py +0 -2503
  702. relationalai/semantics/sql/executor/duck_db.py +0 -52
  703. relationalai/semantics/sql/executor/result_helpers.py +0 -64
  704. relationalai/semantics/sql/executor/snowflake.py +0 -149
  705. relationalai/semantics/sql/rewrite/denormalize.py +0 -222
  706. relationalai/semantics/sql/rewrite/double_negation.py +0 -49
  707. relationalai/semantics/sql/rewrite/recursive_union.py +0 -127
  708. relationalai/semantics/sql/rewrite/sort_output_query.py +0 -246
  709. relationalai/semantics/sql/sql.py +0 -504
  710. relationalai/semantics/std/pragmas.py +0 -11
  711. relationalai/semantics/std/std.py +0 -14
  712. relationalai/semantics/tests/test_snapshot_abstract.py +0 -143
  713. relationalai/semantics/tests/test_snapshot_base.py +0 -9
  714. relationalai/semantics/tests/utils.py +0 -46
  715. relationalai/std/__init__.py +0 -70
  716. relationalai/tools/cli.py +0 -1936
  717. relationalai/tools/cli_controls.py +0 -1826
  718. relationalai/tools/cli_helpers.py +0 -398
  719. relationalai/tools/debugger_client.py +0 -109
  720. relationalai/tools/debugger_server.py +0 -302
  721. relationalai/tools/dev.py +0 -685
  722. relationalai/tools/notes +0 -7
  723. relationalai/tools/qb_debugger.py +0 -425
  724. relationalai/util/clean_up_databases.py +0 -95
  725. relationalai/util/list_databases.py +0 -9
  726. relationalai/util/otel_configuration.py +0 -26
  727. relationalai/util/otel_handler.py +0 -484
  728. relationalai/util/snowflake_handler.py +0 -88
  729. relationalai/util/span_format_test.py +0 -43
  730. relationalai/util/span_tracker.py +0 -207
  731. relationalai/util/spans_file_handler.py +0 -72
  732. relationalai/util/tracing_handler.py +0 -34
  733. relationalai-0.13.0.dist-info/METADATA +0 -74
  734. relationalai-0.13.0.dist-info/RECORD +0 -458
  735. relationalai-0.13.0.dist-info/WHEEL +0 -4
  736. relationalai-0.13.0.dist-info/entry_points.txt +0 -3
  737. relationalai-0.13.0.dist-info/licenses/LICENSE +0 -202
  738. relationalai_test_util/__init__.py +0 -4
  739. relationalai_test_util/fixtures.py +0 -229
  740. relationalai_test_util/snapshot.py +0 -252
  741. relationalai_test_util/traceback.py +0 -118
  742. /relationalai/{analysis → semantics/frontend}/__init__.py +0 -0
  743. /relationalai/{auth/__init__.py → semantics/metamodel/metamodel_compiler.py} +0 -0
  744. /relationalai/{early_access → shims}/__init__.py +0 -0
  745. {relationalai/early_access/dsl/adapters → v0/relationalai/analysis}/__init__.py +0 -0
  746. {relationalai → v0/relationalai}/analysis/mechanistic.py +0 -0
  747. {relationalai → v0/relationalai}/analysis/whynot.py +0 -0
  748. {relationalai/early_access/dsl/adapters/orm → v0/relationalai/auth}/__init__.py +0 -0
  749. {relationalai → v0/relationalai}/auth/jwt_generator.py +0 -0
  750. {relationalai → v0/relationalai}/auth/oauth_callback_server.py +0 -0
  751. {relationalai → v0/relationalai}/auth/token_handler.py +0 -0
  752. {relationalai → v0/relationalai}/auth/util.py +0 -0
  753. {relationalai/clients/resources/snowflake → v0/relationalai/clients}/cache_store.py +0 -0
  754. {relationalai → v0/relationalai}/compiler.py +0 -0
  755. {relationalai → v0/relationalai}/dependencies.py +0 -0
  756. {relationalai → v0/relationalai}/docutils.py +0 -0
  757. {relationalai/early_access/dsl/adapters/owl → v0/relationalai/early_access}/__init__.py +0 -0
  758. {relationalai → v0/relationalai}/early_access/dsl/__init__.py +0 -0
  759. {relationalai/early_access/dsl/bindings → v0/relationalai/early_access/dsl/adapters}/__init__.py +0 -0
  760. {relationalai/early_access/dsl/bindings/legacy → v0/relationalai/early_access/dsl/adapters/orm}/__init__.py +0 -0
  761. {relationalai → v0/relationalai}/early_access/dsl/adapters/orm/model.py +0 -0
  762. {relationalai/early_access/dsl/codegen → v0/relationalai/early_access/dsl/adapters/owl}/__init__.py +0 -0
  763. {relationalai → v0/relationalai}/early_access/dsl/adapters/owl/model.py +0 -0
  764. {relationalai/early_access/dsl/core/temporal → v0/relationalai/early_access/dsl/bindings}/__init__.py +0 -0
  765. {relationalai/early_access/dsl/ir → v0/relationalai/early_access/dsl/bindings/legacy}/__init__.py +0 -0
  766. {relationalai/early_access/dsl/ontologies → v0/relationalai/early_access/dsl/codegen}/__init__.py +0 -0
  767. {relationalai → v0/relationalai}/early_access/dsl/constants.py +0 -0
  768. {relationalai → v0/relationalai}/early_access/dsl/core/__init__.py +0 -0
  769. {relationalai → v0/relationalai}/early_access/dsl/core/constraints/__init__.py +0 -0
  770. {relationalai → v0/relationalai}/early_access/dsl/core/constraints/predicate/__init__.py +0 -0
  771. {relationalai → v0/relationalai}/early_access/dsl/core/stack.py +0 -0
  772. {relationalai/early_access/dsl/orm → v0/relationalai/early_access/dsl/core/temporal}/__init__.py +0 -0
  773. {relationalai → v0/relationalai}/early_access/dsl/core/utils.py +0 -0
  774. {relationalai/early_access/dsl/orm/measures → v0/relationalai/early_access/dsl/ir}/__init__.py +0 -0
  775. {relationalai/early_access/dsl/physical_metadata → v0/relationalai/early_access/dsl/ontologies}/__init__.py +0 -0
  776. {relationalai → v0/relationalai}/early_access/dsl/ontologies/raw_source.py +0 -0
  777. {relationalai/early_access/dsl/serialize → v0/relationalai/early_access/dsl/orm}/__init__.py +0 -0
  778. {relationalai/early_access/dsl/snow → v0/relationalai/early_access/dsl/orm/measures}/__init__.py +0 -0
  779. {relationalai → v0/relationalai}/early_access/dsl/orm/reasoner_errors.py +0 -0
  780. {relationalai/loaders → v0/relationalai/early_access/dsl/physical_metadata}/__init__.py +0 -0
  781. {relationalai/semantics/tests → v0/relationalai/early_access/dsl/serialize}/__init__.py +0 -0
  782. {relationalai → v0/relationalai}/early_access/dsl/serialize/binding_model.py +0 -0
  783. {relationalai → v0/relationalai}/early_access/dsl/serialize/model.py +0 -0
  784. {relationalai/tools → v0/relationalai/early_access/dsl/snow}/__init__.py +0 -0
  785. {relationalai → v0/relationalai}/early_access/tests/__init__.py +0 -0
  786. {relationalai → v0/relationalai}/environments/ci.py +0 -0
  787. {relationalai → v0/relationalai}/environments/hex.py +0 -0
  788. {relationalai → v0/relationalai}/environments/terminal.py +0 -0
  789. {relationalai → v0/relationalai}/experimental/__init__.py +0 -0
  790. {relationalai → v0/relationalai}/experimental/graphs.py +0 -0
  791. {relationalai → v0/relationalai}/experimental/paths/__init__.py +0 -0
  792. {relationalai → v0/relationalai}/experimental/paths/benchmarks/__init__.py +0 -0
  793. {relationalai → v0/relationalai}/experimental/paths/path_algorithms/__init__.py +0 -0
  794. {relationalai → v0/relationalai}/experimental/paths/rpq/__init__.py +0 -0
  795. {relationalai → v0/relationalai}/experimental/paths/rpq/filter.py +0 -0
  796. {relationalai → v0/relationalai}/experimental/paths/rpq/glushkov.py +0 -0
  797. {relationalai → v0/relationalai}/experimental/paths/rpq/transition.py +0 -0
  798. {relationalai → v0/relationalai}/experimental/paths/utilities/__init__.py +0 -0
  799. {relationalai → v0/relationalai}/experimental/paths/utilities/utilities.py +0 -0
  800. {relationalai → v0/relationalai}/metagen.py +0 -0
  801. {relationalai → v0/relationalai}/metamodel.py +0 -0
  802. {relationalai → v0/relationalai}/rel.py +0 -0
  803. {relationalai → v0/relationalai}/semantics/devtools/__init__.py +0 -0
  804. {relationalai → v0/relationalai}/semantics/internal/__init__.py +0 -0
  805. {relationalai → v0/relationalai}/semantics/internal/annotations.py +0 -0
  806. {relationalai → v0/relationalai}/semantics/lqp/__init__.py +0 -0
  807. {relationalai → v0/relationalai}/semantics/lqp/ir.py +0 -0
  808. {relationalai → v0/relationalai}/semantics/lqp/pragmas.py +0 -0
  809. {relationalai → v0/relationalai}/semantics/lqp/rewrite/__init__.py +0 -0
  810. {relationalai → v0/relationalai}/semantics/metamodel/dataflow.py +0 -0
  811. {relationalai → v0/relationalai}/semantics/metamodel/ir.py +0 -0
  812. {relationalai → v0/relationalai}/semantics/metamodel/rewrite/__init__.py +0 -0
  813. {relationalai → v0/relationalai}/semantics/metamodel/typer/__init__.py +0 -0
  814. {relationalai → v0/relationalai}/semantics/metamodel/types.py +0 -0
  815. {relationalai → v0/relationalai}/semantics/metamodel/visitor.py +0 -0
  816. {relationalai → v0/relationalai}/semantics/reasoners/experimental/__init__.py +0 -0
  817. {relationalai → v0/relationalai}/semantics/rel/__init__.py +0 -0
  818. {relationalai → v0/relationalai}/semantics/sql/__init__.py +0 -0
  819. {relationalai → v0/relationalai}/semantics/sql/executor/__init__.py +0 -0
  820. {relationalai → v0/relationalai}/semantics/sql/rewrite/__init__.py +0 -0
  821. {relationalai → v0/relationalai}/semantics/tests/logging.py +0 -0
  822. {relationalai → v0/relationalai}/std/aggregates.py +0 -0
  823. {relationalai → v0/relationalai}/std/dates.py +0 -0
  824. {relationalai → v0/relationalai}/std/graphs.py +0 -0
  825. {relationalai → v0/relationalai}/std/inspect.py +0 -0
  826. {relationalai → v0/relationalai}/std/math.py +0 -0
  827. {relationalai → v0/relationalai}/std/re.py +0 -0
  828. {relationalai → v0/relationalai}/std/strings.py +0 -0
  829. {relationalai → v0/relationalai}/tools/cleanup_snapshots.py +0 -0
  830. {relationalai → v0/relationalai}/tools/constants.py +0 -0
  831. {relationalai → v0/relationalai}/tools/query_utils.py +0 -0
  832. {relationalai → v0/relationalai}/tools/snapshot_viewer.py +0 -0
  833. {relationalai → v0/relationalai}/util/__init__.py +0 -0
  834. {relationalai → v0/relationalai}/util/constants.py +0 -0
  835. {relationalai → v0/relationalai}/util/graph.py +0 -0
  836. {relationalai → v0/relationalai}/util/timeout.py +0 -0
@@ -1,1826 +0,0 @@
1
- #pyright: reportPrivateImportUsage=false
2
- from __future__ import annotations
3
-
4
- # Standard library imports
5
- import io
6
- import itertools
7
- import os
8
- import shutil
9
- import sys
10
- import threading
11
- import time
12
- import importlib
13
- from dataclasses import dataclass
14
- from pathlib import Path
15
- from typing import Any, Callable, Dict, List, Sequence, TextIO, cast
16
-
17
- # Third-party imports
18
- import rich
19
- from InquirerPy import inquirer, utils as inquirer_utils
20
- from InquirerPy.base.complex import FakeDocument
21
- from InquirerPy.base.control import Choice
22
- from prompt_toolkit.key_binding import KeyPressEvent
23
- from prompt_toolkit.validation import ValidationError
24
- from rich.color import Color
25
- from rich.console import Console, Group
26
- from wcwidth import wcwidth
27
-
28
- # Local imports
29
- from relationalai import debugging
30
- from relationalai.util.format import format_duration
31
- from ..environments import (
32
- HexEnvironment,
33
- JupyterEnvironment,
34
- NotebookRuntimeEnvironment,
35
- SnowbookEnvironment,
36
- runtime_env,
37
- )
38
-
39
- #--------------------------------------------------
40
- # Constants
41
- #--------------------------------------------------
42
-
43
- # Display symbols
44
- ARROW = "➜"
45
- CHECK_MARK = "✓"
46
- SUCCESS_ICON = "✅"
47
- FAIL_ICON = "❌"
48
-
49
- # Spinner animation frames
50
- SPINNER_FRAMES = ["▰▱▱▱", "▰▰▱▱", "▰▰▰▱", "▰▰▰▰", "▱▰▰▰", "▱▱▰▰", "▱▱▱▰", "▱▱▱▱"]
51
-
52
- # Terminal display constants
53
- DEFAULT_TERMINAL_WIDTH = 80
54
- SEPARATOR_WIDTH = 40
55
-
56
- # Task progress constants
57
- INITIALIZATION_COMPLETED_TEXT = "Parallel init finished in"
58
- MIN_CATEGORY_DURATION_SECONDS = 0.25 # Only show categories with duration > 250ms
59
-
60
- # Task category constants
61
- TASK_CATEGORY_INDEXING = "indexing"
62
- TASK_CATEGORY_PROVISIONING = "provisioning"
63
- TASK_CATEGORY_CHANGE_TRACKING = "change_tracking"
64
- TASK_CATEGORY_CACHE = "cache"
65
- TASK_CATEGORY_RELATIONS = "relations"
66
- TASK_CATEGORY_STATUS = "status"
67
- TASK_CATEGORY_VALIDATION = "validation"
68
- TASK_CATEGORY_OTHER = "other"
69
-
70
- # Default summary categories
71
- DEFAULT_SUMMARY_CATEGORIES = {
72
- TASK_CATEGORY_INDEXING: "Indexing",
73
- TASK_CATEGORY_PROVISIONING: "Provisioning",
74
- TASK_CATEGORY_CHANGE_TRACKING: "Change tracking",
75
- TASK_CATEGORY_RELATIONS: "Relations",
76
- TASK_CATEGORY_STATUS: "Status",
77
- TASK_CATEGORY_VALIDATION: "Validation",
78
- TASK_CATEGORY_OTHER: "Other"
79
- }
80
-
81
- # Parallel task categories (for duration calculation)
82
- PARALLEL_TASK_CATEGORIES = {
83
- TASK_CATEGORY_INDEXING,
84
- TASK_CATEGORY_PROVISIONING,
85
- TASK_CATEGORY_VALIDATION,
86
- TASK_CATEGORY_CHANGE_TRACKING
87
- }
88
-
89
- # Prompt constants
90
- REFETCH = "[REFETCH LIST]"
91
- MANUAL_ENTRY = "[MANUAL ENTRY]"
92
-
93
- # Timing constants
94
- HIGHLIGHT_DURATION = 2.0
95
- COMPLETION_DISPLAY_DURATION = 8.0
96
- TIMER_CHECK_INTERVAL = 0.1
97
- SPINNER_UPDATE_INTERVAL = 0.15
98
- INITIAL_DISPLAY_DELAY = 0.25
99
- BRIEF_PAUSE = 0.1
100
- LIVE_REFRESH_RATE = 10
101
-
102
- #--------------------------------------------------
103
- # Style
104
- #--------------------------------------------------
105
-
106
- STYLE = inquirer_utils.get_style({
107
- "fuzzy_prompt": "#e5c07b"
108
- }, False)
109
-
110
- #--------------------------------------------------
111
- # Helpers
112
- #--------------------------------------------------
113
-
114
- def rich_str(string:str, style:str|None = None) -> str:
115
- output = io.StringIO()
116
- console = Console(file=output, force_terminal=True)
117
- console.print(string, style=style)
118
- return output.getvalue()
119
-
120
- def _load_ipython_display() -> tuple[Any, Callable[..., Any]]:
121
- """Load IPython display helpers, raising if unavailable."""
122
- try:
123
- module = importlib.import_module("IPython.display")
124
- except ImportError as exc: # pragma: no cover - only triggered without IPython
125
- raise RuntimeError(
126
- "NotebookTaskProgress requires IPython when running in a notebook environment."
127
- ) from exc
128
-
129
- html_factory = getattr(module, "HTML")
130
- display_fn = getattr(module, "display")
131
- return html_factory, cast(Callable[..., Any], display_fn)
132
-
133
- def nat_path(path: Path, base: Path):
134
- resolved_path = path.resolve()
135
- resolved_base = base.resolve()
136
- if resolved_base in resolved_path.parents or resolved_path == resolved_base:
137
- return resolved_path.relative_to(resolved_base)
138
- else:
139
- return resolved_path.absolute()
140
-
141
- def get_default(value:str|None, list_of_values:Sequence[str]):
142
- if value is None:
143
- return None
144
- list_of_values_lower = [v.lower() for v in list_of_values]
145
- value_lower = value.lower()
146
- if value_lower in list_of_values_lower:
147
- return value
148
-
149
- #--------------------------------------------------
150
- # Dividers
151
- #--------------------------------------------------
152
-
153
- def divider(console=None, flush=False):
154
- div = "\n[dim]---------------------------------------------------\n "
155
- if console is None:
156
- rich.print(div)
157
- else:
158
- console.print(div)
159
- if flush:
160
- sys.stdout.flush()
161
-
162
- def abort():
163
- rich.print()
164
- rich.print("[yellow]Aborted")
165
- divider()
166
- sys.exit(1)
167
-
168
- #--------------------------------------------------
169
- # Prompts
170
- #--------------------------------------------------
171
-
172
- default_bindings = cast(Any, {
173
- "interrupt": [
174
- {"key": "escape"},
175
- {"key": "c-c"},
176
- {"key": "c-d"}
177
- ],
178
- "skip": [
179
- {"key": "c-s"}
180
- ]
181
- })
182
-
183
- def prompt(message:str, value:str|None, newline=False, validator:Callable|None = None, invalid_message:str|None = None) -> str:
184
- if value:
185
- return value
186
- if invalid_message is None:
187
- invalid_message = "Invalid input"
188
- try:
189
- result:str = inquirer.text(
190
- message,
191
- validate=validator,
192
- invalid_message=invalid_message,
193
- keybindings=default_bindings,
194
- ).execute()
195
- except KeyboardInterrupt:
196
- abort()
197
- raise Exception("Unreachable")
198
- if newline:
199
- rich.print("")
200
- return result
201
-
202
- def select(message:str, choices:List[str|Choice], value:str|None, newline=False, **kwargs) -> str|Any:
203
- if value:
204
- return value
205
- try:
206
- result:str = inquirer.select(message, choices, keybindings=default_bindings, **kwargs).execute()
207
- except KeyboardInterrupt:
208
- abort()
209
- raise Exception("Unreachable")
210
- if newline:
211
- rich.print("")
212
- return result
213
-
214
- def _enumerate_static_choices(choices: inquirer_utils.InquirerPyChoice) -> inquirer_utils.InquirerPyChoice:
215
- return [{"name": f"{i+1} {choice}", "value": choice} for i, choice in enumerate(choices)]
216
-
217
- def _enumerate_choices(choices: inquirer_utils.InquirerPyListChoices) -> inquirer_utils.InquirerPyListChoices:
218
- if callable(choices):
219
- return lambda session: _enumerate_static_choices(choices(session))
220
- else:
221
- return _enumerate_static_choices(choices)
222
-
223
- def _fuzzy(message:str, choices:inquirer_utils.InquirerPyListChoices, default:str|None = None, multiselect=False, show_index=False, **kwargs) -> str|list[str]|None:
224
- if show_index:
225
- choices = _enumerate_choices(choices)
226
-
227
- try:
228
- kwargs["keybindings"] = default_bindings
229
- if multiselect:
230
- kwargs["keybindings"] = { # pylint: disable=assignment-from-no-return
231
- "toggle": [
232
- {"key": "tab"}, # toggle choices
233
- ],
234
- "toggle-down": [
235
- {"key": "tab", "filter":False},
236
- ],
237
- }.update(default_bindings)
238
- kwargs["multiselect"] = True
239
-
240
- # NOTE: Using the builtin `default` kwarg to do this also filters
241
- # results which is undesirable and confusing for pre-filled
242
- # fields, so we move the cursor ourselves using the internals :(
243
- prompt = inquirer.fuzzy(message, choices=choices, max_height=8, border=True, style=STYLE, **kwargs)
244
- prompt._content_control._get_choices(prompt._content_control.choices, default)
245
-
246
- return prompt.execute()
247
- except KeyboardInterrupt:
248
- return abort()
249
-
250
- def fuzzy(message:str, choices:inquirer_utils.InquirerPyListChoices, default:str|None = None, show_index=False, **kwargs) -> str:
251
- return cast(str, _fuzzy(message, choices, default=default, show_index=show_index, **kwargs))
252
-
253
- def fuzzy_multiselect(message:str, choices:inquirer_utils.InquirerPyListChoices, default:str|None = None, show_index=False, **kwargs) -> list[str]:
254
- return cast(list[str], _fuzzy(message, choices, default=default, show_index=show_index, multiselect=True, **kwargs))
255
-
256
- def fuzzy_with_refetch(prompt: str, type: str, fn: Callable, *args, **kwargs):
257
- exception = None
258
- auto_select = kwargs.get("auto_select", None)
259
- not_found_message = kwargs.get("not_found_message", None)
260
- manual_entry = kwargs.get("manual_entry", None)
261
- items = []
262
- with Spinner(f"Fetching {type}", f"Fetched {type}"):
263
- try:
264
- items = fn(*args)
265
- except Exception as e:
266
- exception = e
267
- if exception is not None:
268
- rich.print(f"\n[red]Error fetching {type}: {exception}\n")
269
- return exception
270
- if len(items) == 0:
271
- if not_found_message:
272
- rich.print(f"\n[yellow]{not_found_message}\n")
273
- else:
274
- rich.print(f"\n[yellow]No valid {type} found\n")
275
- return None
276
-
277
- if auto_select and len(items) == 1 and items[0].lower() == auto_select.lower():
278
- return auto_select
279
-
280
- if manual_entry:
281
- items.insert(0, MANUAL_ENTRY)
282
- items.insert(0, REFETCH)
283
-
284
- passed_default = kwargs.get("default", None)
285
- passed_mandatory = kwargs.get("mandatory", False)
286
-
287
- rich.print("")
288
- result = fuzzy(
289
- prompt,
290
- items,
291
- default=get_default(passed_default, items),
292
- mandatory=passed_mandatory
293
- )
294
- rich.print("")
295
-
296
- while result == REFETCH:
297
- result = fuzzy_with_refetch(prompt, type, fn, *args, **kwargs)
298
- return result
299
-
300
- def confirm(message:str, default:bool = False) -> bool:
301
- try:
302
- return inquirer.confirm(message, default=default, keybindings=default_bindings).execute()
303
- except KeyboardInterrupt:
304
- return abort()
305
-
306
- def text(message:str, default:str|None = None, validator:Callable|None = None, invalid_message:str|None = None, **kwargs) -> str:
307
- if not invalid_message:
308
- invalid_message = "Invalid input"
309
- try:
310
- return inquirer.text(
311
- message,
312
- default=default or "",
313
- keybindings=default_bindings,
314
- validate=validator,
315
- invalid_message=invalid_message,
316
- **kwargs
317
- ).execute()
318
- except KeyboardInterrupt:
319
- return abort()
320
-
321
- def password(message:str, default:str|None = None, validator:Callable|None = None, invalid_message:str|None = None) -> str:
322
- if invalid_message is None:
323
- invalid_message = "Invalid input"
324
- try:
325
- return inquirer.secret(
326
- message,
327
- default=default or "",
328
- keybindings=default_bindings,
329
- validate=validator,
330
- invalid_message=invalid_message
331
- ).execute()
332
- except KeyboardInterrupt:
333
- return abort()
334
-
335
- def number(message:str, default:float|int|None = None, validator:Callable|None = None, invalid_message:str|None = None, **kwargs) -> float|int:
336
- if not invalid_message:
337
- invalid_message = "Invalid input"
338
- try:
339
- return inquirer.number(
340
- message,
341
- default=default or 0,
342
- keybindings=default_bindings,
343
- validate=validator,
344
- invalid_message=invalid_message,
345
- **kwargs
346
- ).execute()
347
- except KeyboardInterrupt:
348
- return abort()
349
-
350
- def file(message: str, start_path:Path|None = None, allow_freeform=False, **kwargs) -> str|None:
351
- try:
352
- return FuzzyFile(message, start_path, allow_freeform=allow_freeform, max_height=8, border=True, style=STYLE, **kwargs).execute()
353
- except KeyboardInterrupt:
354
- return abort()
355
-
356
- class FuzzyFile(inquirer.fuzzy):
357
- def __init__(self, message: str, initial_path: Path|None = None, allow_freeform = False, *args, **kwargs):
358
- self.initial_path = initial_path or Path()
359
- self.current_path = Path(self.initial_path)
360
- self.allow_freeform = allow_freeform
361
-
362
- kwargs["keybindings"] = {
363
- **default_bindings,
364
- "answer": [
365
- {"key": os.sep},
366
- {"key": "enter"},
367
- {"key": "tab"},
368
- {"key": "right"}
369
- ],
370
- **kwargs.get("keybindings", {})
371
- }
372
-
373
- super().__init__(message, *args, **kwargs, choices=self._get_choices)
374
-
375
- def _get_prompt_message(self) -> List[tuple[str, str]]:
376
- pre_answer = ("class:instruction", f" {self.instruction} " if self.instruction else " ")
377
- result = str(nat_path(self.current_path, self.initial_path))
378
-
379
- if result:
380
- sep = " " if self._amark else ""
381
- return [
382
- ("class:answermark", self._amark),
383
- ("class:answered_question", f"{sep}{self._message} "),
384
- ("class:answer", f"{result}{os.sep if not self.status['answered'] else ''}"),
385
- ]
386
- else:
387
- sep = " " if self._qmark else ""
388
- return [
389
- ("class:answermark", self._amark),
390
- ("class:questionmark", self._qmark),
391
- ("class:question", f"{sep}{self._message}"),
392
- pre_answer
393
- ]
394
-
395
- def _handle_enter(self, event: KeyPressEvent) -> None:
396
- try:
397
- fake_document = FakeDocument(self.result_value)
398
- self._validator.validate(fake_document) # type: ignore
399
- cc = self.content_control
400
- if self._multiselect:
401
- self.status["answered"] = True
402
- if not self.selected_choices:
403
- self.status["result"] = [cc.selection["name"]]
404
- event.app.exit(result=[cc.selection["value"]])
405
- else:
406
- self.status["result"] = self.result_name
407
- event.app.exit(result=self.result_value)
408
- else:
409
- res_value = cc.selection["value"]
410
- self.current_path /= res_value
411
- if self.current_path.is_dir():
412
- self._update_choices()
413
- else:
414
- self.status["answered"] = True
415
- self.status["result"] = cc.selection["name"]
416
- event.app.exit(result=str(nat_path(self.current_path, self.initial_path)))
417
- except ValidationError as e:
418
- self._set_error(str(e))
419
- except IndexError:
420
- self.status["answered"] = True
421
- res = self._get_current_text() if self.allow_freeform else None
422
- if self._multiselect:
423
- res = [res] if res is not None else []
424
- self.status["result"] = res
425
- event.app.exit(result=res)
426
-
427
- def _get_choices(self, _ = None):
428
- choices = os.listdir(self.current_path)
429
- choices.append("..")
430
- return choices
431
-
432
- def _update_choices(self):
433
- raw_choices = self._get_choices()
434
- cc = self.content_control
435
- cc.selected_choice_index = 0
436
- cc._raw_choices = raw_choices
437
- cc.choices = cc._get_choices(raw_choices, None)
438
- cc._safety_check()
439
- cc._format_choices()
440
- self._buffer.reset()
441
-
442
- #--------------------------------------------------
443
- # Line Clearing Mixin
444
- #--------------------------------------------------
445
-
446
- class LineClearingMixin:
447
- """Mixin class that provides line clearing functionality for different environments."""
448
-
449
- def __init__(self, *args, **kwargs):
450
- super().__init__(*args, **kwargs)
451
- self.last_line_length = 0
452
- # Detect environment capabilities
453
- import sys
454
- self.is_tty = sys.stdout.isatty()
455
- self.is_snowflake_notebook = isinstance(runtime_env, SnowbookEnvironment)
456
- self.is_jupyter = isinstance(runtime_env, JupyterEnvironment)
457
-
458
- def _get_terminal_width(self):
459
- """Get terminal width, with fallback to reasonable default."""
460
- try:
461
- return shutil.get_terminal_size().columns
462
- except (OSError, AttributeError):
463
- return 80 # Fallback width
464
-
465
- def _clear_line(self, new_text: str):
466
- """Clear the current line and write new text using the best available method."""
467
- import sys
468
-
469
- if self.is_tty and not self.is_snowflake_notebook and not self.is_jupyter:
470
- # Use proper ANSI clear line sequence for terminals
471
- sys.stdout.write(f"\r\033[K{new_text}")
472
- else:
473
- # For notebooks and environments without ANSI support, use smart padding
474
- terminal_width = self._get_terminal_width()
475
-
476
- # Truncate text if it exceeds terminal width to prevent wrapping
477
- if len(new_text) > terminal_width:
478
- new_text = new_text[:terminal_width - 3] + "..."
479
-
480
- # Calculate how much of the line we need to clear
481
- # Use the maximum of last line length or terminal width to ensure full clearing
482
- clear_width = max(self.last_line_length, terminal_width)
483
-
484
- # Clear with spaces and write new text
485
- sys.stdout.write(f"\r{' ' * clear_width}\r{new_text}")
486
-
487
- sys.stdout.flush()
488
- # Update the tracked line length
489
- self.last_line_length = len(new_text)
490
-
491
- def _write_line(self, text: str, newline: bool = False):
492
- """Write text to the current line, optionally adding a newline."""
493
- import sys
494
- if newline:
495
- sys.stdout.write(f"{text}\n")
496
- else:
497
- sys.stdout.write(text)
498
- sys.stdout.flush()
499
-
500
- def _clear_and_write(self, text: str, newline: bool = False):
501
- """Clear the current line and write new text, with optional newline."""
502
- self._clear_line(text)
503
- if newline:
504
- import sys
505
- sys.stdout.write("\n")
506
- sys.stdout.flush()
507
-
508
-
509
- #--------------------------------------------------
510
- # Spinner
511
- #--------------------------------------------------
512
-
513
- class Spinner(LineClearingMixin):
514
- """Shows a spinner control while a task is running.
515
- The finished_message will not be printed if there was an exception and the failed_message is provided.
516
- """
517
- busy = False
518
-
519
- def __init__(
520
- self,
521
- message="",
522
- finished_message: str = "",
523
- failed_message=None,
524
- delay=None,
525
- leading_newline=False,
526
- trailing_newline=False,
527
- ):
528
- self.message = message
529
- self.finished_message = finished_message
530
- self.failed_message = failed_message
531
- self.spinner_generator = itertools.cycle(SPINNER_FRAMES)
532
- self.is_snowflake_notebook = isinstance(runtime_env, SnowbookEnvironment)
533
- self.is_hex = isinstance(runtime_env, HexEnvironment)
534
- self.is_jupyter = isinstance(runtime_env, JupyterEnvironment)
535
- self.in_notebook = isinstance(runtime_env, NotebookRuntimeEnvironment)
536
- self.is_tty = sys.stdout.isatty()
537
-
538
- self._set_delay(delay)
539
- self.leading_newline = leading_newline
540
- self.trailing_newline = trailing_newline
541
- self.last_message = ""
542
- self.display = None
543
- # Add lock to prevent race conditions between spinner thread and main thread
544
- self._update_lock = threading.Lock()
545
-
546
- def _set_delay(self, delay: float|int|None) -> None:
547
- """Set appropriate delay based on environment and user input."""
548
- # If delay value is provided, validate and use it
549
- if delay:
550
- if isinstance(delay, (int, float)) and delay > 0:
551
- self.delay = float(delay)
552
- return
553
- else:
554
- raise ValueError(f"Invalid delay value: {delay}")
555
- # Otherwise, set delay based on environment
556
- elif self.is_hex:
557
- self.delay = 0 # Hex tries to append a new block each frame
558
- elif self.is_snowflake_notebook:
559
- self.delay = 0.5 # SF notebooks get bogged down
560
- elif self.in_notebook or self.is_tty:
561
- # Fast refresh for other notebooks or terminals with good printing support
562
- self.delay = 0.1
563
- else:
564
- # Otherwise disable the spinner animation entirely
565
- # for non-interactive environments.
566
- self.delay = 0
567
-
568
- def get_message(self, starting=False):
569
- max_width = shutil.get_terminal_size().columns
570
- spinner = "⏳⏳⏳⏳" if not self.is_tty and starting else next(self.spinner_generator)
571
- full_message = f"{spinner} {self.message}"
572
- if len(full_message) > max_width:
573
- return full_message[:max_width - 3] + "..."
574
- else:
575
- return full_message
576
-
577
- def update(self, message:str|None=None, color:str|None=None, file:TextIO|None=None, starting=False):
578
- # Use lock to prevent race conditions between spinner thread and main thread
579
- with self._update_lock:
580
- if message is None:
581
- message = self.get_message(starting=starting)
582
- if self.is_jupyter:
583
- # @NOTE: IPython isn't available in CI. This won't ever get invoked w/out IPython available though.
584
- from IPython.display import HTML, display # pyright: ignore[reportMissingImports]
585
- color_string = ""
586
- if color:
587
- color_value = Color.parse(color)
588
- rgb_tuple = color_value.get_truecolor()
589
- rgb_hex = f"#{rgb_tuple[0]:02X}{rgb_tuple[1]:02X}{rgb_tuple[2]:02X}"
590
- color_string = f"color: {rgb_hex};" if color is not None else ""
591
- content = HTML(f"<span style='font-family: monospace;{color_string}'>{message}</span>")
592
- if self.display is not None:
593
- self.display.update(content)
594
- else:
595
- self.display = display(content, display_id=True)
596
- else:
597
- if self.can_use_terminal_colors() and color is not None:
598
- rich_message = f"[{color}]{message}"
599
- else:
600
- rich_message = message
601
- rich_string = rich_str(rich_message)
602
- def width(word):
603
- return sum(wcwidth(c) for c in word)
604
- diff = width(self.last_message) - width(rich_string)
605
- self.reset_cursor()
606
- # Use rich.print with lock protection
607
- output_file = file or sys.stdout
608
- rich.print(rich_message + (" " * diff), file=output_file, end="", flush=False)
609
- if output_file.isatty() or self.in_notebook:
610
- output_file.flush()
611
- self.last_message = rich_string
612
-
613
- def can_use_terminal_colors(self):
614
- return not self.is_snowflake_notebook
615
-
616
- def update_messages(self, updater: dict[str, str]):
617
- if "message" in updater:
618
- self.message = updater["message"]
619
- if "finished_message" in updater:
620
- self.finished_message = updater["finished_message"]
621
- if "failed_message" in updater:
622
- self.failed_message = updater["failed_message"]
623
- self.update()
624
-
625
- def spinner_task(self):
626
- while self.busy and self.delay:
627
- self.update(color="magenta")
628
- time.sleep(self.delay) #type: ignore[union-attr] | we only call spinner_task if delay is not None anyway
629
- self.reset_cursor()
630
-
631
- def reset_cursor(self):
632
- if self.is_tty:
633
- # Clear the entire line and move cursor to beginning
634
- sys.stdout.write("\r\033[K")
635
- elif not self.is_jupyter:
636
- sys.stdout.write("\r")
637
-
638
- def __enter__(self):
639
- if self.leading_newline:
640
- rich.print()
641
- self.update(color="magenta", starting=True)
642
- # return control to the event loop briefly so stdout can be sure to flush:
643
- if self.delay:
644
- time.sleep(INITIAL_DISPLAY_DELAY)
645
- self.reset_cursor()
646
- if not self.delay:
647
- return self
648
- self.busy = True
649
- threading.Thread(target=self.spinner_task, daemon=True).start()
650
- return self
651
-
652
- def __exit__(self, exception, value, _):
653
- self.busy = False
654
- if exception is not None:
655
- if self.failed_message is not None:
656
- self.update(f"{self.failed_message} {value}", color="yellow", file=sys.stderr)
657
- # Use rich.print with explicit newline to ensure proper formatting
658
- rich.print(file=sys.stderr)
659
- return True
660
- return False
661
- if self.delay: # will be None for non-interactive environments
662
- time.sleep(self.delay)
663
- self.reset_cursor()
664
- if self.finished_message != "":
665
- final_message = f"▰▰▰▰ {self.finished_message}"
666
- self.update(final_message, color="green")
667
- # Use rich.print with explicit newline to ensure proper formatting
668
- rich.print()
669
- elif self.finished_message == "":
670
- self.update("")
671
- self.reset_cursor()
672
- if self.trailing_newline:
673
- rich.print()
674
-
675
- class DebuggingSpan:
676
- span: debugging.Span
677
- def __init__(self, span_type: str):
678
- self.span_type = span_type
679
- self.span_attrs = {}
680
-
681
- def attrs(self, **kwargs):
682
- self.span_attrs = kwargs
683
- return self
684
-
685
- def __enter__(self):
686
- self.span = debugging.span_start(self.span_type, **self.span_attrs)
687
- return self
688
-
689
- def __exit__(self, exc_type, exc_val, exc_tb):
690
- debugging.span_end(self.span)
691
-
692
- class SpanSpinner(Spinner):
693
- span: debugging.Span
694
- def __init__(self, span_type: str, *spinner_args, **spinner_kwargs):
695
- super().__init__(*spinner_args, **spinner_kwargs)
696
- self.span_type = span_type
697
- self.span_attrs = {}
698
-
699
- def attrs(self, **kwargs):
700
- self.span_attrs = kwargs
701
- return self
702
-
703
- def __enter__(self):
704
- self.span = debugging.span_start(self.span_type, **self.span_attrs)
705
- return super().__enter__()
706
-
707
- def __exit__(self, exc_type, exc_val, exc_tb):
708
- super().__exit__(exc_type, exc_val, exc_tb)
709
- debugging.span_end(self.span)
710
-
711
-
712
- @dataclass
713
- class TaskInfo:
714
- """Represents a single task with its state and metadata."""
715
- description: str
716
- category: str = "other"
717
- completed: bool = False
718
- added_time: float = 0.0
719
- completed_time: float = 0.0
720
- hidden: bool = False
721
-
722
- def __post_init__(self):
723
- if self.added_time == 0.0:
724
- self.added_time = time.time()
725
-
726
- def get_duration(self) -> float:
727
- """Get the duration of this task in seconds."""
728
- if not self.completed or self.completed_time == 0.0:
729
- return 0.0
730
-
731
- return self.completed_time - self.added_time
732
-
733
-
734
- class _TimerManager:
735
- """Manages all delayed operations for TaskProgress."""
736
-
737
- def __init__(self, progress_instance):
738
- self._progress = progress_instance
739
- self._operations = {} # task_id -> (operation_type, scheduled_time)
740
- self._thread = None
741
- self._running = False
742
-
743
- def schedule_highlight_removal(self, task_id: str, delay: float | None = None):
744
- """Schedule removal of highlighting for a task."""
745
- if delay is None:
746
- delay = HIGHLIGHT_DURATION
747
- scheduled_time = time.time() + delay
748
- self._operations[task_id] = ("remove_highlighting", scheduled_time)
749
- self._start()
750
-
751
- def schedule_task_removal(self, task_id: str, delay: float | None = None):
752
- """Schedule removal of a completed task."""
753
- if delay is None:
754
- delay = COMPLETION_DISPLAY_DURATION
755
- scheduled_time = time.time() + delay
756
- self._operations[task_id] = ("delayed_removal", scheduled_time)
757
- self._start()
758
-
759
- def schedule_task_hiding(self, task_id: str, delay: float | None = None):
760
- """Schedule hiding of a completed task from display (but keep in data structure)."""
761
- if delay is None:
762
- delay = COMPLETION_DISPLAY_DURATION
763
- scheduled_time = time.time() + delay
764
- self._operations[task_id] = ("delayed_hiding", scheduled_time)
765
- self._start()
766
-
767
-
768
- def _start(self):
769
- """Start the timer thread if not already running."""
770
- if self._thread is None or not self._thread.is_alive():
771
- self._running = True
772
- self._thread = threading.Thread(target=self._worker, daemon=True)
773
- self._thread.start()
774
-
775
- def _worker(self):
776
- """Worker thread for handling delayed operations."""
777
- while self._running:
778
- current_time = time.time()
779
- completed_ops = []
780
-
781
- # Find completed operations
782
- for task_id, (op_type, scheduled_time) in self._operations.items():
783
- if current_time >= scheduled_time:
784
- completed_ops.append((task_id, op_type))
785
-
786
- # Process completed operations
787
- for task_id, op_type in completed_ops:
788
- self._process_operation(task_id, op_type)
789
- del self._operations[task_id]
790
-
791
- time.sleep(TIMER_CHECK_INTERVAL)
792
-
793
- def _process_operation(self, task_id: str, op_type: str):
794
- """Process a completed delayed operation."""
795
- progress = self._progress
796
- if op_type == "remove_highlighting":
797
- if hasattr(progress, "_highlighted_tasks") and task_id in progress._highlighted_tasks:
798
- del progress._highlighted_tasks[task_id]
799
- if hasattr(progress, "_after_task_update"):
800
- progress._after_task_update()
801
- elif op_type == "delayed_removal":
802
- if hasattr(progress, "_tasks") and task_id in progress._tasks:
803
- del progress._tasks[task_id]
804
- if hasattr(progress, "_after_task_update"):
805
- progress._after_task_update()
806
- elif op_type == "delayed_hiding":
807
- if hasattr(progress, "_tasks") and task_id in progress._tasks:
808
- progress._tasks[task_id].hidden = True
809
- if hasattr(progress, "_after_task_update"):
810
- progress._after_task_update()
811
-
812
- def stop(self):
813
- """Stop the timer manager."""
814
- self._running = False
815
- self._operations.clear()
816
-
817
-
818
- class _TaskStateMixin:
819
- """Shared task management helpers for notebook and terminal progress displays."""
820
-
821
- enable_highlighting: bool = True
822
-
823
- def _init_task_state(self, *, hide_on_completion: bool = False, show_duration_summary: bool = True) -> None:
824
- self.hide_on_completion = hide_on_completion
825
- self.show_duration_summary = show_duration_summary
826
- self._tasks: dict[str, TaskInfo] = {}
827
- self._next_task_id: int = 1
828
- self._highlighted_tasks: dict[str, float] = {}
829
- self._process_start_time: float | None = None
830
- self._process_end_time: float | None = None
831
- self.main_completed: bool = False
832
- self.main_failed: bool = False
833
- self._timer_manager = _TimerManager(self)
834
-
835
- def _after_task_update(self) -> None:
836
- """Hook for subclasses to react when task state changes."""
837
- # Implemented by subclasses when they need to update the display immediately.
838
- return None
839
-
840
- def _generate_task_id(self) -> str:
841
- """Generate a unique task ID."""
842
- task_id = f"task_{self._next_task_id}"
843
- self._next_task_id += 1
844
- return task_id
845
-
846
- def add_sub_task(self, description: str, task_id: str | None = None, category: str = "general") -> str:
847
- """Add a new sub-task and return its unique ID."""
848
- if task_id is None:
849
- task_id = self._generate_task_id()
850
-
851
- if task_id not in self._tasks:
852
- self._tasks[task_id] = TaskInfo(description=description, category=category)
853
- self._after_task_update()
854
-
855
- return task_id
856
-
857
- def update_sub_task(self, task_id: str, description: str) -> None:
858
- """Update an existing sub-task description."""
859
- if task_id in self._tasks:
860
- task_info = self._tasks[task_id]
861
- if self.enable_highlighting and task_info.description != description:
862
- self._highlighted_tasks[task_id] = time.time() + HIGHLIGHT_DURATION
863
- self._timer_manager.schedule_highlight_removal(task_id)
864
-
865
- task_info.description = description
866
- self._after_task_update()
867
-
868
- def complete_sub_task(self, task_id: str, record_time: bool = True) -> None:
869
- """Complete a sub-task by marking it as done."""
870
- if task_id in self._tasks:
871
- if task_id in self._highlighted_tasks:
872
- del self._highlighted_tasks[task_id]
873
-
874
- if not self._tasks[task_id].completed and record_time:
875
- self._tasks[task_id].completed_time = time.time()
876
- self._tasks[task_id].completed = True
877
-
878
- self._after_task_update()
879
- self._timer_manager.schedule_task_hiding(task_id)
880
-
881
- def remove_sub_task(self, task_id: str, animate: bool = True) -> None:
882
- """Remove a sub-task by ID with optional completion animation."""
883
- if task_id in self._tasks:
884
- if task_id in self._highlighted_tasks:
885
- del self._highlighted_tasks[task_id]
886
-
887
- if animate:
888
- self.complete_sub_task(task_id)
889
- else:
890
- del self._tasks[task_id]
891
- self._after_task_update()
892
-
893
- def update_sub_status(self, sub_status: str) -> None:
894
- """Legacy method for backward compatibility - creates/updates a default sub-task."""
895
- self.add_sub_task(sub_status, "default")
896
- self.update_sub_task("default", sub_status)
897
-
898
- def update_main_status(self, message: str) -> None:
899
- """Update the main status line with custom information."""
900
- if getattr(self, "description", "") != message:
901
- self.description = message
902
- self._after_task_update()
903
-
904
- def update_messages(self, updater: dict[str, str]) -> None:
905
- """Update both main message and sub-status if provided."""
906
- if "message" in updater:
907
- self.description = updater["message"]
908
- self._after_task_update()
909
- if "sub_status" in updater:
910
- self.update_sub_status(updater["sub_status"])
911
- if "success_message" in updater:
912
- self.success_message = updater["success_message"]
913
- if "failure_message" in updater:
914
- self.failure_message = updater["failure_message"]
915
-
916
- def get_sub_task_count(self) -> int:
917
- """Get the current number of active sub-tasks."""
918
- return len(self._tasks)
919
-
920
- def list_sub_tasks(self) -> list[str]:
921
- """Get a list of all active sub-task IDs."""
922
- return list(self._tasks.keys())
923
-
924
- def get_task_status(self) -> str:
925
- """Get a human-readable status of current task count vs limit."""
926
- return f"› Active tasks: {len(self._tasks)}"
927
-
928
- def get_task_duration(self, task_id: str) -> float:
929
- """Get the duration of a specific task in seconds."""
930
- if task_id in self._tasks:
931
- return self._tasks[task_id].get_duration()
932
- return 0.0
933
-
934
- def get_completed_tasks(self) -> dict[str, TaskInfo]:
935
- """Get all completed tasks with their timing information."""
936
- return {task_id: task_info for task_id, task_info in self._tasks.items() if task_info.completed}
937
-
938
- def get_tasks_by_category(self, category: str) -> dict[str, TaskInfo]:
939
- """Get all tasks (completed or active) for a specific category."""
940
- return {
941
- task_id: task_info
942
- for task_id, task_info in self._tasks.items()
943
- if task_info.category == category
944
- }
945
-
946
- def get_completed_tasks_by_category(self, category: str) -> dict[str, TaskInfo]:
947
- """Get all completed tasks for a specific category."""
948
- return {
949
- task_id: task_info
950
- for task_id, task_info in self._tasks.items()
951
- if task_info.category == category and task_info.completed
952
- }
953
-
954
- def _clear_all_tasks(self) -> None:
955
- """Clear all tasks and related data."""
956
- self._tasks.clear()
957
- self._highlighted_tasks.clear()
958
-
959
- def set_process_start_time(self) -> None:
960
- """Set the overall process start time."""
961
- self._process_start_time = time.time()
962
-
963
- def set_process_end_time(self) -> None:
964
- """Set the overall process end time."""
965
- self._process_end_time = time.time()
966
-
967
- def get_total_duration(self) -> float:
968
- """Get the total duration from first task added to last task completed."""
969
- if not self._tasks:
970
- return 0.0
971
-
972
- completed_tasks = self.get_completed_tasks()
973
- if not completed_tasks:
974
- return 0.0
975
-
976
- start_times = [task.added_time for task in self._tasks.values()]
977
- completion_times = [task.completed_time for task in completed_tasks.values() if task.completed_time > 0]
978
-
979
- if not start_times or not completion_times:
980
- return 0.0
981
-
982
- earliest_start = min(start_times)
983
- latest_completion = max(completion_times)
984
- return latest_completion - earliest_start
985
-
986
-
987
- class TaskProgress(_TaskStateMixin):
988
- """A progress component that uses Rich's Live system to provide proper two-line display.
989
-
990
- This class provides:
991
- - Main progress line with spinner and description
992
- - Sub-status lines with hierarchical arrow indicators (➜)
993
- - Proper error handling with success/failure messages
994
- - Task-based progress tracking with context managers
995
- - Highlighting of subtask text changes in yellow for 2 seconds when text differs
996
- - Consistent task ordering with active tasks displayed above completed ones
997
- """
998
-
999
- def __init__(
1000
- self,
1001
- description: str = "",
1002
- success_message: str = "",
1003
- failure_message: str = "",
1004
- leading_newline: bool = False,
1005
- trailing_newline: bool = False,
1006
- transient: bool = False,
1007
- hide_on_completion: bool = False,
1008
- show_duration_summary: bool = True,
1009
- ):
1010
- # Public configuration
1011
- self.description = description
1012
- self.success_message = success_message
1013
- self.failure_message = failure_message
1014
- self.leading_newline = leading_newline
1015
- self.trailing_newline = trailing_newline
1016
- self.transient = transient
1017
- self._init_task_state(
1018
- hide_on_completion=hide_on_completion,
1019
- show_duration_summary=show_duration_summary,
1020
- )
1021
- self.enable_highlighting = True
1022
-
1023
- # Detect CI environment to avoid cursor control issues
1024
- from ..environments import CIEnvironment
1025
- self.is_ci = isinstance(runtime_env, CIEnvironment)
1026
- self.is_jupyter = isinstance(runtime_env, JupyterEnvironment)
1027
-
1028
- # Core components
1029
- # In CI or Jupyter, avoid forcing terminal rendering to prevent duplicate outputs
1030
- force_terminal = not self.is_ci and not self.is_jupyter
1031
- force_jupyter = True if self.is_jupyter else None
1032
- self.console = Console(
1033
- force_terminal=force_terminal,
1034
- force_jupyter=force_jupyter,
1035
- )
1036
- self.live = None
1037
- self.main_completed = False
1038
- self.main_failed = False
1039
-
1040
- # Animation state
1041
- self.spinner_index = 0
1042
-
1043
- # Performance optimizations
1044
- self._render_cache = None
1045
- self._last_state_hash = None
1046
-
1047
- self._spinner_thread = None
1048
-
1049
- def _generate_task_id(self) -> str:
1050
- """Generate a unique task ID."""
1051
- task_id = f"task_{self._next_task_id}"
1052
- self._next_task_id += 1
1053
- return task_id
1054
-
1055
- def _compute_state_hash(self) -> int:
1056
- """Compute a simple hash of the current state for caching."""
1057
- # Use a simple hash based on key state variables
1058
- state_parts = [
1059
- str(self.main_completed),
1060
- str(self.main_failed),
1061
- self.description,
1062
- str(self.spinner_index),
1063
- str(len(self._tasks)),
1064
- str(len(self._highlighted_tasks)),
1065
- ]
1066
-
1067
- # Add task states (only essential info for performance)
1068
- for task_id, task_info in self._tasks.items():
1069
- state_parts.append(f"{task_id}:{task_info.completed}:{task_info.description}")
1070
- if task_id in self._highlighted_tasks:
1071
- state_parts.append(f"highlight:{task_id}")
1072
-
1073
- return hash(tuple(state_parts))
1074
-
1075
- def _render_display(self):
1076
- """Render the current display state with caching optimization."""
1077
- # Check if we need to re-render
1078
- current_hash = self._compute_state_hash()
1079
- if current_hash == self._last_state_hash and self._render_cache is not None:
1080
- return self._render_cache
1081
-
1082
- from rich.text import Text
1083
-
1084
- # Build main task line
1085
- if self.main_failed:
1086
- # Split the description to style only the "Failed:" part in red
1087
- if self.description.startswith("Failed:"):
1088
- failed_part = "Failed:"
1089
- rest_part = self.description[len("Failed:"):].lstrip()
1090
- main_line = (Text(f"{FAIL_ICON} ", style="red") +
1091
- Text(failed_part, style="red") +
1092
- Text(f" {rest_part}", style="default"))
1093
- else:
1094
- # Fallback if description doesn't start with "Failed:"
1095
- main_line = Text(f"{FAIL_ICON} ", style="red") + Text(self.description, style="red")
1096
- elif self.main_completed:
1097
- main_line = Text(f"{SUCCESS_ICON} ", style="green") + Text(self.description, style="green")
1098
- else:
1099
- spinner_text = SPINNER_FRAMES[self.spinner_index]
1100
- main_line = Text(f"{spinner_text} ", style="magenta") + Text(self.description, style="magenta")
1101
-
1102
- # Build subtask lines
1103
- subtask_lines = self._render_subtask_lines()
1104
-
1105
- # Combine all lines
1106
- all_lines = [main_line] + subtask_lines
1107
-
1108
- # Cache the result
1109
- self._render_cache = Group(*all_lines)
1110
- self._last_state_hash = current_hash
1111
-
1112
- return self._render_cache
1113
-
1114
- def _render_subtask_lines(self):
1115
- """Render all subtask lines efficiently."""
1116
- from rich.text import Text
1117
-
1118
- subtask_lines = []
1119
- current_time = time.time()
1120
-
1121
- # Separate incomplete and completed tasks
1122
- incomplete_tasks = []
1123
- completed_tasks = []
1124
-
1125
- for task_id, task_info in self._tasks.items():
1126
- # Skip hidden tasks
1127
- if task_info.hidden:
1128
- continue
1129
- if task_info.completed:
1130
- completed_tasks.append((task_id, task_info))
1131
- else:
1132
- incomplete_tasks.append((task_id, task_info))
1133
-
1134
- # Render incomplete tasks first
1135
- for task_id, task_info in incomplete_tasks:
1136
- is_highlighted = (task_id in self._highlighted_tasks and
1137
- current_time < self._highlighted_tasks[task_id])
1138
-
1139
- style = "yellow" if is_highlighted else "white"
1140
- line = Text(f" {ARROW} ", style=style) + Text(task_info.description, style=style)
1141
- subtask_lines.append(line)
1142
-
1143
- # Render completed tasks
1144
- for task_id, task_info in completed_tasks:
1145
- line = Text(f" {CHECK_MARK} ", style="green") + Text(task_info.description, style="green")
1146
- subtask_lines.append(line)
1147
-
1148
- return subtask_lines
1149
-
1150
- def _advance_spinner(self):
1151
- """Advance the spinner animation."""
1152
- self.spinner_index = (self.spinner_index + 1) % len(SPINNER_FRAMES)
1153
-
1154
- def _invalidate_cache(self):
1155
- """Invalidate the render cache to force re-rendering."""
1156
- self._last_state_hash = None
1157
- self._render_cache = None
1158
-
1159
- def _update_display(self):
1160
- """Update the display if live."""
1161
- if self.live:
1162
- self.live.update(self._render_display())
1163
-
1164
- def _after_task_update(self) -> None:
1165
- """Refresh the live display when task state changes."""
1166
- self._invalidate_cache()
1167
- self._update_display()
1168
-
1169
- def _clear_all_tasks(self) -> None:
1170
- """Clear tasks and refresh display."""
1171
- super()._clear_all_tasks()
1172
- self._after_task_update()
1173
-
1174
- def generate_summary(self, categories: dict[str, str] | None = None) -> str:
1175
- """Generate a summary of completed tasks by category."""
1176
- if categories is None:
1177
- categories = DEFAULT_SUMMARY_CATEGORIES
1178
-
1179
- category_durations: dict[str, float] = {}
1180
- for category_name in categories:
1181
- tasks = self.get_completed_tasks_by_category(category_name)
1182
- category_durations[category_name] = _calculate_category_duration(category_name, tasks)
1183
-
1184
- if not any(category_durations.values()):
1185
- return ""
1186
-
1187
- total_duration = self.get_total_duration()
1188
-
1189
- try:
1190
- from rich.console import Console
1191
- from rich.table import Table
1192
-
1193
- table = Table(show_header=False, box=None, padding=(0, 1))
1194
- table.add_column("Operation", style="white")
1195
- table.add_column("Duration", style="green", justify="right")
1196
-
1197
- if total_duration > 0:
1198
- table.add_row(
1199
- INITIALIZATION_COMPLETED_TEXT,
1200
- format_duration(total_duration)
1201
- )
1202
-
1203
- for category_name, display_name in categories.items():
1204
- duration = category_durations[category_name]
1205
- if duration > MIN_CATEGORY_DURATION_SECONDS:
1206
- table.add_row(
1207
- f" {ARROW} {display_name}",
1208
- format_duration(duration)
1209
- )
1210
-
1211
- table.add_row("", "")
1212
-
1213
- console = Console()
1214
- with console.capture() as capture:
1215
- console.print(table)
1216
- return capture.get()
1217
-
1218
- except ImportError:
1219
- lines: list[str] = []
1220
- if total_duration > 0:
1221
- lines.append(f"{INITIALIZATION_COMPLETED_TEXT} {format_duration(total_duration)}")
1222
-
1223
- for category_name, display_name in categories.items():
1224
- duration = category_durations[category_name]
1225
- if duration > MIN_CATEGORY_DURATION_SECONDS:
1226
- lines.append(f" {ARROW} {display_name} {format_duration(duration)}")
1227
-
1228
- if lines:
1229
- lines.append("")
1230
-
1231
- return "\n".join(lines)
1232
-
1233
- def __enter__(self):
1234
- if self.leading_newline:
1235
- print()
1236
-
1237
- # Start the live display
1238
- from rich.live import Live
1239
- self.live = Live(self._render_display(), console=self.console, refresh_per_second=LIVE_REFRESH_RATE)
1240
- self.live.start()
1241
-
1242
- # Start spinner animation
1243
- self._start_spinner()
1244
-
1245
- return self
1246
-
1247
- def _start_spinner(self):
1248
- """Start the spinner animation thread."""
1249
- def spinner_animation():
1250
- while self.live and not self.main_completed and not self.main_failed:
1251
- time.sleep(SPINNER_UPDATE_INTERVAL)
1252
- if self.live:
1253
- self._advance_spinner()
1254
- self.live.update(self._render_display())
1255
-
1256
- self._spinner_thread = threading.Thread(target=spinner_animation, daemon=True)
1257
- self._spinner_thread.start()
1258
-
1259
- def __exit__(self, exc_type, exc_val, exc_tb):
1260
- # Stop timer manager
1261
- self._timer_manager.stop()
1262
-
1263
- if exc_type is not None:
1264
- # Exception occurred - show failure message
1265
- self._handle_failure(exc_val)
1266
- return False # Don't suppress the exception
1267
- else:
1268
- # Success - show completion
1269
- self._handle_success()
1270
-
1271
- return True
1272
-
1273
- def _handle_failure(self, exc_val):
1274
- """Handle failure case in context manager exit."""
1275
- # Clear all tasks and update main task to show failure state
1276
- self._clear_all_tasks()
1277
- self.main_failed = True
1278
-
1279
- # Update main task description to show failure message
1280
- if self.failure_message:
1281
- self.description = self.failure_message
1282
- else:
1283
- self.description = f"Failed: {exc_val}"
1284
-
1285
- # Update the display to show the failure state before stopping
1286
- if self.live:
1287
- self.live.update(self._render_display())
1288
- # Brief pause to show the failure state
1289
- time.sleep(BRIEF_PAUSE)
1290
-
1291
- if self.trailing_newline:
1292
- print()
1293
- self._cleanup()
1294
-
1295
- def _handle_success(self):
1296
- """Handle success case in context manager exit."""
1297
- self.main_completed = True
1298
-
1299
- # Generate summary before clearing tasks (so we have the timing data)
1300
- # Only generate if show_duration_summary flag is True
1301
- summary = self.generate_summary() if self.show_duration_summary else ""
1302
-
1303
- self._clear_all_tasks()
1304
-
1305
- # Update main task description to show success message
1306
- if self.success_message:
1307
- self.description = self.success_message
1308
-
1309
- # Show success message in Rich Live display
1310
- if self.live:
1311
- self.live.update(self._render_display())
1312
- # Stop the live display
1313
- self.live.stop()
1314
-
1315
- # Print summary if available
1316
- if summary:
1317
- print() # Blank line for separation
1318
- print(summary, end="") # summary already has trailing newline
1319
- print() # Add extra blank line after summary
1320
-
1321
- if self.trailing_newline:
1322
- print()
1323
- self._cleanup()
1324
-
1325
- def _cleanup(self):
1326
- """Clean up resources."""
1327
- if self.live:
1328
- # Stop the live display first
1329
- self.live.stop()
1330
- # Clear the current line using ANSI escape sequence (only in TTY, not in CI)
1331
- if not self.is_ci and sys.stdout.isatty():
1332
- print("\r\033[K", end="", flush=True)
1333
-
1334
- def _calculate_category_duration(category_name: str, tasks: Dict[str, TaskInfo]) -> float:
1335
- """Calculate duration for a category based on task type (parallel vs sequential)."""
1336
- if not tasks:
1337
- return 0.0
1338
-
1339
- if category_name in PARALLEL_TASK_CATEGORIES:
1340
- # For parallel tasks, use time span (max completion - min start)
1341
- category_start_times = [task_info.added_time for task_info in tasks.values()]
1342
- category_completion_times = [
1343
- task_info.completed_time for task_info in tasks.values()
1344
- if task_info.completed_time > 0
1345
- ]
1346
- if category_start_times and category_completion_times:
1347
- return max(category_completion_times) - min(category_start_times)
1348
- else:
1349
- return 0.0
1350
- else:
1351
- # For sequential tasks, sum individual durations
1352
- return sum(task_info.get_duration() for task_info in tasks.values())
1353
-
1354
-
1355
- def create_progress(description: str = "", success_message: str = "", failure_message: str = "",
1356
- leading_newline: bool = False, trailing_newline: bool = False, show_duration_summary: bool = True):
1357
- """Factory function to create the appropriate progress component based on environment.
1358
-
1359
- Automatically detects if we're in a notebook environment (Snowflake, Jupyter, etc.)
1360
- and returns the appropriate progress class.
1361
- """
1362
- from ..environments import runtime_env, SnowbookEnvironment, NotebookRuntimeEnvironment
1363
-
1364
- if isinstance(runtime_env, (SnowbookEnvironment, NotebookRuntimeEnvironment)):
1365
- # Use NotebookTaskProgress for Snowflake and Jupyter notebooks
1366
- return NotebookTaskProgress(
1367
- description=description,
1368
- success_message=success_message,
1369
- failure_message=failure_message,
1370
- leading_newline=leading_newline,
1371
- trailing_newline=trailing_newline,
1372
- show_duration_summary=show_duration_summary
1373
- )
1374
- else:
1375
- # Use TaskProgress for other environments (terminal, CI, etc.)
1376
- return TaskProgress(
1377
- description=description,
1378
- success_message=success_message,
1379
- failure_message=failure_message,
1380
- leading_newline=leading_newline,
1381
- trailing_newline=trailing_newline,
1382
- show_duration_summary=show_duration_summary
1383
- )
1384
-
1385
-
1386
- class SubTaskContext:
1387
- """Context manager for individual subtasks within a TaskProgress."""
1388
-
1389
- def __init__(self, task_progress: TaskProgress, description: str, task_id: str | None = None):
1390
- self.task_progress = task_progress
1391
- self.description = description
1392
- self.task_id = task_id
1393
- self._task_id = None
1394
-
1395
- def __enter__(self):
1396
- # Add the subtask and get its ID
1397
- self._task_id = self.task_progress.add_sub_task(self.description, self.task_id)
1398
- return self._task_id
1399
-
1400
- def __exit__(self, exc_type, exc_val, exc_tb):
1401
- if self._task_id and exc_type is None:
1402
- # Success - complete the subtask automatically when context exits
1403
- self.task_progress.complete_sub_task(self._task_id)
1404
- # If there was an exception, leave the subtask as-is for debugging
1405
- return False # Don't suppress exceptions
1406
-
1407
-
1408
- class NotebookTaskProgress(_TaskStateMixin):
1409
- """A progress component specifically designed for notebook environments like Snowflake.
1410
-
1411
- This class copies the EXACT working Spinner code and adapts it for notebook use.
1412
- """
1413
-
1414
- def __init__(
1415
- self,
1416
- description: str = "",
1417
- success_message: str = "",
1418
- failure_message: str = "",
1419
- leading_newline: bool = False,
1420
- trailing_newline: bool = False,
1421
- show_duration_summary: bool = True,
1422
- ):
1423
- self.description = description
1424
- self.success_message = success_message
1425
- self.failure_message = failure_message
1426
- self.leading_newline = leading_newline
1427
- self.trailing_newline = trailing_newline
1428
- self._init_task_state(show_duration_summary=show_duration_summary)
1429
- self.enable_highlighting = False
1430
-
1431
- self.spinner_generator = itertools.cycle(SPINNER_FRAMES)
1432
-
1433
- # Environment detection for notebook environments only
1434
- self.is_snowflake_notebook = isinstance(runtime_env, SnowbookEnvironment)
1435
- self.is_hex = isinstance(runtime_env, HexEnvironment)
1436
- self.is_jupyter = isinstance(runtime_env, JupyterEnvironment)
1437
- self.in_notebook = isinstance(runtime_env, NotebookRuntimeEnvironment)
1438
-
1439
- self._set_delay(None)
1440
-
1441
- self.last_message = ""
1442
- self.display = None
1443
- self._update_lock = threading.Lock()
1444
-
1445
- # Add sub-task support for TaskProgress compatibility
1446
- self.spinner_thread = None
1447
- self._current_subtask = ""
1448
- self.busy = False # Initialize busy state
1449
-
1450
-
1451
- def _generate_task_id(self) -> str:
1452
- """Generate a unique task ID."""
1453
- task_id = f"task_{self._next_task_id}"
1454
- self._next_task_id += 1
1455
- return task_id
1456
-
1457
- def _set_delay(self, delay: float|int|None) -> None:
1458
- """Set appropriate delay for notebook environments."""
1459
- # If delay value is provided, validate and use it
1460
- if delay:
1461
- if isinstance(delay, (int, float)) and delay > 0:
1462
- self.delay = float(delay)
1463
- return
1464
- else:
1465
- raise ValueError(f"Invalid delay value: {delay}")
1466
- # Simple delay for notebooks - no complex environment detection needed
1467
- elif self.in_notebook or self.is_snowflake_notebook or self.is_jupyter or self.is_hex:
1468
- self.delay = 0.2 # Simple, consistent delay for all notebook environments
1469
- else:
1470
- # Disable animation for non-interactive environments
1471
- self.delay = 0
1472
-
1473
- def get_message(self, starting=False):
1474
- """Get the current message with spinner - notebook environments only."""
1475
- # For notebook environments, use a reasonable default width
1476
- max_width = DEFAULT_TERMINAL_WIDTH
1477
- try:
1478
- max_width = shutil.get_terminal_size().columns
1479
- except (OSError, AttributeError):
1480
- pass # Use default width if terminal size can't be determined
1481
-
1482
- spinner = "⏳⏳⏳⏳" if starting else next(self.spinner_generator)
1483
-
1484
- # If there's an active subtask, show ONLY the subtask
1485
- if hasattr(self, '_current_subtask') and self._current_subtask:
1486
- full_message = f"{spinner} {self._current_subtask}"
1487
- else:
1488
- # Otherwise show the main task with subtask count if any
1489
- if len(self._tasks) > 0:
1490
- full_message = f"{spinner} {self.description} ({len(self._tasks)} active)"
1491
- else:
1492
- full_message = f"{spinner} {self.description}"
1493
-
1494
- if len(full_message) > max_width:
1495
- return full_message[:max_width - 3] + "..."
1496
- else:
1497
- return full_message
1498
-
1499
- def update(self, message:str|None=None, file:TextIO|None=None, starting=False):
1500
- """Update the display - notebook environments only."""
1501
- # Use lock to prevent race conditions between spinner thread and main thread
1502
- with self._update_lock:
1503
- if self.is_jupyter:
1504
- _, display_fn = _load_ipython_display()
1505
-
1506
- if message is None:
1507
- lines = self._build_jupyter_lines(starting=starting)
1508
- elif message == "":
1509
- lines = []
1510
- else:
1511
- lines = [message]
1512
-
1513
- rendered = "\n".join(lines)
1514
- content = {"text/plain": rendered}
1515
- if self.display is not None:
1516
- self.display.update(content, raw=True)
1517
- else:
1518
- self.display = display_fn(content, display_id=True, raw=True)
1519
- return
1520
-
1521
- if message is None:
1522
- message = self.get_message(starting=starting)
1523
-
1524
- rich_string = rich_str(message)
1525
-
1526
- def width(word):
1527
- return sum(wcwidth(c) for c in word)
1528
-
1529
- diff = width(self.last_message) - width(rich_string)
1530
-
1531
- sys.stdout.write("\r") # Move to beginning
1532
- sys.stdout.write(" " * DEFAULT_TERMINAL_WIDTH) # Clear with spaces
1533
- sys.stdout.write("\r") # Move back to beginning
1534
-
1535
- sys.stdout.write(message + (" " * diff)) # Write text directly
1536
- if self.in_notebook:
1537
- sys.stdout.flush() # Force output
1538
- self.last_message = rich_string
1539
-
1540
- def _build_jupyter_lines(self, starting: bool) -> list[str]:
1541
- """Compose the main status and subtasks for Jupyter display."""
1542
- if self.busy or starting:
1543
- spinner = SPINNER_FRAMES[0] if starting else next(self.spinner_generator)
1544
- main_line = f"{spinner} {self.description}"
1545
- else:
1546
- main_text = self.success_message or self.description
1547
- main_line = f"{SUCCESS_ICON} {main_text}"
1548
-
1549
- visible_tasks = self._collect_visible_tasks()
1550
- lines = [main_line]
1551
-
1552
- for marker, tasks in (
1553
- (ARROW, visible_tasks["incomplete"]),
1554
- (CHECK_MARK, visible_tasks["completed"]),
1555
- ):
1556
- for task_info in tasks:
1557
- lines.append(f" {marker} {task_info.description}")
1558
-
1559
- return lines
1560
-
1561
- def _collect_visible_tasks(self) -> dict[str, list["TaskInfo"]]:
1562
- """Separate visible tasks into incomplete and completed lists."""
1563
- incomplete: list["TaskInfo"] = []
1564
- completed: list["TaskInfo"] = []
1565
-
1566
- for task_info in self._tasks.values():
1567
- if task_info.hidden:
1568
- continue
1569
- if task_info.completed:
1570
- completed.append(task_info)
1571
- else:
1572
- incomplete.append(task_info)
1573
-
1574
- return {"incomplete": incomplete, "completed": completed}
1575
-
1576
- def reset_cursor(self):
1577
- """Reset cursor to beginning of line - notebook environments only."""
1578
- # For notebook environments, use simple carriage return
1579
- if not self.is_jupyter:
1580
- sys.stdout.write("\r")
1581
-
1582
- def spinner_task(self):
1583
- """Spinner animation task."""
1584
- while self.busy and self.delay:
1585
- self.update()
1586
- time.sleep(self.delay) #type: ignore[union-attr] | we only call spinner_task if delay is not None anyway
1587
- self.reset_cursor()
1588
-
1589
- def _update_subtask_display(self, subtask_text: str):
1590
- """Update sub-task display - shows ONLY the subtask text."""
1591
- # Store the current display state
1592
- if not hasattr(self, '_current_display'):
1593
- self._current_display = ""
1594
-
1595
- # Only update if the display has changed
1596
- if self._current_display != subtask_text:
1597
- # Store the subtask text for the spinner to use
1598
- self._current_subtask = subtask_text
1599
- self._current_display = subtask_text
1600
- # The spinner will now show the subtask instead of main task
1601
-
1602
- def add_sub_task(self, description: str, task_id: str | None = None, category: str = "general") -> str:
1603
- task_id = super().add_sub_task(description, task_id, category)
1604
- # Update spinner display with the active subtask
1605
- if task_id in self._tasks:
1606
- self._update_subtask_display(self._tasks[task_id].description)
1607
- return task_id
1608
-
1609
- def update_sub_task(self, task_id: str, description: str) -> None:
1610
- super().update_sub_task(task_id, description)
1611
- if task_id in self._tasks:
1612
- self._update_subtask_display(description)
1613
-
1614
- def complete_sub_task(self, task_id: str, record_time: bool = True) -> None:
1615
- super().complete_sub_task(task_id, record_time=record_time)
1616
- # Clear the subtask display when completed
1617
- self._current_subtask = ""
1618
- self._current_display = ""
1619
-
1620
- def remove_sub_task(self, task_id: str, animate: bool = True) -> None:
1621
- """Remove a sub-task by ID."""
1622
- task_description: str | None = None
1623
- if task_id in self._tasks:
1624
- task_description = self._tasks[task_id].description
1625
-
1626
- # Notebook display should drop the subtask immediately to clear the UI.
1627
- super().remove_sub_task(task_id, animate=False)
1628
-
1629
- if task_description and getattr(self, "_current_subtask", "") == task_description:
1630
- self._current_subtask = ""
1631
- self._current_display = ""
1632
- # The spinner will now show the main task again
1633
-
1634
- def update_sub_status(self, sub_status: str):
1635
- """Legacy method for backward compatibility - creates/updates a default sub-task."""
1636
- super().update_sub_status(sub_status)
1637
-
1638
- def update_main_status(self, message: str):
1639
- """Update the main status line with real-time updates."""
1640
- super().update_main_status(message)
1641
- self._current_subtask = ""
1642
- self._current_display = ""
1643
- # The spinner will now show the updated main task
1644
-
1645
- def update_messages(self, updater: dict[str, str]):
1646
- """Update both main message and sub-status if provided."""
1647
- super().update_messages(updater)
1648
-
1649
- def _clear_all_tasks(self) -> None:
1650
- super()._clear_all_tasks()
1651
- self._current_subtask = ""
1652
- self._current_display = ""
1653
-
1654
- def __enter__(self):
1655
- # Skip leading newline for Jupyter - it interferes with IPython display
1656
- if self.leading_newline and not self.is_jupyter:
1657
- rich.print()
1658
- self.update(starting=True)
1659
- # return control to the event loop briefly so stdout can be sure to flush:
1660
- if self.delay:
1661
- time.sleep(INITIAL_DISPLAY_DELAY)
1662
- self.reset_cursor()
1663
- if not self.delay:
1664
- return self
1665
- self.busy = True
1666
- threading.Thread(target=self.spinner_task, daemon=True).start()
1667
- return self
1668
-
1669
- def __exit__(self, exc_type, exc_val, exc_tb):
1670
- self.busy = False
1671
- if exc_type is not None:
1672
- if self.failure_message is not None:
1673
- self.update(f"{self.failure_message} {exc_val}", file=sys.stderr)
1674
- # For non-Jupyter, add newline to ensure proper formatting
1675
- # For Jupyter, IPython display handles formatting
1676
- if not self.is_jupyter:
1677
- rich.print(file=sys.stderr)
1678
- return True
1679
- return False
1680
- if self.delay: # will be None for non-interactive environments
1681
- time.sleep(self.delay)
1682
-
1683
- # Generate summary BEFORE clearing the spinner line (so we have timing data)
1684
- # Only generate if show_duration_summary flag is True
1685
- summary = self.generate_summary() if self.show_duration_summary else ""
1686
-
1687
- # Clear the spinner line completely
1688
- self._clear_spinner_line()
1689
-
1690
- final_message: str | None = None
1691
- if self.success_message:
1692
- final_message = f"{SUCCESS_ICON} {self.success_message}"
1693
- elif summary:
1694
- final_message = f"{SUCCESS_ICON} Done"
1695
-
1696
- if final_message:
1697
- if self.is_jupyter:
1698
- if self.display is not None:
1699
- self.display.update({"text/plain": final_message}, raw=True)
1700
- else:
1701
- _, display_fn = _load_ipython_display()
1702
- self.display = display_fn({"text/plain": final_message}, display_id=True, raw=True)
1703
- else:
1704
- print(final_message)
1705
- elif self.success_message == "":
1706
- # When there's no success message, clear the display for notebooks
1707
- # The summary will be printed below if available
1708
- if self.is_jupyter:
1709
- self.update("")
1710
- # For non-Jupyter notebooks, _clear_spinner_line() already handled it
1711
-
1712
- # Print summary if there are completed tasks
1713
- if summary:
1714
- if self.is_jupyter:
1715
- # Use IPython display to avoid blank stdout lines in notebooks
1716
- _, display_fn = _load_ipython_display()
1717
- display_fn({"text/plain": summary.strip()}, raw=True)
1718
- else:
1719
- print()
1720
- print(summary.strip()) # Summary includes visual separator line
1721
-
1722
- # Skip trailing newline for Jupyter - it interferes with IPython display
1723
- if self.trailing_newline and not self.is_jupyter:
1724
- rich.print()
1725
- return True
1726
-
1727
- def _clear_spinner_line(self):
1728
- """Clear the current spinner line completely."""
1729
- # Skip clearing for Jupyter notebooks - IPython display handles it
1730
- if self.is_jupyter:
1731
- return
1732
-
1733
- # Write enough spaces to clear any content, then move to start of line
1734
- terminal_width = DEFAULT_TERMINAL_WIDTH
1735
- try:
1736
- terminal_width = shutil.get_terminal_size().columns
1737
- except (OSError, AttributeError):
1738
- pass
1739
-
1740
- # Clear with spaces, carriage return, and newline to ensure we're on a fresh line
1741
- sys.stdout.write("\r" + " " * terminal_width + "\r\n")
1742
- sys.stdout.flush()
1743
-
1744
- def set_process_start_time(self) -> None:
1745
- """Set the overall process start time."""
1746
- self._process_start_time = time.time()
1747
-
1748
- def set_process_end_time(self) -> None:
1749
- """Set the overall process end time."""
1750
- self._process_end_time = time.time()
1751
-
1752
- def get_total_duration(self) -> float:
1753
- """Get the total duration from first task added to last task completed."""
1754
- if not self._tasks:
1755
- return 0.0
1756
-
1757
- completed_tasks = self.get_completed_tasks()
1758
- if not completed_tasks:
1759
- return 0.0
1760
-
1761
- # Find earliest start time and latest completion time
1762
- start_times = [task.added_time for task in self._tasks.values()]
1763
- completion_times = [task.completed_time for task in completed_tasks.values() if task.completed_time > 0]
1764
-
1765
- if not start_times or not completion_times:
1766
- return 0.0
1767
-
1768
- earliest_start = min(start_times)
1769
- latest_completion = max(completion_times)
1770
-
1771
- return latest_completion - earliest_start
1772
-
1773
- def generate_summary(self, categories: dict[str, str] | None = None) -> str:
1774
- """Generate a summary of completed tasks by category."""
1775
- if categories is None:
1776
- categories = DEFAULT_SUMMARY_CATEGORIES
1777
-
1778
- # Get completed tasks by category and calculate durations
1779
- category_durations = {}
1780
- for category_name in categories:
1781
- tasks = self.get_completed_tasks_by_category(category_name)
1782
- category_durations[category_name] = _calculate_category_duration(category_name, tasks)
1783
-
1784
- # If there's nothing meaningful to show, return empty string
1785
- if not any(category_durations.values()):
1786
- return ""
1787
-
1788
- # Generate summary lines with proper alignment
1789
- summary_lines = []
1790
- label_width = 30 # Width for category labels
1791
- time_width = 10 # Width for time column (right-aligned)
1792
-
1793
- # Add total time FIRST (at the top) - align with arrow lines
1794
- total_duration = self.get_total_duration()
1795
- if total_duration > 0:
1796
- formatted_total = format_duration(total_duration)
1797
- # Use the same format as arrow lines but with a different prefix
1798
- # This ensures perfect alignment with the time column
1799
- summary_lines.append(f" {INITIALIZATION_COMPLETED_TEXT:<{label_width-1}} {formatted_total:>{time_width}}")
1800
-
1801
- # Add category breakdown
1802
- category_lines = []
1803
- for category_name, display_name in categories.items():
1804
- duration = category_durations[category_name]
1805
- if duration > MIN_CATEGORY_DURATION_SECONDS: # Only show significant durations
1806
- formatted_duration = format_duration(duration)
1807
- # Use arrow for visual consistency with right-aligned time
1808
- category_lines.append(f" {ARROW} {display_name:<{label_width-4}} {formatted_duration:>{time_width}}")
1809
-
1810
- # Only add category lines if there are any
1811
- if category_lines:
1812
- summary_lines.extend(category_lines)
1813
-
1814
- # Add a visual separator line for Snowflake notebook environment
1815
- summary_lines.append("─" * SEPARATOR_WIDTH)
1816
-
1817
- return "\n".join(summary_lines) + "\n"
1818
-
1819
- def get_completed_tasks(self) -> dict[str, TaskInfo]:
1820
- """Get all completed tasks with their timing information."""
1821
- return {task_id: task_info for task_id, task_info in self._tasks.items() if task_info.completed}
1822
-
1823
- def get_completed_tasks_by_category(self, category: str) -> dict[str, TaskInfo]:
1824
- """Get all completed tasks for a specific category."""
1825
- return {task_id: task_info for task_id, task_info in self._tasks.items()
1826
- if task_info.category == category and task_info.completed}