relationalai 0.13.0.dev0__py3-none-any.whl → 0.13.1__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 (837) hide show
  1. frontend/debugger/dist/.gitignore +2 -0
  2. frontend/debugger/dist/assets/favicon-Dy0ZgA6N.png +0 -0
  3. frontend/debugger/dist/assets/index-Cssla-O7.js +208 -0
  4. frontend/debugger/dist/assets/index-DlHsYx1V.css +9 -0
  5. frontend/debugger/dist/index.html +17 -0
  6. relationalai/__init__.py +256 -1
  7. relationalai/clients/__init__.py +18 -0
  8. relationalai/clients/client.py +912 -0
  9. relationalai/clients/config.py +673 -0
  10. relationalai/clients/direct_access_client.py +118 -0
  11. relationalai/clients/exec_txn_poller.py +91 -0
  12. relationalai/clients/hash_util.py +31 -0
  13. relationalai/clients/local.py +571 -0
  14. relationalai/clients/profile_polling.py +73 -0
  15. relationalai/clients/resources/__init__.py +8 -0
  16. relationalai/clients/resources/azure/azure.py +477 -0
  17. relationalai/clients/resources/snowflake/__init__.py +20 -0
  18. relationalai/clients/resources/snowflake/cli_resources.py +87 -0
  19. relationalai/clients/resources/snowflake/direct_access_resources.py +694 -0
  20. relationalai/clients/resources/snowflake/engine_state_handlers.py +309 -0
  21. relationalai/clients/resources/snowflake/error_handlers.py +199 -0
  22. relationalai/clients/resources/snowflake/export_procedure.py.jinja +249 -0
  23. relationalai/clients/resources/snowflake/resources_factory.py +99 -0
  24. relationalai/clients/resources/snowflake/snowflake.py +3190 -0
  25. relationalai/clients/resources/snowflake/use_index_poller.py +1019 -0
  26. relationalai/clients/resources/snowflake/use_index_resources.py +188 -0
  27. relationalai/clients/resources/snowflake/util.py +387 -0
  28. relationalai/clients/result_helpers.py +420 -0
  29. relationalai/clients/types.py +113 -0
  30. relationalai/clients/util.py +356 -0
  31. relationalai/debugging.py +389 -0
  32. relationalai/dsl.py +1749 -0
  33. relationalai/early_access/builder/__init__.py +30 -0
  34. relationalai/early_access/builder/builder/__init__.py +35 -0
  35. relationalai/early_access/builder/snowflake/__init__.py +12 -0
  36. relationalai/early_access/builder/std/__init__.py +25 -0
  37. relationalai/early_access/builder/std/decimals/__init__.py +12 -0
  38. relationalai/early_access/builder/std/integers/__init__.py +12 -0
  39. relationalai/early_access/builder/std/math/__init__.py +12 -0
  40. relationalai/early_access/builder/std/strings/__init__.py +14 -0
  41. relationalai/early_access/devtools/__init__.py +12 -0
  42. relationalai/early_access/devtools/benchmark_lqp/__init__.py +12 -0
  43. relationalai/early_access/devtools/extract_lqp/__init__.py +12 -0
  44. relationalai/early_access/dsl/adapters/orm/adapter_qb.py +427 -0
  45. relationalai/early_access/dsl/adapters/orm/parser.py +636 -0
  46. relationalai/early_access/dsl/adapters/owl/adapter.py +176 -0
  47. relationalai/early_access/dsl/adapters/owl/parser.py +160 -0
  48. relationalai/early_access/dsl/bindings/common.py +402 -0
  49. relationalai/early_access/dsl/bindings/csv.py +170 -0
  50. relationalai/early_access/dsl/bindings/legacy/binding_models.py +143 -0
  51. relationalai/early_access/dsl/bindings/snowflake.py +64 -0
  52. relationalai/early_access/dsl/codegen/binder.py +411 -0
  53. relationalai/early_access/dsl/codegen/common.py +79 -0
  54. relationalai/early_access/dsl/codegen/helpers.py +23 -0
  55. relationalai/early_access/dsl/codegen/relations.py +700 -0
  56. relationalai/early_access/dsl/codegen/weaver.py +417 -0
  57. relationalai/early_access/dsl/core/builders/__init__.py +47 -0
  58. relationalai/early_access/dsl/core/builders/logic.py +19 -0
  59. relationalai/early_access/dsl/core/builders/scalar_constraint.py +11 -0
  60. relationalai/early_access/dsl/core/constraints/predicate/atomic.py +455 -0
  61. relationalai/early_access/dsl/core/constraints/predicate/universal.py +73 -0
  62. relationalai/early_access/dsl/core/constraints/scalar.py +310 -0
  63. relationalai/early_access/dsl/core/context.py +13 -0
  64. relationalai/early_access/dsl/core/cset.py +132 -0
  65. relationalai/early_access/dsl/core/exprs/__init__.py +116 -0
  66. relationalai/early_access/dsl/core/exprs/relational.py +18 -0
  67. relationalai/early_access/dsl/core/exprs/scalar.py +412 -0
  68. relationalai/early_access/dsl/core/instances.py +44 -0
  69. relationalai/early_access/dsl/core/logic/__init__.py +193 -0
  70. relationalai/early_access/dsl/core/logic/aggregation.py +98 -0
  71. relationalai/early_access/dsl/core/logic/exists.py +223 -0
  72. relationalai/early_access/dsl/core/logic/helper.py +163 -0
  73. relationalai/early_access/dsl/core/namespaces.py +32 -0
  74. relationalai/early_access/dsl/core/relations.py +276 -0
  75. relationalai/early_access/dsl/core/rules.py +112 -0
  76. relationalai/early_access/dsl/core/std/__init__.py +45 -0
  77. relationalai/early_access/dsl/core/temporal/recall.py +6 -0
  78. relationalai/early_access/dsl/core/types/__init__.py +270 -0
  79. relationalai/early_access/dsl/core/types/concepts.py +128 -0
  80. relationalai/early_access/dsl/core/types/constrained/__init__.py +267 -0
  81. relationalai/early_access/dsl/core/types/constrained/nominal.py +143 -0
  82. relationalai/early_access/dsl/core/types/constrained/subtype.py +124 -0
  83. relationalai/early_access/dsl/core/types/standard.py +92 -0
  84. relationalai/early_access/dsl/core/types/unconstrained.py +50 -0
  85. relationalai/early_access/dsl/core/types/variables.py +203 -0
  86. relationalai/early_access/dsl/ir/compiler.py +318 -0
  87. relationalai/early_access/dsl/ir/executor.py +260 -0
  88. relationalai/early_access/dsl/ontologies/constraints.py +88 -0
  89. relationalai/early_access/dsl/ontologies/export.py +30 -0
  90. relationalai/early_access/dsl/ontologies/models.py +453 -0
  91. relationalai/early_access/dsl/ontologies/python_printer.py +303 -0
  92. relationalai/early_access/dsl/ontologies/readings.py +60 -0
  93. relationalai/early_access/dsl/ontologies/relationships.py +322 -0
  94. relationalai/early_access/dsl/ontologies/roles.py +87 -0
  95. relationalai/early_access/dsl/ontologies/subtyping.py +55 -0
  96. relationalai/early_access/dsl/orm/constraints.py +438 -0
  97. relationalai/early_access/dsl/orm/measures/dimensions.py +200 -0
  98. relationalai/early_access/dsl/orm/measures/initializer.py +16 -0
  99. relationalai/early_access/dsl/orm/measures/measure_rules.py +275 -0
  100. relationalai/early_access/dsl/orm/measures/measures.py +299 -0
  101. relationalai/early_access/dsl/orm/measures/role_exprs.py +268 -0
  102. relationalai/early_access/dsl/orm/models.py +256 -0
  103. relationalai/early_access/dsl/orm/object_oriented_printer.py +344 -0
  104. relationalai/early_access/dsl/orm/printer.py +469 -0
  105. relationalai/early_access/dsl/orm/reasoners.py +480 -0
  106. relationalai/early_access/dsl/orm/relations.py +19 -0
  107. relationalai/early_access/dsl/orm/relationships.py +251 -0
  108. relationalai/early_access/dsl/orm/types.py +42 -0
  109. relationalai/early_access/dsl/orm/utils.py +79 -0
  110. relationalai/early_access/dsl/orm/verb.py +204 -0
  111. relationalai/early_access/dsl/physical_metadata/tables.py +133 -0
  112. relationalai/early_access/dsl/relations.py +170 -0
  113. relationalai/early_access/dsl/rulesets.py +69 -0
  114. relationalai/early_access/dsl/schemas/__init__.py +450 -0
  115. relationalai/early_access/dsl/schemas/builder.py +48 -0
  116. relationalai/early_access/dsl/schemas/comp_names.py +51 -0
  117. relationalai/early_access/dsl/schemas/components.py +203 -0
  118. relationalai/early_access/dsl/schemas/contexts.py +156 -0
  119. relationalai/early_access/dsl/schemas/exprs.py +89 -0
  120. relationalai/early_access/dsl/schemas/fragments.py +464 -0
  121. relationalai/early_access/dsl/serialization.py +79 -0
  122. relationalai/early_access/dsl/serialize/exporter.py +163 -0
  123. relationalai/early_access/dsl/snow/api.py +105 -0
  124. relationalai/early_access/dsl/snow/common.py +76 -0
  125. relationalai/early_access/dsl/state_mgmt/__init__.py +129 -0
  126. relationalai/early_access/dsl/state_mgmt/state_charts.py +125 -0
  127. relationalai/early_access/dsl/state_mgmt/transitions.py +130 -0
  128. relationalai/early_access/dsl/types/__init__.py +40 -0
  129. relationalai/early_access/dsl/types/concepts.py +12 -0
  130. relationalai/early_access/dsl/types/entities.py +135 -0
  131. relationalai/early_access/dsl/types/values.py +17 -0
  132. relationalai/early_access/dsl/utils.py +102 -0
  133. relationalai/early_access/graphs/__init__.py +13 -0
  134. relationalai/early_access/lqp/__init__.py +12 -0
  135. relationalai/early_access/lqp/compiler/__init__.py +12 -0
  136. relationalai/early_access/lqp/constructors/__init__.py +18 -0
  137. relationalai/early_access/lqp/executor/__init__.py +12 -0
  138. relationalai/early_access/lqp/ir/__init__.py +12 -0
  139. relationalai/early_access/lqp/passes/__init__.py +12 -0
  140. relationalai/early_access/lqp/pragmas/__init__.py +12 -0
  141. relationalai/early_access/lqp/primitives/__init__.py +12 -0
  142. relationalai/early_access/lqp/types/__init__.py +12 -0
  143. relationalai/early_access/lqp/utils/__init__.py +12 -0
  144. relationalai/early_access/lqp/validators/__init__.py +12 -0
  145. relationalai/early_access/metamodel/__init__.py +58 -0
  146. relationalai/early_access/metamodel/builtins/__init__.py +12 -0
  147. relationalai/early_access/metamodel/compiler/__init__.py +12 -0
  148. relationalai/early_access/metamodel/dependency/__init__.py +12 -0
  149. relationalai/early_access/metamodel/factory/__init__.py +17 -0
  150. relationalai/early_access/metamodel/helpers/__init__.py +12 -0
  151. relationalai/early_access/metamodel/ir/__init__.py +14 -0
  152. relationalai/early_access/metamodel/rewrite/__init__.py +7 -0
  153. relationalai/early_access/metamodel/typer/__init__.py +3 -0
  154. relationalai/early_access/metamodel/typer/typer/__init__.py +12 -0
  155. relationalai/early_access/metamodel/types/__init__.py +15 -0
  156. relationalai/early_access/metamodel/util/__init__.py +15 -0
  157. relationalai/early_access/metamodel/visitor/__init__.py +12 -0
  158. relationalai/early_access/rel/__init__.py +12 -0
  159. relationalai/early_access/rel/executor/__init__.py +12 -0
  160. relationalai/early_access/rel/rel_utils/__init__.py +12 -0
  161. relationalai/early_access/rel/rewrite/__init__.py +7 -0
  162. relationalai/early_access/solvers/__init__.py +19 -0
  163. relationalai/early_access/sql/__init__.py +11 -0
  164. relationalai/early_access/sql/executor/__init__.py +3 -0
  165. relationalai/early_access/sql/rewrite/__init__.py +3 -0
  166. relationalai/early_access/tests/logging/__init__.py +12 -0
  167. relationalai/early_access/tests/test_snapshot_base/__init__.py +12 -0
  168. relationalai/early_access/tests/utils/__init__.py +12 -0
  169. relationalai/environments/__init__.py +35 -0
  170. relationalai/environments/base.py +381 -0
  171. relationalai/environments/colab.py +14 -0
  172. relationalai/environments/generic.py +71 -0
  173. relationalai/environments/ipython.py +68 -0
  174. relationalai/environments/jupyter.py +9 -0
  175. relationalai/environments/snowbook.py +169 -0
  176. relationalai/errors.py +2496 -0
  177. relationalai/experimental/SF.py +38 -0
  178. relationalai/experimental/inspect.py +47 -0
  179. relationalai/experimental/pathfinder/__init__.py +158 -0
  180. relationalai/experimental/pathfinder/api.py +160 -0
  181. relationalai/experimental/pathfinder/automaton.py +584 -0
  182. relationalai/experimental/pathfinder/bridge.py +226 -0
  183. relationalai/experimental/pathfinder/compiler.py +416 -0
  184. relationalai/experimental/pathfinder/datalog.py +214 -0
  185. relationalai/experimental/pathfinder/diagnostics.py +56 -0
  186. relationalai/experimental/pathfinder/filter.py +236 -0
  187. relationalai/experimental/pathfinder/glushkov.py +439 -0
  188. relationalai/experimental/pathfinder/options.py +265 -0
  189. relationalai/experimental/pathfinder/pathfinder-v0.7.0.rel +1951 -0
  190. relationalai/experimental/pathfinder/rpq.py +344 -0
  191. relationalai/experimental/pathfinder/transition.py +200 -0
  192. relationalai/experimental/pathfinder/utils.py +26 -0
  193. relationalai/experimental/paths/README.md +107 -0
  194. relationalai/experimental/paths/api.py +143 -0
  195. relationalai/experimental/paths/benchmarks/grid_graph.py +37 -0
  196. relationalai/experimental/paths/code_organization.md +2 -0
  197. relationalai/experimental/paths/examples/Movies.ipynb +16328 -0
  198. relationalai/experimental/paths/examples/basic_example.py +40 -0
  199. relationalai/experimental/paths/examples/minimal_engine_warmup.py +3 -0
  200. relationalai/experimental/paths/examples/movie_example.py +77 -0
  201. relationalai/experimental/paths/examples/movies_data/actedin.csv +193 -0
  202. relationalai/experimental/paths/examples/movies_data/directed.csv +45 -0
  203. relationalai/experimental/paths/examples/movies_data/follows.csv +7 -0
  204. relationalai/experimental/paths/examples/movies_data/movies.csv +39 -0
  205. relationalai/experimental/paths/examples/movies_data/person.csv +134 -0
  206. relationalai/experimental/paths/examples/movies_data/produced.csv +16 -0
  207. relationalai/experimental/paths/examples/movies_data/ratings.csv +10 -0
  208. relationalai/experimental/paths/examples/movies_data/wrote.csv +11 -0
  209. relationalai/experimental/paths/examples/paths_benchmark.py +115 -0
  210. relationalai/experimental/paths/examples/paths_example.py +116 -0
  211. relationalai/experimental/paths/examples/pattern_to_automaton.py +28 -0
  212. relationalai/experimental/paths/find_paths_via_automaton.py +85 -0
  213. relationalai/experimental/paths/graph.py +185 -0
  214. relationalai/experimental/paths/path_algorithms/find_paths.py +280 -0
  215. relationalai/experimental/paths/path_algorithms/one_sided_ball_repetition.py +26 -0
  216. relationalai/experimental/paths/path_algorithms/one_sided_ball_upto.py +111 -0
  217. relationalai/experimental/paths/path_algorithms/single.py +59 -0
  218. relationalai/experimental/paths/path_algorithms/two_sided_balls_repetition.py +39 -0
  219. relationalai/experimental/paths/path_algorithms/two_sided_balls_upto.py +103 -0
  220. relationalai/experimental/paths/path_algorithms/usp-old.py +130 -0
  221. relationalai/experimental/paths/path_algorithms/usp-tuple.py +183 -0
  222. relationalai/experimental/paths/path_algorithms/usp.py +150 -0
  223. relationalai/experimental/paths/product_graph.py +93 -0
  224. relationalai/experimental/paths/rpq/automaton.py +584 -0
  225. relationalai/experimental/paths/rpq/diagnostics.py +56 -0
  226. relationalai/experimental/paths/rpq/rpq.py +378 -0
  227. relationalai/experimental/paths/tests/tests_limit_sp_max_length.py +90 -0
  228. relationalai/experimental/paths/tests/tests_limit_sp_multiple.py +119 -0
  229. relationalai/experimental/paths/tests/tests_limit_sp_single.py +104 -0
  230. relationalai/experimental/paths/tests/tests_limit_walks_multiple.py +113 -0
  231. relationalai/experimental/paths/tests/tests_limit_walks_single.py +149 -0
  232. relationalai/experimental/paths/tests/tests_one_sided_ball_repetition_multiple.py +70 -0
  233. relationalai/experimental/paths/tests/tests_one_sided_ball_repetition_single.py +64 -0
  234. relationalai/experimental/paths/tests/tests_one_sided_ball_upto_multiple.py +115 -0
  235. relationalai/experimental/paths/tests/tests_one_sided_ball_upto_single.py +75 -0
  236. relationalai/experimental/paths/tests/tests_single_paths.py +152 -0
  237. relationalai/experimental/paths/tests/tests_single_walks.py +208 -0
  238. relationalai/experimental/paths/tests/tests_single_walks_undirected.py +297 -0
  239. relationalai/experimental/paths/tests/tests_two_sided_balls_repetition_multiple.py +107 -0
  240. relationalai/experimental/paths/tests/tests_two_sided_balls_repetition_single.py +76 -0
  241. relationalai/experimental/paths/tests/tests_two_sided_balls_upto_multiple.py +76 -0
  242. relationalai/experimental/paths/tests/tests_two_sided_balls_upto_single.py +110 -0
  243. relationalai/experimental/paths/tests/tests_usp_nsp_multiple.py +229 -0
  244. relationalai/experimental/paths/tests/tests_usp_nsp_single.py +108 -0
  245. relationalai/experimental/paths/tree_agg.py +168 -0
  246. relationalai/experimental/paths/utilities/iterators.py +27 -0
  247. relationalai/experimental/paths/utilities/prefix_sum.py +91 -0
  248. relationalai/experimental/solvers.py +1087 -0
  249. relationalai/loaders/csv.py +195 -0
  250. relationalai/loaders/loader.py +177 -0
  251. relationalai/loaders/types.py +23 -0
  252. relationalai/rel_emitter.py +373 -0
  253. relationalai/rel_utils.py +185 -0
  254. relationalai/semantics/__init__.py +22 -146
  255. relationalai/semantics/designs/query_builder/identify_by.md +106 -0
  256. relationalai/semantics/devtools/benchmark_lqp.py +535 -0
  257. relationalai/semantics/devtools/compilation_manager.py +294 -0
  258. relationalai/semantics/devtools/extract_lqp.py +110 -0
  259. relationalai/semantics/internal/internal.py +3785 -0
  260. relationalai/semantics/internal/snowflake.py +325 -0
  261. relationalai/semantics/lqp/README.md +34 -0
  262. relationalai/semantics/lqp/builtins.py +16 -0
  263. relationalai/semantics/lqp/compiler.py +22 -0
  264. relationalai/semantics/lqp/constructors.py +68 -0
  265. relationalai/semantics/lqp/executor.py +469 -0
  266. relationalai/semantics/lqp/intrinsics.py +24 -0
  267. relationalai/semantics/lqp/model2lqp.py +877 -0
  268. relationalai/semantics/lqp/passes.py +680 -0
  269. relationalai/semantics/lqp/primitives.py +252 -0
  270. relationalai/semantics/lqp/result_helpers.py +202 -0
  271. relationalai/semantics/lqp/rewrite/annotate_constraints.py +57 -0
  272. relationalai/semantics/lqp/rewrite/cdc.py +216 -0
  273. relationalai/semantics/lqp/rewrite/extract_common.py +338 -0
  274. relationalai/semantics/lqp/rewrite/extract_keys.py +512 -0
  275. relationalai/semantics/lqp/rewrite/function_annotations.py +114 -0
  276. relationalai/semantics/lqp/rewrite/functional_dependencies.py +314 -0
  277. relationalai/semantics/lqp/rewrite/quantify_vars.py +296 -0
  278. relationalai/semantics/lqp/rewrite/splinter.py +76 -0
  279. relationalai/semantics/lqp/types.py +101 -0
  280. relationalai/semantics/lqp/utils.py +160 -0
  281. relationalai/semantics/lqp/validators.py +57 -0
  282. relationalai/semantics/metamodel/__init__.py +40 -6
  283. relationalai/semantics/metamodel/builtins.py +771 -205
  284. relationalai/semantics/metamodel/compiler.py +133 -0
  285. relationalai/semantics/metamodel/dependency.py +862 -0
  286. relationalai/semantics/metamodel/executor.py +61 -0
  287. relationalai/semantics/metamodel/factory.py +287 -0
  288. relationalai/semantics/metamodel/helpers.py +361 -0
  289. relationalai/semantics/metamodel/rewrite/discharge_constraints.py +39 -0
  290. relationalai/semantics/metamodel/rewrite/dnf_union_splitter.py +210 -0
  291. relationalai/semantics/metamodel/rewrite/extract_nested_logicals.py +78 -0
  292. relationalai/semantics/metamodel/rewrite/flatten.py +554 -0
  293. relationalai/semantics/metamodel/rewrite/format_outputs.py +165 -0
  294. relationalai/semantics/metamodel/typer/checker.py +353 -0
  295. relationalai/semantics/metamodel/typer/typer.py +1395 -0
  296. relationalai/semantics/metamodel/util.py +506 -0
  297. relationalai/semantics/reasoners/__init__.py +10 -0
  298. relationalai/semantics/reasoners/graph/README.md +620 -0
  299. relationalai/semantics/reasoners/graph/__init__.py +37 -0
  300. relationalai/semantics/reasoners/graph/core.py +9019 -0
  301. relationalai/semantics/reasoners/graph/design/beyond_demand_transform.md +797 -0
  302. relationalai/semantics/reasoners/graph/tests/README.md +21 -0
  303. relationalai/semantics/reasoners/optimization/__init__.py +68 -0
  304. relationalai/semantics/reasoners/optimization/common.py +88 -0
  305. relationalai/semantics/reasoners/optimization/solvers_dev.py +568 -0
  306. relationalai/semantics/reasoners/optimization/solvers_pb.py +1414 -0
  307. relationalai/semantics/rel/builtins.py +40 -0
  308. relationalai/semantics/rel/compiler.py +989 -0
  309. relationalai/semantics/rel/executor.py +362 -0
  310. relationalai/semantics/rel/rel.py +482 -0
  311. relationalai/semantics/rel/rel_utils.py +276 -0
  312. relationalai/semantics/snowflake/__init__.py +3 -0
  313. relationalai/semantics/sql/compiler.py +2503 -0
  314. relationalai/semantics/sql/executor/duck_db.py +52 -0
  315. relationalai/semantics/sql/executor/result_helpers.py +64 -0
  316. relationalai/semantics/sql/executor/snowflake.py +149 -0
  317. relationalai/semantics/sql/rewrite/denormalize.py +222 -0
  318. relationalai/semantics/sql/rewrite/double_negation.py +49 -0
  319. relationalai/semantics/sql/rewrite/recursive_union.py +127 -0
  320. relationalai/semantics/sql/rewrite/sort_output_query.py +246 -0
  321. relationalai/semantics/sql/sql.py +504 -0
  322. relationalai/semantics/std/__init__.py +40 -60
  323. relationalai/semantics/std/constraints.py +43 -37
  324. relationalai/semantics/std/datetime.py +135 -246
  325. relationalai/semantics/std/decimals.py +52 -45
  326. relationalai/semantics/std/floats.py +5 -13
  327. relationalai/semantics/std/integers.py +11 -26
  328. relationalai/semantics/std/math.py +112 -183
  329. relationalai/semantics/std/pragmas.py +11 -0
  330. relationalai/semantics/std/re.py +62 -80
  331. relationalai/semantics/std/std.py +14 -0
  332. relationalai/semantics/std/strings.py +60 -117
  333. relationalai/semantics/tests/test_snapshot_abstract.py +143 -0
  334. relationalai/semantics/tests/test_snapshot_base.py +9 -0
  335. relationalai/semantics/tests/utils.py +46 -0
  336. relationalai/std/__init__.py +70 -0
  337. relationalai/tools/cli.py +1936 -0
  338. relationalai/tools/cli_controls.py +1826 -0
  339. relationalai/tools/cli_helpers.py +398 -0
  340. relationalai/tools/debugger.py +183 -289
  341. relationalai/tools/debugger_client.py +109 -0
  342. relationalai/tools/debugger_server.py +302 -0
  343. relationalai/tools/dev.py +685 -0
  344. relationalai/tools/notes +7 -0
  345. relationalai/tools/qb_debugger.py +425 -0
  346. relationalai/util/clean_up_databases.py +95 -0
  347. relationalai/util/format.py +106 -48
  348. relationalai/util/list_databases.py +9 -0
  349. relationalai/util/otel_configuration.py +26 -0
  350. relationalai/util/otel_handler.py +484 -0
  351. relationalai/util/snowflake_handler.py +88 -0
  352. relationalai/util/span_format_test.py +43 -0
  353. relationalai/util/span_tracker.py +207 -0
  354. relationalai/util/spans_file_handler.py +72 -0
  355. relationalai/util/tracing_handler.py +34 -0
  356. relationalai-0.13.1.dist-info/METADATA +74 -0
  357. relationalai-0.13.1.dist-info/RECORD +459 -0
  358. relationalai-0.13.1.dist-info/WHEEL +4 -0
  359. relationalai-0.13.1.dist-info/entry_points.txt +3 -0
  360. relationalai-0.13.1.dist-info/licenses/LICENSE +202 -0
  361. relationalai_test_util/__init__.py +4 -0
  362. relationalai_test_util/fixtures.py +233 -0
  363. relationalai_test_util/snapshot.py +252 -0
  364. relationalai_test_util/traceback.py +118 -0
  365. relationalai/config/__init__.py +0 -56
  366. relationalai/config/config.py +0 -289
  367. relationalai/config/config_fields.py +0 -86
  368. relationalai/config/connections/__init__.py +0 -46
  369. relationalai/config/connections/base.py +0 -23
  370. relationalai/config/connections/duckdb.py +0 -29
  371. relationalai/config/connections/snowflake.py +0 -243
  372. relationalai/config/external/__init__.py +0 -17
  373. relationalai/config/external/dbt_converter.py +0 -101
  374. relationalai/config/external/dbt_models.py +0 -93
  375. relationalai/config/external/snowflake_converter.py +0 -41
  376. relationalai/config/external/snowflake_models.py +0 -85
  377. relationalai/config/external/utils.py +0 -19
  378. relationalai/semantics/backends/lqp/annotations.py +0 -11
  379. relationalai/semantics/backends/sql/sql_compiler.py +0 -327
  380. relationalai/semantics/frontend/base.py +0 -1707
  381. relationalai/semantics/frontend/core.py +0 -179
  382. relationalai/semantics/frontend/front_compiler.py +0 -1313
  383. relationalai/semantics/frontend/pprint.py +0 -408
  384. relationalai/semantics/metamodel/metamodel.py +0 -437
  385. relationalai/semantics/metamodel/metamodel_analyzer.py +0 -519
  386. relationalai/semantics/metamodel/metamodel_compiler.py +0 -0
  387. relationalai/semantics/metamodel/pprint.py +0 -412
  388. relationalai/semantics/metamodel/rewriter.py +0 -266
  389. relationalai/semantics/metamodel/typer.py +0 -1378
  390. relationalai/semantics/std/aggregates.py +0 -149
  391. relationalai/semantics/std/common.py +0 -44
  392. relationalai/semantics/std/numbers.py +0 -86
  393. relationalai/shims/executor.py +0 -147
  394. relationalai/shims/helpers.py +0 -126
  395. relationalai/shims/hoister.py +0 -221
  396. relationalai/shims/mm2v0.py +0 -1290
  397. relationalai/tools/cli/__init__.py +0 -6
  398. relationalai/tools/cli/cli.py +0 -90
  399. relationalai/tools/cli/components/__init__.py +0 -5
  400. relationalai/tools/cli/components/progress_reader.py +0 -1524
  401. relationalai/tools/cli/components/utils.py +0 -58
  402. relationalai/tools/cli/config_template.py +0 -45
  403. relationalai/tools/cli/dev.py +0 -19
  404. relationalai/tools/typer_debugger.py +0 -93
  405. relationalai/util/dataclasses.py +0 -43
  406. relationalai/util/docutils.py +0 -40
  407. relationalai/util/error.py +0 -199
  408. relationalai/util/naming.py +0 -145
  409. relationalai/util/python.py +0 -35
  410. relationalai/util/runtime.py +0 -156
  411. relationalai/util/schema.py +0 -197
  412. relationalai/util/source.py +0 -185
  413. relationalai/util/structures.py +0 -163
  414. relationalai/util/tracing.py +0 -261
  415. relationalai-0.13.0.dev0.dist-info/METADATA +0 -46
  416. relationalai-0.13.0.dev0.dist-info/RECORD +0 -488
  417. relationalai-0.13.0.dev0.dist-info/WHEEL +0 -5
  418. relationalai-0.13.0.dev0.dist-info/entry_points.txt +0 -3
  419. relationalai-0.13.0.dev0.dist-info/top_level.txt +0 -2
  420. v0/relationalai/__init__.py +0 -216
  421. v0/relationalai/clients/__init__.py +0 -5
  422. v0/relationalai/clients/azure.py +0 -477
  423. v0/relationalai/clients/client.py +0 -912
  424. v0/relationalai/clients/config.py +0 -673
  425. v0/relationalai/clients/direct_access_client.py +0 -118
  426. v0/relationalai/clients/hash_util.py +0 -31
  427. v0/relationalai/clients/local.py +0 -571
  428. v0/relationalai/clients/profile_polling.py +0 -73
  429. v0/relationalai/clients/result_helpers.py +0 -420
  430. v0/relationalai/clients/snowflake.py +0 -3869
  431. v0/relationalai/clients/types.py +0 -113
  432. v0/relationalai/clients/use_index_poller.py +0 -980
  433. v0/relationalai/clients/util.py +0 -356
  434. v0/relationalai/debugging.py +0 -389
  435. v0/relationalai/dsl.py +0 -1749
  436. v0/relationalai/early_access/builder/__init__.py +0 -30
  437. v0/relationalai/early_access/builder/builder/__init__.py +0 -35
  438. v0/relationalai/early_access/builder/snowflake/__init__.py +0 -12
  439. v0/relationalai/early_access/builder/std/__init__.py +0 -25
  440. v0/relationalai/early_access/builder/std/decimals/__init__.py +0 -12
  441. v0/relationalai/early_access/builder/std/integers/__init__.py +0 -12
  442. v0/relationalai/early_access/builder/std/math/__init__.py +0 -12
  443. v0/relationalai/early_access/builder/std/strings/__init__.py +0 -14
  444. v0/relationalai/early_access/devtools/__init__.py +0 -12
  445. v0/relationalai/early_access/devtools/benchmark_lqp/__init__.py +0 -12
  446. v0/relationalai/early_access/devtools/extract_lqp/__init__.py +0 -12
  447. v0/relationalai/early_access/dsl/adapters/orm/adapter_qb.py +0 -427
  448. v0/relationalai/early_access/dsl/adapters/orm/parser.py +0 -636
  449. v0/relationalai/early_access/dsl/adapters/owl/adapter.py +0 -176
  450. v0/relationalai/early_access/dsl/adapters/owl/parser.py +0 -160
  451. v0/relationalai/early_access/dsl/bindings/common.py +0 -402
  452. v0/relationalai/early_access/dsl/bindings/csv.py +0 -170
  453. v0/relationalai/early_access/dsl/bindings/legacy/binding_models.py +0 -143
  454. v0/relationalai/early_access/dsl/bindings/snowflake.py +0 -64
  455. v0/relationalai/early_access/dsl/codegen/binder.py +0 -411
  456. v0/relationalai/early_access/dsl/codegen/common.py +0 -79
  457. v0/relationalai/early_access/dsl/codegen/helpers.py +0 -23
  458. v0/relationalai/early_access/dsl/codegen/relations.py +0 -700
  459. v0/relationalai/early_access/dsl/codegen/weaver.py +0 -417
  460. v0/relationalai/early_access/dsl/core/builders/__init__.py +0 -47
  461. v0/relationalai/early_access/dsl/core/builders/logic.py +0 -19
  462. v0/relationalai/early_access/dsl/core/builders/scalar_constraint.py +0 -11
  463. v0/relationalai/early_access/dsl/core/constraints/predicate/atomic.py +0 -455
  464. v0/relationalai/early_access/dsl/core/constraints/predicate/universal.py +0 -73
  465. v0/relationalai/early_access/dsl/core/constraints/scalar.py +0 -310
  466. v0/relationalai/early_access/dsl/core/context.py +0 -13
  467. v0/relationalai/early_access/dsl/core/cset.py +0 -132
  468. v0/relationalai/early_access/dsl/core/exprs/__init__.py +0 -116
  469. v0/relationalai/early_access/dsl/core/exprs/relational.py +0 -18
  470. v0/relationalai/early_access/dsl/core/exprs/scalar.py +0 -412
  471. v0/relationalai/early_access/dsl/core/instances.py +0 -44
  472. v0/relationalai/early_access/dsl/core/logic/__init__.py +0 -193
  473. v0/relationalai/early_access/dsl/core/logic/aggregation.py +0 -98
  474. v0/relationalai/early_access/dsl/core/logic/exists.py +0 -223
  475. v0/relationalai/early_access/dsl/core/logic/helper.py +0 -163
  476. v0/relationalai/early_access/dsl/core/namespaces.py +0 -32
  477. v0/relationalai/early_access/dsl/core/relations.py +0 -276
  478. v0/relationalai/early_access/dsl/core/rules.py +0 -112
  479. v0/relationalai/early_access/dsl/core/std/__init__.py +0 -45
  480. v0/relationalai/early_access/dsl/core/temporal/recall.py +0 -6
  481. v0/relationalai/early_access/dsl/core/types/__init__.py +0 -270
  482. v0/relationalai/early_access/dsl/core/types/concepts.py +0 -128
  483. v0/relationalai/early_access/dsl/core/types/constrained/__init__.py +0 -267
  484. v0/relationalai/early_access/dsl/core/types/constrained/nominal.py +0 -143
  485. v0/relationalai/early_access/dsl/core/types/constrained/subtype.py +0 -124
  486. v0/relationalai/early_access/dsl/core/types/standard.py +0 -92
  487. v0/relationalai/early_access/dsl/core/types/unconstrained.py +0 -50
  488. v0/relationalai/early_access/dsl/core/types/variables.py +0 -203
  489. v0/relationalai/early_access/dsl/ir/compiler.py +0 -318
  490. v0/relationalai/early_access/dsl/ir/executor.py +0 -260
  491. v0/relationalai/early_access/dsl/ontologies/constraints.py +0 -88
  492. v0/relationalai/early_access/dsl/ontologies/export.py +0 -30
  493. v0/relationalai/early_access/dsl/ontologies/models.py +0 -453
  494. v0/relationalai/early_access/dsl/ontologies/python_printer.py +0 -303
  495. v0/relationalai/early_access/dsl/ontologies/readings.py +0 -60
  496. v0/relationalai/early_access/dsl/ontologies/relationships.py +0 -322
  497. v0/relationalai/early_access/dsl/ontologies/roles.py +0 -87
  498. v0/relationalai/early_access/dsl/ontologies/subtyping.py +0 -55
  499. v0/relationalai/early_access/dsl/orm/constraints.py +0 -438
  500. v0/relationalai/early_access/dsl/orm/measures/dimensions.py +0 -200
  501. v0/relationalai/early_access/dsl/orm/measures/initializer.py +0 -16
  502. v0/relationalai/early_access/dsl/orm/measures/measure_rules.py +0 -275
  503. v0/relationalai/early_access/dsl/orm/measures/measures.py +0 -299
  504. v0/relationalai/early_access/dsl/orm/measures/role_exprs.py +0 -268
  505. v0/relationalai/early_access/dsl/orm/models.py +0 -256
  506. v0/relationalai/early_access/dsl/orm/object_oriented_printer.py +0 -344
  507. v0/relationalai/early_access/dsl/orm/printer.py +0 -469
  508. v0/relationalai/early_access/dsl/orm/reasoners.py +0 -480
  509. v0/relationalai/early_access/dsl/orm/relations.py +0 -19
  510. v0/relationalai/early_access/dsl/orm/relationships.py +0 -251
  511. v0/relationalai/early_access/dsl/orm/types.py +0 -42
  512. v0/relationalai/early_access/dsl/orm/utils.py +0 -79
  513. v0/relationalai/early_access/dsl/orm/verb.py +0 -204
  514. v0/relationalai/early_access/dsl/physical_metadata/tables.py +0 -133
  515. v0/relationalai/early_access/dsl/relations.py +0 -170
  516. v0/relationalai/early_access/dsl/rulesets.py +0 -69
  517. v0/relationalai/early_access/dsl/schemas/__init__.py +0 -450
  518. v0/relationalai/early_access/dsl/schemas/builder.py +0 -48
  519. v0/relationalai/early_access/dsl/schemas/comp_names.py +0 -51
  520. v0/relationalai/early_access/dsl/schemas/components.py +0 -203
  521. v0/relationalai/early_access/dsl/schemas/contexts.py +0 -156
  522. v0/relationalai/early_access/dsl/schemas/exprs.py +0 -89
  523. v0/relationalai/early_access/dsl/schemas/fragments.py +0 -464
  524. v0/relationalai/early_access/dsl/serialization.py +0 -79
  525. v0/relationalai/early_access/dsl/serialize/exporter.py +0 -163
  526. v0/relationalai/early_access/dsl/snow/api.py +0 -104
  527. v0/relationalai/early_access/dsl/snow/common.py +0 -76
  528. v0/relationalai/early_access/dsl/state_mgmt/__init__.py +0 -129
  529. v0/relationalai/early_access/dsl/state_mgmt/state_charts.py +0 -125
  530. v0/relationalai/early_access/dsl/state_mgmt/transitions.py +0 -130
  531. v0/relationalai/early_access/dsl/types/__init__.py +0 -40
  532. v0/relationalai/early_access/dsl/types/concepts.py +0 -12
  533. v0/relationalai/early_access/dsl/types/entities.py +0 -135
  534. v0/relationalai/early_access/dsl/types/values.py +0 -17
  535. v0/relationalai/early_access/dsl/utils.py +0 -102
  536. v0/relationalai/early_access/graphs/__init__.py +0 -13
  537. v0/relationalai/early_access/lqp/__init__.py +0 -12
  538. v0/relationalai/early_access/lqp/compiler/__init__.py +0 -12
  539. v0/relationalai/early_access/lqp/constructors/__init__.py +0 -18
  540. v0/relationalai/early_access/lqp/executor/__init__.py +0 -12
  541. v0/relationalai/early_access/lqp/ir/__init__.py +0 -12
  542. v0/relationalai/early_access/lqp/passes/__init__.py +0 -12
  543. v0/relationalai/early_access/lqp/pragmas/__init__.py +0 -12
  544. v0/relationalai/early_access/lqp/primitives/__init__.py +0 -12
  545. v0/relationalai/early_access/lqp/types/__init__.py +0 -12
  546. v0/relationalai/early_access/lqp/utils/__init__.py +0 -12
  547. v0/relationalai/early_access/lqp/validators/__init__.py +0 -12
  548. v0/relationalai/early_access/metamodel/__init__.py +0 -58
  549. v0/relationalai/early_access/metamodel/builtins/__init__.py +0 -12
  550. v0/relationalai/early_access/metamodel/compiler/__init__.py +0 -12
  551. v0/relationalai/early_access/metamodel/dependency/__init__.py +0 -12
  552. v0/relationalai/early_access/metamodel/factory/__init__.py +0 -17
  553. v0/relationalai/early_access/metamodel/helpers/__init__.py +0 -12
  554. v0/relationalai/early_access/metamodel/ir/__init__.py +0 -14
  555. v0/relationalai/early_access/metamodel/rewrite/__init__.py +0 -7
  556. v0/relationalai/early_access/metamodel/typer/__init__.py +0 -3
  557. v0/relationalai/early_access/metamodel/typer/typer/__init__.py +0 -12
  558. v0/relationalai/early_access/metamodel/types/__init__.py +0 -15
  559. v0/relationalai/early_access/metamodel/util/__init__.py +0 -15
  560. v0/relationalai/early_access/metamodel/visitor/__init__.py +0 -12
  561. v0/relationalai/early_access/rel/__init__.py +0 -12
  562. v0/relationalai/early_access/rel/executor/__init__.py +0 -12
  563. v0/relationalai/early_access/rel/rel_utils/__init__.py +0 -12
  564. v0/relationalai/early_access/rel/rewrite/__init__.py +0 -7
  565. v0/relationalai/early_access/solvers/__init__.py +0 -19
  566. v0/relationalai/early_access/sql/__init__.py +0 -11
  567. v0/relationalai/early_access/sql/executor/__init__.py +0 -3
  568. v0/relationalai/early_access/sql/rewrite/__init__.py +0 -3
  569. v0/relationalai/early_access/tests/logging/__init__.py +0 -12
  570. v0/relationalai/early_access/tests/test_snapshot_base/__init__.py +0 -12
  571. v0/relationalai/early_access/tests/utils/__init__.py +0 -12
  572. v0/relationalai/environments/__init__.py +0 -35
  573. v0/relationalai/environments/base.py +0 -381
  574. v0/relationalai/environments/colab.py +0 -14
  575. v0/relationalai/environments/generic.py +0 -71
  576. v0/relationalai/environments/ipython.py +0 -68
  577. v0/relationalai/environments/jupyter.py +0 -9
  578. v0/relationalai/environments/snowbook.py +0 -169
  579. v0/relationalai/errors.py +0 -2455
  580. v0/relationalai/experimental/SF.py +0 -38
  581. v0/relationalai/experimental/inspect.py +0 -47
  582. v0/relationalai/experimental/pathfinder/__init__.py +0 -158
  583. v0/relationalai/experimental/pathfinder/api.py +0 -160
  584. v0/relationalai/experimental/pathfinder/automaton.py +0 -584
  585. v0/relationalai/experimental/pathfinder/bridge.py +0 -226
  586. v0/relationalai/experimental/pathfinder/compiler.py +0 -416
  587. v0/relationalai/experimental/pathfinder/datalog.py +0 -214
  588. v0/relationalai/experimental/pathfinder/diagnostics.py +0 -56
  589. v0/relationalai/experimental/pathfinder/filter.py +0 -236
  590. v0/relationalai/experimental/pathfinder/glushkov.py +0 -439
  591. v0/relationalai/experimental/pathfinder/options.py +0 -265
  592. v0/relationalai/experimental/pathfinder/rpq.py +0 -344
  593. v0/relationalai/experimental/pathfinder/transition.py +0 -200
  594. v0/relationalai/experimental/pathfinder/utils.py +0 -26
  595. v0/relationalai/experimental/paths/api.py +0 -143
  596. v0/relationalai/experimental/paths/benchmarks/grid_graph.py +0 -37
  597. v0/relationalai/experimental/paths/examples/basic_example.py +0 -40
  598. v0/relationalai/experimental/paths/examples/minimal_engine_warmup.py +0 -3
  599. v0/relationalai/experimental/paths/examples/movie_example.py +0 -77
  600. v0/relationalai/experimental/paths/examples/paths_benchmark.py +0 -115
  601. v0/relationalai/experimental/paths/examples/paths_example.py +0 -116
  602. v0/relationalai/experimental/paths/examples/pattern_to_automaton.py +0 -28
  603. v0/relationalai/experimental/paths/find_paths_via_automaton.py +0 -85
  604. v0/relationalai/experimental/paths/graph.py +0 -185
  605. v0/relationalai/experimental/paths/path_algorithms/find_paths.py +0 -280
  606. v0/relationalai/experimental/paths/path_algorithms/one_sided_ball_repetition.py +0 -26
  607. v0/relationalai/experimental/paths/path_algorithms/one_sided_ball_upto.py +0 -111
  608. v0/relationalai/experimental/paths/path_algorithms/single.py +0 -59
  609. v0/relationalai/experimental/paths/path_algorithms/two_sided_balls_repetition.py +0 -39
  610. v0/relationalai/experimental/paths/path_algorithms/two_sided_balls_upto.py +0 -103
  611. v0/relationalai/experimental/paths/path_algorithms/usp-old.py +0 -130
  612. v0/relationalai/experimental/paths/path_algorithms/usp-tuple.py +0 -183
  613. v0/relationalai/experimental/paths/path_algorithms/usp.py +0 -150
  614. v0/relationalai/experimental/paths/product_graph.py +0 -93
  615. v0/relationalai/experimental/paths/rpq/automaton.py +0 -584
  616. v0/relationalai/experimental/paths/rpq/diagnostics.py +0 -56
  617. v0/relationalai/experimental/paths/rpq/rpq.py +0 -378
  618. v0/relationalai/experimental/paths/tests/tests_limit_sp_max_length.py +0 -90
  619. v0/relationalai/experimental/paths/tests/tests_limit_sp_multiple.py +0 -119
  620. v0/relationalai/experimental/paths/tests/tests_limit_sp_single.py +0 -104
  621. v0/relationalai/experimental/paths/tests/tests_limit_walks_multiple.py +0 -113
  622. v0/relationalai/experimental/paths/tests/tests_limit_walks_single.py +0 -149
  623. v0/relationalai/experimental/paths/tests/tests_one_sided_ball_repetition_multiple.py +0 -70
  624. v0/relationalai/experimental/paths/tests/tests_one_sided_ball_repetition_single.py +0 -64
  625. v0/relationalai/experimental/paths/tests/tests_one_sided_ball_upto_multiple.py +0 -115
  626. v0/relationalai/experimental/paths/tests/tests_one_sided_ball_upto_single.py +0 -75
  627. v0/relationalai/experimental/paths/tests/tests_single_paths.py +0 -152
  628. v0/relationalai/experimental/paths/tests/tests_single_walks.py +0 -208
  629. v0/relationalai/experimental/paths/tests/tests_single_walks_undirected.py +0 -297
  630. v0/relationalai/experimental/paths/tests/tests_two_sided_balls_repetition_multiple.py +0 -107
  631. v0/relationalai/experimental/paths/tests/tests_two_sided_balls_repetition_single.py +0 -76
  632. v0/relationalai/experimental/paths/tests/tests_two_sided_balls_upto_multiple.py +0 -76
  633. v0/relationalai/experimental/paths/tests/tests_two_sided_balls_upto_single.py +0 -110
  634. v0/relationalai/experimental/paths/tests/tests_usp_nsp_multiple.py +0 -229
  635. v0/relationalai/experimental/paths/tests/tests_usp_nsp_single.py +0 -108
  636. v0/relationalai/experimental/paths/tree_agg.py +0 -168
  637. v0/relationalai/experimental/paths/utilities/iterators.py +0 -27
  638. v0/relationalai/experimental/paths/utilities/prefix_sum.py +0 -91
  639. v0/relationalai/experimental/solvers.py +0 -1087
  640. v0/relationalai/loaders/csv.py +0 -195
  641. v0/relationalai/loaders/loader.py +0 -177
  642. v0/relationalai/loaders/types.py +0 -23
  643. v0/relationalai/rel_emitter.py +0 -373
  644. v0/relationalai/rel_utils.py +0 -185
  645. v0/relationalai/semantics/__init__.py +0 -29
  646. v0/relationalai/semantics/devtools/benchmark_lqp.py +0 -536
  647. v0/relationalai/semantics/devtools/compilation_manager.py +0 -294
  648. v0/relationalai/semantics/devtools/extract_lqp.py +0 -110
  649. v0/relationalai/semantics/internal/internal.py +0 -3785
  650. v0/relationalai/semantics/internal/snowflake.py +0 -324
  651. v0/relationalai/semantics/lqp/builtins.py +0 -16
  652. v0/relationalai/semantics/lqp/compiler.py +0 -22
  653. v0/relationalai/semantics/lqp/constructors.py +0 -68
  654. v0/relationalai/semantics/lqp/executor.py +0 -469
  655. v0/relationalai/semantics/lqp/intrinsics.py +0 -24
  656. v0/relationalai/semantics/lqp/model2lqp.py +0 -839
  657. v0/relationalai/semantics/lqp/passes.py +0 -680
  658. v0/relationalai/semantics/lqp/primitives.py +0 -252
  659. v0/relationalai/semantics/lqp/result_helpers.py +0 -202
  660. v0/relationalai/semantics/lqp/rewrite/annotate_constraints.py +0 -57
  661. v0/relationalai/semantics/lqp/rewrite/cdc.py +0 -216
  662. v0/relationalai/semantics/lqp/rewrite/extract_common.py +0 -338
  663. v0/relationalai/semantics/lqp/rewrite/extract_keys.py +0 -449
  664. v0/relationalai/semantics/lqp/rewrite/function_annotations.py +0 -114
  665. v0/relationalai/semantics/lqp/rewrite/functional_dependencies.py +0 -314
  666. v0/relationalai/semantics/lqp/rewrite/quantify_vars.py +0 -296
  667. v0/relationalai/semantics/lqp/rewrite/splinter.py +0 -76
  668. v0/relationalai/semantics/lqp/types.py +0 -101
  669. v0/relationalai/semantics/lqp/utils.py +0 -160
  670. v0/relationalai/semantics/lqp/validators.py +0 -57
  671. v0/relationalai/semantics/metamodel/__init__.py +0 -40
  672. v0/relationalai/semantics/metamodel/builtins.py +0 -774
  673. v0/relationalai/semantics/metamodel/compiler.py +0 -133
  674. v0/relationalai/semantics/metamodel/dependency.py +0 -862
  675. v0/relationalai/semantics/metamodel/executor.py +0 -61
  676. v0/relationalai/semantics/metamodel/factory.py +0 -287
  677. v0/relationalai/semantics/metamodel/helpers.py +0 -361
  678. v0/relationalai/semantics/metamodel/rewrite/discharge_constraints.py +0 -39
  679. v0/relationalai/semantics/metamodel/rewrite/dnf_union_splitter.py +0 -210
  680. v0/relationalai/semantics/metamodel/rewrite/extract_nested_logicals.py +0 -78
  681. v0/relationalai/semantics/metamodel/rewrite/flatten.py +0 -549
  682. v0/relationalai/semantics/metamodel/rewrite/format_outputs.py +0 -165
  683. v0/relationalai/semantics/metamodel/typer/checker.py +0 -353
  684. v0/relationalai/semantics/metamodel/typer/typer.py +0 -1395
  685. v0/relationalai/semantics/metamodel/util.py +0 -505
  686. v0/relationalai/semantics/reasoners/__init__.py +0 -10
  687. v0/relationalai/semantics/reasoners/graph/__init__.py +0 -37
  688. v0/relationalai/semantics/reasoners/graph/core.py +0 -9020
  689. v0/relationalai/semantics/reasoners/optimization/__init__.py +0 -68
  690. v0/relationalai/semantics/reasoners/optimization/common.py +0 -88
  691. v0/relationalai/semantics/reasoners/optimization/solvers_dev.py +0 -568
  692. v0/relationalai/semantics/reasoners/optimization/solvers_pb.py +0 -1163
  693. v0/relationalai/semantics/rel/builtins.py +0 -40
  694. v0/relationalai/semantics/rel/compiler.py +0 -989
  695. v0/relationalai/semantics/rel/executor.py +0 -359
  696. v0/relationalai/semantics/rel/rel.py +0 -482
  697. v0/relationalai/semantics/rel/rel_utils.py +0 -276
  698. v0/relationalai/semantics/snowflake/__init__.py +0 -3
  699. v0/relationalai/semantics/sql/compiler.py +0 -2503
  700. v0/relationalai/semantics/sql/executor/duck_db.py +0 -52
  701. v0/relationalai/semantics/sql/executor/result_helpers.py +0 -64
  702. v0/relationalai/semantics/sql/executor/snowflake.py +0 -145
  703. v0/relationalai/semantics/sql/rewrite/denormalize.py +0 -222
  704. v0/relationalai/semantics/sql/rewrite/double_negation.py +0 -49
  705. v0/relationalai/semantics/sql/rewrite/recursive_union.py +0 -127
  706. v0/relationalai/semantics/sql/rewrite/sort_output_query.py +0 -246
  707. v0/relationalai/semantics/sql/sql.py +0 -504
  708. v0/relationalai/semantics/std/__init__.py +0 -54
  709. v0/relationalai/semantics/std/constraints.py +0 -43
  710. v0/relationalai/semantics/std/datetime.py +0 -363
  711. v0/relationalai/semantics/std/decimals.py +0 -62
  712. v0/relationalai/semantics/std/floats.py +0 -7
  713. v0/relationalai/semantics/std/integers.py +0 -22
  714. v0/relationalai/semantics/std/math.py +0 -141
  715. v0/relationalai/semantics/std/pragmas.py +0 -11
  716. v0/relationalai/semantics/std/re.py +0 -83
  717. v0/relationalai/semantics/std/std.py +0 -14
  718. v0/relationalai/semantics/std/strings.py +0 -63
  719. v0/relationalai/semantics/tests/__init__.py +0 -0
  720. v0/relationalai/semantics/tests/test_snapshot_abstract.py +0 -143
  721. v0/relationalai/semantics/tests/test_snapshot_base.py +0 -9
  722. v0/relationalai/semantics/tests/utils.py +0 -46
  723. v0/relationalai/std/__init__.py +0 -70
  724. v0/relationalai/tools/__init__.py +0 -0
  725. v0/relationalai/tools/cli.py +0 -1940
  726. v0/relationalai/tools/cli_controls.py +0 -1826
  727. v0/relationalai/tools/cli_helpers.py +0 -390
  728. v0/relationalai/tools/debugger.py +0 -183
  729. v0/relationalai/tools/debugger_client.py +0 -109
  730. v0/relationalai/tools/debugger_server.py +0 -302
  731. v0/relationalai/tools/dev.py +0 -685
  732. v0/relationalai/tools/qb_debugger.py +0 -425
  733. v0/relationalai/util/clean_up_databases.py +0 -95
  734. v0/relationalai/util/format.py +0 -123
  735. v0/relationalai/util/list_databases.py +0 -9
  736. v0/relationalai/util/otel_configuration.py +0 -25
  737. v0/relationalai/util/otel_handler.py +0 -484
  738. v0/relationalai/util/snowflake_handler.py +0 -88
  739. v0/relationalai/util/span_format_test.py +0 -43
  740. v0/relationalai/util/span_tracker.py +0 -207
  741. v0/relationalai/util/spans_file_handler.py +0 -72
  742. v0/relationalai/util/tracing_handler.py +0 -34
  743. /relationalai/{semantics/frontend → analysis}/__init__.py +0 -0
  744. {v0/relationalai → relationalai}/analysis/mechanistic.py +0 -0
  745. {v0/relationalai → relationalai}/analysis/whynot.py +0 -0
  746. /relationalai/{shims → auth}/__init__.py +0 -0
  747. {v0/relationalai → relationalai}/auth/jwt_generator.py +0 -0
  748. {v0/relationalai → relationalai}/auth/oauth_callback_server.py +0 -0
  749. {v0/relationalai → relationalai}/auth/token_handler.py +0 -0
  750. {v0/relationalai → relationalai}/auth/util.py +0 -0
  751. {v0/relationalai/clients → relationalai/clients/resources/snowflake}/cache_store.py +0 -0
  752. {v0/relationalai → relationalai}/compiler.py +0 -0
  753. {v0/relationalai → relationalai}/dependencies.py +0 -0
  754. {v0/relationalai → relationalai}/docutils.py +0 -0
  755. {v0/relationalai/analysis → relationalai/early_access}/__init__.py +0 -0
  756. {v0/relationalai → relationalai}/early_access/dsl/__init__.py +0 -0
  757. {v0/relationalai/auth → relationalai/early_access/dsl/adapters}/__init__.py +0 -0
  758. {v0/relationalai/early_access → relationalai/early_access/dsl/adapters/orm}/__init__.py +0 -0
  759. {v0/relationalai → relationalai}/early_access/dsl/adapters/orm/model.py +0 -0
  760. {v0/relationalai/early_access/dsl/adapters → relationalai/early_access/dsl/adapters/owl}/__init__.py +0 -0
  761. {v0/relationalai → relationalai}/early_access/dsl/adapters/owl/model.py +0 -0
  762. {v0/relationalai/early_access/dsl/adapters/orm → relationalai/early_access/dsl/bindings}/__init__.py +0 -0
  763. {v0/relationalai/early_access/dsl/adapters/owl → relationalai/early_access/dsl/bindings/legacy}/__init__.py +0 -0
  764. {v0/relationalai/early_access/dsl/bindings → relationalai/early_access/dsl/codegen}/__init__.py +0 -0
  765. {v0/relationalai → relationalai}/early_access/dsl/constants.py +0 -0
  766. {v0/relationalai → relationalai}/early_access/dsl/core/__init__.py +0 -0
  767. {v0/relationalai → relationalai}/early_access/dsl/core/constraints/__init__.py +0 -0
  768. {v0/relationalai → relationalai}/early_access/dsl/core/constraints/predicate/__init__.py +0 -0
  769. {v0/relationalai → relationalai}/early_access/dsl/core/stack.py +0 -0
  770. {v0/relationalai/early_access/dsl/bindings/legacy → relationalai/early_access/dsl/core/temporal}/__init__.py +0 -0
  771. {v0/relationalai → relationalai}/early_access/dsl/core/utils.py +0 -0
  772. {v0/relationalai/early_access/dsl/codegen → relationalai/early_access/dsl/ir}/__init__.py +0 -0
  773. {v0/relationalai/early_access/dsl/core/temporal → relationalai/early_access/dsl/ontologies}/__init__.py +0 -0
  774. {v0/relationalai → relationalai}/early_access/dsl/ontologies/raw_source.py +0 -0
  775. {v0/relationalai/early_access/dsl/ir → relationalai/early_access/dsl/orm}/__init__.py +0 -0
  776. {v0/relationalai/early_access/dsl/ontologies → relationalai/early_access/dsl/orm/measures}/__init__.py +0 -0
  777. {v0/relationalai → relationalai}/early_access/dsl/orm/reasoner_errors.py +0 -0
  778. {v0/relationalai/early_access/dsl/orm → relationalai/early_access/dsl/physical_metadata}/__init__.py +0 -0
  779. {v0/relationalai/early_access/dsl/orm/measures → relationalai/early_access/dsl/serialize}/__init__.py +0 -0
  780. {v0/relationalai → relationalai}/early_access/dsl/serialize/binding_model.py +0 -0
  781. {v0/relationalai → relationalai}/early_access/dsl/serialize/model.py +0 -0
  782. {v0/relationalai/early_access/dsl/physical_metadata → relationalai/early_access/dsl/snow}/__init__.py +0 -0
  783. {v0/relationalai → relationalai}/early_access/tests/__init__.py +0 -0
  784. {v0/relationalai → relationalai}/environments/ci.py +0 -0
  785. {v0/relationalai → relationalai}/environments/hex.py +0 -0
  786. {v0/relationalai → relationalai}/environments/terminal.py +0 -0
  787. {v0/relationalai → relationalai}/experimental/__init__.py +0 -0
  788. {v0/relationalai → relationalai}/experimental/graphs.py +0 -0
  789. {v0/relationalai → relationalai}/experimental/paths/__init__.py +0 -0
  790. {v0/relationalai → relationalai}/experimental/paths/benchmarks/__init__.py +0 -0
  791. {v0/relationalai → relationalai}/experimental/paths/path_algorithms/__init__.py +0 -0
  792. {v0/relationalai → relationalai}/experimental/paths/rpq/__init__.py +0 -0
  793. {v0/relationalai → relationalai}/experimental/paths/rpq/filter.py +0 -0
  794. {v0/relationalai → relationalai}/experimental/paths/rpq/glushkov.py +0 -0
  795. {v0/relationalai → relationalai}/experimental/paths/rpq/transition.py +0 -0
  796. {v0/relationalai → relationalai}/experimental/paths/utilities/__init__.py +0 -0
  797. {v0/relationalai → relationalai}/experimental/paths/utilities/utilities.py +0 -0
  798. {v0/relationalai/early_access/dsl/serialize → relationalai/loaders}/__init__.py +0 -0
  799. {v0/relationalai → relationalai}/metagen.py +0 -0
  800. {v0/relationalai → relationalai}/metamodel.py +0 -0
  801. {v0/relationalai → relationalai}/rel.py +0 -0
  802. {v0/relationalai → relationalai}/semantics/devtools/__init__.py +0 -0
  803. {v0/relationalai → relationalai}/semantics/internal/__init__.py +0 -0
  804. {v0/relationalai → relationalai}/semantics/internal/annotations.py +0 -0
  805. {v0/relationalai → relationalai}/semantics/lqp/__init__.py +0 -0
  806. {v0/relationalai → relationalai}/semantics/lqp/ir.py +0 -0
  807. {v0/relationalai → relationalai}/semantics/lqp/pragmas.py +0 -0
  808. {v0/relationalai → relationalai}/semantics/lqp/rewrite/__init__.py +0 -0
  809. {v0/relationalai → relationalai}/semantics/metamodel/dataflow.py +0 -0
  810. {v0/relationalai → relationalai}/semantics/metamodel/ir.py +0 -0
  811. {v0/relationalai → relationalai}/semantics/metamodel/rewrite/__init__.py +0 -0
  812. {v0/relationalai → relationalai}/semantics/metamodel/typer/__init__.py +0 -0
  813. {v0/relationalai → relationalai}/semantics/metamodel/types.py +0 -0
  814. {v0/relationalai → relationalai}/semantics/metamodel/visitor.py +0 -0
  815. {v0/relationalai → relationalai}/semantics/reasoners/experimental/__init__.py +0 -0
  816. {v0/relationalai → relationalai}/semantics/rel/__init__.py +0 -0
  817. {v0/relationalai → relationalai}/semantics/sql/__init__.py +0 -0
  818. {v0/relationalai → relationalai}/semantics/sql/executor/__init__.py +0 -0
  819. {v0/relationalai → relationalai}/semantics/sql/rewrite/__init__.py +0 -0
  820. {v0/relationalai/early_access/dsl/snow → relationalai/semantics/tests}/__init__.py +0 -0
  821. {v0/relationalai → relationalai}/semantics/tests/logging.py +0 -0
  822. {v0/relationalai → relationalai}/std/aggregates.py +0 -0
  823. {v0/relationalai → relationalai}/std/dates.py +0 -0
  824. {v0/relationalai → relationalai}/std/graphs.py +0 -0
  825. {v0/relationalai → relationalai}/std/inspect.py +0 -0
  826. {v0/relationalai → relationalai}/std/math.py +0 -0
  827. {v0/relationalai → relationalai}/std/re.py +0 -0
  828. {v0/relationalai → relationalai}/std/strings.py +0 -0
  829. {v0/relationalai/loaders → relationalai/tools}/__init__.py +0 -0
  830. {v0/relationalai → relationalai}/tools/cleanup_snapshots.py +0 -0
  831. {v0/relationalai → relationalai}/tools/constants.py +0 -0
  832. {v0/relationalai → relationalai}/tools/query_utils.py +0 -0
  833. {v0/relationalai → relationalai}/tools/snapshot_viewer.py +0 -0
  834. {v0/relationalai → relationalai}/util/__init__.py +0 -0
  835. {v0/relationalai → relationalai}/util/constants.py +0 -0
  836. {v0/relationalai → relationalai}/util/graph.py +0 -0
  837. {v0/relationalai → relationalai}/util/timeout.py +0 -0
