open-swarm 0.1.1743070217__py3-none-any.whl → 0.1.1743364176__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 (217) hide show
  1. open_swarm-0.1.1743364176.dist-info/METADATA +286 -0
  2. open_swarm-0.1.1743364176.dist-info/RECORD +260 -0
  3. {open_swarm-0.1.1743070217.dist-info → open_swarm-0.1.1743364176.dist-info}/WHEEL +1 -2
  4. open_swarm-0.1.1743364176.dist-info/entry_points.txt +2 -0
  5. swarm/__init__.py +0 -2
  6. swarm/auth.py +53 -49
  7. swarm/blueprints/README.md +67 -0
  8. swarm/blueprints/burnt_noodles/blueprint_burnt_noodles.py +412 -0
  9. swarm/blueprints/chatbot/blueprint_chatbot.py +98 -0
  10. swarm/blueprints/chatbot/templates/chatbot/chatbot.html +33 -0
  11. swarm/blueprints/digitalbutlers/blueprint_digitalbutlers.py +183 -0
  12. swarm/blueprints/dilbot_universe/blueprint_dilbot_universe.py +285 -0
  13. swarm/blueprints/divine_code/__init__.py +0 -0
  14. swarm/blueprints/divine_code/apps.py +11 -0
  15. swarm/blueprints/divine_code/blueprint_divine_code.py +219 -0
  16. swarm/blueprints/django_chat/apps.py +6 -0
  17. swarm/blueprints/django_chat/blueprint_django_chat.py +84 -0
  18. swarm/blueprints/django_chat/templates/django_chat/django_chat_webpage.html +37 -0
  19. swarm/blueprints/django_chat/urls.py +8 -0
  20. swarm/blueprints/django_chat/views.py +32 -0
  21. swarm/blueprints/echocraft/blueprint_echocraft.py +44 -0
  22. swarm/blueprints/family_ties/apps.py +11 -0
  23. swarm/blueprints/family_ties/blueprint_family_ties.py +152 -0
  24. swarm/blueprints/family_ties/models.py +19 -0
  25. swarm/blueprints/family_ties/serializers.py +7 -0
  26. swarm/blueprints/family_ties/settings.py +16 -0
  27. swarm/blueprints/family_ties/urls.py +10 -0
  28. swarm/blueprints/family_ties/views.py +26 -0
  29. swarm/blueprints/flock/__init__.py +0 -0
  30. swarm/blueprints/gaggle/blueprint_gaggle.py +184 -0
  31. swarm/blueprints/gotchaman/blueprint_gotchaman.py +232 -0
  32. swarm/blueprints/mcp_demo/blueprint_mcp_demo.py +133 -0
  33. swarm/blueprints/messenger/templates/messenger/messenger.html +46 -0
  34. swarm/blueprints/mission_improbable/blueprint_mission_improbable.py +234 -0
  35. swarm/blueprints/monkai_magic/blueprint_monkai_magic.py +248 -0
  36. swarm/blueprints/nebula_shellz/blueprint_nebula_shellz.py +156 -0
  37. swarm/blueprints/omniplex/blueprint_omniplex.py +221 -0
  38. swarm/blueprints/rue_code/__init__.py +0 -0
  39. swarm/blueprints/rue_code/blueprint_rue_code.py +291 -0
  40. swarm/blueprints/suggestion/blueprint_suggestion.py +110 -0
  41. swarm/blueprints/unapologetic_press/blueprint_unapologetic_press.py +298 -0
  42. swarm/blueprints/whiskeytango_foxtrot/__init__.py +0 -0
  43. swarm/blueprints/whiskeytango_foxtrot/apps.py +11 -0
  44. swarm/blueprints/whiskeytango_foxtrot/blueprint_whiskeytango_foxtrot.py +256 -0
  45. swarm/extensions/blueprint/__init__.py +30 -15
  46. swarm/extensions/blueprint/agent_utils.py +16 -40
  47. swarm/extensions/blueprint/blueprint_base.py +141 -543
  48. swarm/extensions/blueprint/blueprint_discovery.py +112 -98
  49. swarm/extensions/blueprint/cli_handler.py +185 -0
  50. swarm/extensions/blueprint/config_loader.py +122 -0
  51. swarm/extensions/blueprint/django_utils.py +181 -79
  52. swarm/extensions/blueprint/interactive_mode.py +1 -1
  53. swarm/extensions/config/config_loader.py +83 -200
  54. swarm/extensions/launchers/build_swarm_wrapper.py +0 -0
  55. swarm/extensions/launchers/swarm_cli.py +199 -287
  56. swarm/llm/chat_completion.py +26 -55
  57. swarm/management/__init__.py +0 -0
  58. swarm/management/commands/__init__.py +0 -0
  59. swarm/management/commands/runserver.py +58 -0
  60. swarm/permissions.py +38 -0
  61. swarm/serializers.py +96 -5
  62. swarm/settings.py +95 -110
  63. swarm/static/contrib/fonts/fontawesome-webfont.ttf +7 -0
  64. swarm/static/contrib/fonts/fontawesome-webfont.woff +7 -0
  65. swarm/static/contrib/fonts/fontawesome-webfont.woff2 +7 -0
  66. swarm/static/contrib/markedjs/marked.min.js +6 -0
  67. swarm/static/contrib/tabler-icons/adjustments-horizontal.svg +27 -0
  68. swarm/static/contrib/tabler-icons/alert-triangle.svg +21 -0
  69. swarm/static/contrib/tabler-icons/archive.svg +21 -0
  70. swarm/static/contrib/tabler-icons/artboard.svg +27 -0
  71. swarm/static/contrib/tabler-icons/automatic-gearbox.svg +23 -0
  72. swarm/static/contrib/tabler-icons/box-multiple.svg +19 -0
  73. swarm/static/contrib/tabler-icons/carambola.svg +19 -0
  74. swarm/static/contrib/tabler-icons/copy.svg +20 -0
  75. swarm/static/contrib/tabler-icons/download.svg +21 -0
  76. swarm/static/contrib/tabler-icons/edit.svg +21 -0
  77. swarm/static/contrib/tabler-icons/filled/carambola.svg +13 -0
  78. swarm/static/contrib/tabler-icons/filled/paint.svg +13 -0
  79. swarm/static/contrib/tabler-icons/headset.svg +22 -0
  80. swarm/static/contrib/tabler-icons/layout-sidebar-left-collapse.svg +21 -0
  81. swarm/static/contrib/tabler-icons/layout-sidebar-left-expand.svg +21 -0
  82. swarm/static/contrib/tabler-icons/layout-sidebar-right-collapse.svg +21 -0
  83. swarm/static/contrib/tabler-icons/layout-sidebar-right-expand.svg +21 -0
  84. swarm/static/contrib/tabler-icons/message-chatbot.svg +22 -0
  85. swarm/static/contrib/tabler-icons/message-star.svg +22 -0
  86. swarm/static/contrib/tabler-icons/message-x.svg +23 -0
  87. swarm/static/contrib/tabler-icons/message.svg +21 -0
  88. swarm/static/contrib/tabler-icons/paperclip.svg +18 -0
  89. swarm/static/contrib/tabler-icons/playlist-add.svg +22 -0
  90. swarm/static/contrib/tabler-icons/robot.svg +26 -0
  91. swarm/static/contrib/tabler-icons/search.svg +19 -0
  92. swarm/static/contrib/tabler-icons/settings.svg +20 -0
  93. swarm/static/contrib/tabler-icons/thumb-down.svg +19 -0
  94. swarm/static/contrib/tabler-icons/thumb-up.svg +19 -0
  95. swarm/static/css/dropdown.css +22 -0
  96. swarm/static/htmx/htmx.min.js +0 -0
  97. swarm/static/js/dropdown.js +23 -0
  98. swarm/static/rest_mode/css/base.css +470 -0
  99. swarm/static/rest_mode/css/chat-history.css +286 -0
  100. swarm/static/rest_mode/css/chat.css +251 -0
  101. swarm/static/rest_mode/css/chatbot.css +74 -0
  102. swarm/static/rest_mode/css/chatgpt.css +62 -0
  103. swarm/static/rest_mode/css/colors/corporate.css +74 -0
  104. swarm/static/rest_mode/css/colors/pastel.css +81 -0
  105. swarm/static/rest_mode/css/colors/tropical.css +82 -0
  106. swarm/static/rest_mode/css/general.css +142 -0
  107. swarm/static/rest_mode/css/layout.css +167 -0
  108. swarm/static/rest_mode/css/layouts/messenger-layout.css +17 -0
  109. swarm/static/rest_mode/css/layouts/minimalist-layout.css +57 -0
  110. swarm/static/rest_mode/css/layouts/mobile-layout.css +8 -0
  111. swarm/static/rest_mode/css/messages.css +84 -0
  112. swarm/static/rest_mode/css/messenger.css +135 -0
  113. swarm/static/rest_mode/css/settings.css +91 -0
  114. swarm/static/rest_mode/css/simple.css +44 -0
  115. swarm/static/rest_mode/css/slack.css +58 -0
  116. swarm/static/rest_mode/css/style.css +156 -0
  117. swarm/static/rest_mode/css/theme.css +30 -0
  118. swarm/static/rest_mode/css/toast.css +40 -0
  119. swarm/static/rest_mode/js/auth.js +9 -0
  120. swarm/static/rest_mode/js/blueprint.js +41 -0
  121. swarm/static/rest_mode/js/blueprintUtils.js +12 -0
  122. swarm/static/rest_mode/js/chatLogic.js +79 -0
  123. swarm/static/rest_mode/js/debug.js +63 -0
  124. swarm/static/rest_mode/js/events.js +98 -0
  125. swarm/static/rest_mode/js/main.js +19 -0
  126. swarm/static/rest_mode/js/messages.js +264 -0
  127. swarm/static/rest_mode/js/messengerLogic.js +355 -0
  128. swarm/static/rest_mode/js/modules/apiService.js +84 -0
  129. swarm/static/rest_mode/js/modules/blueprintManager.js +162 -0
  130. swarm/static/rest_mode/js/modules/chatHistory.js +110 -0
  131. swarm/static/rest_mode/js/modules/debugLogger.js +14 -0
  132. swarm/static/rest_mode/js/modules/eventHandlers.js +107 -0
  133. swarm/static/rest_mode/js/modules/messageProcessor.js +120 -0
  134. swarm/static/rest_mode/js/modules/state.js +7 -0
  135. swarm/static/rest_mode/js/modules/userInteractions.js +29 -0
  136. swarm/static/rest_mode/js/modules/validation.js +23 -0
  137. swarm/static/rest_mode/js/rendering.js +119 -0
  138. swarm/static/rest_mode/js/settings.js +130 -0
  139. swarm/static/rest_mode/js/sidebar.js +94 -0
  140. swarm/static/rest_mode/js/simpleLogic.js +37 -0
  141. swarm/static/rest_mode/js/slackLogic.js +66 -0
  142. swarm/static/rest_mode/js/splash.js +76 -0
  143. swarm/static/rest_mode/js/theme.js +111 -0
  144. swarm/static/rest_mode/js/toast.js +36 -0
  145. swarm/static/rest_mode/js/ui.js +265 -0
  146. swarm/static/rest_mode/js/validation.js +57 -0
  147. swarm/static/rest_mode/svg/animated_spinner.svg +12 -0
  148. swarm/static/rest_mode/svg/arrow_down.svg +5 -0
  149. swarm/static/rest_mode/svg/arrow_left.svg +5 -0
  150. swarm/static/rest_mode/svg/arrow_right.svg +5 -0
  151. swarm/static/rest_mode/svg/arrow_up.svg +5 -0
  152. swarm/static/rest_mode/svg/attach.svg +8 -0
  153. swarm/static/rest_mode/svg/avatar.svg +7 -0
  154. swarm/static/rest_mode/svg/canvas.svg +6 -0
  155. swarm/static/rest_mode/svg/chat_history.svg +4 -0
  156. swarm/static/rest_mode/svg/close.svg +5 -0
  157. swarm/static/rest_mode/svg/copy.svg +4 -0
  158. swarm/static/rest_mode/svg/dark_mode.svg +3 -0
  159. swarm/static/rest_mode/svg/edit.svg +5 -0
  160. swarm/static/rest_mode/svg/layout.svg +9 -0
  161. swarm/static/rest_mode/svg/logo.svg +29 -0
  162. swarm/static/rest_mode/svg/logout.svg +5 -0
  163. swarm/static/rest_mode/svg/mobile.svg +5 -0
  164. swarm/static/rest_mode/svg/new_chat.svg +4 -0
  165. swarm/static/rest_mode/svg/not_visible.svg +5 -0
  166. swarm/static/rest_mode/svg/plus.svg +7 -0
  167. swarm/static/rest_mode/svg/run_code.svg +6 -0
  168. swarm/static/rest_mode/svg/save.svg +4 -0
  169. swarm/static/rest_mode/svg/search.svg +6 -0
  170. swarm/static/rest_mode/svg/settings.svg +4 -0
  171. swarm/static/rest_mode/svg/speaker.svg +5 -0
  172. swarm/static/rest_mode/svg/stop.svg +6 -0
  173. swarm/static/rest_mode/svg/thumbs_down.svg +3 -0
  174. swarm/static/rest_mode/svg/thumbs_up.svg +3 -0
  175. swarm/static/rest_mode/svg/toggle_off.svg +6 -0
  176. swarm/static/rest_mode/svg/toggle_on.svg +6 -0
  177. swarm/static/rest_mode/svg/trash.svg +10 -0
  178. swarm/static/rest_mode/svg/undo.svg +3 -0
  179. swarm/static/rest_mode/svg/visible.svg +8 -0
  180. swarm/static/rest_mode/svg/voice.svg +10 -0
  181. swarm/templates/account/login.html +22 -0
  182. swarm/templates/account/signup.html +32 -0
  183. swarm/templates/base.html +30 -0
  184. swarm/templates/chat.html +43 -0
  185. swarm/templates/index.html +35 -0
  186. swarm/templates/rest_mode/components/chat_sidebar.html +55 -0
  187. swarm/templates/rest_mode/components/header.html +45 -0
  188. swarm/templates/rest_mode/components/main_chat_pane.html +41 -0
  189. swarm/templates/rest_mode/components/settings_dialog.html +97 -0
  190. swarm/templates/rest_mode/components/splash_screen.html +7 -0
  191. swarm/templates/rest_mode/components/top_bar.html +28 -0
  192. swarm/templates/rest_mode/message_ui.html +50 -0
  193. swarm/templates/rest_mode/slackbot.html +30 -0
  194. swarm/templates/simple_blueprint_page.html +24 -0
  195. swarm/templates/websocket_partials/final_system_message.html +3 -0
  196. swarm/templates/websocket_partials/system_message.html +4 -0
  197. swarm/templates/websocket_partials/user_message.html +5 -0
  198. swarm/urls.py +57 -74
  199. swarm/utils/log_utils.py +63 -0
  200. swarm/views/api_views.py +48 -39
  201. swarm/views/chat_views.py +156 -70
  202. swarm/views/core_views.py +85 -90
  203. swarm/views/model_views.py +64 -121
  204. swarm/views/utils.py +65 -441
  205. open_swarm-0.1.1743070217.dist-info/METADATA +0 -258
  206. open_swarm-0.1.1743070217.dist-info/RECORD +0 -89
  207. open_swarm-0.1.1743070217.dist-info/entry_points.txt +0 -3
  208. open_swarm-0.1.1743070217.dist-info/top_level.txt +0 -1
  209. swarm/agent/agent.py +0 -49
  210. swarm/core.py +0 -326
  211. swarm/extensions/mcp/__init__.py +0 -1
  212. swarm/extensions/mcp/cache_utils.py +0 -36
  213. swarm/extensions/mcp/mcp_client.py +0 -341
  214. swarm/extensions/mcp/mcp_constants.py +0 -7
  215. swarm/extensions/mcp/mcp_tool_provider.py +0 -110
  216. swarm/types.py +0 -126
  217. {open_swarm-0.1.1743070217.dist-info → open_swarm-0.1.1743364176.dist-info}/licenses/LICENSE +0 -0
