quantum-framework 0.9.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 (262) hide show
  1. quantum/__init__.py +1 -0
  2. quantum/cli/__init__.py +2 -0
  3. quantum/cli/commands/__init__.py +15 -0
  4. quantum/cli/commands/build.py +417 -0
  5. quantum/cli/commands/dev.py +185 -0
  6. quantum/cli/commands/docs.py +329 -0
  7. quantum/cli/commands/lint.py +523 -0
  8. quantum/cli/commands/migrate.py +527 -0
  9. quantum/cli/commands/new.py +622 -0
  10. quantum/cli/commands/serve.py +190 -0
  11. quantum/cli/commands/test.py +193 -0
  12. quantum/cli/deploy.py +810 -0
  13. quantum/cli/hot_reload.py +951 -0
  14. quantum/cli/jobs.py +356 -0
  15. quantum/cli/mq.py +582 -0
  16. quantum/cli/pkg.py +390 -0
  17. quantum/cli/runner.py +547 -0
  18. quantum/cli/server_process.py +159 -0
  19. quantum/cli/utils.py +334 -0
  20. quantum/compiler/__init__.py +30 -0
  21. quantum/compiler/base_generator.py +367 -0
  22. quantum/compiler/cli.py +295 -0
  23. quantum/compiler/expression_transformer.py +444 -0
  24. quantum/compiler/javascript/__init__.py +10 -0
  25. quantum/compiler/javascript/generator.py +659 -0
  26. quantum/compiler/optimizer.py +270 -0
  27. quantum/compiler/python/__init__.py +10 -0
  28. quantum/compiler/python/generator.py +883 -0
  29. quantum/compiler/python/runtime.py +863 -0
  30. quantum/compiler/transpiler.py +330 -0
  31. quantum/core/__init__.py +3 -0
  32. quantum/core/ast_nodes.py +2611 -0
  33. quantum/core/expression_diagnostics.py +87 -0
  34. quantum/core/expression_stdlib.py +148 -0
  35. quantum/core/expressions.py +591 -0
  36. quantum/core/features/agents/src/__init__.py +30 -0
  37. quantum/core/features/agents/src/ast_node.py +540 -0
  38. quantum/core/features/conditionals/src/__init__.py +8 -0
  39. quantum/core/features/conditionals/src/ast_node.py +68 -0
  40. quantum/core/features/data_fetching/src/__init__.py +21 -0
  41. quantum/core/features/data_fetching/src/ast_node.py +312 -0
  42. quantum/core/features/data_fetching/src/desktop_adapter.py +351 -0
  43. quantum/core/features/data_fetching/src/html_adapter.py +474 -0
  44. quantum/core/features/data_fetching/src/parser.py +225 -0
  45. quantum/core/features/data_import/src/__init__.py +0 -0
  46. quantum/core/features/data_import/src/ast_node.py +291 -0
  47. quantum/core/features/data_import/src/runtime.py +538 -0
  48. quantum/core/features/dump/src/__init__.py +12 -0
  49. quantum/core/features/dump/src/ast_node.py +106 -0
  50. quantum/core/features/dump/src/parser.py +61 -0
  51. quantum/core/features/dump/src/runtime.py +246 -0
  52. quantum/core/features/functions/src/__init__.py +8 -0
  53. quantum/core/features/functions/src/ast_node.py +149 -0
  54. quantum/core/features/game_engine_2d/src/__init__.py +19 -0
  55. quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
  56. quantum/core/features/game_engine_2d/src/parser.py +983 -0
  57. quantum/core/features/invocation/src/__init__.py +0 -0
  58. quantum/core/features/invocation/src/ast_node.py +146 -0
  59. quantum/core/features/invocation/src/runtime.py +327 -0
  60. quantum/core/features/knowledge_base/src/__init__.py +6 -0
  61. quantum/core/features/knowledge_base/src/ast_node.py +113 -0
  62. quantum/core/features/knowledge_base/src/parser.py +82 -0
  63. quantum/core/features/logging/src/__init__.py +12 -0
  64. quantum/core/features/logging/src/ast_node.py +111 -0
  65. quantum/core/features/logging/src/parser.py +50 -0
  66. quantum/core/features/logging/src/runtime.py +190 -0
  67. quantum/core/features/loops/src/__init__.py +8 -0
  68. quantum/core/features/loops/src/ast_node.py +60 -0
  69. quantum/core/features/query/src/__init__.py +0 -0
  70. quantum/core/features/query/src/database_service.py +322 -0
  71. quantum/core/features/query/src/query_validators.py +20 -0
  72. quantum/core/features/state_management/src/__init__.py +11 -0
  73. quantum/core/features/state_management/src/ast_node.py +228 -0
  74. quantum/core/features/terminal_engine/src/__init__.py +21 -0
  75. quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
  76. quantum/core/features/terminal_engine/src/parser.py +361 -0
  77. quantum/core/features/testing_engine/src/__init__.py +41 -0
  78. quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
  79. quantum/core/features/testing_engine/src/parser.py +604 -0
  80. quantum/core/features/theming/src/__init__.py +48 -0
  81. quantum/core/features/theming/src/ast_node.py +137 -0
  82. quantum/core/features/theming/src/presets.py +405 -0
  83. quantum/core/features/ui_engine/src/__init__.py +20 -0
  84. quantum/core/features/ui_engine/src/ast_nodes.py +1854 -0
  85. quantum/core/features/ui_engine/src/parser.py +1106 -0
  86. quantum/core/features/websocket/src/__init__.py +24 -0
  87. quantum/core/features/websocket/src/ast_node.py +247 -0
  88. quantum/core/html_compat.py +299 -0
  89. quantum/core/parser.py +1235 -0
  90. quantum/core/parser_registry.py +213 -0
  91. quantum/core/parsers/__init__.py +76 -0
  92. quantum/core/parsers/ai/__init__.py +12 -0
  93. quantum/core/parsers/ai/agent_parser.py +106 -0
  94. quantum/core/parsers/ai/knowledge_parser.py +86 -0
  95. quantum/core/parsers/ai/llm_parser.py +78 -0
  96. quantum/core/parsers/ai/team_parser.py +88 -0
  97. quantum/core/parsers/base.py +322 -0
  98. quantum/core/parsers/composition/__init__.py +10 -0
  99. quantum/core/parsers/composition/import_parser.py +50 -0
  100. quantum/core/parsers/composition/slot_parser.py +48 -0
  101. quantum/core/parsers/control_flow/__init__.py +11 -0
  102. quantum/core/parsers/control_flow/if_parser.py +68 -0
  103. quantum/core/parsers/control_flow/loop_parser.py +105 -0
  104. quantum/core/parsers/control_flow/set_parser.py +89 -0
  105. quantum/core/parsers/data/__init__.py +12 -0
  106. quantum/core/parsers/data/data_parser.py +217 -0
  107. quantum/core/parsers/data/invoke_parser.py +116 -0
  108. quantum/core/parsers/data/query_parser.py +188 -0
  109. quantum/core/parsers/data/transaction_parser.py +77 -0
  110. quantum/core/parsers/events/__init__.py +9 -0
  111. quantum/core/parsers/events/dispatch_event_parser.py +44 -0
  112. quantum/core/parsers/forms/__init__.py +11 -0
  113. quantum/core/parsers/forms/action_parser.py +54 -0
  114. quantum/core/parsers/forms/flash_parser.py +34 -0
  115. quantum/core/parsers/forms/redirect_parser.py +33 -0
  116. quantum/core/parsers/functions/__init__.py +11 -0
  117. quantum/core/parsers/functions/function_parser.py +137 -0
  118. quantum/core/parsers/functions/param_parser.py +66 -0
  119. quantum/core/parsers/functions/return_parser.py +31 -0
  120. quantum/core/parsers/html/__init__.py +10 -0
  121. quantum/core/parsers/html/component_call_parser.py +130 -0
  122. quantum/core/parsers/html/html_parser.py +115 -0
  123. quantum/core/parsers/jobs/__init__.py +11 -0
  124. quantum/core/parsers/jobs/job_parser.py +71 -0
  125. quantum/core/parsers/jobs/schedule_parser.py +62 -0
  126. quantum/core/parsers/jobs/thread_parser.py +57 -0
  127. quantum/core/parsers/messaging/__init__.py +17 -0
  128. quantum/core/parsers/messaging/message_ack_parser.py +30 -0
  129. quantum/core/parsers/messaging/message_nack_parser.py +30 -0
  130. quantum/core/parsers/messaging/message_parser.py +114 -0
  131. quantum/core/parsers/messaging/queue_parser.py +61 -0
  132. quantum/core/parsers/messaging/websocket_parser.py +121 -0
  133. quantum/core/parsers/persistence/__init__.py +9 -0
  134. quantum/core/parsers/persistence/persist_parser.py +64 -0
  135. quantum/core/parsers/routing/__init__.py +9 -0
  136. quantum/core/parsers/routing/route_parser.py +41 -0
  137. quantum/core/parsers/scripting/__init__.py +12 -0
  138. quantum/core/parsers/scripting/pyclass_parser.py +62 -0
  139. quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
  140. quantum/core/parsers/scripting/pyimport_parser.py +52 -0
  141. quantum/core/parsers/scripting/python_parser.py +49 -0
  142. quantum/core/parsers/services/__init__.py +12 -0
  143. quantum/core/parsers/services/dump_parser.py +52 -0
  144. quantum/core/parsers/services/file_parser.py +48 -0
  145. quantum/core/parsers/services/log_parser.py +46 -0
  146. quantum/core/parsers/services/mail_parser.py +65 -0
  147. quantum/core/tiers.py +82 -0
  148. quantum/packages/__init__.py +28 -0
  149. quantum/packages/manager.py +413 -0
  150. quantum/packages/manifest.py +351 -0
  151. quantum/packages/registry.py +399 -0
  152. quantum/packages/resolver.py +336 -0
  153. quantum/plugins/__init__.py +33 -0
  154. quantum/plugins/hooks.py +329 -0
  155. quantum/plugins/loader.py +479 -0
  156. quantum/plugins/manifest.py +336 -0
  157. quantum/plugins/registry.py +371 -0
  158. quantum/runtime/__init__.py +28 -0
  159. quantum/runtime/action_handler.py +443 -0
  160. quantum/runtime/adapters/__init__.py +88 -0
  161. quantum/runtime/adapters/memory_adapter.py +690 -0
  162. quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
  163. quantum/runtime/adapters/redis_adapter.py +582 -0
  164. quantum/runtime/adapters/sqlite_adapter.py +414 -0
  165. quantum/runtime/agent_service.py +1133 -0
  166. quantum/runtime/api_server.py +86 -0
  167. quantum/runtime/ast_cache.py +506 -0
  168. quantum/runtime/auth_service.py +267 -0
  169. quantum/runtime/component.py +990 -0
  170. quantum/runtime/component_composer.py +319 -0
  171. quantum/runtime/component_resolver.py +174 -0
  172. quantum/runtime/database_service.py +598 -0
  173. quantum/runtime/email_service.py +162 -0
  174. quantum/runtime/error_handler.py +295 -0
  175. quantum/runtime/execution_context.py +286 -0
  176. quantum/runtime/executor_registry.py +171 -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 +262 -0
  184. quantum/runtime/executors/control_flow/__init__.py +11 -0
  185. quantum/runtime/executors/control_flow/if_executor.py +93 -0
  186. quantum/runtime/executors/control_flow/loop_executor.py +307 -0
  187. quantum/runtime/executors/control_flow/set_executor.py +412 -0
  188. quantum/runtime/executors/data/__init__.py +12 -0
  189. quantum/runtime/executors/data/data_executor.py +145 -0
  190. quantum/runtime/executors/data/invoke_executor.py +176 -0
  191. quantum/runtime/executors/data/query_executor.py +256 -0
  192. quantum/runtime/executors/data/transaction_executor.py +91 -0
  193. quantum/runtime/executors/jobs/__init__.py +11 -0
  194. quantum/runtime/executors/jobs/job_executor.py +190 -0
  195. quantum/runtime/executors/jobs/schedule_executor.py +132 -0
  196. quantum/runtime/executors/jobs/thread_executor.py +127 -0
  197. quantum/runtime/executors/messaging/__init__.py +17 -0
  198. quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
  199. quantum/runtime/executors/messaging/message_executor.py +174 -0
  200. quantum/runtime/executors/messaging/queue_executor.py +103 -0
  201. quantum/runtime/executors/messaging/websocket_executor.py +197 -0
  202. quantum/runtime/executors/scripting/__init__.py +11 -0
  203. quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
  204. quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
  205. quantum/runtime/executors/scripting/python_executor.py +249 -0
  206. quantum/runtime/executors/services/__init__.py +12 -0
  207. quantum/runtime/executors/services/dump_executor.py +72 -0
  208. quantum/runtime/executors/services/file_executor.py +89 -0
  209. quantum/runtime/executors/services/log_executor.py +77 -0
  210. quantum/runtime/executors/services/mail_executor.py +81 -0
  211. quantum/runtime/expression_cache.py +498 -0
  212. quantum/runtime/file_upload_service.py +326 -0
  213. quantum/runtime/function_registry.py +118 -0
  214. quantum/runtime/game_builder.py +166 -0
  215. quantum/runtime/game_code_generator.py +2371 -0
  216. quantum/runtime/game_templates.py +2006 -0
  217. quantum/runtime/godot_code_generator.py +4681 -0
  218. quantum/runtime/godot_templates.py +1449 -0
  219. quantum/runtime/job_executor.py +1599 -0
  220. quantum/runtime/knowledge_service.py +500 -0
  221. quantum/runtime/llm_cache.py +100 -0
  222. quantum/runtime/llm_providers.py +704 -0
  223. quantum/runtime/llm_service.py +287 -0
  224. quantum/runtime/logging_setup.py +140 -0
  225. quantum/runtime/message_broker.py +364 -0
  226. quantum/runtime/message_queue_service.py +571 -0
  227. quantum/runtime/param_validation.py +184 -0
  228. quantum/runtime/pypy_compat.py +315 -0
  229. quantum/runtime/python_bridge.py +698 -0
  230. quantum/runtime/query_validators.py +304 -0
  231. quantum/runtime/renderer.py +733 -0
  232. quantum/runtime/service_container.py +444 -0
  233. quantum/runtime/terminal_builder.py +76 -0
  234. quantum/runtime/terminal_code_generator.py +607 -0
  235. quantum/runtime/terminal_templates.py +243 -0
  236. quantum/runtime/testing_builder.py +77 -0
  237. quantum/runtime/testing_code_generator.py +833 -0
  238. quantum/runtime/testing_templates.py +85 -0
  239. quantum/runtime/ui_builder.py +188 -0
  240. quantum/runtime/ui_desktop_adapter.py +1730 -0
  241. quantum/runtime/ui_desktop_templates.py +307 -0
  242. quantum/runtime/ui_html_adapter.py +2691 -0
  243. quantum/runtime/ui_html_templates.py +2297 -0
  244. quantum/runtime/ui_mobile_adapter.py +1832 -0
  245. quantum/runtime/ui_mobile_templates.py +1003 -0
  246. quantum/runtime/ui_textual_adapter.py +1866 -0
  247. quantum/runtime/ui_textual_templates.py +45 -0
  248. quantum/runtime/ui_tokens.py +465 -0
  249. quantum/runtime/ui_validator.py +365 -0
  250. quantum/runtime/validators.py +256 -0
  251. quantum/runtime/web_server.py +1766 -0
  252. quantum/runtime/websocket_adapter.py +501 -0
  253. quantum/runtime/websocket_service.py +585 -0
  254. quantum/runtime/websocket_transport.py +289 -0
  255. quantum/runtime/wsgi.py +101 -0
  256. quantum/utils/__init__.py +1 -0
  257. quantum_framework-0.9.0.dist-info/METADATA +244 -0
  258. quantum_framework-0.9.0.dist-info/RECORD +262 -0
  259. quantum_framework-0.9.0.dist-info/WHEEL +5 -0
  260. quantum_framework-0.9.0.dist-info/entry_points.txt +2 -0
  261. quantum_framework-0.9.0.dist-info/licenses/LICENSE +21 -0
  262. quantum_framework-0.9.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,336 @@
