quantum-framework 0.18.0__py3-none-any.whl

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.
Files changed (333) hide show
  1. quantum/__init__.py +1 -0
  2. quantum/cli/__init__.py +2 -0
  3. quantum/cli/admin.py +131 -0
  4. quantum/cli/check.py +223 -0
  5. quantum/cli/commands/__init__.py +15 -0
  6. quantum/cli/commands/build.py +414 -0
  7. quantum/cli/commands/dev.py +185 -0
  8. quantum/cli/commands/docs.py +329 -0
  9. quantum/cli/commands/lint.py +523 -0
  10. quantum/cli/commands/migrate.py +548 -0
  11. quantum/cli/commands/new.py +622 -0
  12. quantum/cli/commands/serve.py +190 -0
  13. quantum/cli/commands/test.py +193 -0
  14. quantum/cli/deploy.py +810 -0
  15. quantum/cli/hot_reload.py +951 -0
  16. quantum/cli/jobs.py +356 -0
  17. quantum/cli/mq.py +582 -0
  18. quantum/cli/pkg.py +390 -0
  19. quantum/cli/runner.py +565 -0
  20. quantum/cli/server_process.py +159 -0
  21. quantum/cli/utils.py +334 -0
  22. quantum/compiler/__init__.py +30 -0
  23. quantum/compiler/base_generator.py +367 -0
  24. quantum/compiler/cli.py +295 -0
  25. quantum/compiler/expression_transformer.py +444 -0
  26. quantum/compiler/javascript/__init__.py +10 -0
  27. quantum/compiler/javascript/generator.py +659 -0
  28. quantum/compiler/optimizer.py +270 -0
  29. quantum/compiler/python/__init__.py +10 -0
  30. quantum/compiler/python/generator.py +883 -0
  31. quantum/compiler/python/runtime.py +863 -0
  32. quantum/compiler/transpiler.py +330 -0
  33. quantum/core/__init__.py +3 -0
  34. quantum/core/ast_nodes.py +2619 -0
  35. quantum/core/config_env.py +54 -0
  36. quantum/core/docs_links.py +8 -0
  37. quantum/core/expression_diagnostics.py +87 -0
  38. quantum/core/expression_stdlib.py +208 -0
  39. quantum/core/expressions.py +677 -0
  40. quantum/core/features/agents/src/__init__.py +30 -0
  41. quantum/core/features/agents/src/ast_node.py +540 -0
  42. quantum/core/features/conditionals/src/__init__.py +8 -0
  43. quantum/core/features/conditionals/src/ast_node.py +68 -0
  44. quantum/core/features/data_import/src/__init__.py +0 -0
  45. quantum/core/features/data_import/src/ast_node.py +292 -0
  46. quantum/core/features/data_import/src/runtime.py +533 -0
  47. quantum/core/features/dump/src/__init__.py +12 -0
  48. quantum/core/features/dump/src/ast_node.py +106 -0
  49. quantum/core/features/dump/src/parser.py +61 -0
  50. quantum/core/features/dump/src/runtime.py +246 -0
  51. quantum/core/features/functions/src/__init__.py +8 -0
  52. quantum/core/features/functions/src/ast_node.py +149 -0
  53. quantum/core/features/game_engine_2d/src/__init__.py +19 -0
  54. quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
  55. quantum/core/features/game_engine_2d/src/parser.py +983 -0
  56. quantum/core/features/invocation/src/__init__.py +0 -0
  57. quantum/core/features/invocation/src/ast_node.py +147 -0
  58. quantum/core/features/invocation/src/runtime.py +332 -0
  59. quantum/core/features/knowledge_base/src/__init__.py +6 -0
  60. quantum/core/features/knowledge_base/src/ast_node.py +113 -0
  61. quantum/core/features/knowledge_base/src/parser.py +82 -0
  62. quantum/core/features/logging/src/__init__.py +12 -0
  63. quantum/core/features/logging/src/ast_node.py +111 -0
  64. quantum/core/features/logging/src/parser.py +50 -0
  65. quantum/core/features/logging/src/runtime.py +190 -0
  66. quantum/core/features/loops/src/__init__.py +8 -0
  67. quantum/core/features/loops/src/ast_node.py +60 -0
  68. quantum/core/features/query/src/__init__.py +0 -0
  69. quantum/core/features/query/src/query_validators.py +20 -0
  70. quantum/core/features/state_management/src/__init__.py +11 -0
  71. quantum/core/features/state_management/src/ast_node.py +121 -0
  72. quantum/core/features/terminal_engine/src/__init__.py +21 -0
  73. quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
  74. quantum/core/features/terminal_engine/src/parser.py +361 -0
  75. quantum/core/features/testing_engine/src/__init__.py +41 -0
  76. quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
  77. quantum/core/features/testing_engine/src/parser.py +604 -0
  78. quantum/core/features/theming/src/__init__.py +48 -0
  79. quantum/core/features/theming/src/ast_node.py +137 -0
  80. quantum/core/features/theming/src/presets.py +405 -0
  81. quantum/core/features/ui_engine/src/__init__.py +20 -0
  82. quantum/core/features/ui_engine/src/ast_nodes.py +1913 -0
  83. quantum/core/features/ui_engine/src/parser.py +1192 -0
  84. quantum/core/features/websocket/src/__init__.py +24 -0
  85. quantum/core/features/websocket/src/ast_node.py +247 -0
  86. quantum/core/guards.py +53 -0
  87. quantum/core/html_compat.py +299 -0
  88. quantum/core/parser.py +1551 -0
  89. quantum/core/parser_registry.py +213 -0
  90. quantum/core/parsers/__init__.py +76 -0
  91. quantum/core/parsers/ai/__init__.py +12 -0
  92. quantum/core/parsers/ai/agent_parser.py +106 -0
  93. quantum/core/parsers/ai/knowledge_parser.py +86 -0
  94. quantum/core/parsers/ai/llm_parser.py +78 -0
  95. quantum/core/parsers/ai/team_parser.py +88 -0
  96. quantum/core/parsers/base.py +322 -0
  97. quantum/core/parsers/composition/__init__.py +10 -0
  98. quantum/core/parsers/composition/import_parser.py +50 -0
  99. quantum/core/parsers/composition/slot_parser.py +48 -0
  100. quantum/core/parsers/control_flow/__init__.py +11 -0
  101. quantum/core/parsers/control_flow/if_parser.py +71 -0
  102. quantum/core/parsers/control_flow/loop_parser.py +105 -0
  103. quantum/core/parsers/control_flow/set_parser.py +93 -0
  104. quantum/core/parsers/data/__init__.py +12 -0
  105. quantum/core/parsers/data/data_parser.py +229 -0
  106. quantum/core/parsers/data/invoke_parser.py +133 -0
  107. quantum/core/parsers/data/query_parser.py +213 -0
  108. quantum/core/parsers/data/transaction_parser.py +86 -0
  109. quantum/core/parsers/events/__init__.py +9 -0
  110. quantum/core/parsers/events/dispatch_event_parser.py +44 -0
  111. quantum/core/parsers/forms/__init__.py +11 -0
  112. quantum/core/parsers/forms/action_parser.py +79 -0
  113. quantum/core/parsers/forms/flash_parser.py +34 -0
  114. quantum/core/parsers/forms/redirect_parser.py +33 -0
  115. quantum/core/parsers/functions/__init__.py +11 -0
  116. quantum/core/parsers/functions/function_parser.py +98 -0
  117. quantum/core/parsers/functions/param_parser.py +73 -0
  118. quantum/core/parsers/functions/return_parser.py +31 -0
  119. quantum/core/parsers/html/__init__.py +10 -0
  120. quantum/core/parsers/html/component_call_parser.py +130 -0
  121. quantum/core/parsers/html/html_parser.py +115 -0
  122. quantum/core/parsers/jobs/__init__.py +11 -0
  123. quantum/core/parsers/jobs/job_parser.py +71 -0
  124. quantum/core/parsers/jobs/schedule_parser.py +62 -0
  125. quantum/core/parsers/jobs/thread_parser.py +57 -0
  126. quantum/core/parsers/messaging/__init__.py +17 -0
  127. quantum/core/parsers/messaging/message_ack_parser.py +30 -0
  128. quantum/core/parsers/messaging/message_nack_parser.py +30 -0
  129. quantum/core/parsers/messaging/message_parser.py +114 -0
  130. quantum/core/parsers/messaging/queue_parser.py +61 -0
  131. quantum/core/parsers/messaging/websocket_parser.py +121 -0
  132. quantum/core/parsers/routing/__init__.py +9 -0
  133. quantum/core/parsers/routing/route_parser.py +41 -0
  134. quantum/core/parsers/scripting/__init__.py +12 -0
  135. quantum/core/parsers/scripting/pyclass_parser.py +62 -0
  136. quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
  137. quantum/core/parsers/scripting/pyimport_parser.py +52 -0
  138. quantum/core/parsers/scripting/python_parser.py +49 -0
  139. quantum/core/parsers/services/__init__.py +12 -0
  140. quantum/core/parsers/services/dump_parser.py +52 -0
  141. quantum/core/parsers/services/file_parser.py +48 -0
  142. quantum/core/parsers/services/log_parser.py +46 -0
  143. quantum/core/parsers/services/mail_parser.py +65 -0
  144. quantum/core/tiers.py +164 -0
  145. quantum/core/xml_lines.py +81 -0
  146. quantum/packages/__init__.py +28 -0
  147. quantum/packages/manager.py +413 -0
  148. quantum/packages/manifest.py +351 -0
  149. quantum/packages/registry.py +399 -0
  150. quantum/packages/resolver.py +336 -0
  151. quantum/plugins/__init__.py +33 -0
  152. quantum/plugins/hooks.py +329 -0
  153. quantum/plugins/loader.py +479 -0
  154. quantum/plugins/manifest.py +336 -0
  155. quantum/plugins/registry.py +371 -0
  156. quantum/runtime/__init__.py +25 -0
  157. quantum/runtime/action_handler.py +532 -0
  158. quantum/runtime/adapters/__init__.py +88 -0
  159. quantum/runtime/adapters/memory_adapter.py +690 -0
  160. quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
  161. quantum/runtime/adapters/redis_adapter.py +582 -0
  162. quantum/runtime/adapters/sqlite_adapter.py +414 -0
  163. quantum/runtime/agent_service.py +1133 -0
  164. quantum/runtime/ast_cache.py +506 -0
  165. quantum/runtime/auth_service.py +267 -0
  166. quantum/runtime/component.py +1048 -0
  167. quantum/runtime/component_composer.py +133 -0
  168. quantum/runtime/component_resolver.py +174 -0
  169. quantum/runtime/conditions.py +54 -0
  170. quantum/runtime/database_service.py +595 -0
  171. quantum/runtime/db_schema.py +100 -0
  172. quantum/runtime/dev_panel.py +178 -0
  173. quantum/runtime/email_service.py +162 -0
  174. quantum/runtime/error_handler.py +295 -0
  175. quantum/runtime/execution_context.py +283 -0
  176. quantum/runtime/executor_registry.py +178 -0
  177. quantum/runtime/executors/__init__.py +71 -0
  178. quantum/runtime/executors/ai/__init__.py +12 -0
  179. quantum/runtime/executors/ai/agent_executor.py +217 -0
  180. quantum/runtime/executors/ai/knowledge_executor.py +114 -0
  181. quantum/runtime/executors/ai/llm_executor.py +153 -0
  182. quantum/runtime/executors/ai/team_executor.py +171 -0
  183. quantum/runtime/executors/base.py +264 -0
  184. quantum/runtime/executors/control_flow/__init__.py +12 -0
  185. quantum/runtime/executors/control_flow/if_executor.py +98 -0
  186. quantum/runtime/executors/control_flow/loop_executor.py +308 -0
  187. quantum/runtime/executors/control_flow/redirect_executor.py +43 -0
  188. quantum/runtime/executors/control_flow/set_executor.py +464 -0
  189. quantum/runtime/executors/data/__init__.py +12 -0
  190. quantum/runtime/executors/data/data_executor.py +156 -0
  191. quantum/runtime/executors/data/invoke_executor.py +228 -0
  192. quantum/runtime/executors/data/query_executor.py +290 -0
  193. quantum/runtime/executors/data/transaction_executor.py +91 -0
  194. quantum/runtime/executors/jobs/__init__.py +11 -0
  195. quantum/runtime/executors/jobs/job_executor.py +190 -0
  196. quantum/runtime/executors/jobs/schedule_executor.py +132 -0
  197. quantum/runtime/executors/jobs/thread_executor.py +127 -0
  198. quantum/runtime/executors/messaging/__init__.py +17 -0
  199. quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
  200. quantum/runtime/executors/messaging/message_executor.py +174 -0
  201. quantum/runtime/executors/messaging/queue_executor.py +103 -0
  202. quantum/runtime/executors/messaging/websocket_executor.py +197 -0
  203. quantum/runtime/executors/scripting/__init__.py +11 -0
  204. quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
  205. quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
  206. quantum/runtime/executors/scripting/python_executor.py +249 -0
  207. quantum/runtime/executors/services/__init__.py +12 -0
  208. quantum/runtime/executors/services/dump_executor.py +72 -0
  209. quantum/runtime/executors/services/file_executor.py +89 -0
  210. quantum/runtime/executors/services/log_executor.py +77 -0
  211. quantum/runtime/executors/services/mail_executor.py +81 -0
  212. quantum/runtime/expression_cache.py +498 -0
  213. quantum/runtime/file_upload_service.py +326 -0
  214. quantum/runtime/function_registry.py +118 -0
  215. quantum/runtime/game_builder.py +166 -0
  216. quantum/runtime/game_code_generator.py +2371 -0
  217. quantum/runtime/game_templates.py +2006 -0
  218. quantum/runtime/godot_code_generator.py +4681 -0
  219. quantum/runtime/godot_templates.py +1449 -0
  220. quantum/runtime/job_executor.py +1599 -0
  221. quantum/runtime/knowledge_service.py +546 -0
  222. quantum/runtime/llm_cache.py +100 -0
  223. quantum/runtime/llm_providers.py +704 -0
  224. quantum/runtime/llm_service.py +287 -0
  225. quantum/runtime/local_server.py +28 -0
  226. quantum/runtime/logging_setup.py +141 -0
  227. quantum/runtime/message_broker.py +364 -0
  228. quantum/runtime/message_queue_service.py +571 -0
  229. quantum/runtime/param_validation.py +218 -0
  230. quantum/runtime/pypy_compat.py +317 -0
  231. quantum/runtime/python_bridge.py +698 -0
  232. quantum/runtime/query_validators.py +304 -0
  233. quantum/runtime/renderer.py +734 -0
  234. quantum/runtime/service_container.py +470 -0
  235. quantum/runtime/table_params.py +163 -0
  236. quantum/runtime/terminal_builder.py +76 -0
  237. quantum/runtime/terminal_code_generator.py +607 -0
  238. quantum/runtime/terminal_templates.py +243 -0
  239. quantum/runtime/testing_builder.py +77 -0
  240. quantum/runtime/testing_code_generator.py +833 -0
  241. quantum/runtime/testing_templates.py +85 -0
  242. quantum/runtime/ui_builder.py +222 -0
  243. quantum/runtime/ui_console.py +405 -0
  244. quantum/runtime/ui_desktop.py +56 -0
  245. quantum/runtime/ui_form_rules.py +193 -0
  246. quantum/runtime/ui_html_adapter.py +2567 -0
  247. quantum/runtime/ui_html_templates.py +2106 -0
  248. quantum/runtime/ui_mobile_adapter.py +1832 -0
  249. quantum/runtime/ui_mobile_templates.py +1003 -0
  250. quantum/runtime/ui_page_renderer.py +392 -0
  251. quantum/runtime/ui_pager.py +73 -0
  252. quantum/runtime/ui_table_edit.py +139 -0
  253. quantum/runtime/ui_textual_adapter.py +1866 -0
  254. quantum/runtime/ui_textual_templates.py +45 -0
  255. quantum/runtime/ui_tokens.py +465 -0
  256. quantum/runtime/ui_validator.py +365 -0
  257. quantum/runtime/ui_view.py +221 -0
  258. quantum/runtime/validators.py +256 -0
  259. quantum/runtime/web_server.py +2131 -0
  260. quantum/runtime/websocket_adapter.py +501 -0
  261. quantum/runtime/websocket_service.py +585 -0
  262. quantum/runtime/websocket_transport.py +289 -0
  263. quantum/runtime/wsgi.py +101 -0
  264. quantum/services.py +109 -0
  265. quantum/utils/__init__.py +1 -0
  266. quantum_admin/__init__.py +4 -0
  267. quantum_admin/assets/quantum-admin-connectors.js +123 -0
  268. quantum_admin/assets/quantum-admin.css +2058 -0
  269. quantum_admin/assets/quantum-source-highlight.js +106 -0
  270. quantum_admin/backend/__init__.py +4 -0
  271. quantum_admin/backend/audit_service.py +346 -0
  272. quantum_admin/backend/auth_service.py +394 -0
  273. quantum_admin/backend/component_discovery.py +766 -0
  274. quantum_admin/backend/config_generator.py +586 -0
  275. quantum_admin/backend/connector_service.py +1016 -0
  276. quantum_admin/backend/crud.py +557 -0
  277. quantum_admin/backend/database.py +226 -0
  278. quantum_admin/backend/db_setup_service.py +193 -0
  279. quantum_admin/backend/deploy_service.py +698 -0
  280. quantum_admin/backend/docker_service.py +422 -0
  281. quantum_admin/backend/environment_service.py +418 -0
  282. quantum_admin/backend/git_service.py +370 -0
  283. quantum_admin/backend/health_service.py +390 -0
  284. quantum_admin/backend/job_service.py +347 -0
  285. quantum_admin/backend/log_service.py +331 -0
  286. quantum_admin/backend/main.py +14426 -0
  287. quantum_admin/backend/models.py +986 -0
  288. quantum_admin/backend/pipeline_service.py +812 -0
  289. quantum_admin/backend/resource_manager.py +1118 -0
  290. quantum_admin/backend/schemas.py +419 -0
  291. quantum_admin/backend/secret_manager.py +277 -0
  292. quantum_admin/backend/settings_service.py +488 -0
  293. quantum_admin/backend/template_service.py +710 -0
  294. quantum_admin/backend/test_execution_service.py +364 -0
  295. quantum_admin/backend/test_generator.py +782 -0
  296. quantum_admin/backend/webhook_service.py +454 -0
  297. quantum_admin/backend/websocket_manager.py +360 -0
  298. quantum_admin/components/admin/_layout/AdminShell.q +121 -0
  299. quantum_admin/components/admin/agents.q +107 -0
  300. quantum_admin/components/admin/app/[name].q +558 -0
  301. quantum_admin/components/admin/applications.q +160 -0
  302. quantum_admin/components/admin/component/[...path].q +205 -0
  303. quantum_admin/components/admin/components.q +59 -0
  304. quantum_admin/components/admin/connectors.q +283 -0
  305. quantum_admin/components/admin/dashboard.q +86 -0
  306. quantum_admin/components/admin/database.q +95 -0
  307. quantum_admin/components/admin/features.q +69 -0
  308. quantum_admin/components/admin/index.q +12 -0
  309. quantum_admin/components/admin/jobs.q +75 -0
  310. quantum_admin/components/admin/login.q +65 -0
  311. quantum_admin/components/admin/logout.q +11 -0
  312. quantum_admin/components/admin/settings.q +174 -0
  313. quantum_admin/components/admin/source.q +45 -0
  314. quantum_admin/components/admin/tests.q +73 -0
  315. quantum_admin/run.py +13 -0
  316. quantum_admin/services/__init__.py +16 -0
  317. quantum_admin/services/_base.py +84 -0
  318. quantum_admin/services/_component_test_generator.py +205 -0
  319. quantum_admin/services/_yaml_editor.py +82 -0
  320. quantum_admin/services/apps.py +330 -0
  321. quantum_admin/services/auth.py +61 -0
  322. quantum_admin/services/components.py +210 -0
  323. quantum_admin/services/connectors.py +164 -0
  324. quantum_admin/services/projects.py +189 -0
  325. quantum_admin/services/repository.py +223 -0
  326. quantum_admin/services/settings.py +150 -0
  327. quantum_admin/services/yaml_import.py +118 -0
  328. quantum_framework-0.18.0.dist-info/METADATA +281 -0
  329. quantum_framework-0.18.0.dist-info/RECORD +333 -0
  330. quantum_framework-0.18.0.dist-info/WHEEL +5 -0
  331. quantum_framework-0.18.0.dist-info/entry_points.txt +2 -0
  332. quantum_framework-0.18.0.dist-info/licenses/LICENSE +21 -0
  333. quantum_framework-0.18.0.dist-info/top_level.txt +2 -0