@@ -5,10 +5,13 @@ Django integration utilities for blueprint extensions.
5
5
  import logging
6
6
  import os
7
7
  import importlib.util
8
+ import sys # Import sys
9
+ import inspect # Import inspect
8
10
  from typing import Any, TYPE_CHECKING
9
11
  from django.conf import settings # Import settings directly
10
12
  # Import necessary URL handling functions
11
- from django.urls import clear_url_caches, get_resolver, get_urlconf, set_urlconf, URLPattern, URLResolver
13
+ from django.urls import clear_url_caches, get_resolver, get_urlconf, set_urlconf, URLPattern, URLResolver, path, include # Added path, include
14
+ from django.utils.module_loading import import_module # More standard way to import
12
15
  from collections import OrderedDict
13
16
  from django.apps import apps as django_apps
14
17
 
@@ -20,6 +23,7 @@ logger = logging.getLogger(__name__)
20
23
 
21
24
  def register_django_components(blueprint: 'BlueprintBase') -> None:
22
25
  """Register Django settings and URLs if applicable for the given blueprint."""
26
+ # Use getattr to safely check _urls_registered, default to False if not present
23
27
  if blueprint.skip_django_registration or getattr(blueprint, "_urls_registered", False):
24
28
  logger.debug(f"Skipping Django registration for {blueprint.__class__.__name__}: Skipped by flag or already registered.")