1
+ """
2
+ Quantum Dependency Resolver
3
+
4
+ Resolves package dependencies with version constraints.
5
+
6
+ Supported version constraints:
7
+ - Exact: "1.0.0"
8
+ - Caret: "^1.0.0" (compatible with 1.x.x)
9
+ - Tilde: "~1.0.0" (compatible with 1.0.x)
10
+ - Range: ">=1.0.0", ">1.0.0", "<=1.0.0", "<1.0.0"
11
+ - Any: "*"
12
+ """
13
+
14
+ import re
15
+ from dataclasses import dataclass
16
+ from typing import Dict, List, Optional, Set, Tuple
17
+
18
+ from .manifest import PackageManifest
19
+ from .registry import PackageRegistry
20
+
21
+
22
+ class ResolutionError(Exception):
23
+ """Error resolving package dependencies."""
24
+ pass
25
+
26
+
27
+ @dataclass
28
+ class VersionConstraint:
29
+ """Represents a version constraint."""
30
+ operator: str # exact, caret, tilde, >=, >, <=, <, any
31
+ major: int
32
+ minor: int
33
+ patch: int
34
+ prerelease: Optional[str] = None
35
+
36
+ @classmethod
37
+ def parse(cls, constraint: str) -> 'VersionConstraint':
38
+ """Parse version constraint string."""
39
+ constraint = constraint.strip()
40
+
41
+ if constraint == '*':
42
+ return cls(operator='any', major=0, minor=0, patch=0)
43
+
44
+ # Extract operator
45
+ if constraint.startswith('^'):
46
+ operator = 'caret'
47
+ version_str = constraint[1:]
48
+ elif constraint.startswith('~'):
49
+ operator = 'tilde'
50
+ version_str = constraint[1:]
51
+ elif constraint.startswith('>='):
52
+ operator = '>='
53
+ version_str = constraint[2:]
54
+ elif constraint.startswith('<='):
55
+ operator = '<='
56
+ version_str = constraint[2:]
57
+ elif constraint.startswith('>'):
58
+ operator = '>'
59
+ version_str = constraint[1:]
60
+ elif constraint.startswith('<'):
61
+ operator = '<'
62
+ version_str = constraint[1:]
63
+ else:
64
+ operator = 'exact'
65
+ version_str = constraint
66
+
67
+ # Parse version
68
+ match = re.match(r'^(\d+)\.(\d+)\.(\d+)(-[a-zA-Z0-9.]+)?$', version_str)
69
+ if not match:
70
+ raise ResolutionError(f"Invalid version format: {constraint}")
71
+
72
+ return cls(
73
+ operator=operator,
74
+ major=int(match.group(1)),
75
+ minor=int(match.group(2)),
76
+ patch=int(match.group(3)),
77
+ prerelease=match.group(4)[1:] if match.group(4) else None
78
+ )
79
+
80
+ def satisfies(self, version: str) -> bool:
81
+ """Check if a version satisfies this constraint."""
82
+ match = re.match(r'^(\d+)\.(\d+)\.(\d+)(-[a-zA-Z0-9.]+)?$', version)
83
+ if not match:
84
+ return False
85
+
86
+ v_major = int(match.group(1))
87
+ v_minor = int(match.group(2))
88
+ v_patch = int(match.group(3))
89
+ v_prerelease = match.group(4)[1:] if match.group(4) else None
90
+
91
+ if self.operator == 'any':
92
+ return True
93
+
94
+ if self.operator == 'exact':
95
+ return (v_major == self.major and
96
+ v_minor == self.minor and
97
+ v_patch == self.patch)
98
+
99
+ if self.operator == 'caret':
100
+ # ^1.2.3 means >=1.2.3 and <2.0.0 (for major > 0)
101
+ # ^0.2.3 means >=0.2.3 and <0.3.0 (for major == 0)
102
+ if self.major > 0:
103
+ if v_major != self.major:
104
+ return False
105
+ return self._version_tuple(v_major, v_minor, v_patch) >= \
106
+ self._version_tuple(self.major, self.minor, self.patch)
107
+ else:
108
+ if v_major != 0 or v_minor != self.minor:
109
+ return False
110
+ return v_patch >= self.patch
111
+
112
+ if self.operator == 'tilde':
113
+ # ~1.2.3 means >=1.2.3 and <1.3.0
114
+ if v_major != self.major or v_minor != self.minor:
115
+ return False
116
+ return v_patch >= self.patch
117
+
118
+ if self.operator == '>=':
119
+ return self._version_tuple(v_major, v_minor, v_patch) >= \
120
+ self._version_tuple(self.major, self.minor, self.patch)
121
+
122
+ if self.operator == '>':
123
+ return self._version_tuple(v_major, v_minor, v_patch) > \
124
+ self._version_tuple(self.major, self.minor, self.patch)
125
+
126
+ if self.operator == '<=':
127
+ return self._version_tuple(v_major, v_minor, v_patch) <= \
128
+ self._version_tuple(self.major, self.minor, self.patch)
129
+
130
+ if self.operator == '<':
131
+ return self._version_tuple(v_major, v_minor, v_patch) < \
132
+ self._version_tuple(self.major, self.minor, self.patch)
133
+
134
+ return False
135
+
136
+ @staticmethod
137
+ def _version_tuple(major: int, minor: int, patch: int) -> Tuple[int, int, int]:
138
+ return (major, minor, patch)
139
+
140
+
141
+ @dataclass
142
+ class ResolvedPackage:
143
+ """A resolved package with specific version."""
144
+ name: str
145
+ version: str
146
+ manifest: PackageManifest
147
+ dependencies: List['ResolvedPackage']
148
+
149
+
150
+ class DependencyResolver:
151
+ """
152
+ Resolves package dependencies.
153
+
154
+ Uses a simple depth-first resolution strategy with version constraints.
155
+ """
156
+
157
+ def __init__(self, registry: PackageRegistry):
158
+ self.registry = registry
159
+ self._resolution_cache: Dict[str, ResolvedPackage] = {}
160
+ self._resolution_stack: Set[str] = set()
161
+
162
+ def resolve(
163
+ self,
164
+ package_name: str,
165
+ version_constraint: str = '*'
166
+ ) -> ResolvedPackage:
167
+ """
168
+ Resolve a package and all its dependencies.
169
+
170
+ Args:
171
+ package_name: Package to resolve
172
+ version_constraint: Version constraint string
173
+
174
+ Returns:
175
+ ResolvedPackage with all dependencies resolved
176
+
177
+ Raises:
178
+ ResolutionError: If resolution fails
179
+ """
180
+ # Check for circular dependencies
181
+ if package_name in self._resolution_stack:
182
+ raise ResolutionError(
183
+ f"Circular dependency detected: {package_name}"
184
+ )
185
+
186
+ # Check cache
187
+ cache_key = f"{package_name}@{version_constraint}"
188
+ if cache_key in self._resolution_cache:
189
+ return self._resolution_cache[cache_key]
190
+
191
+ # Add to resolution stack
192
+ self._resolution_stack.add(package_name)
193
+
194
+ try:
195
+ # Find matching version
196
+ version = self._find_matching_version(package_name, version_constraint)
197
+ if not version:
198
+ raise ResolutionError(
199
+ f"No matching version found for {package_name} {version_constraint}"
200
+ )
201
+
202
+ # Get manifest
203
+ manifest = self.registry.get_package(package_name, version)
204
+ if not manifest:
205
+ raise ResolutionError(
206
+ f"Package {package_name}@{version} not found in registry"
207
+ )
208
+
209
+ # Resolve dependencies recursively
210
+ resolved_deps = []
211
+ for dep_name, dep_constraint in manifest.dependencies.items():
212
+ resolved_dep = self.resolve(dep_name, dep_constraint)
213
+ resolved_deps.append(resolved_dep)
214
+
215
+ # Create resolved package
216
+ resolved = ResolvedPackage(
217
+ name=package_name,
218
+ version=version,
219
+ manifest=manifest,
220
+ dependencies=resolved_deps
221
+ )
222
+
223
+ # Cache result
224
+ self._resolution_cache[cache_key] = resolved
225
+
226
+ return resolved
227
+
228
+ finally:
229
+ # Remove from resolution stack
230
+ self._resolution_stack.discard(package_name)
231
+
232
+ def resolve_all(
233
+ self,
234
+ dependencies: Dict[str, str]
235
+ ) -> List[ResolvedPackage]:
236
+ """
237
+ Resolve multiple dependencies.
238
+
239
+ Args:
240
+ dependencies: Dict of package name -> version constraint
241
+
242
+ Returns:
243
+ List of resolved packages
244
+ """
245
+ resolved = []
246
+ for name, constraint in dependencies.items():
247
+ resolved.append(self.resolve(name, constraint))
248
+ return resolved
249
+
250
+ def _find_matching_version(
251
+ self,
252
+ package_name: str,
253
+ version_constraint: str
254
+ ) -> Optional[str]:
255
+ """Find the best matching version for a constraint."""
256
+ constraint = VersionConstraint.parse(version_constraint)
257
+
258
+ # Get all installed versions
259
+ versions = self.registry.get_all_versions(package_name)
260
+ if not versions:
261
+ return None
262
+
263
+ # Find matching versions
264
+ matching = [v for v in versions if constraint.satisfies(v)]
265
+ if not matching:
266
+ return None
267
+
268
+ # Return highest matching version (already sorted in descending order)
269
+ return matching[0]
270
+
271
+ def get_install_order(self, resolved: ResolvedPackage) -> List[ResolvedPackage]:
272
+ """
273
+ Get packages in installation order (dependencies first).
274
+
275
+ Args:
276
+ resolved: Resolved package with dependencies
277
+
278
+ Returns:
279
+ List of packages in installation order
280
+ """
281
+ order = []
282
+ visited = set()
283
+
284
+ def visit(pkg: ResolvedPackage):
285
+ if pkg.name in visited:
286
+ return
287
+ visited.add(pkg.name)
288
+
289
+ # Visit dependencies first
290
+ for dep in pkg.dependencies:
291
+ visit(dep)
292
+
293
+ order.append(pkg)
294
+
295
+ visit(resolved)
296
+ return order
297
+
298
+ def check_conflicts(
299
+ self,
300
+ packages: List[ResolvedPackage]
301
+ ) -> List[str]:
302
+ """
303
+ Check for version conflicts in resolved packages.
304
+
305
+ Args:
306
+ packages: List of resolved packages
307
+
308
+ Returns:
309
+ List of conflict descriptions (empty if no conflicts)
310
+ """
311
+ conflicts = []
312
+ versions: Dict[str, Set[str]] = {}
313
+
314
+ def collect_versions(pkg: ResolvedPackage):
315
+ if pkg.name not in versions:
316
+ versions[pkg.name] = set()
317
+ versions[pkg.name].add(pkg.version)
318
+
319
+ for dep in pkg.dependencies:
320
+ collect_versions(dep)
321
+
322
+ for pkg in packages:
323
+ collect_versions(pkg)
324
+
325
+ for name, vers in versions.items():
326
+ if len(vers) > 1:
327
+ conflicts.append(
328
+ f"Conflicting versions of {name}: {', '.join(sorted(vers))}"
329
+ )
330
+
331
+ return conflicts
332
+
333
+ def clear_cache(self):
334
+ """Clear resolution cache."""
335
+ self._resolution_cache.clear()
336
+ self._resolution_stack.clear()
@@ -0,0 +1,33 @@
1
+ """
2
+ Quantum Plugin System
3
+
4
+ Provides extensible architecture for custom AST nodes, tags, adapters,
5
+ lifecycle hooks, and CLI commands.
6
+
7
+ Usage:
8
+ from quantum.plugins import PluginLoader, PluginRegistry, HookManager
9
+
10
+ # Load plugins from directory
11
+ loader = PluginLoader()
12
+ loader.discover_plugins('./plugins')
13
+
14
+ # Access registry
15
+ registry = loader.registry
16
+ tag_handler = registry.get_tag_handler('my', 'custom-tag')
17
+ """
18
+
19
+ from .manifest import PluginManifest, ManifestError
20
+ from .registry import PluginRegistry, PluginInfo
21
+ from .hooks import HookManager, HookType
22
+ from .loader import PluginLoader, PluginLoadError
23
+
24
+ __all__ = [
25
+ 'PluginManifest',
26
+ 'ManifestError',
27
+ 'PluginRegistry',
28
+ 'PluginInfo',
29
+ 'HookManager',
30
+ 'HookType',
31
+ 'PluginLoader',
32
+ 'PluginLoadError',
33
+ ]
@@ -0,0 +1,329 @@
1
+ """
2
+ Quantum Plugin Hook System
3
+
4
+ Provides lifecycle hooks that plugins can use to intercept and modify
5
+ the parsing, rendering, and execution phases.
6
+ """
7
+
8
+ from enum import Enum
9
+ from dataclasses import dataclass, field
10
+ from typing import Callable, Any, Dict, List, Optional
11
+ import logging
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class HookType(Enum):
17
+ """Available hook types in the Quantum lifecycle"""
18
+
19
+ # Parsing phase
20
+ BEFORE_PARSE = "before_parse" # Before XML is parsed
21
+ AFTER_PARSE = "after_parse" # After AST is created
22
+
23
+ # Rendering phase
24
+ BEFORE_RENDER = "before_render" # Before HTML generation
25
+ AFTER_RENDER = "after_render" # After HTML generation
26
+
27
+ # Execution phase
28
+ BEFORE_EXECUTE = "before_execute" # Before component execution
29
+ AFTER_EXECUTE = "after_execute" # After component execution
30
+
31
+ # Error handling
32
+ ON_ERROR = "on_error" # When an error occurs
33
+
34
+ # Lifecycle
35
+ ON_INIT = "on_init" # Plugin initialization
36
+ ON_SHUTDOWN = "on_shutdown" # Application shutdown
37
+
38
+
39
+ @dataclass
40
+ class HookRegistration:
41
+ """A registered hook callback"""
42
+ hook_type: HookType
43
+ callback: Callable
44
+ plugin_name: str
45
+ priority: int = 100 # Lower = earlier execution
46
+
47
+ def __lt__(self, other):
48
+ """Allow sorting by priority"""
49
+ return self.priority < other.priority
50
+
51
+
52
+ @dataclass
53
+ class HookContext:
54
+ """Context passed to hook callbacks"""
55
+ hook_type: HookType
56
+ plugin_name: str = ""
57
+ data: Dict[str, Any] = field(default_factory=dict)
58
+
59
+ # Parsing context
60
+ source: Optional[str] = None # Original source code
61
+ ast: Optional[Any] = None # Parsed AST
62
+
63
+ # Rendering context
64
+ component: Optional[Any] = None # Component being rendered
65
+ html: Optional[str] = None # Rendered HTML
66
+
67
+ # Execution context
68
+ params: Optional[Dict] = None # Execution parameters
69
+ result: Optional[Any] = None # Execution result
70
+
71
+ # Error context
72
+ error: Optional[Exception] = None # Error that occurred
73
+
74
+ # Control flow
75
+ skip: bool = False # Skip remaining processing
76
+ modified: bool = False # Data was modified
77
+
78
+ def set_result(self, value: Any):
79
+ """Set result and mark as modified"""
80
+ self.result = value
81
+ self.modified = True
82
+
83
+ def set_html(self, value: str):
84
+ """Set HTML and mark as modified"""
85
+ self.html = value
86
+ self.modified = True
87
+
88
+ def set_ast(self, value: Any):
89
+ """Set AST and mark as modified"""
90
+ self.ast = value
91
+ self.modified = True
92
+
93
+ def skip_remaining(self):
94
+ """Skip remaining hooks and processing"""
95
+ self.skip = True
96
+
97
+
98
+ class HookManager:
99
+ """
100
+ Manages plugin lifecycle hooks.
101
+
102
+ Example usage:
103
+ manager = HookManager()
104
+
105
+ # Register a hook
106
+ def my_before_render(ctx: HookContext):
107
+ print(f"Rendering: {ctx.component}")
108
+ ctx.data['custom'] = 'value'
109
+
110
+ manager.register(HookType.BEFORE_RENDER, my_before_render, "my-plugin")
111
+
112
+ # Execute hooks
113
+ ctx = HookContext(hook_type=HookType.BEFORE_RENDER)
114
+ ctx.component = my_component
115
+ manager.execute(HookType.BEFORE_RENDER, ctx)
116
+ """
117
+
118
+ def __init__(self):
119
+ self._hooks: Dict[HookType, List[HookRegistration]] = {
120
+ hook_type: [] for hook_type in HookType
121
+ }
122
+ self._enabled = True
123
+
124
+ def register(
125
+ self,
126
+ hook_type: HookType,
127
+ callback: Callable[[HookContext], None],
128
+ plugin_name: str,
129
+ priority: int = 100
130
+ ) -> None:
131
+ """
132
+ Register a hook callback.
133
+
134
+ Args:
135
+ hook_type: Type of hook to register
136
+ callback: Function to call when hook is triggered
137
+ plugin_name: Name of the plugin registering the hook
138
+ priority: Execution priority (lower = earlier)
139
+ """
140
+ registration = HookRegistration(
141
+ hook_type=hook_type,
142
+ callback=callback,
143
+ plugin_name=plugin_name,
144
+ priority=priority
145
+ )
146
+ self._hooks[hook_type].append(registration)
147
+ # Keep sorted by priority
148
+ self._hooks[hook_type].sort()
149
+
150
+ logger.debug(f"Registered hook: {hook_type.value} from {plugin_name} (priority={priority})")
151
+
152
+ def unregister(self, hook_type: HookType, plugin_name: str) -> int:
153
+ """
154
+ Unregister all hooks of a type for a plugin.
155
+
156
+ Returns:
157
+ Number of hooks removed
158
+ """
159
+ original_count = len(self._hooks[hook_type])
160
+ self._hooks[hook_type] = [
161
+ h for h in self._hooks[hook_type]
162
+ if h.plugin_name != plugin_name
163
+ ]
164
+ removed = original_count - len(self._hooks[hook_type])
165
+ if removed:
166
+ logger.debug(f"Unregistered {removed} hooks for {plugin_name}")
167
+ return removed
168
+
169
+ def unregister_all(self, plugin_name: str) -> int:
170
+ """
171
+ Unregister all hooks for a plugin.
172
+
173
+ Returns:
174
+ Total number of hooks removed
175
+ """
176
+ total_removed = 0
177
+ for hook_type in HookType:
178
+ total_removed += self.unregister(hook_type, plugin_name)
179
+ return total_removed
180
+
181
+ def execute(self, hook_type: HookType, context: HookContext) -> HookContext:
182
+ """
183
+ Execute all registered hooks of a type.
184
+
185
+ Args:
186
+ hook_type: Type of hook to execute
187
+ context: Context to pass to callbacks
188
+
189
+ Returns:
190
+ Modified context after all hooks execute
191
+ """
192
+ if not self._enabled:
193
+ return context
194
+
195
+ hooks = self._hooks.get(hook_type, [])
196
+ if not hooks:
197
+ return context
198
+
199
+ logger.debug(f"Executing {len(hooks)} hooks for {hook_type.value}")
200
+
201
+ for registration in hooks:
202
+ if context.skip:
203
+ logger.debug(f"Skipping remaining hooks after {context.plugin_name}")
204
+ break
205
+
206
+ try:
207
+ context.plugin_name = registration.plugin_name
208
+ registration.callback(context)
209
+ except Exception as e:
210
+ logger.error(f"Hook error in {registration.plugin_name}: {e}")
211
+ # Don't propagate hook errors unless it's an ON_ERROR hook
212
+ if hook_type != HookType.ON_ERROR:
213
+ # Execute error hooks
214
+ error_ctx = HookContext(
215
+ hook_type=HookType.ON_ERROR,
216
+ error=e,
217
+ data={'original_hook': hook_type.value}
218
+ )
219
+ self.execute(HookType.ON_ERROR, error_ctx)
220
+
221
+ return context
222
+
223
+ def execute_before_parse(self, source: str) -> str:
224
+ """Execute before_parse hooks and return potentially modified source"""
225
+ ctx = HookContext(hook_type=HookType.BEFORE_PARSE, source=source)
226
+ ctx = self.execute(HookType.BEFORE_PARSE, ctx)
227
+ return ctx.source if ctx.modified else source
228
+
229
+ def execute_after_parse(self, ast: Any) -> Any:
230
+ """Execute after_parse hooks and return potentially modified AST"""
231
+ ctx = HookContext(hook_type=HookType.AFTER_PARSE, ast=ast)
232
+ ctx = self.execute(HookType.AFTER_PARSE, ctx)
233
+ return ctx.ast if ctx.modified else ast
234
+
235
+ def execute_before_render(self, component: Any, params: Dict = None) -> HookContext:
236
+ """Execute before_render hooks"""
237
+ ctx = HookContext(
238
+ hook_type=HookType.BEFORE_RENDER,
239
+ component=component,
240
+ params=params or {}
241
+ )
242
+ return self.execute(HookType.BEFORE_RENDER, ctx)
243
+
244
+ def execute_after_render(self, component: Any, html: str) -> str:
245
+ """Execute after_render hooks and return potentially modified HTML"""
246
+ ctx = HookContext(
247
+ hook_type=HookType.AFTER_RENDER,
248
+ component=component,
249
+ html=html
250
+ )
251
+ ctx = self.execute(HookType.AFTER_RENDER, ctx)
252
+ return ctx.html if ctx.modified else html
253
+
254
+ def execute_before_execute(self, component: Any, params: Dict = None) -> HookContext:
255
+ """Execute before_execute hooks"""
256
+ ctx = HookContext(
257
+ hook_type=HookType.BEFORE_EXECUTE,
258
+ component=component,
259
+ params=params or {}
260
+ )
261
+ return self.execute(HookType.BEFORE_EXECUTE, ctx)
262
+
263
+ def execute_after_execute(self, component: Any, result: Any) -> Any:
264
+ """Execute after_execute hooks and return potentially modified result"""
265
+ ctx = HookContext(
266
+ hook_type=HookType.AFTER_EXECUTE,
267
+ component=component,
268
+ result=result
269
+ )
270
+ ctx = self.execute(HookType.AFTER_EXECUTE, ctx)
271
+ return ctx.result if ctx.modified else result
272
+
273
+ def get_hooks(self, hook_type: HookType) -> List[HookRegistration]:
274
+ """Get all registered hooks of a type"""
275
+ return self._hooks.get(hook_type, [])
276
+
277
+ def get_all_hooks(self) -> Dict[HookType, List[HookRegistration]]:
278
+ """Get all registered hooks"""
279
+ return self._hooks
280
+
281
+ def enable(self):
282
+ """Enable hook execution"""
283
+ self._enabled = True
284
+
285
+ def disable(self):
286
+ """Disable hook execution"""
287
+ self._enabled = False
288
+
289
+ @property
290
+ def is_enabled(self) -> bool:
291
+ """Check if hooks are enabled"""
292
+ return self._enabled
293
+
294
+ def clear(self):
295
+ """Clear all registered hooks"""
296
+ for hook_type in HookType:
297
+ self._hooks[hook_type] = []
298
+
299
+ def count(self, hook_type: HookType = None) -> int:
300
+ """
301
+ Count registered hooks.
302
+
303
+ Args:
304
+ hook_type: Specific type to count, or None for total
305
+
306
+ Returns:
307
+ Number of registered hooks
308
+ """
309
+ if hook_type:
310
+ return len(self._hooks.get(hook_type, []))
311
+ return sum(len(hooks) for hooks in self._hooks.values())
312
+
313
+
314
+ # Global hook manager instance
315
+ _global_hook_manager: Optional[HookManager] = None
316
+
317
+
318
+ def get_hook_manager() -> HookManager:
319
+ """Get the global hook manager instance"""
320
+ global _global_hook_manager
321
+ if _global_hook_manager is None:
322
+ _global_hook_manager = HookManager()
323
+ return _global_hook_manager
324
+
325
+
326
+ def set_hook_manager(manager: HookManager) -> None:
327
+ """Set the global hook manager instance"""
328
+ global _global_hook_manager
329
+ _global_hook_manager = manager