open-swarm 0.1.1743070217__py3-none-any.whl → 0.1.1743362777__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.
- open_swarm-0.1.1743362777.dist-info/METADATA +217 -0
- open_swarm-0.1.1743362777.dist-info/RECORD +260 -0
- {open_swarm-0.1.1743070217.dist-info → open_swarm-0.1.1743362777.dist-info}/WHEEL +1 -2
- open_swarm-0.1.1743362777.dist-info/entry_points.txt +2 -0
- swarm/__init__.py +0 -2
- swarm/auth.py +53 -49
- swarm/blueprints/README.md +67 -0
- swarm/blueprints/burnt_noodles/blueprint_burnt_noodles.py +412 -0
- swarm/blueprints/chatbot/blueprint_chatbot.py +98 -0
- swarm/blueprints/chatbot/templates/chatbot/chatbot.html +33 -0
- swarm/blueprints/digitalbutlers/blueprint_digitalbutlers.py +183 -0
- swarm/blueprints/dilbot_universe/blueprint_dilbot_universe.py +285 -0
- swarm/blueprints/divine_code/__init__.py +0 -0
- swarm/blueprints/divine_code/apps.py +11 -0
- swarm/blueprints/divine_code/blueprint_divine_code.py +219 -0
- swarm/blueprints/django_chat/apps.py +6 -0
- swarm/blueprints/django_chat/blueprint_django_chat.py +84 -0
- swarm/blueprints/django_chat/templates/django_chat/django_chat_webpage.html +37 -0
- swarm/blueprints/django_chat/urls.py +8 -0
- swarm/blueprints/django_chat/views.py +32 -0
- swarm/blueprints/echocraft/blueprint_echocraft.py +44 -0
- swarm/blueprints/family_ties/apps.py +11 -0
- swarm/blueprints/family_ties/blueprint_family_ties.py +152 -0
- swarm/blueprints/family_ties/models.py +19 -0
- swarm/blueprints/family_ties/serializers.py +7 -0
- swarm/blueprints/family_ties/settings.py +16 -0
- swarm/blueprints/family_ties/urls.py +10 -0
- swarm/blueprints/family_ties/views.py +26 -0
- swarm/blueprints/flock/__init__.py +0 -0
- swarm/blueprints/gaggle/blueprint_gaggle.py +184 -0
- swarm/blueprints/gotchaman/blueprint_gotchaman.py +232 -0
- swarm/blueprints/mcp_demo/blueprint_mcp_demo.py +133 -0
- swarm/blueprints/messenger/templates/messenger/messenger.html +46 -0
- swarm/blueprints/mission_improbable/blueprint_mission_improbable.py +234 -0
- swarm/blueprints/monkai_magic/blueprint_monkai_magic.py +248 -0
- swarm/blueprints/nebula_shellz/blueprint_nebula_shellz.py +156 -0
- swarm/blueprints/omniplex/blueprint_omniplex.py +221 -0
- swarm/blueprints/rue_code/__init__.py +0 -0
- swarm/blueprints/rue_code/blueprint_rue_code.py +291 -0
- swarm/blueprints/suggestion/blueprint_suggestion.py +110 -0
- swarm/blueprints/unapologetic_press/blueprint_unapologetic_press.py +298 -0
- swarm/blueprints/whiskeytango_foxtrot/__init__.py +0 -0
- swarm/blueprints/whiskeytango_foxtrot/apps.py +11 -0
- swarm/blueprints/whiskeytango_foxtrot/blueprint_whiskeytango_foxtrot.py +256 -0
- swarm/extensions/blueprint/__init__.py +30 -15
- swarm/extensions/blueprint/agent_utils.py +16 -40
- swarm/extensions/blueprint/blueprint_base.py +141 -543
- swarm/extensions/blueprint/blueprint_discovery.py +112 -98
- swarm/extensions/blueprint/cli_handler.py +185 -0
- swarm/extensions/blueprint/config_loader.py +122 -0
- swarm/extensions/blueprint/django_utils.py +181 -79
- swarm/extensions/blueprint/interactive_mode.py +1 -1
- swarm/extensions/config/config_loader.py +83 -200
- swarm/extensions/launchers/build_swarm_wrapper.py +0 -0
- swarm/extensions/launchers/swarm_cli.py +199 -287
- swarm/llm/chat_completion.py +26 -55
- swarm/management/__init__.py +0 -0
- swarm/management/commands/__init__.py +0 -0
- swarm/management/commands/runserver.py +58 -0
- swarm/permissions.py +38 -0
- swarm/serializers.py +96 -5
- swarm/settings.py +95 -110
- swarm/static/contrib/fonts/fontawesome-webfont.ttf +7 -0
- swarm/static/contrib/fonts/fontawesome-webfont.woff +7 -0
- swarm/static/contrib/fonts/fontawesome-webfont.woff2 +7 -0
- swarm/static/contrib/markedjs/marked.min.js +6 -0
- swarm/static/contrib/tabler-icons/adjustments-horizontal.svg +27 -0
- swarm/static/contrib/tabler-icons/alert-triangle.svg +21 -0
- swarm/static/contrib/tabler-icons/archive.svg +21 -0
- swarm/static/contrib/tabler-icons/artboard.svg +27 -0
- swarm/static/contrib/tabler-icons/automatic-gearbox.svg +23 -0
- swarm/static/contrib/tabler-icons/box-multiple.svg +19 -0
- swarm/static/contrib/tabler-icons/carambola.svg +19 -0
- swarm/static/contrib/tabler-icons/copy.svg +20 -0
- swarm/static/contrib/tabler-icons/download.svg +21 -0
- swarm/static/contrib/tabler-icons/edit.svg +21 -0
- swarm/static/contrib/tabler-icons/filled/carambola.svg +13 -0
- swarm/static/contrib/tabler-icons/filled/paint.svg +13 -0
- swarm/static/contrib/tabler-icons/headset.svg +22 -0
- swarm/static/contrib/tabler-icons/layout-sidebar-left-collapse.svg +21 -0
- swarm/static/contrib/tabler-icons/layout-sidebar-left-expand.svg +21 -0
- swarm/static/contrib/tabler-icons/layout-sidebar-right-collapse.svg +21 -0
- swarm/static/contrib/tabler-icons/layout-sidebar-right-expand.svg +21 -0
- swarm/static/contrib/tabler-icons/message-chatbot.svg +22 -0
- swarm/static/contrib/tabler-icons/message-star.svg +22 -0
- swarm/static/contrib/tabler-icons/message-x.svg +23 -0
- swarm/static/contrib/tabler-icons/message.svg +21 -0
- swarm/static/contrib/tabler-icons/paperclip.svg +18 -0
- swarm/static/contrib/tabler-icons/playlist-add.svg +22 -0
- swarm/static/contrib/tabler-icons/robot.svg +26 -0
- swarm/static/contrib/tabler-icons/search.svg +19 -0
- swarm/static/contrib/tabler-icons/settings.svg +20 -0
- swarm/static/contrib/tabler-icons/thumb-down.svg +19 -0
- swarm/static/contrib/tabler-icons/thumb-up.svg +19 -0
- swarm/static/css/dropdown.css +22 -0
- swarm/static/htmx/htmx.min.js +0 -0
- swarm/static/js/dropdown.js +23 -0
- swarm/static/rest_mode/css/base.css +470 -0
- swarm/static/rest_mode/css/chat-history.css +286 -0
- swarm/static/rest_mode/css/chat.css +251 -0
- swarm/static/rest_mode/css/chatbot.css +74 -0
- swarm/static/rest_mode/css/chatgpt.css +62 -0
- swarm/static/rest_mode/css/colors/corporate.css +74 -0
- swarm/static/rest_mode/css/colors/pastel.css +81 -0
- swarm/static/rest_mode/css/colors/tropical.css +82 -0
- swarm/static/rest_mode/css/general.css +142 -0
- swarm/static/rest_mode/css/layout.css +167 -0
- swarm/static/rest_mode/css/layouts/messenger-layout.css +17 -0
- swarm/static/rest_mode/css/layouts/minimalist-layout.css +57 -0
- swarm/static/rest_mode/css/layouts/mobile-layout.css +8 -0
- swarm/static/rest_mode/css/messages.css +84 -0
- swarm/static/rest_mode/css/messenger.css +135 -0
- swarm/static/rest_mode/css/settings.css +91 -0
- swarm/static/rest_mode/css/simple.css +44 -0
- swarm/static/rest_mode/css/slack.css +58 -0
- swarm/static/rest_mode/css/style.css +156 -0
- swarm/static/rest_mode/css/theme.css +30 -0
- swarm/static/rest_mode/css/toast.css +40 -0
- swarm/static/rest_mode/js/auth.js +9 -0
- swarm/static/rest_mode/js/blueprint.js +41 -0
- swarm/static/rest_mode/js/blueprintUtils.js +12 -0
- swarm/static/rest_mode/js/chatLogic.js +79 -0
- swarm/static/rest_mode/js/debug.js +63 -0
- swarm/static/rest_mode/js/events.js +98 -0
- swarm/static/rest_mode/js/main.js +19 -0
- swarm/static/rest_mode/js/messages.js +264 -0
- swarm/static/rest_mode/js/messengerLogic.js +355 -0
- swarm/static/rest_mode/js/modules/apiService.js +84 -0
- swarm/static/rest_mode/js/modules/blueprintManager.js +162 -0
- swarm/static/rest_mode/js/modules/chatHistory.js +110 -0
- swarm/static/rest_mode/js/modules/debugLogger.js +14 -0
- swarm/static/rest_mode/js/modules/eventHandlers.js +107 -0
- swarm/static/rest_mode/js/modules/messageProcessor.js +120 -0
- swarm/static/rest_mode/js/modules/state.js +7 -0
- swarm/static/rest_mode/js/modules/userInteractions.js +29 -0
- swarm/static/rest_mode/js/modules/validation.js +23 -0
- swarm/static/rest_mode/js/rendering.js +119 -0
- swarm/static/rest_mode/js/settings.js +130 -0
- swarm/static/rest_mode/js/sidebar.js +94 -0
- swarm/static/rest_mode/js/simpleLogic.js +37 -0
- swarm/static/rest_mode/js/slackLogic.js +66 -0
- swarm/static/rest_mode/js/splash.js +76 -0
- swarm/static/rest_mode/js/theme.js +111 -0
- swarm/static/rest_mode/js/toast.js +36 -0
- swarm/static/rest_mode/js/ui.js +265 -0
- swarm/static/rest_mode/js/validation.js +57 -0
- swarm/static/rest_mode/svg/animated_spinner.svg +12 -0
- swarm/static/rest_mode/svg/arrow_down.svg +5 -0
- swarm/static/rest_mode/svg/arrow_left.svg +5 -0
- swarm/static/rest_mode/svg/arrow_right.svg +5 -0
- swarm/static/rest_mode/svg/arrow_up.svg +5 -0
- swarm/static/rest_mode/svg/attach.svg +8 -0
- swarm/static/rest_mode/svg/avatar.svg +7 -0
- swarm/static/rest_mode/svg/canvas.svg +6 -0
- swarm/static/rest_mode/svg/chat_history.svg +4 -0
- swarm/static/rest_mode/svg/close.svg +5 -0
- swarm/static/rest_mode/svg/copy.svg +4 -0
- swarm/static/rest_mode/svg/dark_mode.svg +3 -0
- swarm/static/rest_mode/svg/edit.svg +5 -0
- swarm/static/rest_mode/svg/layout.svg +9 -0
- swarm/static/rest_mode/svg/logo.svg +29 -0
- swarm/static/rest_mode/svg/logout.svg +5 -0
- swarm/static/rest_mode/svg/mobile.svg +5 -0
- swarm/static/rest_mode/svg/new_chat.svg +4 -0
- swarm/static/rest_mode/svg/not_visible.svg +5 -0
- swarm/static/rest_mode/svg/plus.svg +7 -0
- swarm/static/rest_mode/svg/run_code.svg +6 -0
- swarm/static/rest_mode/svg/save.svg +4 -0
- swarm/static/rest_mode/svg/search.svg +6 -0
- swarm/static/rest_mode/svg/settings.svg +4 -0
- swarm/static/rest_mode/svg/speaker.svg +5 -0
- swarm/static/rest_mode/svg/stop.svg +6 -0
- swarm/static/rest_mode/svg/thumbs_down.svg +3 -0
- swarm/static/rest_mode/svg/thumbs_up.svg +3 -0
- swarm/static/rest_mode/svg/toggle_off.svg +6 -0
- swarm/static/rest_mode/svg/toggle_on.svg +6 -0
- swarm/static/rest_mode/svg/trash.svg +10 -0
- swarm/static/rest_mode/svg/undo.svg +3 -0
- swarm/static/rest_mode/svg/visible.svg +8 -0
- swarm/static/rest_mode/svg/voice.svg +10 -0
- swarm/templates/account/login.html +22 -0
- swarm/templates/account/signup.html +32 -0
- swarm/templates/base.html +30 -0
- swarm/templates/chat.html +43 -0
- swarm/templates/index.html +35 -0
- swarm/templates/rest_mode/components/chat_sidebar.html +55 -0
- swarm/templates/rest_mode/components/header.html +45 -0
- swarm/templates/rest_mode/components/main_chat_pane.html +41 -0
- swarm/templates/rest_mode/components/settings_dialog.html +97 -0
- swarm/templates/rest_mode/components/splash_screen.html +7 -0
- swarm/templates/rest_mode/components/top_bar.html +28 -0
- swarm/templates/rest_mode/message_ui.html +50 -0
- swarm/templates/rest_mode/slackbot.html +30 -0
- swarm/templates/simple_blueprint_page.html +24 -0
- swarm/templates/websocket_partials/final_system_message.html +3 -0
- swarm/templates/websocket_partials/system_message.html +4 -0
- swarm/templates/websocket_partials/user_message.html +5 -0
- swarm/urls.py +57 -74
- swarm/utils/log_utils.py +63 -0
- swarm/views/api_views.py +48 -39
- swarm/views/chat_views.py +156 -70
- swarm/views/core_views.py +85 -90
- swarm/views/model_views.py +64 -121
- swarm/views/utils.py +65 -441
- open_swarm-0.1.1743070217.dist-info/METADATA +0 -258
- open_swarm-0.1.1743070217.dist-info/RECORD +0 -89
- open_swarm-0.1.1743070217.dist-info/entry_points.txt +0 -3
- open_swarm-0.1.1743070217.dist-info/top_level.txt +0 -1
- swarm/agent/agent.py +0 -49
- swarm/core.py +0 -326
- swarm/extensions/mcp/__init__.py +0 -1
- swarm/extensions/mcp/cache_utils.py +0 -36
- swarm/extensions/mcp/mcp_client.py +0 -341
- swarm/extensions/mcp/mcp_constants.py +0 -7
- swarm/extensions/mcp/mcp_tool_provider.py +0 -110
- swarm/types.py +0 -126
- {open_swarm-0.1.1743070217.dist-info → open_swarm-0.1.1743362777.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
|
-
|
33
|
-
|
34
|
-
|
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) #
|
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
|
-
|
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
|
-
|
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
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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.
|
68
|
-
blueprint
|
69
|
-
|
70
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
93
|
-
logger.debug(f"Added app '{app}' from blueprint {blueprint.__class__.__name__} to INSTALLED_APPS.")
|
94
|
-
|
95
|
-
if
|
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
|
-
|
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
|
-
|
123
|
-
|
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
|
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}'.
|
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
|
-
|
163
|
-
|
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
|
-
#
|
169
|
-
if (isinstance(existing_pattern,
|
170
|
-
str(existing_pattern.pattern) == str(new_pattern.pattern) and
|
171
|
-
getattr(existing_pattern, 'app_name', None) == app_name
|
172
|
-
|
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
|
-
#
|
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
|
-
|
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
|
-
|
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
|
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.")
|