django-cfg 1.1.82__py3-none-any.whl → 1.2.1__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.
- django_cfg/__init__.py +20 -448
- django_cfg/apps/accounts/README.md +3 -3
- django_cfg/apps/accounts/admin/__init__.py +0 -2
- django_cfg/apps/accounts/admin/activity.py +2 -9
- django_cfg/apps/accounts/admin/filters.py +0 -42
- django_cfg/apps/accounts/admin/inlines.py +8 -8
- django_cfg/apps/accounts/admin/otp.py +5 -5
- django_cfg/apps/accounts/admin/registration_source.py +1 -8
- django_cfg/apps/accounts/admin/user.py +12 -20
- django_cfg/apps/accounts/managers/user_manager.py +2 -129
- django_cfg/apps/accounts/migrations/0006_remove_twilioresponse_otp_secret_and_more.py +46 -0
- django_cfg/apps/accounts/models.py +3 -123
- django_cfg/apps/accounts/serializers/otp.py +40 -44
- django_cfg/apps/accounts/serializers/profile.py +0 -2
- django_cfg/apps/accounts/services/otp_service.py +98 -186
- django_cfg/apps/accounts/signals.py +25 -15
- django_cfg/apps/accounts/utils/auth_email_service.py +84 -0
- django_cfg/apps/accounts/views/otp.py +35 -36
- django_cfg/apps/agents/README.md +129 -0
- django_cfg/apps/agents/__init__.py +68 -0
- django_cfg/apps/agents/admin/__init__.py +17 -0
- django_cfg/apps/agents/admin/execution_admin.py +460 -0
- django_cfg/apps/agents/admin/registry_admin.py +360 -0
- django_cfg/apps/agents/admin/toolsets_admin.py +482 -0
- django_cfg/apps/agents/apps.py +29 -0
- django_cfg/apps/agents/core/__init__.py +20 -0
- django_cfg/apps/agents/core/agent.py +281 -0
- django_cfg/apps/agents/core/dependencies.py +154 -0
- django_cfg/apps/agents/core/exceptions.py +66 -0
- django_cfg/apps/agents/core/models.py +106 -0
- django_cfg/apps/agents/core/orchestrator.py +391 -0
- django_cfg/apps/agents/examples/__init__.py +3 -0
- django_cfg/apps/agents/examples/simple_example.py +161 -0
- django_cfg/apps/agents/integration/__init__.py +14 -0
- django_cfg/apps/agents/integration/middleware.py +80 -0
- django_cfg/apps/agents/integration/registry.py +345 -0
- django_cfg/apps/agents/integration/signals.py +50 -0
- django_cfg/apps/agents/management/__init__.py +3 -0
- django_cfg/apps/agents/management/commands/__init__.py +3 -0
- django_cfg/apps/agents/management/commands/create_agent.py +365 -0
- django_cfg/apps/agents/management/commands/orchestrator_status.py +191 -0
- django_cfg/apps/agents/managers/__init__.py +23 -0
- django_cfg/apps/agents/managers/execution.py +236 -0
- django_cfg/apps/agents/managers/registry.py +254 -0
- django_cfg/apps/agents/managers/toolsets.py +496 -0
- django_cfg/apps/agents/migrations/0001_initial.py +286 -0
- django_cfg/apps/agents/migrations/__init__.py +5 -0
- django_cfg/apps/agents/models/__init__.py +15 -0
- django_cfg/apps/agents/models/execution.py +215 -0
- django_cfg/apps/agents/models/registry.py +220 -0
- django_cfg/apps/agents/models/toolsets.py +305 -0
- django_cfg/apps/agents/patterns/__init__.py +24 -0
- django_cfg/apps/agents/patterns/content_agents.py +234 -0
- django_cfg/apps/agents/toolsets/__init__.py +15 -0
- django_cfg/apps/agents/toolsets/cache_toolset.py +285 -0
- django_cfg/apps/agents/toolsets/django_toolset.py +220 -0
- django_cfg/apps/agents/toolsets/file_toolset.py +324 -0
- django_cfg/apps/agents/toolsets/orm_toolset.py +319 -0
- django_cfg/apps/agents/urls.py +46 -0
- django_cfg/apps/knowbase/README.md +150 -0
- django_cfg/apps/knowbase/__init__.py +27 -0
- django_cfg/apps/knowbase/admin/__init__.py +23 -0
- django_cfg/apps/knowbase/admin/archive_admin.py +857 -0
- django_cfg/apps/knowbase/admin/chat_admin.py +386 -0
- django_cfg/apps/knowbase/admin/document_admin.py +650 -0
- django_cfg/apps/knowbase/admin/external_data_admin.py +685 -0
- django_cfg/apps/knowbase/apps.py +81 -0
- django_cfg/apps/knowbase/config/README.md +176 -0
- django_cfg/apps/knowbase/config/__init__.py +51 -0
- django_cfg/apps/knowbase/config/constance_fields.py +186 -0
- django_cfg/apps/knowbase/config/constance_settings.py +200 -0
- django_cfg/apps/knowbase/config/settings.py +450 -0
- django_cfg/apps/knowbase/examples/__init__.py +3 -0
- django_cfg/apps/knowbase/examples/external_data_usage.py +191 -0
- django_cfg/apps/knowbase/management/__init__.py +0 -0
- django_cfg/apps/knowbase/management/commands/__init__.py +0 -0
- django_cfg/apps/knowbase/management/commands/knowbase_stats.py +158 -0
- django_cfg/apps/knowbase/management/commands/setup_knowbase.py +59 -0
- django_cfg/apps/knowbase/managers/__init__.py +22 -0
- django_cfg/apps/knowbase/managers/archive.py +426 -0
- django_cfg/apps/knowbase/managers/base.py +32 -0
- django_cfg/apps/knowbase/managers/chat.py +141 -0
- django_cfg/apps/knowbase/managers/document.py +203 -0
- django_cfg/apps/knowbase/managers/external_data.py +471 -0
- django_cfg/apps/knowbase/migrations/0001_initial.py +427 -0
- django_cfg/apps/knowbase/migrations/0002_archiveitem_archiveitemchunk_documentarchive_and_more.py +434 -0
- django_cfg/apps/knowbase/migrations/__init__.py +5 -0
- django_cfg/apps/knowbase/mixins/__init__.py +15 -0
- django_cfg/apps/knowbase/mixins/config.py +108 -0
- django_cfg/apps/knowbase/mixins/creator.py +81 -0
- django_cfg/apps/knowbase/mixins/examples/vehicle_model_example.py +199 -0
- django_cfg/apps/knowbase/mixins/external_data_mixin.py +813 -0
- django_cfg/apps/knowbase/mixins/service.py +362 -0
- django_cfg/apps/knowbase/models/__init__.py +41 -0
- django_cfg/apps/knowbase/models/archive.py +599 -0
- django_cfg/apps/knowbase/models/base.py +58 -0
- django_cfg/apps/knowbase/models/chat.py +157 -0
- django_cfg/apps/knowbase/models/document.py +267 -0
- django_cfg/apps/knowbase/models/external_data.py +376 -0
- django_cfg/apps/knowbase/serializers/__init__.py +68 -0
- django_cfg/apps/knowbase/serializers/archive_serializers.py +386 -0
- django_cfg/apps/knowbase/serializers/chat_serializers.py +137 -0
- django_cfg/apps/knowbase/serializers/document_serializers.py +94 -0
- django_cfg/apps/knowbase/serializers/external_data_serializers.py +256 -0
- django_cfg/apps/knowbase/serializers/public_serializers.py +74 -0
- django_cfg/apps/knowbase/services/__init__.py +40 -0
- django_cfg/apps/knowbase/services/archive/__init__.py +42 -0
- django_cfg/apps/knowbase/services/archive/archive_service.py +541 -0
- django_cfg/apps/knowbase/services/archive/chunking_service.py +791 -0
- django_cfg/apps/knowbase/services/archive/exceptions.py +52 -0
- django_cfg/apps/knowbase/services/archive/extraction_service.py +508 -0
- django_cfg/apps/knowbase/services/archive/vectorization_service.py +362 -0
- django_cfg/apps/knowbase/services/base.py +53 -0
- django_cfg/apps/knowbase/services/chat_service.py +239 -0
- django_cfg/apps/knowbase/services/document_service.py +144 -0
- django_cfg/apps/knowbase/services/embedding/__init__.py +43 -0
- django_cfg/apps/knowbase/services/embedding/async_processor.py +244 -0
- django_cfg/apps/knowbase/services/embedding/batch_processor.py +250 -0
- django_cfg/apps/knowbase/services/embedding/batch_result.py +61 -0
- django_cfg/apps/knowbase/services/embedding/models.py +229 -0
- django_cfg/apps/knowbase/services/embedding/processors.py +148 -0
- django_cfg/apps/knowbase/services/embedding/utils.py +176 -0
- django_cfg/apps/knowbase/services/prompt_builder.py +191 -0
- django_cfg/apps/knowbase/services/search_service.py +293 -0
- django_cfg/apps/knowbase/signals/__init__.py +21 -0
- django_cfg/apps/knowbase/signals/archive_signals.py +211 -0
- django_cfg/apps/knowbase/signals/chat_signals.py +37 -0
- django_cfg/apps/knowbase/signals/document_signals.py +143 -0
- django_cfg/apps/knowbase/signals/external_data_signals.py +157 -0
- django_cfg/apps/knowbase/tasks/__init__.py +39 -0
- django_cfg/apps/knowbase/tasks/archive_tasks.py +316 -0
- django_cfg/apps/knowbase/tasks/document_processing.py +341 -0
- django_cfg/apps/knowbase/tasks/external_data_tasks.py +341 -0
- django_cfg/apps/knowbase/tasks/maintenance.py +195 -0
- django_cfg/apps/knowbase/urls.py +43 -0
- django_cfg/apps/knowbase/utils/__init__.py +12 -0
- django_cfg/apps/knowbase/utils/chunk_settings.py +261 -0
- django_cfg/apps/knowbase/utils/text_processing.py +375 -0
- django_cfg/apps/knowbase/utils/validation.py +99 -0
- django_cfg/apps/knowbase/views/__init__.py +28 -0
- django_cfg/apps/knowbase/views/archive_views.py +469 -0
- django_cfg/apps/knowbase/views/base.py +49 -0
- django_cfg/apps/knowbase/views/chat_views.py +181 -0
- django_cfg/apps/knowbase/views/document_views.py +183 -0
- django_cfg/apps/knowbase/views/public_views.py +129 -0
- django_cfg/apps/leads/admin.py +70 -0
- django_cfg/apps/newsletter/admin.py +234 -0
- django_cfg/apps/newsletter/admin_filters.py +124 -0
- django_cfg/apps/support/admin.py +196 -0
- django_cfg/apps/support/admin_filters.py +71 -0
- django_cfg/apps/support/templates/support/chat/ticket_chat.html +1 -1
- django_cfg/apps/urls.py +5 -4
- django_cfg/cli/README.md +1 -1
- django_cfg/cli/commands/create_project.py +2 -2
- django_cfg/cli/commands/info.py +1 -1
- django_cfg/config.py +44 -0
- django_cfg/core/config.py +29 -82
- django_cfg/core/environment.py +1 -1
- django_cfg/core/generation.py +19 -107
- django_cfg/{integration.py → core/integration.py} +18 -16
- django_cfg/core/validation.py +1 -1
- django_cfg/management/__init__.py +1 -1
- django_cfg/management/commands/__init__.py +1 -1
- django_cfg/management/commands/auto_generate.py +482 -0
- django_cfg/management/commands/migrator.py +19 -101
- django_cfg/management/commands/test_email.py +1 -1
- django_cfg/middleware/README.md +0 -158
- django_cfg/middleware/__init__.py +0 -2
- django_cfg/middleware/user_activity.py +3 -3
- django_cfg/models/api.py +145 -0
- django_cfg/models/base.py +287 -0
- django_cfg/models/cache.py +4 -4
- django_cfg/models/constance.py +25 -88
- django_cfg/models/database.py +9 -9
- django_cfg/models/drf.py +3 -36
- django_cfg/models/email.py +163 -0
- django_cfg/models/environment.py +276 -0
- django_cfg/models/limits.py +1 -1
- django_cfg/models/logging.py +366 -0
- django_cfg/models/revolution.py +41 -2
- django_cfg/models/security.py +125 -0
- django_cfg/models/services.py +1 -1
- django_cfg/modules/__init__.py +2 -56
- django_cfg/modules/base.py +78 -52
- django_cfg/modules/django_currency/service.py +2 -2
- django_cfg/modules/django_email.py +2 -2
- django_cfg/modules/django_health.py +267 -0
- django_cfg/modules/django_llm/llm/client.py +91 -19
- django_cfg/modules/django_llm/translator/translator.py +2 -2
- django_cfg/modules/django_logger.py +2 -2
- django_cfg/modules/django_ngrok.py +2 -2
- django_cfg/modules/django_tasks.py +68 -3
- django_cfg/modules/django_telegram.py +3 -3
- django_cfg/modules/django_twilio/sendgrid_service.py +2 -2
- django_cfg/modules/django_twilio/service.py +2 -2
- django_cfg/modules/django_twilio/simple_service.py +2 -2
- django_cfg/modules/django_twilio/twilio_service.py +2 -2
- django_cfg/modules/django_unfold/__init__.py +69 -0
- django_cfg/modules/{unfold → django_unfold}/callbacks.py +23 -22
- django_cfg/modules/django_unfold/dashboard.py +278 -0
- django_cfg/modules/django_unfold/icons/README.md +145 -0
- django_cfg/modules/django_unfold/icons/__init__.py +12 -0
- django_cfg/modules/django_unfold/icons/constants.py +2851 -0
- django_cfg/modules/django_unfold/icons/generate_icons.py +486 -0
- django_cfg/modules/django_unfold/models/__init__.py +42 -0
- django_cfg/modules/django_unfold/models/config.py +601 -0
- django_cfg/modules/django_unfold/models/dashboard.py +206 -0
- django_cfg/modules/django_unfold/models/dropdown.py +40 -0
- django_cfg/modules/django_unfold/models/navigation.py +73 -0
- django_cfg/modules/django_unfold/models/tabs.py +25 -0
- django_cfg/modules/{unfold → django_unfold}/system_monitor.py +2 -2
- django_cfg/modules/django_unfold/utils.py +140 -0
- django_cfg/registry/__init__.py +23 -0
- django_cfg/registry/core.py +61 -0
- django_cfg/registry/exceptions.py +11 -0
- django_cfg/registry/modules.py +12 -0
- django_cfg/registry/services.py +26 -0
- django_cfg/registry/third_party.py +52 -0
- django_cfg/routing/__init__.py +19 -0
- django_cfg/routing/callbacks.py +198 -0
- django_cfg/routing/routers.py +48 -0
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +8 -9
- django_cfg/templatetags/__init__.py +0 -0
- django_cfg/templatetags/django_cfg.py +33 -0
- django_cfg/urls.py +33 -0
- django_cfg/utils/path_resolution.py +1 -1
- django_cfg/utils/smart_defaults.py +7 -61
- django_cfg/utils/toolkit.py +663 -0
- {django_cfg-1.1.82.dist-info → django_cfg-1.2.1.dist-info}/METADATA +83 -86
- django_cfg-1.2.1.dist-info/RECORD +441 -0
- django_cfg/archive/django_sample.zip +0 -0
- django_cfg/models/unfold.py +0 -271
- django_cfg/modules/unfold/__init__.py +0 -29
- django_cfg/modules/unfold/dashboard.py +0 -318
- django_cfg/pyproject.toml +0 -370
- django_cfg/routers.py +0 -83
- django_cfg-1.1.82.dist-info/RECORD +0 -278
- /django_cfg/{exceptions.py → core/exceptions.py} +0 -0
- /django_cfg/modules/{unfold → django_unfold}/models.py +0 -0
- /django_cfg/modules/{unfold → django_unfold}/tailwind.py +0 -0
- /django_cfg/{version_check.py → utils/version_check.py} +0 -0
- {django_cfg-1.1.82.dist-info → django_cfg-1.2.1.dist-info}/WHEEL +0 -0
- {django_cfg-1.1.82.dist-info → django_cfg-1.2.1.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.1.82.dist-info → django_cfg-1.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,267 @@
|
|
1
|
+
"""
|
2
|
+
Health Check Views for Django Config Toolkit
|
3
|
+
|
4
|
+
Provides health check endpoints for monitoring system status.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import Dict, Any
|
8
|
+
from django.http import JsonResponse
|
9
|
+
from django.views import View
|
10
|
+
from django.db import connections
|
11
|
+
from django.core.cache import cache
|
12
|
+
from django.conf import settings
|
13
|
+
import time
|
14
|
+
import psutil
|
15
|
+
import os
|
16
|
+
from datetime import datetime
|
17
|
+
|
18
|
+
|
19
|
+
class HealthCheckView(View):
|
20
|
+
"""
|
21
|
+
Health check endpoint that validates system components.
|
22
|
+
|
23
|
+
GET /health/ returns:
|
24
|
+
- Database connectivity
|
25
|
+
- Cache availability
|
26
|
+
- System resources
|
27
|
+
- Configuration status
|
28
|
+
"""
|
29
|
+
|
30
|
+
def get(self, request):
|
31
|
+
"""Return comprehensive health check data."""
|
32
|
+
health_data = {
|
33
|
+
"status": "healthy",
|
34
|
+
"timestamp": datetime.now().isoformat(),
|
35
|
+
"checks": {}
|
36
|
+
}
|
37
|
+
|
38
|
+
# Database check
|
39
|
+
try:
|
40
|
+
db_status = self._check_databases()
|
41
|
+
health_data["checks"]["database"] = db_status
|
42
|
+
except Exception as e:
|
43
|
+
health_data["checks"]["database"] = {
|
44
|
+
"status": "error",
|
45
|
+
"error": str(e)
|
46
|
+
}
|
47
|
+
health_data["status"] = "unhealthy"
|
48
|
+
|
49
|
+
# Cache check
|
50
|
+
try:
|
51
|
+
cache_status = self._check_cache()
|
52
|
+
health_data["checks"]["cache"] = cache_status
|
53
|
+
except Exception as e:
|
54
|
+
health_data["checks"]["cache"] = {
|
55
|
+
"status": "error",
|
56
|
+
"error": str(e)
|
57
|
+
}
|
58
|
+
|
59
|
+
# System resources
|
60
|
+
try:
|
61
|
+
system_status = self._check_system_resources()
|
62
|
+
health_data["checks"]["system"] = system_status
|
63
|
+
except Exception as e:
|
64
|
+
health_data["checks"]["system"] = {
|
65
|
+
"status": "warning",
|
66
|
+
"error": str(e)
|
67
|
+
}
|
68
|
+
|
69
|
+
# Configuration check
|
70
|
+
config_status = self._check_configuration()
|
71
|
+
health_data["checks"]["configuration"] = config_status
|
72
|
+
|
73
|
+
# Overall status
|
74
|
+
if any(check.get("status") == "error" for check in health_data["checks"].values()):
|
75
|
+
health_data["status"] = "unhealthy"
|
76
|
+
elif any(check.get("status") == "warning" for check in health_data["checks"].values()):
|
77
|
+
health_data["status"] = "degraded"
|
78
|
+
|
79
|
+
return JsonResponse(health_data)
|
80
|
+
|
81
|
+
def _check_databases(self) -> Dict[str, Any]:
|
82
|
+
"""Check database connectivity."""
|
83
|
+
db_status = {
|
84
|
+
"status": "healthy",
|
85
|
+
"databases": {}
|
86
|
+
}
|
87
|
+
|
88
|
+
for db_name in connections:
|
89
|
+
try:
|
90
|
+
start_time = time.time()
|
91
|
+
connection = connections[db_name]
|
92
|
+
|
93
|
+
# Test connection
|
94
|
+
with connection.cursor() as cursor:
|
95
|
+
cursor.execute("SELECT 1")
|
96
|
+
cursor.fetchone()
|
97
|
+
|
98
|
+
response_time = (time.time() - start_time) * 1000
|
99
|
+
|
100
|
+
db_status["databases"][db_name] = {
|
101
|
+
"status": "healthy",
|
102
|
+
"response_time_ms": round(response_time, 2),
|
103
|
+
"engine": connection.settings_dict.get('ENGINE', 'unknown')
|
104
|
+
}
|
105
|
+
|
106
|
+
except Exception as e:
|
107
|
+
db_status["databases"][db_name] = {
|
108
|
+
"status": "error",
|
109
|
+
"error": str(e)
|
110
|
+
}
|
111
|
+
db_status["status"] = "error"
|
112
|
+
|
113
|
+
return db_status
|
114
|
+
|
115
|
+
def _check_cache(self) -> Dict[str, Any]:
|
116
|
+
"""Check cache availability."""
|
117
|
+
cache_status = {
|
118
|
+
"status": "healthy"
|
119
|
+
}
|
120
|
+
|
121
|
+
try:
|
122
|
+
# Test cache write/read
|
123
|
+
test_key = "health_check"
|
124
|
+
test_value = "ok"
|
125
|
+
|
126
|
+
start_time = time.time()
|
127
|
+
cache.set(test_key, test_value, 30)
|
128
|
+
retrieved_value = cache.get(test_key)
|
129
|
+
response_time = (time.time() - start_time) * 1000
|
130
|
+
|
131
|
+
if retrieved_value == test_value:
|
132
|
+
cache_status.update({
|
133
|
+
"status": "healthy",
|
134
|
+
"response_time_ms": round(response_time, 2),
|
135
|
+
"backend": getattr(settings, 'CACHES', {}).get('default', {}).get('BACKEND', 'unknown')
|
136
|
+
})
|
137
|
+
else:
|
138
|
+
cache_status = {
|
139
|
+
"status": "error",
|
140
|
+
"error": "Cache read/write test failed"
|
141
|
+
}
|
142
|
+
|
143
|
+
except Exception as e:
|
144
|
+
cache_status = {
|
145
|
+
"status": "error",
|
146
|
+
"error": str(e)
|
147
|
+
}
|
148
|
+
|
149
|
+
return cache_status
|
150
|
+
|
151
|
+
def _check_system_resources(self) -> Dict[str, Any]:
|
152
|
+
"""Check system resource usage."""
|
153
|
+
try:
|
154
|
+
# CPU usage
|
155
|
+
cpu_percent = psutil.cpu_percent(interval=1)
|
156
|
+
|
157
|
+
# Memory usage
|
158
|
+
memory = psutil.virtual_memory()
|
159
|
+
memory_percent = memory.percent
|
160
|
+
|
161
|
+
# Disk usage
|
162
|
+
disk = psutil.disk_usage('/')
|
163
|
+
disk_percent = (disk.used / disk.total) * 100
|
164
|
+
|
165
|
+
# Load average (Unix-like systems)
|
166
|
+
try:
|
167
|
+
load_avg = os.getloadavg()
|
168
|
+
except (AttributeError, OSError):
|
169
|
+
load_avg = None
|
170
|
+
|
171
|
+
system_status = {
|
172
|
+
"status": "healthy",
|
173
|
+
"cpu": {
|
174
|
+
"usage_percent": cpu_percent,
|
175
|
+
"status": "warning" if cpu_percent > 80 else "healthy"
|
176
|
+
},
|
177
|
+
"memory": {
|
178
|
+
"usage_percent": memory_percent,
|
179
|
+
"total_gb": round(memory.total / (1024**3), 2),
|
180
|
+
"available_gb": round(memory.available / (1024**3), 2),
|
181
|
+
"status": "warning" if memory_percent > 80 else "healthy"
|
182
|
+
},
|
183
|
+
"disk": {
|
184
|
+
"usage_percent": round(disk_percent, 2),
|
185
|
+
"total_gb": round(disk.total / (1024**3), 2),
|
186
|
+
"free_gb": round(disk.free / (1024**3), 2),
|
187
|
+
"status": "warning" if disk_percent > 80 else "healthy"
|
188
|
+
}
|
189
|
+
}
|
190
|
+
|
191
|
+
if load_avg:
|
192
|
+
system_status["load_average"] = {
|
193
|
+
"1min": load_avg[0],
|
194
|
+
"5min": load_avg[1],
|
195
|
+
"15min": load_avg[2]
|
196
|
+
}
|
197
|
+
|
198
|
+
# Overall system status
|
199
|
+
if (cpu_percent > 90 or memory_percent > 90 or disk_percent > 90):
|
200
|
+
system_status["status"] = "warning"
|
201
|
+
|
202
|
+
return system_status
|
203
|
+
|
204
|
+
except Exception as e:
|
205
|
+
return {
|
206
|
+
"status": "error",
|
207
|
+
"error": f"Failed to get system resources: {str(e)}"
|
208
|
+
}
|
209
|
+
|
210
|
+
def _check_configuration(self) -> Dict[str, Any]:
|
211
|
+
"""Check Django Config Toolkit configuration."""
|
212
|
+
try:
|
213
|
+
from django_cfg import ConfigToolkit
|
214
|
+
|
215
|
+
toolkit = ConfigToolkit()
|
216
|
+
|
217
|
+
config_status = {
|
218
|
+
"status": "healthy",
|
219
|
+
"toolkit": {
|
220
|
+
"version": "1.0.0",
|
221
|
+
"init_time_ms": toolkit._init_time_ms,
|
222
|
+
"config_count": toolkit._config_count,
|
223
|
+
"environment": toolkit.environment,
|
224
|
+
"debug": toolkit.debug
|
225
|
+
},
|
226
|
+
"features": {
|
227
|
+
"unfold": toolkit.unfold_enabled,
|
228
|
+
"revolution": toolkit.revolution_enabled,
|
229
|
+
"constance": toolkit.constance_enabled,
|
230
|
+
"logging": toolkit.logging_enabled
|
231
|
+
}
|
232
|
+
}
|
233
|
+
|
234
|
+
# Validate configuration
|
235
|
+
if not toolkit.secret_key or len(toolkit.secret_key) < 50:
|
236
|
+
config_status["status"] = "warning"
|
237
|
+
config_status["warnings"] = ["Secret key is too short"]
|
238
|
+
|
239
|
+
return config_status
|
240
|
+
|
241
|
+
except Exception as e:
|
242
|
+
return {
|
243
|
+
"status": "error",
|
244
|
+
"error": f"Configuration check failed: {str(e)}"
|
245
|
+
}
|
246
|
+
|
247
|
+
|
248
|
+
class SimpleHealthView(View):
|
249
|
+
"""Simple health check endpoint that just returns OK."""
|
250
|
+
|
251
|
+
def get(self, request):
|
252
|
+
"""Return simple OK response."""
|
253
|
+
return JsonResponse({
|
254
|
+
"status": "ok",
|
255
|
+
"timestamp": datetime.now().isoformat()
|
256
|
+
})
|
257
|
+
|
258
|
+
|
259
|
+
# URL patterns for health checks
|
260
|
+
def get_health_urls():
|
261
|
+
"""Get URL patterns for health check endpoints."""
|
262
|
+
from django.urls import path
|
263
|
+
|
264
|
+
return [
|
265
|
+
path('health/', HealthCheckView.as_view(), name='health-check'),
|
266
|
+
path('health/simple/', SimpleHealthView.as_view(), name='simple-health'),
|
267
|
+
]
|
@@ -37,11 +37,12 @@ from .models import (
|
|
37
37
|
CacheInfo,
|
38
38
|
LLMError
|
39
39
|
)
|
40
|
+
from ...base import BaseCfgModule
|
40
41
|
|
41
42
|
logger = logging.getLogger(__name__)
|
42
43
|
|
43
44
|
|
44
|
-
class LLMClient:
|
45
|
+
class LLMClient(BaseCfgModule):
|
45
46
|
"""Universal LLM client with caching and token optimization."""
|
46
47
|
|
47
48
|
def __init__(
|
@@ -52,33 +53,40 @@ class LLMClient:
|
|
52
53
|
cache_ttl: int = 3600,
|
53
54
|
max_cache_size: int = 1000,
|
54
55
|
models_cache_ttl: int = 86400,
|
55
|
-
config: Optional[Any] = None
|
56
|
+
config: Optional[Any] = None,
|
57
|
+
preferred_provider: Optional[str] = None
|
56
58
|
):
|
57
59
|
"""
|
58
60
|
Initialize LLM client.
|
59
61
|
|
60
62
|
Args:
|
61
|
-
apikey_openrouter: API key for OpenRouter
|
62
|
-
apikey_openai: API key for OpenAI
|
63
|
+
apikey_openrouter: API key for OpenRouter (auto-detected if not provided)
|
64
|
+
apikey_openai: API key for OpenAI (auto-detected if not provided)
|
63
65
|
cache_dir: Cache directory path
|
64
66
|
cache_ttl: Cache TTL in seconds
|
65
67
|
max_cache_size: Maximum cache size
|
66
68
|
models_cache_ttl: Models cache TTL in seconds (default: 24 hours)
|
67
69
|
config: DjangoConfig instance for getting headers and settings
|
70
|
+
preferred_provider: Preferred provider ("openai" or "openrouter").
|
71
|
+
If None, defaults to "openai" for embeddings, "openrouter" for chat
|
68
72
|
"""
|
69
|
-
|
73
|
+
super().__init__()
|
74
|
+
|
75
|
+
# Auto-detect API keys from config if not provided
|
76
|
+
django_config = self.get_config()
|
77
|
+
if django_config:
|
78
|
+
if apikey_openai is None:
|
79
|
+
apikey_openai = getattr(django_config, 'openai_api_key', None)
|
80
|
+
# Add other API keys as needed
|
81
|
+
|
82
|
+
# Store API keys and preferred provider
|
70
83
|
self.apikey_openrouter = apikey_openrouter
|
71
84
|
self.apikey_openai = apikey_openai
|
85
|
+
self.preferred_provider = preferred_provider
|
72
86
|
|
73
|
-
# Determine primary provider based on available keys
|
74
|
-
|
75
|
-
|
76
|
-
self.primary_api_key = apikey_openrouter
|
77
|
-
elif apikey_openai:
|
78
|
-
self.primary_provider = "openai"
|
79
|
-
self.primary_api_key = apikey_openai
|
80
|
-
else:
|
81
|
-
raise ValueError("At least one API key (openrouter or openai) must be provided")
|
87
|
+
# Determine primary provider based on preference and available keys
|
88
|
+
self.primary_provider = self._determine_primary_provider()
|
89
|
+
self.primary_api_key = self._get_primary_api_key()
|
82
90
|
|
83
91
|
self.cache = LLMCache(cache_dir=cache_dir, ttl=cache_ttl, max_size=max_cache_size)
|
84
92
|
self.django_config = config
|
@@ -161,7 +169,7 @@ class LLMClient:
|
|
161
169
|
|
162
170
|
config = base_configs[self.provider].copy()
|
163
171
|
|
164
|
-
site_url = getattr(self.django_config, 'site_url', 'https://
|
172
|
+
site_url = getattr(self.django_config, 'site_url', 'https://djangocfg.com')
|
165
173
|
project_name = getattr(self.django_config, 'project_name', 'UnrealOS LLM Client')
|
166
174
|
|
167
175
|
# Get headers from django config if available
|
@@ -245,6 +253,11 @@ class LLMClient:
|
|
245
253
|
if model is None:
|
246
254
|
model = self.default_models[self.primary_provider]
|
247
255
|
|
256
|
+
# For OpenAI, remove provider prefix if present
|
257
|
+
api_model = model
|
258
|
+
if self.primary_provider == "openai" and model.startswith("openai/"):
|
259
|
+
api_model = model.replace("openai/", "")
|
260
|
+
|
248
261
|
# Generate cache key
|
249
262
|
request_hash = self.cache.generate_request_hash(
|
250
263
|
messages=messages,
|
@@ -275,7 +288,7 @@ class LLMClient:
|
|
275
288
|
try:
|
276
289
|
# Prepare parameters
|
277
290
|
params = {
|
278
|
-
"model":
|
291
|
+
"model": api_model,
|
279
292
|
"messages": messages,
|
280
293
|
"stream": False
|
281
294
|
}
|
@@ -514,9 +527,12 @@ class LLMClient:
|
|
514
527
|
|
515
528
|
start_time = time.time()
|
516
529
|
try:
|
530
|
+
# Get the best provider for embedding task
|
531
|
+
embedding_provider = self.get_provider_for_task("embedding")
|
532
|
+
|
517
533
|
# For OpenRouter, we need to use a different model for embeddings
|
518
534
|
# OpenRouter doesn't support OpenAI embedding models directly
|
519
|
-
if
|
535
|
+
if embedding_provider == "openrouter":
|
520
536
|
# Use a text generation model to simulate embeddings
|
521
537
|
# This is a workaround since OpenRouter doesn't have embedding endpoints
|
522
538
|
logger.warning("OpenRouter doesn't support embedding models, using text generation as fallback")
|
@@ -561,9 +577,15 @@ class LLMClient:
|
|
561
577
|
)
|
562
578
|
else:
|
563
579
|
# Use real OpenAI embedding API
|
564
|
-
|
580
|
+
embedding_client = self.clients[embedding_provider]
|
581
|
+
# For OpenAI, remove provider prefix if present
|
582
|
+
api_model = model
|
583
|
+
if embedding_provider == "openai" and model.startswith("openai/"):
|
584
|
+
api_model = model.replace("openai/", "")
|
585
|
+
|
586
|
+
response = embedding_client.embeddings.create(
|
565
587
|
input=text,
|
566
|
-
model=
|
588
|
+
model=api_model
|
567
589
|
)
|
568
590
|
|
569
591
|
# Extract embedding data
|
@@ -600,4 +622,54 @@ class LLMClient:
|
|
600
622
|
error_msg = f"Embedding generation failed: {e}"
|
601
623
|
logger.error(error_msg)
|
602
624
|
raise RuntimeError(error_msg) from e
|
625
|
+
|
626
|
+
def _determine_primary_provider(self) -> str:
|
627
|
+
"""
|
628
|
+
Determine primary provider based on preference and available keys.
|
629
|
+
|
630
|
+
Returns:
|
631
|
+
Primary provider name
|
632
|
+
"""
|
633
|
+
# If preferred provider is explicitly set and available, use it
|
634
|
+
if self.preferred_provider:
|
635
|
+
if self.preferred_provider == "openai" and self.apikey_openai:
|
636
|
+
return "openai"
|
637
|
+
elif self.preferred_provider == "openrouter" and self.apikey_openrouter:
|
638
|
+
return "openrouter"
|
639
|
+
else:
|
640
|
+
logger.warning(f"Preferred provider '{self.preferred_provider}' not available, falling back to auto-detection")
|
641
|
+
|
642
|
+
# Auto-detection: prefer OpenAI for embeddings, OpenRouter for chat
|
643
|
+
if self.apikey_openai:
|
644
|
+
return "openai"
|
645
|
+
elif self.apikey_openrouter:
|
646
|
+
return "openrouter"
|
647
|
+
else:
|
648
|
+
raise ValueError("At least one API key (openrouter or openai) must be provided")
|
649
|
+
|
650
|
+
def _get_primary_api_key(self) -> str:
|
651
|
+
"""Get API key for the primary provider."""
|
652
|
+
if self.primary_provider == "openai":
|
653
|
+
return self.apikey_openai
|
654
|
+
elif self.primary_provider == "openrouter":
|
655
|
+
return self.apikey_openrouter
|
656
|
+
else:
|
657
|
+
raise ValueError(f"Unknown primary provider: {self.primary_provider}")
|
658
|
+
|
659
|
+
def get_provider_for_task(self, task: str = "chat") -> str:
|
660
|
+
"""
|
661
|
+
Get the best provider for a specific task.
|
662
|
+
|
663
|
+
Args:
|
664
|
+
task: Task type ("chat", "embedding", "completion")
|
665
|
+
|
666
|
+
Returns:
|
667
|
+
Provider name for the task
|
668
|
+
"""
|
669
|
+
# For embeddings, always prefer OpenAI if available
|
670
|
+
if task == "embedding" and "openai" in self.clients:
|
671
|
+
return "openai"
|
672
|
+
|
673
|
+
# For other tasks, use primary provider or preferred
|
674
|
+
return self.primary_provider
|
603
675
|
|
@@ -12,7 +12,7 @@ from typing import Dict, List, Optional, Any, Set
|
|
12
12
|
from datetime import datetime
|
13
13
|
from pathlib import Path
|
14
14
|
|
15
|
-
from django_cfg.modules import
|
15
|
+
from django_cfg.modules import BaseCfgModule
|
16
16
|
from ..llm.client import LLMClient
|
17
17
|
from ..llm.cache import LLMCache
|
18
18
|
from .cache import TranslationCacheManager
|
@@ -30,7 +30,7 @@ class LanguageDetectionError(TranslationError):
|
|
30
30
|
pass
|
31
31
|
|
32
32
|
|
33
|
-
class DjangoTranslator(
|
33
|
+
class DjangoTranslator(BaseCfgModule):
|
34
34
|
"""
|
35
35
|
Translation Service for django_cfg, configured via DjangoConfig.
|
36
36
|
|
@@ -9,10 +9,10 @@ import logging
|
|
9
9
|
from typing import Optional, Dict, Any, Union
|
10
10
|
from pathlib import Path
|
11
11
|
|
12
|
-
from . import
|
12
|
+
from . import BaseCfgModule
|
13
13
|
|
14
14
|
|
15
|
-
class DjangoLogger(
|
15
|
+
class DjangoLogger(BaseCfgModule):
|
16
16
|
"""
|
17
17
|
Auto-configuring logger that gets settings from DjangoConfig.
|
18
18
|
|
@@ -8,7 +8,7 @@ import os
|
|
8
8
|
import logging
|
9
9
|
import atexit
|
10
10
|
from typing import Optional
|
11
|
-
from django_cfg.modules.base import
|
11
|
+
from django_cfg.modules.base import BaseCfgModule
|
12
12
|
from django_cfg.models.ngrok import NgrokConfig
|
13
13
|
|
14
14
|
logger = logging.getLogger(__name__)
|
@@ -100,7 +100,7 @@ class NgrokManager:
|
|
100
100
|
self.stop_tunnel()
|
101
101
|
|
102
102
|
|
103
|
-
class DjangoNgrok(
|
103
|
+
class DjangoNgrok(BaseCfgModule):
|
104
104
|
"""Main ngrok service for django-cfg."""
|
105
105
|
|
106
106
|
def __init__(self):
|
@@ -9,7 +9,7 @@ from typing import Optional, Dict, Any, List
|
|
9
9
|
import logging
|
10
10
|
from urllib.parse import urlparse
|
11
11
|
|
12
|
-
from django_cfg.modules.base import
|
12
|
+
from django_cfg.modules.base import BaseCfgModule
|
13
13
|
from django_cfg.models.tasks import TaskConfig, validate_task_config
|
14
14
|
from django_cfg.models.constance import ConstanceField
|
15
15
|
|
@@ -35,7 +35,7 @@ except ImportError:
|
|
35
35
|
logger = logging.getLogger(__name__)
|
36
36
|
|
37
37
|
|
38
|
-
class DjangoTasks(
|
38
|
+
class DjangoTasks(BaseCfgModule):
|
39
39
|
"""
|
40
40
|
Simplified Django-CFG task service.
|
41
41
|
|
@@ -325,7 +325,34 @@ def initialize_task_system():
|
|
325
325
|
|
326
326
|
# Django-dramatiq automatically configures the broker from DRAMATIQ_BROKER setting
|
327
327
|
if hasattr(settings, 'DRAMATIQ_BROKER'):
|
328
|
-
|
328
|
+
# Configure broker with middleware
|
329
|
+
broker_config = settings.DRAMATIQ_BROKER
|
330
|
+
middleware_list = getattr(settings, 'DRAMATIQ_MIDDLEWARE', [])
|
331
|
+
|
332
|
+
# Import and instantiate middleware
|
333
|
+
middleware_instances = []
|
334
|
+
for middleware_path in middleware_list:
|
335
|
+
try:
|
336
|
+
module_path, class_name = middleware_path.rsplit('.', 1)
|
337
|
+
module = __import__(module_path, fromlist=[class_name])
|
338
|
+
middleware_class = getattr(module, class_name)
|
339
|
+
middleware_instances.append(middleware_class())
|
340
|
+
except Exception as e:
|
341
|
+
logger.warning(f"Failed to load middleware {middleware_path}: {e}")
|
342
|
+
|
343
|
+
# Create broker with middleware
|
344
|
+
broker_class_path = broker_config['BROKER']
|
345
|
+
module_path, class_name = broker_class_path.rsplit('.', 1)
|
346
|
+
module = __import__(module_path, fromlist=[class_name])
|
347
|
+
broker_class = getattr(module, class_name)
|
348
|
+
|
349
|
+
broker_options = broker_config.get('OPTIONS', {})
|
350
|
+
broker = broker_class(middleware=middleware_instances, **broker_options)
|
351
|
+
|
352
|
+
# Set as default broker
|
353
|
+
dramatiq.set_broker(broker)
|
354
|
+
|
355
|
+
logger.debug(f"✅ Dramatiq broker configured with {len(middleware_instances)} middleware")
|
329
356
|
else:
|
330
357
|
logger.warning("DRAMATIQ_BROKER not found in Django settings")
|
331
358
|
|
@@ -341,6 +368,43 @@ def initialize_task_system():
|
|
341
368
|
logger.error(f"Failed to initialize task system: {e}")
|
342
369
|
|
343
370
|
|
371
|
+
def generate_dramatiq_settings_from_config(config):
|
372
|
+
"""
|
373
|
+
Generate Dramatiq settings from DjangoConfig instance.
|
374
|
+
|
375
|
+
Args:
|
376
|
+
config: DjangoConfig instance with tasks configuration
|
377
|
+
|
378
|
+
Returns:
|
379
|
+
Dict[str, Any]: Dramatiq settings dictionary or empty dict if not enabled
|
380
|
+
"""
|
381
|
+
try:
|
382
|
+
if not hasattr(config, "tasks") or not config.tasks or not config.tasks.enabled:
|
383
|
+
return {}
|
384
|
+
|
385
|
+
# Get Redis URL from cache configuration
|
386
|
+
redis_url = None
|
387
|
+
if config.cache_default and hasattr(config.cache_default, 'redis_url'):
|
388
|
+
redis_url = config.cache_default.redis_url
|
389
|
+
elif config.cache_default and hasattr(config.cache_default, 'location'):
|
390
|
+
redis_url = config.cache_default.location
|
391
|
+
else:
|
392
|
+
# Fallback to default Redis URL
|
393
|
+
redis_url = "redis://localhost:6379"
|
394
|
+
|
395
|
+
if redis_url:
|
396
|
+
dramatiq_settings = config.tasks.get_dramatiq_settings(redis_url)
|
397
|
+
logger.debug(f"Generated Dramatiq settings with Redis URL: {redis_url}")
|
398
|
+
return dramatiq_settings
|
399
|
+
else:
|
400
|
+
logger.warning("Tasks enabled but no Redis URL available for Dramatiq")
|
401
|
+
return {}
|
402
|
+
|
403
|
+
except Exception as e:
|
404
|
+
logger.error(f"Failed to generate Dramatiq settings: {e}")
|
405
|
+
return {}
|
406
|
+
|
407
|
+
|
344
408
|
def extend_constance_config_with_tasks():
|
345
409
|
"""
|
346
410
|
Extend Constance configuration with Dramatiq task fields if tasks are enabled.
|
@@ -368,6 +432,7 @@ __all__ = [
|
|
368
432
|
"reset_task_service",
|
369
433
|
"is_task_system_available",
|
370
434
|
"get_task_health",
|
435
|
+
"generate_dramatiq_settings_from_config",
|
371
436
|
"extend_constance_config_with_tasks",
|
372
437
|
"initialize_task_system",
|
373
438
|
]
|
@@ -9,8 +9,8 @@ import time
|
|
9
9
|
from typing import Optional, Dict, Any, Union, List, BinaryIO
|
10
10
|
from enum import Enum
|
11
11
|
import yaml
|
12
|
-
from django_cfg.modules import
|
13
|
-
from django_cfg.exceptions import ConfigurationError
|
12
|
+
from django_cfg.modules import BaseCfgModule
|
13
|
+
from django_cfg.core.exceptions import ConfigurationError
|
14
14
|
import telebot
|
15
15
|
|
16
16
|
logger = logging.getLogger(__name__)
|
@@ -42,7 +42,7 @@ class TelegramSendError(TelegramError):
|
|
42
42
|
pass
|
43
43
|
|
44
44
|
|
45
|
-
class DjangoTelegram(
|
45
|
+
class DjangoTelegram(BaseCfgModule):
|
46
46
|
"""
|
47
47
|
Telegram Service for django_cfg, configured via DjangoConfig.
|
48
48
|
|
@@ -19,7 +19,7 @@ from asgiref.sync import sync_to_async
|
|
19
19
|
from django.template.loader import render_to_string
|
20
20
|
from django.utils.html import strip_tags
|
21
21
|
|
22
|
-
from django_cfg.modules.base import
|
22
|
+
from django_cfg.modules.base import BaseCfgModule
|
23
23
|
from django_cfg.modules.django_twilio.models import TwilioConfig, SendGridConfig
|
24
24
|
from django_cfg.modules.django_twilio.exceptions import (
|
25
25
|
TwilioConfigurationError,
|
@@ -34,7 +34,7 @@ except ImportError:
|
|
34
34
|
logger = logging.getLogger(__name__)
|
35
35
|
|
36
36
|
|
37
|
-
class SendGridService(
|
37
|
+
class SendGridService(BaseCfgModule):
|
38
38
|
"""
|
39
39
|
SendGrid service for email delivery.
|
40
40
|
|
@@ -27,7 +27,7 @@ from sendgrid.helpers.mail import Mail
|
|
27
27
|
from asgiref.sync import sync_to_async, async_to_sync
|
28
28
|
|
29
29
|
# Django CFG imports
|
30
|
-
from django_cfg.modules.base import
|
30
|
+
from django_cfg.modules.base import BaseCfgModule
|
31
31
|
from django_cfg.modules.django_twilio.models import (
|
32
32
|
TwilioConfig,
|
33
33
|
TwilioChannelType,
|
@@ -55,7 +55,7 @@ def is_async_context() -> bool:
|
|
55
55
|
return False
|
56
56
|
|
57
57
|
|
58
|
-
class BaseTwilioService(
|
58
|
+
class BaseTwilioService(BaseCfgModule):
|
59
59
|
"""
|
60
60
|
Base service class for all Twilio operations.
|
61
61
|
|
@@ -10,7 +10,7 @@ from typing import Optional, Dict, Any
|
|
10
10
|
from twilio.rest import Client
|
11
11
|
from twilio.base.exceptions import TwilioException
|
12
12
|
|
13
|
-
from django_cfg.modules.base import
|
13
|
+
from django_cfg.modules.base import BaseCfgModule
|
14
14
|
from django_cfg.modules.django_twilio.models import TwilioConfig
|
15
15
|
from django_cfg.modules.django_twilio.exceptions import (
|
16
16
|
TwilioError,
|
@@ -21,7 +21,7 @@ from django_cfg.modules.django_twilio.exceptions import (
|
|
21
21
|
logger = logging.getLogger(__name__)
|
22
22
|
|
23
23
|
|
24
|
-
class SimpleTwilioService(
|
24
|
+
class SimpleTwilioService(BaseCfgModule):
|
25
25
|
"""
|
26
26
|
Simplified Twilio service for basic messaging operations.
|
27
27
|
|