quantum-framework 0.16.0__tar.gz → 0.18.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.16.0/quantum_framework.egg-info → quantum_framework-0.18.0}/PKG-INFO +1 -1
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/pyproject.toml +1 -1
- quantum_framework-0.18.0/quantum/cli/check.py +223 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/runner.py +9 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/ast_nodes.py +6 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/ui_engine/src/ast_nodes.py +27 -1
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/ui_engine/src/parser.py +56 -3
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parser.py +62 -17
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/data/query_parser.py +21 -1
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/forms/action_parser.py +15 -0
- quantum_framework-0.18.0/quantum/core/xml_lines.py +81 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/action_handler.py +89 -24
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/component.py +6 -1
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/database_service.py +5 -0
- quantum_framework-0.18.0/quantum/runtime/db_schema.py +100 -0
- quantum_framework-0.18.0/quantum/runtime/dev_panel.py +178 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executor_registry.py +8 -1
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/data/query_executor.py +38 -4
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/renderer.py +11 -0
- quantum_framework-0.18.0/quantum/runtime/table_params.py +163 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/ui_console.py +48 -3
- quantum_framework-0.18.0/quantum/runtime/ui_form_rules.py +193 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/ui_html_adapter.py +1 -1
- quantum_framework-0.18.0/quantum/runtime/ui_page_renderer.py +392 -0
- quantum_framework-0.18.0/quantum/runtime/ui_pager.py +73 -0
- quantum_framework-0.18.0/quantum/runtime/ui_table_edit.py +139 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/ui_view.py +76 -10
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/web_server.py +234 -22
- {quantum_framework-0.16.0 → quantum_framework-0.18.0/quantum_framework.egg-info}/PKG-INFO +1 -1
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_framework.egg-info/SOURCES.txt +8 -0
- quantum_framework-0.16.0/quantum/runtime/ui_page_renderer.py +0 -226
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/LICENSE +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/README.md +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/admin.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/commands/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/commands/build.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/commands/dev.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/commands/docs.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/commands/lint.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/commands/migrate.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/commands/new.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/commands/serve.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/commands/test.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/deploy.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/hot_reload.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/jobs.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/mq.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/pkg.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/server_process.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/utils.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/compiler/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/compiler/base_generator.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/compiler/cli.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/compiler/expression_transformer.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/compiler/javascript/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/compiler/javascript/generator.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/compiler/optimizer.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/compiler/python/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/compiler/python/generator.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/compiler/python/runtime.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/compiler/transpiler.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/config_env.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/docs_links.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/expression_diagnostics.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/expression_stdlib.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/expressions.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/agents/src/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/agents/src/ast_node.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/conditionals/src/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/conditionals/src/ast_node.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/data_import/src/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/data_import/src/ast_node.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/data_import/src/runtime.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/dump/src/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/dump/src/ast_node.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/dump/src/parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/dump/src/runtime.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/functions/src/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/functions/src/ast_node.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/game_engine_2d/src/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/game_engine_2d/src/ast_nodes.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/game_engine_2d/src/parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/invocation/src/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/invocation/src/ast_node.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/invocation/src/runtime.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/knowledge_base/src/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/knowledge_base/src/ast_node.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/knowledge_base/src/parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/logging/src/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/logging/src/ast_node.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/logging/src/parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/logging/src/runtime.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/loops/src/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/loops/src/ast_node.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/query/src/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/query/src/query_validators.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/state_management/src/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/state_management/src/ast_node.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/terminal_engine/src/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/terminal_engine/src/ast_nodes.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/terminal_engine/src/parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/testing_engine/src/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/testing_engine/src/ast_nodes.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/testing_engine/src/parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/theming/src/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/theming/src/ast_node.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/theming/src/presets.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/ui_engine/src/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/websocket/src/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/websocket/src/ast_node.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/guards.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/html_compat.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parser_registry.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/ai/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/ai/agent_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/ai/knowledge_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/ai/llm_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/ai/team_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/base.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/composition/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/composition/import_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/composition/slot_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/control_flow/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/control_flow/if_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/control_flow/loop_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/control_flow/set_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/data/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/data/data_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/data/invoke_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/data/transaction_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/events/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/events/dispatch_event_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/forms/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/forms/flash_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/forms/redirect_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/functions/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/functions/function_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/functions/param_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/functions/return_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/html/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/html/component_call_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/html/html_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/jobs/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/jobs/job_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/jobs/schedule_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/jobs/thread_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/messaging/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/messaging/message_ack_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/messaging/message_nack_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/messaging/message_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/messaging/queue_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/messaging/websocket_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/routing/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/routing/route_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/scripting/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/scripting/pyclass_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/scripting/pydecorator_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/scripting/pyimport_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/scripting/python_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/services/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/services/dump_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/services/file_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/services/log_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/services/mail_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/tiers.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/packages/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/packages/manager.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/packages/manifest.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/packages/registry.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/packages/resolver.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/plugins/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/plugins/hooks.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/plugins/loader.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/plugins/manifest.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/plugins/registry.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/adapters/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/adapters/memory_adapter.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/adapters/rabbitmq_adapter.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/adapters/redis_adapter.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/adapters/sqlite_adapter.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/agent_service.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/ast_cache.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/auth_service.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/component_composer.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/component_resolver.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/conditions.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/email_service.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/error_handler.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/execution_context.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/ai/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/ai/agent_executor.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/ai/knowledge_executor.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/ai/llm_executor.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/ai/team_executor.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/base.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/control_flow/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/control_flow/if_executor.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/control_flow/loop_executor.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/control_flow/redirect_executor.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/control_flow/set_executor.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/data/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/data/data_executor.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/data/invoke_executor.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/data/transaction_executor.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/jobs/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/jobs/job_executor.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/jobs/schedule_executor.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/jobs/thread_executor.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/messaging/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/messaging/message_ack_executor.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/messaging/message_executor.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/messaging/queue_executor.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/messaging/websocket_executor.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/scripting/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/scripting/pyclass_executor.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/scripting/pyimport_executor.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/scripting/python_executor.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/services/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/services/dump_executor.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/services/file_executor.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/services/log_executor.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/services/mail_executor.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/expression_cache.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/file_upload_service.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/function_registry.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/game_builder.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/game_code_generator.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/game_templates.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/godot_code_generator.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/godot_templates.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/job_executor.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/knowledge_service.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/llm_cache.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/llm_providers.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/llm_service.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/local_server.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/logging_setup.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/message_broker.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/message_queue_service.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/param_validation.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/pypy_compat.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/python_bridge.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/query_validators.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/service_container.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/terminal_builder.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/terminal_code_generator.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/terminal_templates.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/testing_builder.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/testing_code_generator.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/testing_templates.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/ui_builder.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/ui_desktop.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/ui_html_templates.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/ui_mobile_adapter.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/ui_mobile_templates.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/ui_textual_adapter.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/ui_textual_templates.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/ui_tokens.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/ui_validator.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/validators.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/websocket_adapter.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/websocket_service.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/websocket_transport.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/wsgi.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/services.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/utils/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/assets/quantum-admin-connectors.js +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/assets/quantum-admin.css +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/assets/quantum-source-highlight.js +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/audit_service.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/auth_service.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/component_discovery.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/config_generator.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/connector_service.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/crud.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/database.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/db_setup_service.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/deploy_service.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/docker_service.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/environment_service.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/git_service.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/health_service.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/job_service.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/log_service.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/main.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/models.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/pipeline_service.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/resource_manager.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/schemas.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/secret_manager.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/settings_service.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/template_service.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/test_execution_service.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/test_generator.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/webhook_service.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/websocket_manager.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/_layout/AdminShell.q +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/agents.q +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/app/[name].q +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/applications.q +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/component/[...path].q +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/components.q +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/connectors.q +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/dashboard.q +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/database.q +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/features.q +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/index.q +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/jobs.q +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/login.q +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/logout.q +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/settings.q +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/source.q +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/tests.q +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/run.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/services/__init__.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/services/_base.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/services/_component_test_generator.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/services/_yaml_editor.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/services/apps.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/services/auth.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/services/components.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/services/connectors.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/services/projects.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/services/repository.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/services/settings.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/services/yaml_import.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_framework.egg-info/dependency_links.txt +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_framework.egg-info/entry_points.txt +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_framework.egg-info/requires.txt +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_framework.egg-info/top_level.txt +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/setup.cfg +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_agent.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_animations.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_ast_cache.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_cli_commands.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_component_library.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_expression_cache.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_form_validation.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_game_codegen.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_game_e2e.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_game_engine.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_game_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_godot_codegen.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_job_executor.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_llm_providers.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_message_broker.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_message_queue.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_mobile_target.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_multi_agent.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_packages.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_plugins.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_pypy_compat.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_python_scripting.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_terminal_codegen.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_terminal_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_testing_codegen.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_testing_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_theming.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_ui_html_adapter.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_ui_parity.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_ui_parser.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_ui_textual_adapter.py +0 -0
- {quantum_framework-0.16.0 → quantum_framework-0.18.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.18.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"}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"""`quantum check`: every page parses, every q:query compiles against the database,
|
|
2
|
+
and every `{query.field}` a page reads is a column the query returns (M8, DEV-3).
|
|
3
|
+
|
|
4
|
+
A typo in SQL or in a field name used to surface only when someone opened the
|
|
5
|
+
page — `no such column` as a 500, or, for a field, an empty cell. This reads
|
|
6
|
+
the project the way the server would and reports each problem with its file
|
|
7
|
+
and line, without running anything: a query is compiled by the database
|
|
8
|
+
(EXPLAIN) and a SELECT's columns come from running it with LIMIT 0.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Dict, Iterator, List, Optional
|
|
15
|
+
|
|
16
|
+
# Fields of `<name>_result` and of a query row that are not columns.
|
|
17
|
+
_META = {'recordCount', 'columnList', 'executionTime', 'success', 'error', 'sql', 'page',
|
|
18
|
+
'totalPages', 'hasMore', 'cached', 'affectedRows', 'lastInsertId', 'data', 'length'}
|
|
19
|
+
_EXPR = re.compile(r'\{([^{}]*)\}')
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class Problem:
|
|
24
|
+
file: str
|
|
25
|
+
line: Optional[int]
|
|
26
|
+
message: str
|
|
27
|
+
|
|
28
|
+
def __str__(self) -> str:
|
|
29
|
+
return f'{self.file}:{self.line or "?"}: {self.message}'
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _config(config_path: Path) -> dict:
|
|
33
|
+
import yaml
|
|
34
|
+
from quantum.core.config_env import expand_env
|
|
35
|
+
if not config_path.exists():
|
|
36
|
+
return {}
|
|
37
|
+
return expand_env(yaml.safe_load(config_path.read_text(encoding='utf-8')) or {})
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _children(node) -> Iterator:
|
|
41
|
+
"""The nodes inside a node, whatever the field is called."""
|
|
42
|
+
for campo in ('statements', 'body', 'if_body', 'else_body', 'children'):
|
|
43
|
+
for filho in getattr(node, campo, None) or []:
|
|
44
|
+
if hasattr(filho, '__dict__'):
|
|
45
|
+
yield filho
|
|
46
|
+
for bloco in getattr(node, 'elseif_blocks', None) or []:
|
|
47
|
+
corpo = bloco.get('body') if isinstance(bloco, dict) else getattr(bloco, 'body', None)
|
|
48
|
+
for filho in corpo or []:
|
|
49
|
+
yield filho
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _texts(node) -> List[str]:
|
|
53
|
+
"""The strings of a node that may hold {expressions}."""
|
|
54
|
+
textos = []
|
|
55
|
+
for campo, valor in vars(node).items():
|
|
56
|
+
if campo.startswith('_') or campo in ('sql',):
|
|
57
|
+
continue
|
|
58
|
+
if isinstance(valor, str) and '{' in valor:
|
|
59
|
+
textos.append(valor)
|
|
60
|
+
elif isinstance(valor, dict):
|
|
61
|
+
textos += [v for v in valor.values() if isinstance(v, str) and '{' in v]
|
|
62
|
+
return textos
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ProjectChecker:
|
|
66
|
+
def __init__(self, project: Path, config_path: Path):
|
|
67
|
+
self.project = project
|
|
68
|
+
self.config = _config(config_path)
|
|
69
|
+
self.datasources = self.config.get('datasources') or {}
|
|
70
|
+
self._conexoes: Dict[str, object] = {}
|
|
71
|
+
self.problems: List[Problem] = []
|
|
72
|
+
self.notes: List[str] = []
|
|
73
|
+
self.queries_checked = 0
|
|
74
|
+
self.files = 0
|
|
75
|
+
|
|
76
|
+
# -- the database ---------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
def _connection(self, nome: str):
|
|
79
|
+
from quantum.runtime.db_schema import SchemaError, connect_readonly
|
|
80
|
+
if nome not in self._conexoes:
|
|
81
|
+
try:
|
|
82
|
+
self._conexoes[nome] = connect_readonly(self.datasources[nome], self.project)
|
|
83
|
+
except SchemaError as exc:
|
|
84
|
+
self._conexoes[nome] = None
|
|
85
|
+
self.notes.append(f'datasource {nome!r} not checked: {exc}')
|
|
86
|
+
return self._conexoes[nome]
|
|
87
|
+
|
|
88
|
+
# -- a file ---------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
def check_file(self, arquivo: Path) -> None:
|
|
91
|
+
from quantum.core.parser import QuantumParser
|
|
92
|
+
rel = arquivo.relative_to(self.project).as_posix() if arquivo.is_relative_to(self.project) else str(arquivo)
|
|
93
|
+
self.files += 1
|
|
94
|
+
try:
|
|
95
|
+
ast = QuantumParser(use_cache=False).parse_file(str(arquivo))
|
|
96
|
+
except Exception as exc: # a parse error is a problem like any other
|
|
97
|
+
primeira = str(exc).split('\n')[0]
|
|
98
|
+
self.problems.append(Problem(rel, getattr(exc, 'line', None), primeira))
|
|
99
|
+
return
|
|
100
|
+
colunas: Dict[str, List[str]] = {}
|
|
101
|
+
self._queries(ast, rel, colunas)
|
|
102
|
+
self._fields(ast, rel, colunas, {nome: nome for nome in colunas}, None)
|
|
103
|
+
|
|
104
|
+
def _queries(self, node, rel: str, colunas: Dict[str, List[str]]) -> None:
|
|
105
|
+
from quantum.core.ast_nodes import ActionNode, QueryNode
|
|
106
|
+
from quantum.runtime.db_schema import check_sql, read_schema
|
|
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}"'))
|
|
129
|
+
if isinstance(filho, QueryNode):
|
|
130
|
+
ds = filho.datasource
|
|
131
|
+
if not ds or ds.startswith('knowledge:') or getattr(filho, 'source', None):
|
|
132
|
+
pass # RAG or query of queries: not SQL on a database
|
|
133
|
+
elif ds not in self.datasources:
|
|
134
|
+
self.problems.append(Problem(rel, getattr(filho, 'source_line', None),
|
|
135
|
+
f'<q:query name="{filho.name}">: datasource {ds!r} is not '
|
|
136
|
+
f'declared in quantum.config.yaml'))
|
|
137
|
+
else:
|
|
138
|
+
conexao = self._connection(ds)
|
|
139
|
+
if conexao is not None:
|
|
140
|
+
self.queries_checked += 1
|
|
141
|
+
erro, cols = check_sql(conexao, filho.sql or '')
|
|
142
|
+
if erro:
|
|
143
|
+
self.problems.append(Problem(rel, getattr(filho, 'source_line', None),
|
|
144
|
+
f'<q:query name="{filho.name}">: {erro}'))
|
|
145
|
+
elif cols is not None:
|
|
146
|
+
colunas[filho.name] = cols
|
|
147
|
+
self._queries(filho, rel, colunas)
|
|
148
|
+
|
|
149
|
+
def _fields(self, node, rel: str, colunas: Dict[str, List[str]], ligados: Dict[str, str],
|
|
150
|
+
linha: Optional[int]) -> None:
|
|
151
|
+
"""Each `{var.field}` where var is a query, or a row of one, names a column it returns."""
|
|
152
|
+
from quantum.core.features.loops.src.ast_node import LoopNode
|
|
153
|
+
from quantum.core.features.ui_engine.src.ast_nodes import UIColumnNode, UIListNode, UITableNode
|
|
154
|
+
linha = getattr(node, 'source_line', None) or linha
|
|
155
|
+
for texto in _texts(node):
|
|
156
|
+
for expressao in _EXPR.findall(texto):
|
|
157
|
+
for var, campo in re.findall(r'(?<![\w.])(\w+)\.(\w+)', expressao):
|
|
158
|
+
self._field(rel, linha, colunas, ligados, var, campo)
|
|
159
|
+
if isinstance(node, UIColumnNode) and node.key and '$tabela' in ligados:
|
|
160
|
+
self._field(rel, linha, colunas, ligados, '$tabela', node.key, f'<ui:column key="{node.key}">')
|
|
161
|
+
|
|
162
|
+
for filho in _children(node):
|
|
163
|
+
dentro = dict(ligados)
|
|
164
|
+
origem = None
|
|
165
|
+
if isinstance(filho, LoopNode):
|
|
166
|
+
origem = filho.query_name if filho.loop_type == 'query' else _single_name(filho.items)
|
|
167
|
+
if origem in colunas:
|
|
168
|
+
dentro[filho.var_name] = origem
|
|
169
|
+
elif isinstance(filho, (UITableNode, UIListNode)):
|
|
170
|
+
origem = _single_name(filho.source)
|
|
171
|
+
if origem in colunas:
|
|
172
|
+
dentro[filho.as_var or ('row' if isinstance(filho, UITableNode) else 'item')] = origem
|
|
173
|
+
if isinstance(filho, UITableNode):
|
|
174
|
+
dentro['$tabela'] = origem
|
|
175
|
+
self._fields(filho, rel, colunas, dentro, linha)
|
|
176
|
+
|
|
177
|
+
def _field(self, rel, linha, colunas, ligados, var, campo, como=None) -> None:
|
|
178
|
+
consulta = ligados.get(var)
|
|
179
|
+
if consulta is None or campo in _META:
|
|
180
|
+
return
|
|
181
|
+
cols = colunas.get(consulta, [])
|
|
182
|
+
if campo not in cols:
|
|
183
|
+
quem = como or f'{{{var}.{campo}}}'
|
|
184
|
+
self.problems.append(Problem(
|
|
185
|
+
rel, linha, f'{quem}: query "{consulta}" returns no column "{campo}" '
|
|
186
|
+
f'(columns: {", ".join(cols) or "none"})'))
|
|
187
|
+
|
|
188
|
+
# -- the project ----------------------------------------------------------
|
|
189
|
+
|
|
190
|
+
def run(self) -> List[Problem]:
|
|
191
|
+
pasta = self.project / (self.config.get('paths', {}) or {}).get('components', 'components')
|
|
192
|
+
if not pasta.exists():
|
|
193
|
+
self.notes.append(f'no components folder at {pasta}')
|
|
194
|
+
return self.problems
|
|
195
|
+
for arquivo in sorted(pasta.rglob('*.q')):
|
|
196
|
+
self.check_file(arquivo)
|
|
197
|
+
for conexao in self._conexoes.values():
|
|
198
|
+
if conexao is not None:
|
|
199
|
+
conexao.close()
|
|
200
|
+
return self.problems
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _single_name(expr: Optional[str]) -> Optional[str]:
|
|
204
|
+
"""`{tarefas}` -> 'tarefas'; anything more complex -> None."""
|
|
205
|
+
m = re.fullmatch(r'\s*\{\s*(\w+)\s*\}\s*', expr or '')
|
|
206
|
+
return m.group(1) if m else None
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def run_check(config_path: str = 'quantum.config.yaml') -> int:
|
|
210
|
+
"""`quantum check`: print each problem as file:line: message; exit 1 if there is any."""
|
|
211
|
+
caminho = Path(config_path).resolve()
|
|
212
|
+
checker = ProjectChecker(caminho.parent, caminho)
|
|
213
|
+
problemas = checker.run()
|
|
214
|
+
for nota in checker.notes:
|
|
215
|
+
print(f'[NOTE] {nota}')
|
|
216
|
+
for problema in problemas:
|
|
217
|
+
print(problema)
|
|
218
|
+
resumo = (f'{checker.files} file(s), {checker.queries_checked} query(ies) checked against the database')
|
|
219
|
+
if problemas:
|
|
220
|
+
print(f'\n{len(problemas)} problem(s) — {resumo}.')
|
|
221
|
+
return 1
|
|
222
|
+
print(f'OK — {resumo}.')
|
|
223
|
+
return 0
|
|
@@ -287,6 +287,7 @@ Examples:
|
|
|
287
287
|
quantum admin # Start the Quantum Admin
|
|
288
288
|
quantum console # The application's pages in the terminal
|
|
289
289
|
quantum desktop # The application's pages in a desktop window
|
|
290
|
+
quantum check # Pages parse, SQL compiles, query fields exist
|
|
290
291
|
quantum run game.q --engine godot # Build Godot 4 project
|
|
291
292
|
quantum run backup-job.q # Execute job
|
|
292
293
|
quantum deploy ./my-app # Deploy application
|
|
@@ -342,6 +343,10 @@ Examples:
|
|
|
342
343
|
console_parser.add_argument('path', nargs='?', default='/', help='Page to open (default: /)')
|
|
343
344
|
console_parser.add_argument('--config', default='quantum.config.yaml', help='Config file')
|
|
344
345
|
|
|
346
|
+
# Check: pages parse, queries compile, fields exist (M8, DEV-3)
|
|
347
|
+
check_parser = subparsers.add_parser('check', help='Check pages, SQL and query fields against the database')
|
|
348
|
+
check_parser.add_argument('--config', default='quantum.config.yaml', help='Config file')
|
|
349
|
+
|
|
345
350
|
# Desktop: the application's pages in a native window (UI-4)
|
|
346
351
|
desktop_parser = subparsers.add_parser('desktop', help="Open the application's pages in a desktop window")
|
|
347
352
|
desktop_parser.add_argument('path', nargs='?', default='/', help='Page to open (default: /)')
|
|
@@ -419,6 +424,10 @@ Examples:
|
|
|
419
424
|
from quantum.runtime.ui_console import run_console
|
|
420
425
|
sys.exit(run_console(args.config, args.path))
|
|
421
426
|
|
|
427
|
+
elif args.command == 'check':
|
|
428
|
+
from quantum.cli.check import run_check
|
|
429
|
+
sys.exit(run_check(args.config))
|
|
430
|
+
|
|
422
431
|
elif args.command == 'desktop':
|
|
423
432
|
from quantum.runtime.ui_desktop import run_desktop
|
|
424
433
|
sys.exit(run_desktop(args.config, args.path, width=args.width, height=args.height))
|
|
@@ -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,23 @@ 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
|
+
|
|
1873
1899
|
def is_ui_node(node) -> bool:
|
|
1874
1900
|
"""A ui:* element (UI-1): markup, drawn by a renderer, never executed."""
|
|
1875
1901
|
return type(node).__module__ == __name__
|
|
@@ -1882,6 +1908,6 @@ CORE_TAGS = frozenset({
|
|
|
1882
1908
|
'window', 'hbox', 'vbox', 'grid', 'panel', 'section', 'scrollbox', 'spacer', 'rule',
|
|
1883
1909
|
'header', 'footer', 'card', 'card-header', 'card-body', 'card-footer', 'tabpanel', 'tab',
|
|
1884
1910
|
'text', 'badge', 'alert', 'link', 'image', 'progress',
|
|
1885
|
-
'table', 'column', 'list', 'item',
|
|
1911
|
+
'table', 'column', 'list', 'item', 'pager',
|
|
1886
1912
|
'form', 'formitem', 'input', 'checkbox', 'switch', 'radio', 'select', 'option', 'button',
|
|
1887
1913
|
})
|
{quantum_framework-0.16.0 → quantum_framework-0.18.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,
|
|
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,7 @@ 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',
|
|
84
85
|
'list': '_parse_ui_list',
|
|
85
86
|
'item': '_parse_ui_item',
|
|
86
87
|
'image': '_parse_ui_image',
|
|
@@ -137,9 +138,24 @@ class UIParser:
|
|
|
137
138
|
import difflib
|
|
138
139
|
parecidas = difflib.get_close_matches(local_name, self.UI_TAG_MAP, n=1)
|
|
139
140
|
dica = f"; did you mean <ui:{parecidas[0]}>?" if parecidas else ""
|
|
140
|
-
|
|
141
|
+
erro = UIParseError(f"Unknown UI tag: <ui:{local_name}>{dica}")
|
|
142
|
+
erro.line = getattr(element, 'sourceline', None)
|
|
143
|
+
raise erro
|
|
141
144
|
method = getattr(self, method_name)
|
|
142
|
-
|
|
145
|
+
try:
|
|
146
|
+
node = method(element)
|
|
147
|
+
except UIParseError as exc:
|
|
148
|
+
if getattr(exc, 'line', None) is None:
|
|
149
|
+
exc.line = getattr(element, 'sourceline', None)
|
|
150
|
+
raise
|
|
151
|
+
# DEV-2: a ui:* element knows its line and file, like a q: statement.
|
|
152
|
+
if getattr(element, 'sourceline', None) and node is not None:
|
|
153
|
+
try:
|
|
154
|
+
node.source_line = element.sourceline
|
|
155
|
+
node.source_file = getattr(self.parent, '_source_path', None)
|
|
156
|
+
except AttributeError:
|
|
157
|
+
pass
|
|
158
|
+
return node
|
|
143
159
|
|
|
144
160
|
# ------------------------------------------------------------------
|
|
145
161
|
# Helpers
|
|
@@ -344,6 +360,13 @@ class UIParser:
|
|
|
344
360
|
node = UIFormNode()
|
|
345
361
|
node.on_submit = element.get('on-submit')
|
|
346
362
|
node.send = element.get('with')
|
|
363
|
+
# UI-9: rules="off" stops the form from taking its fields' rules from the action.
|
|
364
|
+
node.rules = element.get('rules')
|
|
365
|
+
# UI-10: an edit form opens with a row's values; the generated button's text.
|
|
366
|
+
node.values = element.get('values')
|
|
367
|
+
node.submit = element.get('submit')
|
|
368
|
+
if node.rules not in (None, 'on', 'off'):
|
|
369
|
+
raise UIParseError(f'<ui:form rules="{node.rules}">: use on (the default) or off')
|
|
347
370
|
|
|
348
371
|
# Validation attributes
|
|
349
372
|
node.validation_mode = element.get('validation', 'both')
|
|
@@ -415,6 +438,14 @@ class UIParser:
|
|
|
415
438
|
node = UIInputNode()
|
|
416
439
|
node.bind = element.get('bind')
|
|
417
440
|
node.value = element.get('value')
|
|
441
|
+
# UI-12: search as you type — refresh the element with this id.
|
|
442
|
+
node.search = element.get('search')
|
|
443
|
+
espera = element.get('delay')
|
|
444
|
+
if espera is not None and not espera.isdigit():
|
|
445
|
+
raise UIParseError(f'<ui:input delay="{espera}">: milliseconds, e.g. delay="300"')
|
|
446
|
+
node.delay = int(espera) if espera is not None else (300 if node.search else None)
|
|
447
|
+
if node.search is not None and not node.bind:
|
|
448
|
+
raise UIParseError('<ui:input search=...> needs bind= (the URL parameter it sets)')
|
|
418
449
|
node.placeholder = element.get('placeholder')
|
|
419
450
|
node.input_type = element.get('type', 'text')
|
|
420
451
|
node.on_change = element.get('on-change')
|
|
@@ -483,6 +514,14 @@ class UIParser:
|
|
|
483
514
|
node = UITableNode()
|
|
484
515
|
node.source = element.get('source')
|
|
485
516
|
node.as_var = element.get('as')
|
|
517
|
+
# UI-13: sortable headers and cells edited in place.
|
|
518
|
+
node.sort = self._parse_bool(element.get('sort'))
|
|
519
|
+
node.edit = element.get('edit')
|
|
520
|
+
node.edit_datasource = element.get('datasource')
|
|
521
|
+
if node.edit and not node.edit_datasource:
|
|
522
|
+
raise UIParseError(f'<ui:table edit="{node.edit}"> needs datasource= (where the table is)')
|
|
523
|
+
if (node.sort or node.edit) and not node.source:
|
|
524
|
+
raise UIParseError('<ui:table sort/edit> needs source="{query}"')
|
|
486
525
|
self._apply_layout_attrs(node, element)
|
|
487
526
|
# Parse child <ui:column> elements
|
|
488
527
|
self._parse_children(element, node)
|
|
@@ -494,9 +533,23 @@ class UIParser:
|
|
|
494
533
|
node.label = element.get('label')
|
|
495
534
|
node.column_width = element.get('width')
|
|
496
535
|
node.align = element.get('align')
|
|
536
|
+
node.edit = element.get('edit', 'true') != 'false'
|
|
497
537
|
self._parse_children(element, node)
|
|
498
538
|
return node
|
|
499
539
|
|
|
540
|
+
def _parse_ui_pager(self, element: ET.Element) -> UIPagerNode:
|
|
541
|
+
node = UIPagerNode()
|
|
542
|
+
node.for_query = element.get('for')
|
|
543
|
+
if not node.for_query:
|
|
544
|
+
raise UIParseError('<ui:pager> needs for="<the paginated q:query>"')
|
|
545
|
+
node.param = element.get('param') or 'page'
|
|
546
|
+
janela = element.get('window')
|
|
547
|
+
if janela is not None and not janela.isdigit():
|
|
548
|
+
raise UIParseError(f'<ui:pager window="{janela}">: the number of pages shown each side of the current one')
|
|
549
|
+
node.window = int(janela) if janela is not None else 2
|
|
550
|
+
self._apply_layout_attrs(node, element)
|
|
551
|
+
return node
|
|
552
|
+
|
|
500
553
|
def _parse_ui_list(self, element: ET.Element) -> UIListNode:
|
|
501
554
|
node = UIListNode()
|
|
502
555
|
node.source = element.get('source')
|
|
@@ -11,6 +11,7 @@ from typing import Union, Optional, List
|
|
|
11
11
|
# Fix imports
|
|
12
12
|
|
|
13
13
|
from quantum.core.html_compat import normalise_html
|
|
14
|
+
from quantum.core.xml_lines import fromstring_with_lines, line_of
|
|
14
15
|
from quantum.core.ast_nodes import (
|
|
15
16
|
QuantumNode, ComponentNode, ApplicationNode, JobNode,
|
|
16
17
|
QuantumParam, QuantumReturn, QuantumRoute,
|
|
@@ -222,6 +223,23 @@ def _explain_xml_error(error, source: str) -> str:
|
|
|
222
223
|
return message
|
|
223
224
|
|
|
224
225
|
|
|
226
|
+
def _at(erro: 'QuantumParseError', onde) -> 'QuantumParseError':
|
|
227
|
+
"""The error belongs to this node's (or element's) line."""
|
|
228
|
+
erro.line = line_of(onde)
|
|
229
|
+
return erro
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _with_line(erro: 'QuantumParseError', source: str, line: Optional[int] = None) -> 'QuantumParseError':
|
|
233
|
+
"""DEV-2: a parse error says where it is — `at line N: <the line>`."""
|
|
234
|
+
line = line or getattr(erro, 'line', None)
|
|
235
|
+
erro.line = line
|
|
236
|
+
if line and ' at line ' not in str(erro):
|
|
237
|
+
linhas = source.splitlines()
|
|
238
|
+
trecho = linhas[line - 1].strip() if 0 < line <= len(linhas) else ''
|
|
239
|
+
erro.args = (f"{erro}\n at line {line}: {trecho}" if trecho else f"{erro} (line {line})",)
|
|
240
|
+
return erro
|
|
241
|
+
|
|
242
|
+
|
|
225
243
|
class QuantumParser:
|
|
226
244
|
"""Main parser for .q files"""
|
|
227
245
|
|
|
@@ -370,17 +388,19 @@ class QuantumParser:
|
|
|
370
388
|
# parse for exactly those two reasons and returned HTTP 400.
|
|
371
389
|
content = normalise_html(source)
|
|
372
390
|
content = self._inject_namespace(content)
|
|
373
|
-
root =
|
|
391
|
+
root = fromstring_with_lines(content)
|
|
374
392
|
self._nest_else_siblings(root)
|
|
375
393
|
return self._parse_root_element(root, Path("<string>"))
|
|
376
394
|
except ET.ParseError as e:
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
raise
|
|
395
|
+
erro = QuantumParseError(_explain_xml_error(e, source))
|
|
396
|
+
erro.line = getattr(e, 'position', (None, None))[0]
|
|
397
|
+
raise erro
|
|
398
|
+
except QuantumParseError as e:
|
|
399
|
+
raise _with_line(e, source)
|
|
380
400
|
except ParserError as e:
|
|
381
401
|
# A tag parser's own error is a parse error with a message meant
|
|
382
402
|
# for the author, not an "Unexpected error".
|
|
383
|
-
raise QuantumParseError(str(e)) from e
|
|
403
|
+
raise _with_line(QuantumParseError(str(e)), source, getattr(e, 'line', None)) from e
|
|
384
404
|
except Exception as e:
|
|
385
405
|
raise QuantumParseError(f"Unexpected error: {e}")
|
|
386
406
|
|
|
@@ -412,7 +432,9 @@ class QuantumParser:
|
|
|
412
432
|
self._source_path = str(path.resolve())
|
|
413
433
|
source = Path(file_path).read_text(encoding="utf-8")
|
|
414
434
|
return self.parse(source)
|
|
415
|
-
except QuantumParseError:
|
|
435
|
+
except QuantumParseError as e:
|
|
436
|
+
if getattr(e, 'file', None) is None:
|
|
437
|
+
e.file = str(path.resolve()) # DEV-2
|
|
416
438
|
raise
|
|
417
439
|
except ParserError as e:
|
|
418
440
|
# A tag parser's own error is a parse error with a message meant
|
|
@@ -498,7 +520,7 @@ class QuantumParser:
|
|
|
498
520
|
conhecidas |= {'return', 'else', 'elseif'}
|
|
499
521
|
parecidas = difflib.get_close_matches(name, sorted(conhecidas), n=3, cutoff=0.6)
|
|
500
522
|
dica = (" Did you mean " + " or ".join(f"<q:{p}>" for p in parecidas) + "?") if parecidas else ""
|
|
501
|
-
return f"<q:{name}> is not a Quantum tag.{dica}"
|
|
523
|
+
return f"<q:{name}> is not a Quantum tag (PARSE-1).{dica}"
|
|
502
524
|
|
|
503
525
|
@staticmethod
|
|
504
526
|
def _is_quantum_tag(element: ET.Element) -> bool:
|
|
@@ -683,12 +705,12 @@ class QuantumParser:
|
|
|
683
705
|
lidos = names_read(re.sub(r'\{([^{}]*)\}', r'\1', condicao)) & definidos
|
|
684
706
|
if lidos:
|
|
685
707
|
nome = sorted(lidos)[0]
|
|
686
|
-
raise QuantumParseError(
|
|
708
|
+
raise _at(QuantumParseError(
|
|
687
709
|
f'<q:if condition="{condicao}"> in <q:component name="{component.name}"> '
|
|
688
|
-
f'redirects, so it also runs before the page\'s q:action — where the page '
|
|
710
|
+
f'redirects (AUTH-6), so it also runs before the page\'s q:action — where the page '
|
|
689
711
|
f'has not run and {{{nome}}} does not exist, and the guard would let the '
|
|
690
712
|
f'action through. Read the scope directly (e.g. session.x), or protect the '
|
|
691
|
-
f'page with require_auth / require_role. See {HOW_A_PAGE_RUNS}')
|
|
713
|
+
f'page with require_auth / require_role. See {HOW_A_PAGE_RUNS}'), guarda)
|
|
692
714
|
|
|
693
715
|
# Nodes the HTML renderer draws. Anything else inside markup is a statement,
|
|
694
716
|
# and statements run in the execution pass, before the render pass sees
|
|
@@ -762,21 +784,21 @@ class QuantumParser:
|
|
|
762
784
|
for node in nodes:
|
|
763
785
|
if not isinstance(node, self._RENDERED) and not is_ui_node(node):
|
|
764
786
|
if onde and onde[0] == 'markup':
|
|
765
|
-
raise QuantumParseError(
|
|
787
|
+
raise _at(QuantumParseError(
|
|
766
788
|
f'{tag(node)} inside {onde[1]} in <q:component name="{component.name}"> '
|
|
767
|
-
f'never runs: statements run before the page is rendered, and markup '
|
|
789
|
+
f'never runs (PARSE-2): statements run before the page is rendered, and markup '
|
|
768
790
|
f'is only rendered. Move it above the markup (or into a q:action). '
|
|
769
|
-
f'See {HOW_A_PAGE_RUNS}')
|
|
791
|
+
f'See {HOW_A_PAGE_RUNS}'), node)
|
|
770
792
|
nome = getattr(node, 'name', None)
|
|
771
793
|
# A running total read AFTER the loop is fine; a value the
|
|
772
794
|
# loop's own rows read is not.
|
|
773
795
|
if onde and isinstance(nome, str) and usado(nome, onde[1]):
|
|
774
|
-
raise QuantumParseError(
|
|
796
|
+
raise _at(QuantumParseError(
|
|
775
797
|
f'{tag(node)} inside a q:loop whose rows use {{{nome}}} in '
|
|
776
798
|
f'<q:component name="{component.name}">: the loop runs for every item '
|
|
777
|
-
f'before any row is rendered, so every row would show the last value. '
|
|
799
|
+
f'before any row is rendered, so every row would show the last value (PARSE-2). '
|
|
778
800
|
f'Compute it in the expression instead, e.g. {{item.price * item.qty}}. '
|
|
779
|
-
f'See {HOW_A_PAGE_RUNS}')
|
|
801
|
+
f'See {HOW_A_PAGE_RUNS}'), node)
|
|
780
802
|
continue
|
|
781
803
|
if isinstance(node, HTMLNode):
|
|
782
804
|
andar(filhos(node), onde if onde and onde[0] == 'markup' else ('markup', f'<{node.tag}>'))
|
|
@@ -844,6 +866,27 @@ class QuantumParser:
|
|
|
844
866
|
if_node.add_else_statement(stmt)
|
|
845
867
|
|
|
846
868
|
def _parse_statement(self, element: ET.Element) -> Optional[QuantumNode]:
|
|
869
|
+
"""Parse one statement, and remember where it is in the file (DEV-2).
|
|
870
|
+
|
|
871
|
+
The node gets `source_line`, which runtime errors report; a parse
|
|
872
|
+
error from this element — or from inside it, if nothing deeper
|
|
873
|
+
claimed it — gets `line`.
|
|
874
|
+
"""
|
|
875
|
+
try:
|
|
876
|
+
node = self._parse_statement_node(element)
|
|
877
|
+
except (QuantumParseError, ParserError) as exc:
|
|
878
|
+
if getattr(exc, 'line', None) is None:
|
|
879
|
+
exc.line = getattr(element, 'sourceline', None)
|
|
880
|
+
raise
|
|
881
|
+
if node is not None and getattr(element, 'sourceline', None):
|
|
882
|
+
try:
|
|
883
|
+
node.source_line = element.sourceline
|
|
884
|
+
node.source_file = self._source_path
|
|
885
|
+
except AttributeError:
|
|
886
|
+
pass # a node with __slots__
|
|
887
|
+
return node
|
|
888
|
+
|
|
889
|
+
def _parse_statement_node(self, element: ET.Element) -> Optional[QuantumNode]:
|
|
847
890
|
"""Parse individual statement (return, set, dispatchEvent, etc)"""
|
|
848
891
|
element_type = self._get_element_name(element)
|
|
849
892
|
|
|
@@ -857,7 +900,9 @@ class QuantumParser:
|
|
|
857
900
|
try:
|
|
858
901
|
return UIParser(self, page_mode=True).parse_ui_element(element_type, element)
|
|
859
902
|
except UIParseError as exc:
|
|
860
|
-
|
|
903
|
+
erro = QuantumParseError(str(exc))
|
|
904
|
+
erro.line = getattr(exc, 'line', None) # the inner ui:* element's line
|
|
905
|
+
raise erro from exc
|
|
861
906
|
|
|
862
907
|
# PARSE-1: <q:html>, <q:div> — an HTML element name in the q:
|
|
863
908
|
# namespace. The registry answered for it with the HTML parser, so
|
{quantum_framework-0.16.0 → quantum_framework-0.18.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
|