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,418 @@
1
+ """
2
+ Environment Service for Quantum Admin
3
+ Handles deployment environment CRUD operations
4
+ """
5
+ import json
6
+ import logging
7
+ from typing import Optional, List, Dict, Any
8
+ from datetime import datetime
9
+
10
+ from sqlalchemy.orm import Session
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ try:
15
+ from .models import Environment, Project
16
+ from .database import get_db, SessionLocal
17
+ except ImportError:
18
+ from models import Environment, Project
19
+ from database import get_db, SessionLocal
20
+
21
+
22
+ class EnvironmentService:
23
+ """Service for managing deployment environments"""
24
+
25
+ def __init__(self, db: Session = None):
26
+ """
27
+ Initialize environment service
28
+
29
+ Args:
30
+ db: SQLAlchemy session (optional, creates new if not provided)
31
+ """
32
+ self._db = db
33
+ self._own_session = db is None
34
+
35
+ @property
36
+ def db(self) -> Session:
37
+ """Get database session"""
38
+ if self._db is None:
39
+ self._db = SessionLocal()
40
+ return self._db
41
+
42
+ def close(self):
43
+ """Close database session if we own it"""
44
+ if self._own_session and self._db:
45
+ self._db.close()
46
+ self._db = None
47
+
48
+ # =========================================================================
49
+ # CRUD Operations
50
+ # =========================================================================
51
+
52
+ def create_environment(self, project_id: int, data: Dict[str, Any]) -> Environment:
53
+ """
54
+ Create a new environment
55
+
56
+ Args:
57
+ project_id: Project ID
58
+ data: Environment data dict
59
+
60
+ Returns:
61
+ Created Environment object
62
+ """
63
+ # Verify project exists
64
+ project = self.db.query(Project).filter(Project.id == project_id).first()
65
+ if not project:
66
+ raise ValueError(f"Project {project_id} not found")
67
+
68
+ # Determine order if not specified
69
+ if "order" not in data:
70
+ max_order = self.db.query(Environment).filter(
71
+ Environment.project_id == project_id
72
+ ).count()
73
+ data["order"] = max_order + 1
74
+
75
+ # Handle env_vars
76
+ env_vars = data.pop("env_vars", None)
77
+ if env_vars and isinstance(env_vars, dict):
78
+ data["env_vars_json"] = json.dumps(env_vars)
79
+
80
+ env = Environment(
81
+ project_id=project_id,
82
+ name=data.get("name"),
83
+ display_name=data.get("display_name") or data.get("name", "").title(),
84
+ order=data.get("order", 1),
85
+ docker_host=data.get("docker_host"),
86
+ docker_registry=data.get("docker_registry"),
87
+ deploy_path=data.get("deploy_path"),
88
+ container_name=data.get("container_name"),
89
+ port=data.get("port"),
90
+ branch=data.get("branch", "main"),
91
+ auto_deploy=data.get("auto_deploy", False),
92
+ requires_approval=data.get("requires_approval", False),
93
+ health_url=data.get("health_url"),
94
+ health_timeout=data.get("health_timeout", 30),
95
+ env_vars_json=data.get("env_vars_json"),
96
+ is_active=data.get("is_active", True)
97
+ )
98
+
99
+ self.db.add(env)
100
+ self.db.commit()
101
+ self.db.refresh(env)
102
+
103
+ logger.info(f"Created environment: {env.name} for project {project_id}")
104
+ return env
105
+
106
+ def get_environment(self, env_id: int) -> Optional[Environment]:
107
+ """
108
+ Get environment by ID
109
+
110
+ Args:
111
+ env_id: Environment ID
112
+
113
+ Returns:
114
+ Environment or None
115
+ """
116
+ return self.db.query(Environment).filter(Environment.id == env_id).first()
117
+
118
+ def get_environment_by_name(self, project_id: int, name: str) -> Optional[Environment]:
119
+ """
120
+ Get environment by project and name
121
+
122
+ Args:
123
+ project_id: Project ID
124
+ name: Environment name (dev, staging, production)
125
+
126
+ Returns:
127
+ Environment or None
128
+ """
129
+ return self.db.query(Environment).filter(
130
+ Environment.project_id == project_id,
131
+ Environment.name == name
132
+ ).first()
133
+
134
+ def list_environments(self, project_id: int, active_only: bool = True) -> List[Environment]:
135
+ """
136
+ List all environments for a project
137
+
138
+ Args:
139
+ project_id: Project ID
140
+ active_only: Only return active environments
141
+
142
+ Returns:
143
+ List of environments ordered by order field
144
+ """
145
+ query = self.db.query(Environment).filter(Environment.project_id == project_id)
146
+
147
+ if active_only:
148
+ query = query.filter(Environment.is_active == True)
149
+
150
+ return query.order_by(Environment.order).all()
151
+
152
+ def update_environment(self, env_id: int, data: Dict[str, Any]) -> Optional[Environment]:
153
+ """
154
+ Update an environment
155
+
156
+ Args:
157
+ env_id: Environment ID
158
+ data: Fields to update
159
+
160
+ Returns:
161
+ Updated Environment or None
162
+ """
163
+ env = self.get_environment(env_id)
164
+ if not env:
165
+ return None
166
+
167
+ # Handle env_vars specially
168
+ env_vars = data.pop("env_vars", None)
169
+ if env_vars is not None:
170
+ if isinstance(env_vars, dict):
171
+ data["env_vars_json"] = json.dumps(env_vars)
172
+ elif isinstance(env_vars, str):
173
+ data["env_vars_json"] = env_vars
174
+
175
+ # Update allowed fields
176
+ allowed_fields = {
177
+ "name", "display_name", "order", "docker_host", "docker_registry",
178
+ "deploy_path", "container_name", "port", "branch", "auto_deploy",
179
+ "requires_approval", "approved_by", "health_url", "health_timeout",
180
+ "env_vars_json", "is_active"
181
+ }
182
+
183
+ for key, value in data.items():
184
+ if key in allowed_fields:
185
+ setattr(env, key, value)
186
+
187
+ env.updated_at = datetime.utcnow()
188
+ self.db.commit()
189
+ self.db.refresh(env)
190
+
191
+ logger.info(f"Updated environment: {env.name}")
192
+ return env
193
+
194
+ def delete_environment(self, env_id: int) -> bool:
195
+ """
196
+ Delete an environment
197
+
198
+ Args:
199
+ env_id: Environment ID
200
+
201
+ Returns:
202
+ True if deleted
203
+ """
204
+ env = self.get_environment(env_id)
205
+ if not env:
206
+ return False
207
+
208
+ self.db.delete(env)
209
+ self.db.commit()
210
+
211
+ logger.info(f"Deleted environment: {env.name}")
212
+ return True
213
+
214
+ # =========================================================================
215
+ # Environment Operations
216
+ # =========================================================================
217
+
218
+ def get_env_vars(self, env_id: int) -> Dict[str, str]:
219
+ """
220
+ Get environment variables for an environment
221
+
222
+ Args:
223
+ env_id: Environment ID
224
+
225
+ Returns:
226
+ Dict of environment variables
227
+ """
228
+ env = self.get_environment(env_id)
229
+ if not env or not env.env_vars_json:
230
+ return {}
231
+
232
+ try:
233
+ return json.loads(env.env_vars_json)
234
+ except json.JSONDecodeError:
235
+ return {}
236
+
237
+ def set_env_vars(self, env_id: int, env_vars: Dict[str, str]) -> bool:
238
+ """
239
+ Set environment variables
240
+
241
+ Args:
242
+ env_id: Environment ID
243
+ env_vars: Dict of environment variables
244
+
245
+ Returns:
246
+ True if successful
247
+ """
248
+ env = self.get_environment(env_id)
249
+ if not env:
250
+ return False
251
+
252
+ env.env_vars_json = json.dumps(env_vars)
253
+ env.updated_at = datetime.utcnow()
254
+ self.db.commit()
255
+ return True
256
+
257
+ def get_next_environment(self, env_id: int) -> Optional[Environment]:
258
+ """
259
+ Get the next environment in the promotion chain
260
+
261
+ Args:
262
+ env_id: Current environment ID
263
+
264
+ Returns:
265
+ Next environment or None if at the end
266
+ """
267
+ current = self.get_environment(env_id)
268
+ if not current:
269
+ return None
270
+
271
+ return self.db.query(Environment).filter(
272
+ Environment.project_id == current.project_id,
273
+ Environment.order > current.order,
274
+ Environment.is_active == True
275
+ ).order_by(Environment.order).first()
276
+
277
+ def find_environment_by_branch(self, project_id: int, branch: str) -> Optional[Environment]:
278
+ """
279
+ Find an environment configured for auto-deploy on a specific branch
280
+
281
+ Args:
282
+ project_id: Project ID
283
+ branch: Git branch name
284
+
285
+ Returns:
286
+ Environment configured for auto-deploy on this branch, or None
287
+ """
288
+ return self.db.query(Environment).filter(
289
+ Environment.project_id == project_id,
290
+ Environment.branch == branch,
291
+ Environment.auto_deploy == True,
292
+ Environment.is_active == True
293
+ ).first()
294
+
295
+ def approve_environment(self, env_id: int, approved_by: str) -> bool:
296
+ """
297
+ Record approval for an environment that requires it
298
+
299
+ Args:
300
+ env_id: Environment ID
301
+ approved_by: Username of approver
302
+
303
+ Returns:
304
+ True if successful
305
+ """
306
+ env = self.get_environment(env_id)
307
+ if not env:
308
+ return False
309
+
310
+ env.approved_by = approved_by
311
+ env.updated_at = datetime.utcnow()
312
+ self.db.commit()
313
+
314
+ logger.info(f"Environment {env.name} approved by {approved_by}")
315
+ return True
316
+
317
+ async def test_connection(self, env_id: int) -> Dict[str, Any]:
318
+ """
319
+ Test connection to an environment's Docker host
320
+
321
+ Args:
322
+ env_id: Environment ID
323
+
324
+ Returns:
325
+ Dict with success status and info/error
326
+ """
327
+ env = self.get_environment(env_id)
328
+ if not env:
329
+ return {"success": False, "error": "Environment not found"}
330
+
331
+ if not env.docker_host:
332
+ return {"success": False, "error": "No Docker host configured"}
333
+
334
+ try:
335
+ # Import Docker service
336
+ try:
337
+ from .docker_service import DockerService
338
+ except ImportError:
339
+ from docker_service import DockerService
340
+
341
+ docker = DockerService(docker_host=env.docker_host)
342
+ info = docker.client.info()
343
+
344
+ return {
345
+ "success": True,
346
+ "docker_version": info.get("ServerVersion"),
347
+ "containers": info.get("Containers"),
348
+ "images": info.get("Images"),
349
+ "os": info.get("OperatingSystem")
350
+ }
351
+
352
+ except Exception as e:
353
+ logger.error(f"Failed to connect to environment {env.name}: {e}")
354
+ return {"success": False, "error": str(e)}
355
+
356
+ # =========================================================================
357
+ # Default Environments
358
+ # =========================================================================
359
+
360
+ def create_default_environments(self, project_id: int) -> List[Environment]:
361
+ """
362
+ Create default environments for a new project
363
+
364
+ Args:
365
+ project_id: Project ID
366
+
367
+ Returns:
368
+ List of created environments
369
+ """
370
+ defaults = [
371
+ {
372
+ "name": "development",
373
+ "display_name": "Development",
374
+ "order": 1,
375
+ "branch": "develop",
376
+ "auto_deploy": True,
377
+ "requires_approval": False
378
+ },
379
+ {
380
+ "name": "staging",
381
+ "display_name": "Staging",
382
+ "order": 2,
383
+ "branch": "staging",
384
+ "auto_deploy": True,
385
+ "requires_approval": False
386
+ },
387
+ {
388
+ "name": "production",
389
+ "display_name": "Production",
390
+ "order": 3,
391
+ "branch": "main",
392
+ "auto_deploy": False,
393
+ "requires_approval": True
394
+ }
395
+ ]
396
+
397
+ environments = []
398
+ for env_data in defaults:
399
+ env = self.create_environment(project_id, env_data)
400
+ environments.append(env)
401
+
402
+ logger.info(f"Created default environments for project {project_id}")
403
+ return environments
404
+
405
+
406
+ # Singleton instance
407
+ _env_service = None
408
+
409
+
410
+ def get_environment_service(db: Session = None) -> EnvironmentService:
411
+ """Get environment service instance"""
412
+ if db:
413
+ return EnvironmentService(db)
414
+
415
+ global _env_service
416
+ if _env_service is None:
417
+ _env_service = EnvironmentService()
418
+ return _env_service