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,419 @@
1
+ """
2
+ Pydantic schemas for request/response validation
3
+ """
4
+ from pydantic import BaseModel, Field
5
+ from typing import Optional, List
6
+ from datetime import datetime
7
+
8
+
9
+ # ============================================================================
10
+ # PROJECT SCHEMAS
11
+ # ============================================================================
12
+
13
+ class ProjectBase(BaseModel):
14
+ name: str = Field(..., min_length=1, max_length=255)
15
+ description: Optional[str] = None
16
+
17
+
18
+ class ProjectCreate(ProjectBase):
19
+ source_path: Optional[str] = None
20
+ git_url: Optional[str] = None
21
+ git_branch: Optional[str] = "main"
22
+
23
+
24
+ class ProjectUpdate(BaseModel):
25
+ name: Optional[str] = Field(None, min_length=1, max_length=255)
26
+ description: Optional[str] = None
27
+ status: Optional[str] = Field(None, pattern="^(active|inactive)$")
28
+ source_path: Optional[str] = None
29
+ git_url: Optional[str] = None
30
+ git_branch: Optional[str] = None
31
+
32
+
33
+ class ProjectResponse(ProjectBase):
34
+ id: int
35
+ status: str
36
+ source_path: Optional[str] = None
37
+ git_url: Optional[str] = None
38
+ git_branch: Optional[str] = None
39
+ created_at: datetime
40
+ updated_at: datetime
41
+
42
+ class Config:
43
+ from_attributes = True # Enables ORM mode
44
+
45
+
46
+ # ============================================================================
47
+ # DATASOURCE SCHEMAS
48
+ # ============================================================================
49
+
50
+ class DatasourceBase(BaseModel):
51
+ name: str = Field(..., min_length=1, max_length=255)
52
+ type: str = Field(..., pattern="^(postgres|mysql|mongodb)$")
53
+ connection_type: str = Field(..., pattern="^(docker|direct)$")
54
+
55
+
56
+ class DatasourceCreate(DatasourceBase):
57
+ # Docker fields
58
+ image: Optional[str] = None
59
+ port: Optional[int] = None
60
+
61
+ # Direct connection fields
62
+ host: Optional[str] = None
63
+ database_name: Optional[str] = None
64
+ username: Optional[str] = None
65
+ password: Optional[str] = None # Will be encrypted before storage
66
+
67
+ auto_start: bool = False
68
+
69
+
70
+ class DatasourceUpdate(BaseModel):
71
+ name: Optional[str] = Field(None, min_length=1, max_length=255)
72
+ status: Optional[str] = Field(None, pattern="^(running|stopped|error)$")
73
+ health_status: Optional[str] = Field(None, pattern="^(healthy|unhealthy|unknown)$")
74
+ auto_start: Optional[bool] = None
75
+
76
+ # Dados de conexão. Este schema existia sem estes campos e sem NENHUMA
77
+ # rota que o usasse: dava para criar e excluir um datasource, nunca
78
+ # corrigi-lo. Errar o host ou a senha significava excluir e recriar — o
79
+ # buraco mais visível do núcleo do admin (ADMIN_STATUS.md).
80
+ host: Optional[str] = None
81
+ port: Optional[int] = Field(None, ge=1, le=65535)
82
+ database_name: Optional[str] = None
83
+ username: Optional[str] = None
84
+ password: Optional[str] = None # cifrada antes de gravar
85
+
86
+
87
+ class DatasourceResponse(DatasourceBase):
88
+ id: int
89
+ project_id: int
90
+ container_id: Optional[str]
91
+ image: Optional[str]
92
+ port: Optional[int]
93
+ host: Optional[str]
94
+ database_name: Optional[str]
95
+ username: Optional[str]
96
+ status: str
97
+ health_status: str
98
+ setup_status: str
99
+ auto_start: bool
100
+ created_at: datetime
101
+ updated_at: datetime
102
+
103
+ class Config:
104
+ from_attributes = True
105
+
106
+
107
+ # ============================================================================
108
+ # COMPONENT SCHEMAS
109
+ # ============================================================================
110
+
111
+ class ComponentResponse(BaseModel):
112
+ id: int
113
+ project_id: int
114
+ name: str
115
+ file_path: str
116
+ status: str
117
+ error_message: Optional[str]
118
+ last_compiled: Optional[datetime]
119
+
120
+ # Metadata counters
121
+ function_count: int = 0
122
+ endpoint_count: int = 0
123
+ query_count: int = 0
124
+
125
+ # Test tracking
126
+ test_count: int = 0
127
+ tests_passing: int = 0
128
+ tests_failing: int = 0
129
+ last_test_run_at: Optional[datetime] = None
130
+
131
+ class Config:
132
+ from_attributes = True
133
+
134
+ def get_has_tests(self) -> bool:
135
+ """Check if component has tests"""
136
+ return self.test_count > 0
137
+
138
+ def get_test_coverage(self) -> int:
139
+ """Calculate test coverage percentage"""
140
+ if self.test_count == 0:
141
+ return 0
142
+ return int((self.tests_passing / self.test_count) * 100)
143
+
144
+
145
+ # ============================================================================
146
+ # COMPONENT TEST SCHEMAS
147
+ # ============================================================================
148
+
149
+ class ComponentTestCreate(BaseModel):
150
+ """Schema for creating a component test record"""
151
+ component_id: int
152
+ test_file: str = Field(..., min_length=1, max_length=512)
153
+ test_name: str = Field(..., min_length=1, max_length=255)
154
+ test_type: str = Field(default="unit", pattern="^(unit|integration|api|e2e)$")
155
+ generated_by: Optional[str] = Field(default="auto", pattern="^(auto|manual)$")
156
+
157
+
158
+ class ComponentTestUpdate(BaseModel):
159
+ """Schema for updating a component test record"""
160
+ last_status: Optional[str] = Field(None, pattern="^(passed|failed|skipped|pending|error)$")
161
+ last_run_at: Optional[datetime] = None
162
+ last_duration_seconds: Optional[int] = None
163
+ last_error_message: Optional[str] = None
164
+
165
+
166
+ class ComponentTestResponse(BaseModel):
167
+ """Schema for component test response"""
168
+ id: int
169
+ component_id: int
170
+ test_file: str
171
+ test_name: str
172
+ test_type: str
173
+ generated_at: Optional[datetime]
174
+ generated_by: Optional[str]
175
+ last_status: Optional[str]
176
+ last_run_at: Optional[datetime]
177
+ last_duration_seconds: Optional[int]
178
+ last_error_message: Optional[str]
179
+ created_at: datetime
180
+ updated_at: datetime
181
+
182
+ class Config:
183
+ from_attributes = True
184
+
185
+
186
+ class GenerateTestsRequest(BaseModel):
187
+ """Schema for test generation request"""
188
+ test_types: List[str] = Field(default=["unit", "api"], description="Types of tests to generate")
189
+ output_dir: Optional[str] = Field(default="tests/", description="Output directory for test files")
190
+
191
+
192
+ class GenerateTestsResponse(BaseModel):
193
+ """Schema for test generation response"""
194
+ component_id: int
195
+ component_name: str
196
+ generated: int
197
+ tests: List[dict]
198
+
199
+
200
+ class RunComponentTestsRequest(BaseModel):
201
+ """Schema for running component tests"""
202
+ test_types: Optional[List[str]] = Field(default=None, description="Optional filter by test types")
203
+ verbose: bool = Field(default=False, description="Verbose output")
204
+
205
+
206
+ class RunComponentTestsResponse(BaseModel):
207
+ """Schema for component tests run response"""
208
+ test_run_id: int
209
+ component_id: int
210
+ status: str
211
+ message: str
212
+
213
+
214
+ # ============================================================================
215
+ # ENDPOINT SCHEMAS
216
+ # ============================================================================
217
+
218
+ class EndpointResponse(BaseModel):
219
+ id: int
220
+ project_id: int
221
+ component_id: Optional[int]
222
+ method: str
223
+ path: str
224
+ function_name: str
225
+ description: Optional[str]
226
+
227
+ class Config:
228
+ from_attributes = True
229
+
230
+
231
+ # ============================================================================
232
+ # DETAILED RESPONSES (with nested data)
233
+ # ============================================================================
234
+
235
+ class ProjectDetailResponse(ProjectResponse):
236
+ """Project with all related data"""
237
+ datasources: List[DatasourceResponse] = []
238
+ components: List[ComponentResponse] = []
239
+ endpoints: List[EndpointResponse] = []
240
+
241
+ class Config:
242
+ from_attributes = True
243
+
244
+
245
+ # ============================================================================
246
+ # TEST EXECUTION SCHEMAS
247
+ # ============================================================================
248
+
249
+ class TestRunCreate(BaseModel):
250
+ """Schema for creating a new test run"""
251
+ suite_filter: Optional[str] = Field(None, description="Optional suite name to run (e.g., 'phase1', 'phase2')")
252
+ verbose: bool = Field(False, description="Enable verbose output")
253
+ stop_on_fail: bool = Field(False, description="Stop execution on first failure")
254
+ triggered_by: str = Field("manual", description="Who/what triggered the test (manual, ci/cd, schedule)")
255
+
256
+
257
+ class TestResultResponse(BaseModel):
258
+ """Schema for test result response"""
259
+ id: int
260
+ test_run_id: int
261
+ suite_name: str
262
+ test_file: str
263
+ status: str
264
+ duration_seconds: Optional[int]
265
+ error_message: Optional[str]
266
+ output: Optional[str]
267
+
268
+ class Config:
269
+ from_attributes = True
270
+
271
+
272
+ class TestRunResponse(BaseModel):
273
+ """Schema for test run response"""
274
+ id: int
275
+ project_id: int
276
+ status: str
277
+ started_at: datetime
278
+ completed_at: Optional[datetime]
279
+ total_tests: int
280
+ passed_tests: int
281
+ failed_tests: int
282
+ duration_seconds: Optional[int]
283
+ triggered_by: Optional[str]
284
+ suite_filter: Optional[str]
285
+ error_message: Optional[str]
286
+
287
+ class Config:
288
+ from_attributes = True
289
+
290
+
291
+ class TestRunDetailResponse(TestRunResponse):
292
+ """Test run with all test results"""
293
+ test_results: List[TestResultResponse] = []
294
+
295
+ class Config:
296
+ from_attributes = True
297
+
298
+
299
+ class TestRunStatusResponse(BaseModel):
300
+ """Schema for real-time test run status"""
301
+ id: int
302
+ status: str
303
+ total_tests: int
304
+ passed_tests: int
305
+ failed_tests: int
306
+ current_suite: Optional[str]
307
+ progress_percentage: Optional[float]
308
+ estimated_time_remaining: Optional[int]
309
+
310
+ class Config:
311
+ from_attributes = True
312
+
313
+
314
+ # ============================================================================
315
+ # CONFIGURATION MANAGEMENT SCHEMAS
316
+ # ============================================================================
317
+
318
+ class EnvironmentVariableCreate(BaseModel):
319
+ """Schema for creating an environment variable"""
320
+ key: str = Field(..., min_length=1, max_length=255)
321
+ value: str = Field(..., description="Plain text value (will be encrypted)")
322
+ description: Optional[str] = None
323
+ is_secret: bool = Field(False, description="If true, value is masked in responses")
324
+
325
+
326
+ class EnvironmentVariableUpdate(BaseModel):
327
+ """Schema for updating an environment variable"""
328
+ value: Optional[str] = Field(None, description="New plain text value (will be encrypted)")
329
+ description: Optional[str] = None
330
+ is_secret: Optional[bool] = None
331
+
332
+
333
+ class EnvironmentVariableResponse(BaseModel):
334
+ """Schema for environment variable response"""
335
+ id: int
336
+ project_id: int
337
+ key: str
338
+ value_masked: str = Field(..., description="Masked value for display (e.g., ****xyz)")
339
+ description: Optional[str]
340
+ is_secret: bool
341
+ created_at: datetime
342
+ updated_at: datetime
343
+
344
+ class Config:
345
+ from_attributes = True
346
+
347
+
348
+ class ConfigurationHistoryResponse(BaseModel):
349
+ """Schema for configuration history response"""
350
+ id: int
351
+ project_id: int
352
+ version: int
353
+ changed_at: datetime
354
+ changed_by: Optional[str]
355
+ changes_json: Optional[str]
356
+
357
+ class Config:
358
+ from_attributes = True
359
+
360
+
361
+ # ============================================================================
362
+ # DEPLOYMENT AUTOMATION SCHEMAS
363
+ # ============================================================================
364
+
365
+ class DeploymentTargetCreate(BaseModel):
366
+ """Schema for creating a deployment target"""
367
+ name: str = Field(..., min_length=1, max_length=255)
368
+ type: str = Field(..., pattern="^(local|docker|ssh|kubernetes)$")
369
+ config_json: Optional[str] = Field(None, description="JSON configuration for target")
370
+ is_active: bool = True
371
+
372
+
373
+ class DeploymentTargetResponse(BaseModel):
374
+ """Schema for deployment target response"""
375
+ id: int
376
+ project_id: int
377
+ name: str
378
+ type: str
379
+ config_json: Optional[str]
380
+ is_active: bool
381
+ created_at: datetime
382
+ updated_at: datetime
383
+
384
+ class Config:
385
+ from_attributes = True
386
+
387
+
388
+ class DeploymentCreate(BaseModel):
389
+ """Schema for creating a deployment"""
390
+ target_id: int
391
+ version: Optional[str] = Field(None, description="Git commit, tag, or version")
392
+ triggered_by: str = Field("manual", description="Who/what triggered deployment")
393
+
394
+
395
+ class DeploymentResponse(BaseModel):
396
+ """Schema for deployment response"""
397
+ id: int
398
+ project_id: int
399
+ target_id: int
400
+ version: Optional[str]
401
+ status: str
402
+ started_at: datetime
403
+ completed_at: Optional[datetime]
404
+ duration_seconds: Optional[int]
405
+ triggered_by: Optional[str]
406
+ error_message: Optional[str]
407
+
408
+ class Config:
409
+ from_attributes = True
410
+
411
+
412
+ class DeploymentDetailResponse(DeploymentResponse):
413
+ """Schema for deployment response with logs"""
414
+ build_log: Optional[str]
415
+ deploy_log: Optional[str]
416
+ rollback_from: Optional[int]
417
+
418
+ class Config:
419
+ from_attributes = True
@@ -0,0 +1,277 @@
1
+ """
2
+ Secret Manager for Quantum Admin
3
+ Handles encryption and decryption of sensitive data using Fernet symmetric encryption
4
+ """
5
+ import os
6
+ import base64
7
+ from cryptography.fernet import Fernet
8
+ from cryptography.hazmat.primitives import hashes
9
+ from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
10
+ from cryptography.hazmat.backends import default_backend
11
+ import logging
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class SecretManager:
17
+ """
18
+ Manages encryption and decryption of secrets using Fernet symmetric encryption
19
+
20
+ The encryption key is derived from environment variable QUANTUM_ENCRYPTION_KEY.
21
+ If not set, a default key is generated (NOT secure for production).
22
+ """
23
+
24
+ _instance = None
25
+ _cipher = None
26
+
27
+ def __new__(cls):
28
+ """Singleton pattern to ensure only one instance exists"""
29
+ if cls._instance is None:
30
+ cls._instance = super(SecretManager, cls).__new__(cls)
31
+ cls._instance._initialize_cipher()
32
+ return cls._instance
33
+
34
+ def _initialize_cipher(self):
35
+ """Initialize Fernet cipher with encryption key from environment"""
36
+ encryption_key = os.getenv('QUANTUM_ENCRYPTION_KEY')
37
+
38
+ # Fail-closed em producao. A chave padrao abaixo e DERIVADA DE LITERAIS
39
+ # NO FONTE (senha fixa + salt fixo, PBKDF2 100k): qualquer um com o
40
+ # codigo reconstroi a chave e decifra tudo que foi cifrado com ela. E
41
+ # o admin grava esses valores cifrados em quantum_admin/settings/*.yaml,
42
+ # que ja foi versionado — ou seja, o default re-arma o vazamento a
43
+ # cada save. Em producao isso nao pode ser um aviso que ninguem le;
44
+ # o servico se recusa a subir, espelhando auth_service._load_config.
45
+ if not encryption_key:
46
+ estrito = os.environ.get('QUANTUM_ADMIN_ENV', '').lower() in (
47
+ 'production', 'prod')
48
+ if estrito:
49
+ raise RuntimeError(
50
+ "QUANTUM_ADMIN_ENV=production requires QUANTUM_ENCRYPTION_KEY. "
51
+ "The built-in default key is derived from source literals and "
52
+ "provides no confidentiality; refusing to start. Generate one "
53
+ "with: python -m quantum_admin.backend.secret_manager"
54
+ )
55
+
56
+ try:
57
+ if encryption_key:
58
+ # Use provided key
59
+ key = encryption_key.encode()
60
+ logger.info("✅ Using encryption key from QUANTUM_ENCRYPTION_KEY environment variable")
61
+ else:
62
+ # Generate default key (NOT secure for production!)
63
+ logger.warning("⚠️ QUANTUM_ENCRYPTION_KEY not set! Using default key (NOT SECURE FOR PRODUCTION!)")
64
+ logger.warning("⚠️ Set QUANTUM_ENCRYPTION_KEY environment variable for production use")
65
+
66
+ # Use a deterministic default key derived from a fixed password
67
+ # This allows decryption after restart but is NOT secure
68
+ password = b"quantum-admin-default-key-change-me-in-production"
69
+ salt = b"quantum-salt" # Fixed salt for deterministic key
70
+ kdf = PBKDF2HMAC(
71
+ algorithm=hashes.SHA256(),
72
+ length=32,
73
+ salt=salt,
74
+ iterations=100000,
75
+ backend=default_backend()
76
+ )
77
+ key = base64.urlsafe_b64encode(kdf.derive(password))
78
+
79
+ self._cipher = Fernet(key)
80
+ logger.info("🔐 Secret manager initialized successfully")
81
+
82
+ except Exception as e:
83
+ logger.error(f"❌ Failed to initialize secret manager: {e}")
84
+ raise RuntimeError(f"Secret manager initialization failed: {e}")
85
+
86
+ def encrypt(self, plain_text: str) -> str:
87
+ """
88
+ Encrypt a plain text string
89
+
90
+ Args:
91
+ plain_text: The plain text to encrypt
92
+
93
+ Returns:
94
+ str: Base64-encoded encrypted string
95
+
96
+ Raises:
97
+ ValueError: If encryption fails
98
+ """
99
+ if not plain_text:
100
+ raise ValueError("Cannot encrypt empty string")
101
+
102
+ try:
103
+ encrypted_bytes = self._cipher.encrypt(plain_text.encode('utf-8'))
104
+ return encrypted_bytes.decode('utf-8')
105
+ except Exception as e:
106
+ logger.error(f"❌ Encryption failed: {e}")
107
+ raise ValueError(f"Encryption failed: {e}")
108
+
109
+ def decrypt(self, encrypted_text: str) -> str:
110
+ """
111
+ Decrypt an encrypted string
112
+
113
+ Args:
114
+ encrypted_text: The encrypted text (base64-encoded)
115
+
116
+ Returns:
117
+ str: Decrypted plain text
118
+
119
+ Raises:
120
+ ValueError: If decryption fails
121
+ """
122
+ if not encrypted_text:
123
+ raise ValueError("Cannot decrypt empty string")
124
+
125
+ try:
126
+ decrypted_bytes = self._cipher.decrypt(encrypted_text.encode('utf-8'))
127
+ return decrypted_bytes.decode('utf-8')
128
+ except Exception as e:
129
+ logger.error(f"❌ Decryption failed: {e}")
130
+ raise ValueError(f"Decryption failed: {e}")
131
+
132
+ def mask_value(self, value: str, show_chars: int = 4) -> str:
133
+ """
134
+ Mask a sensitive value for display purposes
135
+
136
+ Args:
137
+ value: The value to mask
138
+ show_chars: Number of characters to show at the end
139
+
140
+ Returns:
141
+ str: Masked value (e.g., "****xyz")
142
+
143
+ Example:
144
+ >>> manager = SecretManager()
145
+ >>> manager.mask_value("my-secret-password")
146
+ '****word'
147
+ """
148
+ if not value:
149
+ return ""
150
+
151
+ if len(value) <= show_chars:
152
+ return "*" * len(value)
153
+
154
+ masked_length = len(value) - show_chars
155
+ return ("*" * min(masked_length, 8)) + value[-show_chars:]
156
+
157
+ @staticmethod
158
+ def generate_key() -> str:
159
+ """
160
+ Generate a new Fernet encryption key
161
+
162
+ Returns:
163
+ str: Base64-encoded encryption key
164
+
165
+ Example:
166
+ >>> key = SecretManager.generate_key()
167
+ >>> print(f"Set this as environment variable:")
168
+ >>> print(f"export QUANTUM_ENCRYPTION_KEY={key}")
169
+ """
170
+ return Fernet.generate_key().decode('utf-8')
171
+
172
+
173
+ # Singleton instance
174
+ secret_manager = SecretManager()
175
+
176
+
177
+ # Convenience functions
178
+ def encrypt_value(plain_text: str) -> str:
179
+ """Encrypt a value using the global secret manager"""
180
+ return secret_manager.encrypt(plain_text)
181
+
182
+
183
+ def decrypt_value(encrypted_text: str) -> str:
184
+ """Decrypt a value using the global secret manager"""
185
+ return secret_manager.decrypt(encrypted_text)
186
+
187
+
188
+ def mask_value(value: str, show_chars: int = 4) -> str:
189
+ """Mask a value for display using the global secret manager"""
190
+ return secret_manager.mask_value(value, show_chars)
191
+
192
+
193
+ def _looks_like_fernet_token(stored: str) -> bool:
194
+ """Is this a Fernet token — regardless of whether OUR key opens it?
195
+
196
+ A Fernet token is urlsafe-base64 over `0x80 | 8-byte timestamp | 16-byte
197
+ IV | ciphertext | 32-byte HMAC`, so the shortest possible one is 57
198
+ bytes and every one of them starts with the version byte 0x80. Plain
199
+ text that happens to be valid base64 (`user:pass`, a JSON blob) does
200
+ not.
201
+ """
202
+ try:
203
+ raw = base64.urlsafe_b64decode(stored.encode('ascii'))
204
+ except Exception:
205
+ return False
206
+ return len(raw) >= 57 and raw[0] == 0x80
207
+
208
+
209
+ class DecryptionKeyMismatch(ValueError):
210
+ """The stored value IS encrypted and the current key does not open it."""
211
+
212
+
213
+ def decrypt_or_legacy(stored: str) -> str:
214
+ """Decrypt a value, tolerating one written before it was encrypted.
215
+
216
+ Cloud integration credentials were saved as plain JSON into a column
217
+ named credentials_encrypted, with a `# TODO: encrypt properly` beside it.
218
+ Rows written back then are still plain text, and refusing to read them
219
+ would lose configuration people already have.
220
+
221
+ So: decrypt when it decrypts, and when it does not, hand back what is
222
+ stored and say why. The next write encrypts it.
223
+
224
+ MAS so quando o guardado nao e um token cifrado.
225
+
226
+ O `except Exception: return stored` valia para os dois motivos de falha,
227
+ e o segundo e desastroso. Se QUANTUM_ENCRYPTION_KEY mudar — trocada,
228
+ perdida, ou simplesmente ausente num deploy novo, quando o modulo cai na
229
+ chave padrao — a decifragem falha com InvalidToken e o texto CIFRADO era
230
+ devolvido como se fosse a senha. Dai:
231
+
232
+ * a conexao com o banco tentava autenticar com o token base64 como
233
+ senha, e o erro que aparecia era "senha invalida", apontando para o
234
+ lugar errado;
235
+ * pior, o proximo save re-cifrava esse token com a chave NOVA. A senha
236
+ de verdade, que so a chave antiga abria, ia embora de vez — e nada
237
+ no caminho dizia que algo tinha se perdido.
238
+
239
+ Um segredo que nao abre e um erro, nao um valor. So texto que nunca foi
240
+ cifrado passa pelo caminho legado.
241
+ """
242
+ if not stored:
243
+ return stored
244
+ try:
245
+ return secret_manager.decrypt(stored)
246
+ except Exception as exc:
247
+ if _looks_like_fernet_token(stored):
248
+ logger.error(
249
+ "a stored secret is encrypted but the current "
250
+ "QUANTUM_ENCRYPTION_KEY does not decrypt it — the key has "
251
+ "changed or is missing. Restore the original key; saving "
252
+ "over this value would destroy the secret permanently."
253
+ )
254
+ raise DecryptionKeyMismatch(
255
+ "cannot decrypt a stored secret with the current "
256
+ "QUANTUM_ENCRYPTION_KEY (the key changed or is not set). "
257
+ "Restore the key that encrypted it — re-saving this record "
258
+ "would overwrite the secret irrecoverably."
259
+ ) from exc
260
+ logger.warning(
261
+ "reading a credential that was stored unencrypted (written "
262
+ "before encryption was wired up); it will be encrypted on the "
263
+ "next save"
264
+ )
265
+ return stored
266
+
267
+
268
+ if __name__ == "__main__":
269
+ # Generate a new encryption key
270
+ print("="*70)
271
+ print("QUANTUM ADMIN - ENCRYPTION KEY GENERATOR")
272
+ print("="*70)
273
+ print("\nGenerate a new encryption key for production use:\n")
274
+ key = SecretManager.generate_key()
275
+ print(f"export QUANTUM_ENCRYPTION_KEY='{key}'")
276
+ print("\nAdd this to your environment variables or .env file")
277
+ print("="*70)