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,479 @@
1
+ """
2
+ Quantum Plugin Loader
3
+
4
+ Discovers and loads plugins from:
5
+ 1. Local plugin directories
6
+ 2. Installed Python packages (quantum-plugin-* naming convention)
7
+ 3. Explicit plugin paths
8
+ """
9
+
10
+ import sys
11
+ import importlib
12
+ import importlib.util
13
+ from pathlib import Path
14
+ from typing import List, Dict, Any, Optional, Callable
15
+ import logging
16
+
17
+ from .manifest import PluginManifest, ManifestError
18
+ from .registry import PluginRegistry, PluginInfo, TagHandler, get_registry
19
+ from .hooks import HookManager, HookType, get_hook_manager
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class PluginLoadError(Exception):
25
+ """Error loading a plugin"""
26
+ pass
27
+
28
+
29
+ class PluginLoader:
30
+ """
31
+ Discovers and loads Quantum plugins.
32
+
33
+ Plugins can be loaded from:
34
+ 1. Local directories containing quantum-plugin.yaml
35
+ 2. Installed Python packages with quantum_plugin entry point
36
+ 3. Explicit paths to plugin directories
37
+
38
+ Example usage:
39
+ loader = PluginLoader()
40
+
41
+ # Discover plugins in a directory
42
+ loader.discover_plugins('./plugins')
43
+
44
+ # Load a specific plugin
45
+ loader.load_plugin('./plugins/my-plugin')
46
+
47
+ # Access loaded plugins
48
+ plugin = loader.registry.get_plugin('my-plugin')
49
+ """
50
+
51
+ MANIFEST_FILENAME = 'quantum-plugin.yaml'
52
+ PACKAGE_PREFIX = 'quantum_plugin_'
53
+
54
+ def __init__(
55
+ self,
56
+ registry: PluginRegistry = None,
57
+ hook_manager: HookManager = None
58
+ ):
59
+ self.registry = registry or get_registry()
60
+ self.hook_manager = hook_manager or get_hook_manager()
61
+ self._loaded_paths: set = set()
62
+
63
+ def discover_plugins(self, search_path: str) -> List[str]:
64
+ """
65
+ Discover and load all plugins in a directory.
66
+
67
+ Args:
68
+ search_path: Path to search for plugins
69
+
70
+ Returns:
71
+ List of loaded plugin names
72
+ """
73
+ search_dir = Path(search_path)
74
+ if not search_dir.exists():
75
+ logger.warning(f"Plugin search path does not exist: {search_path}")
76
+ return []
77
+
78
+ loaded = []
79
+ for item in search_dir.iterdir():
80
+ if item.is_dir():
81
+ manifest_path = item / self.MANIFEST_FILENAME
82
+ if manifest_path.exists():
83
+ try:
84
+ name = self.load_plugin(str(item))
85
+ loaded.append(name)
86
+ except PluginLoadError as e:
87
+ logger.error(f"Failed to load plugin {item.name}: {e}")
88
+
89
+ logger.info(f"Discovered {len(loaded)} plugins in {search_path}")
90
+ return loaded
91
+
92
+ def discover_installed_plugins(self) -> List[str]:
93
+ """
94
+ Discover plugins installed as Python packages.
95
+
96
+ Looks for packages with names starting with 'quantum_plugin_'
97
+ or using the 'quantum.plugins' entry point group.
98
+
99
+ Returns:
100
+ List of loaded plugin names
101
+ """
102
+ loaded = []
103
+
104
+ # Method 1: Look for quantum_plugin_* packages
105
+ try:
106
+ import pkgutil
107
+ for importer, modname, ispkg in pkgutil.iter_modules():
108
+ if modname.startswith(self.PACKAGE_PREFIX):
109
+ try:
110
+ name = self._load_installed_plugin(modname)
111
+ if name:
112
+ loaded.append(name)
113
+ except Exception as e:
114
+ logger.error(f"Failed to load installed plugin {modname}: {e}")
115
+ except Exception as e:
116
+ logger.debug(f"Error scanning for installed plugins: {e}")
117
+
118
+ # Method 2: Use entry points (Python 3.9+)
119
+ try:
120
+ from importlib.metadata import entry_points
121
+ eps = entry_points()
122
+
123
+ # Handle different Python versions
124
+ if hasattr(eps, 'select'):
125
+ # Python 3.10+
126
+ quantum_plugins = eps.select(group='quantum.plugins')
127
+ else:
128
+ # Python 3.9
129
+ quantum_plugins = eps.get('quantum.plugins', [])
130
+
131
+ for ep in quantum_plugins:
132
+ try:
133
+ plugin_module = ep.load()
134
+ if hasattr(plugin_module, 'get_manifest'):
135
+ manifest_data = plugin_module.get_manifest()
136
+ manifest = PluginManifest.from_dict(manifest_data)
137
+ self._register_plugin_from_module(manifest, plugin_module)
138
+ loaded.append(manifest.name)
139
+ except Exception as e:
140
+ logger.error(f"Failed to load entry point {ep.name}: {e}")
141
+ except ImportError:
142
+ pass # importlib.metadata not available
143
+
144
+ logger.info(f"Discovered {len(loaded)} installed plugins")
145
+ return loaded
146
+
147
+ def load_plugin(self, plugin_path: str) -> str:
148
+ """
149
+ Load a plugin from a directory.
150
+
151
+ Args:
152
+ plugin_path: Path to plugin directory
153
+
154
+ Returns:
155
+ Loaded plugin name
156
+
157
+ Raises:
158
+ PluginLoadError: If plugin cannot be loaded
159
+ """
160
+ plugin_dir = Path(plugin_path).resolve()
161
+
162
+ # Check if already loaded
163
+ if str(plugin_dir) in self._loaded_paths:
164
+ logger.debug(f"Plugin already loaded: {plugin_dir}")
165
+ manifest = PluginManifest.from_file(plugin_dir / self.MANIFEST_FILENAME)
166
+ return manifest.name
167
+
168
+ # Find and parse manifest
169
+ manifest_path = plugin_dir / self.MANIFEST_FILENAME
170
+ if not manifest_path.exists():
171
+ raise PluginLoadError(f"Manifest not found: {manifest_path}")
172
+
173
+ try:
174
+ manifest = PluginManifest.from_file(manifest_path)
175
+ except ManifestError as e:
176
+ raise PluginLoadError(f"Invalid manifest: {e}")
177
+
178
+ # Validate manifest
179
+ errors = manifest.validate()
180
+ if errors:
181
+ raise PluginLoadError(f"Manifest validation failed: {errors}")
182
+
183
+ # Create plugin info
184
+ info = PluginInfo(
185
+ name=manifest.name,
186
+ version=manifest.version,
187
+ manifest=manifest,
188
+ source_path=plugin_dir
189
+ )
190
+
191
+ # Add plugin directory to Python path
192
+ plugin_src = plugin_dir / 'src'
193
+ if plugin_src.exists():
194
+ sys.path.insert(0, str(plugin_src))
195
+ sys.path.insert(0, str(plugin_dir))
196
+
197
+ try:
198
+ # Load components
199
+ self._load_tag_handlers(manifest, info, plugin_dir)
200
+ self._load_node_classes(manifest, info, plugin_dir)
201
+ self._load_adapters(manifest, info, plugin_dir)
202
+ self._load_cli_commands(manifest, info, plugin_dir)
203
+ self._load_hooks(manifest, info, plugin_dir)
204
+
205
+ # Register plugin
206
+ self.registry.register_plugin(info)
207
+ self._loaded_paths.add(str(plugin_dir))
208
+
209
+ # Execute on_init hooks
210
+ from .hooks import HookContext
211
+ ctx = HookContext(
212
+ hook_type=HookType.ON_INIT,
213
+ data={'plugin': info}
214
+ )
215
+ self.hook_manager.execute(HookType.ON_INIT, ctx)
216
+
217
+ logger.info(f"Loaded plugin: {manifest.name}@{manifest.version}")
218
+ return manifest.name
219
+
220
+ except Exception as e:
221
+ # Clean up on failure
222
+ if str(plugin_src) in sys.path:
223
+ sys.path.remove(str(plugin_src))
224
+ if str(plugin_dir) in sys.path:
225
+ sys.path.remove(str(plugin_dir))
226
+ raise PluginLoadError(f"Failed to load plugin components: {e}")
227
+
228
+ def unload_plugin(self, name: str) -> bool:
229
+ """
230
+ Unload a plugin.
231
+
232
+ Args:
233
+ name: Plugin name
234
+
235
+ Returns:
236
+ True if plugin was unloaded
237
+ """
238
+ info = self.registry.get_plugin(name)
239
+ if not info:
240
+ return False
241
+
242
+ # Execute on_shutdown hooks for this plugin
243
+ from .hooks import HookContext
244
+ ctx = HookContext(
245
+ hook_type=HookType.ON_SHUTDOWN,
246
+ data={'plugin': info}
247
+ )
248
+ self.hook_manager.execute(HookType.ON_SHUTDOWN, ctx)
249
+
250
+ # Unregister hooks
251
+ self.hook_manager.unregister_all(name)
252
+
253
+ # Unregister from registry
254
+ self.registry.unregister_plugin(name)
255
+
256
+ # Remove from loaded paths
257
+ if info.source_path:
258
+ path_str = str(info.source_path)
259
+ if path_str in self._loaded_paths:
260
+ self._loaded_paths.remove(path_str)
261
+
262
+ logger.info(f"Unloaded plugin: {name}")
263
+ return True
264
+
265
+ def reload_plugin(self, name: str) -> str:
266
+ """
267
+ Reload a plugin (unload and load again).
268
+
269
+ Args:
270
+ name: Plugin name
271
+
272
+ Returns:
273
+ Plugin name
274
+
275
+ Raises:
276
+ PluginLoadError: If plugin cannot be reloaded
277
+ """
278
+ info = self.registry.get_plugin(name)
279
+ if not info:
280
+ raise PluginLoadError(f"Plugin not found: {name}")
281
+
282
+ source_path = str(info.source_path)
283
+ self.unload_plugin(name)
284
+ return self.load_plugin(source_path)
285
+
286
+ def _load_installed_plugin(self, module_name: str) -> Optional[str]:
287
+ """Load a plugin from an installed package"""
288
+ try:
289
+ module = importlib.import_module(module_name)
290
+ if hasattr(module, 'get_manifest'):
291
+ manifest_data = module.get_manifest()
292
+ manifest = PluginManifest.from_dict(manifest_data)
293
+ self._register_plugin_from_module(manifest, module)
294
+ return manifest.name
295
+ except Exception as e:
296
+ logger.error(f"Error loading installed plugin {module_name}: {e}")
297
+ return None
298
+
299
+ def _register_plugin_from_module(self, manifest: PluginManifest, module: Any) -> None:
300
+ """Register a plugin loaded from a Python module"""
301
+ info = PluginInfo(
302
+ name=manifest.name,
303
+ version=manifest.version,
304
+ manifest=manifest,
305
+ source_path=Path(module.__file__).parent if hasattr(module, '__file__') else Path('.')
306
+ )
307
+ self.registry.register_plugin(info)
308
+
309
+ def _load_module_from_handler(self, handler_str: str, plugin_dir: Path) -> tuple:
310
+ """
311
+ Load a module and get the specified object.
312
+
313
+ Args:
314
+ handler_str: Handler string in format "module.path:ClassName"
315
+ plugin_dir: Plugin directory
316
+
317
+ Returns:
318
+ Tuple of (module, object)
319
+ """
320
+ if ':' not in handler_str:
321
+ raise PluginLoadError(f"Invalid handler format: {handler_str}. Expected 'module:Class'")
322
+
323
+ module_path, obj_name = handler_str.rsplit(':', 1)
324
+
325
+ # Try to import the module
326
+ try:
327
+ module = importlib.import_module(module_path)
328
+ except ImportError:
329
+ # Try as relative path from plugin dir
330
+ try:
331
+ module_file = plugin_dir / (module_path.replace('.', '/') + '.py')
332
+ if not module_file.exists():
333
+ module_file = plugin_dir / 'src' / (module_path.replace('.', '/') + '.py')
334
+
335
+ if not module_file.exists():
336
+ raise PluginLoadError(f"Module not found: {module_path}")
337
+
338
+ spec = importlib.util.spec_from_file_location(module_path, module_file)
339
+ module = importlib.util.module_from_spec(spec)
340
+ spec.loader.exec_module(module)
341
+ except Exception as e:
342
+ raise PluginLoadError(f"Failed to import module {module_path}: {e}")
343
+
344
+ # Get the object
345
+ if not hasattr(module, obj_name):
346
+ raise PluginLoadError(f"Object not found in module: {obj_name}")
347
+
348
+ return module, getattr(module, obj_name)
349
+
350
+ def _load_tag_handlers(self, manifest: PluginManifest, info: PluginInfo, plugin_dir: Path) -> None:
351
+ """Load tag handlers from manifest"""
352
+ for tag_def in manifest.tags:
353
+ try:
354
+ module, handler_class = self._load_module_from_handler(tag_def.handler, plugin_dir)
355
+
356
+ # Instantiate handler
357
+ if isinstance(handler_class, type):
358
+ handler = handler_class()
359
+ else:
360
+ handler = handler_class
361
+
362
+ # Set prefix if not set
363
+ if hasattr(handler, 'prefix') and not handler.prefix:
364
+ handler.prefix = tag_def.prefix
365
+
366
+ # Register
367
+ self.registry.register_tag_handler(tag_def.prefix, handler, manifest.name)
368
+ info.tag_handlers[tag_def.prefix] = handler
369
+ info.loaded_modules[tag_def.handler] = module
370
+
371
+ logger.debug(f"Loaded tag handler: {tag_def.prefix}:* from {tag_def.handler}")
372
+
373
+ except Exception as e:
374
+ logger.error(f"Failed to load tag handler {tag_def.prefix}: {e}")
375
+ raise
376
+
377
+ def _load_node_classes(self, manifest: PluginManifest, info: PluginInfo, plugin_dir: Path) -> None:
378
+ """Load AST node classes from manifest"""
379
+ for node_def in manifest.nodes:
380
+ try:
381
+ module, node_class = self._load_module_from_handler(node_def.handler, plugin_dir)
382
+
383
+ self.registry.register_node_class(node_def.name, node_class, manifest.name)
384
+ info.node_classes[node_def.name] = node_class
385
+ info.loaded_modules[node_def.handler] = module
386
+
387
+ logger.debug(f"Loaded node class: {node_def.name}")
388
+
389
+ except Exception as e:
390
+ logger.error(f"Failed to load node class {node_def.name}: {e}")
391
+ raise
392
+
393
+ def _load_adapters(self, manifest: PluginManifest, info: PluginInfo, plugin_dir: Path) -> None:
394
+ """Load adapters from manifest"""
395
+ for adapter_def in manifest.adapters:
396
+ try:
397
+ module, adapter_class = self._load_module_from_handler(adapter_def.handler, plugin_dir)
398
+
399
+ # Instantiate if class
400
+ if isinstance(adapter_class, type):
401
+ adapter = adapter_class()
402
+ else:
403
+ adapter = adapter_class
404
+
405
+ self.registry.register_adapter(adapter_def.name, adapter, manifest.name)
406
+ info.adapters[adapter_def.name] = adapter
407
+ info.loaded_modules[adapter_def.handler] = module
408
+
409
+ logger.debug(f"Loaded adapter: {adapter_def.name}")
410
+
411
+ except Exception as e:
412
+ logger.error(f"Failed to load adapter {adapter_def.name}: {e}")
413
+ raise
414
+
415
+ def _load_cli_commands(self, manifest: PluginManifest, info: PluginInfo, plugin_dir: Path) -> None:
416
+ """Load CLI commands from manifest"""
417
+ for cmd_def in manifest.cli_commands:
418
+ try:
419
+ module, handler = self._load_module_from_handler(cmd_def.handler, plugin_dir)
420
+
421
+ self.registry.register_cli_command(
422
+ cmd_def.name,
423
+ handler,
424
+ manifest.name,
425
+ cmd_def.aliases
426
+ )
427
+ info.cli_commands[cmd_def.name] = handler
428
+ info.loaded_modules[cmd_def.handler] = module
429
+
430
+ logger.debug(f"Loaded CLI command: {cmd_def.name}")
431
+
432
+ except Exception as e:
433
+ logger.error(f"Failed to load CLI command {cmd_def.name}: {e}")
434
+ raise
435
+
436
+ def _load_hooks(self, manifest: PluginManifest, info: PluginInfo, plugin_dir: Path) -> None:
437
+ """Load lifecycle hooks from manifest"""
438
+ for hook_def in manifest.hooks:
439
+ try:
440
+ module, handler = self._load_module_from_handler(hook_def.handler, plugin_dir)
441
+
442
+ # Convert string to HookType
443
+ hook_type = HookType(hook_def.hook_type)
444
+
445
+ self.hook_manager.register(
446
+ hook_type,
447
+ handler,
448
+ manifest.name,
449
+ hook_def.priority
450
+ )
451
+ info.loaded_modules[hook_def.handler] = module
452
+
453
+ logger.debug(f"Loaded hook: {hook_def.hook_type} from {hook_def.handler}")
454
+
455
+ except Exception as e:
456
+ logger.error(f"Failed to load hook {hook_def.hook_type}: {e}")
457
+ raise
458
+
459
+ def get_loaded_plugins(self) -> List[str]:
460
+ """Get names of all loaded plugins"""
461
+ return list(self.registry.get_all_plugins().keys())
462
+
463
+
464
+ # Global loader instance
465
+ _global_loader: Optional[PluginLoader] = None
466
+
467
+
468
+ def get_loader() -> PluginLoader:
469
+ """Get the global plugin loader instance"""
470
+ global _global_loader
471
+ if _global_loader is None:
472
+ _global_loader = PluginLoader()
473
+ return _global_loader
474
+
475
+
476
+ def set_loader(loader: PluginLoader) -> None:
477
+ """Set the global plugin loader instance"""
478
+ global _global_loader
479
+ _global_loader = loader