@@ -1,1524 +0,0 @@
1
- #pyright: reportPrivateImportUsage=false
2
- from __future__ import annotations
3
-
4
- # Standard library imports
5
- import re
6
- import threading
7
- import time
8
- import textwrap
9
- from collections.abc import Callable, Iterable
10
- from dataclasses import dataclass, field
11
- from datetime import datetime
12
- from enum import Enum
13
- from io import StringIO
14
- from pathlib import Path
15
- from typing import TYPE_CHECKING, Iterator, overload
16
- import uuid
17
-
18
-
19
- if TYPE_CHECKING:
20
- from rich.console import ConsoleOptions, RenderableType
21
- from rich.progress import Progress, TaskID
22
-
23
- # Third-party imports
24
- import rich
25
- from rich.console import Console, Group
26
- from rich.live import Live
27
- from rich.text import Text
28
-
29
- # Local imports
30
- from relationalai.util.format import format_duration
31
- from .utils import normalize_tasks
32
-
33
-
34
- #--------------------------------------------------
35
- # Constants
36
- #--------------------------------------------------
37
-
38
- # Display symbols
39
- CHECK_MARK = "✓"
40
- FAIL_ICON = "ⓧ"
41
-
42
- # Spinner animation frames for running tasks
43
- SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
44
-
45
- # Terminal display constants
46
- DEFAULT_TERMINAL_WIDTH = 80
47
-
48
- # Animation constants
49
- ANIMATION_INTERVAL = 0.1 # Seconds between animation frame updates (~10 fps)
50
- REFRESH_RATE = 10 # Rich Live refresh rate (frames per second)
51
- MIN_DETAIL_WIDTH = 20 # Minimum width for task details text wrapping
52
-
53
-
54
- # ============================================================================
55
- # Task Status Enum
56
- # ============================================================================
57
-
58
- class TaskStatus(str, Enum):
59
- """Enumeration of valid task statuses.
60
-
61
- Using str as the base class allows the enum values to be used as strings
62
- in comparisons and string operations, while providing type safety and
63
- preventing typos.
64
- """
65
- RUNNING = "RUNNING"
66
- COMPLETED = "COMPLETED"
67
- FAILED = "FAILED"
68
-
69
-
70
- # ============================================================================
71
- # Task Class
72
- # ============================================================================
73
-
74
- class _RenderableWrapper:
75
- """Wrapper to make a callable renderable for Rich's Live.
76
-
77
- Rich's ``Live`` container expects a renderable object that implements
78
- ``__rich_console__`` and returns fresh content each time the live display
79
- refreshes. ``ProgressReader`` generates its layout on the fly via a
80
- callable, so we wrap that callable in this helper to bridge the protocol
81
- gap: ``Live`` sees a renderable, but internally we still recompute the
82
- layout lazily on every refresh.
83
- """
84
-
85
- def __init__(self, callable_fn: Callable[[], "RenderableType"]) -> None:
86
- """Initialize the renderable wrapper.
87
-
88
- Args:
89
- callable_fn: A callable that returns a Rich renderable object
90
- """
91
- self._callable = callable_fn
92
-
93
- def __rich_console__(
94
- self, console: Console, options: "ConsoleOptions"
95
- ) -> Iterator["RenderableType"]:
96
- """Rich protocol - yield the result of calling the callable."""
97
- result = self._callable()
98
- # Result should always be a Group, which implements __rich_console__
99
- yield from result.__rich_console__(console, options)
100
-
101
- @dataclass(frozen=False)
102
- class Task:
103
- """Represents a single task with its status and optional error.
104
-
105
- Note: Task objects are mutable only through ProgressReader APIs. When using
106
- ProgressReader from multiple threads, avoid modifying Task objects directly
107
- from different threads concurrently. Use ProgressReader helpers
108
- (complete_task, fail_task, update_task_status, update_task_details, etc.) for
109
- thread-safe updates and to ensure the UI refreshes."""
110
-
111
- id: str = field(default_factory=lambda: str(uuid.uuid4()))
112
- text: str = ""
113
- details: str = ""
114
- status: TaskStatus = field(default=TaskStatus.RUNNING)
115
- error: str = ""
116
- _allow_direct_mutation: bool = field(default=True, init=False, repr=False, compare=False)
117
-
118
- _PROTECTED_FIELDS = {"status", "details", "error"}
119
-
120
- def __setattr__(self, name: str, value: object) -> None:
121
- if name in Task._PROTECTED_FIELDS:
122
- try:
123
- allow = object.__getattribute__(self, "_allow_direct_mutation")
124
- except AttributeError:
125
- allow = True
126
- if not allow:
127
- raise AttributeError(
128
- "Task attributes are managed by ProgressReader. Use its public API to make changes."
129
- )
130
- object.__setattr__(self, name, value)
131
-
132
- def __post_init__(self):
133
- """Validate status values and ensure task_id."""
134
- # Validate that id is a non-empty string if explicitly provided
135
- # (If not provided, default_factory generates a UUID automatically)
136
- if not isinstance(self.id, str):
137
- raise ValueError(
138
- f"Task id must be a string, got {type(self.id).__name__}: {self.id!r}. "
139
- "Either provide a non-empty string id or omit it to auto-generate one."
140
- )
141
- if not self.id:
142
- raise ValueError(
143
- f"Task id cannot be empty. Got: {self.id!r}. "
144
- "Either provide a non-empty string id or omit it to auto-generate one."
145
- )
146
- # Validate status is a TaskStatus enum value
147
- if not isinstance(self.status, TaskStatus):
148
- # Allow string values for backward compatibility, but convert to enum
149
- try:
150
- self.status = TaskStatus(self.status)
151
- except ValueError:
152
- raise ValueError(
153
- f"Invalid status: {self.status}. Must be one of: "
154
- f"{', '.join(s.value for s in TaskStatus)}"
155
- )
156
- object.__setattr__(self, "_allow_direct_mutation", False)
157
-
158
- def __hash__(self) -> int:
159
- """Make Task hashable for use in sets.
160
-
161
- Uses task ID for hashing, ensuring that tasks with the same ID
162
- have the same hash value (required for consistent behavior in sets/dicts).
163
- """
164
- return hash(self.id)
165
-
166
- def __eq__(self, other: object) -> bool:
167
- """Equality comparison based on task ID.
168
-
169
- Two Task objects are considered equal if they have the same ID.
170
- This is more reliable than identity-based comparison since Task IDs
171
- are unique identifiers within a registry.
172
- """
173
- if not isinstance(other, Task):
174
- return False
175
- return self.id == other.id
176
-
177
- @dataclass
178
- class _TimingTracker:
179
- enabled: bool
180
- task_start_times: dict[Task, float] = field(default_factory=dict)
181
- task_end_times: dict[Task, float] = field(default_factory=dict)
182
- run_start: float | None = None
183
- run_end: float | None = None
184
- summary_printed: bool = False
185
-
186
- def track_initial_tasks(self, tasks: Iterable[Task]) -> None:
187
- if not self.enabled:
188
- return
189
- now = time.time()
190
- for task in tasks:
191
- self.task_start_times.setdefault(task, now)
192
- if task.status in (TaskStatus.COMPLETED, TaskStatus.FAILED):
193
- self.task_end_times.setdefault(task, now)
194
-
195
- def track_start(self, task: Task) -> None:
196
- if not self.enabled:
197
- return
198
- self.task_start_times.setdefault(task, time.time())
199
-
200
- def track_end(self, task: Task) -> None:
201
- if not self.enabled:
202
- return
203
- self.task_end_times.setdefault(task, time.time())
204
-
205
- def clear_end(self, task: Task) -> None:
206
- if not self.enabled:
207
- return
208
- self.task_end_times.pop(task, None)
209
-
210
- def forget(self, task: Task) -> None:
211
- if not self.enabled:
212
- return
213
- self.task_start_times.pop(task, None)
214
- self.task_end_times.pop(task, None)
215
-
216
- def start_run(self) -> None:
217
- if not self.enabled:
218
- return
219
- self.run_start = time.time()
220
- self.run_end = None
221
- self.summary_printed = False
222
-
223
- def stop_run(self) -> None:
224
- if not self.enabled:
225
- return
226
- self.run_end = time.time()
227
-
228
- def format_duration(self, task: Task) -> str | None:
229
- if not self.enabled:
230
- return None
231
- start = self.task_start_times.get(task)
232
- if start is None:
233
- return None
234
- end = self.task_end_times.get(task)
235
- if end is None:
236
- if task.status == TaskStatus.RUNNING:
237
- return None
238
- end = time.time()
239
- duration = max(0.0, end - start)
240
- return format_duration(duration)
241
-
242
- def consume_elapsed_runtime(self) -> str | None:
243
- if not self.enabled or self.summary_printed:
244
- return None
245
- start_candidates: list[float] = []
246
- if self.run_start is not None:
247
- start_candidates.append(self.run_start)
248
- start_candidates.extend(self.task_start_times.values())
249
- if not start_candidates:
250
- return None
251
- start_time = min(start_candidates)
252
-
253
- end_candidates: list[float] = []
254
- if self.run_end is not None:
255
- end_candidates.append(self.run_end)
256
- end_candidates.extend(self.task_end_times.values())
257
- if not end_candidates:
258
- end_candidates.append(time.time())
259
-
260
- elapsed = max(0.0, max(end_candidates) - start_time)
261
- self.summary_printed = True
262
- return format_duration(elapsed)
263
-
264
- # ============================================================================
265
- # TaskRegistry - Task Storage and Lookup
266
- # ============================================================================
267
-
268
- class TaskRegistry:
269
- """Thread-safe registry for task storage, ID lookups, and basic operations.
270
-
271
- This class manages the core data structure for tasks, providing:
272
- - Task storage and indexing
273
- - ID-based lookups
274
- - Add/remove operations
275
- - Completed task tracking
276
-
277
- All operations are thread-safe using an RLock.
278
- """
279
-
280
- def __init__(self, tasks: Iterable[Task] | None = None):
281
- """Initialize the task registry.
282
-
283
- Args:
284
- tasks: Optional iterable of Task objects to seed the registry with
285
- """
286
- self._lock = threading.RLock()
287
- self._tasks: list[Task] = []
288
- self._tasks_by_id: dict[str, Task] = {}
289
- self._completed_tasks: set[Task] = set()
290
-
291
- # Initialize with provided tasks
292
- if tasks is not None:
293
- for task in tasks:
294
- self._add_task_internal(task)
295
-
296
- def _add_task_internal(self, task: Task) -> None:
297
- """Internal method to add a task without lock (caller must hold lock)."""
298
- if not isinstance(task, Task):
299
- raise TypeError("TaskRegistry expects Task instances")
300
-
301
- # Assign and validate task ID
302
- task_id = task.id
303
- existing = self._tasks_by_id.get(task_id)
304
- if existing is not None and existing is not task:
305
- raise ValueError(f"Duplicate task_id detected: {task_id}")
306
-
307
- self._tasks.append(task)
308
- self._tasks_by_id[task_id] = task
309
-
310
- def add_task(self, task: Task | Iterable[Task]) -> None:
311
- """Add one or more tasks to the registry.
312
-
313
- Args:
314
- task: A Task object or iterable of Task objects to add
315
- """
316
- tasks_to_add = normalize_tasks(task)
317
- if not tasks_to_add:
318
- return
319
-
320
- with self._lock:
321
- for item in tasks_to_add:
322
- self._add_task_internal(item)
323
-
324
- def remove_task(self, task: Task | Iterable[Task]) -> bool:
325
- """Remove one or more tasks from the registry.
326
-
327
- Args:
328
- task: A Task object or iterable of Task objects to remove
329
-
330
- Returns:
331
- True if any tasks were removed, False if no tasks were removed.
332
- Note: If a task is not in the registry, it is silently ignored
333
- (idempotent operation).
334
- """
335
- tasks = normalize_tasks(task)
336
- if not tasks:
337
- return False
338
-
339
- with self._lock:
340
- removed = False
341
- for t in tasks:
342
- try:
343
- self._tasks.remove(t)
344
- self._completed_tasks.discard(t)
345
- if t.id is not None:
346
- self._tasks_by_id.pop(t.id, None)
347
- removed = True
348
- except ValueError:
349
- pass # Task not found, continue
350
- return removed
351
-
352
- def get_task_by_id(self, task_id: str) -> Task | None:
353
- """Get a task by its ID.
354
-
355
- Args:
356
- task_id: The ID of the task to retrieve
357
-
358
- Returns:
359
- The Task object if found, None otherwise
360
- """
361
- with self._lock:
362
- return self._tasks_by_id.get(task_id)
363
-
364
- def get_task_index_by_id(self, task_id: str) -> int | None:
365
- """Get the index of a task by its ID.
366
-
367
- Args:
368
- task_id: The ID of the task to find
369
-
370
- Returns:
371
- The index of the task if found, None otherwise
372
- """
373
- with self._lock:
374
- task = self._tasks_by_id.get(task_id)
375
- if task is None:
376
- return None
377
- try:
378
- return self._tasks.index(task)
379
- except ValueError:
380
- return None
381
-
382
- def find_task(self, text: str) -> Task | None:
383
- """Find a task by its text.
384
-
385
- Args:
386
- text: The text of the task to find
387
-
388
- Returns:
389
- The Task object if found, None otherwise
390
- """
391
- with self._lock:
392
- for task in self._tasks:
393
- if task.text == text:
394
- return task
395
- return None
396
-
397
- def get_task_index(self, task: Task) -> int | None:
398
- """Get the index of a task.
399
-
400
- Args:
401
- task: The Task object to find
402
-
403
- Returns:
404
- The index of the task if found, None otherwise
405
- """
406
- with self._lock:
407
- try:
408
- return self._tasks.index(task)
409
- except ValueError:
410
- return None
411
-
412
- @property
413
- def tasks(self) -> list[Task]:
414
- """Get a read-only copy of the tasks list."""
415
- with self._lock:
416
- return list(self._tasks)
417
-
418
- @property
419
- def completed_tasks(self) -> set[Task]:
420
- """Get a copy of the completed tasks set."""
421
- with self._lock:
422
- return set(self._completed_tasks)
423
-
424
- def mark_completed(self, task: Task) -> bool:
425
- """Mark a task as completed.
426
-
427
- Args:
428
- task: The task to mark as completed
429
-
430
- Returns:
431
- True if the task was newly marked as completed, False if already completed
432
- """
433
- with self._lock:
434
- if task not in self._completed_tasks:
435
- self._completed_tasks.add(task)
436
- return True
437
- return False
438
-
439
- def _unmark_completed(self, task: Task) -> None:
440
- """Remove a task from the completed set.
441
-
442
- Internal method used when a task transitions back to RUNNING status.
443
-
444
- Args:
445
- task: The task to remove from completed set
446
- """
447
- with self._lock:
448
- self._completed_tasks.discard(task)
449
-
450
- def __len__(self) -> int:
451
- """Return the number of tasks."""
452
- with self._lock:
453
- return len(self._tasks)
454
-
455
- def __contains__(self, item: object) -> bool:
456
- """Check if a task is in the registry."""
457
- with self._lock:
458
- return item in self._tasks
459
-
460
-
461
- # ============================================================================
462
- # ProgressReader - Progress Display and Task Orchestration
463
- # ============================================================================
464
-
465
- class ProgressReader:
466
- """A progress component that reads a list of tasks with progress tracking.
467
-
468
- Shows a progress bar and updates task status as tasks are processed.
469
- Can be used as a context manager or with manual start/stop methods.
470
-
471
- Example (context manager):
472
- tasks = [
473
- Task(text="Task 1"),
474
- Task(text="Task 2"),
475
- ]
476
-
477
- with ProgressReader(tasks) as reader:
478
- for task in reader:
479
- # Process task...
480
- reader.complete_task(task) # Mark as done
481
-
482
- Example (manual control):
483
- reader = ProgressReader(tasks)
484
- reader.start()
485
- for task in reader:
486
- process_task(task)
487
- reader.complete_task(task)
488
- reader.stop()
489
-
490
- Note:
491
- For multi-threaded usage, prefer the public API methods
492
- (e.g., ``complete_task``, ``fail_task``, ``update_task_status``)
493
- instead of mutating ``Task`` attributes directly. This ensures the
494
- UI stays in sync and timing information remains accurate.
495
- """
496
-
497
- def __init__(
498
- self,
499
- tasks: Iterable[Task] | None = None,
500
- description: str = "Processing tasks",
501
- show_task_count: bool = True,
502
- show_progress_bar: bool = True,
503
- show_durations: bool = True,
504
- max_visible_tasks: int | None = 20,
505
- show_run_log: bool = False,
506
- log_file_folder: str | Path | None = "logs",
507
- ):
508
- """Initialize the ProgressReader.
509
-
510
- Args:
511
- tasks: Optional iterable of Task objects to seed the reader with
512
- description: Description to show in the progress bar
513
- show_task_count: Whether to show the "x/y" task count display (default: True)
514
- show_progress_bar: Whether to show the progress bar (default: True)
515
- show_durations: Whether to display per-task and summary durations (default: True)
516
- max_visible_tasks: Maximum number of active tasks (``RUNNING`` or ``FAILED``) to render at once.
517
- Completed tasks are omitted from the live view. Set to ``None`` to show every task.
518
- show_run_log: Whether to print a full task log to the console when the reader stops.
519
- Useful for archival/logging scenarios. Defaults to False.
520
- log_file_folder: Folder path where log files will be saved. A log file will be created with
521
- a timestamped filename (format: ``YYYYMMDD_HHMMSS.log``). Defaults to
522
- ``logs`` (saves in a visible folder relative to the current working directory, following
523
- conventions used by dbt and other major platforms). Set to ``None`` to disable file logging.
524
- """
525
- # --------------------------------------------------------------------
526
- # Configuration
527
- # --------------------------------------------------------------------
528
- self.description = description
529
- self.show_task_count = show_task_count
530
- self.show_progress_bar = show_progress_bar
531
- self.show_durations = show_durations
532
- self.show_run_log = show_run_log
533
- if max_visible_tasks is not None and max_visible_tasks <= 0:
534
- raise ValueError("max_visible_tasks must be a positive integer or None")
535
- self.max_visible_tasks = max_visible_tasks
536
- self.log_file_folder = Path(log_file_folder) if log_file_folder is not None else None
537
-
538
- # --------------------------------------------------------------------
539
- # Task Registry - Core data management
540
- # --------------------------------------------------------------------
541
- self._registry = TaskRegistry(tasks)
542
-
543
- # --------------------------------------------------------------------
544
- # Timing Information
545
- # --------------------------------------------------------------------
546
- self._timing = _TimingTracker(show_durations)
547
- if tasks is not None:
548
- self._timing.track_initial_tasks(tasks)
549
-
550
- # --------------------------------------------------------------------
551
- # Display Components (Rich library)
552
- # --------------------------------------------------------------------
553
- self.console = Console()
554
- self._progress: "Progress | None" = None
555
- self._task_id: "TaskID | None" = None
556
- self._live: Live | None = None
557
- self._started = False
558
-
559
- # --------------------------------------------------------------------
560
- # Animation
561
- # --------------------------------------------------------------------
562
- self._spinner_frame_index = 0 # Current spinner animation frame
563
- self._animation_thread: threading.Thread | None = None
564
- self._stop_animation = threading.Event()
565
-
566
- # --------------------------------------------------------------------
567
- # Thread Safety
568
- # --------------------------------------------------------------------
569
- self._lock = threading.RLock() # Reentrant lock for thread-safe operations
570
-
571
- # ------------------------------------------------------------------------
572
- # Task Access (delegates to registry)
573
- # ------------------------------------------------------------------------
574
-
575
- @property
576
- def tasks(self) -> list[Task]:
577
- """Get a read-only copy of the tasks list."""
578
- return self._registry.tasks
579
-
580
- # ------------------------------------------------------------------------
581
- # Display Helpers
582
- # ------------------------------------------------------------------------
583
-
584
- def _get_progress_and_id(self) -> tuple["Progress", "TaskID"] | None:
585
- """Get progress and task_id if active, None otherwise. Helps with type narrowing."""
586
- if self._started and self._progress is not None and self._task_id is not None:
587
- return (self._progress, self._task_id)
588
- return None
589
-
590
- # ------------------------------------------------------------------------
591
- # Task Utilities
592
- # ------------------------------------------------------------------------
593
-
594
- def _ensure_task_list(self, task_or_tasks: Task | Iterable[Task]) -> list[Task]:
595
- """Convert task or iterable to list of tasks, with validation.
596
-
597
- This is a wrapper around the shared normalize_tasks utility for consistency.
598
- """
599
- return normalize_tasks(task_or_tasks)
600
-
601
- # ------------------------------------------------------------------------
602
- # Text Formatting
603
- # ------------------------------------------------------------------------
604
-
605
- @staticmethod
606
- def _wrap_detail_lines(details: str, max_width: int) -> list[str]:
607
- if not details:
608
- return []
609
-
610
- raw_lines = details.splitlines() or [details]
611
- if details.endswith("\n"):
612
- raw_lines.append("")
613
-
614
- wrapped: list[str] = []
615
- for raw in raw_lines:
616
- if raw:
617
- wrapped.extend(
618
- textwrap.wrap(
619
- raw,
620
- width=max_width,
621
- replace_whitespace=False,
622
- drop_whitespace=False,
623
- )
624
- or [""]
625
- )
626
- else:
627
- wrapped.append("")
628
- return wrapped
629
-
630
- def _render_task_row(
631
- self,
632
- task: Task,
633
- spinner_char: str,
634
- detail_indent: str,
635
- max_detail_width: int,
636
- ) -> Text:
637
- line = Text(" ")
638
- detail_style = "dim"
639
-
640
- if task.status == TaskStatus.RUNNING:
641
- line.append(f"{spinner_char} ", style="dim")
642
- line.append(task.text, style="yellow dim")
643
- elif task.status == TaskStatus.COMPLETED:
644
- line.append(f"{CHECK_MARK} ")
645
- line.append(task.text, style="white")
646
- duration_text = self._timing.format_duration(task)
647
- if duration_text:
648
- line.append(f" ({duration_text})")
649
- elif task.status == TaskStatus.FAILED:
650
- error_msg = f" ({task.error})" if task.error else ""
651
- line = Text(f" {FAIL_ICON} {task.text}{error_msg}", style="red")
652
- duration_text = self._timing.format_duration(task)
653
- if duration_text:
654
- line.append(f" ({duration_text})", style="red")
655
- else:
656
- line.append(task.text)
657
-
658
- for detail_line in self._wrap_detail_lines(task.details, max_detail_width):
659
- line.append("\n")
660
- line.append(detail_indent)
661
- if detail_line:
662
- line.append(detail_line, style=detail_style)
663
-
664
- return line
665
-
666
- # ------------------------------------------------------------------------
667
- # Task Mutation & Status Management
668
- # ------------------------------------------------------------------------
669
-
670
- def _validate_and_convert_status(self, status: TaskStatus | str) -> TaskStatus:
671
- """Validate and convert status to TaskStatus enum.
672
-
673
- Args:
674
- status: Status value (TaskStatus enum or string)
675
-
676
- Returns:
677
- TaskStatus enum value
678
-
679
- Raises:
680
- ValueError: If status is invalid
681
- """
682
- if isinstance(status, str):
683
- # Convert string to enum (for backward compatibility)
684
- try:
685
- return TaskStatus(status)
686
- except ValueError:
687
- raise ValueError(
688
- f"Invalid status: {status!r}. Must be one of: "
689
- f"{', '.join(s.value for s in TaskStatus)}"
690
- )
691
- elif not isinstance(status, TaskStatus):
692
- # Reject invalid types (e.g., int, None, etc.)
693
- raise ValueError(
694
- f"Invalid status type: {type(status).__name__}. "
695
- f"Expected TaskStatus enum or string, got: {status!r}"
696
- )
697
- return status
698
-
699
- def _update_task_attributes(
700
- self, task: Task, status: TaskStatus | None, error: str | None, details: str | None
701
- ) -> tuple[bool, bool]:
702
- """Update task attributes (status, error, details) and track timing.
703
-
704
- Args:
705
- task: The task to update
706
- status: New status to set (validated TaskStatus enum)
707
- error: New error message to set
708
- details: New details text to set
709
-
710
- Returns:
711
- Tuple of (should_refresh_totals, should_update_description)
712
- """
713
- should_refresh_totals = False
714
- should_update_description = False
715
-
716
- if status is not None:
717
- self._timing.track_start(task)
718
- object.__setattr__(task, "status", status)
719
- if status in (TaskStatus.COMPLETED, TaskStatus.FAILED):
720
- self._timing.track_end(task)
721
- elif status == TaskStatus.RUNNING:
722
- # Remove from completed set if transitioning back to RUNNING
723
- was_completed = task in self._registry._completed_tasks
724
- self._registry._unmark_completed(task)
725
- if was_completed:
726
- should_refresh_totals = True
727
- self._timing.clear_end(task)
728
- should_update_description = True
729
-
730
- if error is not None:
731
- object.__setattr__(task, "error", error)
732
- should_update_description = True
733
-
734
- if details is not None:
735
- object.__setattr__(task, "details", details)
736
- should_update_description = True
737
-
738
- return should_refresh_totals, should_update_description
739
-
740
- def _mutate_task(
741
- self,
742
- task: Task,
743
- *,
744
- status: TaskStatus | str | None = None,
745
- error: str | None = None,
746
- details: str | None = None,
747
- advance: bool = True,
748
- ) -> bool:
749
- """Mutate a task's attributes (status, error, details).
750
-
751
- Args:
752
- task: The task to mutate
753
- status: New status to set
754
- error: New error message to set
755
- details: New details text to set
756
- advance: Whether to advance progress bar
757
-
758
- Returns:
759
- True if the task was found and updated, False if the task was not in registry
760
- """
761
- if status is None and error is None and details is None:
762
- return True # Nothing to do, but task exists
763
-
764
- # Check if task exists in registry
765
- with self._registry._lock:
766
- if task not in self._registry._tasks:
767
- return False # Task not in registry
768
-
769
- # Validate and convert status if provided
770
- validated_status = None
771
- if status is not None:
772
- validated_status = self._validate_and_convert_status(status)
773
-
774
- # Update task attributes
775
- should_refresh_totals, should_update_description = self._update_task_attributes(
776
- task, validated_status, error, details
777
- )
778
-
779
- # Handle status-specific side effects
780
- if validated_status == TaskStatus.COMPLETED:
781
- self._mark_task_completed(task, advance=advance)
782
- elif validated_status == TaskStatus.FAILED:
783
- self._mark_task_failed(task, advance=advance)
784
-
785
- if should_refresh_totals:
786
- self.update_progress_total()
787
-
788
- if should_update_description:
789
- self._update_progress(description=f"[bright_cyan]{task.text}[/bright_cyan]")
790
-
791
- return True # Task was found and updated successfully
792
-
793
- # ------------------------------------------------------------------------
794
- # Progress Bar Management
795
- # ------------------------------------------------------------------------
796
-
797
- def _update_progress(self, advance: bool = False, **kwargs) -> None:
798
- """Update the progress bar if active.
799
-
800
- Args:
801
- advance: If True, advance the progress by 1
802
- **kwargs: Keyword arguments to pass to progress.update()
803
- """
804
- with self._lock:
805
- progress_info = self._get_progress_and_id()
806
- if progress_info:
807
- progress, task_id = progress_info
808
- if advance:
809
- progress.advance(task_id)
810
- if kwargs:
811
- progress.update(task_id, **kwargs)
812
-
813
- # ------------------------------------------------------------------------
814
- # Output & Logging
815
- # ------------------------------------------------------------------------
816
-
817
- def _print_elapsed_runtime(self, log_file_path: Path | None = None) -> None:
818
- runtime = self._timing.consume_elapsed_runtime()
819
- if runtime:
820
- rich.print()
821
- if log_file_path is not None:
822
- rich.print(f"Elapsed runtime: [cyan]{runtime}[/cyan], logs: [cyan]{log_file_path}[/cyan]")
823
- else:
824
- rich.print(f"Elapsed runtime: [cyan]{runtime}[/cyan]")
825
-
826
- def _print_run_log(self) -> Path | None:
827
- """Print run log and save to file if configured.
828
-
829
- Returns:
830
- Path to the saved log file if file logging was successful, None otherwise.
831
- """
832
- if not self.show_run_log and self.log_file_folder is None:
833
- return None
834
-
835
- with self._registry._lock:
836
- tasks = list(self._registry._tasks)
837
-
838
- if not tasks:
839
- return None
840
-
841
- detail_indent = " "
842
- console_width = self.console.size.width if self.console else DEFAULT_TERMINAL_WIDTH
843
- max_detail_width = max(console_width - len(detail_indent) - 2, MIN_DETAIL_WIDTH)
844
-
845
- # Prepare log content
846
- log_lines: list[str] = []
847
- if self.show_run_log:
848
- rich.print()
849
- rich.print("[bold]Task log[/bold]")
850
-
851
- log_lines.append("Task log")
852
- log_lines.append("=" * 80)
853
-
854
- # Create file console once for converting Rich renderables to plain text
855
- # Use no_color=True and StringIO to strip ANSI escape codes from file output
856
- file_buffer = StringIO() if self.log_file_folder is not None else None
857
- file_console = Console(file=file_buffer, width=console_width, legacy_windows=False, no_color=True) if file_buffer is not None else None
858
-
859
- for task in tasks:
860
- row = self._render_task_row(task, " ", detail_indent, max_detail_width)
861
- if self.show_run_log:
862
- self.console.print(row)
863
- # Convert Rich renderable to plain text for file logging
864
- if file_console is not None and file_buffer is not None:
865
- file_console.print(row)
866
- # Get the text and strip any remaining ANSI escape codes as a safety measure
867
- text = file_buffer.getvalue()
868
- file_buffer.seek(0)
869
- file_buffer.truncate(0)
870
- # Remove ANSI escape codes (pattern matches \x1b[...m sequences)
871
- text = re.sub(r'\x1b\[[0-9;]*m', '', text)
872
- log_lines.append(text.rstrip('\n'))
873
-
874
- # Save to file if log_file_folder is provided
875
- if self.log_file_folder is not None:
876
- return self._save_log_to_file(log_lines)
877
-
878
- return None
879
-
880
- def _save_log_to_file(self, log_lines: list[str]) -> Path | None:
881
- """Save log content to a timestamped file.
882
-
883
- Args:
884
- log_lines: List of log lines to write to file.
885
-
886
- Returns:
887
- Path to the saved log file if successful, None otherwise.
888
- """
889
- if self.log_file_folder is None:
890
- return None
891
-
892
- try:
893
- # Create directory if it doesn't exist
894
- self.log_file_folder.mkdir(parents=True, exist_ok=True)
895
-
896
- # Generate timestamped filename
897
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
898
- log_filename = f"{timestamp}.log"
899
- log_file_path = self.log_file_folder / log_filename
900
-
901
- # Write log content to file
902
- with open(log_file_path, "w", encoding="utf-8") as f:
903
- f.write("\n".join(log_lines))
904
- f.write("\n")
905
-
906
- return log_file_path
907
- except Exception as e:
908
- # Don't fail silently, but also don't break the main flow
909
- rich.print(f"[red]Warning: Failed to save log file: {e}[/red]")
910
- return None
911
-
912
- def _create_progress_components(self) -> tuple["Progress", "TaskID", Live]:
913
- """Create Rich progress components (Progress, TaskID, Live).
914
-
915
- Returns:
916
- Tuple of (progress, task_id, live) objects
917
-
918
- Raises:
919
- RuntimeError: If Live fails to start
920
- """
921
- from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
922
-
923
- # Capture values needed for Rich object creation
924
- total = len(self._registry) if self.show_progress_bar else None
925
- description = self.description
926
- show_progress_bar = self.show_progress_bar
927
- show_task_count = self.show_task_count
928
-
929
- # Always show spinner and description
930
- columns = [
931
- SpinnerColumn(style="bright_cyan"),
932
- TextColumn("[bright_cyan]{task.description}[/bright_cyan]"),
933
- ]
934
-
935
- # Add progress bar components only if enabled
936
- if show_progress_bar:
937
- columns.extend([
938
- BarColumn(bar_width=None, complete_style="bright_cyan", finished_style="bright_cyan", pulse_style="bright_cyan"),
939
- TextColumn("[bright_cyan]{task.percentage:>3.0f}%"),
940
- ])
941
-
942
- # Conditionally add task count display (only relevant with progress bar)
943
- if show_task_count:
944
- columns.extend([
945
- TextColumn("•"),
946
- TextColumn("{task.completed}/{task.total}"),
947
- ])
948
-
949
- # Create Progress but don't start it - Live will handle rendering
950
- progress = Progress(*columns, console=self.console, transient=True)
951
-
952
- # Add the main overall progress task
953
- task_id = progress.add_task(description, total=total)
954
-
955
- # Start Live display - it will render both Progress and tasks
956
- # Use a wrapper class to ensure Rich recognizes it as renderable
957
- renderable_wrapper = _RenderableWrapper(lambda: self._render_all())
958
- live = Live(
959
- renderable_wrapper,
960
- console=self.console,
961
- refresh_per_second=REFRESH_RATE,
962
- vertical_overflow="crop",
963
- )
964
-
965
- return progress, task_id, live
966
-
967
- def start(self):
968
- """Start the progress display."""
969
- with self._lock:
970
- if self._started:
971
- return
972
- # Set _started early to prevent race condition with concurrent start() calls
973
- self._started = True
974
-
975
- # Create Rich objects outside lock to minimize lock duration
976
- progress, task_id, live = self._create_progress_components()
977
-
978
- self._timing.start_run()
979
-
980
- try:
981
- live.start()
982
- except Exception as e:
983
- # If Live fails to start, reset _started flag and clean up
984
- with self._lock:
985
- self._started = False
986
- progress.stop()
987
- raise RuntimeError(f"Failed to start progress display: {e}") from e
988
-
989
- # Update state while holding lock
990
- with self._lock:
991
- self._progress = progress
992
- self._task_id = task_id
993
- self._live = live
994
-
995
- # Start animation thread for spinner updates
996
- self._start_animation()
997
-
998
- def stop(self):
999
- """Stop the progress display."""
1000
- # Stop animation first
1001
- self._stop_animation_thread()
1002
-
1003
- with self._lock:
1004
- progress_info = self._get_progress_and_id()
1005
- if progress_info:
1006
- progress, task_id = progress_info
1007
- # Complete the progress bar and clear description
1008
- progress.update(task_id, completed=len(self._registry), description="")
1009
- # Don't call progress.stop() - Live will handle cleanup
1010
-
1011
- # Stop Live display (this will also clean up Progress)
1012
- if self._live is not None:
1013
- try:
1014
- self._live.stop()
1015
- except Exception:
1016
- pass # Ignore errors during stop
1017
- self._live = None
1018
-
1019
- self._started = False
1020
- self._timing.stop_run()
1021
-
1022
- # Print elapsed runtime with log file location combined
1023
- log_file_path = self._print_run_log()
1024
- self._print_elapsed_runtime(log_file_path)
1025
-
1026
- def __enter__(self) -> "ProgressReader":
1027
- """Enter the context manager and start the progress display."""
1028
- self.start()
1029
- return self
1030
-
1031
- def __exit__(self, exc_type, exc_val, exc_tb):
1032
- """Exit the context manager and stop the progress display."""
1033
- self.stop()
1034
- return False # Don't suppress exceptions
1035
-
1036
- # ------------------------------------------------------------------------
1037
- # Iterator Protocol
1038
- # ------------------------------------------------------------------------
1039
-
1040
- def __iter__(self) -> Iterator[Task]:
1041
- """Iterate through tasks, updating progress as we go.
1042
-
1043
- Note: Creates a snapshot of tasks and releases the lock during iteration
1044
- to avoid deadlocks and allow concurrent modifications.
1045
- """
1046
- with self._registry._lock:
1047
- tasks_copy = list(self._registry._tasks) # Create a snapshot to iterate over
1048
-
1049
- # Lock is released here to allow concurrent modifications during iteration
1050
- for i, task in enumerate(tasks_copy):
1051
- # Show current task being processed
1052
- self._update_progress(description=f"[bright_cyan]{task.text}[/bright_cyan]")
1053
-
1054
- yield task
1055
-
1056
- # Check status and update display (don't advance - iterator handles it)
1057
- self._update_task_status_display(task, advance=False)
1058
-
1059
- # Advance overall progress (based on iteration, not completion)
1060
- self._update_progress(advance=True)
1061
-
1062
- # If this is the last task, clear the description
1063
- if i == len(tasks_copy) - 1:
1064
- progress_info = self._get_progress_and_id()
1065
- if progress_info:
1066
- progress, task_id = progress_info
1067
- progress.update(task_id, description="")
1068
-
1069
- # ------------------------------------------------------------------------
1070
- # Rendering
1071
- # ------------------------------------------------------------------------
1072
-
1073
- def _select_visible_tasks(self, tasks: list[Task]) -> tuple[list[Task], dict[str, int], bool]:
1074
- """Select a subset of active tasks to render and compute hidden task statistics.
1075
-
1076
- Returns:
1077
- Tuple of (visible_tasks, hidden_counts, all_done)
1078
- - visible_tasks: List of tasks to display
1079
- - hidden_counts: Dict with counts of hidden tasks by status
1080
- - all_done: True if no RUNNING tasks remain (all completed or failed)
1081
- """
1082
- limit = self.max_visible_tasks
1083
-
1084
- # Single pass: filter active tasks and separate by status
1085
- failed_tasks = []
1086
- non_failed_tasks = []
1087
- running_count = 0
1088
-
1089
- for task in tasks:
1090
- if task.status == TaskStatus.COMPLETED:
1091
- continue
1092
-
1093
- if task.status == TaskStatus.FAILED:
1094
- failed_tasks.append(task)
1095
- else:
1096
- non_failed_tasks.append(task)
1097
- if task.status == TaskStatus.RUNNING:
1098
- running_count += 1
1099
-
1100
- active_tasks = failed_tasks + non_failed_tasks
1101
-
1102
- if limit is None or len(active_tasks) <= limit:
1103
- all_done = running_count == 0
1104
- return active_tasks, {"total": 0, "RUNNING": 0, "FAILED": 0}, all_done
1105
-
1106
- # Calculate remaining slots (limit is guaranteed to be an int here due to early return above)
1107
- remaining_slots = max(limit - len(failed_tasks), 0)
1108
-
1109
- visible = failed_tasks + non_failed_tasks[:remaining_slots]
1110
- hidden_tasks = non_failed_tasks[remaining_slots:]
1111
-
1112
- # Count hidden tasks by status and track running tasks
1113
- # Note: All RUNNING tasks are in non_failed_tasks (failed tasks are never RUNNING)
1114
- hidden_running = 0
1115
- for task in hidden_tasks:
1116
- if task.status == TaskStatus.RUNNING:
1117
- hidden_running += 1
1118
-
1119
- # all_done is True if no RUNNING tasks exist (neither visible nor hidden)
1120
- all_done = running_count == 0
1121
-
1122
- counts = {
1123
- "total": len(hidden_tasks),
1124
- "RUNNING": hidden_running,
1125
- "FAILED": 0, # Failed tasks are always visible, so hidden_failed is always 0
1126
- }
1127
-
1128
- return visible, counts, all_done
1129
-
1130
- def _render_all(self) -> Group:
1131
- """Render both Progress and task list as a Group for Live display."""
1132
- # Minimize lock time - only grab what we need
1133
- with self._registry._lock:
1134
- tasks = list(self._registry._tasks)
1135
- spinner_char = SPINNER_FRAMES[self._spinner_frame_index]
1136
- progress_info = self._get_progress_and_id()
1137
-
1138
- # Build task list (outside lock for better performance)
1139
- task_lines = []
1140
-
1141
- console_width = self.console.size.width if self.console else DEFAULT_TERMINAL_WIDTH
1142
- detail_indent = " "
1143
- max_detail_width = max(console_width - len(detail_indent) - 2, MIN_DETAIL_WIDTH)
1144
-
1145
- visible_tasks, hidden_counts, all_done = self._select_visible_tasks(tasks)
1146
-
1147
- # Build task lines
1148
- for task in visible_tasks:
1149
- task_lines.append(
1150
- self._render_task_row(task, spinner_char, detail_indent, max_detail_width)
1151
- )
1152
-
1153
- if hidden_counts["total"] > 0:
1154
- hidden_parts = []
1155
- if hidden_counts["RUNNING"]:
1156
- hidden_parts.append(f"{hidden_counts['RUNNING']} running")
1157
- # Note: hidden_counts["FAILED"] is always 0 because failed tasks are always visible
1158
- detail_suffix = f" ({', '.join(hidden_parts)})" if hidden_parts else ""
1159
- task_lines.append(
1160
- Text(f"... {hidden_counts['total']} more active tasks hidden{detail_suffix}", style="dim")
1161
- )
1162
-
1163
- if not visible_tasks and hidden_counts["total"] == 0 and not tasks:
1164
- task_lines.append(Text("No active tasks", style="dim"))
1165
-
1166
- task_display = Group(*task_lines) if task_lines else Text("")
1167
-
1168
- if progress_info and not all_done:
1169
- progress, _ = progress_info
1170
- spacer = Text("")
1171
- return Group(progress, spacer, task_display)
1172
-
1173
- # Either no progress bar or all tasks done
1174
- return Group(task_display)
1175
-
1176
- # ------------------------------------------------------------------------
1177
- # Animation
1178
- # ------------------------------------------------------------------------
1179
-
1180
- def _start_animation(self) -> None:
1181
- """Start the animation thread for spinner updates."""
1182
- if self._animation_thread is not None and self._animation_thread.is_alive():
1183
- return
1184
-
1185
- def animate():
1186
- while not self._stop_animation.is_set():
1187
- with self._lock:
1188
- self._spinner_frame_index = (self._spinner_frame_index + 1) % len(SPINNER_FRAMES)
1189
- # Live will auto-refresh and call _render_all(), which uses the updated spinner_frame_index
1190
- time.sleep(ANIMATION_INTERVAL)
1191
-
1192
- self._stop_animation.clear()
1193
- self._animation_thread = threading.Thread(target=animate, daemon=True)
1194
- self._animation_thread.start()
1195
-
1196
- def _stop_animation_thread(self) -> None:
1197
- """Stop the animation thread."""
1198
- self._stop_animation.set()
1199
- if self._animation_thread is not None:
1200
- self._animation_thread.join(timeout=0.5)
1201
-
1202
- # ------------------------------------------------------------------------
1203
- # Task Status Handlers
1204
- # ------------------------------------------------------------------------
1205
-
1206
- def _mark_task_completed(self, task: Task, advance: bool = True) -> None:
1207
- """Mark a task as completed and display the result.
1208
-
1209
- Args:
1210
- task: The task to mark as completed
1211
- advance: Whether to advance the progress bar (True for manual completion, False during iteration)
1212
- """
1213
- # Mark in registry (thread-safe)
1214
- newly_completed = self._registry.mark_completed(task)
1215
-
1216
- # Note: timing.track_end() is already called in _mutate_task, so we don't duplicate it here
1217
- if newly_completed and advance:
1218
- self._update_progress(advance=True)
1219
- # Live will automatically update the display via _render_all()
1220
-
1221
- def _mark_task_failed(self, task: Task, advance: bool = True) -> None:
1222
- """Mark a task as failed and display the result.
1223
-
1224
- Args:
1225
- task: The task to mark as failed
1226
- advance: Whether to advance the progress bar (True for manual completion, False during iteration)
1227
- """
1228
- # Mark in registry (thread-safe)
1229
- newly_completed = self._registry.mark_completed(task)
1230
-
1231
- # Note: timing.track_end() is already called in _mutate_task, so we don't duplicate it here
1232
- if newly_completed and advance:
1233
- self._update_progress(advance=True)
1234
- # Live will automatically update the display via _render_all()
1235
-
1236
- def _update_task_status_display(self, task: Task, advance: bool = True) -> None:
1237
- """Update the display based on task status.
1238
-
1239
- Args:
1240
- task: The task to check and display
1241
- advance: Whether to advance the progress bar (True for manual completion, False during iteration)
1242
- """
1243
- if task.status == TaskStatus.COMPLETED:
1244
- self._mark_task_completed(task, advance)
1245
- elif task.status == TaskStatus.FAILED:
1246
- self._mark_task_failed(task, advance)
1247
- # RUNNING tasks are handled automatically by Live display
1248
-
1249
- # ------------------------------------------------------------------------
1250
- # Task Lookup (delegates to registry)
1251
- # ------------------------------------------------------------------------
1252
-
1253
- def __len__(self) -> int:
1254
- """Return the number of tasks."""
1255
- return len(self._registry)
1256
-
1257
- def find_task(self, text: str) -> Task | None:
1258
- """Find a task by its text.
1259
-
1260
- Args:
1261
- text: The text of the task to find
1262
-
1263
- Returns:
1264
- The Task object if found, None otherwise
1265
- """
1266
- return self._registry.find_task(text)
1267
-
1268
- def get_task_index(self, task: Task) -> int | None:
1269
- """Get the index of a task.
1270
-
1271
- Args:
1272
- task: The Task object to find
1273
-
1274
- Returns:
1275
- The index of the task if found, None otherwise
1276
- """
1277
- return self._registry.get_task_index(task)
1278
-
1279
- def get_task_by_id(self, task_id: str) -> Task | None:
1280
- """Get a task by its ID.
1281
-
1282
- Args:
1283
- task_id: The ID of the task to retrieve
1284
-
1285
- Returns:
1286
- The Task object if found, None otherwise
1287
- """
1288
- return self._registry.get_task_by_id(task_id)
1289
-
1290
- def get_task_index_by_id(self, task_id: str) -> int | None:
1291
- """Get the index of a task by its ID.
1292
-
1293
- Args:
1294
- task_id: The ID of the task to find
1295
-
1296
- Returns:
1297
- The index of the task if found, None otherwise
1298
- """
1299
- return self._registry.get_task_index_by_id(task_id)
1300
-
1301
- # ------------------------------------------------------------------------
1302
- # Public API - Task Status Management
1303
- # ------------------------------------------------------------------------
1304
-
1305
- @overload
1306
- def complete_task(self, task: Task) -> bool: ...
1307
-
1308
- @overload
1309
- def complete_task(self, task: Iterable[Task]) -> bool: ...
1310
-
1311
- def complete_task(self, task: Task | Iterable[Task]) -> bool:
1312
- """Mark one or more tasks as completed.
1313
-
1314
- Args:
1315
- task: A Task object or iterable of Task objects to mark as completed
1316
-
1317
- Returns:
1318
- True if all tasks were found and updated, False if any task was not found
1319
- """
1320
- tasks = self._ensure_task_list(task)
1321
- if not tasks:
1322
- return False
1323
-
1324
- all_succeeded = True
1325
- for t in tasks:
1326
- success = self._mutate_task(t, status=TaskStatus.COMPLETED)
1327
- if not success:
1328
- all_succeeded = False
1329
-
1330
- return all_succeeded
1331
-
1332
- @overload
1333
- def fail_task(self, task: Task, error: str = "") -> bool: ...
1334
-
1335
- @overload
1336
- def fail_task(self, task: Iterable[Task], error: str = "") -> bool: ...
1337
-
1338
- def fail_task(self, task: Task | Iterable[Task], error: str = "") -> bool:
1339
- """Mark one or more tasks as failed.
1340
-
1341
- Args:
1342
- task: A Task object or iterable of Task objects to mark as failed
1343
- error: Optional error message (applied to all tasks if iterable is provided)
1344
-
1345
- Returns:
1346
- True if all tasks were found and updated, False if any task was not found
1347
- """
1348
- tasks = self._ensure_task_list(task)
1349
- if not tasks:
1350
- return False
1351
-
1352
- all_succeeded = True
1353
- for t in tasks:
1354
- success = self._mutate_task(t, status=TaskStatus.FAILED, error=error)
1355
- if not success:
1356
- all_succeeded = False
1357
-
1358
- return all_succeeded
1359
-
1360
- def update_task_status(self, task: Task, status: TaskStatus | str, error: str | None = None) -> bool:
1361
- """Update the status of a task.
1362
-
1363
- Args:
1364
- task: The task to update
1365
- status: New status (TaskStatus enum or string for backward compatibility)
1366
- error: Optional error message if status is "FAILED"
1367
-
1368
- Returns:
1369
- True if the task was found and updated, False if the task was not found
1370
- """
1371
- return self._mutate_task(task, status=status, error=error)
1372
-
1373
- def update_task_details(self, task: Task, details: str) -> bool:
1374
- """Update the details text of a task.
1375
-
1376
- Args:
1377
- task: The task to update
1378
- details: New details content to display under the task text
1379
-
1380
- Returns:
1381
- True if the task was found and updated, False if the task was not found
1382
- """
1383
- return self._mutate_task(task, details=details, advance=False)
1384
-
1385
- # ------------------------------------------------------------------------
1386
- # Public API - Task Management
1387
- # ------------------------------------------------------------------------
1388
-
1389
- @overload
1390
- def add_task(self, task: Task) -> None: ...
1391
-
1392
- @overload
1393
- def add_task(self, task: Iterable[Task]) -> None: ...
1394
-
1395
- def add_task(self, task: Task | Iterable[Task]) -> None:
1396
- """Add one or more tasks to the list and automatically update the progress bar total.
1397
-
1398
- Tasks in RUNNING status are displayed immediately when added. Tasks in other statuses
1399
- are handled by their respective status update methods.
1400
-
1401
- Args:
1402
- task: A Task object or iterable of Task objects to append to the reader
1403
- """
1404
- tasks_to_add = self._ensure_task_list(task)
1405
- if not tasks_to_add:
1406
- return
1407
-
1408
- # Add to registry
1409
- self._registry.add_task(tasks_to_add)
1410
-
1411
- # Track timing for new tasks
1412
- with self._lock:
1413
- for item in tasks_to_add:
1414
- self._timing.track_start(item)
1415
-
1416
- # Update totals once after all tasks are added
1417
- self.update_progress_total()
1418
-
1419
- @overload
1420
- def remove_task(self, task: Task) -> bool: ...
1421
-
1422
- @overload
1423
- def remove_task(self, task: Iterable[Task]) -> bool: ...
1424
-
1425
- def remove_task(self, task: Task | Iterable[Task]) -> bool:
1426
- """Remove one or more tasks from the list and automatically update the progress bar total.
1427
-
1428
- Args:
1429
- task: A Task object or iterable of Task objects to remove
1430
-
1431
- Returns:
1432
- True if any tasks were removed, False if no tasks were removed.
1433
- Note: If a task is not in the registry, it is silently ignored
1434
- (idempotent operation).
1435
- """
1436
- tasks = self._ensure_task_list(task)
1437
- if not tasks:
1438
- return False
1439
-
1440
- # Remove from registry
1441
- removed = self._registry.remove_task(tasks)
1442
-
1443
- if removed:
1444
- # Clean up timing information
1445
- with self._lock:
1446
- for t in tasks:
1447
- self._timing.forget(t)
1448
- # Update totals after removal
1449
- self.update_progress_total()
1450
-
1451
- return removed
1452
-
1453
- # ------------------------------------------------------------------------
1454
- # Public API - ID-based Operations
1455
- # ------------------------------------------------------------------------
1456
-
1457
- def complete_task_by_id(self, task_id: str) -> bool:
1458
- """Mark the task with ``task_id`` as completed.
1459
-
1460
- Returns ``True`` if the task exists and was updated, ``False`` otherwise.
1461
- """
1462
- task = self.get_task_by_id(task_id)
1463
- if task is None:
1464
- return False
1465
- self.complete_task(task)
1466
- return True
1467
-
1468
- def fail_task_by_id(self, task_id: str, error: str = "") -> bool:
1469
- """Mark the task with ``task_id`` as failed.
1470
-
1471
- Returns ``True`` if the task exists and was updated, ``False`` otherwise.
1472
- """
1473
- task = self.get_task_by_id(task_id)
1474
- if task is None:
1475
- return False
1476
- self.fail_task(task, error=error)
1477
- return True
1478
-
1479
- def update_task_status_by_id(self, task_id: str, status: TaskStatus | str, error: str | None = None) -> bool:
1480
- """Update the status of the task identified by ``task_id``.
1481
-
1482
- Returns ``True`` if the task exists and was updated, ``False`` otherwise.
1483
- """
1484
- task = self.get_task_by_id(task_id)
1485
- if task is None:
1486
- return False
1487
- self.update_task_status(task, status, error)
1488
- return True
1489
-
1490
- def update_task_details_by_id(self, task_id: str, details: str) -> bool:
1491
- """Update the details of the task identified by ``task_id``.
1492
-
1493
- Returns ``True`` if the task exists and was updated, ``False`` otherwise.
1494
- """
1495
- task = self.get_task_by_id(task_id)
1496
- if task is None:
1497
- return False
1498
- self.update_task_details(task, details)
1499
- return True
1500
-
1501
- # ------------------------------------------------------------------------
1502
- # Progress Bar Updates
1503
- # ------------------------------------------------------------------------
1504
-
1505
- def update_progress_total(self) -> None:
1506
- """Update the progress bar total to match the current number of tasks.
1507
-
1508
- This is automatically called when using add_task() or remove_task().
1509
-
1510
- Also updates the completed count to preserve the current progress percentage
1511
- when the total changes (e.g., when tasks are added dynamically).
1512
-
1513
- Note: This method may be called while already holding the lock, which is safe
1514
- because we use RLock.
1515
- """
1516
- with self._lock:
1517
- # Count how many tasks are actually completed
1518
- completed_count = len(self._registry.completed_tasks)
1519
- total = len(self._registry)
1520
- # Update progress while holding lock to ensure atomicity
1521
- progress_info = self._get_progress_and_id()
1522
- if progress_info:
1523
- progress, task_id = progress_info
1524
- progress.update(task_id, total=total, completed=completed_count)