quantum-framework 0.12.0__tar.gz → 0.13.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.12.0/quantum_framework.egg-info → quantum_framework-0.13.0}/PKG-INFO +1 -1
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/pyproject.toml +1 -1
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/cli/commands/migrate.py +31 -3
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/cli/runner.py +4 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/expression_stdlib.py +28 -2
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/expressions.py +69 -4
- quantum_framework-0.13.0/quantum/core/guards.py +53 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parser.py +67 -12
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/control_flow/if_parser.py +25 -22
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/forms/action_parser.py +17 -7
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/action_handler.py +8 -2
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/component.py +46 -20
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/database_service.py +32 -40
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/error_handler.py +1 -1
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/execution_context.py +0 -3
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/__init__.py +2 -2
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/base.py +3 -1
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/control_flow/__init__.py +2 -1
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/control_flow/if_executor.py +14 -10
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/control_flow/loop_executor.py +6 -6
- quantum_framework-0.13.0/quantum/runtime/executors/control_flow/redirect_executor.py +43 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/renderer.py +1 -1
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/web_server.py +128 -47
- {quantum_framework-0.12.0 → quantum_framework-0.13.0/quantum_framework.egg-info}/PKG-INFO +1 -1
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_framework.egg-info/SOURCES.txt +2 -1
- quantum_framework-0.12.0/tests/test_projects_blog_components_admin.py +0 -514
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/LICENSE +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/README.md +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/cli/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/cli/admin.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/cli/commands/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/cli/commands/build.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/cli/commands/dev.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/cli/commands/docs.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/cli/commands/lint.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/cli/commands/new.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/cli/commands/serve.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/cli/commands/test.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/cli/deploy.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/cli/hot_reload.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/cli/jobs.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/cli/mq.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/cli/pkg.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/cli/server_process.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/cli/utils.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/compiler/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/compiler/base_generator.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/compiler/cli.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/compiler/expression_transformer.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/compiler/javascript/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/compiler/javascript/generator.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/compiler/optimizer.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/compiler/python/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/compiler/python/generator.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/compiler/python/runtime.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/compiler/transpiler.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/ast_nodes.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/config_env.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/expression_diagnostics.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/agents/src/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/agents/src/ast_node.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/conditionals/src/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/conditionals/src/ast_node.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/data_import/src/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/data_import/src/ast_node.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/data_import/src/runtime.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/dump/src/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/dump/src/ast_node.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/dump/src/parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/dump/src/runtime.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/functions/src/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/functions/src/ast_node.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/game_engine_2d/src/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/game_engine_2d/src/ast_nodes.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/game_engine_2d/src/parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/invocation/src/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/invocation/src/ast_node.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/invocation/src/runtime.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/knowledge_base/src/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/knowledge_base/src/ast_node.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/knowledge_base/src/parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/logging/src/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/logging/src/ast_node.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/logging/src/parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/logging/src/runtime.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/loops/src/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/loops/src/ast_node.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/query/src/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/query/src/database_service.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/query/src/query_validators.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/state_management/src/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/state_management/src/ast_node.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/terminal_engine/src/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/terminal_engine/src/ast_nodes.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/terminal_engine/src/parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/testing_engine/src/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/testing_engine/src/ast_nodes.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/testing_engine/src/parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/theming/src/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/theming/src/ast_node.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/theming/src/presets.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/ui_engine/src/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/ui_engine/src/ast_nodes.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/ui_engine/src/parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/websocket/src/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/features/websocket/src/ast_node.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/html_compat.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parser_registry.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/ai/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/ai/agent_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/ai/knowledge_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/ai/llm_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/ai/team_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/base.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/composition/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/composition/import_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/composition/slot_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/control_flow/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/control_flow/loop_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/control_flow/set_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/data/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/data/data_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/data/invoke_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/data/query_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/data/transaction_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/events/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/events/dispatch_event_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/forms/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/forms/flash_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/forms/redirect_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/functions/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/functions/function_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/functions/param_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/functions/return_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/html/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/html/component_call_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/html/html_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/jobs/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/jobs/job_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/jobs/schedule_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/jobs/thread_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/messaging/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/messaging/message_ack_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/messaging/message_nack_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/messaging/message_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/messaging/queue_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/messaging/websocket_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/persistence/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/persistence/persist_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/routing/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/routing/route_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/scripting/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/scripting/pyclass_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/scripting/pydecorator_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/scripting/pyimport_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/scripting/python_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/services/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/services/dump_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/services/file_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/services/log_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/services/mail_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/tiers.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/packages/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/packages/manager.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/packages/manifest.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/packages/registry.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/packages/resolver.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/plugins/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/plugins/hooks.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/plugins/loader.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/plugins/manifest.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/plugins/registry.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/adapters/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/adapters/memory_adapter.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/adapters/rabbitmq_adapter.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/adapters/redis_adapter.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/adapters/sqlite_adapter.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/agent_service.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/ast_cache.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/auth_service.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/component_composer.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/component_resolver.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/email_service.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executor_registry.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/ai/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/ai/agent_executor.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/ai/knowledge_executor.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/ai/llm_executor.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/ai/team_executor.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/control_flow/set_executor.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/data/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/data/data_executor.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/data/invoke_executor.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/data/query_executor.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/data/transaction_executor.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/jobs/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/jobs/job_executor.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/jobs/schedule_executor.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/jobs/thread_executor.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/messaging/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/messaging/message_ack_executor.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/messaging/message_executor.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/messaging/queue_executor.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/messaging/websocket_executor.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/scripting/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/scripting/pyclass_executor.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/scripting/pyimport_executor.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/scripting/python_executor.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/services/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/services/dump_executor.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/services/file_executor.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/services/log_executor.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/executors/services/mail_executor.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/expression_cache.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/file_upload_service.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/function_registry.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/game_builder.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/game_code_generator.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/game_templates.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/godot_code_generator.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/godot_templates.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/job_executor.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/knowledge_service.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/llm_cache.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/llm_providers.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/llm_service.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/logging_setup.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/message_broker.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/message_queue_service.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/param_validation.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/pypy_compat.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/python_bridge.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/query_validators.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/service_container.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/terminal_builder.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/terminal_code_generator.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/terminal_templates.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/testing_builder.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/testing_code_generator.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/testing_templates.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/ui_builder.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/ui_desktop_adapter.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/ui_desktop_templates.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/ui_html_adapter.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/ui_html_templates.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/ui_mobile_adapter.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/ui_mobile_templates.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/ui_textual_adapter.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/ui_textual_templates.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/ui_tokens.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/ui_validator.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/validators.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/websocket_adapter.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/websocket_service.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/websocket_transport.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/runtime/wsgi.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/services.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/utils/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/assets/quantum-admin-connectors.js +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/assets/quantum-admin.css +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/assets/quantum-source-highlight.js +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/backend/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/backend/audit_service.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/backend/auth_service.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/backend/component_discovery.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/backend/config_generator.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/backend/connector_service.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/backend/crud.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/backend/database.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/backend/db_setup_service.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/backend/deploy_service.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/backend/docker_service.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/backend/environment_service.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/backend/git_service.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/backend/health_service.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/backend/job_service.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/backend/log_service.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/backend/main.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/backend/models.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/backend/pipeline_service.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/backend/resource_manager.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/backend/schemas.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/backend/secret_manager.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/backend/settings_service.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/backend/template_service.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/backend/test_execution_service.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/backend/test_generator.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/backend/webhook_service.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/backend/websocket_manager.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/components/admin/_layout/AdminShell.q +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/components/admin/agents.q +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/components/admin/app/[name].q +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/components/admin/applications.q +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/components/admin/component/[...path].q +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/components/admin/components.q +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/components/admin/connectors.q +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/components/admin/dashboard.q +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/components/admin/database.q +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/components/admin/features.q +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/components/admin/index.q +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/components/admin/jobs.q +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/components/admin/login.q +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/components/admin/logout.q +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/components/admin/settings.q +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/components/admin/source.q +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/components/admin/tests.q +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/run.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/services/__init__.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/services/_base.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/services/_component_test_generator.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/services/_yaml_editor.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/services/apps.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/services/auth.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/services/components.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/services/connectors.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/services/projects.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/services/repository.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/services/settings.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_admin/services/yaml_import.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_framework.egg-info/dependency_links.txt +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_framework.egg-info/entry_points.txt +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_framework.egg-info/requires.txt +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum_framework.egg-info/top_level.txt +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/setup.cfg +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_agent.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_animations.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_ast_cache.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_cli_commands.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_component_library.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_desktop_bridge.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_expression_cache.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_form_validation.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_game_codegen.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_game_e2e.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_game_engine.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_game_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_godot_codegen.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_job_executor.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_llm_providers.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_message_broker.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_message_queue.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_mobile_target.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_multi_agent.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_packages.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_plugins.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_pypy_compat.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_python_scripting.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_state_persistence.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_terminal_codegen.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_terminal_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_testing_codegen.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_testing_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_theming.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_ui_html_adapter.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_ui_parity.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_ui_parser.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.0}/tests/test_ui_textual_adapter.py +0 -0
- {quantum_framework-0.12.0 → quantum_framework-0.13.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.13.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"}
|
|
@@ -70,7 +70,9 @@ class DatabaseConnection:
|
|
|
70
70
|
if not caminho.is_absolute():
|
|
71
71
|
caminho = self.project_path / caminho
|
|
72
72
|
caminho.parent.mkdir(parents=True, exist_ok=True)
|
|
73
|
-
|
|
73
|
+
# isolation_level=None: execute_script opens the transaction
|
|
74
|
+
# itself, so a CREATE TABLE is inside it too (DB-8).
|
|
75
|
+
self._conn = sqlite3.connect(str(caminho), isolation_level=None)
|
|
74
76
|
self._db_type = 'sqlite'
|
|
75
77
|
logger.info("Migrating datasource %s (SQLite at %s)", nome, caminho)
|
|
76
78
|
return self._conn
|
|
@@ -128,6 +130,32 @@ class DatabaseConnection:
|
|
|
128
130
|
cursor.execute(sql)
|
|
129
131
|
return cursor
|
|
130
132
|
|
|
133
|
+
def execute_script(self, sql: str):
|
|
134
|
+
"""Run a whole migration file, in one transaction (DB-8).
|
|
135
|
+
|
|
136
|
+
sqlite3's execute() takes ONE statement, so every migration with more
|
|
137
|
+
than one — a table and its index — failed with "You can only execute
|
|
138
|
+
one statement at a time". The statements are split here and run
|
|
139
|
+
inside an explicit BEGIN, which commit()/rollback() close: a failure
|
|
140
|
+
halfway leaves no table behind.
|
|
141
|
+
"""
|
|
142
|
+
if self._db_type != 'sqlite':
|
|
143
|
+
return self.execute(sql)
|
|
144
|
+
import sqlite3
|
|
145
|
+
cursor = self._conn.cursor()
|
|
146
|
+
if not self._conn.in_transaction:
|
|
147
|
+
cursor.execute('BEGIN')
|
|
148
|
+
pedaco = ''
|
|
149
|
+
for linha in sql.splitlines(keepends=True):
|
|
150
|
+
pedaco += linha
|
|
151
|
+
if sqlite3.complete_statement(pedaco):
|
|
152
|
+
if pedaco.strip():
|
|
153
|
+
cursor.execute(pedaco)
|
|
154
|
+
pedaco = ''
|
|
155
|
+
if pedaco.strip() and not all(l.strip().startswith('--') or not l.strip() for l in pedaco.splitlines()):
|
|
156
|
+
raise MigrationError(f"incomplete SQL statement at the end: {pedaco.strip()[:80]!r}")
|
|
157
|
+
return cursor
|
|
158
|
+
|
|
131
159
|
def commit(self):
|
|
132
160
|
self._conn.commit()
|
|
133
161
|
|
|
@@ -275,7 +303,7 @@ class MigrationRunner:
|
|
|
275
303
|
click.echo(f"Applying {migration.version}: {migration.name}...")
|
|
276
304
|
|
|
277
305
|
# Execute migration SQL
|
|
278
|
-
self.db.
|
|
306
|
+
self.db.execute_script(migration.content)
|
|
279
307
|
|
|
280
308
|
# Record migration
|
|
281
309
|
self._record_migration(migration)
|
|
@@ -338,7 +366,7 @@ class MigrationRunner:
|
|
|
338
366
|
click.echo(f"Rolling back {version}: {migration_info['name']}...")
|
|
339
367
|
|
|
340
368
|
# Execute rollback SQL
|
|
341
|
-
self.db.
|
|
369
|
+
self.db.execute_script(down_file.read_text())
|
|
342
370
|
|
|
343
371
|
# Remove migration record
|
|
344
372
|
self._remove_migration_record(version)
|
|
@@ -24,6 +24,7 @@ if os.name == 'nt': # Windows
|
|
|
24
24
|
from quantum.core.parser import QuantumParser, QuantumParseError
|
|
25
25
|
from quantum.core.ast_nodes import ComponentNode, ApplicationNode, JobNode
|
|
26
26
|
from quantum.runtime.component import ComponentRuntime, ComponentExecutionError
|
|
27
|
+
from quantum.runtime.executors.control_flow.redirect_executor import PageRedirect
|
|
27
28
|
from quantum.runtime.web_server import QuantumWebServer
|
|
28
29
|
|
|
29
30
|
# Import deploy commands (lazy load to avoid import errors if requests not installed)
|
|
@@ -166,6 +167,9 @@ class QuantumRunner:
|
|
|
166
167
|
except ComponentExecutionError as e:
|
|
167
168
|
print(f"[ERROR] Execution error: {e}")
|
|
168
169
|
return 1
|
|
170
|
+
except PageRedirect as r:
|
|
171
|
+
print(f"[REDIRECT] {r.status} {r.url}")
|
|
172
|
+
return 0
|
|
169
173
|
|
|
170
174
|
def _execute_application(self, app: ApplicationNode, debug: bool = False) -> int:
|
|
171
175
|
"""Execute q:application"""
|
|
@@ -15,6 +15,9 @@ Functions are looked up by name from expressions, so anything added here
|
|
|
15
15
|
becomes language surface. That is the reason to keep it short.
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
+
import math
|
|
19
|
+
import re
|
|
20
|
+
from decimal import Decimal, ROUND_HALF_UP
|
|
18
21
|
from datetime import datetime, timedelta
|
|
19
22
|
from typing import Any, Iterable, Optional
|
|
20
23
|
|
|
@@ -84,6 +87,13 @@ def _split(v: Any, sep: str = ",") -> list: return str(v).split(sep)
|
|
|
84
87
|
def _contains(v: Any, needle: Any) -> bool: return str(needle) in str(v)
|
|
85
88
|
|
|
86
89
|
|
|
90
|
+
def _slugify(v: Any) -> str:
|
|
91
|
+
"""'Olá, Mundo!' -> 'ola-mundo': for URLs made from a title (EXPR-9)."""
|
|
92
|
+
import unicodedata
|
|
93
|
+
texto = unicodedata.normalize('NFKD', str(v)).encode('ascii', 'ignore').decode('ascii')
|
|
94
|
+
return re.sub(r'[^a-z0-9]+', '-', texto.lower()).strip('-')
|
|
95
|
+
|
|
96
|
+
|
|
87
97
|
# -- collections -----------------------------------------------------------
|
|
88
98
|
|
|
89
99
|
def _len(v: Any) -> int:
|
|
@@ -114,8 +124,21 @@ def _sort(v: Iterable, reverse: bool = False) -> list:
|
|
|
114
124
|
# -- numbers ---------------------------------------------------------------
|
|
115
125
|
|
|
116
126
|
def _round(v: Any, digits: int = 0) -> Any:
|
|
117
|
-
|
|
118
|
-
|
|
127
|
+
# EXPR-9: half away from zero, as in ColdFusion and spreadsheets. Python's
|
|
128
|
+
# round() is banker's rounding over binary floats: round(2.5) was 2 and
|
|
129
|
+
# round(0.125, 2) was 0.12. Decimal over the number's text avoids both.
|
|
130
|
+
casas = int(digits)
|
|
131
|
+
passo = Decimal(1).scaleb(-casas)
|
|
132
|
+
result = Decimal(str(v)).quantize(passo, rounding=ROUND_HALF_UP)
|
|
133
|
+
return int(result) if casas <= 0 else float(result)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _ceil(v: Any) -> int:
|
|
137
|
+
return math.ceil(float(v))
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _floor(v: Any) -> int:
|
|
141
|
+
return math.floor(float(v))
|
|
119
142
|
|
|
120
143
|
|
|
121
144
|
# -- passwords ---------------------------------------------------------------
|
|
@@ -165,6 +188,7 @@ STDLIB = {
|
|
|
165
188
|
'replace': _replace,
|
|
166
189
|
'split': _split,
|
|
167
190
|
'contains': _contains,
|
|
191
|
+
'slugify': _slugify,
|
|
168
192
|
# collections
|
|
169
193
|
'len': _len,
|
|
170
194
|
'first': _first,
|
|
@@ -173,6 +197,8 @@ STDLIB = {
|
|
|
173
197
|
'sort': _sort,
|
|
174
198
|
# numbers
|
|
175
199
|
'round': _round,
|
|
200
|
+
'ceil': _ceil,
|
|
201
|
+
'floor': _floor,
|
|
176
202
|
'abs': abs,
|
|
177
203
|
'min': min,
|
|
178
204
|
'max': max,
|
|
@@ -23,6 +23,7 @@ access, and the whole arithmetic/comparison/call surface is about 15%.
|
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
import ast
|
|
26
|
+
import contextvars
|
|
26
27
|
import math
|
|
27
28
|
import operator
|
|
28
29
|
import re
|
|
@@ -30,6 +31,11 @@ from typing import Any, Callable, Dict, Optional
|
|
|
30
31
|
|
|
31
32
|
from quantum.core.expression_stdlib import STDLIB
|
|
32
33
|
|
|
34
|
+
# EXPR-8: in a condition, a key a scope does not have (`session.userId`
|
|
35
|
+
# before login) is absent, not a failure — see ExpressionEvaluator.evaluate.
|
|
36
|
+
_SCOPES = ('session', 'application', 'request', 'cookie', 'form', 'query')
|
|
37
|
+
_ABSENT_SCOPE_KEY_IS_NONE = contextvars.ContextVar('absent_scope_key_is_none', default=False)
|
|
38
|
+
|
|
33
39
|
|
|
34
40
|
class ExpressionError(Exception):
|
|
35
41
|
"""Raised when an expression cannot be evaluated."""
|
|
@@ -249,6 +255,43 @@ def _normalise_js_operators(source: str) -> str:
|
|
|
249
255
|
return ''.join(out)
|
|
250
256
|
|
|
251
257
|
|
|
258
|
+
# EXPR-10: what a JavaScript habit is called here. `Math.ceil(x)` and
|
|
259
|
+
# `text.split(' ')` are the first things people write; the bare "only named
|
|
260
|
+
# function calls are allowed" did not say what to write instead.
|
|
261
|
+
_JS_EQUIVALENTS = {
|
|
262
|
+
'Math.ceil': 'ceil(x)', 'Math.floor': 'floor(x)', 'Math.round': 'round(x)',
|
|
263
|
+
'Math.max': 'max(a, b)', 'Math.min': 'min(a, b)', 'Math.abs': 'abs(x)',
|
|
264
|
+
'Date.now': 'now()', 'parseInt': 'int(x)', 'parseFloat': 'float(x)',
|
|
265
|
+
'split': "split(text, ' ')", 'join': "join(items, ', ')", 'toUpperCase': 'upper(text)',
|
|
266
|
+
'toLowerCase': 'lower(text)', 'trim': 'trim(text)', 'replace': 'replace(text, old, new)',
|
|
267
|
+
'includes': 'contains(text, part)', 'indexOf': 'contains(text, part)',
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _method_hint(func: ast.AST) -> str:
|
|
272
|
+
if not isinstance(func, ast.Attribute):
|
|
273
|
+
return ""
|
|
274
|
+
base = func.value.id if isinstance(func.value, ast.Name) else None
|
|
275
|
+
chave = f"{base}.{func.attr}" if base in ('Math', 'Date') else func.attr
|
|
276
|
+
if chave in _JS_EQUIVALENTS:
|
|
277
|
+
escrito = chave if '.' in chave else f"x.{func.attr}"
|
|
278
|
+
return f"; write {_JS_EQUIVALENTS[chave]} instead of {escrito}(...)"
|
|
279
|
+
return f"; call functions by name, e.g. {func.attr}(x), not x.{func.attr}()"
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def names_read(expression: str) -> set:
|
|
283
|
+
"""The root names an expression reads — `user` in `user.name > 1` — not the
|
|
284
|
+
functions it calls. An expression that does not parse reads nothing here;
|
|
285
|
+
evaluating it reports the error."""
|
|
286
|
+
try:
|
|
287
|
+
tree = ast.parse(_normalise_js_operators(expression.strip()), mode='eval')
|
|
288
|
+
except SyntaxError:
|
|
289
|
+
return set()
|
|
290
|
+
chamadas = {id(n.func) for n in ast.walk(tree) if isinstance(n, ast.Call)}
|
|
291
|
+
return {n.id for n in ast.walk(tree)
|
|
292
|
+
if isinstance(n, ast.Name) and id(n) not in chamadas}
|
|
293
|
+
|
|
294
|
+
|
|
252
295
|
class ExpressionEvaluator:
|
|
253
296
|
"""Evaluates Quantum `{...}` expressions against a variable context."""
|
|
254
297
|
|
|
@@ -274,8 +317,24 @@ class ExpressionEvaluator:
|
|
|
274
317
|
|
|
275
318
|
# -- public API --------------------------------------------------------
|
|
276
319
|
|
|
277
|
-
def evaluate(self, expression: str, context: Dict[str, Any]
|
|
278
|
-
|
|
320
|
+
def evaluate(self, expression: str, context: Dict[str, Any],
|
|
321
|
+
absent_scope_key_is_none: bool = False) -> Any:
|
|
322
|
+
"""Evaluate an expression, raising ExpressionError on any failure.
|
|
323
|
+
|
|
324
|
+
absent_scope_key_is_none (conditions, EXPR-8): `session.x` where the
|
|
325
|
+
scope has no `x` reads as None instead of raising. A condition is a
|
|
326
|
+
presence test, and a scope key that is not there yet is the ordinary
|
|
327
|
+
case (a page runs before login) — raising made the WHOLE condition
|
|
328
|
+
false, so `not session.authenticated` was false for a visitor with no
|
|
329
|
+
session and a guard written that way never fired.
|
|
330
|
+
"""
|
|
331
|
+
marca = _ABSENT_SCOPE_KEY_IS_NONE.set(absent_scope_key_is_none)
|
|
332
|
+
try:
|
|
333
|
+
return self._evaluate(expression, context)
|
|
334
|
+
finally:
|
|
335
|
+
_ABSENT_SCOPE_KEY_IS_NONE.reset(marca)
|
|
336
|
+
|
|
337
|
+
def _evaluate(self, expression: str, context: Dict[str, Any]) -> Any:
|
|
279
338
|
if expression is None:
|
|
280
339
|
raise ExpressionError("expression is None")
|
|
281
340
|
|
|
@@ -473,6 +532,11 @@ class ExpressionEvaluator:
|
|
|
473
532
|
plano = self._dotted_name(node)
|
|
474
533
|
if plano is not None and plano in ctx:
|
|
475
534
|
return ctx[plano]
|
|
535
|
+
if (_ABSENT_SCOPE_KEY_IS_NONE.get() and isinstance(node.value, ast.Name)
|
|
536
|
+
and node.value.id in _SCOPES):
|
|
537
|
+
escopo = ctx.get(node.value.id)
|
|
538
|
+
if escopo is None or (isinstance(escopo, dict) and node.attr not in escopo):
|
|
539
|
+
return None
|
|
476
540
|
return self._attribute(self._eval(node.value, ctx), node.attr)
|
|
477
541
|
|
|
478
542
|
if isinstance(node, ast.Subscript):
|
|
@@ -588,7 +652,7 @@ class ExpressionEvaluator:
|
|
|
588
652
|
|
|
589
653
|
def _call(self, node: ast.Call, ctx: Dict[str, Any]) -> Any:
|
|
590
654
|
if not isinstance(node.func, ast.Name):
|
|
591
|
-
raise ExpressionError("only named function calls are allowed")
|
|
655
|
+
raise ExpressionError("only named function calls are allowed" + _method_hint(node.func))
|
|
592
656
|
fn = self.functions.get(node.func.id)
|
|
593
657
|
if fn is None and self.function_resolver is not None:
|
|
594
658
|
# A q:function declared by the component being executed.
|
|
@@ -600,7 +664,8 @@ class ExpressionEvaluator:
|
|
|
600
664
|
if fn is None:
|
|
601
665
|
raise ExpressionError(
|
|
602
666
|
f"function {node.func.id!r} is not defined"
|
|
603
|
-
f"{
|
|
667
|
+
+ (f"; write {_JS_EQUIVALENTS[node.func.id]}" if node.func.id in _JS_EQUIVALENTS
|
|
668
|
+
else _suggest(node.func.id, self.functions))
|
|
604
669
|
)
|
|
605
670
|
args = [self._eval(a, ctx) for a in node.args]
|
|
606
671
|
kwargs = {kw.arg: self._eval(kw.value, ctx) for kw in node.keywords}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Page guards (AUTH-6): a q:if at the top of a page whose branch has a q:redirect.
|
|
2
|
+
|
|
3
|
+
<q:if condition="not session.authenticated">
|
|
4
|
+
<q:redirect url="/login" />
|
|
5
|
+
</q:if>
|
|
6
|
+
|
|
7
|
+
A q:action does not run the page's statements, so a guard written like this
|
|
8
|
+
used to protect only the GET — the POST ran the action without a session.
|
|
9
|
+
Guards now run before each action of the page as well as before the render.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import List
|
|
13
|
+
|
|
14
|
+
from quantum.core.ast_nodes import RedirectNode
|
|
15
|
+
from quantum.core.features.conditionals.src.ast_node import IfNode
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _branches(node: IfNode) -> List:
|
|
19
|
+
corpo = list(node.if_body)
|
|
20
|
+
for bloco in node.elseif_blocks:
|
|
21
|
+
corpo += list(bloco.get('body', []))
|
|
22
|
+
return corpo + list(node.else_body)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _has_redirect(nodes) -> bool:
|
|
26
|
+
return any(isinstance(n, RedirectNode) or (isinstance(n, IfNode) and _has_redirect(_branches(n)))
|
|
27
|
+
for n in nodes)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def page_guards(component) -> List[IfNode]:
|
|
31
|
+
"""The top-level q:if statements that can redirect, in document order."""
|
|
32
|
+
return [s for s in getattr(component, 'statements', [])
|
|
33
|
+
if isinstance(s, IfNode) and _has_redirect(_branches(s))]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def guard_conditions(node: IfNode) -> List[str]:
|
|
37
|
+
"""Every condition a guard evaluates, nested q:if included."""
|
|
38
|
+
condicoes = [node.condition] + [b.get('condition', '') for b in node.elseif_blocks]
|
|
39
|
+
for filho in _branches(node):
|
|
40
|
+
if isinstance(filho, IfNode):
|
|
41
|
+
condicoes += guard_conditions(filho)
|
|
42
|
+
return [c for c in condicoes if c]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def mark_guards(component) -> None:
|
|
46
|
+
"""Flag each guard, nested q:if included, so its condition fails closed (AUTH-6)."""
|
|
47
|
+
def marcar(node):
|
|
48
|
+
node.is_guard = True
|
|
49
|
+
for filho in _branches(node):
|
|
50
|
+
if isinstance(filho, IfNode):
|
|
51
|
+
marcar(filho)
|
|
52
|
+
for guarda in page_guards(component):
|
|
53
|
+
marcar(guarda)
|
|
@@ -626,9 +626,60 @@ class QuantumParser:
|
|
|
626
626
|
self._parse_control_flow_statements(root, component)
|
|
627
627
|
|
|
628
628
|
self._check_statements_in_markup(component)
|
|
629
|
+
self._check_guards(component)
|
|
629
630
|
|
|
630
631
|
return component
|
|
631
632
|
|
|
633
|
+
def _check_guards(self, component: ComponentNode) -> None:
|
|
634
|
+
"""AUTH-6: a guard of a page with actions reads only what exists before the page runs.
|
|
635
|
+
|
|
636
|
+
Guards also run before each q:action, and an action does not run the
|
|
637
|
+
page's other statements. A guard reading a page variable would find it
|
|
638
|
+
missing there, read the condition as false, and let the action run.
|
|
639
|
+
"""
|
|
640
|
+
from quantum.core.guards import guard_conditions, mark_guards, page_guards
|
|
641
|
+
from quantum.core.expressions import names_read
|
|
642
|
+
|
|
643
|
+
mark_guards(component)
|
|
644
|
+
if not any(isinstance(s, ActionNode) for s in component.statements):
|
|
645
|
+
return
|
|
646
|
+
guardas = page_guards(component)
|
|
647
|
+
if not guardas:
|
|
648
|
+
return
|
|
649
|
+
|
|
650
|
+
definidos = {p.name for p in getattr(component, 'params', []) or []}
|
|
651
|
+
|
|
652
|
+
def definir(nodes):
|
|
653
|
+
for node in nodes:
|
|
654
|
+
if isinstance(node, (ActionNode, FunctionNode, HTMLNode, TextNode, CommentNode)):
|
|
655
|
+
continue
|
|
656
|
+
if isinstance(node, IfNode):
|
|
657
|
+
corpo = list(node.if_body) + list(node.else_body)
|
|
658
|
+
for bloco in node.elseif_blocks:
|
|
659
|
+
corpo += list(bloco.get('body', []))
|
|
660
|
+
definir(corpo)
|
|
661
|
+
continue
|
|
662
|
+
if isinstance(node, LoopNode):
|
|
663
|
+
definir(node.body)
|
|
664
|
+
continue
|
|
665
|
+
nome = getattr(node, 'name', None)
|
|
666
|
+
if isinstance(nome, str) and nome and '.' not in nome:
|
|
667
|
+
definidos.update({nome, f'{nome}_result'})
|
|
668
|
+
|
|
669
|
+
definir([s for s in component.statements if not any(s is g for g in guardas)])
|
|
670
|
+
|
|
671
|
+
for guarda in guardas:
|
|
672
|
+
for condicao in guard_conditions(guarda):
|
|
673
|
+
lidos = names_read(re.sub(r'\{([^{}]*)\}', r'\1', condicao)) & definidos
|
|
674
|
+
if lidos:
|
|
675
|
+
nome = sorted(lidos)[0]
|
|
676
|
+
raise QuantumParseError(
|
|
677
|
+
f'<q:if condition="{condicao}"> in <q:component name="{component.name}"> '
|
|
678
|
+
f'redirects, so it also runs before the page\'s q:action — where the page '
|
|
679
|
+
f'has not run and {{{nome}}} does not exist, and the guard would let the '
|
|
680
|
+
f'action through. Read the scope directly (e.g. session.x), or protect the '
|
|
681
|
+
f'page with require_auth / require_role.')
|
|
682
|
+
|
|
632
683
|
# Nodes the HTML renderer draws. Anything else inside markup is a statement,
|
|
633
684
|
# and statements run in the execution pass, before the render pass sees
|
|
634
685
|
# the markup around them.
|
|
@@ -769,20 +820,13 @@ class QuantumParser:
|
|
|
769
820
|
|
|
770
821
|
def _attach_else_sibling(self, if_node: 'IfNode', kind: str, element: ET.Element):
|
|
771
822
|
"""Anexa um else/elseif IRMAO ao IfNode anterior — mesmo resultado da
|
|
772
|
-
forma aninhada que o IfParser ja monta."""
|
|
823
|
+
forma aninhada que o IfParser ja monta, texto incluido (IF-3)."""
|
|
824
|
+
corpo = self._parser_registry.get_parser('if')._body(element)
|
|
773
825
|
if kind == 'elseif':
|
|
774
|
-
|
|
775
|
-
corpo = []
|
|
776
|
-
for filho in element:
|
|
777
|
-
stmt = self._parse_statement(filho)
|
|
778
|
-
if stmt:
|
|
779
|
-
corpo.append(stmt)
|
|
780
|
-
if_node.add_elseif_block(condicao, corpo)
|
|
826
|
+
if_node.add_elseif_block(element.get('condition', ''), corpo)
|
|
781
827
|
else:
|
|
782
|
-
for
|
|
783
|
-
|
|
784
|
-
if stmt:
|
|
785
|
-
if_node.add_else_statement(stmt)
|
|
828
|
+
for stmt in corpo:
|
|
829
|
+
if_node.add_else_statement(stmt)
|
|
786
830
|
|
|
787
831
|
def _parse_statement(self, element: ET.Element) -> Optional[QuantumNode]:
|
|
788
832
|
"""Parse individual statement (return, set, dispatchEvent, etc)"""
|
|
@@ -791,6 +835,17 @@ class QuantumParser:
|
|
|
791
835
|
# === MODULAR PARSER REGISTRY (no fallback) ===
|
|
792
836
|
# PHASE 1 DIAGNOSTIC: Legacy fallback disabled to test modular coverage
|
|
793
837
|
if self._use_modular_parsers and self._parser_registry:
|
|
838
|
+
# PARSE-1: <q:html>, <q:div> — an HTML element name in the q:
|
|
839
|
+
# namespace. The registry answered for it with the HTML parser, so
|
|
840
|
+
# <q:html value="{post.content}"/> became an empty <html> element
|
|
841
|
+
# instead of an unknown tag.
|
|
842
|
+
# q:script is a Quantum tag on purpose: client JavaScript, kept as
|
|
843
|
+
# an HTML node (see ui_desktop_adapter).
|
|
844
|
+
if (self._is_quantum_tag(element) and element_type != 'script'
|
|
845
|
+
and element_type in self._parser_registry.html_parser.tag_names):
|
|
846
|
+
raise QuantumParseError(
|
|
847
|
+
f"<q:{element_type}> is not a Quantum tag: HTML elements are written "
|
|
848
|
+
f"without the q: prefix (<{element_type}>)")
|
|
794
849
|
if self._parser_registry.has_parser(element_type):
|
|
795
850
|
return self._parser_registry.parse(element, element_type)
|
|
796
851
|
|
{quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/control_flow/if_parser.py
RENAMED
|
@@ -8,6 +8,7 @@ from typing import List
|
|
|
8
8
|
from xml.etree import ElementTree as ET
|
|
9
9
|
from quantum.core.parsers.base import BaseTagParser
|
|
10
10
|
from quantum.core.features.conditionals.src.ast_node import IfNode
|
|
11
|
+
from quantum.core.ast_nodes import TextNode
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class IfParser(BaseTagParser):
|
|
@@ -37,32 +38,34 @@ class IfParser(BaseTagParser):
|
|
|
37
38
|
condition = self.get_attr(element, 'condition', '')
|
|
38
39
|
if_node = IfNode(condition)
|
|
39
40
|
|
|
41
|
+
# IF-3: text directly in a branch is content, like in q:loop and in an
|
|
42
|
+
# HTML element. Only child elements were read, so
|
|
43
|
+
# <h2><q:if condition="tag">Posts tagged {tag}</q:if></h2> rendered
|
|
44
|
+
# an empty heading.
|
|
45
|
+
principal = self._body(element, skip=('elseif', 'else'))
|
|
46
|
+
for statement in principal:
|
|
47
|
+
if_node.add_if_statement(statement)
|
|
48
|
+
|
|
40
49
|
for child in element:
|
|
41
50
|
child_type = self.get_element_name(child)
|
|
42
|
-
|
|
43
51
|
if child_type == 'elseif':
|
|
44
|
-
|
|
45
|
-
elseif_condition = self.get_attr(child, 'condition', '')
|
|
46
|
-
elseif_body = []
|
|
47
|
-
|
|
48
|
-
for elseif_child in child:
|
|
49
|
-
statement = self.parse_statement(elseif_child)
|
|
50
|
-
if statement:
|
|
51
|
-
elseif_body.append(statement)
|
|
52
|
-
|
|
53
|
-
if_node.add_elseif_block(elseif_condition, elseif_body)
|
|
54
|
-
|
|
52
|
+
if_node.add_elseif_block(self.get_attr(child, 'condition', ''), self._body(child))
|
|
55
53
|
elif child_type == 'else':
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
statement = self.parse_statement(else_child)
|
|
59
|
-
if statement:
|
|
60
|
-
if_node.add_else_statement(statement)
|
|
54
|
+
for statement in self._body(child):
|
|
55
|
+
if_node.add_else_statement(statement)
|
|
61
56
|
|
|
62
|
-
|
|
63
|
-
|
|
57
|
+
return if_node
|
|
58
|
+
|
|
59
|
+
def _body(self, element: ET.Element, skip=()) -> list:
|
|
60
|
+
"""The statements and text of a branch, in document order."""
|
|
61
|
+
corpo = []
|
|
62
|
+
if element.text and element.text.strip():
|
|
63
|
+
corpo.append(TextNode(element.text))
|
|
64
|
+
for child in element:
|
|
65
|
+
if self.get_element_name(child) not in skip:
|
|
64
66
|
statement = self.parse_statement(child)
|
|
65
67
|
if statement:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
corpo.append(statement)
|
|
69
|
+
if child.tail and child.tail.strip():
|
|
70
|
+
corpo.append(TextNode(child.tail))
|
|
71
|
+
return corpo
|
{quantum_framework-0.12.0 → quantum_framework-0.13.0}/quantum/core/parsers/forms/action_parser.py
RENAMED
|
@@ -8,6 +8,14 @@ from quantum.core.parsers.base import BaseTagParser
|
|
|
8
8
|
from quantum.core.ast_nodes import ActionNode
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
_NEVER_ENFORCED = {
|
|
12
|
+
'require_auth': 'Protect the page with require_auth / require_role on the '
|
|
13
|
+
'q:component: that covers its actions too (AUTH-1, AUTH-6).',
|
|
14
|
+
'csrf': 'There is no CSRF token; the session cookie is SameSite=Lax (AUTH-5).',
|
|
15
|
+
'rate_limit': 'Nothing limits requests; put a reverse proxy in front for that.',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
11
19
|
class ActionParser(BaseTagParser):
|
|
12
20
|
"""
|
|
13
21
|
Parser for q:action statements.
|
|
@@ -30,13 +38,15 @@ class ActionParser(BaseTagParser):
|
|
|
30
38
|
method = element.get('method', 'POST')
|
|
31
39
|
action_node = ActionNode(name, method)
|
|
32
40
|
|
|
33
|
-
#
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
# AUTH-7: attributes that were parsed and never enforced. The worst
|
|
42
|
+
# is require_auth="true": the action ran for anyone, while the page
|
|
43
|
+
# read as protected. The docs promised csrf= and rate_limit= too.
|
|
44
|
+
for attr, porque in _NEVER_ENFORCED.items():
|
|
45
|
+
if element.get(attr) is not None:
|
|
46
|
+
from quantum.core.parser import QuantumParseError
|
|
47
|
+
raise QuantumParseError(
|
|
48
|
+
f'<q:action name="{name}"> {attr}= is not supported: it was accepted '
|
|
49
|
+
f'and never enforced. {porque}')
|
|
40
50
|
|
|
41
51
|
# Parse children (params, statements)
|
|
42
52
|
for child in element:
|
|
@@ -139,13 +139,19 @@ class ActionHandler:
|
|
|
139
139
|
# nothing — the failure existed only as a flash message the user
|
|
140
140
|
# might never see. Whoever is debugging gets the action name, the
|
|
141
141
|
# exception and the stack.
|
|
142
|
+
# ACT-9: the most common failure. The action runs without the
|
|
143
|
+
# page's statements, so a variable the page sets is not here.
|
|
144
|
+
mensagem = str(e)
|
|
145
|
+
if 'is not defined' in mensagem:
|
|
146
|
+
mensagem += (". A q:action does not run the page's statements: "
|
|
147
|
+
"query or compute what it needs inside the action")
|
|
142
148
|
logger.exception(
|
|
143
|
-
"q:action %r failed: %s", getattr(action, 'name', '?'),
|
|
149
|
+
"q:action %r failed: %s", getattr(action, 'name', '?'), mensagem
|
|
144
150
|
)
|
|
145
151
|
if owns_context:
|
|
146
152
|
session['quantum_session'] = context.session_vars
|
|
147
153
|
session.modified = True
|
|
148
|
-
self._set_flash_message(f'Action error: {
|
|
154
|
+
self._set_flash_message(f'Action error: {mensagem}', 'error')
|
|
149
155
|
return None, 500
|
|
150
156
|
|
|
151
157
|
def _extract_form_data(self) -> Dict[str, Any]:
|