25
29
  return
@@ -29,52 +33,121 @@ def register_django_components(blueprint: 'BlueprintBase') -> None:
29
33
 
30
34
  try:
31
35
  # App readiness check less critical now if called within test fixtures after setup
32
- if not django_apps.ready and not getattr(settings, 'TESTING', False):
33
- logger.debug("Django apps not ready; registration likely handled by AppConfig.ready().")
34
- return
36
+ # but still useful to avoid redundant work during normal server startup.
37
+ # Let's assume if DJANGO_SETTINGS_MODULE is set, setup has likely happened or will happen.
38
+ # if not django_apps.ready and not getattr(settings, 'TESTING', False):
39
+ # logger.debug("Django apps not ready; registration likely handled by AppConfig.ready().")
40
+ # return
35
41
 
36
42
  _load_local_settings(blueprint)
37
- _merge_installed_apps(blueprint) # Still attempt, might need restart/reload
43
+ _merge_installed_apps(blueprint) # Attempt merge
38
44
 
39
45
  if hasattr(blueprint, 'register_blueprint_urls') and callable(blueprint.register_blueprint_urls):
40
46
  logger.debug(f"Calling blueprint-specific register_blueprint_urls for {blueprint.__class__.__name__}")
41
47
  blueprint.register_blueprint_urls()
