quantum-framework 0.17.0__tar.gz → 0.19.0__tar.gz
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.
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/PKG-INFO +1 -1
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/pyproject.toml +1 -1
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/check.py +23 -2
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/runner.py +15 -1
- quantum_framework-0.19.0/quantum/cli/schema_plan.py +273 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/ast_nodes.py +6 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/ui_engine/src/ast_nodes.py +45 -1
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/ui_engine/src/parser.py +55 -1
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/data/query_parser.py +21 -1
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/forms/action_parser.py +15 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/action_handler.py +85 -22
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/component.py +5 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/database_service.py +12 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/data/query_executor.py +38 -4
- quantum_framework-0.19.0/quantum/runtime/history.py +203 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/renderer.py +2 -0
- quantum_framework-0.19.0/quantum/runtime/table_params.py +163 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/ui_console.py +48 -3
- quantum_framework-0.19.0/quantum/runtime/ui_form_rules.py +193 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/ui_html_adapter.py +1 -1
- quantum_framework-0.19.0/quantum/runtime/ui_page_renderer.py +423 -0
- quantum_framework-0.19.0/quantum/runtime/ui_pager.py +73 -0
- quantum_framework-0.19.0/quantum/runtime/ui_table_edit.py +139 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/ui_view.py +84 -10
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/web_server.py +92 -1
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_framework.egg-info/PKG-INFO +1 -1
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_framework.egg-info/SOURCES.txt +6 -0
- quantum_framework-0.17.0/quantum/runtime/ui_page_renderer.py +0 -232
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/LICENSE +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/README.md +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/admin.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/commands/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/commands/build.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/commands/dev.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/commands/docs.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/commands/lint.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/commands/migrate.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/commands/new.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/commands/serve.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/commands/test.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/deploy.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/hot_reload.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/jobs.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/mq.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/pkg.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/server_process.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/utils.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/compiler/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/compiler/base_generator.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/compiler/cli.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/compiler/expression_transformer.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/compiler/javascript/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/compiler/javascript/generator.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/compiler/optimizer.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/compiler/python/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/compiler/python/generator.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/compiler/python/runtime.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/compiler/transpiler.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/config_env.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/docs_links.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/expression_diagnostics.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/expression_stdlib.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/expressions.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/agents/src/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/agents/src/ast_node.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/conditionals/src/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/conditionals/src/ast_node.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/data_import/src/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/data_import/src/ast_node.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/data_import/src/runtime.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/dump/src/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/dump/src/ast_node.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/dump/src/parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/dump/src/runtime.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/functions/src/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/functions/src/ast_node.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/game_engine_2d/src/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/game_engine_2d/src/ast_nodes.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/game_engine_2d/src/parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/invocation/src/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/invocation/src/ast_node.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/invocation/src/runtime.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/knowledge_base/src/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/knowledge_base/src/ast_node.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/knowledge_base/src/parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/logging/src/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/logging/src/ast_node.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/logging/src/parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/logging/src/runtime.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/loops/src/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/loops/src/ast_node.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/query/src/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/query/src/query_validators.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/state_management/src/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/state_management/src/ast_node.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/terminal_engine/src/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/terminal_engine/src/ast_nodes.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/terminal_engine/src/parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/testing_engine/src/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/testing_engine/src/ast_nodes.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/testing_engine/src/parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/theming/src/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/theming/src/ast_node.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/theming/src/presets.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/ui_engine/src/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/websocket/src/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/websocket/src/ast_node.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/guards.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/html_compat.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parser_registry.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/ai/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/ai/agent_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/ai/knowledge_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/ai/llm_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/ai/team_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/base.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/composition/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/composition/import_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/composition/slot_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/control_flow/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/control_flow/if_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/control_flow/loop_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/control_flow/set_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/data/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/data/data_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/data/invoke_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/data/transaction_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/events/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/events/dispatch_event_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/forms/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/forms/flash_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/forms/redirect_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/functions/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/functions/function_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/functions/param_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/functions/return_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/html/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/html/component_call_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/html/html_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/jobs/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/jobs/job_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/jobs/schedule_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/jobs/thread_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/messaging/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/messaging/message_ack_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/messaging/message_nack_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/messaging/message_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/messaging/queue_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/messaging/websocket_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/routing/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/routing/route_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/scripting/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/scripting/pyclass_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/scripting/pydecorator_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/scripting/pyimport_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/scripting/python_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/services/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/services/dump_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/services/file_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/services/log_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/services/mail_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/tiers.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/xml_lines.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/packages/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/packages/manager.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/packages/manifest.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/packages/registry.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/packages/resolver.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/plugins/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/plugins/hooks.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/plugins/loader.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/plugins/manifest.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/plugins/registry.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/adapters/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/adapters/memory_adapter.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/adapters/rabbitmq_adapter.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/adapters/redis_adapter.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/adapters/sqlite_adapter.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/agent_service.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/ast_cache.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/auth_service.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/component_composer.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/component_resolver.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/conditions.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/db_schema.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/dev_panel.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/email_service.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/error_handler.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/execution_context.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executor_registry.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/ai/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/ai/agent_executor.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/ai/knowledge_executor.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/ai/llm_executor.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/ai/team_executor.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/base.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/control_flow/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/control_flow/if_executor.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/control_flow/loop_executor.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/control_flow/redirect_executor.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/control_flow/set_executor.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/data/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/data/data_executor.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/data/invoke_executor.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/data/transaction_executor.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/jobs/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/jobs/job_executor.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/jobs/schedule_executor.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/jobs/thread_executor.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/messaging/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/messaging/message_ack_executor.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/messaging/message_executor.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/messaging/queue_executor.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/messaging/websocket_executor.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/scripting/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/scripting/pyclass_executor.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/scripting/pyimport_executor.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/scripting/python_executor.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/services/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/services/dump_executor.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/services/file_executor.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/services/log_executor.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/services/mail_executor.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/expression_cache.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/file_upload_service.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/function_registry.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/game_builder.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/game_code_generator.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/game_templates.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/godot_code_generator.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/godot_templates.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/job_executor.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/knowledge_service.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/llm_cache.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/llm_providers.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/llm_service.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/local_server.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/logging_setup.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/message_broker.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/message_queue_service.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/param_validation.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/pypy_compat.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/python_bridge.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/query_validators.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/service_container.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/terminal_builder.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/terminal_code_generator.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/terminal_templates.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/testing_builder.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/testing_code_generator.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/testing_templates.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/ui_builder.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/ui_desktop.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/ui_html_templates.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/ui_mobile_adapter.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/ui_mobile_templates.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/ui_textual_adapter.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/ui_textual_templates.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/ui_tokens.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/ui_validator.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/validators.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/websocket_adapter.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/websocket_service.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/websocket_transport.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/wsgi.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/services.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/utils/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/assets/quantum-admin-connectors.js +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/assets/quantum-admin.css +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/assets/quantum-source-highlight.js +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/audit_service.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/auth_service.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/component_discovery.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/config_generator.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/connector_service.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/crud.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/database.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/db_setup_service.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/deploy_service.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/docker_service.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/environment_service.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/git_service.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/health_service.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/job_service.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/log_service.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/main.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/models.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/pipeline_service.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/resource_manager.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/schemas.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/secret_manager.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/settings_service.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/template_service.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/test_execution_service.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/test_generator.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/webhook_service.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/websocket_manager.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/_layout/AdminShell.q +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/agents.q +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/app/[name].q +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/applications.q +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/component/[...path].q +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/components.q +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/connectors.q +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/dashboard.q +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/database.q +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/features.q +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/index.q +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/jobs.q +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/login.q +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/logout.q +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/settings.q +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/source.q +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/tests.q +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/run.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/services/__init__.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/services/_base.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/services/_component_test_generator.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/services/_yaml_editor.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/services/apps.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/services/auth.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/services/components.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/services/connectors.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/services/projects.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/services/repository.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/services/settings.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/services/yaml_import.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_framework.egg-info/dependency_links.txt +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_framework.egg-info/entry_points.txt +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_framework.egg-info/requires.txt +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_framework.egg-info/top_level.txt +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/setup.cfg +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_agent.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_animations.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_ast_cache.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_cli_commands.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_component_library.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_expression_cache.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_form_validation.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_game_codegen.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_game_e2e.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_game_engine.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_game_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_godot_codegen.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_job_executor.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_llm_providers.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_message_broker.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_message_queue.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_mobile_target.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_multi_agent.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_packages.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_plugins.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_pypy_compat.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_python_scripting.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_terminal_codegen.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_terminal_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_testing_codegen.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_testing_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_theming.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_ui_html_adapter.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_ui_parity.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_ui_parser.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_ui_textual_adapter.py +0 -0
- {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_websocket.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "quantum-framework"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.19.0"
|
|
8
8
|
description = "Full-stack declarative framework for building web, desktop, and mobile applications"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -102,9 +102,30 @@ class ProjectChecker:
|
|
|
102
102
|
self._fields(ast, rel, colunas, {nome: nome for nome in colunas}, None)
|
|
103
103
|
|
|
104
104
|
def _queries(self, node, rel: str, colunas: Dict[str, List[str]]) -> None:
|
|
105
|
-
from quantum.core.ast_nodes import QueryNode
|
|
106
|
-
from quantum.runtime.db_schema import check_sql
|
|
105
|
+
from quantum.core.ast_nodes import ActionNode, QueryNode
|
|
106
|
+
from quantum.runtime.db_schema import check_sql, read_schema
|
|
107
107
|
for filho in _children(node):
|
|
108
|
+
if isinstance(filho, ActionNode) and getattr(filho, 'table', None):
|
|
109
|
+
# UI-10: the table the action takes its params from exists, with those columns.
|
|
110
|
+
ds = filho.table_datasource
|
|
111
|
+
if ds not in self.datasources:
|
|
112
|
+
self.problems.append(Problem(rel, getattr(filho, 'source_line', None),
|
|
113
|
+
f'<q:action name="{filho.name}">: datasource {ds!r} is not '
|
|
114
|
+
f'declared in quantum.config.yaml'))
|
|
115
|
+
elif self._connection(ds) is not None:
|
|
116
|
+
esquema = read_schema(self._connection(ds))
|
|
117
|
+
tabela = esquema.get(filho.table)
|
|
118
|
+
if tabela is None:
|
|
119
|
+
self.problems.append(Problem(rel, getattr(filho, 'source_line', None),
|
|
120
|
+
f'<q:action name="{filho.name}" table="{filho.table}">: '
|
|
121
|
+
f'no such table (tables: {", ".join(esquema) or "none"})'))
|
|
122
|
+
else:
|
|
123
|
+
for coluna in filho.table_columns or []:
|
|
124
|
+
if coluna not in tabela.columns:
|
|
125
|
+
self.problems.append(Problem(
|
|
126
|
+
rel, getattr(filho, 'source_line', None),
|
|
127
|
+
f'<q:action name="{filho.name}" columns=...>: table "{filho.table}" '
|
|
128
|
+
f'has no column "{coluna}"'))
|
|
108
129
|
if isinstance(filho, QueryNode):
|
|
109
130
|
ds = filho.datasource
|
|
110
131
|
if not ds or ds.startswith('knowledge:') or getattr(filho, 'source', None):
|
|
@@ -399,6 +399,15 @@ Examples:
|
|
|
399
399
|
migrate_create_parser = migrate_subparsers.add_parser('create', help='Create a new migration')
|
|
400
400
|
migrate_create_parser.add_argument('name', help='Migration name')
|
|
401
401
|
|
|
402
|
+
# M6 (DB-10): schema.sql as the source of truth
|
|
403
|
+
migrate_plan_parser = migrate_subparsers.add_parser(
|
|
404
|
+
'plan', help='Compare schema.sql with the migrations and plan the migration')
|
|
405
|
+
migrate_plan_parser.add_argument('--schema', default='schema.sql', help='Schema file (default: schema.sql)')
|
|
406
|
+
migrate_plan_parser.add_argument('--write', metavar='NAME', help='Save the plan as migration V00N_NAME')
|
|
407
|
+
migrate_plan_parser.add_argument('--yes', action='store_true', help='Do not ask before writing')
|
|
408
|
+
migrate_plan_parser.add_argument('--allow-data-loss', action='store_true',
|
|
409
|
+
help='Write a plan that drops tables or columns')
|
|
410
|
+
|
|
402
411
|
# Parse arguments
|
|
403
412
|
args = parser.parse_args()
|
|
404
413
|
|
|
@@ -541,13 +550,18 @@ Examples:
|
|
|
541
550
|
if failed:
|
|
542
551
|
sys.exit(1)
|
|
543
552
|
|
|
553
|
+
elif args.migrate_command == 'plan':
|
|
554
|
+
from quantum.cli.schema_plan import run_plan
|
|
555
|
+
sys.exit(run_plan(Path.cwd(), args.schema, args.write, args.yes, args.allow_data_loss,
|
|
556
|
+
interactive=sys.stdin.isatty()))
|
|
557
|
+
|
|
544
558
|
elif args.migrate_command == 'create':
|
|
545
559
|
filepath = runner.create(args.name)
|
|
546
560
|
print(f"Created migration: {filepath}")
|
|
547
561
|
print(f"Also created rollback: {filepath.with_suffix('.down.sql')}")
|
|
548
562
|
|
|
549
563
|
else:
|
|
550
|
-
print("Usage: quantum migrate <status|up|down|create> [options]")
|
|
564
|
+
print("Usage: quantum migrate <status|up|down|create|plan> [options]")
|
|
551
565
|
sys.exit(1)
|
|
552
566
|
|
|
553
567
|
sys.exit(0)
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""`quantum migrate plan`: a declarative schema (M6, DB-10).
|
|
2
|
+
|
|
3
|
+
You edit `schema.sql` — the tables as they should be. `quantum migrate plan`
|
|
4
|
+
compares it with the schema the migrations produce, shows what changes, marks
|
|
5
|
+
what loses data, and `--write` saves the migration (and its rollback) after
|
|
6
|
+
confirmation. `quantum migrate up` applies it as any other.
|
|
7
|
+
|
|
8
|
+
Both sides are built in in-memory SQLite databases — the migrations applied in
|
|
9
|
+
order, and schema.sql — and compared with the schema reader of `quantum check`
|
|
10
|
+
(quantum/runtime/db_schema.py). The development database is not read: the
|
|
11
|
+
plan is the same on any machine and in CI.
|
|
12
|
+
|
|
13
|
+
SQLite can add a column but not change one, so any other change to a table
|
|
14
|
+
rebuilds it: create the new table, copy the columns both have, drop the old,
|
|
15
|
+
rename. Before a plan is written it is checked: applied to the migrations'
|
|
16
|
+
schema it must produce exactly schema.sql's.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import re
|
|
20
|
+
import sqlite3
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Dict, List, Optional, Tuple
|
|
24
|
+
|
|
25
|
+
from quantum.runtime.db_schema import Column, read_schema
|
|
26
|
+
|
|
27
|
+
# Tables that belong to Quantum, not to the app's schema.
|
|
28
|
+
INTERNAL_TABLES = {'_migrations', 'quantum_history'}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class SchemaPlanError(Exception):
|
|
32
|
+
"""The plan cannot be made (no schema.sql, invalid SQL, a change it cannot express)."""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class Step:
|
|
37
|
+
kind: str # add-table, drop-table, add-column, rebuild, add-index, drop-index
|
|
38
|
+
target: str # "posts" or "posts.summary"
|
|
39
|
+
detail: str = ''
|
|
40
|
+
sql: List[str] = field(default_factory=list)
|
|
41
|
+
loses_data: bool = False
|
|
42
|
+
warning: str = ''
|
|
43
|
+
|
|
44
|
+
def line(self) -> str:
|
|
45
|
+
mark = {'add-table': '+', 'add-column': '+', 'add-index': '+', 'drop-table': '-',
|
|
46
|
+
'drop-index': '-', 'rebuild': '~'}.get(self.kind, '?')
|
|
47
|
+
text = f'{mark} {self.kind.replace("-", " ")} {self.target}'
|
|
48
|
+
if self.detail:
|
|
49
|
+
text += f' ({self.detail})'
|
|
50
|
+
if self.loses_data:
|
|
51
|
+
text += ' !! loses data'
|
|
52
|
+
if self.warning:
|
|
53
|
+
text += f' !! {self.warning}'
|
|
54
|
+
return text
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _database(scripts: List[Tuple[str, str]]) -> sqlite3.Connection:
|
|
58
|
+
conn = sqlite3.connect(':memory:')
|
|
59
|
+
for origin, sql in scripts:
|
|
60
|
+
try:
|
|
61
|
+
conn.executescript(sql)
|
|
62
|
+
except sqlite3.Error as exc:
|
|
63
|
+
raise SchemaPlanError(f'{origin}: {exc}') from exc
|
|
64
|
+
return conn
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _migration_scripts(migrations_dir: Path) -> List[Tuple[str, str]]:
|
|
68
|
+
if not migrations_dir.exists():
|
|
69
|
+
return []
|
|
70
|
+
files = [f for f in sorted(migrations_dir.glob('*.sql')) if not f.name.endswith('.down.sql')]
|
|
71
|
+
return [(f.name, f.read_text(encoding='utf-8')) for f in files if '_' in f.stem]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _tables(conn) -> Dict[str, object]:
|
|
75
|
+
return {name: t for name, t in read_schema(conn).items() if name not in INTERNAL_TABLES}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _create_sql(conn, table: str) -> str:
|
|
79
|
+
return conn.execute("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?",
|
|
80
|
+
(table,)).fetchone()[0]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _indexes(conn) -> Dict[str, Tuple[str, str]]:
|
|
84
|
+
"""Explicit indexes: name -> (table, sql)."""
|
|
85
|
+
return {name: (table, sql) for name, table, sql in conn.execute(
|
|
86
|
+
"SELECT name, tbl_name, sql FROM sqlite_master WHERE type = 'index' AND sql IS NOT NULL")
|
|
87
|
+
if table not in INTERNAL_TABLES}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _column_sql(column: Column) -> str:
|
|
91
|
+
parts = [f'"{column.name}"']
|
|
92
|
+
if column.type:
|
|
93
|
+
parts.append(column.type)
|
|
94
|
+
if column.not_null:
|
|
95
|
+
parts.append('NOT NULL')
|
|
96
|
+
if column.default is not None:
|
|
97
|
+
parts.append(f'DEFAULT {column.default}')
|
|
98
|
+
if column.options:
|
|
99
|
+
options = ', '.join("'" + o.replace("'", "''") + "'" for o in column.options)
|
|
100
|
+
parts.append(f'CHECK ("{column.name}" IN ({options}))')
|
|
101
|
+
if column.references:
|
|
102
|
+
parts.append(f'REFERENCES "{column.references[0]}"("{column.references[1]}")')
|
|
103
|
+
return ' '.join(parts)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _differences(a: Column, b: Column) -> List[str]:
|
|
107
|
+
changed = []
|
|
108
|
+
for label, x, y in (('type', (a.type or '').upper(), (b.type or '').upper()),
|
|
109
|
+
('NOT NULL', a.not_null, b.not_null), ('default', a.default, b.default),
|
|
110
|
+
('primary key', a.primary_key, b.primary_key), ('CHECK', a.options, b.options),
|
|
111
|
+
('references', a.references, b.references)):
|
|
112
|
+
if x != y:
|
|
113
|
+
changed.append(label)
|
|
114
|
+
return changed
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def diff(current_db, desired_db) -> List[Step]:
|
|
118
|
+
"""The steps that turn the first database's schema into the second's."""
|
|
119
|
+
current, desired = _tables(current_db), _tables(desired_db)
|
|
120
|
+
steps: List[Step] = []
|
|
121
|
+
rebuilt = set()
|
|
122
|
+
|
|
123
|
+
for name in sorted(set(desired) - set(current)):
|
|
124
|
+
steps.append(Step('add-table', name, sql=[_create_sql(desired_db, name) + ';']))
|
|
125
|
+
|
|
126
|
+
for name in sorted(set(current) & set(desired)):
|
|
127
|
+
before, after = current[name].columns, desired[name].columns
|
|
128
|
+
added = [c for c in after if c not in before]
|
|
129
|
+
removed = [c for c in before if c not in after]
|
|
130
|
+
changed = {c: _differences(before[c], after[c]) for c in before
|
|
131
|
+
if c in after and _differences(before[c], after[c])}
|
|
132
|
+
addable = all(not after[c].primary_key and (not after[c].not_null or after[c].default is not None)
|
|
133
|
+
for c in added)
|
|
134
|
+
if not removed and not changed and addable:
|
|
135
|
+
for c in added:
|
|
136
|
+
steps.append(Step('add-column', f'{name}.{c}', after[c].type or '',
|
|
137
|
+
[f'ALTER TABLE "{name}" ADD COLUMN {_column_sql(after[c])};']))
|
|
138
|
+
continue
|
|
139
|
+
if not (added or removed or changed):
|
|
140
|
+
continue
|
|
141
|
+
common = ', '.join(f'"{c}"' for c in after if c in before)
|
|
142
|
+
create = re.sub(r'^\s*CREATE\s+TABLE\s+("?)' + re.escape(name) + r'\1',
|
|
143
|
+
f'CREATE TABLE "{name}__new"', _create_sql(desired_db, name), count=1, flags=re.I)
|
|
144
|
+
details = [f'+ {c}' for c in added] + [f'- {c}' for c in removed] + \
|
|
145
|
+
[f'{c}: {", ".join(d)}' for c, d in changed.items()]
|
|
146
|
+
warning = ''
|
|
147
|
+
if any('NOT NULL' in d and after[c].not_null for c, d in changed.items()) or \
|
|
148
|
+
any(after[c].not_null and after[c].default is None for c in added):
|
|
149
|
+
warning = 'fails if existing rows have no value for a NOT NULL column'
|
|
150
|
+
steps.append(Step('rebuild', name, '; '.join(details), [
|
|
151
|
+
create + ';',
|
|
152
|
+
f'INSERT INTO "{name}__new" ({common}) SELECT {common} FROM "{name}";',
|
|
153
|
+
f'DROP TABLE "{name}";',
|
|
154
|
+
f'ALTER TABLE "{name}__new" RENAME TO "{name}";',
|
|
155
|
+
], loses_data=bool(removed) or any('type' in d for d in changed.values()), warning=warning))
|
|
156
|
+
rebuilt.add(name)
|
|
157
|
+
|
|
158
|
+
for name in sorted(set(current) - set(desired)):
|
|
159
|
+
steps.append(Step('drop-table', name, sql=[f'DROP TABLE "{name}";'], loses_data=True))
|
|
160
|
+
|
|
161
|
+
# Indexes: a rebuilt table lost its own, so they are created again.
|
|
162
|
+
before_idx, after_idx = _indexes(current_db), _indexes(desired_db)
|
|
163
|
+
for name, (table, sql) in sorted(before_idx.items()):
|
|
164
|
+
if table in desired and table not in rebuilt and after_idx.get(name, (None, None))[1] != sql:
|
|
165
|
+
steps.append(Step('drop-index', name, sql=[f'DROP INDEX "{name}";']))
|
|
166
|
+
for name, (table, sql) in sorted(after_idx.items()):
|
|
167
|
+
if before_idx.get(name, (None, None))[1] != sql or table in rebuilt:
|
|
168
|
+
steps.append(Step('add-index', name, sql=[sql + ';']))
|
|
169
|
+
return steps
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _schema_only(conn) -> str:
|
|
173
|
+
"""The CREATE statements of a database, tables before indexes, without its rows."""
|
|
174
|
+
rows = conn.execute("SELECT type, sql FROM sqlite_master WHERE sql IS NOT NULL "
|
|
175
|
+
"AND name NOT LIKE 'sqlite_%' ORDER BY type = 'index', rowid").fetchall()
|
|
176
|
+
return '\n'.join(sql + ';' for _type, sql in rows)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _signature(conn) -> tuple:
|
|
180
|
+
tables = _tables(conn)
|
|
181
|
+
return (tuple(sorted((n, tuple(sorted((c.name, (c.type or '').upper(), c.not_null, c.default, c.primary_key,
|
|
182
|
+
tuple(c.options or ()), c.references)
|
|
183
|
+
for c in t.columns.values()))) for n, t in tables.items())),
|
|
184
|
+
tuple(sorted((n, s) for n, (_t, s) in _indexes(conn).items())))
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@dataclass
|
|
188
|
+
class Plan:
|
|
189
|
+
steps: List[Step]
|
|
190
|
+
up_sql: str
|
|
191
|
+
down_sql: str
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def loses_data(self) -> bool:
|
|
195
|
+
return any(s.loses_data for s in self.steps)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def make_plan(schema_file: Path, migrations_dir: Path) -> Plan:
|
|
199
|
+
if not schema_file.exists():
|
|
200
|
+
raise SchemaPlanError(f'{schema_file} does not exist: write the tables as they should be, '
|
|
201
|
+
f'e.g. CREATE TABLE posts (id INTEGER PRIMARY KEY, title TEXT NOT NULL);')
|
|
202
|
+
migrations = _migration_scripts(migrations_dir)
|
|
203
|
+
current = _database(migrations)
|
|
204
|
+
desired = _database([(schema_file.name, schema_file.read_text(encoding='utf-8'))])
|
|
205
|
+
steps = diff(current, desired)
|
|
206
|
+
up = '\n'.join(s for step in steps for s in step.sql)
|
|
207
|
+
down = '\n'.join(s for step in diff(desired, current) for s in step.sql)
|
|
208
|
+
if steps:
|
|
209
|
+
# The plan must do what it says: applied, it yields schema.sql's schema.
|
|
210
|
+
# Checked on the structure only — what existing rows may break is the
|
|
211
|
+
# step's warning, shown in the plan, not a reason to hide the plan.
|
|
212
|
+
check = _database([('migrations (schema only)', _schema_only(current)), ('plan', up)])
|
|
213
|
+
if _signature(check) != _signature(desired):
|
|
214
|
+
raise SchemaPlanError('the generated migration does not reproduce schema.sql — '
|
|
215
|
+
'write this change as a migration by hand (quantum migrate create)')
|
|
216
|
+
return Plan(steps, up, down)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def next_version(migrations_dir: Path) -> str:
|
|
220
|
+
numbers = [int(m.group(1)) for f in migrations_dir.glob('V*.sql')
|
|
221
|
+
for m in [re.match(r'V(\d+)_', f.name)] if m]
|
|
222
|
+
return f'V{(max(numbers) + 1 if numbers else 1):03d}'
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def write_plan(plan: Plan, migrations_dir: Path, name: str) -> Path:
|
|
226
|
+
migrations_dir.mkdir(parents=True, exist_ok=True)
|
|
227
|
+
slug = re.sub(r'[^a-z0-9_]+', '_', name.lower()).strip('_') or 'schema'
|
|
228
|
+
version = next_version(migrations_dir)
|
|
229
|
+
path = migrations_dir / f'{version}_{slug}.sql'
|
|
230
|
+
header = '-- Generated by `quantum migrate plan` from schema.sql (DB-10)\n'
|
|
231
|
+
summary = '\n'.join(f'-- {s.line()}' for s in plan.steps)
|
|
232
|
+
path.write_text(f'{header}{summary}\n\n{plan.up_sql}\n', encoding='utf-8')
|
|
233
|
+
(migrations_dir / f'{version}_{slug}.down.sql').write_text(
|
|
234
|
+
f'{header}-- rollback\n\n{plan.down_sql}\n', encoding='utf-8')
|
|
235
|
+
return path
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def run_plan(project: Path, schema: Optional[str], write: Optional[str], yes: bool,
|
|
239
|
+
allow_data_loss: bool, ask=input, interactive: bool = True) -> int:
|
|
240
|
+
"""The `quantum migrate plan` command. Returns the exit code."""
|
|
241
|
+
from quantum.cli.commands.migrate import _paths_migrations
|
|
242
|
+
migrations_dir = project / _paths_migrations(project)
|
|
243
|
+
schema_file = project / (schema or 'schema.sql')
|
|
244
|
+
try:
|
|
245
|
+
plan = make_plan(schema_file, migrations_dir)
|
|
246
|
+
except SchemaPlanError as exc:
|
|
247
|
+
print(f'[ERROR] {exc}')
|
|
248
|
+
return 1
|
|
249
|
+
if not plan.steps:
|
|
250
|
+
print(f'Nothing to do: the migrations already produce {schema_file.name}.')
|
|
251
|
+
return 0
|
|
252
|
+
print(f'Plan: {schema_file.name} vs. the migrations in {migrations_dir.name}/\n')
|
|
253
|
+
for step in plan.steps:
|
|
254
|
+
print(' ' + step.line())
|
|
255
|
+
print('\nSQL:\n' + plan.up_sql)
|
|
256
|
+
if not write:
|
|
257
|
+
print('\nNothing written. `quantum migrate plan --write <name>` saves it as a migration.')
|
|
258
|
+
return 0
|
|
259
|
+
if plan.loses_data and not allow_data_loss:
|
|
260
|
+
print('\n[REFUSED] This plan loses data (marked !!). Check it, then run again with '
|
|
261
|
+
'--allow-data-loss.')
|
|
262
|
+
return 1
|
|
263
|
+
if not yes:
|
|
264
|
+
if not interactive:
|
|
265
|
+
print('\n[REFUSED] Confirm with --yes (not a terminal: nobody to ask).')
|
|
266
|
+
return 1
|
|
267
|
+
answer = ask(f'\nWrite migration {next_version(migrations_dir)}_{write}? [y/N] ').strip().lower()
|
|
268
|
+
if answer not in ('y', 'yes'):
|
|
269
|
+
print('Nothing written.')
|
|
270
|
+
return 1
|
|
271
|
+
path = write_plan(plan, migrations_dir, write)
|
|
272
|
+
print(f'Written: {path} (and {path.stem}.down.sql). Apply with `quantum migrate up`.')
|
|
273
|
+
return 0
|
|
@@ -698,6 +698,8 @@ class QueryNode(QuantumNode):
|
|
|
698
698
|
self.interval = None
|
|
699
699
|
self.paginate = False # Enable automatic pagination
|
|
700
700
|
self.page = None
|
|
701
|
+
self.page_expr = None # DB-9: page as an expression (default {query.page})
|
|
702
|
+
self.sortable = False # UI-13: ORDER BY the URL's ?sort=/&dir=
|
|
701
703
|
self.page_size = 20
|
|
702
704
|
self.timeout = None
|
|
703
705
|
self.maxrows = None
|
|
@@ -1137,6 +1139,10 @@ class ActionNode(QuantumNode):
|
|
|
1137
1139
|
self.method = method.upper()
|
|
1138
1140
|
self.params: List[QuantumParam] = []
|
|
1139
1141
|
self.body: List[QuantumNode] = []
|
|
1142
|
+
# UI-10 (M17): params from a table's schema — see quantum/runtime/table_params.py
|
|
1143
|
+
self.table: Optional[str] = None
|
|
1144
|
+
self.table_datasource: Optional[str] = None
|
|
1145
|
+
self.table_columns: Optional[List[str]] = None
|
|
1140
1146
|
|
|
1141
1147
|
# Action configuration
|
|
1142
1148
|
self.validate_csrf = True # Auto CSRF protection
|
|
@@ -319,6 +319,9 @@ class UIFormNode(QuantumNode, UILayoutMixin):
|
|
|
319
319
|
|
|
320
320
|
def __init__(self):
|
|
321
321
|
self.on_submit: Optional[str] = None
|
|
322
|
+
self.rules: Optional[str] = None # UI-9: 'off' = no rules from the action
|
|
323
|
+
self.values: Optional[str] = None # UI-10: {row} whose fields fill the form
|
|
324
|
+
self.submit: Optional[str] = None # UI-10: text of the generated button
|
|
322
325
|
self.children: List[QuantumNode] = []
|
|
323
326
|
|
|
324
327
|
# Validation mode: 'client', 'server', or 'both' (default)
|
|
@@ -550,6 +553,8 @@ class UIInputNode(QuantumNode, UILayoutMixin):
|
|
|
550
553
|
def __init__(self):
|
|
551
554
|
self.bind: Optional[str] = None
|
|
552
555
|
self.value: Optional[str] = None # initial value (UI-6)
|
|
556
|
+
self.search: Optional[str] = None # UI-12: id of the region it refreshes as you type
|
|
557
|
+
self.delay: Optional[int] = None # UI-12: ms after the last key (300 with search=)
|
|
553
558
|
self.placeholder: Optional[str] = None
|
|
554
559
|
self.input_type: str = "text" # text, password, email, number
|
|
555
560
|
self.on_change: Optional[str] = None
|
|
@@ -673,6 +678,9 @@ class UITableNode(QuantumNode, UILayoutMixin):
|
|
|
673
678
|
def __init__(self):
|
|
674
679
|
self.source: Optional[str] = None
|
|
675
680
|
self.as_var: Optional[str] = None # row variable for column content (UI-5)
|
|
681
|
+
self.sort: bool = False # UI-13: headers sort the query
|
|
682
|
+
self.edit: Optional[str] = None # UI-13: table whose cells are edited in place
|
|
683
|
+
self.edit_datasource: Optional[str] = None
|
|
676
684
|
self.children: List[QuantumNode] = [] # UIColumnNode children
|
|
677
685
|
self._init_layout_attrs()
|
|
678
686
|
|
|
@@ -697,6 +705,7 @@ class UIColumnNode(QuantumNode):
|
|
|
697
705
|
self.label: Optional[str] = None
|
|
698
706
|
self.column_width: Optional[str] = None
|
|
699
707
|
self.align: Optional[str] = None # left, center, right
|
|
708
|
+
self.edit: bool = True # UI-13: edit="false" keeps it read-only
|
|
700
709
|
# UI-5: a column with content draws it once per row (buttons, links).
|
|
701
710
|
self.children: List[QuantumNode] = []
|
|
702
711
|
|
|
@@ -1870,6 +1879,41 @@ class UIDatePickerNode(QuantumNode, UILayoutMixin):
|
|
|
1870
1879
|
return self._validate_layout()
|
|
1871
1880
|
|
|
1872
1881
|
|
|
1882
|
+
class UIPagerNode(QuantumNode, UILayoutMixin):
|
|
1883
|
+
"""<ui:pager for="query"> — previous, page numbers and next over a paginated query (UI-11)."""
|
|
1884
|
+
|
|
1885
|
+
def __init__(self):
|
|
1886
|
+
self.for_query: Optional[str] = None
|
|
1887
|
+
self.param: str = 'page'
|
|
1888
|
+
self.window: int = 2
|
|
1889
|
+
self.children: List[QuantumNode] = []
|
|
1890
|
+
self._init_layout_attrs()
|
|
1891
|
+
|
|
1892
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
1893
|
+
return {"type": "ui_pager", "for": self.for_query, "param": self.param}
|
|
1894
|
+
|
|
1895
|
+
def validate(self) -> List[str]:
|
|
1896
|
+
return [] if self.for_query else ["ui:pager needs for="]
|
|
1897
|
+
|
|
1898
|
+
|
|
1899
|
+
class UIHistoryNode(QuantumNode, UILayoutMixin):
|
|
1900
|
+
"""<ui:history table="t" key="{row.id}" datasource="db"> — a row's change history (DB-11)."""
|
|
1901
|
+
|
|
1902
|
+
def __init__(self):
|
|
1903
|
+
self.table: Optional[str] = None
|
|
1904
|
+
self.key: Optional[str] = None
|
|
1905
|
+
self.datasource: Optional[str] = None
|
|
1906
|
+
self.limit: int = 50
|
|
1907
|
+
self.children: List[QuantumNode] = []
|
|
1908
|
+
self._init_layout_attrs()
|
|
1909
|
+
|
|
1910
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
1911
|
+
return {"type": "ui_history", "table": self.table, "key": self.key}
|
|
1912
|
+
|
|
1913
|
+
def validate(self) -> List[str]:
|
|
1914
|
+
return []
|
|
1915
|
+
|
|
1916
|
+
|
|
1873
1917
|
def is_ui_node(node) -> bool:
|
|
1874
1918
|
"""A ui:* element (UI-1): markup, drawn by a renderer, never executed."""
|
|
1875
1919
|
return type(node).__module__ == __name__
|
|
@@ -1882,6 +1926,6 @@ CORE_TAGS = frozenset({
|
|
|
1882
1926
|
'window', 'hbox', 'vbox', 'grid', 'panel', 'section', 'scrollbox', 'spacer', 'rule',
|
|
1883
1927
|
'header', 'footer', 'card', 'card-header', 'card-body', 'card-footer', 'tabpanel', 'tab',
|
|
1884
1928
|
'text', 'badge', 'alert', 'link', 'image', 'progress',
|
|
1885
|
-
'table', 'column', 'list', 'item',
|
|
1929
|
+
'table', 'column', 'list', 'item', 'pager', 'history',
|
|
1886
1930
|
'form', 'formitem', 'input', 'checkbox', 'switch', 'radio', 'select', 'option', 'button',
|
|
1887
1931
|
})
|
{quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/ui_engine/src/parser.py
RENAMED
|
@@ -28,7 +28,7 @@ from .ast_nodes import (
|
|
|
28
28
|
UIToastNode, UIToastContainerNode,
|
|
29
29
|
UICarouselNode, UISlideNode,
|
|
30
30
|
UIStepperNode, UIStepNode,
|
|
31
|
-
UICalendarNode, UIDatePickerNode,
|
|
31
|
+
UICalendarNode, UIDatePickerNode, UIPagerNode, UIHistoryNode,
|
|
32
32
|
)
|
|
33
33
|
from quantum.core.features.theming.src import UIThemeNode, UIColorNode
|
|
34
34
|
from quantum.core.ast_nodes import TextNode
|
|
@@ -81,6 +81,8 @@ class UIParser:
|
|
|
81
81
|
'select': '_parse_ui_select',
|
|
82
82
|
'table': '_parse_ui_table',
|
|
83
83
|
'column': '_parse_ui_column',
|
|
84
|
+
'pager': '_parse_ui_pager',
|
|
85
|
+
'history': '_parse_ui_history',
|
|
84
86
|
'list': '_parse_ui_list',
|
|
85
87
|
'item': '_parse_ui_item',
|
|
86
88
|
'image': '_parse_ui_image',
|
|
@@ -359,6 +361,13 @@ class UIParser:
|
|
|
359
361
|
node = UIFormNode()
|
|
360
362
|
node.on_submit = element.get('on-submit')
|
|
361
363
|
node.send = element.get('with')
|
|
364
|
+
# UI-9: rules="off" stops the form from taking its fields' rules from the action.
|
|
365
|
+
node.rules = element.get('rules')
|
|
366
|
+
# UI-10: an edit form opens with a row's values; the generated button's text.
|
|
367
|
+
node.values = element.get('values')
|
|
368
|
+
node.submit = element.get('submit')
|
|
369
|
+
if node.rules not in (None, 'on', 'off'):
|
|
370
|
+
raise UIParseError(f'<ui:form rules="{node.rules}">: use on (the default) or off')
|
|
362
371
|
|
|
363
372
|
# Validation attributes
|
|
364
373
|
node.validation_mode = element.get('validation', 'both')
|
|
@@ -430,6 +439,14 @@ class UIParser:
|
|
|
430
439
|
node = UIInputNode()
|
|
431
440
|
node.bind = element.get('bind')
|
|
432
441
|
node.value = element.get('value')
|
|
442
|
+
# UI-12: search as you type — refresh the element with this id.
|
|
443
|
+
node.search = element.get('search')
|
|
444
|
+
espera = element.get('delay')
|
|
445
|
+
if espera is not None and not espera.isdigit():
|
|
446
|
+
raise UIParseError(f'<ui:input delay="{espera}">: milliseconds, e.g. delay="300"')
|
|
447
|
+
node.delay = int(espera) if espera is not None else (300 if node.search else None)
|
|
448
|
+
if node.search is not None and not node.bind:
|
|
449
|
+
raise UIParseError('<ui:input search=...> needs bind= (the URL parameter it sets)')
|
|
433
450
|
node.placeholder = element.get('placeholder')
|
|
434
451
|
node.input_type = element.get('type', 'text')
|
|
435
452
|
node.on_change = element.get('on-change')
|
|
@@ -498,6 +515,14 @@ class UIParser:
|
|
|
498
515
|
node = UITableNode()
|
|
499
516
|
node.source = element.get('source')
|
|
500
517
|
node.as_var = element.get('as')
|
|
518
|
+
# UI-13: sortable headers and cells edited in place.
|
|
519
|
+
node.sort = self._parse_bool(element.get('sort'))
|
|
520
|
+
node.edit = element.get('edit')
|
|
521
|
+
node.edit_datasource = element.get('datasource')
|
|
522
|
+
if node.edit and not node.edit_datasource:
|
|
523
|
+
raise UIParseError(f'<ui:table edit="{node.edit}"> needs datasource= (where the table is)')
|
|
524
|
+
if (node.sort or node.edit) and not node.source:
|
|
525
|
+
raise UIParseError('<ui:table sort/edit> needs source="{query}"')
|
|
501
526
|
self._apply_layout_attrs(node, element)
|
|
502
527
|
# Parse child <ui:column> elements
|
|
503
528
|
self._parse_children(element, node)
|
|
@@ -509,9 +534,38 @@ class UIParser:
|
|
|
509
534
|
node.label = element.get('label')
|
|
510
535
|
node.column_width = element.get('width')
|
|
511
536
|
node.align = element.get('align')
|
|
537
|
+
node.edit = element.get('edit', 'true') != 'false'
|
|
512
538
|
self._parse_children(element, node)
|
|
513
539
|
return node
|
|
514
540
|
|
|
541
|
+
def _parse_ui_pager(self, element: ET.Element) -> UIPagerNode:
|
|
542
|
+
node = UIPagerNode()
|
|
543
|
+
node.for_query = element.get('for')
|
|
544
|
+
if not node.for_query:
|
|
545
|
+
raise UIParseError('<ui:pager> needs for="<the paginated q:query>"')
|
|
546
|
+
node.param = element.get('param') or 'page'
|
|
547
|
+
janela = element.get('window')
|
|
548
|
+
if janela is not None and not janela.isdigit():
|
|
549
|
+
raise UIParseError(f'<ui:pager window="{janela}">: the number of pages shown each side of the current one')
|
|
550
|
+
node.window = int(janela) if janela is not None else 2
|
|
551
|
+
self._apply_layout_attrs(node, element)
|
|
552
|
+
return node
|
|
553
|
+
|
|
554
|
+
def _parse_ui_history(self, element: ET.Element) -> UIHistoryNode:
|
|
555
|
+
node = UIHistoryNode()
|
|
556
|
+
node.table = element.get('table')
|
|
557
|
+
node.key = element.get('key')
|
|
558
|
+
node.datasource = element.get('datasource')
|
|
559
|
+
missing = [a for a in ('table', 'key', 'datasource') if not element.get(a)]
|
|
560
|
+
if missing:
|
|
561
|
+
raise UIParseError(f'<ui:history> needs {", ".join(a + "=" for a in missing)}')
|
|
562
|
+
limit = element.get('limit')
|
|
563
|
+
if limit is not None and not limit.isdigit():
|
|
564
|
+
raise UIParseError(f'<ui:history limit="{limit}">: a number of entries')
|
|
565
|
+
node.limit = int(limit) if limit is not None else 50
|
|
566
|
+
self._apply_layout_attrs(node, element)
|
|
567
|
+
return node
|
|
568
|
+
|
|
515
569
|
def _parse_ui_list(self, element: ET.Element) -> UIListNode:
|
|
516
570
|
node = UIListNode()
|
|
517
571
|
node.source = element.get('source')
|
{quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/data/query_parser.py
RENAMED
|
@@ -77,7 +77,27 @@ class QueryParser(BaseTagParser):
|
|
|
77
77
|
# Parse attributes
|
|
78
78
|
query_node.source = source
|
|
79
79
|
query_node.paginate = self.get_bool_attr(element, 'paginate', False)
|
|
80
|
-
|
|
80
|
+
# UI-13: ordered by the URL's ?sort=/&dir= (a <ui:table sort="true"> writes them)
|
|
81
|
+
query_node.sortable = self.get_bool_attr(element, 'sortable', False)
|
|
82
|
+
# DB-9: page= is an expression, resolved when the query runs; it was
|
|
83
|
+
# read as an int at parse time, so page="{query.page}" was always
|
|
84
|
+
# page 1. With paginate="true" and no page=, the page is the URL's
|
|
85
|
+
# `page` parameter — the one <ui:pager> writes.
|
|
86
|
+
pagina = element.get('page')
|
|
87
|
+
query_node.page = None
|
|
88
|
+
query_node.page_expr = None
|
|
89
|
+
if pagina is not None:
|
|
90
|
+
if '{' in pagina:
|
|
91
|
+
query_node.page_expr = pagina
|
|
92
|
+
elif pagina.strip().isdigit():
|
|
93
|
+
query_node.page = int(pagina)
|
|
94
|
+
else:
|
|
95
|
+
from quantum.core.parser import QuantumParseError
|
|
96
|
+
raise QuantumParseError(
|
|
97
|
+
f'<q:query name="{name}" page="{pagina}">: a page number or an expression, '
|
|
98
|
+
f'e.g. page="{{query.pagina}}"')
|
|
99
|
+
elif query_node.paginate:
|
|
100
|
+
query_node.page_expr = '{query.page}'
|
|
81
101
|
# Accept both spellings: the codebase convention is camelCase, but
|
|
82
102
|
# snake_case reads naturally in XML and the examples used it —
|
|
83
103
|
# the mismatch made page_size None and crashed pagination with
|
{quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/forms/action_parser.py
RENAMED
|
@@ -48,6 +48,21 @@ class ActionParser(BaseTagParser):
|
|
|
48
48
|
f'<q:action name="{name}"> {attr}= is not supported: it was accepted '
|
|
49
49
|
f'and never enforced. {porque}')
|
|
50
50
|
|
|
51
|
+
# UI-10 (M17): the action's params come from a table's schema, read
|
|
52
|
+
# from the database when the action is used; a q:param written wins.
|
|
53
|
+
action_node.table = element.get('table')
|
|
54
|
+
action_node.table_datasource = element.get('datasource')
|
|
55
|
+
colunas = element.get('columns')
|
|
56
|
+
action_node.table_columns = [c.strip() for c in colunas.split(',') if c.strip()] if colunas else None
|
|
57
|
+
if (action_node.table_datasource or colunas) and not action_node.table:
|
|
58
|
+
from quantum.core.parser import QuantumParseError
|
|
59
|
+
raise QuantumParseError(
|
|
60
|
+
f'<q:action name="{name}"> datasource= and columns= describe table=, which is missing')
|
|
61
|
+
if action_node.table and not action_node.table_datasource:
|
|
62
|
+
from quantum.core.parser import QuantumParseError
|
|
63
|
+
raise QuantumParseError(
|
|
64
|
+
f'<q:action name="{name}" table="{action_node.table}"> needs datasource= (where the table is)')
|
|
65
|
+
|
|
51
66
|
# Parse children (params, statements)
|
|
52
67
|
for child in element:
|
|
53
68
|
child_type = self._get_element_name(child)
|