quantum/__init__.py ADDED
@@ -0,0 +1 @@
1
+ # Quantum main package
@@ -0,0 +1,2 @@
1
+ # CLI Quantum modules
2
+ from .runner import QuantumRunner, main
quantum/cli/admin.py ADDED
@@ -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
quantum/cli/check.py ADDED
@@ -0,0 +1,223 @@
1
+ """`quantum check`: every page parses, every q:query compiles against the database,
2
+ and every `{query.field}` a page reads is a column the query returns (M8, DEV-3).
3
+
4
+ A typo in SQL or in a field name used to surface only when someone opened the
5
+ page — `no such column` as a 500, or, for a field, an empty cell. This reads
6
+ the project the way the server would and reports each problem with its file
7
+ and line, without running anything: a query is compiled by the database
8
+ (EXPLAIN) and a SELECT's columns come from running it with LIMIT 0.
9
+ """
10
+
11
+ import re
12
+ from dataclasses import dataclass
13
+ from pathlib import Path
14
+ from typing import Dict, Iterator, List, Optional
15
+
16
+ # Fields of `<name>_result` and of a query row that are not columns.
17
+ _META = {'recordCount', 'columnList', 'executionTime', 'success', 'error', 'sql', 'page',
18
+ 'totalPages', 'hasMore', 'cached', 'affectedRows', 'lastInsertId', 'data', 'length'}
19
+ _EXPR = re.compile(r'\{([^{}]*)\}')
20
+
21
+
22
+ @dataclass
23
+ class Problem:
24
+ file: str
25
+ line: Optional[int]
26
+ message: str
27
+
28
+ def __str__(self) -> str:
29
+ return f'{self.file}:{self.line or "?"}: {self.message}'
30
+
31
+
32
+ def _config(config_path: Path) -> dict:
33
+ import yaml
34
+ from quantum.core.config_env import expand_env
35
+ if not config_path.exists():
36
+ return {}
37
+ return expand_env(yaml.safe_load(config_path.read_text(encoding='utf-8')) or {})
38
+
39
+
40
+ def _children(node) -> Iterator:
41
+ """The nodes inside a node, whatever the field is called."""
42
+ for campo in ('statements', 'body', 'if_body', 'else_body', 'children'):
43
+ for filho in getattr(node, campo, None) or []:
44
+ if hasattr(filho, '__dict__'):
45
+ yield filho
46
+ for bloco in getattr(node, 'elseif_blocks', None) or []:
47
+ corpo = bloco.get('body') if isinstance(bloco, dict) else getattr(bloco, 'body', None)
48
+ for filho in corpo or []:
49
+ yield filho
50
+
51
+
52
+ def _texts(node) -> List[str]:
53
+ """The strings of a node that may hold {expressions}."""
54
+ textos = []
55
+ for campo, valor in vars(node).items():
56
+ if campo.startswith('_') or campo in ('sql',):
57
+ continue
58
+ if isinstance(valor, str) and '{' in valor:
59
+ textos.append(valor)
60
+ elif isinstance(valor, dict):
61
+ textos += [v for v in valor.values() if isinstance(v, str) and '{' in v]
62
+ return textos
63
+
64
+
65
+ class ProjectChecker:
66
+ def __init__(self, project: Path, config_path: Path):
67
+ self.project = project
68
+ self.config = _config(config_path)
69
+ self.datasources = self.config.get('datasources') or {}
70
+ self._conexoes: Dict[str, object] = {}
71
+ self.problems: List[Problem] = []
72
+ self.notes: List[str] = []
73
+ self.queries_checked = 0
74
+ self.files = 0
75
+
76
+ # -- the database ---------------------------------------------------------
77
+
78
+ def _connection(self, nome: str):
79
+ from quantum.runtime.db_schema import SchemaError, connect_readonly
80
+ if nome not in self._conexoes:
81
+ try:
82
+ self._conexoes[nome] = connect_readonly(self.datasources[nome], self.project)
83
+ except SchemaError as exc:
84
+ self._conexoes[nome] = None
85
+ self.notes.append(f'datasource {nome!r} not checked: {exc}')
86
+ return self._conexoes[nome]
87
+
88
+ # -- a file ---------------------------------------------------------------
89
+
90
+ def check_file(self, arquivo: Path) -> None:
91
+ from quantum.core.parser import QuantumParser
92
+ rel = arquivo.relative_to(self.project).as_posix() if arquivo.is_relative_to(self.project) else str(arquivo)
93
+ self.files += 1
94
+ try:
95
+ ast = QuantumParser(use_cache=False).parse_file(str(arquivo))
96
+ except Exception as exc: # a parse error is a problem like any other
97
+ primeira = str(exc).split('\n')[0]
98
+ self.problems.append(Problem(rel, getattr(exc, 'line', None), primeira))
99
+ return
100
+ colunas: Dict[str, List[str]] = {}
101
+ self._queries(ast, rel, colunas)
102
+ self._fields(ast, rel, colunas, {nome: nome for nome in colunas}, None)
103
+
104
+ def _queries(self, node, rel: str, colunas: Dict[str, List[str]]) -> None:
105
+ from quantum.core.ast_nodes import ActionNode, QueryNode
106
+ from quantum.runtime.db_schema import check_sql, read_schema
107
+ for filho in _children(node):
108
+ if isinstance(filho, ActionNode) and getattr(filho, 'table', None):
109
+ # UI-10: the table the action takes its params from exists, with those columns.
110
+ ds = filho.table_datasource
111
+ if ds not in self.datasources:
112
+ self.problems.append(Problem(rel, getattr(filho, 'source_line', None),
113
+ f'<q:action name="{filho.name}">: datasource {ds!r} is not '
114
+ f'declared in quantum.config.yaml'))
115
+ elif self._connection(ds) is not None:
116
+ esquema = read_schema(self._connection(ds))
117
+ tabela = esquema.get(filho.table)
118
+ if tabela is None:
119
+ self.problems.append(Problem(rel, getattr(filho, 'source_line', None),
120
+ f'<q:action name="{filho.name}" table="{filho.table}">: '
121
+ f'no such table (tables: {", ".join(esquema) or "none"})'))
122
+ else:
123
+ for coluna in filho.table_columns or []:
124
+ if coluna not in tabela.columns:
125
+ self.problems.append(Problem(
126
+ rel, getattr(filho, 'source_line', None),
127
+ f'<q:action name="{filho.name}" columns=...>: table "{filho.table}" '
128
+ f'has no column "{coluna}"'))
129
+ if isinstance(filho, QueryNode):
130
+ ds = filho.datasource
131
+ if not ds or ds.startswith('knowledge:') or getattr(filho, 'source', None):
132
+ pass # RAG or query of queries: not SQL on a database
133
+ elif ds not in self.datasources:
134
+ self.problems.append(Problem(rel, getattr(filho, 'source_line', None),
135
+ f'<q:query name="{filho.name}">: datasource {ds!r} is not '
136
+ f'declared in quantum.config.yaml'))
137
+ else:
138
+ conexao = self._connection(ds)
139
+ if conexao is not None:
140
+ self.queries_checked += 1
141
+ erro, cols = check_sql(conexao, filho.sql or '')
142
+ if erro:
143
+ self.problems.append(Problem(rel, getattr(filho, 'source_line', None),
144
+ f'<q:query name="{filho.name}">: {erro}'))
145
+ elif cols is not None:
146
+ colunas[filho.name] = cols
147
+ self._queries(filho, rel, colunas)
148
+
149
+ def _fields(self, node, rel: str, colunas: Dict[str, List[str]], ligados: Dict[str, str],
150
+ linha: Optional[int]) -> None:
151
+ """Each `{var.field}` where var is a query, or a row of one, names a column it returns."""
152
+ from quantum.core.features.loops.src.ast_node import LoopNode
153
+ from quantum.core.features.ui_engine.src.ast_nodes import UIColumnNode, UIListNode, UITableNode
154
+ linha = getattr(node, 'source_line', None) or linha
155
+ for texto in _texts(node):
156
+ for expressao in _EXPR.findall(texto):
157
+ for var, campo in re.findall(r'(?<![\w.])(\w+)\.(\w+)', expressao):
158
+ self._field(rel, linha, colunas, ligados, var, campo)
159
+ if isinstance(node, UIColumnNode) and node.key and '$tabela' in ligados:
160
+ self._field(rel, linha, colunas, ligados, '$tabela', node.key, f'<ui:column key="{node.key}">')
161
+
162
+ for filho in _children(node):
163
+ dentro = dict(ligados)
164
+ origem = None
165
+ if isinstance(filho, LoopNode):
166
+ origem = filho.query_name if filho.loop_type == 'query' else _single_name(filho.items)
167
+ if origem in colunas:
168
+ dentro[filho.var_name] = origem
169
+ elif isinstance(filho, (UITableNode, UIListNode)):
170
+ origem = _single_name(filho.source)
171
+ if origem in colunas:
172
+ dentro[filho.as_var or ('row' if isinstance(filho, UITableNode) else 'item')] = origem
173
+ if isinstance(filho, UITableNode):
174
+ dentro['$tabela'] = origem
175
+ self._fields(filho, rel, colunas, dentro, linha)
176
+
177
+ def _field(self, rel, linha, colunas, ligados, var, campo, como=None) -> None:
178
+ consulta = ligados.get(var)
179
+ if consulta is None or campo in _META:
180
+ return
181
+ cols = colunas.get(consulta, [])
182
+ if campo not in cols:
183
+ quem = como or f'{{{var}.{campo}}}'
184
+ self.problems.append(Problem(
185
+ rel, linha, f'{quem}: query "{consulta}" returns no column "{campo}" '
186
+ f'(columns: {", ".join(cols) or "none"})'))
187
+
188
+ # -- the project ----------------------------------------------------------
189
+
190
+ def run(self) -> List[Problem]:
191
+ pasta = self.project / (self.config.get('paths', {}) or {}).get('components', 'components')
192
+ if not pasta.exists():
193
+ self.notes.append(f'no components folder at {pasta}')
194
+ return self.problems
195
+ for arquivo in sorted(pasta.rglob('*.q')):
196
+ self.check_file(arquivo)
197
+ for conexao in self._conexoes.values():
198
+ if conexao is not None:
199
+ conexao.close()
200
+ return self.problems
201
+
202
+
203
+ def _single_name(expr: Optional[str]) -> Optional[str]:
204
+ """`{tarefas}` -> 'tarefas'; anything more complex -> None."""
205
+ m = re.fullmatch(r'\s*\{\s*(\w+)\s*\}\s*', expr or '')
206
+ return m.group(1) if m else None
207
+
208
+
209
+ def run_check(config_path: str = 'quantum.config.yaml') -> int:
210
+ """`quantum check`: print each problem as file:line: message; exit 1 if there is any."""
211
+ caminho = Path(config_path).resolve()
212
+ checker = ProjectChecker(caminho.parent, caminho)
213
+ problemas = checker.run()
214
+ for nota in checker.notes:
215
+ print(f'[NOTE] {nota}')
216
+ for problema in problemas:
217
+ print(problema)
218
+ resumo = (f'{checker.files} file(s), {checker.queries_checked} query(ies) checked against the database')
219
+ if problemas:
220
+ print(f'\n{len(problemas)} problem(s) — {resumo}.')
221
+ return 1
222
+ print(f'OK — {resumo}.')
223
+ return 0
@@ -0,0 +1,15 @@
1
+ """
2
+ Quantum CLI Commands
3
+
4
+ Each command module provides a click command group or command.
5
+ """
6
+
7
+ from quantum.cli.commands.new import new
8
+ from quantum.cli.commands.dev import dev
9
+ from quantum.cli.commands.build import build
10
+ from quantum.cli.commands.serve import serve
11
+ from quantum.cli.commands.test import test
12
+ from quantum.cli.commands.lint import lint
13
+ from quantum.cli.commands.docs import docs
14
+
15
+ __all__ = ['new', 'dev', 'build', 'serve', 'test', 'lint', 'docs']