42
- blueprint._urls_registered = True
48
+ # Assume the custom function sets _urls_registered if it succeeds
49
+ # blueprint._urls_registered = True # Let the custom function handle this flag
43
50
  else:
44
51
  logger.debug(f"Using generic URL registration for {blueprint.__class__.__name__}")
45
52
  _register_blueprint_urls_generic(blueprint)
46
53
 
47
- except ImportError:
48
- logger.warning("Django not available; skipping Django component registration.")
54
+ except ImportError as e:
55
+ # Catch cases where Django itself or essential parts are not installed/available
56
+ logger.warning(f"Django not fully available; skipping Django component registration for {blueprint.__class__.__name__}. Error: {e}")
49
57
  except Exception as e:
50
58
  logger.error(f"Failed to register Django components for {blueprint.__class__.__name__}: {e}", exc_info=True)
51
59
 
60
+
52
61
  def _load_local_settings(blueprint: 'BlueprintBase') -> None:
53
- """Load local settings.py from the blueprint's directory if it exists."""
62
+ """Load local settings.py from the blueprint's directory if it exists.
63
+ Handles being called when the blueprint module is '__main__'.
64
+ """
65
+ local_settings_path = None
66
+ settings_module_name = None # A unique name for the loaded settings module
67
+
54
68
  try:
