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
@@ -0,0 +1,488 @@
1
+ """
2
+ Settings Service for Quantum Admin
3
+ Manages global and project-specific settings with YAML persistence
4
+ """
5
+ import os
6
+ import yaml
7
+ import logging
8
+ from pathlib import Path
9
+ from typing import Any, Dict, Optional
10
+ from dataclasses import dataclass, field, asdict
11
+ from datetime import datetime
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ # Base paths
16
+ # QUANTUM_ADMIN_SETTINGS_DIR moves these files, as QUANTUM_ADMIN_DATABASE_URL
17
+ # moves the database: the test suite points both at a temporary directory
18
+ # before importing the backend, so no test run writes the owner's settings.
19
+ SETTINGS_DIR = Path(os.environ.get("QUANTUM_ADMIN_SETTINGS_DIR") or Path(__file__).parent.parent / "settings")
20
+ GLOBAL_SETTINGS_FILE = SETTINGS_DIR / "global.yaml"
21
+ PROJECTS_SETTINGS_DIR = SETTINGS_DIR / "projects"
22
+
23
+
24
+ @dataclass
25
+ class ServerSettings:
26
+ port: int = 8001
27
+ host: str = "0.0.0.0"
28
+ debug: bool = False
29
+ reload: bool = True
30
+
31
+
32
+ @dataclass
33
+ class DockerSettings:
34
+ host: str = "" # Empty = local, or ssh://user@host
35
+ registry: str = ""
36
+ timeout: int = 30
37
+
38
+
39
+ @dataclass
40
+ class EmailSettings:
41
+ enabled: bool = False
42
+ provider: str = "smtp"
43
+ host: str = ""
44
+ port: int = 587
45
+ use_tls: bool = True
46
+ username: str = ""
47
+ password: str = ""
48
+ from_address: str = "noreply@quantum.local"
49
+
50
+
51
+ @dataclass
52
+ class SecuritySettings:
53
+ # There was a `secret_key: str = "change-me-in-production"` here. Nothing
54
+ # in the admin ever read it — the JWT signing key comes from
55
+ # JWT_SECRET_KEY in auth_service — but _create_default_global_settings
56
+ # wrote it into the settings file on disk, where it looked like the place
57
+ # to set a secret. A decoy credential is worse than none: someone sets it,
58
+ # believes the admin is configured, and it signs nothing. Removed; set
59
+ # JWT_SECRET_KEY instead.
60
+ jwt_algorithm: str = "HS256"
61
+ jwt_expiry_hours: int = 24
62
+ # Loopback by default. "*" here plus allow_credentials let any website
63
+ # call the admin API.
64
+ allowed_origins: list = field(default_factory=lambda: [
65
+ "http://localhost:8000", "http://127.0.0.1:8000",
66
+ ])
67
+ rate_limit_enabled: bool = False
68
+ rate_limit_requests: int = 100
69
+ rate_limit_window: int = 60
70
+
71
+
72
+ @dataclass
73
+ class DeployEnvironment:
74
+ name: str = ""
75
+ host: str = ""
76
+ user: str = ""
77
+ ssh_key: str = "~/.ssh/id_ed25519"
78
+ deploy_path: str = ""
79
+
80
+
81
+ @dataclass
82
+ class DeploymentSettings:
83
+ default_dockerfile: str = "Dockerfile"
84
+ default_port: int = 8080
85
+ health_check_path: str = "/health"
86
+ health_check_timeout: int = 30
87
+ environments: list = field(default_factory=list)
88
+
89
+
90
+ @dataclass
91
+ class DatabaseSettings:
92
+ url: str = "sqlite:///quantum_admin.db"
93
+ echo: bool = False
94
+
95
+
96
+ @dataclass
97
+ class LoggingSettings:
98
+ level: str = "INFO"
99
+ format: str = "%(asctime)s [%(levelname)s] %(message)s"
100
+ file: str = "logs/quantum_admin.log"
101
+
102
+
103
+ @dataclass
104
+ class GlobalSettings:
105
+ server: ServerSettings = field(default_factory=ServerSettings)
106
+ docker: DockerSettings = field(default_factory=DockerSettings)
107
+ email: EmailSettings = field(default_factory=EmailSettings)
108
+ security: SecuritySettings = field(default_factory=SecuritySettings)
109
+ deployment: DeploymentSettings = field(default_factory=DeploymentSettings)
110
+ database: DatabaseSettings = field(default_factory=DatabaseSettings)
111
+ logging: LoggingSettings = field(default_factory=LoggingSettings)
112
+
113
+
114
+ @dataclass
115
+ class ProjectSettings:
116
+ name: str = ""
117
+ description: str = ""
118
+ default_datasource: str = ""
119
+ dockerfile: str = "Dockerfile"
120
+ port: int = 8080
121
+ env_vars: Dict[str, str] = field(default_factory=dict)
122
+ features: Dict[str, bool] = field(default_factory=lambda: {
123
+ "htmx": True,
124
+ "auth": False,
125
+ "email": False
126
+ })
127
+ deployment: Dict[str, str] = field(default_factory=dict)
128
+
129
+
130
+ class SettingsService:
131
+ """Service for managing application settings"""
132
+
133
+ def __init__(self):
134
+ self._ensure_directories()
135
+ self._settings_cache: Optional[Dict] = None
136
+ self._cache_time: Optional[datetime] = None
137
+ self._cache_ttl = 5 # seconds
138
+
139
+ def _ensure_directories(self):
140
+ """Ensure settings directories exist"""
141
+ SETTINGS_DIR.mkdir(parents=True, exist_ok=True)
142
+ PROJECTS_SETTINGS_DIR.mkdir(parents=True, exist_ok=True)
143
+
144
+ def _resolve_env_vars(self, value: Any) -> Any:
145
+ """Resolve ${ENV_VAR} placeholders in values"""
146
+ if isinstance(value, str) and value.startswith("${") and value.endswith("}"):
147
+ env_var = value[2:-1]
148
+ return os.environ.get(env_var, value)
149
+ elif isinstance(value, dict):
150
+ return {k: self._resolve_env_vars(v) for k, v in value.items()}
151
+ elif isinstance(value, list):
152
+ return [self._resolve_env_vars(v) for v in value]
153
+ return value
154
+
155
+ # =========================================================================
156
+ # Global Settings
157
+ # =========================================================================
158
+
159
+ def get_global_settings(self, resolve_env: bool = True) -> Dict:
160
+ """
161
+ Load global settings from YAML file
162
+
163
+ Args:
164
+ resolve_env: Whether to resolve ${ENV_VAR} placeholders
165
+
166
+ Returns:
167
+ Dictionary of settings
168
+ """
169
+ # Check cache
170
+ if self._settings_cache and self._cache_time:
171
+ age = (datetime.now() - self._cache_time).total_seconds()
172
+ if age < self._cache_ttl:
173
+ return self._settings_cache
174
+
175
+ if not GLOBAL_SETTINGS_FILE.exists():
176
+ logger.warning("Global settings file not found, creating default")
177
+ self._create_default_global_settings()
178
+
179
+ try:
180
+ with open(GLOBAL_SETTINGS_FILE, 'r', encoding='utf-8') as f:
181
+ settings = yaml.safe_load(f) or {}
182
+
183
+ if resolve_env:
184
+ settings = self._resolve_env_vars(settings)
185
+
186
+ # Update cache
187
+ self._settings_cache = settings
188
+ self._cache_time = datetime.now()
189
+
190
+ return settings
191
+ except Exception as e:
192
+ logger.error(f"Error loading global settings: {e}")
193
+ return {}
194
+
195
+ def update_global_settings(self, updates: Dict) -> Dict:
196
+ """
197
+ Update global settings (partial update)
198
+
199
+ Args:
200
+ updates: Dictionary of settings to update (can be nested)
201
+
202
+ Returns:
203
+ Updated settings dictionary
204
+ """
205
+ current = self.get_global_settings(resolve_env=False)
206
+ merged = self._deep_merge(current, updates)
207
+
208
+ try:
209
+ with open(GLOBAL_SETTINGS_FILE, 'w', encoding='utf-8') as f:
210
+ yaml.dump(merged, f, default_flow_style=False, allow_unicode=True)
211
+
212
+ # Invalidate cache
213
+ self._settings_cache = None
214
+ self._cache_time = None
215
+
216
+ logger.info("Global settings updated successfully")
217
+ return self.get_global_settings()
218
+ except Exception as e:
219
+ logger.error(f"Error saving global settings: {e}")
220
+ raise
221
+
222
+ def get_setting(self, path: str, default: Any = None) -> Any:
223
+ """
224
+ Get a specific setting by dot-notation path
225
+
226
+ Args:
227
+ path: Setting path (e.g., "server.port" or "docker.host")
228
+ default: Default value if not found
229
+
230
+ Returns:
231
+ Setting value or default
232
+ """
233
+ settings = self.get_global_settings()
234
+ keys = path.split(".")
235
+ value = settings
236
+
237
+ try:
238
+ for key in keys:
239
+ value = value[key]
240
+ return value
241
+ except (KeyError, TypeError):
242
+ return default
243
+
244
+ def set_setting(self, path: str, value: Any) -> Dict:
245
+ """
246
+ Set a specific setting by dot-notation path
247
+
248
+ Args:
249
+ path: Setting path (e.g., "server.port")
250
+ value: Value to set
251
+
252
+ Returns:
253
+ Updated settings
254
+ """
255
+ keys = path.split(".")
256
+ updates = {}
257
+ current = updates
258
+
259
+ for i, key in enumerate(keys[:-1]):
260
+ current[key] = {}
261
+ current = current[key]
262
+
263
+ current[keys[-1]] = value
264
+ return self.update_global_settings(updates)
265
+
266
+ def _create_default_global_settings(self):
267
+ """Create default global settings file"""
268
+ defaults = asdict(GlobalSettings())
269
+ with open(GLOBAL_SETTINGS_FILE, 'w', encoding='utf-8') as f:
270
+ yaml.dump(defaults, f, default_flow_style=False, allow_unicode=True)
271
+
272
+ # =========================================================================
273
+ # Project Settings
274
+ # =========================================================================
275
+
276
+ def get_project_settings(self, project_id: int) -> Dict:
277
+ """Load settings for a specific project"""
278
+ project_file = PROJECTS_SETTINGS_DIR / f"{project_id}.yaml"
279
+
280
+ if not project_file.exists():
281
+ return asdict(ProjectSettings())
282
+
283
+ try:
284
+ with open(project_file, 'r', encoding='utf-8') as f:
285
+ settings = yaml.safe_load(f) or {}
286
+ return self._resolve_env_vars(settings)
287
+ except Exception as e:
288
+ logger.error(f"Error loading project {project_id} settings: {e}")
289
+ return asdict(ProjectSettings())
290
+
291
+ def update_project_settings(self, project_id: int, updates: Dict) -> Dict:
292
+ """Update settings for a specific project"""
293
+ project_file = PROJECTS_SETTINGS_DIR / f"{project_id}.yaml"
294
+ current = self.get_project_settings(project_id)
295
+ merged = self._deep_merge(current, updates)
296
+
297
+ try:
298
+ with open(project_file, 'w', encoding='utf-8') as f:
299
+ yaml.dump(merged, f, default_flow_style=False, allow_unicode=True)
300
+
301
+ logger.info(f"Project {project_id} settings updated")
302
+ return self.get_project_settings(project_id)
303
+ except Exception as e:
304
+ logger.error(f"Error saving project {project_id} settings: {e}")
305
+ raise
306
+
307
+ def delete_project_settings(self, project_id: int) -> bool:
308
+ """Delete settings for a project"""
309
+ project_file = PROJECTS_SETTINGS_DIR / f"{project_id}.yaml"
310
+
311
+ if project_file.exists():
312
+ project_file.unlink()
313
+ logger.info(f"Project {project_id} settings deleted")
314
+ return True
315
+ return False
316
+
317
+ def list_project_settings(self) -> Dict[int, Dict]:
318
+ """List all project settings"""
319
+ result = {}
320
+ for file in PROJECTS_SETTINGS_DIR.glob("*.yaml"):
321
+ try:
322
+ project_id = int(file.stem)
323
+ result[project_id] = self.get_project_settings(project_id)
324
+ except ValueError:
325
+ continue
326
+ return result
327
+
328
+ # =========================================================================
329
+ # Utility Methods
330
+ # =========================================================================
331
+
332
+ def _deep_merge(self, base: Dict, updates: Dict) -> Dict:
333
+ """Deep merge two dictionaries"""
334
+ result = base.copy()
335
+
336
+ for key, value in updates.items():
337
+ if key in result and isinstance(result[key], dict) and isinstance(value, dict):
338
+ result[key] = self._deep_merge(result[key], value)
339
+ else:
340
+ result[key] = value
341
+
342
+ return result
343
+
344
+ # =========================================================================
345
+ # Connectors
346
+ # =========================================================================
347
+
348
+ def get_connectors(self) -> list:
349
+ """
350
+ Get list of available connectors (email, LLM, storage, etc.)
351
+
352
+ Returns:
353
+ List of connector dictionaries with name, type, provider, scope
354
+ """
355
+ # Define available connector types
356
+ connectors = [
357
+ {
358
+ "id": "email",
359
+ "name": "Email Service",
360
+ "type": "email",
361
+ "provider": self.get_setting("email.provider", "SMTP"),
362
+ "scope": "private", # Requires explicit access
363
+ "icon": "envelope"
364
+ },
365
+ {
366
+ "id": "llm_openai",
367
+ "name": "OpenAI API",
368
+ "type": "llm",
369
+ "provider": "OpenAI",
370
+ "scope": "private",
371
+ "icon": "robot"
372
+ },
373
+ {
374
+ "id": "llm_anthropic",
375
+ "name": "Anthropic API",
376
+ "type": "llm",
377
+ "provider": "Anthropic",
378
+ "scope": "private",
379
+ "icon": "brain"
380
+ },
381
+ {
382
+ "id": "storage_s3",
383
+ "name": "AWS S3",
384
+ "type": "storage",
385
+ "provider": "AWS",
386
+ "scope": "private",
387
+ "icon": "cloud"
388
+ },
389
+ {
390
+ "id": "storage_local",
391
+ "name": "Local Storage",
392
+ "type": "storage",
393
+ "provider": "Filesystem",
394
+ "scope": "public", # Available to all projects
395
+ "icon": "folder"
396
+ },
397
+ {
398
+ "id": "cache_redis",
399
+ "name": "Redis Cache",
400
+ "type": "cache",
401
+ "provider": "Redis",
402
+ "scope": "private",
403
+ "icon": "bolt"
404
+ },
405
+ {
406
+ "id": "queue_rabbitmq",
407
+ "name": "RabbitMQ",
408
+ "type": "queue",
409
+ "provider": "RabbitMQ",
410
+ "scope": "private",
411
+ "icon": "share"
412
+ },
413
+ {
414
+ "id": "webhook",
415
+ "name": "Webhooks",
416
+ "type": "integration",
417
+ "provider": "HTTP",
418
+ "scope": "public", # Available to all projects
419
+ "icon": "link"
420
+ }
421
+ ]
422
+
423
+ return connectors
424
+
425
+ def get_project_connectors(self, project_id: int) -> Dict[str, bool]:
426
+ """
427
+ Get which connectors are enabled for a specific project
428
+
429
+ Args:
430
+ project_id: Project ID
431
+
432
+ Returns:
433
+ Dictionary of connector_id -> enabled status
434
+ """
435
+ settings = self.get_project_settings(project_id)
436
+ return settings.get("connectors", {})
437
+
438
+ def update_project_connector(self, project_id: int, connector_id: str, enabled: bool) -> Dict:
439
+ """
440
+ Enable/disable a connector for a project
441
+
442
+ Args:
443
+ project_id: Project ID
444
+ connector_id: Connector ID (e.g., "email", "llm_openai")
445
+ enabled: Whether to enable the connector
446
+
447
+ Returns:
448
+ Updated project settings
449
+ """
450
+ settings = self.get_project_settings(project_id)
451
+ if "connectors" not in settings:
452
+ settings["connectors"] = {}
453
+ settings["connectors"][connector_id] = enabled
454
+ return self.update_project_settings(project_id, {"connectors": settings["connectors"]})
455
+
456
+ def export_settings(self) -> Dict:
457
+ """Export all settings (global + all projects)"""
458
+ return {
459
+ "global": self.get_global_settings(resolve_env=False),
460
+ "projects": self.list_project_settings()
461
+ }
462
+
463
+ def import_settings(self, data: Dict) -> bool:
464
+ """Import settings from export data"""
465
+ try:
466
+ if "global" in data:
467
+ self.update_global_settings(data["global"])
468
+
469
+ if "projects" in data:
470
+ for project_id, settings in data["projects"].items():
471
+ self.update_project_settings(int(project_id), settings)
472
+
473
+ return True
474
+ except Exception as e:
475
+ logger.error(f"Error importing settings: {e}")
476
+ return False
477
+
478
+
479
+ # Singleton instance
480
+ _settings_service = None
481
+
482
+
483
+ def get_settings_service() -> SettingsService:
484
+ """Get singleton instance of SettingsService"""
485
+ global _settings_service
486
+ if _settings_service is None:
487
+ _settings_service = SettingsService()
488
+ return _settings_service