quantum-framework 0.11.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.11.0/quantum_framework.egg-info → quantum_framework-0.13.0}/PKG-INFO +5 -1
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/pyproject.toml +16 -2
- quantum_framework-0.13.0/quantum/cli/admin.py +131 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/cli/commands/migrate.py +31 -3
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/cli/runner.py +13 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/ast_nodes.py +1 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/expression_stdlib.py +28 -2
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/expressions.py +81 -16
- quantum_framework-0.13.0/quantum/core/guards.py +53 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parser.py +175 -12
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/control_flow/if_parser.py +25 -22
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/data/invoke_parser.py +4 -1
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/forms/action_parser.py +17 -7
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/action_handler.py +27 -13
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/component.py +49 -24
- quantum_framework-0.13.0/quantum/runtime/component_composer.py +133 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/database_service.py +32 -40
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/error_handler.py +1 -1
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/execution_context.py +0 -3
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/__init__.py +2 -2
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/base.py +3 -1
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/control_flow/__init__.py +2 -1
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/control_flow/if_executor.py +14 -10
- {quantum_framework-0.11.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.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/data/invoke_executor.py +60 -15
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/renderer.py +21 -22
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/web_server.py +155 -49
- quantum_framework-0.13.0/quantum/services.py +109 -0
- quantum_framework-0.13.0/quantum_admin/__init__.py +4 -0
- quantum_framework-0.13.0/quantum_admin/assets/quantum-admin-connectors.js +123 -0
- quantum_framework-0.13.0/quantum_admin/assets/quantum-admin.css +2058 -0
- quantum_framework-0.13.0/quantum_admin/assets/quantum-source-highlight.js +106 -0
- quantum_framework-0.13.0/quantum_admin/backend/__init__.py +4 -0
- quantum_framework-0.13.0/quantum_admin/backend/audit_service.py +346 -0
- quantum_framework-0.13.0/quantum_admin/backend/auth_service.py +394 -0
- quantum_framework-0.13.0/quantum_admin/backend/component_discovery.py +766 -0
- quantum_framework-0.13.0/quantum_admin/backend/config_generator.py +586 -0
- quantum_framework-0.13.0/quantum_admin/backend/connector_service.py +1016 -0
- quantum_framework-0.13.0/quantum_admin/backend/crud.py +557 -0
- quantum_framework-0.13.0/quantum_admin/backend/database.py +226 -0
- quantum_framework-0.13.0/quantum_admin/backend/db_setup_service.py +193 -0
- quantum_framework-0.13.0/quantum_admin/backend/deploy_service.py +698 -0
- quantum_framework-0.13.0/quantum_admin/backend/docker_service.py +422 -0
- quantum_framework-0.13.0/quantum_admin/backend/environment_service.py +418 -0
- quantum_framework-0.13.0/quantum_admin/backend/git_service.py +370 -0
- quantum_framework-0.13.0/quantum_admin/backend/health_service.py +390 -0
- quantum_framework-0.13.0/quantum_admin/backend/job_service.py +347 -0
- quantum_framework-0.13.0/quantum_admin/backend/log_service.py +331 -0
- quantum_framework-0.13.0/quantum_admin/backend/main.py +14426 -0
- quantum_framework-0.13.0/quantum_admin/backend/models.py +986 -0
- quantum_framework-0.13.0/quantum_admin/backend/pipeline_service.py +812 -0
- quantum_framework-0.13.0/quantum_admin/backend/resource_manager.py +1118 -0
- quantum_framework-0.13.0/quantum_admin/backend/schemas.py +419 -0
- quantum_framework-0.13.0/quantum_admin/backend/secret_manager.py +277 -0
- quantum_framework-0.13.0/quantum_admin/backend/settings_service.py +488 -0
- quantum_framework-0.13.0/quantum_admin/backend/template_service.py +710 -0
- quantum_framework-0.13.0/quantum_admin/backend/test_execution_service.py +364 -0
- quantum_framework-0.13.0/quantum_admin/backend/test_generator.py +782 -0
- quantum_framework-0.13.0/quantum_admin/backend/webhook_service.py +454 -0
- quantum_framework-0.13.0/quantum_admin/backend/websocket_manager.py +360 -0
- quantum_framework-0.13.0/quantum_admin/components/admin/_layout/AdminShell.q +121 -0
- quantum_framework-0.13.0/quantum_admin/components/admin/agents.q +107 -0
- quantum_framework-0.13.0/quantum_admin/components/admin/app/[name].q +558 -0
- quantum_framework-0.13.0/quantum_admin/components/admin/applications.q +160 -0
- quantum_framework-0.13.0/quantum_admin/components/admin/component/[...path].q +205 -0
- quantum_framework-0.13.0/quantum_admin/components/admin/components.q +59 -0
- quantum_framework-0.13.0/quantum_admin/components/admin/connectors.q +283 -0
- quantum_framework-0.13.0/quantum_admin/components/admin/dashboard.q +86 -0
- quantum_framework-0.13.0/quantum_admin/components/admin/database.q +95 -0
- quantum_framework-0.13.0/quantum_admin/components/admin/features.q +69 -0
- quantum_framework-0.13.0/quantum_admin/components/admin/index.q +12 -0
- quantum_framework-0.13.0/quantum_admin/components/admin/jobs.q +75 -0
- quantum_framework-0.13.0/quantum_admin/components/admin/login.q +65 -0
- quantum_framework-0.13.0/quantum_admin/components/admin/logout.q +11 -0
- quantum_framework-0.13.0/quantum_admin/components/admin/settings.q +174 -0
- quantum_framework-0.13.0/quantum_admin/components/admin/source.q +45 -0
- quantum_framework-0.13.0/quantum_admin/components/admin/tests.q +73 -0
- quantum_framework-0.13.0/quantum_admin/run.py +13 -0
- quantum_framework-0.13.0/quantum_admin/services/__init__.py +16 -0
- quantum_framework-0.13.0/quantum_admin/services/_base.py +84 -0
- quantum_framework-0.13.0/quantum_admin/services/_component_test_generator.py +205 -0
- quantum_framework-0.13.0/quantum_admin/services/_yaml_editor.py +82 -0
- quantum_framework-0.13.0/quantum_admin/services/apps.py +330 -0
- quantum_framework-0.13.0/quantum_admin/services/auth.py +61 -0
- quantum_framework-0.13.0/quantum_admin/services/components.py +210 -0
- quantum_framework-0.13.0/quantum_admin/services/connectors.py +164 -0
- quantum_framework-0.13.0/quantum_admin/services/projects.py +189 -0
- quantum_framework-0.13.0/quantum_admin/services/repository.py +223 -0
- quantum_framework-0.13.0/quantum_admin/services/settings.py +150 -0
- quantum_framework-0.13.0/quantum_admin/services/yaml_import.py +118 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0/quantum_framework.egg-info}/PKG-INFO +5 -1
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum_framework.egg-info/SOURCES.txt +66 -1
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum_framework.egg-info/requires.txt +5 -0
- quantum_framework-0.13.0/quantum_framework.egg-info/top_level.txt +2 -0
- quantum_framework-0.11.0/quantum/runtime/component_composer.py +0 -320
- quantum_framework-0.11.0/quantum_framework.egg-info/top_level.txt +0 -1
- quantum_framework-0.11.0/tests/test_projects_blog_components_admin.py +0 -514
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/LICENSE +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/README.md +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/cli/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/cli/commands/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/cli/commands/build.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/cli/commands/dev.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/cli/commands/docs.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/cli/commands/lint.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/cli/commands/new.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/cli/commands/serve.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/cli/commands/test.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/cli/deploy.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/cli/hot_reload.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/cli/jobs.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/cli/mq.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/cli/pkg.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/cli/server_process.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/cli/utils.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/compiler/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/compiler/base_generator.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/compiler/cli.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/compiler/expression_transformer.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/compiler/javascript/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/compiler/javascript/generator.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/compiler/optimizer.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/compiler/python/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/compiler/python/generator.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/compiler/python/runtime.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/compiler/transpiler.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/config_env.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/expression_diagnostics.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/agents/src/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/agents/src/ast_node.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/conditionals/src/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/conditionals/src/ast_node.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/data_import/src/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/data_import/src/ast_node.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/data_import/src/runtime.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/dump/src/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/dump/src/ast_node.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/dump/src/parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/dump/src/runtime.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/functions/src/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/functions/src/ast_node.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/game_engine_2d/src/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/game_engine_2d/src/ast_nodes.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/game_engine_2d/src/parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/invocation/src/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/invocation/src/ast_node.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/invocation/src/runtime.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/knowledge_base/src/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/knowledge_base/src/ast_node.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/knowledge_base/src/parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/logging/src/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/logging/src/ast_node.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/logging/src/parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/logging/src/runtime.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/loops/src/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/loops/src/ast_node.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/query/src/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/query/src/database_service.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/query/src/query_validators.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/state_management/src/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/state_management/src/ast_node.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/terminal_engine/src/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/terminal_engine/src/ast_nodes.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/terminal_engine/src/parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/testing_engine/src/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/testing_engine/src/ast_nodes.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/testing_engine/src/parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/theming/src/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/theming/src/ast_node.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/theming/src/presets.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/ui_engine/src/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/ui_engine/src/ast_nodes.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/ui_engine/src/parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/websocket/src/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/features/websocket/src/ast_node.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/html_compat.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parser_registry.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/ai/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/ai/agent_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/ai/knowledge_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/ai/llm_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/ai/team_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/base.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/composition/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/composition/import_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/composition/slot_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/control_flow/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/control_flow/loop_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/control_flow/set_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/data/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/data/data_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/data/query_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/data/transaction_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/events/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/events/dispatch_event_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/forms/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/forms/flash_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/forms/redirect_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/functions/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/functions/function_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/functions/param_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/functions/return_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/html/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/html/component_call_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/html/html_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/jobs/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/jobs/job_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/jobs/schedule_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/jobs/thread_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/messaging/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/messaging/message_ack_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/messaging/message_nack_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/messaging/message_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/messaging/queue_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/messaging/websocket_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/persistence/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/persistence/persist_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/routing/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/routing/route_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/scripting/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/scripting/pyclass_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/scripting/pydecorator_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/scripting/pyimport_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/scripting/python_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/services/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/services/dump_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/services/file_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/services/log_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/parsers/services/mail_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/core/tiers.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/packages/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/packages/manager.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/packages/manifest.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/packages/registry.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/packages/resolver.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/plugins/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/plugins/hooks.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/plugins/loader.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/plugins/manifest.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/plugins/registry.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/adapters/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/adapters/memory_adapter.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/adapters/rabbitmq_adapter.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/adapters/redis_adapter.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/adapters/sqlite_adapter.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/agent_service.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/ast_cache.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/auth_service.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/component_resolver.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/email_service.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executor_registry.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/ai/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/ai/agent_executor.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/ai/knowledge_executor.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/ai/llm_executor.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/ai/team_executor.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/control_flow/set_executor.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/data/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/data/data_executor.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/data/query_executor.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/data/transaction_executor.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/jobs/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/jobs/job_executor.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/jobs/schedule_executor.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/jobs/thread_executor.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/messaging/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/messaging/message_ack_executor.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/messaging/message_executor.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/messaging/queue_executor.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/messaging/websocket_executor.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/scripting/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/scripting/pyclass_executor.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/scripting/pyimport_executor.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/scripting/python_executor.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/services/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/services/dump_executor.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/services/file_executor.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/services/log_executor.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/executors/services/mail_executor.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/expression_cache.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/file_upload_service.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/function_registry.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/game_builder.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/game_code_generator.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/game_templates.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/godot_code_generator.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/godot_templates.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/job_executor.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/knowledge_service.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/llm_cache.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/llm_providers.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/llm_service.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/logging_setup.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/message_broker.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/message_queue_service.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/param_validation.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/pypy_compat.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/python_bridge.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/query_validators.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/service_container.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/terminal_builder.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/terminal_code_generator.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/terminal_templates.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/testing_builder.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/testing_code_generator.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/testing_templates.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/ui_builder.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/ui_desktop_adapter.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/ui_desktop_templates.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/ui_html_adapter.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/ui_html_templates.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/ui_mobile_adapter.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/ui_mobile_templates.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/ui_textual_adapter.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/ui_textual_templates.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/ui_tokens.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/ui_validator.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/validators.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/websocket_adapter.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/websocket_service.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/websocket_transport.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/runtime/wsgi.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum/utils/__init__.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum_framework.egg-info/dependency_links.txt +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/quantum_framework.egg-info/entry_points.txt +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/setup.cfg +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_agent.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_animations.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_ast_cache.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_cli_commands.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_component_library.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_desktop_bridge.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_expression_cache.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_form_validation.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_game_codegen.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_game_e2e.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_game_engine.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_game_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_godot_codegen.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_job_executor.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_llm_providers.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_message_broker.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_message_queue.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_mobile_target.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_multi_agent.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_packages.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_plugins.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_pypy_compat.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_python_scripting.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_state_persistence.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_terminal_codegen.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_terminal_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_testing_codegen.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_testing_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_theming.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_ui_html_adapter.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_ui_parity.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_ui_parser.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.0}/tests/test_ui_textual_adapter.py +0 -0
- {quantum_framework-0.11.0 → quantum_framework-0.13.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.13.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
|
|
@@ -37,6 +37,10 @@ Provides-Extra: rag
|
|
|
37
37
|
Requires-Dist: chromadb>=0.5.0; extra == "rag"
|
|
38
38
|
Provides-Extra: jobs
|
|
39
39
|
Requires-Dist: apscheduler>=3.10.0; extra == "jobs"
|
|
40
|
+
Provides-Extra: admin
|
|
41
|
+
Requires-Dist: sqlalchemy>=2.0; extra == "admin"
|
|
42
|
+
Requires-Dist: PyJWT>=2.8; extra == "admin"
|
|
43
|
+
Requires-Dist: cryptography>=42.0; extra == "admin"
|
|
40
44
|
Provides-Extra: websocket
|
|
41
45
|
Requires-Dist: websockets>=12.0; extra == "websocket"
|
|
42
46
|
Dynamic: license-file
|
|
@@ -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"}
|
|
@@ -50,6 +50,13 @@ jobs = [
|
|
|
50
50
|
# q:schedule failed on any clean install.
|
|
51
51
|
"apscheduler>=3.10.0",
|
|
52
52
|
]
|
|
53
|
+
admin = [
|
|
54
|
+
# `quantum admin`: the admin database (SQLAlchemy), its login tokens
|
|
55
|
+
# (PyJWT) and encrypted connector secrets (cryptography).
|
|
56
|
+
"sqlalchemy>=2.0",
|
|
57
|
+
"PyJWT>=2.8",
|
|
58
|
+
"cryptography>=42.0",
|
|
59
|
+
]
|
|
53
60
|
websocket = [
|
|
54
61
|
# The transport behind q:websocket. Without it the tag registers a
|
|
55
62
|
# connection and opens nothing — and says so, rather than pretending.
|
|
@@ -71,7 +78,14 @@ Repository = "https://github.com/danielgregorio/quantum"
|
|
|
71
78
|
# do core), e as pastas `quantum-as4`/`quantum-lsp` (projetos irmaos,
|
|
72
79
|
# hifenados e nem importaveis) — 91 arquivos de peso morto no wheel. O ponto
|
|
73
80
|
# em `quantum.*` casa subpacotes; sem ele, `quantum_admin` nao entra.
|
|
74
|
-
include = ["quantum", "quantum.*"
|
|
81
|
+
include = ["quantum", "quantum.*",
|
|
82
|
+
# The admin (`quantum admin`, extra [admin]): its screens, its
|
|
83
|
+
# services and the backend library they use. The backend's
|
|
84
|
+
# FastAPI modules ship with it, unused by the screens.
|
|
85
|
+
"quantum_admin", "quantum_admin.services", "quantum_admin.backend"]
|
|
86
|
+
|
|
87
|
+
[tool.setuptools.package-data]
|
|
88
|
+
quantum_admin = ["components/**/*.q", "assets/*"]
|
|
75
89
|
|
|
76
90
|
|
|
77
91
|
[tool.black]
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""`quantum admin` — serve the Quantum Admin (quantum_admin/components/admin).
|
|
2
|
+
|
|
3
|
+
The admin is a Quantum app: .q screens over declared services. This command
|
|
4
|
+
writes its configuration into a data folder and starts the same server as
|
|
5
|
+
`quantum start`, so nothing has to be set up by hand.
|
|
6
|
+
|
|
7
|
+
quantum admin # data in ./.quantum-admin, projects under ./
|
|
8
|
+
quantum admin --data quantum_admin # the repository's own admin data
|
|
9
|
+
|
|
10
|
+
The data folder holds the admin database, its settings (connectors, global
|
|
11
|
+
settings, process PIDs) and the generated config. Paths of projects are
|
|
12
|
+
relative to the folder the command runs in (--root to change it).
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import secrets
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
MODULES = [
|
|
20
|
+
"quantum_admin.services.auth",
|
|
21
|
+
"quantum_admin.services.projects",
|
|
22
|
+
"quantum_admin.services.yaml_import",
|
|
23
|
+
"quantum_admin.services.connectors",
|
|
24
|
+
"quantum_admin.services.settings",
|
|
25
|
+
"quantum_admin.services.components",
|
|
26
|
+
"quantum_admin.services.apps",
|
|
27
|
+
"quantum_admin.services.repository",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
INSTALL_HINT = 'pip install "quantum-framework[admin]"'
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def create_admin_parser(subparsers):
|
|
34
|
+
parser = subparsers.add_parser('admin', help='Start the Quantum Admin')
|
|
35
|
+
parser.add_argument('--port', type=int, default=8090, help='Port (default 8090)')
|
|
36
|
+
parser.add_argument('--data', default='.quantum-admin',
|
|
37
|
+
help='Folder for the admin database and settings (default ./.quantum-admin)')
|
|
38
|
+
parser.add_argument('--root', default='.',
|
|
39
|
+
help='Folder the project paths are relative to (default: current folder)')
|
|
40
|
+
return parser
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def missing_dependencies():
|
|
44
|
+
"""Names of the [admin] extra's packages that do not import."""
|
|
45
|
+
faltando = []
|
|
46
|
+
for modulo, pacote in (("sqlalchemy", "SQLAlchemy"), ("jwt", "PyJWT"),
|
|
47
|
+
("cryptography", "cryptography")):
|
|
48
|
+
try:
|
|
49
|
+
__import__(modulo)
|
|
50
|
+
except ImportError:
|
|
51
|
+
faltando.append(pacote)
|
|
52
|
+
return faltando
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _package() -> Path:
|
|
56
|
+
import quantum_admin
|
|
57
|
+
return Path(quantum_admin.__file__).resolve().parent
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def screens_folder() -> Path:
|
|
61
|
+
return _package() / "components"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def assets_folder() -> Path:
|
|
65
|
+
"""The screens' CSS and JS, served under /static/."""
|
|
66
|
+
return _package() / "assets"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _secret(arquivo: Path) -> str:
|
|
70
|
+
"""A key kept in the data folder, so sessions survive a restart."""
|
|
71
|
+
if arquivo.exists():
|
|
72
|
+
return arquivo.read_text(encoding="utf-8").strip()
|
|
73
|
+
chave = secrets.token_hex(32)
|
|
74
|
+
arquivo.write_text(chave, encoding="utf-8")
|
|
75
|
+
try:
|
|
76
|
+
arquivo.chmod(0o600)
|
|
77
|
+
except OSError:
|
|
78
|
+
pass
|
|
79
|
+
return chave
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def prepare(data: str, root: str, port: int) -> Path:
|
|
83
|
+
"""Create the data folder, point the admin at it, write the config. Returns its path."""
|
|
84
|
+
import yaml
|
|
85
|
+
|
|
86
|
+
pasta = Path(data).resolve()
|
|
87
|
+
(pasta / "settings").mkdir(parents=True, exist_ok=True)
|
|
88
|
+
|
|
89
|
+
# Read by quantum_admin.backend at import time, so set before any import.
|
|
90
|
+
os.environ["QUANTUM_ADMIN_DATABASE_URL"] = f"sqlite:///{(pasta / 'quantum_admin.db').as_posix()}"
|
|
91
|
+
os.environ["QUANTUM_ADMIN_SETTINGS_DIR"] = str(pasta / "settings")
|
|
92
|
+
os.environ["QUANTUM_ADMIN_ROOT"] = str(Path(root).resolve())
|
|
93
|
+
os.environ.setdefault("JWT_SECRET_KEY", _secret(pasta / "jwt_secret"))
|
|
94
|
+
|
|
95
|
+
config = pasta / "quantum.config.yaml"
|
|
96
|
+
config.write_text(
|
|
97
|
+
"# Generated by `quantum admin` on every start; edits are overwritten.\n" +
|
|
98
|
+
yaml.safe_dump({
|
|
99
|
+
"server": {"host": "127.0.0.1", "port": port, "reload": False, "debug": False},
|
|
100
|
+
"paths": {"components": screens_folder().as_posix(),
|
|
101
|
+
"static": assets_folder().as_posix(),
|
|
102
|
+
"uploads": (pasta / "uploads").as_posix(),
|
|
103
|
+
"logs": (pasta / "logs").as_posix()},
|
|
104
|
+
"security": {"login_url": "/admin/login", "secret_key": _secret(pasta / "session_secret")},
|
|
105
|
+
"logging": {"level": "WARNING", "console": True, "file": False},
|
|
106
|
+
"services": MODULES,
|
|
107
|
+
}, sort_keys=False),
|
|
108
|
+
encoding="utf-8")
|
|
109
|
+
return config
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def handle_admin(args) -> int:
|
|
113
|
+
faltando = missing_dependencies()
|
|
114
|
+
if faltando:
|
|
115
|
+
print(f"[ERROR] The admin needs {', '.join(faltando)}. Install with:\n {INSTALL_HINT}")
|
|
116
|
+
return 1
|
|
117
|
+
|
|
118
|
+
config = prepare(args.data, args.root, args.port)
|
|
119
|
+
|
|
120
|
+
from quantum.runtime.web_server import QuantumWebServer
|
|
121
|
+
from quantum_admin.backend.auth_service import get_auth_service
|
|
122
|
+
|
|
123
|
+
# Created now, not at the first login, so a generated password is printed
|
|
124
|
+
# before the URL instead of in the middle of the request log.
|
|
125
|
+
get_auth_service()
|
|
126
|
+
print(f"\n Quantum Admin: http://127.0.0.1:{args.port}/admin"
|
|
127
|
+
f"\n data: {config.parent}\n")
|
|
128
|
+
|
|
129
|
+
server = QuantumWebServer(str(config))
|
|
130
|
+
server.PID_FILE = str(config.parent / "admin.pid")
|
|
131
|
+
return server.start() or 0
|
|
@@ -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"""
|
|
@@ -280,6 +284,7 @@ def main():
|
|
|
280
284
|
Examples:
|
|
281
285
|
quantum start # Start web server (magic!)
|
|
282
286
|
quantum run hello.q # Execute component
|
|
287
|
+
quantum admin # Start the Quantum Admin
|
|
283
288
|
quantum run game.q --engine godot # Build Godot 4 project
|
|
284
289
|
quantum run backup-job.q # Execute job
|
|
285
290
|
quantum deploy ./my-app # Deploy application
|
|
@@ -330,6 +335,10 @@ Examples:
|
|
|
330
335
|
# Stop command
|
|
331
336
|
subparsers.add_parser('stop', help='Stop running web server')
|
|
332
337
|
|
|
338
|
+
# Admin command (the screens need the [admin] extra; checked when run)
|
|
339
|
+
from quantum.cli.admin import create_admin_parser
|
|
340
|
+
create_admin_parser(subparsers)
|
|
341
|
+
|
|
333
342
|
# Deploy commands (if available)
|
|
334
343
|
deploy_available, create_deploy_parser, create_apps_parser, handle_deploy, handle_apps = _get_deploy_module()
|
|
335
344
|
|
|
@@ -392,6 +401,10 @@ Examples:
|
|
|
392
401
|
traceback.print_exc()
|
|
393
402
|
sys.exit(1)
|
|
394
403
|
|
|
404
|
+
elif args.command == 'admin':
|
|
405
|
+
from quantum.cli.admin import handle_admin
|
|
406
|
+
sys.exit(handle_admin(args))
|
|
407
|
+
|
|
395
408
|
# Handle 'stop' command
|
|
396
409
|
elif args.command == 'stop':
|
|
397
410
|
from quantum.cli.server_process import stop_server
|
|
@@ -112,6 +112,7 @@ class ComponentNode(QuantumNode):
|
|
|
112
112
|
# Phase G: Authentication & Security
|
|
113
113
|
self.require_auth = False # Require authentication to access component
|
|
114
114
|
self.require_role = None # Required role(s) to access (comma-separated: "admin,editor")
|
|
115
|
+
self.login_url = None # AUTH-4: overrides security.login_url for this component
|
|
115
116
|
self.require_permission = None # Required permission(s)
|
|
116
117
|
|
|
117
118
|
# HTML rendering & interactivity (Phase 1 & future Phase 3)
|
|
@@ -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
|
|
|
@@ -423,18 +482,18 @@ class ExpressionEvaluator:
|
|
|
423
482
|
return op(operand)
|
|
424
483
|
|
|
425
484
|
if isinstance(node, ast.BoolOp):
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
if
|
|
436
|
-
return
|
|
437
|
-
return
|
|
485
|
+
# EXPR-6: short-circuit, as in Python. Every operand used to be
|
|
486
|
+
# evaluated first, so `user and user.name` failed with "variable
|
|
487
|
+
# 'user' is not defined"-style errors exactly when the left side
|
|
488
|
+
# was there to guard the right.
|
|
489
|
+
valor = False
|
|
490
|
+
for operando in node.values:
|
|
491
|
+
valor = self._eval(operando, ctx)
|
|
492
|
+
if isinstance(node.op, ast.And) and not valor:
|
|
493
|
+
return valor
|
|
494
|
+
if isinstance(node.op, ast.Or) and valor:
|
|
495
|
+
return valor
|
|
496
|
+
return valor
|
|
438
497
|
|
|
439
498
|
if isinstance(node, ast.Compare):
|
|
440
499
|
left = self._eval(node.left, ctx)
|
|
@@ -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)
|