quantum-framework 0.9.1__tar.gz → 0.11.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.9.1/quantum_framework.egg-info → quantum_framework-0.11.0}/PKG-INFO +2 -2
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/README.md +1 -1
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/pyproject.toml +1 -1
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/cli/commands/migrate.py +62 -81
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/cli/runner.py +9 -35
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/ast_nodes.py +1 -0
- quantum_framework-0.11.0/quantum/core/config_env.py +54 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/expression_stdlib.py +34 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/expressions.py +52 -31
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/data_import/src/ast_node.py +1 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/data_import/src/runtime.py +13 -18
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/invocation/src/ast_node.py +1 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/invocation/src/runtime.py +8 -3
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parser.py +78 -1
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/data/data_parser.py +14 -2
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/data/invoke_parser.py +9 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/data/query_parser.py +11 -6
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/data/transaction_parser.py +9 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/functions/function_parser.py +22 -68
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/tiers.py +63 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/__init__.py +0 -3
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/action_handler.py +9 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/component.py +134 -84
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/component_composer.py +2 -1
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/control_flow/set_executor.py +91 -39
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/data/data_executor.py +12 -1
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/data/invoke_executor.py +11 -4
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/knowledge_service.py +78 -32
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/param_validation.py +8 -6
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/renderer.py +13 -5
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/service_container.py +34 -8
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/web_server.py +35 -12
- {quantum_framework-0.9.1 → quantum_framework-0.11.0/quantum_framework.egg-info}/PKG-INFO +2 -2
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum_framework.egg-info/SOURCES.txt +1 -7
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_game_engine.py +2 -2
- quantum_framework-0.9.1/quantum/core/features/data_fetching/src/__init__.py +0 -21
- quantum_framework-0.9.1/quantum/core/features/data_fetching/src/ast_node.py +0 -312
- quantum_framework-0.9.1/quantum/core/features/data_fetching/src/desktop_adapter.py +0 -351
- quantum_framework-0.9.1/quantum/core/features/data_fetching/src/html_adapter.py +0 -474
- quantum_framework-0.9.1/quantum/core/features/data_fetching/src/parser.py +0 -225
- quantum_framework-0.9.1/quantum/runtime/api_server.py +0 -86
- quantum_framework-0.9.1/tests/test_data_fetching.py +0 -593
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/LICENSE +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/cli/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/cli/commands/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/cli/commands/build.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/cli/commands/dev.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/cli/commands/docs.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/cli/commands/lint.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/cli/commands/new.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/cli/commands/serve.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/cli/commands/test.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/cli/deploy.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/cli/hot_reload.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/cli/jobs.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/cli/mq.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/cli/pkg.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/cli/server_process.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/cli/utils.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/compiler/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/compiler/base_generator.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/compiler/cli.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/compiler/expression_transformer.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/compiler/javascript/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/compiler/javascript/generator.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/compiler/optimizer.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/compiler/python/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/compiler/python/generator.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/compiler/python/runtime.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/compiler/transpiler.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/expression_diagnostics.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/agents/src/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/agents/src/ast_node.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/conditionals/src/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/conditionals/src/ast_node.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/data_import/src/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/dump/src/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/dump/src/ast_node.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/dump/src/parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/dump/src/runtime.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/functions/src/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/functions/src/ast_node.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/game_engine_2d/src/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/game_engine_2d/src/ast_nodes.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/game_engine_2d/src/parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/invocation/src/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/knowledge_base/src/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/knowledge_base/src/ast_node.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/knowledge_base/src/parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/logging/src/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/logging/src/ast_node.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/logging/src/parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/logging/src/runtime.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/loops/src/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/loops/src/ast_node.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/query/src/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/query/src/database_service.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/query/src/query_validators.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/state_management/src/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/state_management/src/ast_node.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/terminal_engine/src/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/terminal_engine/src/ast_nodes.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/terminal_engine/src/parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/testing_engine/src/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/testing_engine/src/ast_nodes.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/testing_engine/src/parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/theming/src/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/theming/src/ast_node.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/theming/src/presets.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/ui_engine/src/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/ui_engine/src/ast_nodes.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/ui_engine/src/parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/websocket/src/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/features/websocket/src/ast_node.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/html_compat.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parser_registry.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/ai/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/ai/agent_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/ai/knowledge_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/ai/llm_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/ai/team_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/base.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/composition/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/composition/import_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/composition/slot_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/control_flow/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/control_flow/if_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/control_flow/loop_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/control_flow/set_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/data/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/events/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/events/dispatch_event_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/forms/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/forms/action_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/forms/flash_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/forms/redirect_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/functions/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/functions/param_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/functions/return_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/html/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/html/component_call_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/html/html_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/jobs/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/jobs/job_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/jobs/schedule_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/jobs/thread_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/messaging/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/messaging/message_ack_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/messaging/message_nack_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/messaging/message_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/messaging/queue_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/messaging/websocket_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/persistence/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/persistence/persist_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/routing/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/routing/route_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/scripting/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/scripting/pyclass_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/scripting/pydecorator_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/scripting/pyimport_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/scripting/python_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/services/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/services/dump_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/services/file_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/services/log_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/core/parsers/services/mail_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/packages/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/packages/manager.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/packages/manifest.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/packages/registry.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/packages/resolver.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/plugins/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/plugins/hooks.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/plugins/loader.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/plugins/manifest.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/plugins/registry.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/adapters/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/adapters/memory_adapter.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/adapters/rabbitmq_adapter.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/adapters/redis_adapter.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/adapters/sqlite_adapter.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/agent_service.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/ast_cache.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/auth_service.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/component_resolver.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/database_service.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/email_service.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/error_handler.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/execution_context.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executor_registry.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/ai/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/ai/agent_executor.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/ai/knowledge_executor.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/ai/llm_executor.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/ai/team_executor.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/base.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/control_flow/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/control_flow/if_executor.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/control_flow/loop_executor.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/data/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/data/query_executor.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/data/transaction_executor.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/jobs/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/jobs/job_executor.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/jobs/schedule_executor.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/jobs/thread_executor.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/messaging/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/messaging/message_ack_executor.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/messaging/message_executor.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/messaging/queue_executor.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/messaging/websocket_executor.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/scripting/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/scripting/pyclass_executor.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/scripting/pyimport_executor.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/scripting/python_executor.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/services/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/services/dump_executor.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/services/file_executor.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/services/log_executor.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/executors/services/mail_executor.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/expression_cache.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/file_upload_service.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/function_registry.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/game_builder.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/game_code_generator.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/game_templates.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/godot_code_generator.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/godot_templates.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/job_executor.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/llm_cache.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/llm_providers.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/llm_service.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/logging_setup.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/message_broker.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/message_queue_service.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/pypy_compat.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/python_bridge.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/query_validators.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/terminal_builder.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/terminal_code_generator.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/terminal_templates.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/testing_builder.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/testing_code_generator.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/testing_templates.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/ui_builder.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/ui_desktop_adapter.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/ui_desktop_templates.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/ui_html_adapter.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/ui_html_templates.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/ui_mobile_adapter.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/ui_mobile_templates.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/ui_textual_adapter.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/ui_textual_templates.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/ui_tokens.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/ui_validator.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/validators.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/websocket_adapter.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/websocket_service.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/websocket_transport.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/runtime/wsgi.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum/utils/__init__.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum_framework.egg-info/dependency_links.txt +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum_framework.egg-info/entry_points.txt +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum_framework.egg-info/requires.txt +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/quantum_framework.egg-info/top_level.txt +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/setup.cfg +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_agent.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_animations.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_ast_cache.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_cli_commands.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_component_library.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_desktop_bridge.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_expression_cache.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_form_validation.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_game_codegen.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_game_e2e.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_game_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_godot_codegen.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_job_executor.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_llm_providers.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_message_broker.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_message_queue.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_mobile_target.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_multi_agent.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_packages.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_plugins.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_projects_blog_components_admin.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_pypy_compat.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_python_scripting.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_state_persistence.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_terminal_codegen.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_terminal_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_testing_codegen.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_testing_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_theming.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_ui_html_adapter.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_ui_parity.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_ui_parser.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_ui_textual_adapter.py +0 -0
- {quantum_framework-0.9.1 → quantum_framework-0.11.0}/tests/test_websocket.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quantum-framework
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.0
|
|
4
4
|
Summary: Full-stack declarative framework for building web, desktop, and mobile applications
|
|
5
5
|
Author-email: Daniel Gregorio <danielgregorio2611@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -216,7 +216,7 @@ Full docs at **[danielgregorio.github.io/quantum](https://danielgregorio.github.
|
|
|
216
216
|
|
|
217
217
|
- [Getting Started](https://danielgregorio.github.io/quantum/guide/getting-started) · [Installation](https://danielgregorio.github.io/quantum/guide/installation) · [Quick Start](https://danielgregorio.github.io/quantum/guide/quick-start)
|
|
218
218
|
- [Components](https://danielgregorio.github.io/quantum/guide/components) · [State](https://danielgregorio.github.io/quantum/guide/state-management) · [Loops](https://danielgregorio.github.io/quantum/guide/loops) · [Conditionals](https://danielgregorio.github.io/quantum/guide/conditionals)
|
|
219
|
-
- [Queries](https://danielgregorio.github.io/quantum/guide/query) · [Functions](https://danielgregorio.github.io/quantum/guide/functions)
|
|
219
|
+
- [Queries](https://danielgregorio.github.io/quantum/guide/query) · [Functions](https://danielgregorio.github.io/quantum/guide/functions)
|
|
220
220
|
|
|
221
221
|
Releases and their notes are on the [GitHub Releases](https://github.com/danielgregorio/quantum/releases) page.
|
|
222
222
|
|
|
@@ -173,7 +173,7 @@ Full docs at **[danielgregorio.github.io/quantum](https://danielgregorio.github.
|
|
|
173
173
|
|
|
174
174
|
- [Getting Started](https://danielgregorio.github.io/quantum/guide/getting-started) · [Installation](https://danielgregorio.github.io/quantum/guide/installation) · [Quick Start](https://danielgregorio.github.io/quantum/guide/quick-start)
|
|
175
175
|
- [Components](https://danielgregorio.github.io/quantum/guide/components) · [State](https://danielgregorio.github.io/quantum/guide/state-management) · [Loops](https://danielgregorio.github.io/quantum/guide/loops) · [Conditionals](https://danielgregorio.github.io/quantum/guide/conditionals)
|
|
176
|
-
- [Queries](https://danielgregorio.github.io/quantum/guide/query) · [Functions](https://danielgregorio.github.io/quantum/guide/functions)
|
|
176
|
+
- [Queries](https://danielgregorio.github.io/quantum/guide/query) · [Functions](https://danielgregorio.github.io/quantum/guide/functions)
|
|
177
177
|
|
|
178
178
|
Releases and their notes are on the [GitHub Releases](https://github.com/danielgregorio/quantum/releases) page.
|
|
179
179
|
|
|
@@ -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.11.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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Quantum CLI - Database Migration Commands
|
|
3
|
-
|
|
3
|
+
Applies migrations to a datasource declared in quantum.config.yaml (DB-6)
|
|
4
4
|
"""
|
|
5
5
|
import os
|
|
6
6
|
import sys
|
|
@@ -44,95 +44,76 @@ class Migration:
|
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
class DatabaseConnection:
|
|
47
|
-
"""
|
|
47
|
+
"""Connection to the datasource the migrations are for (DB-6).
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
It used to read a separate `database:` section that nothing else uses and,
|
|
50
|
+
without one, try a PostgreSQL at localhost/quantum with user postgres before
|
|
51
|
+
falling back to ./data/quantum.db. A project whose pages query
|
|
52
|
+
datasources.db (./data/app.db) had its migrations applied to a different
|
|
53
|
+
file — or to whatever local PostgreSQL answered.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(self, project_path: Path = None, datasource: str = None):
|
|
50
57
|
self.project_path = project_path or Path.cwd()
|
|
58
|
+
self.datasource = datasource
|
|
51
59
|
self._conn = None
|
|
52
60
|
self._db_type = None
|
|
53
61
|
|
|
54
62
|
def connect(self) -> Any:
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
nome, config = self._datasource_config()
|
|
64
|
+
driver = str(config.get('driver', '')).lower()
|
|
65
|
+
if driver == 'sqlite':
|
|
66
|
+
import sqlite3
|
|
67
|
+
caminho = Path(config.get('database') or '')
|
|
68
|
+
if not config.get('database'):
|
|
69
|
+
raise MigrationError(f"datasource '{nome}' (sqlite) has no database: path")
|
|
70
|
+
if not caminho.is_absolute():
|
|
71
|
+
caminho = self.project_path / caminho
|
|
72
|
+
caminho.parent.mkdir(parents=True, exist_ok=True)
|
|
73
|
+
self._conn = sqlite3.connect(str(caminho))
|
|
74
|
+
self._db_type = 'sqlite'
|
|
75
|
+
logger.info("Migrating datasource %s (SQLite at %s)", nome, caminho)
|
|
64
76
|
return self._conn
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
77
|
+
if driver in ('postgres', 'postgresql'):
|
|
78
|
+
try:
|
|
79
|
+
import psycopg2
|
|
80
|
+
except ImportError as exc:
|
|
81
|
+
raise MigrationError("datasource '%s' is PostgreSQL: pip install psycopg2-binary" % nome) from exc
|
|
82
|
+
self._conn = psycopg2.connect(
|
|
83
|
+
host=config.get('host', 'localhost'), port=int(config.get('port', 5432)),
|
|
84
|
+
dbname=config.get('database'), user=config.get('username') or config.get('user'),
|
|
85
|
+
password=config.get('password', ''), connect_timeout=5)
|
|
86
|
+
self._db_type = 'postgres'
|
|
87
|
+
logger.info("Migrating datasource %s (PostgreSQL %s)", nome, config.get('database'))
|
|
69
88
|
return self._conn
|
|
89
|
+
raise MigrationError(f"datasource '{nome}': driver {driver!r} is not supported by quantum migrate "
|
|
90
|
+
f"(use sqlite or postgres)")
|
|
70
91
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def _load_config(self) -> Dict:
|
|
74
|
-
"""Load database configuration from quantum.config.yaml"""
|
|
92
|
+
def _datasource_config(self):
|
|
75
93
|
config_path = self.project_path / "quantum.config.yaml"
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
try:
|
|
94
|
+
datasources = {}
|
|
95
|
+
if config_path.exists():
|
|
80
96
|
import yaml
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
self.
|
|
101
|
-
host=host,
|
|
102
|
-
port=int(port),
|
|
103
|
-
database=database,
|
|
104
|
-
user=user,
|
|
105
|
-
password=password,
|
|
106
|
-
connect_timeout=5
|
|
107
|
-
)
|
|
108
|
-
self._db_type = 'postgres'
|
|
109
|
-
logger.info(f"Connected to PostgreSQL at {host}:{port}/{database}")
|
|
110
|
-
return True
|
|
111
|
-
|
|
112
|
-
except ImportError:
|
|
113
|
-
logger.debug("psycopg2 not installed")
|
|
114
|
-
return False
|
|
115
|
-
except Exception as e:
|
|
116
|
-
logger.debug(f"PostgreSQL connection failed: {e}")
|
|
117
|
-
return False
|
|
118
|
-
|
|
119
|
-
def _try_sqlite(self, config: Dict) -> bool:
|
|
120
|
-
"""Try to connect to SQLite"""
|
|
121
|
-
try:
|
|
122
|
-
import sqlite3
|
|
123
|
-
|
|
124
|
-
db_path = config.get('sqlite_path') or self.project_path / "data" / "quantum.db"
|
|
125
|
-
db_path = Path(db_path)
|
|
126
|
-
db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
127
|
-
|
|
128
|
-
self._conn = sqlite3.connect(str(db_path))
|
|
129
|
-
self._db_type = 'sqlite'
|
|
130
|
-
logger.info(f"Connected to SQLite at {db_path}")
|
|
131
|
-
return True
|
|
132
|
-
|
|
133
|
-
except Exception as e:
|
|
134
|
-
logger.error(f"SQLite connection failed: {e}")
|
|
135
|
-
return False
|
|
97
|
+
from quantum.core.config_env import expand_env
|
|
98
|
+
with open(config_path, encoding='utf-8') as f:
|
|
99
|
+
datasources = (expand_env(yaml.safe_load(f) or {}).get('datasources') or {})
|
|
100
|
+
if not datasources:
|
|
101
|
+
raise MigrationError(
|
|
102
|
+
"quantum.config.yaml declares no datasources. Add one, for example:\n"
|
|
103
|
+
" datasources:\n db:\n driver: sqlite\n database: ./data/app.db")
|
|
104
|
+
if self.datasource:
|
|
105
|
+
if self.datasource not in datasources:
|
|
106
|
+
raise MigrationError(f"no datasource '{self.datasource}'; declared: {', '.join(datasources)}")
|
|
107
|
+
return self.datasource, datasources[self.datasource]
|
|
108
|
+
if len(datasources) > 1:
|
|
109
|
+
raise MigrationError(f"several datasources are declared ({', '.join(datasources)}); "
|
|
110
|
+
f"choose one with --datasource")
|
|
111
|
+
nome = next(iter(datasources))
|
|
112
|
+
return nome, datasources[nome]
|
|
113
|
+
|
|
114
|
+
def _ensure_connected(self):
|
|
115
|
+
if self._conn is None:
|
|
116
|
+
self.connect()
|
|
136
117
|
|
|
137
118
|
@property
|
|
138
119
|
def db_type(self) -> str:
|
|
@@ -163,10 +144,10 @@ class MigrationRunner:
|
|
|
163
144
|
|
|
164
145
|
MIGRATIONS_TABLE = "_migrations"
|
|
165
146
|
|
|
166
|
-
def __init__(self, project_path: Path = None):
|
|
147
|
+
def __init__(self, project_path: Path = None, datasource: str = None):
|
|
167
148
|
self.project_path = project_path or Path.cwd()
|
|
168
149
|
self.migrations_dir = self.project_path / "migrations"
|
|
169
|
-
self.db = DatabaseConnection(self.project_path)
|
|
150
|
+
self.db = DatabaseConnection(self.project_path, datasource)
|
|
170
151
|
|
|
171
152
|
def _ensure_migrations_table(self):
|
|
172
153
|
"""Create migrations tracking table if it doesn't exist"""
|
|
@@ -25,7 +25,6 @@ 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
27
|
from quantum.runtime.web_server import QuantumWebServer
|
|
28
|
-
from quantum.runtime.api_server import QuantumAPIServer
|
|
29
28
|
|
|
30
29
|
# Import deploy commands (lazy load to avoid import errors if requests not installed)
|
|
31
30
|
def _get_deploy_module():
|
|
@@ -77,12 +76,15 @@ def load_config(config_path: str = 'quantum.config.yaml') -> dict:
|
|
|
77
76
|
so `datasources:` declared in quantum.config.yaml were never seen and
|
|
78
77
|
every q:query fell through to the optional Quantum Admin API.
|
|
79
78
|
"""
|
|
79
|
+
from quantum.core.config_env import ConfigEnvError, expand_env
|
|
80
80
|
try:
|
|
81
81
|
import yaml
|
|
82
82
|
path = Path(config_path)
|
|
83
83
|
if path.exists():
|
|
84
84
|
with open(path, 'r', encoding='utf-8') as fh:
|
|
85
|
-
return yaml.safe_load(fh) or {}
|
|
85
|
+
return expand_env(yaml.safe_load(fh) or {})
|
|
86
|
+
except ConfigEnvError:
|
|
87
|
+
raise # CFG-1: never run with a half-read config
|
|
86
88
|
except Exception as exc:
|
|
87
89
|
print(f"[WARN] Could not read {config_path}: {exc}")
|
|
88
90
|
return {}
|
|
@@ -175,6 +177,9 @@ class QuantumRunner:
|
|
|
175
177
|
for route in app.routes:
|
|
176
178
|
print(f" {route.method} {route.path}")
|
|
177
179
|
|
|
180
|
+
from quantum.core.tiers import warn_app_type
|
|
181
|
+
warn_app_type(app.app_type)
|
|
182
|
+
|
|
178
183
|
if app.app_type == 'game':
|
|
179
184
|
return self._build_game(app, debug)
|
|
180
185
|
elif app.app_type == 'terminal':
|
|
@@ -184,10 +189,6 @@ class QuantumRunner:
|
|
|
184
189
|
elif app.app_type == 'ui':
|
|
185
190
|
target = getattr(self, '_ui_target', 'html')
|
|
186
191
|
return self._build_ui(app, target, debug)
|
|
187
|
-
elif app.app_type == 'html':
|
|
188
|
-
return self._start_web_server(app)
|
|
189
|
-
elif app.app_type in ('microservices', 'api'):
|
|
190
|
-
return self._start_api_server(app)
|
|
191
192
|
else:
|
|
192
193
|
print(f"[ERROR] Application type '{app.app_type}' not supported")
|
|
193
194
|
return 1
|
|
@@ -268,32 +269,6 @@ class QuantumRunner:
|
|
|
268
269
|
# TODO: Implement real job execution
|
|
269
270
|
print("[SUCCESS] Job executed successfully")
|
|
270
271
|
return 0
|
|
271
|
-
|
|
272
|
-
def _start_web_server(self, app: ApplicationNode) -> int:
|
|
273
|
-
"""Start web server"""
|
|
274
|
-
try:
|
|
275
|
-
server = QuantumWebServer(port=8080)
|
|
276
|
-
server.configure_from_ast(app)
|
|
277
|
-
# Propagate the server's exit code: it returns non-zero when the
|
|
278
|
-
# port is taken or the bind fails, and swallowing that made
|
|
279
|
-
# `quantum run` report success with nothing listening.
|
|
280
|
-
return server.start() or 0
|
|
281
|
-
except Exception as e:
|
|
282
|
-
print(f"[ERROR] Web server error: {e}")
|
|
283
|
-
return 1
|
|
284
|
-
|
|
285
|
-
def _start_api_server(self, app: ApplicationNode) -> int:
|
|
286
|
-
"""Start API server"""
|
|
287
|
-
try:
|
|
288
|
-
server = QuantumAPIServer(port=8080)
|
|
289
|
-
server.configure_from_ast(app)
|
|
290
|
-
# Propagate the server's exit code: it returns non-zero when the
|
|
291
|
-
# port is taken or the bind fails, and swallowing that made
|
|
292
|
-
# `quantum run` report success with nothing listening.
|
|
293
|
-
return server.start() or 0
|
|
294
|
-
except Exception as e:
|
|
295
|
-
print(f"[ERROR] API server error: {e}")
|
|
296
|
-
return 1
|
|
297
272
|
|
|
298
273
|
|
|
299
274
|
def main():
|
|
@@ -305,8 +280,6 @@ def main():
|
|
|
305
280
|
Examples:
|
|
306
281
|
quantum start # Start web server (magic!)
|
|
307
282
|
quantum run hello.q # Execute component
|
|
308
|
-
quantum run webapp.q # Start web server from .q file
|
|
309
|
-
quantum run api.q # Start API server
|
|
310
283
|
quantum run game.q --engine godot # Build Godot 4 project
|
|
311
284
|
quantum run backup-job.q # Execute job
|
|
312
285
|
quantum deploy ./my-app # Deploy application
|
|
@@ -384,6 +357,7 @@ Examples:
|
|
|
384
357
|
|
|
385
358
|
# Migration commands
|
|
386
359
|
migrate_parser = subparsers.add_parser('migrate', help='Database migration commands')
|
|
360
|
+
migrate_parser.add_argument('--datasource', help='Datasource from quantum.config.yaml (default: the only one declared)')
|
|
387
361
|
migrate_subparsers = migrate_parser.add_subparsers(dest='migrate_command')
|
|
388
362
|
|
|
389
363
|
migrate_subparsers.add_parser('status', help='Show migration status')
|
|
@@ -488,7 +462,7 @@ Examples:
|
|
|
488
462
|
MigrationRunner = result
|
|
489
463
|
|
|
490
464
|
try:
|
|
491
|
-
runner = MigrationRunner()
|
|
465
|
+
runner = MigrationRunner(datasource=getattr(args, 'datasource', None))
|
|
492
466
|
|
|
493
467
|
if args.migrate_command == 'status':
|
|
494
468
|
import json
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
`${VAR}` in quantum.config.yaml (CFG-1).
|
|
3
|
+
|
|
4
|
+
datasources:
|
|
5
|
+
db:
|
|
6
|
+
password: ${DB_PASSWORD}
|
|
7
|
+
host: ${DB_HOST:-localhost}
|
|
8
|
+
port: ${DB_PORT:5432} # ':' works as well as ':-'
|
|
9
|
+
|
|
10
|
+
A config file is the place people put secrets by reference, and every loader
|
|
11
|
+
used to read `${DB_PASSWORD}` as the literal password. There were four loaders
|
|
12
|
+
(the CLI, the web server, the service container, migrations); they all go
|
|
13
|
+
through expand_env now.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import re
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
_REFERENCE = re.compile(r'\$\$|\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-?([^}]*))?\}')
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ConfigEnvError(ValueError):
|
|
24
|
+
"""A `${VAR}` with no value and no default."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def expand_env(value: Any, path: str = '') -> Any:
|
|
28
|
+
"""Replace `${NAME}` and `${NAME:-default}` in every string of a loaded config.
|
|
29
|
+
|
|
30
|
+
`$$` is a literal `$`. A variable that is not set and has no default is an
|
|
31
|
+
error naming the variable and where it was used — an empty password or
|
|
32
|
+
database path would otherwise fail much later, somewhere else.
|
|
33
|
+
"""
|
|
34
|
+
if isinstance(value, dict):
|
|
35
|
+
return {k: expand_env(v, f'{path}.{k}' if path else str(k)) for k, v in value.items()}
|
|
36
|
+
if isinstance(value, list):
|
|
37
|
+
return [expand_env(v, f'{path}[{i}]') for i, v in enumerate(value)]
|
|
38
|
+
if not isinstance(value, str) or '$' not in value:
|
|
39
|
+
return value
|
|
40
|
+
|
|
41
|
+
def substitute(match):
|
|
42
|
+
if match.group(0) == '$$':
|
|
43
|
+
return '$'
|
|
44
|
+
name, default = match.group(1), match.group(2)
|
|
45
|
+
if name in os.environ:
|
|
46
|
+
return os.environ[name]
|
|
47
|
+
if default is not None:
|
|
48
|
+
return default
|
|
49
|
+
raise ConfigEnvError(
|
|
50
|
+
f"quantum.config.yaml: {path or 'value'} uses ${{{name}}}, but the "
|
|
51
|
+
f"environment variable {name} is not set. Set it, or give a default: "
|
|
52
|
+
f"${{{name}:-value}}")
|
|
53
|
+
|
|
54
|
+
return _REFERENCE.sub(substitute, value)
|
|
@@ -118,7 +118,41 @@ def _round(v: Any, digits: int = 0) -> Any:
|
|
|
118
118
|
return int(result) if digits == 0 else result
|
|
119
119
|
|
|
120
120
|
|
|
121
|
+
# -- passwords ---------------------------------------------------------------
|
|
122
|
+
#
|
|
123
|
+
# Authentication is Core (decision D4), and a login is "look the user up, then
|
|
124
|
+
# check the password". Without these, a .q could not check a password against
|
|
125
|
+
# a stored hash without q:python — so every login example just set
|
|
126
|
+
# session.authenticated=true for whoever submitted the form (gap AUTH-1).
|
|
127
|
+
|
|
128
|
+
def hash_password(password: Any) -> str:
|
|
129
|
+
"""bcrypt hash of a password, for storing. Never store the password itself."""
|
|
130
|
+
import bcrypt
|
|
131
|
+
if password is None or password == '':
|
|
132
|
+
raise ValueError("hashPassword: the password is empty")
|
|
133
|
+
return bcrypt.hashpw(str(password).encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def verify_password(password: Any, hashed: Any) -> bool:
|
|
137
|
+
"""True when `password` matches a hash made by hashPassword.
|
|
138
|
+
|
|
139
|
+
Fail-closed: an empty password, a missing hash or a malformed hash is
|
|
140
|
+
False, never an error — so `q:if condition="verifyPassword(senha, h)"`
|
|
141
|
+
cannot be tricked into the true branch by bad data.
|
|
142
|
+
"""
|
|
143
|
+
import bcrypt
|
|
144
|
+
if not password or not hashed or not isinstance(hashed, str):
|
|
145
|
+
return False
|
|
146
|
+
try:
|
|
147
|
+
return bcrypt.checkpw(str(password).encode('utf-8'), hashed.encode('utf-8'))
|
|
148
|
+
except (ValueError, TypeError):
|
|
149
|
+
return False
|
|
150
|
+
|
|
151
|
+
|
|
121
152
|
STDLIB = {
|
|
153
|
+
# passwords
|
|
154
|
+
'hashPassword': hash_password,
|
|
155
|
+
'verifyPassword': verify_password,
|
|
122
156
|
# dates
|
|
123
157
|
'now': now,
|
|
124
158
|
'dateAdd': date_add,
|
|
@@ -35,6 +35,15 @@ class ExpressionError(Exception):
|
|
|
35
35
|
"""Raised when an expression cannot be evaluated."""
|
|
36
36
|
|
|
37
37
|
|
|
38
|
+
class UndefinedError(ExpressionError):
|
|
39
|
+
"""A name, key or attribute that does not exist.
|
|
40
|
+
|
|
41
|
+
Kept apart from the other failures because a condition is a presence test
|
|
42
|
+
(EXPR-5): `<q:if condition="flash">` is false before there is a flash, but
|
|
43
|
+
`a === b` or a misspelled function is still an error.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
|
|
38
47
|
# `{n}`, `{n,}` and `{n,m}` are regex quantifiers, not Quantum expressions.
|
|
39
48
|
# They occur for real: examples/form_validation.q has pattern="\d{10,11}" and
|
|
40
49
|
# examples/python-scripting.q has r'[a-zA-Z]{2,}'. The old evaluator left them
|
|
@@ -51,9 +60,9 @@ _REGEX_QUANTIFIER = re.compile(r'^\d+\s*(,\s*\d*)?$')
|
|
|
51
60
|
def is_regex_quantifier(content: str) -> bool:
|
|
52
61
|
"""True when a `{...}` group should be left as literal text.
|
|
53
62
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
63
|
+
A q: attribute that is ONLY `{5}` is the number 5 (EXPR-7) — the runtime
|
|
64
|
+
checks that case before calling this. Inside other text, `[0-9]{3}` stays a
|
|
65
|
+
quantifier.
|
|
57
66
|
"""
|
|
58
67
|
return bool(_REGEX_QUANTIFIER.match(content.strip()))
|
|
59
68
|
|
|
@@ -148,14 +157,12 @@ def _suggest(wanted: str, target: Any, limit: int = 3) -> str:
|
|
|
148
157
|
#
|
|
149
158
|
# {9**9**9} -> does not return
|
|
150
159
|
# {10**100000000} -> does not return
|
|
151
|
-
# {'a' * 999999999}
|
|
160
|
+
# ({'a' * 999999999} allocated ~1 GB; since EXPR-7 `*` needs numbers.)
|
|
152
161
|
#
|
|
153
162
|
# No dunder, no escape, no attacker — a typo in a template is enough, and there
|
|
154
163
|
# is no timeout anywhere above this to catch it. The caps are far above any
|
|
155
|
-
# real expression: 4096 bits is a 1233-digit number
|
|
156
|
-
# string is already absurd for a page.
|
|
164
|
+
# real expression: 4096 bits is a 1233-digit number.
|
|
157
165
|
_MAX_POW_BITS = 4096
|
|
158
|
-
_MAX_REPEAT = 10_000_000
|
|
159
166
|
|
|
160
167
|
|
|
161
168
|
def _guard_pow(left: Any, right: Any) -> None:
|
|
@@ -193,31 +200,24 @@ def _guard_pow(left: Any, right: Any) -> None:
|
|
|
193
200
|
)
|
|
194
201
|
|
|
195
202
|
|
|
196
|
-
def _guard_repeat(left: Any, right: Any) -> None:
|
|
197
|
-
for seq, count in ((left, right), (right, left)):
|
|
198
|
-
if isinstance(seq, (str, bytes, list, tuple)) and isinstance(count, int) \
|
|
199
|
-
and not isinstance(count, bool):
|
|
200
|
-
if len(seq) * max(count, 0) > _MAX_REPEAT:
|
|
201
|
-
raise ExpressionError(
|
|
202
|
-
f"repeating a {len(seq)}-item sequence {count} times "
|
|
203
|
-
f"exceeds {_MAX_REPEAT}; this would exhaust memory"
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
|
|
207
203
|
def _both_numeric(left: Any, right: Any) -> bool:
|
|
208
204
|
return isinstance(left, (int, float)) and isinstance(right, (int, float)) \
|
|
209
205
|
and not isinstance(left, bool) and not isinstance(right, bool)
|
|
210
206
|
|
|
211
207
|
|
|
212
208
|
|
|
213
|
-
def
|
|
214
|
-
"""Rewrite JavaScript-style `!x`
|
|
209
|
+
def _normalise_js_operators(source: str) -> str:
|
|
210
|
+
"""Rewrite JavaScript-style `!x`, `a && b` and `a || b` into Python's
|
|
211
|
+
`not`, `and` and `or`.
|
|
215
212
|
|
|
216
|
-
Real .q files use
|
|
217
|
-
|
|
218
|
-
|
|
213
|
+
Real .q files use them ({!result.success} appears across the examples, and
|
|
214
|
+
docs/guide/conditionals.md teaches `age >= 18 && hasLicense`), so they are
|
|
215
|
+
part of the language surface. `&&` and `||` were never translated: the
|
|
216
|
+
condition failed to parse and, read as false, took the else branch for
|
|
217
|
+
every input. `!=` is left alone, and nothing inside a string literal is
|
|
218
|
+
touched.
|
|
219
219
|
"""
|
|
220
|
-
if '!' not in source:
|
|
220
|
+
if '!' not in source and '&&' not in source and '||' not in source:
|
|
221
221
|
return source
|
|
222
222
|
|
|
223
223
|
out = []
|
|
@@ -240,6 +240,10 @@ def _normalise_not(source: str) -> str:
|
|
|
240
240
|
out.append('not ')
|
|
241
241
|
i += 1
|
|
242
242
|
continue
|
|
243
|
+
if source[i:i + 2] in ('&&', '||'):
|
|
244
|
+
out.append(' and ' if ch == '&' else ' or ')
|
|
245
|
+
i += 2
|
|
246
|
+
continue
|
|
243
247
|
out.append(ch)
|
|
244
248
|
i += 1
|
|
245
249
|
return ''.join(out)
|
|
@@ -281,7 +285,7 @@ class ExpressionEvaluator:
|
|
|
281
285
|
if len(source) > self.max_length:
|
|
282
286
|
raise ExpressionError(f"expression exceeds {self.max_length} characters")
|
|
283
287
|
|
|
284
|
-
source =
|
|
288
|
+
source = _normalise_js_operators(source)
|
|
285
289
|
|
|
286
290
|
try:
|
|
287
291
|
tree = ast.parse(source, mode='eval')
|
|
@@ -374,12 +378,27 @@ class ExpressionEvaluator:
|
|
|
374
378
|
cl, cr = coerce_number(left), coerce_number(right)
|
|
375
379
|
if _both_numeric(cl, cr):
|
|
376
380
|
left, right = cl, cr
|
|
381
|
+
elif isinstance(node.op, ast.Add) and not (
|
|
382
|
+
(isinstance(left, str) and isinstance(right, str))
|
|
383
|
+
or (isinstance(left, list) and isinstance(right, list))):
|
|
384
|
+
# EXPR-7: '+' adds numbers or joins two texts / two lists. The
|
|
385
|
+
# mixed case used to surface Python's "can only concatenate
|
|
386
|
+
# str (not "int") to str".
|
|
387
|
+
raise ExpressionError(
|
|
388
|
+
f"'+' needs two numbers, two texts or two lists, got {left!r} and {right!r}")
|
|
389
|
+
elif not isinstance(node.op, ast.Add):
|
|
390
|
+
# EXPR-7: arithmetic is on numbers. Python would repeat text
|
|
391
|
+
# ('ab' * 3), format it ('%s' % x) or fail with its own
|
|
392
|
+
# message; `n * factorial(n - 1)` whose base case returned the
|
|
393
|
+
# text '{1}' produced '{1}{1}{1}{1}{1}' and no error.
|
|
394
|
+
simbolo = {ast.Sub: '-', ast.Mult: '*', ast.Div: '/', ast.FloorDiv: '//',
|
|
395
|
+
ast.Mod: '%', ast.Pow: '**'}[type(node.op)]
|
|
396
|
+
raise ExpressionError(
|
|
397
|
+
f"'{simbolo}' needs two numbers, got {left!r} and {right!r}")
|
|
377
398
|
|
|
378
399
|
# Refuse what would not finish, before attempting it.
|
|
379
400
|
if isinstance(node.op, ast.Pow):
|
|
380
401
|
_guard_pow(left, right)
|
|
381
|
-
elif isinstance(node.op, ast.Mult):
|
|
382
|
-
_guard_repeat(left, right)
|
|
383
402
|
|
|
384
403
|
try:
|
|
385
404
|
return op(left, right)
|
|
@@ -467,7 +486,9 @@ class ExpressionEvaluator:
|
|
|
467
486
|
if isinstance(target, dict):
|
|
468
487
|
return target.get(key)
|
|
469
488
|
return target[key]
|
|
470
|
-
except (IndexError, KeyError
|
|
489
|
+
except (IndexError, KeyError) as exc:
|
|
490
|
+
raise UndefinedError(f"cannot index: {exc}") from exc
|
|
491
|
+
except TypeError as exc:
|
|
471
492
|
raise ExpressionError(f"cannot index: {exc}") from exc
|
|
472
493
|
|
|
473
494
|
if isinstance(node, ast.Dict):
|
|
@@ -513,7 +534,7 @@ class ExpressionEvaluator:
|
|
|
513
534
|
return False
|
|
514
535
|
if name == 'null':
|
|
515
536
|
return None
|
|
516
|
-
raise
|
|
537
|
+
raise UndefinedError(
|
|
517
538
|
f"variable {name!r} is not defined{_suggest(name, ctx)}"
|
|
518
539
|
)
|
|
519
540
|
|
|
@@ -521,7 +542,7 @@ class ExpressionEvaluator:
|
|
|
521
542
|
if isinstance(target, dict):
|
|
522
543
|
if attr in target:
|
|
523
544
|
return target[attr]
|
|
524
|
-
raise
|
|
545
|
+
raise UndefinedError(
|
|
525
546
|
f"key {attr!r} not found{_suggest(attr, target)}"
|
|
526
547
|
)
|
|
527
548
|
if isinstance(target, (list, tuple, str)) and attr == 'length':
|
|
@@ -560,7 +581,7 @@ class ExpressionEvaluator:
|
|
|
560
581
|
f"{attr!r}; use .length for the count, or the query's "
|
|
561
582
|
f"<name>_result for its metadata"
|
|
562
583
|
)
|
|
563
|
-
raise
|
|
584
|
+
raise UndefinedError(
|
|
564
585
|
f"attribute {attr!r} not found on {type(target).__name__}"
|
|
565
586
|
f"{_suggest(attr, target)}"
|
|
566
587
|
)
|