55
- module_spec = importlib.util.find_spec(blueprint.__class__.__module__)
56
- if module_spec and module_spec.origin:
57
- blueprint_dir = os.path.dirname(module_spec.origin)
58
- local_settings_path = os.path.join(blueprint_dir, "settings.py")
59
- if os.path.isfile(local_settings_path):
60
- spec = importlib.util.spec_from_file_location(f"{blueprint.__class__.__module__}.local_settings", local_settings_path)
61
- if spec and spec.loader:
62
- local_settings = importlib.util.module_from_spec(spec)
63
- blueprint.local_settings = local_settings
64
- spec.loader.exec_module(local_settings)
65
- logger.debug(f"Loaded local settings from {local_settings_path} for {blueprint.__class__.__name__}")
69
+ module_name = blueprint.__class__.__module__
70
+
71
+ if module_name == "__main__":
72
+ # --- Handling direct script execution ---
73
+ logger.debug(f"Blueprint class module is '__main__'. Determining path from file location.")
74
+ try:
75
+ # Get the path to the file where the blueprint class is defined
76
+ blueprint_file_path = inspect.getfile(blueprint.__class__)
77
+ blueprint_dir = os.path.dirname(blueprint_file_path)
78
+ local_settings_path = os.path.join(blueprint_dir, "settings.py")
79
+ # Create a unique, safe module name based on the blueprint class
80
+ # Using a prefix to avoid potential clashes with real modules
81
+ settings_module_name = f"_swarm_local_settings_{blueprint.__class__.__name__}"
82
+ logger.debug(f"Derived potential local settings path for __main__: {local_settings_path}")
83
+ except TypeError as e:
84
+ logger.error(f"Could not determine file path for blueprint class {blueprint.__class__.__name__} when run as __main__: {e}")
85
+ setattr(blueprint, 'local_settings', None) # Ensure attribute exists
86
+ return
87
+ except Exception as e:
88
+ logger.error(f"Unexpected error getting blueprint file path for __main__: {e}", exc_info=True)
89
+ setattr(blueprint, 'local_settings', None)
90
+ return
91
+ else:
92
+ # --- Handling standard import execution ---
93
+ logger.debug(f"Blueprint class module is '{module_name}'. Using importlib.")
94
+ try:
95
+ module_spec = importlib.util.find_spec(module_name)
96
+ if module_spec and module_spec.origin:
97
+ blueprint_dir = os.path.dirname(module_spec.origin)
98
+ local_settings_path = os.path.join(blueprint_dir, "settings.py")
99
+ # Use a name relative to the original module to avoid clashes
100
+ settings_module_name = f"{module_name}.local_settings"
101
+ logger.debug(f"Derived potential local settings path via importlib: {local_settings_path}")
66
102
  else:
67
- logger.warning(f"Could not create module spec for local settings at {local_settings_path}")
68
- blueprint.local_settings = None
69
- else: blueprint.local_settings = None
70
- else: blueprint.local_settings = None
103
+ logger.debug(f"Could not find module spec or origin for '{module_name}'. Cannot determine local settings path.")
104
+ setattr(blueprint, 'local_settings', None)
105
+ return
106
+ except Exception as e: # Catch potential errors during find_spec
107
+ logger.error(f"Error finding spec for module '{module_name}': {e}", exc_info=True)
108
+ setattr(blueprint, 'local_settings', None)
109
+ return
110
+
111
+ # --- Common Loading Logic ---
112
+ if local_settings_path and os.path.isfile(local_settings_path):
113
+ # Check if already loaded (using the determined name)
114
+ if settings_module_name in sys.modules:
115
+ logger.debug(f"Local settings module '{settings_module_name}' already loaded. Assigning.")
116
+ blueprint.local_settings = sys.modules[settings_module_name]
117
+ # Optionally, re-apply settings if your local_settings has an apply function
118
+ # if hasattr(blueprint.local_settings, 'apply_settings'):
119
+ # blueprint.local_settings.apply_settings()
120
+ return
121
+
122
+ spec = importlib.util.spec_from_file_location(settings_module_name, local_settings_path)
123
+ if spec and spec.loader:
124
+ local_settings = importlib.util.module_from_spec(spec)
125
+ # Add to sys.modules BEFORE execution to handle potential internal imports
126
+ sys.modules[settings_module_name] = local_settings
127
+ blueprint.local_settings = local_settings # Assign early
128
+ logger.info(f"Loading and executing local settings from '{local_settings_path}' as '{settings_module_name}' for '{blueprint.__class__.__name__}'.")
129
+ spec.loader.exec_module(local_settings)
130
+ logger.debug(f"Finished executing local settings module '{settings_module_name}'.")
131
+ else:
132
+ logger.warning(f"Could not create module spec/loader for local settings at '{local_settings_path}'")
133
+ setattr(blueprint, 'local_settings', None)
134
+ else:
135
+ logger.debug(f"No local settings file found at '{local_settings_path}' for {blueprint.__class__.__name__}.")
136
+ setattr(blueprint, 'local_settings', None)
137
+
71
138
  except Exception as e:
