relationalai 0.13.0.dev0__py3-none-any.whl → 0.13.2__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.
- frontend/debugger/dist/.gitignore +2 -0
- frontend/debugger/dist/assets/favicon-Dy0ZgA6N.png +0 -0
- frontend/debugger/dist/assets/index-Cssla-O7.js +208 -0
- frontend/debugger/dist/assets/index-DlHsYx1V.css +9 -0
- frontend/debugger/dist/index.html +17 -0
- relationalai/__init__.py +256 -1
- relationalai/clients/__init__.py +18 -0
- relationalai/clients/client.py +947 -0
- relationalai/clients/config.py +673 -0
- relationalai/clients/direct_access_client.py +118 -0
- relationalai/clients/exec_txn_poller.py +91 -0
- relationalai/clients/hash_util.py +31 -0
- relationalai/clients/local.py +586 -0
- relationalai/clients/profile_polling.py +73 -0
- relationalai/clients/resources/__init__.py +8 -0
- relationalai/clients/resources/azure/azure.py +502 -0
- relationalai/clients/resources/snowflake/__init__.py +20 -0
- relationalai/clients/resources/snowflake/cli_resources.py +98 -0
- relationalai/clients/resources/snowflake/direct_access_resources.py +734 -0
- relationalai/clients/resources/snowflake/engine_service.py +381 -0
- relationalai/clients/resources/snowflake/engine_state_handlers.py +315 -0
- relationalai/clients/resources/snowflake/error_handlers.py +240 -0
- relationalai/clients/resources/snowflake/export_procedure.py.jinja +249 -0
- relationalai/clients/resources/snowflake/resources_factory.py +99 -0
- relationalai/clients/resources/snowflake/snowflake.py +3185 -0
- relationalai/clients/resources/snowflake/use_index_poller.py +1019 -0
- relationalai/clients/resources/snowflake/use_index_resources.py +188 -0
- relationalai/clients/resources/snowflake/util.py +387 -0
- relationalai/clients/result_helpers.py +420 -0
- relationalai/clients/types.py +118 -0
- relationalai/clients/util.py +356 -0
- relationalai/debugging.py +389 -0
- relationalai/dsl.py +1749 -0
- relationalai/early_access/builder/__init__.py +30 -0
- relationalai/early_access/builder/builder/__init__.py +35 -0
- relationalai/early_access/builder/snowflake/__init__.py +12 -0
- relationalai/early_access/builder/std/__init__.py +25 -0
- relationalai/early_access/builder/std/decimals/__init__.py +12 -0
- relationalai/early_access/builder/std/integers/__init__.py +12 -0
- relationalai/early_access/builder/std/math/__init__.py +12 -0
- relationalai/early_access/builder/std/strings/__init__.py +14 -0
- relationalai/early_access/devtools/__init__.py +12 -0
- relationalai/early_access/devtools/benchmark_lqp/__init__.py +12 -0
- relationalai/early_access/devtools/extract_lqp/__init__.py +12 -0
- relationalai/early_access/dsl/adapters/orm/adapter_qb.py +427 -0
- relationalai/early_access/dsl/adapters/orm/parser.py +636 -0
- relationalai/early_access/dsl/adapters/owl/adapter.py +176 -0
- relationalai/early_access/dsl/adapters/owl/parser.py +160 -0
- relationalai/early_access/dsl/bindings/common.py +402 -0
- relationalai/early_access/dsl/bindings/csv.py +170 -0
- relationalai/early_access/dsl/bindings/legacy/binding_models.py +143 -0
- relationalai/early_access/dsl/bindings/snowflake.py +64 -0
- relationalai/early_access/dsl/codegen/binder.py +411 -0
- relationalai/early_access/dsl/codegen/common.py +79 -0
- relationalai/early_access/dsl/codegen/helpers.py +23 -0
- relationalai/early_access/dsl/codegen/relations.py +700 -0
- relationalai/early_access/dsl/codegen/weaver.py +417 -0
- relationalai/early_access/dsl/core/builders/__init__.py +47 -0
- relationalai/early_access/dsl/core/builders/logic.py +19 -0
- relationalai/early_access/dsl/core/builders/scalar_constraint.py +11 -0
- relationalai/early_access/dsl/core/constraints/predicate/atomic.py +455 -0
- relationalai/early_access/dsl/core/constraints/predicate/universal.py +73 -0
- relationalai/early_access/dsl/core/constraints/scalar.py +310 -0
- relationalai/early_access/dsl/core/context.py +13 -0
- relationalai/early_access/dsl/core/cset.py +132 -0
- relationalai/early_access/dsl/core/exprs/__init__.py +116 -0
- relationalai/early_access/dsl/core/exprs/relational.py +18 -0
- relationalai/early_access/dsl/core/exprs/scalar.py +412 -0
- relationalai/early_access/dsl/core/instances.py +44 -0
- relationalai/early_access/dsl/core/logic/__init__.py +193 -0
- relationalai/early_access/dsl/core/logic/aggregation.py +98 -0
- relationalai/early_access/dsl/core/logic/exists.py +223 -0
- relationalai/early_access/dsl/core/logic/helper.py +163 -0
- relationalai/early_access/dsl/core/namespaces.py +32 -0
- relationalai/early_access/dsl/core/relations.py +276 -0
- relationalai/early_access/dsl/core/rules.py +112 -0
- relationalai/early_access/dsl/core/std/__init__.py +45 -0
- relationalai/early_access/dsl/core/temporal/recall.py +6 -0
- relationalai/early_access/dsl/core/types/__init__.py +270 -0
- relationalai/early_access/dsl/core/types/concepts.py +128 -0
- relationalai/early_access/dsl/core/types/constrained/__init__.py +267 -0
- relationalai/early_access/dsl/core/types/constrained/nominal.py +143 -0
- relationalai/early_access/dsl/core/types/constrained/subtype.py +124 -0
- relationalai/early_access/dsl/core/types/standard.py +92 -0
- relationalai/early_access/dsl/core/types/unconstrained.py +50 -0
- relationalai/early_access/dsl/core/types/variables.py +203 -0
- relationalai/early_access/dsl/ir/compiler.py +318 -0
- relationalai/early_access/dsl/ir/executor.py +260 -0
- relationalai/early_access/dsl/ontologies/constraints.py +88 -0
- relationalai/early_access/dsl/ontologies/export.py +30 -0
- relationalai/early_access/dsl/ontologies/models.py +453 -0
- relationalai/early_access/dsl/ontologies/python_printer.py +303 -0
- relationalai/early_access/dsl/ontologies/readings.py +60 -0
- relationalai/early_access/dsl/ontologies/relationships.py +322 -0
- relationalai/early_access/dsl/ontologies/roles.py +87 -0
- relationalai/early_access/dsl/ontologies/subtyping.py +55 -0
- relationalai/early_access/dsl/orm/constraints.py +438 -0
- relationalai/early_access/dsl/orm/measures/dimensions.py +200 -0
- relationalai/early_access/dsl/orm/measures/initializer.py +16 -0
- relationalai/early_access/dsl/orm/measures/measure_rules.py +275 -0
- relationalai/early_access/dsl/orm/measures/measures.py +299 -0
- relationalai/early_access/dsl/orm/measures/role_exprs.py +268 -0
- relationalai/early_access/dsl/orm/models.py +256 -0
- relationalai/early_access/dsl/orm/object_oriented_printer.py +344 -0
- relationalai/early_access/dsl/orm/printer.py +469 -0
- relationalai/early_access/dsl/orm/reasoners.py +480 -0
- relationalai/early_access/dsl/orm/relations.py +19 -0
- relationalai/early_access/dsl/orm/relationships.py +251 -0
- relationalai/early_access/dsl/orm/types.py +42 -0
- relationalai/early_access/dsl/orm/utils.py +79 -0
- relationalai/early_access/dsl/orm/verb.py +204 -0
- relationalai/early_access/dsl/physical_metadata/tables.py +133 -0
- relationalai/early_access/dsl/relations.py +170 -0
- relationalai/early_access/dsl/rulesets.py +69 -0
- relationalai/early_access/dsl/schemas/__init__.py +450 -0
- relationalai/early_access/dsl/schemas/builder.py +48 -0
- relationalai/early_access/dsl/schemas/comp_names.py +51 -0
- relationalai/early_access/dsl/schemas/components.py +203 -0
- relationalai/early_access/dsl/schemas/contexts.py +156 -0
- relationalai/early_access/dsl/schemas/exprs.py +89 -0
- relationalai/early_access/dsl/schemas/fragments.py +464 -0
- relationalai/early_access/dsl/serialization.py +79 -0
- relationalai/early_access/dsl/serialize/exporter.py +163 -0
- relationalai/early_access/dsl/snow/api.py +105 -0
- relationalai/early_access/dsl/snow/common.py +76 -0
- relationalai/early_access/dsl/state_mgmt/__init__.py +129 -0
- relationalai/early_access/dsl/state_mgmt/state_charts.py +125 -0
- relationalai/early_access/dsl/state_mgmt/transitions.py +130 -0
- relationalai/early_access/dsl/types/__init__.py +40 -0
- relationalai/early_access/dsl/types/concepts.py +12 -0
- relationalai/early_access/dsl/types/entities.py +135 -0
- relationalai/early_access/dsl/types/values.py +17 -0
- relationalai/early_access/dsl/utils.py +102 -0
- relationalai/early_access/graphs/__init__.py +13 -0
- relationalai/early_access/lqp/__init__.py +12 -0
- relationalai/early_access/lqp/compiler/__init__.py +12 -0
- relationalai/early_access/lqp/constructors/__init__.py +18 -0
- relationalai/early_access/lqp/executor/__init__.py +12 -0
- relationalai/early_access/lqp/ir/__init__.py +12 -0
- relationalai/early_access/lqp/passes/__init__.py +12 -0
- relationalai/early_access/lqp/pragmas/__init__.py +12 -0
- relationalai/early_access/lqp/primitives/__init__.py +12 -0
- relationalai/early_access/lqp/types/__init__.py +12 -0
- relationalai/early_access/lqp/utils/__init__.py +12 -0
- relationalai/early_access/lqp/validators/__init__.py +12 -0
- relationalai/early_access/metamodel/__init__.py +58 -0
- relationalai/early_access/metamodel/builtins/__init__.py +12 -0
- relationalai/early_access/metamodel/compiler/__init__.py +12 -0
- relationalai/early_access/metamodel/dependency/__init__.py +12 -0
- relationalai/early_access/metamodel/factory/__init__.py +17 -0
- relationalai/early_access/metamodel/helpers/__init__.py +12 -0
- relationalai/early_access/metamodel/ir/__init__.py +14 -0
- relationalai/early_access/metamodel/rewrite/__init__.py +7 -0
- relationalai/early_access/metamodel/typer/__init__.py +3 -0
- relationalai/early_access/metamodel/typer/typer/__init__.py +12 -0
- relationalai/early_access/metamodel/types/__init__.py +15 -0
- relationalai/early_access/metamodel/util/__init__.py +15 -0
- relationalai/early_access/metamodel/visitor/__init__.py +12 -0
- relationalai/early_access/rel/__init__.py +12 -0
- relationalai/early_access/rel/executor/__init__.py +12 -0
- relationalai/early_access/rel/rel_utils/__init__.py +12 -0
- relationalai/early_access/rel/rewrite/__init__.py +7 -0
- relationalai/early_access/solvers/__init__.py +19 -0
- relationalai/early_access/sql/__init__.py +11 -0
- relationalai/early_access/sql/executor/__init__.py +3 -0
- relationalai/early_access/sql/rewrite/__init__.py +3 -0
- relationalai/early_access/tests/logging/__init__.py +12 -0
- relationalai/early_access/tests/test_snapshot_base/__init__.py +12 -0
- relationalai/early_access/tests/utils/__init__.py +12 -0
- relationalai/environments/__init__.py +35 -0
- relationalai/environments/base.py +381 -0
- relationalai/environments/colab.py +14 -0
- relationalai/environments/generic.py +71 -0
- relationalai/environments/ipython.py +68 -0
- relationalai/environments/jupyter.py +9 -0
- relationalai/environments/snowbook.py +169 -0
- relationalai/errors.py +2496 -0
- relationalai/experimental/SF.py +38 -0
- relationalai/experimental/inspect.py +47 -0
- relationalai/experimental/pathfinder/__init__.py +158 -0
- relationalai/experimental/pathfinder/api.py +160 -0
- relationalai/experimental/pathfinder/automaton.py +584 -0
- relationalai/experimental/pathfinder/bridge.py +226 -0
- relationalai/experimental/pathfinder/compiler.py +416 -0
- relationalai/experimental/pathfinder/datalog.py +214 -0
- relationalai/experimental/pathfinder/diagnostics.py +56 -0
- relationalai/experimental/pathfinder/filter.py +236 -0
- relationalai/experimental/pathfinder/glushkov.py +439 -0
- relationalai/experimental/pathfinder/options.py +265 -0
- relationalai/experimental/pathfinder/pathfinder-v0.7.0.rel +1951 -0
- relationalai/experimental/pathfinder/rpq.py +344 -0
- relationalai/experimental/pathfinder/transition.py +200 -0
- relationalai/experimental/pathfinder/utils.py +26 -0
- relationalai/experimental/paths/README.md +107 -0
- relationalai/experimental/paths/api.py +143 -0
- relationalai/experimental/paths/benchmarks/grid_graph.py +37 -0
- relationalai/experimental/paths/code_organization.md +2 -0
- relationalai/experimental/paths/examples/Movies.ipynb +16328 -0
- relationalai/experimental/paths/examples/basic_example.py +40 -0
- relationalai/experimental/paths/examples/minimal_engine_warmup.py +3 -0
- relationalai/experimental/paths/examples/movie_example.py +77 -0
- relationalai/experimental/paths/examples/movies_data/actedin.csv +193 -0
- relationalai/experimental/paths/examples/movies_data/directed.csv +45 -0
- relationalai/experimental/paths/examples/movies_data/follows.csv +7 -0
- relationalai/experimental/paths/examples/movies_data/movies.csv +39 -0
- relationalai/experimental/paths/examples/movies_data/person.csv +134 -0
- relationalai/experimental/paths/examples/movies_data/produced.csv +16 -0
- relationalai/experimental/paths/examples/movies_data/ratings.csv +10 -0
- relationalai/experimental/paths/examples/movies_data/wrote.csv +11 -0
- relationalai/experimental/paths/examples/paths_benchmark.py +115 -0
- relationalai/experimental/paths/examples/paths_example.py +116 -0
- relationalai/experimental/paths/examples/pattern_to_automaton.py +28 -0
- relationalai/experimental/paths/find_paths_via_automaton.py +85 -0
- relationalai/experimental/paths/graph.py +185 -0
- relationalai/experimental/paths/path_algorithms/find_paths.py +280 -0
- relationalai/experimental/paths/path_algorithms/one_sided_ball_repetition.py +26 -0
- relationalai/experimental/paths/path_algorithms/one_sided_ball_upto.py +111 -0
- relationalai/experimental/paths/path_algorithms/single.py +59 -0
- relationalai/experimental/paths/path_algorithms/two_sided_balls_repetition.py +39 -0
- relationalai/experimental/paths/path_algorithms/two_sided_balls_upto.py +103 -0
- relationalai/experimental/paths/path_algorithms/usp-old.py +130 -0
- relationalai/experimental/paths/path_algorithms/usp-tuple.py +183 -0
- relationalai/experimental/paths/path_algorithms/usp.py +150 -0
- relationalai/experimental/paths/product_graph.py +93 -0
- relationalai/experimental/paths/rpq/automaton.py +584 -0
- relationalai/experimental/paths/rpq/diagnostics.py +56 -0
- relationalai/experimental/paths/rpq/rpq.py +378 -0
- relationalai/experimental/paths/tests/tests_limit_sp_max_length.py +90 -0
- relationalai/experimental/paths/tests/tests_limit_sp_multiple.py +119 -0
- relationalai/experimental/paths/tests/tests_limit_sp_single.py +104 -0
- relationalai/experimental/paths/tests/tests_limit_walks_multiple.py +113 -0
- relationalai/experimental/paths/tests/tests_limit_walks_single.py +149 -0
- relationalai/experimental/paths/tests/tests_one_sided_ball_repetition_multiple.py +70 -0
- relationalai/experimental/paths/tests/tests_one_sided_ball_repetition_single.py +64 -0
- relationalai/experimental/paths/tests/tests_one_sided_ball_upto_multiple.py +115 -0
- relationalai/experimental/paths/tests/tests_one_sided_ball_upto_single.py +75 -0
- relationalai/experimental/paths/tests/tests_single_paths.py +152 -0
- relationalai/experimental/paths/tests/tests_single_walks.py +208 -0
- relationalai/experimental/paths/tests/tests_single_walks_undirected.py +297 -0
- relationalai/experimental/paths/tests/tests_two_sided_balls_repetition_multiple.py +107 -0
- relationalai/experimental/paths/tests/tests_two_sided_balls_repetition_single.py +76 -0
- relationalai/experimental/paths/tests/tests_two_sided_balls_upto_multiple.py +76 -0
- relationalai/experimental/paths/tests/tests_two_sided_balls_upto_single.py +110 -0
- relationalai/experimental/paths/tests/tests_usp_nsp_multiple.py +229 -0
- relationalai/experimental/paths/tests/tests_usp_nsp_single.py +108 -0
- relationalai/experimental/paths/tree_agg.py +168 -0
- relationalai/experimental/paths/utilities/iterators.py +27 -0
- relationalai/experimental/paths/utilities/prefix_sum.py +91 -0
- relationalai/experimental/solvers.py +1087 -0
- relationalai/loaders/csv.py +195 -0
- relationalai/loaders/loader.py +177 -0
- relationalai/loaders/types.py +23 -0
- relationalai/rel_emitter.py +373 -0
- relationalai/rel_utils.py +185 -0
- relationalai/semantics/__init__.py +22 -146
- relationalai/semantics/designs/query_builder/identify_by.md +106 -0
- relationalai/semantics/devtools/benchmark_lqp.py +535 -0
- relationalai/semantics/devtools/compilation_manager.py +294 -0
- relationalai/semantics/devtools/extract_lqp.py +110 -0
- relationalai/semantics/internal/internal.py +3785 -0
- relationalai/semantics/internal/snowflake.py +325 -0
- relationalai/semantics/lqp/README.md +34 -0
- relationalai/semantics/lqp/builtins.py +16 -0
- relationalai/semantics/lqp/compiler.py +22 -0
- relationalai/semantics/lqp/constructors.py +68 -0
- relationalai/semantics/lqp/executor.py +469 -0
- relationalai/semantics/lqp/intrinsics.py +24 -0
- relationalai/semantics/lqp/model2lqp.py +877 -0
- relationalai/semantics/lqp/passes.py +680 -0
- relationalai/semantics/lqp/primitives.py +252 -0
- relationalai/semantics/lqp/result_helpers.py +202 -0
- relationalai/semantics/lqp/rewrite/annotate_constraints.py +57 -0
- relationalai/semantics/lqp/rewrite/cdc.py +216 -0
- relationalai/semantics/lqp/rewrite/extract_common.py +338 -0
- relationalai/semantics/lqp/rewrite/extract_keys.py +512 -0
- relationalai/semantics/lqp/rewrite/function_annotations.py +114 -0
- relationalai/semantics/lqp/rewrite/functional_dependencies.py +314 -0
- relationalai/semantics/lqp/rewrite/quantify_vars.py +296 -0
- relationalai/semantics/lqp/rewrite/splinter.py +76 -0
- relationalai/semantics/lqp/types.py +101 -0
- relationalai/semantics/lqp/utils.py +160 -0
- relationalai/semantics/lqp/validators.py +57 -0
- relationalai/semantics/metamodel/__init__.py +40 -6
- relationalai/semantics/metamodel/builtins.py +771 -205
- relationalai/semantics/metamodel/compiler.py +133 -0
- relationalai/semantics/metamodel/dependency.py +862 -0
- relationalai/semantics/metamodel/executor.py +61 -0
- relationalai/semantics/metamodel/factory.py +287 -0
- relationalai/semantics/metamodel/helpers.py +361 -0
- relationalai/semantics/metamodel/rewrite/discharge_constraints.py +39 -0
- relationalai/semantics/metamodel/rewrite/dnf_union_splitter.py +210 -0
- relationalai/semantics/metamodel/rewrite/extract_nested_logicals.py +78 -0
- relationalai/semantics/metamodel/rewrite/flatten.py +554 -0
- relationalai/semantics/metamodel/rewrite/format_outputs.py +165 -0
- relationalai/semantics/metamodel/typer/checker.py +353 -0
- relationalai/semantics/metamodel/typer/typer.py +1399 -0
- relationalai/semantics/metamodel/util.py +506 -0
- relationalai/semantics/reasoners/__init__.py +10 -0
- relationalai/semantics/reasoners/graph/README.md +620 -0
- relationalai/semantics/reasoners/graph/__init__.py +37 -0
- relationalai/semantics/reasoners/graph/core.py +9019 -0
- relationalai/semantics/reasoners/graph/design/beyond_demand_transform.md +797 -0
- relationalai/semantics/reasoners/graph/tests/README.md +21 -0
- relationalai/semantics/reasoners/optimization/__init__.py +68 -0
- relationalai/semantics/reasoners/optimization/common.py +88 -0
- relationalai/semantics/reasoners/optimization/solvers_dev.py +568 -0
- relationalai/semantics/reasoners/optimization/solvers_pb.py +1414 -0
- relationalai/semantics/rel/builtins.py +40 -0
- relationalai/semantics/rel/compiler.py +989 -0
- relationalai/semantics/rel/executor.py +362 -0
- relationalai/semantics/rel/rel.py +482 -0
- relationalai/semantics/rel/rel_utils.py +276 -0
- relationalai/semantics/snowflake/__init__.py +3 -0
- relationalai/semantics/sql/compiler.py +2503 -0
- relationalai/semantics/sql/executor/duck_db.py +52 -0
- relationalai/semantics/sql/executor/result_helpers.py +64 -0
- relationalai/semantics/sql/executor/snowflake.py +149 -0
- relationalai/semantics/sql/rewrite/denormalize.py +222 -0
- relationalai/semantics/sql/rewrite/double_negation.py +49 -0
- relationalai/semantics/sql/rewrite/recursive_union.py +127 -0
- relationalai/semantics/sql/rewrite/sort_output_query.py +246 -0
- relationalai/semantics/sql/sql.py +504 -0
- relationalai/semantics/std/__init__.py +40 -60
- relationalai/semantics/std/constraints.py +43 -37
- relationalai/semantics/std/datetime.py +135 -246
- relationalai/semantics/std/decimals.py +52 -45
- relationalai/semantics/std/floats.py +5 -13
- relationalai/semantics/std/integers.py +11 -26
- relationalai/semantics/std/math.py +112 -183
- relationalai/semantics/std/pragmas.py +11 -0
- relationalai/semantics/std/re.py +62 -80
- relationalai/semantics/std/std.py +14 -0
- relationalai/semantics/std/strings.py +60 -117
- relationalai/semantics/tests/test_snapshot_abstract.py +143 -0
- relationalai/semantics/tests/test_snapshot_base.py +9 -0
- relationalai/semantics/tests/utils.py +46 -0
- relationalai/std/__init__.py +70 -0
- relationalai/tools/cli.py +2089 -0
- relationalai/tools/cli_controls.py +1826 -0
- relationalai/tools/cli_helpers.py +802 -0
- relationalai/tools/debugger.py +183 -289
- relationalai/tools/debugger_client.py +109 -0
- relationalai/tools/debugger_server.py +302 -0
- relationalai/tools/dev.py +685 -0
- relationalai/tools/notes +7 -0
- relationalai/tools/qb_debugger.py +425 -0
- relationalai/util/clean_up_databases.py +95 -0
- relationalai/util/format.py +106 -48
- relationalai/util/list_databases.py +9 -0
- relationalai/util/otel_configuration.py +26 -0
- relationalai/util/otel_handler.py +484 -0
- relationalai/util/snowflake_handler.py +88 -0
- relationalai/util/span_format_test.py +43 -0
- relationalai/util/span_tracker.py +207 -0
- relationalai/util/spans_file_handler.py +72 -0
- relationalai/util/tracing_handler.py +34 -0
- relationalai-0.13.2.dist-info/METADATA +74 -0
- relationalai-0.13.2.dist-info/RECORD +460 -0
- relationalai-0.13.2.dist-info/WHEEL +4 -0
- relationalai-0.13.2.dist-info/entry_points.txt +3 -0
- relationalai-0.13.2.dist-info/licenses/LICENSE +202 -0
- relationalai_test_util/__init__.py +4 -0
- relationalai_test_util/fixtures.py +233 -0
- relationalai_test_util/snapshot.py +252 -0
- relationalai_test_util/traceback.py +118 -0
- relationalai/config/__init__.py +0 -56
- relationalai/config/config.py +0 -289
- relationalai/config/config_fields.py +0 -86
- relationalai/config/connections/__init__.py +0 -46
- relationalai/config/connections/base.py +0 -23
- relationalai/config/connections/duckdb.py +0 -29
- relationalai/config/connections/snowflake.py +0 -243
- relationalai/config/external/__init__.py +0 -17
- relationalai/config/external/dbt_converter.py +0 -101
- relationalai/config/external/dbt_models.py +0 -93
- relationalai/config/external/snowflake_converter.py +0 -41
- relationalai/config/external/snowflake_models.py +0 -85
- relationalai/config/external/utils.py +0 -19
- relationalai/semantics/backends/lqp/annotations.py +0 -11
- relationalai/semantics/backends/sql/sql_compiler.py +0 -327
- relationalai/semantics/frontend/base.py +0 -1707
- relationalai/semantics/frontend/core.py +0 -179
- relationalai/semantics/frontend/front_compiler.py +0 -1313
- relationalai/semantics/frontend/pprint.py +0 -408
- relationalai/semantics/metamodel/metamodel.py +0 -437
- relationalai/semantics/metamodel/metamodel_analyzer.py +0 -519
- relationalai/semantics/metamodel/metamodel_compiler.py +0 -0
- relationalai/semantics/metamodel/pprint.py +0 -412
- relationalai/semantics/metamodel/rewriter.py +0 -266
- relationalai/semantics/metamodel/typer.py +0 -1378
- relationalai/semantics/std/aggregates.py +0 -149
- relationalai/semantics/std/common.py +0 -44
- relationalai/semantics/std/numbers.py +0 -86
- relationalai/shims/executor.py +0 -147
- relationalai/shims/helpers.py +0 -126
- relationalai/shims/hoister.py +0 -221
- relationalai/shims/mm2v0.py +0 -1290
- relationalai/tools/cli/__init__.py +0 -6
- relationalai/tools/cli/cli.py +0 -90
- relationalai/tools/cli/components/__init__.py +0 -5
- relationalai/tools/cli/components/progress_reader.py +0 -1524
- relationalai/tools/cli/components/utils.py +0 -58
- relationalai/tools/cli/config_template.py +0 -45
- relationalai/tools/cli/dev.py +0 -19
- relationalai/tools/typer_debugger.py +0 -93
- relationalai/util/dataclasses.py +0 -43
- relationalai/util/docutils.py +0 -40
- relationalai/util/error.py +0 -199
- relationalai/util/naming.py +0 -145
- relationalai/util/python.py +0 -35
- relationalai/util/runtime.py +0 -156
- relationalai/util/schema.py +0 -197
- relationalai/util/source.py +0 -185
- relationalai/util/structures.py +0 -163
- relationalai/util/tracing.py +0 -261
- relationalai-0.13.0.dev0.dist-info/METADATA +0 -46
- relationalai-0.13.0.dev0.dist-info/RECORD +0 -488
- relationalai-0.13.0.dev0.dist-info/WHEEL +0 -5
- relationalai-0.13.0.dev0.dist-info/entry_points.txt +0 -3
- relationalai-0.13.0.dev0.dist-info/top_level.txt +0 -2
- v0/relationalai/__init__.py +0 -216
- v0/relationalai/clients/__init__.py +0 -5
- v0/relationalai/clients/azure.py +0 -477
- v0/relationalai/clients/client.py +0 -912
- v0/relationalai/clients/config.py +0 -673
- v0/relationalai/clients/direct_access_client.py +0 -118
- v0/relationalai/clients/hash_util.py +0 -31
- v0/relationalai/clients/local.py +0 -571
- v0/relationalai/clients/profile_polling.py +0 -73
- v0/relationalai/clients/result_helpers.py +0 -420
- v0/relationalai/clients/snowflake.py +0 -3869
- v0/relationalai/clients/types.py +0 -113
- v0/relationalai/clients/use_index_poller.py +0 -980
- v0/relationalai/clients/util.py +0 -356
- v0/relationalai/debugging.py +0 -389
- v0/relationalai/dsl.py +0 -1749
- v0/relationalai/early_access/builder/__init__.py +0 -30
- v0/relationalai/early_access/builder/builder/__init__.py +0 -35
- v0/relationalai/early_access/builder/snowflake/__init__.py +0 -12
- v0/relationalai/early_access/builder/std/__init__.py +0 -25
- v0/relationalai/early_access/builder/std/decimals/__init__.py +0 -12
- v0/relationalai/early_access/builder/std/integers/__init__.py +0 -12
- v0/relationalai/early_access/builder/std/math/__init__.py +0 -12
- v0/relationalai/early_access/builder/std/strings/__init__.py +0 -14
- v0/relationalai/early_access/devtools/__init__.py +0 -12
- v0/relationalai/early_access/devtools/benchmark_lqp/__init__.py +0 -12
- v0/relationalai/early_access/devtools/extract_lqp/__init__.py +0 -12
- v0/relationalai/early_access/dsl/adapters/orm/adapter_qb.py +0 -427
- v0/relationalai/early_access/dsl/adapters/orm/parser.py +0 -636
- v0/relationalai/early_access/dsl/adapters/owl/adapter.py +0 -176
- v0/relationalai/early_access/dsl/adapters/owl/parser.py +0 -160
- v0/relationalai/early_access/dsl/bindings/common.py +0 -402
- v0/relationalai/early_access/dsl/bindings/csv.py +0 -170
- v0/relationalai/early_access/dsl/bindings/legacy/binding_models.py +0 -143
- v0/relationalai/early_access/dsl/bindings/snowflake.py +0 -64
- v0/relationalai/early_access/dsl/codegen/binder.py +0 -411
- v0/relationalai/early_access/dsl/codegen/common.py +0 -79
- v0/relationalai/early_access/dsl/codegen/helpers.py +0 -23
- v0/relationalai/early_access/dsl/codegen/relations.py +0 -700
- v0/relationalai/early_access/dsl/codegen/weaver.py +0 -417
- v0/relationalai/early_access/dsl/core/builders/__init__.py +0 -47
- v0/relationalai/early_access/dsl/core/builders/logic.py +0 -19
- v0/relationalai/early_access/dsl/core/builders/scalar_constraint.py +0 -11
- v0/relationalai/early_access/dsl/core/constraints/predicate/atomic.py +0 -455
- v0/relationalai/early_access/dsl/core/constraints/predicate/universal.py +0 -73
- v0/relationalai/early_access/dsl/core/constraints/scalar.py +0 -310
- v0/relationalai/early_access/dsl/core/context.py +0 -13
- v0/relationalai/early_access/dsl/core/cset.py +0 -132
- v0/relationalai/early_access/dsl/core/exprs/__init__.py +0 -116
- v0/relationalai/early_access/dsl/core/exprs/relational.py +0 -18
- v0/relationalai/early_access/dsl/core/exprs/scalar.py +0 -412
- v0/relationalai/early_access/dsl/core/instances.py +0 -44
- v0/relationalai/early_access/dsl/core/logic/__init__.py +0 -193
- v0/relationalai/early_access/dsl/core/logic/aggregation.py +0 -98
- v0/relationalai/early_access/dsl/core/logic/exists.py +0 -223
- v0/relationalai/early_access/dsl/core/logic/helper.py +0 -163
- v0/relationalai/early_access/dsl/core/namespaces.py +0 -32
- v0/relationalai/early_access/dsl/core/relations.py +0 -276
- v0/relationalai/early_access/dsl/core/rules.py +0 -112
- v0/relationalai/early_access/dsl/core/std/__init__.py +0 -45
- v0/relationalai/early_access/dsl/core/temporal/recall.py +0 -6
- v0/relationalai/early_access/dsl/core/types/__init__.py +0 -270
- v0/relationalai/early_access/dsl/core/types/concepts.py +0 -128
- v0/relationalai/early_access/dsl/core/types/constrained/__init__.py +0 -267
- v0/relationalai/early_access/dsl/core/types/constrained/nominal.py +0 -143
- v0/relationalai/early_access/dsl/core/types/constrained/subtype.py +0 -124
- v0/relationalai/early_access/dsl/core/types/standard.py +0 -92
- v0/relationalai/early_access/dsl/core/types/unconstrained.py +0 -50
- v0/relationalai/early_access/dsl/core/types/variables.py +0 -203
- v0/relationalai/early_access/dsl/ir/compiler.py +0 -318
- v0/relationalai/early_access/dsl/ir/executor.py +0 -260
- v0/relationalai/early_access/dsl/ontologies/constraints.py +0 -88
- v0/relationalai/early_access/dsl/ontologies/export.py +0 -30
- v0/relationalai/early_access/dsl/ontologies/models.py +0 -453
- v0/relationalai/early_access/dsl/ontologies/python_printer.py +0 -303
- v0/relationalai/early_access/dsl/ontologies/readings.py +0 -60
- v0/relationalai/early_access/dsl/ontologies/relationships.py +0 -322
- v0/relationalai/early_access/dsl/ontologies/roles.py +0 -87
- v0/relationalai/early_access/dsl/ontologies/subtyping.py +0 -55
- v0/relationalai/early_access/dsl/orm/constraints.py +0 -438
- v0/relationalai/early_access/dsl/orm/measures/dimensions.py +0 -200
- v0/relationalai/early_access/dsl/orm/measures/initializer.py +0 -16
- v0/relationalai/early_access/dsl/orm/measures/measure_rules.py +0 -275
- v0/relationalai/early_access/dsl/orm/measures/measures.py +0 -299
- v0/relationalai/early_access/dsl/orm/measures/role_exprs.py +0 -268
- v0/relationalai/early_access/dsl/orm/models.py +0 -256
- v0/relationalai/early_access/dsl/orm/object_oriented_printer.py +0 -344
- v0/relationalai/early_access/dsl/orm/printer.py +0 -469
- v0/relationalai/early_access/dsl/orm/reasoners.py +0 -480
- v0/relationalai/early_access/dsl/orm/relations.py +0 -19
- v0/relationalai/early_access/dsl/orm/relationships.py +0 -251
- v0/relationalai/early_access/dsl/orm/types.py +0 -42
- v0/relationalai/early_access/dsl/orm/utils.py +0 -79
- v0/relationalai/early_access/dsl/orm/verb.py +0 -204
- v0/relationalai/early_access/dsl/physical_metadata/tables.py +0 -133
- v0/relationalai/early_access/dsl/relations.py +0 -170
- v0/relationalai/early_access/dsl/rulesets.py +0 -69
- v0/relationalai/early_access/dsl/schemas/__init__.py +0 -450
- v0/relationalai/early_access/dsl/schemas/builder.py +0 -48
- v0/relationalai/early_access/dsl/schemas/comp_names.py +0 -51
- v0/relationalai/early_access/dsl/schemas/components.py +0 -203
- v0/relationalai/early_access/dsl/schemas/contexts.py +0 -156
- v0/relationalai/early_access/dsl/schemas/exprs.py +0 -89
- v0/relationalai/early_access/dsl/schemas/fragments.py +0 -464
- v0/relationalai/early_access/dsl/serialization.py +0 -79
- v0/relationalai/early_access/dsl/serialize/exporter.py +0 -163
- v0/relationalai/early_access/dsl/snow/api.py +0 -104
- v0/relationalai/early_access/dsl/snow/common.py +0 -76
- v0/relationalai/early_access/dsl/state_mgmt/__init__.py +0 -129
- v0/relationalai/early_access/dsl/state_mgmt/state_charts.py +0 -125
- v0/relationalai/early_access/dsl/state_mgmt/transitions.py +0 -130
- v0/relationalai/early_access/dsl/types/__init__.py +0 -40
- v0/relationalai/early_access/dsl/types/concepts.py +0 -12
- v0/relationalai/early_access/dsl/types/entities.py +0 -135
- v0/relationalai/early_access/dsl/types/values.py +0 -17
- v0/relationalai/early_access/dsl/utils.py +0 -102
- v0/relationalai/early_access/graphs/__init__.py +0 -13
- v0/relationalai/early_access/lqp/__init__.py +0 -12
- v0/relationalai/early_access/lqp/compiler/__init__.py +0 -12
- v0/relationalai/early_access/lqp/constructors/__init__.py +0 -18
- v0/relationalai/early_access/lqp/executor/__init__.py +0 -12
- v0/relationalai/early_access/lqp/ir/__init__.py +0 -12
- v0/relationalai/early_access/lqp/passes/__init__.py +0 -12
- v0/relationalai/early_access/lqp/pragmas/__init__.py +0 -12
- v0/relationalai/early_access/lqp/primitives/__init__.py +0 -12
- v0/relationalai/early_access/lqp/types/__init__.py +0 -12
- v0/relationalai/early_access/lqp/utils/__init__.py +0 -12
- v0/relationalai/early_access/lqp/validators/__init__.py +0 -12
- v0/relationalai/early_access/metamodel/__init__.py +0 -58
- v0/relationalai/early_access/metamodel/builtins/__init__.py +0 -12
- v0/relationalai/early_access/metamodel/compiler/__init__.py +0 -12
- v0/relationalai/early_access/metamodel/dependency/__init__.py +0 -12
- v0/relationalai/early_access/metamodel/factory/__init__.py +0 -17
- v0/relationalai/early_access/metamodel/helpers/__init__.py +0 -12
- v0/relationalai/early_access/metamodel/ir/__init__.py +0 -14
- v0/relationalai/early_access/metamodel/rewrite/__init__.py +0 -7
- v0/relationalai/early_access/metamodel/typer/__init__.py +0 -3
- v0/relationalai/early_access/metamodel/typer/typer/__init__.py +0 -12
- v0/relationalai/early_access/metamodel/types/__init__.py +0 -15
- v0/relationalai/early_access/metamodel/util/__init__.py +0 -15
- v0/relationalai/early_access/metamodel/visitor/__init__.py +0 -12
- v0/relationalai/early_access/rel/__init__.py +0 -12
- v0/relationalai/early_access/rel/executor/__init__.py +0 -12
- v0/relationalai/early_access/rel/rel_utils/__init__.py +0 -12
- v0/relationalai/early_access/rel/rewrite/__init__.py +0 -7
- v0/relationalai/early_access/solvers/__init__.py +0 -19
- v0/relationalai/early_access/sql/__init__.py +0 -11
- v0/relationalai/early_access/sql/executor/__init__.py +0 -3
- v0/relationalai/early_access/sql/rewrite/__init__.py +0 -3
- v0/relationalai/early_access/tests/logging/__init__.py +0 -12
- v0/relationalai/early_access/tests/test_snapshot_base/__init__.py +0 -12
- v0/relationalai/early_access/tests/utils/__init__.py +0 -12
- v0/relationalai/environments/__init__.py +0 -35
- v0/relationalai/environments/base.py +0 -381
- v0/relationalai/environments/colab.py +0 -14
- v0/relationalai/environments/generic.py +0 -71
- v0/relationalai/environments/ipython.py +0 -68
- v0/relationalai/environments/jupyter.py +0 -9
- v0/relationalai/environments/snowbook.py +0 -169
- v0/relationalai/errors.py +0 -2455
- v0/relationalai/experimental/SF.py +0 -38
- v0/relationalai/experimental/inspect.py +0 -47
- v0/relationalai/experimental/pathfinder/__init__.py +0 -158
- v0/relationalai/experimental/pathfinder/api.py +0 -160
- v0/relationalai/experimental/pathfinder/automaton.py +0 -584
- v0/relationalai/experimental/pathfinder/bridge.py +0 -226
- v0/relationalai/experimental/pathfinder/compiler.py +0 -416
- v0/relationalai/experimental/pathfinder/datalog.py +0 -214
- v0/relationalai/experimental/pathfinder/diagnostics.py +0 -56
- v0/relationalai/experimental/pathfinder/filter.py +0 -236
- v0/relationalai/experimental/pathfinder/glushkov.py +0 -439
- v0/relationalai/experimental/pathfinder/options.py +0 -265
- v0/relationalai/experimental/pathfinder/rpq.py +0 -344
- v0/relationalai/experimental/pathfinder/transition.py +0 -200
- v0/relationalai/experimental/pathfinder/utils.py +0 -26
- v0/relationalai/experimental/paths/api.py +0 -143
- v0/relationalai/experimental/paths/benchmarks/grid_graph.py +0 -37
- v0/relationalai/experimental/paths/examples/basic_example.py +0 -40
- v0/relationalai/experimental/paths/examples/minimal_engine_warmup.py +0 -3
- v0/relationalai/experimental/paths/examples/movie_example.py +0 -77
- v0/relationalai/experimental/paths/examples/paths_benchmark.py +0 -115
- v0/relationalai/experimental/paths/examples/paths_example.py +0 -116
- v0/relationalai/experimental/paths/examples/pattern_to_automaton.py +0 -28
- v0/relationalai/experimental/paths/find_paths_via_automaton.py +0 -85
- v0/relationalai/experimental/paths/graph.py +0 -185
- v0/relationalai/experimental/paths/path_algorithms/find_paths.py +0 -280
- v0/relationalai/experimental/paths/path_algorithms/one_sided_ball_repetition.py +0 -26
- v0/relationalai/experimental/paths/path_algorithms/one_sided_ball_upto.py +0 -111
- v0/relationalai/experimental/paths/path_algorithms/single.py +0 -59
- v0/relationalai/experimental/paths/path_algorithms/two_sided_balls_repetition.py +0 -39
- v0/relationalai/experimental/paths/path_algorithms/two_sided_balls_upto.py +0 -103
- v0/relationalai/experimental/paths/path_algorithms/usp-old.py +0 -130
- v0/relationalai/experimental/paths/path_algorithms/usp-tuple.py +0 -183
- v0/relationalai/experimental/paths/path_algorithms/usp.py +0 -150
- v0/relationalai/experimental/paths/product_graph.py +0 -93
- v0/relationalai/experimental/paths/rpq/automaton.py +0 -584
- v0/relationalai/experimental/paths/rpq/diagnostics.py +0 -56
- v0/relationalai/experimental/paths/rpq/rpq.py +0 -378
- v0/relationalai/experimental/paths/tests/tests_limit_sp_max_length.py +0 -90
- v0/relationalai/experimental/paths/tests/tests_limit_sp_multiple.py +0 -119
- v0/relationalai/experimental/paths/tests/tests_limit_sp_single.py +0 -104
- v0/relationalai/experimental/paths/tests/tests_limit_walks_multiple.py +0 -113
- v0/relationalai/experimental/paths/tests/tests_limit_walks_single.py +0 -149
- v0/relationalai/experimental/paths/tests/tests_one_sided_ball_repetition_multiple.py +0 -70
- v0/relationalai/experimental/paths/tests/tests_one_sided_ball_repetition_single.py +0 -64
- v0/relationalai/experimental/paths/tests/tests_one_sided_ball_upto_multiple.py +0 -115
- v0/relationalai/experimental/paths/tests/tests_one_sided_ball_upto_single.py +0 -75
- v0/relationalai/experimental/paths/tests/tests_single_paths.py +0 -152
- v0/relationalai/experimental/paths/tests/tests_single_walks.py +0 -208
- v0/relationalai/experimental/paths/tests/tests_single_walks_undirected.py +0 -297
- v0/relationalai/experimental/paths/tests/tests_two_sided_balls_repetition_multiple.py +0 -107
- v0/relationalai/experimental/paths/tests/tests_two_sided_balls_repetition_single.py +0 -76
- v0/relationalai/experimental/paths/tests/tests_two_sided_balls_upto_multiple.py +0 -76
- v0/relationalai/experimental/paths/tests/tests_two_sided_balls_upto_single.py +0 -110
- v0/relationalai/experimental/paths/tests/tests_usp_nsp_multiple.py +0 -229
- v0/relationalai/experimental/paths/tests/tests_usp_nsp_single.py +0 -108
- v0/relationalai/experimental/paths/tree_agg.py +0 -168
- v0/relationalai/experimental/paths/utilities/iterators.py +0 -27
- v0/relationalai/experimental/paths/utilities/prefix_sum.py +0 -91
- v0/relationalai/experimental/solvers.py +0 -1087
- v0/relationalai/loaders/csv.py +0 -195
- v0/relationalai/loaders/loader.py +0 -177
- v0/relationalai/loaders/types.py +0 -23
- v0/relationalai/rel_emitter.py +0 -373
- v0/relationalai/rel_utils.py +0 -185
- v0/relationalai/semantics/__init__.py +0 -29
- v0/relationalai/semantics/devtools/benchmark_lqp.py +0 -536
- v0/relationalai/semantics/devtools/compilation_manager.py +0 -294
- v0/relationalai/semantics/devtools/extract_lqp.py +0 -110
- v0/relationalai/semantics/internal/internal.py +0 -3785
- v0/relationalai/semantics/internal/snowflake.py +0 -324
- v0/relationalai/semantics/lqp/builtins.py +0 -16
- v0/relationalai/semantics/lqp/compiler.py +0 -22
- v0/relationalai/semantics/lqp/constructors.py +0 -68
- v0/relationalai/semantics/lqp/executor.py +0 -469
- v0/relationalai/semantics/lqp/intrinsics.py +0 -24
- v0/relationalai/semantics/lqp/model2lqp.py +0 -839
- v0/relationalai/semantics/lqp/passes.py +0 -680
- v0/relationalai/semantics/lqp/primitives.py +0 -252
- v0/relationalai/semantics/lqp/result_helpers.py +0 -202
- v0/relationalai/semantics/lqp/rewrite/annotate_constraints.py +0 -57
- v0/relationalai/semantics/lqp/rewrite/cdc.py +0 -216
- v0/relationalai/semantics/lqp/rewrite/extract_common.py +0 -338
- v0/relationalai/semantics/lqp/rewrite/extract_keys.py +0 -449
- v0/relationalai/semantics/lqp/rewrite/function_annotations.py +0 -114
- v0/relationalai/semantics/lqp/rewrite/functional_dependencies.py +0 -314
- v0/relationalai/semantics/lqp/rewrite/quantify_vars.py +0 -296
- v0/relationalai/semantics/lqp/rewrite/splinter.py +0 -76
- v0/relationalai/semantics/lqp/types.py +0 -101
- v0/relationalai/semantics/lqp/utils.py +0 -160
- v0/relationalai/semantics/lqp/validators.py +0 -57
- v0/relationalai/semantics/metamodel/__init__.py +0 -40
- v0/relationalai/semantics/metamodel/builtins.py +0 -774
- v0/relationalai/semantics/metamodel/compiler.py +0 -133
- v0/relationalai/semantics/metamodel/dependency.py +0 -862
- v0/relationalai/semantics/metamodel/executor.py +0 -61
- v0/relationalai/semantics/metamodel/factory.py +0 -287
- v0/relationalai/semantics/metamodel/helpers.py +0 -361
- v0/relationalai/semantics/metamodel/rewrite/discharge_constraints.py +0 -39
- v0/relationalai/semantics/metamodel/rewrite/dnf_union_splitter.py +0 -210
- v0/relationalai/semantics/metamodel/rewrite/extract_nested_logicals.py +0 -78
- v0/relationalai/semantics/metamodel/rewrite/flatten.py +0 -549
- v0/relationalai/semantics/metamodel/rewrite/format_outputs.py +0 -165
- v0/relationalai/semantics/metamodel/typer/checker.py +0 -353
- v0/relationalai/semantics/metamodel/typer/typer.py +0 -1395
- v0/relationalai/semantics/metamodel/util.py +0 -505
- v0/relationalai/semantics/reasoners/__init__.py +0 -10
- v0/relationalai/semantics/reasoners/graph/__init__.py +0 -37
- v0/relationalai/semantics/reasoners/graph/core.py +0 -9020
- v0/relationalai/semantics/reasoners/optimization/__init__.py +0 -68
- v0/relationalai/semantics/reasoners/optimization/common.py +0 -88
- v0/relationalai/semantics/reasoners/optimization/solvers_dev.py +0 -568
- v0/relationalai/semantics/reasoners/optimization/solvers_pb.py +0 -1163
- v0/relationalai/semantics/rel/builtins.py +0 -40
- v0/relationalai/semantics/rel/compiler.py +0 -989
- v0/relationalai/semantics/rel/executor.py +0 -359
- v0/relationalai/semantics/rel/rel.py +0 -482
- v0/relationalai/semantics/rel/rel_utils.py +0 -276
- v0/relationalai/semantics/snowflake/__init__.py +0 -3
- v0/relationalai/semantics/sql/compiler.py +0 -2503
- v0/relationalai/semantics/sql/executor/duck_db.py +0 -52
- v0/relationalai/semantics/sql/executor/result_helpers.py +0 -64
- v0/relationalai/semantics/sql/executor/snowflake.py +0 -145
- v0/relationalai/semantics/sql/rewrite/denormalize.py +0 -222
- v0/relationalai/semantics/sql/rewrite/double_negation.py +0 -49
- v0/relationalai/semantics/sql/rewrite/recursive_union.py +0 -127
- v0/relationalai/semantics/sql/rewrite/sort_output_query.py +0 -246
- v0/relationalai/semantics/sql/sql.py +0 -504
- v0/relationalai/semantics/std/__init__.py +0 -54
- v0/relationalai/semantics/std/constraints.py +0 -43
- v0/relationalai/semantics/std/datetime.py +0 -363
- v0/relationalai/semantics/std/decimals.py +0 -62
- v0/relationalai/semantics/std/floats.py +0 -7
- v0/relationalai/semantics/std/integers.py +0 -22
- v0/relationalai/semantics/std/math.py +0 -141
- v0/relationalai/semantics/std/pragmas.py +0 -11
- v0/relationalai/semantics/std/re.py +0 -83
- v0/relationalai/semantics/std/std.py +0 -14
- v0/relationalai/semantics/std/strings.py +0 -63
- v0/relationalai/semantics/tests/__init__.py +0 -0
- v0/relationalai/semantics/tests/test_snapshot_abstract.py +0 -143
- v0/relationalai/semantics/tests/test_snapshot_base.py +0 -9
- v0/relationalai/semantics/tests/utils.py +0 -46
- v0/relationalai/std/__init__.py +0 -70
- v0/relationalai/tools/__init__.py +0 -0
- v0/relationalai/tools/cli.py +0 -1940
- v0/relationalai/tools/cli_controls.py +0 -1826
- v0/relationalai/tools/cli_helpers.py +0 -390
- v0/relationalai/tools/debugger.py +0 -183
- v0/relationalai/tools/debugger_client.py +0 -109
- v0/relationalai/tools/debugger_server.py +0 -302
- v0/relationalai/tools/dev.py +0 -685
- v0/relationalai/tools/qb_debugger.py +0 -425
- v0/relationalai/util/clean_up_databases.py +0 -95
- v0/relationalai/util/format.py +0 -123
- v0/relationalai/util/list_databases.py +0 -9
- v0/relationalai/util/otel_configuration.py +0 -25
- v0/relationalai/util/otel_handler.py +0 -484
- v0/relationalai/util/snowflake_handler.py +0 -88
- v0/relationalai/util/span_format_test.py +0 -43
- v0/relationalai/util/span_tracker.py +0 -207
- v0/relationalai/util/spans_file_handler.py +0 -72
- v0/relationalai/util/tracing_handler.py +0 -34
- /relationalai/{semantics/frontend → analysis}/__init__.py +0 -0
- {v0/relationalai → relationalai}/analysis/mechanistic.py +0 -0
- {v0/relationalai → relationalai}/analysis/whynot.py +0 -0
- /relationalai/{shims → auth}/__init__.py +0 -0
- {v0/relationalai → relationalai}/auth/jwt_generator.py +0 -0
- {v0/relationalai → relationalai}/auth/oauth_callback_server.py +0 -0
- {v0/relationalai → relationalai}/auth/token_handler.py +0 -0
- {v0/relationalai → relationalai}/auth/util.py +0 -0
- {v0/relationalai/clients → relationalai/clients/resources/snowflake}/cache_store.py +0 -0
- {v0/relationalai → relationalai}/compiler.py +0 -0
- {v0/relationalai → relationalai}/dependencies.py +0 -0
- {v0/relationalai → relationalai}/docutils.py +0 -0
- {v0/relationalai/analysis → relationalai/early_access}/__init__.py +0 -0
- {v0/relationalai → relationalai}/early_access/dsl/__init__.py +0 -0
- {v0/relationalai/auth → relationalai/early_access/dsl/adapters}/__init__.py +0 -0
- {v0/relationalai/early_access → relationalai/early_access/dsl/adapters/orm}/__init__.py +0 -0
- {v0/relationalai → relationalai}/early_access/dsl/adapters/orm/model.py +0 -0
- {v0/relationalai/early_access/dsl/adapters → relationalai/early_access/dsl/adapters/owl}/__init__.py +0 -0
- {v0/relationalai → relationalai}/early_access/dsl/adapters/owl/model.py +0 -0
- {v0/relationalai/early_access/dsl/adapters/orm → relationalai/early_access/dsl/bindings}/__init__.py +0 -0
- {v0/relationalai/early_access/dsl/adapters/owl → relationalai/early_access/dsl/bindings/legacy}/__init__.py +0 -0
- {v0/relationalai/early_access/dsl/bindings → relationalai/early_access/dsl/codegen}/__init__.py +0 -0
- {v0/relationalai → relationalai}/early_access/dsl/constants.py +0 -0
- {v0/relationalai → relationalai}/early_access/dsl/core/__init__.py +0 -0
- {v0/relationalai → relationalai}/early_access/dsl/core/constraints/__init__.py +0 -0
- {v0/relationalai → relationalai}/early_access/dsl/core/constraints/predicate/__init__.py +0 -0
- {v0/relationalai → relationalai}/early_access/dsl/core/stack.py +0 -0
- {v0/relationalai/early_access/dsl/bindings/legacy → relationalai/early_access/dsl/core/temporal}/__init__.py +0 -0
- {v0/relationalai → relationalai}/early_access/dsl/core/utils.py +0 -0
- {v0/relationalai/early_access/dsl/codegen → relationalai/early_access/dsl/ir}/__init__.py +0 -0
- {v0/relationalai/early_access/dsl/core/temporal → relationalai/early_access/dsl/ontologies}/__init__.py +0 -0
- {v0/relationalai → relationalai}/early_access/dsl/ontologies/raw_source.py +0 -0
- {v0/relationalai/early_access/dsl/ir → relationalai/early_access/dsl/orm}/__init__.py +0 -0
- {v0/relationalai/early_access/dsl/ontologies → relationalai/early_access/dsl/orm/measures}/__init__.py +0 -0
- {v0/relationalai → relationalai}/early_access/dsl/orm/reasoner_errors.py +0 -0
- {v0/relationalai/early_access/dsl/orm → relationalai/early_access/dsl/physical_metadata}/__init__.py +0 -0
- {v0/relationalai/early_access/dsl/orm/measures → relationalai/early_access/dsl/serialize}/__init__.py +0 -0
- {v0/relationalai → relationalai}/early_access/dsl/serialize/binding_model.py +0 -0
- {v0/relationalai → relationalai}/early_access/dsl/serialize/model.py +0 -0
- {v0/relationalai/early_access/dsl/physical_metadata → relationalai/early_access/dsl/snow}/__init__.py +0 -0
- {v0/relationalai → relationalai}/early_access/tests/__init__.py +0 -0
- {v0/relationalai → relationalai}/environments/ci.py +0 -0
- {v0/relationalai → relationalai}/environments/hex.py +0 -0
- {v0/relationalai → relationalai}/environments/terminal.py +0 -0
- {v0/relationalai → relationalai}/experimental/__init__.py +0 -0
- {v0/relationalai → relationalai}/experimental/graphs.py +0 -0
- {v0/relationalai → relationalai}/experimental/paths/__init__.py +0 -0
- {v0/relationalai → relationalai}/experimental/paths/benchmarks/__init__.py +0 -0
- {v0/relationalai → relationalai}/experimental/paths/path_algorithms/__init__.py +0 -0
- {v0/relationalai → relationalai}/experimental/paths/rpq/__init__.py +0 -0
- {v0/relationalai → relationalai}/experimental/paths/rpq/filter.py +0 -0
- {v0/relationalai → relationalai}/experimental/paths/rpq/glushkov.py +0 -0
- {v0/relationalai → relationalai}/experimental/paths/rpq/transition.py +0 -0
- {v0/relationalai → relationalai}/experimental/paths/utilities/__init__.py +0 -0
- {v0/relationalai → relationalai}/experimental/paths/utilities/utilities.py +0 -0
- {v0/relationalai/early_access/dsl/serialize → relationalai/loaders}/__init__.py +0 -0
- {v0/relationalai → relationalai}/metagen.py +0 -0
- {v0/relationalai → relationalai}/metamodel.py +0 -0
- {v0/relationalai → relationalai}/rel.py +0 -0
- {v0/relationalai → relationalai}/semantics/devtools/__init__.py +0 -0
- {v0/relationalai → relationalai}/semantics/internal/__init__.py +0 -0
- {v0/relationalai → relationalai}/semantics/internal/annotations.py +0 -0
- {v0/relationalai → relationalai}/semantics/lqp/__init__.py +0 -0
- {v0/relationalai → relationalai}/semantics/lqp/ir.py +0 -0
- {v0/relationalai → relationalai}/semantics/lqp/pragmas.py +0 -0
- {v0/relationalai → relationalai}/semantics/lqp/rewrite/__init__.py +0 -0
- {v0/relationalai → relationalai}/semantics/metamodel/dataflow.py +0 -0
- {v0/relationalai → relationalai}/semantics/metamodel/ir.py +0 -0
- {v0/relationalai → relationalai}/semantics/metamodel/rewrite/__init__.py +0 -0
- {v0/relationalai → relationalai}/semantics/metamodel/typer/__init__.py +0 -0
- {v0/relationalai → relationalai}/semantics/metamodel/types.py +0 -0
- {v0/relationalai → relationalai}/semantics/metamodel/visitor.py +0 -0
- {v0/relationalai → relationalai}/semantics/reasoners/experimental/__init__.py +0 -0
- {v0/relationalai → relationalai}/semantics/rel/__init__.py +0 -0
- {v0/relationalai → relationalai}/semantics/sql/__init__.py +0 -0
- {v0/relationalai → relationalai}/semantics/sql/executor/__init__.py +0 -0
- {v0/relationalai → relationalai}/semantics/sql/rewrite/__init__.py +0 -0
- {v0/relationalai/early_access/dsl/snow → relationalai/semantics/tests}/__init__.py +0 -0
- {v0/relationalai → relationalai}/semantics/tests/logging.py +0 -0
- {v0/relationalai → relationalai}/std/aggregates.py +0 -0
- {v0/relationalai → relationalai}/std/dates.py +0 -0
- {v0/relationalai → relationalai}/std/graphs.py +0 -0
- {v0/relationalai → relationalai}/std/inspect.py +0 -0
- {v0/relationalai → relationalai}/std/math.py +0 -0
- {v0/relationalai → relationalai}/std/re.py +0 -0
- {v0/relationalai → relationalai}/std/strings.py +0 -0
- {v0/relationalai/loaders → relationalai/tools}/__init__.py +0 -0
- {v0/relationalai → relationalai}/tools/cleanup_snapshots.py +0 -0
- {v0/relationalai → relationalai}/tools/constants.py +0 -0
- {v0/relationalai → relationalai}/tools/query_utils.py +0 -0
- {v0/relationalai → relationalai}/tools/snapshot_viewer.py +0 -0
- {v0/relationalai → relationalai}/util/__init__.py +0 -0
- {v0/relationalai → relationalai}/util/constants.py +0 -0
- {v0/relationalai → relationalai}/util/graph.py +0 -0
- {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)
|