django-cfg 1.4.106__py3-none-any.whl → 1.4.108__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.

Potentially problematic release.


This version of django-cfg might be problematic. Click here for more details.

Files changed (137) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/accounts/views/profile.py +19 -9
  3. django_cfg/apps/centrifugo/views/admin_api.py +4 -7
  4. django_cfg/apps/centrifugo/views/monitoring.py +3 -6
  5. django_cfg/apps/centrifugo/views/testing_api.py +3 -6
  6. django_cfg/apps/dashboard/services/system_health_service.py +16 -11
  7. django_cfg/apps/dashboard/views/activity_views.py +3 -5
  8. django_cfg/apps/dashboard/views/apizones_views.py +4 -5
  9. django_cfg/apps/dashboard/views/charts_views.py +4 -5
  10. django_cfg/apps/dashboard/views/overview_views.py +4 -5
  11. django_cfg/apps/dashboard/views/statistics_views.py +4 -5
  12. django_cfg/apps/dashboard/views/system_views.py +4 -5
  13. django_cfg/apps/knowbase/__init__.py +2 -2
  14. django_cfg/apps/knowbase/apps.py +2 -8
  15. django_cfg/apps/knowbase/views/base.py +9 -4
  16. django_cfg/apps/support/views/api.py +16 -7
  17. django_cfg/apps/tasks/__init__.py +61 -2
  18. django_cfg/apps/tasks/admin/__init__.py +3 -10
  19. django_cfg/apps/tasks/admin/config.py +98 -0
  20. django_cfg/apps/tasks/admin/task_log.py +265 -0
  21. django_cfg/apps/tasks/apps.py +7 -9
  22. django_cfg/apps/tasks/filters/__init__.py +10 -0
  23. django_cfg/apps/tasks/filters/task_log.py +121 -0
  24. django_cfg/apps/tasks/migrations/0001_initial.py +196 -0
  25. django_cfg/apps/tasks/models/__init__.py +4 -0
  26. django_cfg/apps/tasks/models/task_log.py +246 -0
  27. django_cfg/apps/tasks/serializers/__init__.py +28 -0
  28. django_cfg/apps/tasks/serializers/task_log.py +249 -0
  29. django_cfg/apps/tasks/services/__init__.py +10 -0
  30. django_cfg/apps/tasks/services/client/__init__.py +7 -0
  31. django_cfg/apps/tasks/services/client/client.py +234 -0
  32. django_cfg/apps/tasks/services/config_helper.py +63 -0
  33. django_cfg/apps/tasks/services/sync.py +204 -0
  34. django_cfg/apps/tasks/urls.py +7 -13
  35. django_cfg/apps/tasks/views/__init__.py +4 -10
  36. django_cfg/apps/tasks/views/task_log.py +41 -0
  37. django_cfg/apps/tasks/views/task_log_base.py +41 -0
  38. django_cfg/apps/tasks/views/task_log_overview.py +100 -0
  39. django_cfg/apps/tasks/views/task_log_related.py +41 -0
  40. django_cfg/apps/tasks/views/task_log_stats.py +91 -0
  41. django_cfg/apps/tasks/views/task_log_timeline.py +81 -0
  42. django_cfg/apps/urls.py +0 -1
  43. django_cfg/cli/commands/info.py +1 -1
  44. django_cfg/cli/utils.py +1 -1
  45. django_cfg/core/base/config_model.py +1 -1
  46. django_cfg/core/builders/apps_builder.py +1 -1
  47. django_cfg/core/generation/integration_generators/__init__.py +1 -1
  48. django_cfg/core/generation/integration_generators/tasks.py +14 -18
  49. django_cfg/core/generation/security_generators/crypto_fields.py +2 -1
  50. django_cfg/core/integration/display/startup.py +1 -1
  51. django_cfg/mixins/__init__.py +12 -0
  52. django_cfg/mixins/admin_api.py +37 -0
  53. django_cfg/mixins/client_api.py +39 -0
  54. django_cfg/models/django/constance.py +2 -8
  55. django_cfg/models/django/crypto_fields.py +13 -48
  56. django_cfg/models/tasks/__init__.py +8 -10
  57. django_cfg/models/tasks/backends.py +76 -207
  58. django_cfg/models/tasks/config.py +20 -127
  59. django_cfg/models/tasks/utils.py +17 -29
  60. django_cfg/modules/django_admin/RESOURCE_CONFIG_ENHANCEMENT.md +350 -0
  61. django_cfg/modules/django_admin/__init__.py +4 -0
  62. django_cfg/modules/django_admin/base/pydantic_admin.py +70 -15
  63. django_cfg/modules/django_admin/config/__init__.py +4 -0
  64. django_cfg/modules/django_admin/config/admin_config.py +13 -1
  65. django_cfg/modules/django_admin/config/background_task_config.py +76 -0
  66. django_cfg/modules/django_admin/config/resource_config.py +129 -0
  67. django_cfg/modules/django_client/management/commands/generate_client.py +13 -1
  68. django_cfg/modules/django_unfold/navigation.py +121 -22
  69. django_cfg/pyproject.toml +2 -2
  70. django_cfg/registry/core.py +1 -1
  71. django_cfg/static/frontend/admin.zip +0 -0
  72. {django_cfg-1.4.106.dist-info → django_cfg-1.4.108.dist-info}/METADATA +5 -3
  73. {django_cfg-1.4.106.dist-info → django_cfg-1.4.108.dist-info}/RECORD +77 -111
  74. django_cfg/apps/tasks/admin/actions.py +0 -29
  75. django_cfg/apps/tasks/admin/tasks_admin.py +0 -154
  76. django_cfg/apps/tasks/api/serializers.py +0 -82
  77. django_cfg/apps/tasks/api/views.py +0 -571
  78. django_cfg/apps/tasks/serializers.py +0 -82
  79. django_cfg/apps/tasks/static/tasks/css/dashboard-alpine.css +0 -299
  80. django_cfg/apps/tasks/static/tasks/css/dashboard.css +0 -120
  81. django_cfg/apps/tasks/static/tasks/js/alpine/README.md +0 -47
  82. django_cfg/apps/tasks/static/tasks/js/alpine/actions/index.js +0 -8
  83. django_cfg/apps/tasks/static/tasks/js/alpine/actions/management.js +0 -123
  84. django_cfg/apps/tasks/static/tasks/js/alpine/actions/pagination.js +0 -21
  85. django_cfg/apps/tasks/static/tasks/js/alpine/actions/tasks.js +0 -101
  86. django_cfg/apps/tasks/static/tasks/js/alpine/actions/workers.js +0 -59
  87. django_cfg/apps/tasks/static/tasks/js/alpine/computed.js +0 -35
  88. django_cfg/apps/tasks/static/tasks/js/alpine/index.js +0 -148
  89. django_cfg/apps/tasks/static/tasks/js/alpine/loaders/index.js +0 -36
  90. django_cfg/apps/tasks/static/tasks/js/alpine/loaders/overview.js +0 -37
  91. django_cfg/apps/tasks/static/tasks/js/alpine/loaders/queues.js +0 -27
  92. django_cfg/apps/tasks/static/tasks/js/alpine/loaders/tasks.js +0 -32
  93. django_cfg/apps/tasks/static/tasks/js/alpine/loaders/workers.js +0 -21
  94. django_cfg/apps/tasks/static/tasks/js/alpine/state.js +0 -36
  95. django_cfg/apps/tasks/static/tasks/js/alpine/utils/formatters.js +0 -42
  96. django_cfg/apps/tasks/static/tasks/js/alpine/utils/helpers.js +0 -68
  97. django_cfg/apps/tasks/static/tasks/js/dashboard-alpine.js +0 -725
  98. django_cfg/apps/tasks/tasks/__init__.py +0 -10
  99. django_cfg/apps/tasks/tasks/demo_tasks.py +0 -127
  100. django_cfg/apps/tasks/templates/tasks/components/management_actions.html +0 -71
  101. django_cfg/apps/tasks/templates/tasks/components/overview_content.html +0 -94
  102. django_cfg/apps/tasks/templates/tasks/components/queues_content.html +0 -44
  103. django_cfg/apps/tasks/templates/tasks/components/tab_navigation.html +0 -45
  104. django_cfg/apps/tasks/templates/tasks/components/task_details_modal.html +0 -151
  105. django_cfg/apps/tasks/templates/tasks/components/tasks_content.html +0 -61
  106. django_cfg/apps/tasks/templates/tasks/components/tasks_mjs_integration.html +0 -269
  107. django_cfg/apps/tasks/templates/tasks/components/workers_content.html +0 -60
  108. django_cfg/apps/tasks/templates/tasks/layout/base.html +0 -20
  109. django_cfg/apps/tasks/templates/tasks/pages/dashboard-improved.html +0 -168
  110. django_cfg/apps/tasks/templates/tasks/pages/dashboard.html +0 -77
  111. django_cfg/apps/tasks/templates/tasks/partials/task_row_template.html +0 -40
  112. django_cfg/apps/tasks/templates/tasks/widgets/task_filters.html +0 -40
  113. django_cfg/apps/tasks/templates/tasks/widgets/task_footer.html +0 -86
  114. django_cfg/apps/tasks/templates/tasks/widgets/task_table.html +0 -90
  115. django_cfg/apps/tasks/urls_admin.py +0 -15
  116. django_cfg/apps/tasks/utils/__init__.py +0 -1
  117. django_cfg/apps/tasks/utils/simulator.py +0 -353
  118. django_cfg/apps/tasks/views/api.py +0 -571
  119. django_cfg/apps/tasks/views/dashboard.py +0 -89
  120. django_cfg/management/commands/rundramatiq.py +0 -24
  121. django_cfg/management/commands/rundramatiq_simulator.py +0 -22
  122. django_cfg/management/commands/task_clear.py +0 -25
  123. django_cfg/management/commands/task_status.py +0 -24
  124. django_cfg/modules/django_tasks/__init__.py +0 -29
  125. django_cfg/modules/django_tasks/dramatiq_setup.py +0 -20
  126. django_cfg/modules/django_tasks/factory.py +0 -127
  127. django_cfg/modules/django_tasks/management/commands/__init__.py +0 -0
  128. django_cfg/modules/django_tasks/management/commands/rundramatiq.py +0 -253
  129. django_cfg/modules/django_tasks/management/commands/rundramatiq_simulator.py +0 -436
  130. django_cfg/modules/django_tasks/management/commands/task_clear.py +0 -226
  131. django_cfg/modules/django_tasks/management/commands/task_status.py +0 -257
  132. django_cfg/modules/django_tasks/service.py +0 -281
  133. django_cfg/modules/django_tasks/settings.py +0 -107
  134. /django_cfg/{modules/django_tasks/management → apps/tasks/migrations}/__init__.py +0 -0
  135. {django_cfg-1.4.106.dist-info → django_cfg-1.4.108.dist-info}/WHEEL +0 -0
  136. {django_cfg-1.4.106.dist-info → django_cfg-1.4.108.dist-info}/entry_points.txt +0 -0
  137. {django_cfg-1.4.106.dist-info → django_cfg-1.4.108.dist-info}/licenses/LICENSE +0 -0