72
139
  logger.error(f"Error loading local settings for {blueprint.__class__.__name__}: {e}", exc_info=True)
73
- blueprint.local_settings = None
140
+ # Explicitly check for the original error if needed
141
+ if isinstance(e, ValueError) and "__spec__ is None" in str(e):
142
+ logger.critical("Original Error Context: Failed during importlib processing, likely due to __main__ module details.")
143
+ setattr(blueprint, 'local_settings', None) # Ensure attribute exists even on error
74
144
 
75
145
 
76
146
  def _merge_installed_apps(blueprint: 'BlueprintBase') -> None:
77
- """Merge INSTALLED_APPS from blueprint's local settings into main Django settings."""
147
+ """Merge INSTALLED_APPS from blueprint's local settings into main Django settings.
148
+ Note: This might require a server restart/reload to take full effect.
149
+ """
150
+ # Check if local_settings was successfully loaded and has INSTALLED_APPS
78
151
  if hasattr(blueprint, "local_settings") and blueprint.local_settings and hasattr(blueprint.local_settings, "INSTALLED_APPS"):
79
152
  try:
80
153
  blueprint_apps = getattr(blueprint.local_settings, "INSTALLED_APPS", [])
@@ -82,118 +155,147 @@ def _merge_installed_apps(blueprint: 'BlueprintBase') -> None:
82
155
  logger.warning(f"Blueprint {blueprint.__class__.__name__}'s local INSTALLED_APPS is not a list or tuple.")
83
156
  return
84
157
 
85
- apps_added = False
158
+ # Ensure settings.INSTALLED_APPS is available and is a list
159
+ if not hasattr(settings, 'INSTALLED_APPS'):
160
+ logger.error("Cannot merge apps: django.conf.settings.INSTALLED_APPS is not defined.")
161
+ return
86
162
  if isinstance(settings.INSTALLED_APPS, tuple):
87
- settings.INSTALLED_APPS = list(settings.INSTALLED_APPS)
163
+ settings.INSTALLED_APPS = list(settings.INSTALLED_APPS)
164
+ elif not isinstance(settings.INSTALLED_APPS, list):
165
+ logger.error(f"Cannot merge apps: django.conf.settings.INSTALLED_APPS is not a list or tuple (type: {type(settings.INSTALLED_APPS)}).")
166
+ return
88
167
 
168
+ apps_added_names = []
89
169
  for app in blueprint_apps:
90
170
  if app not in settings.INSTALLED_APPS:
91
- settings.INSTALLED_APPS.append(app)
92
- apps_added = True
93
- logger.debug(f"Added app '{app}' from blueprint {blueprint.__class__.__name__} to INSTALLED_APPS.")
94
-
95
- if apps_added:
96
- logger.info(f"Merged INSTALLED_APPS from blueprint {blueprint.__class__.__name__}. App registry reload might be needed.")
97
- # Attempt app registry reload - Use with caution!
98
- try:
99
- logger.debug("Attempting to reload Django app registry...")
100
- django_apps.app_configs = OrderedDict()
101
- django_apps.ready = False
102
- django_apps.clear_cache()
103
- django_apps.populate(settings.INSTALLED_APPS)
104
- logger.info("Successfully reloaded Django app registry.")
105
- except RuntimeError as e:
106
- logger.error(f"Could not reload app registry (likely reentrant call): {e}")
107
- except Exception as e:
108
- logger.error(f"Error reloading Django app registry: {e}", exc_info=True)
171
+ settings.INSTALLED_APPS.append(app) # Directly modify the list
172
+ apps_added_names.append(app)
173
+ logger.debug(f"Added app '{app}' from blueprint {blueprint.__class__.__name__} to INSTALLED_APPS in settings.")
174
+
175
+ if apps_added_names:
176
+ logger.info(f"Merged INSTALLED_APPS from blueprint {blueprint.__class__.__name__}: {apps_added_names}. App registry reload/server restart might be needed.")
109
177
 
178
+ # Attempt app registry reload - Use with extreme caution! Can lead to instability.
179
+ # It's generally safer to rely on server restart/reload mechanisms.
180
+ if getattr(settings, 'AUTO_RELOAD_APP_REGISTRY', False): # Add a setting to control this
181
+ try:
182
+ logger.warning("Attempting to dynamically reload Django app registry (Experimental)...")
183
+ django_apps.app_configs = OrderedDict()
184
+ django_apps.ready = False
185
+ django_apps.clear_cache()
186
+ django_apps.populate(settings.INSTALLED_APPS)
187
+ logger.info("Successfully reloaded Django app registry.")
188
+ except RuntimeError as e:
189
+ logger.error(f"Could not reload app registry (likely reentrant call): {e}")
190
+ except Exception as e:
191
+ logger.error(f"Error reloading Django app registry: {e}", exc_info=True)
192
+ else:
193
+ logger.debug("Automatic app registry reload is disabled (settings.AUTO_RELOAD_APP_REGISTRY=False).")
110
194
 
111
195
  except ImportError:
112
- logger.error("Could not import django.conf.settings to merge INSTALLED_APPS.")
196
+ # This might happen if django.conf.settings itself wasn't importable earlier
197
+ logger.error("Could not import or access django.conf.settings to merge INSTALLED_APPS.")
113
198
  except Exception as e:
114
199
  logger.error(f"Error merging INSTALLED_APPS for {blueprint.__class__.__name__}: {e}", exc_info=True)
200
+ else:
201
+ logger.debug(f"No local settings or INSTALLED_APPS found for blueprint {blueprint.__class__.__name__} to merge.")
202
+
115
203
 
116
204
  def _register_blueprint_urls_generic(blueprint: 'BlueprintBase') -> None:
117
- """Generic function to register blueprint URLs based on metadata."""
205
+ """Generic function to register blueprint URLs based on metadata.
206
+ Dynamically adds patterns to the root urlconf's urlpatterns list.
207
+ """
208
+ # Check if already done for this blueprint instance
118
209
  if getattr(blueprint, "_urls_registered", False):
119
- logger.debug(f"URLs for {blueprint.__class__.__name__} already registered.")
210
+ logger.debug(f"URLs for {blueprint.__class__.__name__} already marked as registered.")
120
211
  return
121
212
 
122
- module_path = blueprint.metadata.get("django_modules", {}).get("urls")
123
- url_prefix = blueprint.metadata.get("url_prefix", "")
213
+ # Safely get metadata attributes
214
+ metadata = getattr(blueprint, 'metadata', {})
215
+ if not isinstance(metadata, dict):
216
+ logger.warning(f"Blueprint {blueprint.__class__.__name__} metadata is not a dictionary. Skipping URL registration.")
217
+ return
218
+ django_modules = metadata.get("django_modules", {})
219
+ module_path = django_modules.get("urls")
220
+ url_prefix = metadata.get("url_prefix", "")
124
221
 
125
222
  if not module_path:
126
223
  logger.debug(f"No 'urls' module specified in metadata for {blueprint.__class__.__name__}; skipping generic URL registration.")
127
224
  return
128
225
 
129
226
  try:
130
- from django.urls import include, path
131
- from importlib import import_module
132
-
133
227
  root_urlconf_name = settings.ROOT_URLCONF
134
228
  if not root_urlconf_name:
135
- logger.error("settings.ROOT_URLCONF is not set.")
229
+ logger.error("settings.ROOT_URLCONF is not set. Cannot register URLs.")
136
230
  return
137
231
 
138
- # --- Get the root urlpatterns list directly ---
139
- # This is potentially fragile if ROOT_URLCONF itself changes, but necessary for tests
232
+ # --- Get the root urlpatterns list dynamically ---
140
233
  try:
234
+ # Use Django's utility function for importing
141
235
  root_urlconf_module = import_module(root_urlconf_name)
142
236
  if not hasattr(root_urlconf_module, 'urlpatterns') or not isinstance(root_urlconf_module.urlpatterns, list):
143
- logger.error(f"Cannot modify urlpatterns in '{root_urlconf_name}'. It's missing or not a list.")
237
+ logger.error(f"Cannot modify urlpatterns in '{root_urlconf_name}'. 'urlpatterns' attribute is missing or not a list.")
144
238
  return
145
239
  root_urlpatterns = root_urlconf_module.urlpatterns
146
240
  except ImportError:
147
241
  logger.error(f"Could not import main URLconf '{root_urlconf_name}' to modify urlpatterns.")
148
242
  return
243
+ except Exception as e:
244
+ logger.error(f"Error accessing urlpatterns in '{root_urlconf_name}': {e}", exc_info=True)
245
+ return
149
246
 
150
247
  # Import the blueprint's URL module
151
248
  try:
152
249
  urls_module = import_module(module_path)
153
250
  if not hasattr(urls_module, "urlpatterns"):