@@ -1,257 +0,0 @@
1
- """
2
- Django management command for checking task system status.
3
-
4
- This command provides comprehensive status information about the
5
- Dramatiq task system, including queue statistics, worker status,
6
- and configuration details.
7
- """
8
-
9
- import json
10
- from typing import Any, Dict
11
-
12
- from django.core.management.base import BaseCommand, CommandError
13
-
14
- from django_cfg.modules.django_logging import get_logger
15
-
16
- logger = get_logger('task_status')
17
-
18
-
19
- class Command(BaseCommand):
20
- """
21
- Display comprehensive task system status.
22
-
23
- Shows information about:
24
- - Task system configuration
25
- - Redis connection status
26
- - Queue statistics
27
- - Worker status
28
- - Discovered task modules
29
- """
30
-
31
- # Web execution metadata
32
- web_executable = True
33
- requires_input = False
34
- is_destructive = False
35
-
36
- help = "Display task system status and statistics"
37
-
38
- def add_arguments(self, parser):
39
- """Add command line arguments."""
40
- parser.add_argument(
41
- "--format",
42
- choices=["text", "json"],
43
- default="text",
44
- help="Output format (default: text)",
45
- )
46
- parser.add_argument(
47
- "--verbose",
48
- action="store_true",
49
- help="Show detailed information",
50
- )
51
-
52
- def handle(self, *args, **options):
53
- """Handle the command execution."""
54
- logger.info("Starting task_status command")
55
- try:
56
- # Import here to avoid issues if dramatiq is not installed
57
- from django_cfg.modules.django_tasks import get_task_service
58
-
59
- # Get task service
60
- task_service = get_task_service()
61
-
62
- # Get comprehensive health status
63
- status = task_service.get_health_status()
64
-
65
- # Format and display output
66
- if options["format"] == "json":
67
- self._output_json(status)
68
- else:
69
- self._output_text(status, options["verbose"])
70
-
71
- except ImportError:
72
- raise CommandError(
73
- "Dramatiq dependencies not installed. "
74
- "Install with: pip install django-cfg[tasks]"
75
- )
76
- except Exception as e:
77
- logger.exception("Failed to get task status")
78
- raise CommandError(f"Failed to get status: {e}")
79
-
80
- def _output_json(self, status: Dict[str, Any]):
81
- """Output status in JSON format."""
82
- self.stdout.write(json.dumps(status, indent=2, default=str))
83
-
84
- def _output_text(self, status: Dict[str, Any], verbose: bool):
85
- """Output status in human-readable text format."""
86
- # Header
87
- self.stdout.write(
88
- self.style.SUCCESS("=== Django-CFG Task System Status ===")
89
- )
90
- self.stdout.write()
91
-
92
- # Basic status
93
- enabled = status.get("enabled", False)
94
- if enabled:
95
- self.stdout.write(
96
- self.style.SUCCESS("✓ Task system is ENABLED")
97
- )
98
- else:
99
- self.stdout.write(
100
- self.style.ERROR("✗ Task system is DISABLED")
101
- )
102
- return
103
-
104
- # Redis connection
105
- redis_ok = status.get("redis_connection", False)
106
- if redis_ok:
107
- self.stdout.write(
108
- self.style.SUCCESS("✓ Redis connection is OK")
109
- )
110
- else:
111
- self.stdout.write(
112
- self.style.ERROR("✗ Redis connection FAILED")
113
- )
114
-
115
- # Configuration validation
116
- config_valid = status.get("configuration_valid", False)
117
- if config_valid:
118
- self.stdout.write(
119
- self.style.SUCCESS("✓ Configuration is VALID")
120
- )
121
- else:
122
- self.stdout.write(
123
- self.style.ERROR("✗ Configuration is INVALID")
124
- )
125
-
126
- self.stdout.write()
127
-
128
- # Configuration details (if verbose)
129
- if verbose:
130
- self._show_configuration_details()
131
-
132
- # Queue statistics
133
- queues = status.get("queues", [])
134
- if queues:
135
- self.stdout.write(
136
- self.style.SUCCESS("=== Queue Statistics ===")
137
- )
138
- for queue in queues:
139
- name = queue.get("name", "unknown")
140
- pending = queue.get("pending", 0)
141
- running = queue.get("running", 0)
142
- completed = queue.get("completed", 0)
143
- failed = queue.get("failed", 0)
144
-
145
- self.stdout.write(f"Queue: {name}")
146
- self.stdout.write(f" Pending: {pending}")
147
- self.stdout.write(f" Running: {running}")
148
- self.stdout.write(f" Completed: {completed}")
149
- self.stdout.write(f" Failed: {failed}")
150
- self.stdout.write()
151
-
152
- # Worker status
153
- workers = status.get("workers", [])
154
- if workers:
155
- self.stdout.write(
156
- self.style.SUCCESS("=== Worker Status ===")
157
- )
158
- for worker in workers:
159
- worker_id = worker.get("id", "unknown")
160
- worker_status = worker.get("status", "unknown")
161
- current_task = worker.get("current_task")
162
- processed = worker.get("processed_tasks", 0)
163
-
164
- status_style = (
165
- self.style.SUCCESS if worker_status == "active"
166
- else self.style.WARNING if worker_status == "idle"
167
- else self.style.ERROR
168
- )
169
-
170
- self.stdout.write(f"Worker: {worker_id}")
171
- self.stdout.write(f" Status: {status_style(worker_status)}")
172
- if current_task:
173
- self.stdout.write(f" Current Task: {current_task}")
174
- self.stdout.write(f" Processed: {processed}")
175
- self.stdout.write()
176
- else:
177
- self.stdout.write(
178
- self.style.WARNING("No active workers found")
179
- )
180
-
181
- # Discovered modules
182
- modules = status.get("discovered_modules", [])
183
- if modules:
184
- self.stdout.write(
185
- self.style.SUCCESS("=== Discovered Task Modules ===")
186
- )
187
- for module in modules:
188
- self.stdout.write(f" - {module}")
189
- self.stdout.write()
190
- else:
191
- self.stdout.write(
192
- self.style.WARNING("No task modules discovered")
193
- )
194
-
195
- # Error information
196
- if "error" in status:
197
- self.stdout.write(
198
- self.style.ERROR(f"Error: {status['error']}")
199
- )
200
-
201
- def _show_configuration_details(self):
202
- """Show detailed configuration information."""
203
- try:
204
- from django_cfg.modules.django_tasks import get_task_service
205
-
206
- task_service = get_task_service()
207
- config = task_service.config
208
-
209
- if not config:
210
- self.stdout.write(
211
- self.style.WARNING("Configuration not available")
212
- )
213
- return
214
-
215
- self.stdout.write(
216
- self.style.SUCCESS("=== Configuration Details ===")
217
- )
218
-
219
- # Basic settings
220
- self.stdout.write(f"Backend: {config.backend}")
221
- self.stdout.write(f"Enabled: {config.enabled}")
222
- self.stdout.write(f"Auto-discover: {config.auto_discover_tasks}")
223
- self.stdout.write()
224
-
225
- # Dramatiq settings
226
- dramatiq = config.dramatiq
227
- self.stdout.write("Dramatiq Configuration:")
228
- self.stdout.write(f" Redis DB: {dramatiq.redis_db}")
229
- self.stdout.write(f" Max Retries: {dramatiq.max_retries}")
230
- self.stdout.write(f" Processes: {dramatiq.processes}")
231
- self.stdout.write(f" Threads: {dramatiq.threads}")
232
- self.stdout.write(f" Queues: {', '.join(dramatiq.queues)}")
233
- self.stdout.write(f" Time Limit: {dramatiq.time_limit_seconds}s")
234
- self.stdout.write(f" Max Age: {dramatiq.max_age_seconds}s")
235
- self.stdout.write()
236
-
237
- # Worker settings
238
- worker = config.worker
239
- self.stdout.write("Worker Configuration:")
240
- self.stdout.write(f" Log Level: {worker.log_level}")
241
- self.stdout.write(f" Shutdown Timeout: {worker.shutdown_timeout}s")
242
- self.stdout.write(f" Health Check: {worker.health_check_enabled}")
243
- if worker.max_memory_mb:
244
- self.stdout.write(f" Memory Limit: {worker.max_memory_mb}MB")
245
- self.stdout.write()
246
-
247
- # Middleware
248
- if dramatiq.middleware:
249
- self.stdout.write("Middleware Stack:")
250
- for middleware in dramatiq.middleware:
251
- self.stdout.write(f" - {middleware}")
252
- self.stdout.write()
253
-
254
- except Exception as e:
255
- self.stdout.write(
256
- self.style.ERROR(f"Failed to show configuration: {e}")
257
- )
@@ -1,281 +0,0 @@
1
- """
2
- Django-CFG Task Service.
3
-
4
- Main DjangoTasks class for Dramatiq integration.
5
- """
6
-
7
- import logging
8
- from typing import Any, Dict, List, Optional
9
- from urllib.parse import urlparse
10
-
11
- from django_cfg.models.django.constance import ConstanceField
12
- from django_cfg.models.tasks import TaskConfig, validate_task_config
13
-
14
- from ..base import BaseCfgModule
15
-
16
- # Django imports
17
- try:
18
- from django.apps import apps
19
- except ImportError:
20
- apps = None
21
-
22
- # Optional imports
23
- try:
24
- import dramatiq
25
- except ImportError:
26
- dramatiq = None
27
-
28
- try:
29
- import redis
30
- except ImportError:
31
- redis = None
32
-
33
- logger = logging.getLogger(__name__)
34
-
35
-
36
- class DjangoTasks(BaseCfgModule):
37
- """
38
- Simplified Django-CFG task service.
39
-
40
- Focuses on essential functionality:
41
- - Configuration management
42
- - Task discovery
43
- - Health checks
44
- - Constance integration
45
- """
46
-
47
- def __init__(self):
48
- super().__init__()
49
- self._config: Optional[TaskConfig] = None
50
- self._redis_url: Optional[str] = None
51
-
52
- @property
53
- def config(self) -> Optional[TaskConfig]:
54
- """Get task configuration (lazy-loaded)."""
55
- if self._config is None:
56
- try:
57
- django_config = self.get_config()
58
- if django_config and hasattr(django_config, 'tasks'):
59
- self._config = django_config.tasks
60
- logger.debug(f"Loaded TaskConfig: enabled={self._config.enabled if self._config else False}")
61
- else:
62
- # Fallback: try direct import
63
- try:
64
- from api.config import config as api_config
65
- if hasattr(api_config, 'tasks') and api_config.tasks:
66
- self._config = api_config.tasks
67
- logger.debug(f"Loaded TaskConfig from api.config: enabled={self._config.enabled}")
68
- except ImportError:
69
- logger.debug("Could not import api.config")
70
- except Exception as e:
71
- logger.warning(f"Failed to get task config: {e}")
72
-
73
- return self._config
74
-
75
- def is_enabled(self) -> bool:
76
- """Check if task system is enabled and properly configured."""
77
- if not self.config or not self.config.enabled:
78
- return False
79
-
80
- if dramatiq is None:
81
- logger.warning("Dramatiq not available")
82
- return False
83
-
84
- return True
85
-
86
- def get_redis_url(self) -> Optional[str]:
87
- """Get Redis URL using the same logic as Dramatiq settings generation."""
88
- if self._redis_url is None:
89
- config = self.get_config()
90
-
91
- if not config:
92
- raise RuntimeError("No Django-CFG configuration available")
93
-
94
- # Get Redis URL from cache config
95
- if hasattr(config, 'cache_default') and config.cache_default:
96
- self._redis_url = getattr(config.cache_default, 'redis_url', None)
97
- if self._redis_url:
98
- logger.debug(f"Got Redis URL from cache config: {self._redis_url}")
99
- return self._redis_url
100
-
101
- # If no cache_default, try Django cache settings
102
- try:
103
- from django.conf import settings
104
- if hasattr(settings, 'CACHES') and 'default' in settings.CACHES:
105
- cache_config = settings.CACHES['default']
106
- if cache_config.get('BACKEND') == 'django_redis.cache.RedisCache':
107
- self._redis_url = cache_config.get('LOCATION')
108
- if self._redis_url:
109
- logger.debug(f"Got Redis URL from Django cache settings: {self._redis_url}")
110
- return self._redis_url
111
- except Exception as e:
112
- logger.debug(f"Could not get Redis URL from Django settings: {e}")
113
-
114
- # Try DRAMATIQ_BROKER settings
115
- try:
116
- from django.conf import settings
117
- if hasattr(settings, 'DRAMATIQ_BROKER'):
118
- dramatiq_config = settings.DRAMATIQ_BROKER
119
- if isinstance(dramatiq_config, dict) and 'OPTIONS' in dramatiq_config:
120
- self._redis_url = dramatiq_config['OPTIONS'].get('url')
121
- if self._redis_url:
122
- logger.debug(f"Got Redis URL from DRAMATIQ_BROKER settings: {self._redis_url}")
123
- return self._redis_url
124
- except Exception as e:
125
- logger.debug(f"Could not get Redis URL from DRAMATIQ_BROKER settings: {e}")
126
-
127
- raise RuntimeError("No Redis URL found in cache configuration, Django settings, or DRAMATIQ_BROKER")
128
-
129
- return self._redis_url
130
-
131
- def get_redis_client(self):
132
- """Get Redis client instance."""
133
- redis_url = self.get_redis_url()
134
- if not redis_url or redis is None:
135
- return None
136
-
137
- try:
138
- parsed = urlparse(redis_url)
139
-
140
- # Extract database from URL path
141
- db = 1 # Default
142
- if parsed.path and parsed.path != "/":
143
- try:
144
- db = int(parsed.path.lstrip('/'))
145
- except ValueError:
146
- pass
147
- elif self.config and self.config.dramatiq:
148
- db = self.config.dramatiq.redis_db
149
-
150
- logger.debug(f"Using Redis DB: {db} from URL: {redis_url}")
151
-
152
- return redis.Redis(
153
- host=parsed.hostname or 'localhost',
154
- port=parsed.port or 6379,
155
- db=db,
156
- password=parsed.password,
157
- socket_timeout=5
158
- )
159
- except Exception as e:
160
- logger.error(f"Failed to create Redis client: {e}")
161
- return None
162
-
163
- def check_redis_connection(self) -> bool:
164
- """Check if Redis connection is available."""
165
- redis_client = self.get_redis_client()
166
- if not redis_client:
167
- return False
168
-
169
- try:
170
- redis_client.ping()
171
- return True
172
- except Exception as e:
173
- logger.error(f"Redis connection failed: {e}")
174
- return False
175
-
176
- def validate_configuration(self) -> bool:
177
- """Validate complete task system configuration."""
178
- if not self.config:
179
- logger.error("Task configuration not available")
180
- return False
181
-
182
- redis_url = self.get_redis_url()
183
- if not redis_url:
184
- logger.error("Redis URL not configured")
185
- return False
186
-
187
- return validate_task_config(self.config, redis_url)
188
-
189
- def discover_tasks(self) -> List[str]:
190
- """Discover task modules in Django apps."""
191
- if not self.config or not self.config.auto_discover_tasks:
192
- return []
193
-
194
- discovered = []
195
-
196
- if apps is None:
197
- logger.warning("Django apps not available")
198
- return []
199
-
200
- try:
201
- for app_config in apps.get_app_configs():
202
- for module_name in self.config.task_modules:
203
- module_path = f"{app_config.name}.{module_name}"
204
- try:
205
- __import__(module_path)
206
- discovered.append(module_path)
207
- logger.debug(f"Discovered task module: {module_path}")
208
- except ImportError:
209
- pass
210
- except Exception as e:
211
- logger.warning(f"Error importing task module {module_path}: {e}")
212
- except Exception as e:
213
- logger.error(f"Task discovery failed: {e}")
214
-
215
- return discovered
216
-
217
- def get_constance_fields(self) -> List[ConstanceField]:
218
- """Get Constance fields for Dramatiq configuration."""
219
- if not self.is_enabled():
220
- return []
221
-
222
- fields = [
223
- ConstanceField(
224
- name="DRAMATIQ_WORKER_PROCESSES",
225
- default=self.config.dramatiq.processes if self.config else 2,
226
- help_text="Number of worker processes for Dramatiq",
227
- field_type="int",
228
- group="Tasks",
229
- ),
230
- ConstanceField(
231
- name="DRAMATIQ_WORKER_THREADS",
232
- default=self.config.dramatiq.threads if self.config else 4,
233
- help_text="Number of threads per worker process",
234
- field_type="int",
235
- group="Tasks",
236
- ),
237
- ConstanceField(
238
- name="DRAMATIQ_MAX_RETRIES",
239
- default=3,
240
- help_text="Maximum number of retries for failed tasks",
241
- field_type="int",
242
- group="Tasks",
243
- ),
244
- ConstanceField(
245
- name="DRAMATIQ_TASK_TIMEOUT",
246
- default=600,
247
- help_text="Task timeout in seconds (10 minutes default)",
248
- field_type="int",
249
- group="Tasks",
250
- ),
251
- ConstanceField(
252
- name="DRAMATIQ_PROMETHEUS_ENABLED",
253
- default=int(self.config.dramatiq.prometheus_enabled if self.config else False),
254
- help_text="Enable Prometheus metrics for Dramatiq (0=disabled, 1=enabled)",
255
- field_type="bool",
256
- group="Tasks",
257
- required=False,
258
- ),
259
- ]
260
-
261
- logger.debug(f"Generated {len(fields)} Constance fields for Dramatiq")
262
- return fields
263
-
264
- def get_health_status(self) -> Dict[str, Any]:
265
- """Get comprehensive health status of task system."""
266
- status = {
267
- "enabled": self.is_enabled(),
268
- "redis_connection": False,
269
- "configuration_valid": False,
270
- "discovered_modules": [],
271
- }
272
-
273
- if self.is_enabled():
274
- status["redis_connection"] = self.check_redis_connection()
275
- status["configuration_valid"] = self.validate_configuration()
276
- status["discovered_modules"] = self.discover_tasks()
277
-
278
- return status
279
-
280
-
281
- __all__ = ["DjangoTasks"]
@@ -1,107 +0,0 @@
1
- """
2
- Dramatiq settings generation.
3
-
4
- Functions to generate Dramatiq settings from DjangoConfig.
5
- """
6
-
7
- import logging
8
- from typing import Any, Dict, Optional
9
-
10
- logger = logging.getLogger(__name__)
11
-
12
-
13
- def generate_dramatiq_settings_from_config(config=None) -> Optional[Dict[str, Any]]:
14
- """
15
- Generate Dramatiq settings from DjangoConfig instance.
16
-
17
- Args:
18
- config: DjangoConfig instance (optional, will auto-discover if not provided)
19
-
20
- Returns:
21
- Dict[str, Any]: Dramatiq settings dictionary or None if not enabled
22
- """
23
- # If config provided, use it directly
24
- if config is not None:
25
- try:
26
- if not hasattr(config, "tasks") or not config.tasks or not config.tasks.enabled:
27
- return None
28
-
29
- # Get Redis URL from cache configuration
30
- redis_url = None
31
- if config.cache_default and hasattr(config.cache_default, 'redis_url'):
32
- redis_url = config.cache_default.redis_url
33
- elif config.cache_default and hasattr(config.cache_default, 'location'):
34
- redis_url = config.cache_default.location
35
- else:
36
- redis_url = "redis://localhost:6379"
37
-
38
- if redis_url:
39
- dramatiq_settings = config.tasks.get_dramatiq_settings(redis_url)
40
- logger.debug(f"Generated Dramatiq settings with Redis URL: {redis_url}")
41
- return dramatiq_settings
42
- else:
43
- logger.warning("Tasks enabled but no Redis URL available for Dramatiq")
44
- return None
45
-
46
- except Exception as e:
47
- logger.error(f"Failed to generate Dramatiq settings: {e}")
48
- return None
49
-
50
- # Auto-discover config if not provided
51
- try:
52
- from ..base import BaseCfgModule
53
-
54
- base_module = BaseCfgModule()
55
- config = base_module.get_config()
56
-
57
- if not config or not hasattr(config, 'tasks') or not config.tasks or not config.tasks.enabled:
58
- return None
59
-
60
- # Get Redis URL from cache config or environment
61
- redis_url = None
62
- if hasattr(config, 'cache_default') and config.cache_default:
63
- redis_url = getattr(config.cache_default, 'redis_url', None)
64
-
65
- if not redis_url:
66
- # Fallback to environment or default
67
- import os
68
- redis_url = os.getenv('REDIS_URL', 'redis://localhost:6379/1')
69
-
70
- # Generate Dramatiq settings
71
- dramatiq_settings = config.tasks.get_dramatiq_settings(redis_url)
72
-
73
- # Ensure we only use Redis broker (no RabbitMQ)
74
- if 'DRAMATIQ_BROKER' in dramatiq_settings:
75
- dramatiq_settings['DRAMATIQ_BROKER']['BROKER'] = 'dramatiq.brokers.redis.RedisBroker'
76
-
77
- logger.info(f"✅ Generated Dramatiq settings with Redis broker and {len(config.tasks.dramatiq.queues)} queues")
78
- return dramatiq_settings
79
-
80
- except Exception as e:
81
- logger.error(f"Failed to generate Dramatiq settings: {e}")
82
- return None
83
-
84
-
85
- def extend_constance_config_with_tasks():
86
- """Extend Constance configuration with Dramatiq task fields if tasks are enabled."""
87
- try:
88
- from .factory import get_task_service
89
-
90
- service = get_task_service()
91
- if not service.is_enabled():
92
- logger.debug("Task system not enabled, skipping Constance extension")
93
- return []
94
-
95
- fields = service.get_constance_fields()
96
- logger.info(f"🔧 Extended Constance with {len(fields)} task configuration fields")
97
- return fields
98
-
99
- except Exception as e:
100
- logger.error(f"Failed to extend Constance config with tasks: {e}")
101
- return []
102
-
103
-
104
- __all__ = [
105
- "generate_dramatiq_settings_from_config",
106
- "extend_constance_config_with_tasks",
107
- ]