154
- logger.debug(f"Blueprint URL module '{module_path}' has no 'urlpatterns'.")
251
+ logger.debug(f"Blueprint URL module '{module_path}' has no 'urlpatterns'. Skipping.")
252
+ # Mark as registered even if no patterns, to avoid re-attempting
155
253
  blueprint._urls_registered = True
156
254
  return
157
255
  except ImportError:
158
256
  logger.error(f"Could not import blueprint URL module: '{module_path}'")
159
257
  return
258
+ except Exception as e:
259
+ logger.error(f"Error importing or accessing urlpatterns in '{module_path}': {e}", exc_info=True)
260
+ return
160
261
 
262
+ # Prepare the new pattern
161
263
  if url_prefix and not url_prefix.endswith('/'): url_prefix += '/'
162
- app_name = blueprint.metadata.get("cli_name", blueprint.__class__.__name__.lower())
163
- new_pattern = path(url_prefix, include((urls_module, app_name)))
264
+ # Use blueprint's cli_name or class name as app_name for namespacing
265
+ app_name = metadata.get("cli_name", blueprint.__class__.__name__.lower().replace('blueprint', ''))
266
+ # Include the module directly for `include` to find its `urlpatterns`
267
+ new_pattern = path(url_prefix, include((urls_module, app_name))) # Pass tuple (module, app_name)
164
268
 
165
- # Check if an identical pattern already exists
269
+ # Check if an identical pattern (prefix + app_name) already exists
166
270
  already_exists = False
167
271
  for existing_pattern in root_urlpatterns:
168
- # Compare based on pattern regex and included module/app_name if possible
169
- if (isinstance(existing_pattern, (URLPattern, URLResolver)) and
170
- str(existing_pattern.pattern) == str(new_pattern.pattern) and
171
- getattr(existing_pattern, 'app_name', None) == app_name and
172
- getattr(existing_pattern, 'namespace', None) == getattr(new_pattern, 'namespace', None)): # Check namespace too
173
- # A bit more robust check, might need refinement
174
- logger.warning(f"URL pattern for prefix '{url_prefix}' and app '{app_name}' seems already registered. Skipping.")
272
+ # Check if it's a resolver (include()) and compare prefix and app_name
273
+ if (isinstance(existing_pattern, URLResolver) and
274
+ str(existing_pattern.pattern) == str(new_pattern.pattern) and # Compare URL prefix pattern
275
+ getattr(existing_pattern, 'app_name', None) == app_name): # Compare app_name
276
+ logger.warning(f"URL pattern for prefix '{url_prefix}' and app '{app_name}' seems already registered in '{root_urlconf_name}'. Skipping.")
175
277
  already_exists = True
176
278
  break
177
279
 
178
280
  if not already_exists:
179
281
  root_urlpatterns.append(new_pattern)
180
- logger.info(f"Dynamically registered URLs from '{module_path}' at prefix '{url_prefix}' (app_name: '{app_name}')")
282
+ logger.info(f"Dynamically registered URLs from '{module_path}' at prefix '{url_prefix}' (app_name: '{app_name}') into '{root_urlconf_name}'.")
181
283
 
182
- # --- Force update of URL resolver ---
284
+ # --- Force update of URL resolver (Important!) ---
183
285
  clear_url_caches()
184
- # Reload the root URLconf module itself
286
+ # Reloading the root URLconf module is generally needed for changes to take effect
185
287
  try:
186
- reload(root_urlconf_module)
288
+ importlib.reload(root_urlconf_module)
187
289
  logger.debug(f"Reloaded root URLconf module: {root_urlconf_name}")
188
290
  except Exception as e:
189
- logger.error(f"Failed to reload root URLconf module: {e}")
190
- # Try setting urlconf to None to force re-reading from settings
291
+ logger.error(f"Failed to reload root URLconf module '{root_urlconf_name}': {e}")
292
+ logger.warning("URL changes might not be active until server restart.")
293
+
294
+ # Explicitly reset the URLconf to force Django to re-read it
191
295
  set_urlconf(None)
192
- # Explicitly getting the resolver again might help
193
- resolver = get_resolver(get_urlconf())
194
- resolver._populate() # Re-populate cache
195
- logger.info(f"Cleared URL caches and attempted to refresh resolver for {root_urlconf_name}.")
296
+ logger.debug(f"Cleared URL caches and reset urlconf. Django should rebuild resolver on next request.")
196
297
 
298
+ # Mark this blueprint instance as having its URLs registered (or attempted)
197
299
  blueprint._urls_registered = True
198
300
 
199
301
  except ImportError as e:
@@ -20,7 +20,7 @@ prompted for input,
20
20
  ance's methods.
21
21
  """
22
22
  logger.debug("Starting interactive mode.")
23
- if not blueprint.starting_agent or not blueprint.swarm:
23
+ if not blueprint.starting_agent:
24
24
  logger.error("Starting agent or Swarm not initialized.")
25
25
  # --- FIX: Terminate string literal correctly ---
26
26
  raise ValueError("Starting agent and Swarm must be initialized.")