django-cfg 1.4.107__py3-none-any.whl → 1.4.109__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 (140) 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_client/management/commands/generate_client.py +13 -1
  61. django_cfg/modules/django_unfold/navigation.py +121 -22
  62. django_cfg/pyproject.toml +2 -2
  63. django_cfg/registry/core.py +1 -1
  64. django_cfg/static/frontend/admin.zip +0 -0
  65. {django_cfg-1.4.107.dist-info → django_cfg-1.4.109.dist-info}/METADATA +3 -3
  66. {django_cfg-1.4.107.dist-info → django_cfg-1.4.109.dist-info}/RECORD +70 -117
  67. django_cfg/apps/tasks/admin/actions.py +0 -29
  68. django_cfg/apps/tasks/admin/tasks_admin.py +0 -154
  69. django_cfg/apps/tasks/api/serializers.py +0 -82
  70. django_cfg/apps/tasks/api/views.py +0 -571
  71. django_cfg/apps/tasks/serializers.py +0 -82
  72. django_cfg/apps/tasks/static/tasks/css/dashboard-alpine.css +0 -299
  73. django_cfg/apps/tasks/static/tasks/css/dashboard.css +0 -120
  74. django_cfg/apps/tasks/static/tasks/js/alpine/README.md +0 -47
  75. django_cfg/apps/tasks/static/tasks/js/alpine/actions/index.js +0 -8
  76. django_cfg/apps/tasks/static/tasks/js/alpine/actions/management.js +0 -123
  77. django_cfg/apps/tasks/static/tasks/js/alpine/actions/pagination.js +0 -21
  78. django_cfg/apps/tasks/static/tasks/js/alpine/actions/tasks.js +0 -101
  79. django_cfg/apps/tasks/static/tasks/js/alpine/actions/workers.js +0 -59
  80. django_cfg/apps/tasks/static/tasks/js/alpine/computed.js +0 -35
  81. django_cfg/apps/tasks/static/tasks/js/alpine/index.js +0 -148
  82. django_cfg/apps/tasks/static/tasks/js/alpine/loaders/index.js +0 -36
  83. django_cfg/apps/tasks/static/tasks/js/alpine/loaders/overview.js +0 -37
  84. django_cfg/apps/tasks/static/tasks/js/alpine/loaders/queues.js +0 -27
  85. django_cfg/apps/tasks/static/tasks/js/alpine/loaders/tasks.js +0 -32
  86. django_cfg/apps/tasks/static/tasks/js/alpine/loaders/workers.js +0 -21
  87. django_cfg/apps/tasks/static/tasks/js/alpine/state.js +0 -36
  88. django_cfg/apps/tasks/static/tasks/js/alpine/utils/formatters.js +0 -42
  89. django_cfg/apps/tasks/static/tasks/js/alpine/utils/helpers.js +0 -68
  90. django_cfg/apps/tasks/static/tasks/js/dashboard-alpine.js +0 -725
  91. django_cfg/apps/tasks/tasks/__init__.py +0 -10
  92. django_cfg/apps/tasks/tasks/demo_tasks.py +0 -127
  93. django_cfg/apps/tasks/templates/tasks/components/management_actions.html +0 -71
  94. django_cfg/apps/tasks/templates/tasks/components/overview_content.html +0 -94
  95. django_cfg/apps/tasks/templates/tasks/components/queues_content.html +0 -44
  96. django_cfg/apps/tasks/templates/tasks/components/tab_navigation.html +0 -45
  97. django_cfg/apps/tasks/templates/tasks/components/task_details_modal.html +0 -151
  98. django_cfg/apps/tasks/templates/tasks/components/tasks_content.html +0 -61
  99. django_cfg/apps/tasks/templates/tasks/components/tasks_mjs_integration.html +0 -269
  100. django_cfg/apps/tasks/templates/tasks/components/workers_content.html +0 -60
  101. django_cfg/apps/tasks/templates/tasks/layout/base.html +0 -20
  102. django_cfg/apps/tasks/templates/tasks/pages/dashboard-improved.html +0 -168
  103. django_cfg/apps/tasks/templates/tasks/pages/dashboard.html +0 -77
  104. django_cfg/apps/tasks/templates/tasks/partials/task_row_template.html +0 -40
  105. django_cfg/apps/tasks/templates/tasks/widgets/task_filters.html +0 -40
  106. django_cfg/apps/tasks/templates/tasks/widgets/task_footer.html +0 -86
  107. django_cfg/apps/tasks/templates/tasks/widgets/task_table.html +0 -90
  108. django_cfg/apps/tasks/urls_admin.py +0 -15
  109. django_cfg/apps/tasks/utils/__init__.py +0 -1
  110. django_cfg/apps/tasks/utils/simulator.py +0 -353
  111. django_cfg/apps/tasks/views/api.py +0 -571
  112. django_cfg/apps/tasks/views/dashboard.py +0 -89
  113. django_cfg/management/commands/rundramatiq.py +0 -24
  114. django_cfg/management/commands/rundramatiq_simulator.py +0 -22
  115. django_cfg/management/commands/task_clear.py +0 -25
  116. django_cfg/management/commands/task_status.py +0 -24
  117. django_cfg/modules/django_client/system/__init__.py +0 -24
  118. django_cfg/modules/django_client/system/base_generator.py +0 -123
  119. django_cfg/modules/django_client/system/generate_mjs_clients.py +0 -176
  120. django_cfg/modules/django_client/system/mjs_generator.py +0 -219
  121. django_cfg/modules/django_client/system/schema_parser.py +0 -199
  122. django_cfg/modules/django_client/system/templates/api_client.js.j2 +0 -87
  123. django_cfg/modules/django_client/system/templates/app_index.js.j2 +0 -13
  124. django_cfg/modules/django_client/system/templates/base_client.js.j2 +0 -166
  125. django_cfg/modules/django_client/system/templates/main_index.js.j2 +0 -80
  126. django_cfg/modules/django_client/system/templates/types.js.j2 +0 -24
  127. django_cfg/modules/django_tasks/__init__.py +0 -29
  128. django_cfg/modules/django_tasks/dramatiq_setup.py +0 -20
  129. django_cfg/modules/django_tasks/factory.py +0 -127
  130. django_cfg/modules/django_tasks/management/commands/__init__.py +0 -0
  131. django_cfg/modules/django_tasks/management/commands/rundramatiq.py +0 -253
  132. django_cfg/modules/django_tasks/management/commands/rundramatiq_simulator.py +0 -436
  133. django_cfg/modules/django_tasks/management/commands/task_clear.py +0 -226
  134. django_cfg/modules/django_tasks/management/commands/task_status.py +0 -257
  135. django_cfg/modules/django_tasks/service.py +0 -281
  136. django_cfg/modules/django_tasks/settings.py +0 -107
  137. /django_cfg/{modules/django_tasks/management → apps/tasks/migrations}/__init__.py +0 -0
  138. {django_cfg-1.4.107.dist-info → django_cfg-1.4.109.dist-info}/WHEEL +0 -0
  139. {django_cfg-1.4.107.dist-info → django_cfg-1.4.109.dist-info}/entry_points.txt +0 -0
  140. {django_cfg-1.4.107.dist-info → django_cfg-1.4.109.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,249 @@
1
+ """
2
+ TaskLog DRF Serializers.
3
+
4
+ Serializers for TaskLog model with filtering and statistics.
5
+ """
6
+ from rest_framework import serializers
7
+ from ..models import TaskLog
8
+
9
+
10
+ class TaskLogSerializer(serializers.ModelSerializer):
11
+ """
12
+ Basic TaskLog serializer.
13
+
14
+ Used for list views with essential fields only.
15
+ Includes computed properties matching ReArq response format.
16
+ """
17
+
18
+ duration_seconds = serializers.SerializerMethodField()
19
+ is_completed = serializers.BooleanField(read_only=True)
20
+ is_successful = serializers.BooleanField(read_only=True)
21
+ is_failed = serializers.BooleanField(read_only=True)
22
+
23
+ class Meta:
24
+ model = TaskLog
25
+ fields = [
26
+ 'id',
27
+ 'job_id',
28
+ 'task_name',
29
+ 'queue_name',
30
+ 'status',
31
+ 'success',
32
+ 'duration_ms',
33
+ 'duration_seconds',
34
+ 'job_retry',
35
+ 'job_retries',
36
+ 'enqueue_time',
37
+ 'expire_time',
38
+ 'start_time',
39
+ 'finish_time',
40
+ 'is_completed',
41
+ 'is_successful',
42
+ 'is_failed',
43
+ ]
44
+ read_only_fields = fields
45
+
46
+ def get_duration_seconds(self, obj) -> float:
47
+ """Convert duration from ms to seconds."""
48
+ if obj.duration_ms is not None:
49
+ return round(obj.duration_ms / 1000, 2)
50
+ return None
51
+
52
+
53
+ class TaskLogListSerializer(serializers.ModelSerializer):
54
+ """
55
+ Compact serializer for list views.
56
+
57
+ Minimal fields for performance, matching ReArq Job list format.
58
+ """
59
+
60
+ status_display = serializers.CharField(source='get_status_display', read_only=True)
61
+
62
+ class Meta:
63
+ model = TaskLog
64
+ fields = [
65
+ 'id',
66
+ 'job_id',
67
+ 'task_name',
68
+ 'queue_name',
69
+ 'status',
70
+ 'status_display',
71
+ 'success',
72
+ 'job_retries',
73
+ 'duration_ms',
74
+ 'enqueue_time',
75
+ 'start_time',
76
+ 'finish_time',
77
+ ]
78
+ read_only_fields = fields
79
+
80
+
81
+ class TaskLogDetailSerializer(serializers.ModelSerializer):
82
+ """
83
+ Detailed TaskLog serializer.
84
+
85
+ Includes all fields including args, kwargs, result, error messages.
86
+ Combines ReArq Job + JobResult data.
87
+ """
88
+
89
+ status_display = serializers.CharField(source='get_status_display', read_only=True)
90
+ duration_seconds = serializers.SerializerMethodField()
91
+ user_display = serializers.SerializerMethodField()
92
+
93
+ class Meta:
94
+ model = TaskLog
95
+ fields = [
96
+ # Job identification
97
+ 'id',
98
+ 'job_id',
99
+ 'task_name',
100
+ 'queue_name',
101
+ # Status
102
+ 'status',
103
+ 'status_display',
104
+ 'success',
105
+ # Arguments
106
+ 'args',
107
+ 'kwargs',
108
+ # Result
109
+ 'result',
110
+ 'error_message',
111
+ # Performance
112
+ 'duration_ms',
113
+ 'duration_seconds',
114
+ # Retry info (from ReArq Job)
115
+ 'job_retry',
116
+ 'job_retries',
117
+ 'job_retry_after',
118
+ # Worker
119
+ 'worker_id',
120
+ # Timestamps (from ReArq)
121
+ 'enqueue_time',
122
+ 'expire_time',
123
+ 'start_time',
124
+ 'finish_time',
125
+ # Django timestamps
126
+ 'created_at',
127
+ 'updated_at',
128
+ # User
129
+ 'user',
130
+ 'user_display',
131
+ ]
132
+ read_only_fields = fields
133
+
134
+ def get_duration_seconds(self, obj) -> float:
135
+ """Convert duration from ms to seconds."""
136
+ if obj.duration_ms is not None:
137
+ return round(obj.duration_ms / 1000, 2)
138
+ return None
139
+
140
+ def get_user_display(self, obj) -> str:
141
+ """Get user display name."""
142
+ if obj.user:
143
+ return f"{obj.user.username} ({obj.user.email})"
144
+ return None
145
+
146
+
147
+ class TaskLogStatsSerializer(serializers.Serializer):
148
+ """
149
+ Statistics serializer for task metrics.
150
+
151
+ Not tied to a model - used for aggregated data.
152
+ """
153
+
154
+ total = serializers.IntegerField(help_text="Total number of task executions")
155
+ successful = serializers.IntegerField(help_text="Number of successful executions")
156
+ failed = serializers.IntegerField(help_text="Number of failed executions")
157
+ in_progress = serializers.IntegerField(help_text="Number of tasks currently running")
158
+ success_rate = serializers.FloatField(help_text="Success rate percentage")
159
+ avg_duration_ms = serializers.IntegerField(help_text="Average duration in milliseconds")
160
+ avg_duration_seconds = serializers.FloatField(help_text="Average duration in seconds")
161
+ period_hours = serializers.IntegerField(help_text="Statistics period in hours", required=False)
162
+
163
+
164
+ class TasksByQueueSerializer(serializers.Serializer):
165
+ """
166
+ Tasks count by queue.
167
+
168
+ Used in overview endpoint for tasks_by_queue list.
169
+ """
170
+ queue_name = serializers.CharField(help_text="Queue name")
171
+ count = serializers.IntegerField(help_text="Number of tasks in this queue")
172
+
173
+
174
+ class TasksByStatusSerializer(serializers.Serializer):
175
+ """
176
+ Tasks count by status.
177
+
178
+ Used in overview endpoint for tasks_by_status list.
179
+ """
180
+ status = serializers.CharField(help_text="Task status")
181
+ count = serializers.IntegerField(help_text="Number of tasks with this status")
182
+
183
+
184
+ class TaskLogOverviewSerializer(serializers.Serializer):
185
+ """
186
+ Overview of task system with proper structure.
187
+
188
+ Provides high-level statistics about the entire task system:
189
+ - Total tasks count (all-time)
190
+ - Active queues list
191
+ - Recent failures (last 24h)
192
+ - Tasks distribution by queue (as array)
193
+ - Tasks distribution by status (as array)
194
+
195
+ Used by /cfg/tasks/logs/overview/ endpoint.
196
+ """
197
+ total_tasks = serializers.IntegerField(help_text="Total number of tasks all-time")
198
+ active_queues = serializers.ListField(
199
+ child=serializers.CharField(),
200
+ help_text="List of active queue names"
201
+ )
202
+ recent_failures = serializers.IntegerField(help_text="Failed tasks in last 24 hours")
203
+ tasks_by_queue = TasksByQueueSerializer(
204
+ many=True,
205
+ help_text="Tasks grouped by queue name"
206
+ )
207
+ tasks_by_status = TasksByStatusSerializer(
208
+ many=True,
209
+ help_text="Tasks grouped by status"
210
+ )
211
+
212
+
213
+ class TaskLogTimelineItemSerializer(serializers.Serializer):
214
+ """
215
+ Single timeline data point.
216
+
217
+ Represents aggregated task statistics for a specific time period.
218
+ """
219
+ timestamp = serializers.DateTimeField(help_text="Time bucket start")
220
+ total = serializers.IntegerField(help_text="Total tasks in this period")
221
+ successful = serializers.IntegerField(help_text="Successful tasks")
222
+ failed = serializers.IntegerField(help_text="Failed tasks")
223
+ in_progress = serializers.IntegerField(help_text="Tasks currently in progress", required=False)
224
+ avg_duration_ms = serializers.FloatField(help_text="Average duration in milliseconds", required=False)
225
+
226
+
227
+ class TaskLogTimelineSerializer(serializers.Serializer):
228
+ """
229
+ Timeline response wrapper.
230
+
231
+ Returns timeline data as array of time-bucketed statistics.
232
+ Used by /cfg/tasks/logs/timeline/ endpoint.
233
+ """
234
+ period_hours = serializers.IntegerField(help_text="Time period covered in hours")
235
+ interval = serializers.CharField(help_text="Time bucket interval (hour/day)")
236
+ data = TaskLogTimelineItemSerializer(many=True, help_text="Timeline data points")
237
+
238
+
239
+ __all__ = [
240
+ "TaskLogSerializer",
241
+ "TaskLogListSerializer",
242
+ "TaskLogDetailSerializer",
243
+ "TaskLogStatsSerializer",
244
+ "TasksByQueueSerializer",
245
+ "TasksByStatusSerializer",
246
+ "TaskLogOverviewSerializer",
247
+ "TaskLogTimelineItemSerializer",
248
+ "TaskLogTimelineSerializer",
249
+ ]
@@ -0,0 +1,10 @@
1
+ """Task services module."""
2
+ from .client import ReArqClient, get_rearq_client
3
+ from .config_helper import get_tasks_config, get_tasks_config_or_default
4
+
5
+ __all__ = [
6
+ "ReArqClient",
7
+ "get_rearq_client",
8
+ "get_tasks_config",
9
+ "get_tasks_config_or_default",
10
+ ]
@@ -0,0 +1,7 @@
1
+ """ReArq client module."""
2
+ from .client import ReArqClient, get_rearq_client
3
+
4
+ __all__ = [
5
+ "ReArqClient",
6
+ "get_rearq_client",
7
+ ]
@@ -0,0 +1,234 @@
1
+ """
2
+ ReArq client wrapper for Django-CFG.
3
+
4
+ Provides singleton access to ReArq with django-cfg configuration.
5
+ """
6
+ from typing import TYPE_CHECKING, Optional
7
+
8
+ from django_cfg.modules.django_logging import get_logger
9
+
10
+ if TYPE_CHECKING:
11
+ from rearq import ReArq
12
+
13
+ logger = get_logger("tasks.client")
14
+
15
+
16
+ class ReArqClient:
17
+ """
18
+ Django wrapper for ReArq client.
19
+
20
+ Provides singleton access to ReArq with django-cfg configuration.
21
+
22
+ Features:
23
+ - Async task queue with Redis backend
24
+ - Job persistence with Tortoise ORM
25
+ - Built-in retry logic
26
+ - Cron task support
27
+ - Job result tracking
28
+
29
+ Example:
30
+ >>> from django_cfg.apps.tasks import get_rearq_client
31
+ >>>
32
+ >>> client = get_rearq_client()
33
+ >>>
34
+ >>> # Define task
35
+ >>> @client.task(queue="default")
36
+ >>> async def process_data(data_id: str):
37
+ ... # Process data
38
+ ... return {"status": "done"}
39
+ >>>
40
+ >>> # Execute task
41
+ >>> job = await process_data.delay(data_id="123")
42
+ >>> result = await job.result(timeout=30)
43
+ """
44
+
45
+ def __init__(
46
+ self,
47
+ redis_url: str,
48
+ db_url: str,
49
+ max_jobs: int = 10,
50
+ job_timeout: int = 300,
51
+ job_retry: int = 3,
52
+ job_retry_after: int = 60,
53
+ keep_job_days: int | None = 7,
54
+ ):
55
+ """
56
+ Initialize ReArq client.
57
+
58
+ Args:
59
+ redis_url: Redis connection URL for task queue
60
+ db_url: Database URL for job persistence (Tortoise ORM)
61
+ max_jobs: Maximum concurrent jobs per worker
62
+ job_timeout: Default job timeout in seconds
63
+ job_retry: Default number of retries for failed jobs
64
+ job_retry_after: Delay in seconds before retrying failed job
65
+ keep_job_days: Days to keep job history (None = forever)
66
+ """
67
+ self.redis_url = redis_url
68
+ self.db_url = db_url
69
+ self.max_jobs = max_jobs
70
+ self.job_timeout = job_timeout
71
+ self.job_retry = job_retry
72
+ self.job_retry_after = job_retry_after
73
+ self.keep_job_days = keep_job_days
74
+
75
+ # Lazy import ReArq to avoid distutils issues at startup
76
+ from rearq import ReArq
77
+
78
+ # Create ReArq instance
79
+ self.rearq = ReArq(
80
+ redis_url=redis_url,
81
+ job_retry=job_retry,
82
+ job_retry_after=job_retry_after,
83
+ max_jobs=max_jobs,
84
+ job_timeout=job_timeout,
85
+ keep_job_days=keep_job_days,
86
+ )
87
+
88
+ logger.info(f"ReArq client initialized: {redis_url}")
89
+
90
+ def task(self, queue: str = "default", **kwargs):
91
+ """
92
+ Task decorator for defining async tasks.
93
+
94
+ Args:
95
+ queue: Queue name for the task
96
+ **kwargs: Additional task options
97
+
98
+ Returns:
99
+ Task decorator
100
+
101
+ Example:
102
+ >>> @client.task(queue="default")
103
+ >>> async def my_task(arg1: str):
104
+ ... return f"Processed {arg1}"
105
+ """
106
+ return self.rearq.task(queue=queue, **kwargs)
107
+
108
+ def cron_task(self, cron: str, **kwargs):
109
+ """
110
+ Cron task decorator for scheduled tasks.
111
+
112
+ Args:
113
+ cron: Cron expression (e.g., "0 * * * *" for hourly)
114
+ **kwargs: Additional task options
115
+
116
+ Returns:
117
+ Task decorator
118
+
119
+ Example:
120
+ >>> @client.cron_task(cron="0 0 * * *") # Daily at midnight
121
+ >>> async def daily_cleanup():
122
+ ... return "Cleanup complete"
123
+ """
124
+ return self.rearq.task(cron=cron, **kwargs)
125
+
126
+ async def close(self):
127
+ """
128
+ Close client connections.
129
+
130
+ Call this when shutting down application to clean up resources.
131
+
132
+ Example:
133
+ >>> await client.close()
134
+ """
135
+ await self.rearq.close()
136
+ logger.info("ReArq client closed")
137
+
138
+ def get_connection_info(self) -> dict:
139
+ """
140
+ Get connection information.
141
+
142
+ Returns:
143
+ Dictionary with connection details
144
+
145
+ Example:
146
+ >>> info = client.get_connection_info()
147
+ >>> print(info["redis_url"])
148
+ """
149
+ return {
150
+ "redis_url": self.redis_url,
151
+ "db_url": self.db_url,
152
+ "max_jobs": self.max_jobs,
153
+ "job_timeout": self.job_timeout,
154
+ "job_retry": self.job_retry,
155
+ "job_retry_after": self.job_retry_after,
156
+ "keep_job_days": self.keep_job_days,
157
+ }
158
+
159
+
160
+ # ==================== Singleton Pattern ====================
161
+
162
+ _rearq_client: Optional[ReArqClient] = None
163
+ _rearq_client_lock = None
164
+
165
+
166
+ def get_rearq_client(force_new: bool = False) -> ReArqClient:
167
+ """
168
+ Get global ReArq client instance (singleton).
169
+
170
+ Creates client from Django settings on first call.
171
+ Subsequent calls return the same instance (thread-safe).
172
+
173
+ Args:
174
+ force_new: Force create new instance (for testing)
175
+
176
+ Returns:
177
+ ReArqClient instance
178
+
179
+ Example:
180
+ >>> from django_cfg.apps.tasks import get_rearq_client
181
+ >>> client = get_rearq_client()
182
+ >>> @client.task(queue="default")
183
+ >>> async def my_task():
184
+ ... pass
185
+ """
186
+ global _rearq_client, _rearq_client_lock
187
+
188
+ if force_new:
189
+ return _create_client_from_settings()
190
+
191
+ if _rearq_client is None:
192
+ # Thread-safe singleton creation
193
+ import threading
194
+
195
+ if _rearq_client_lock is None:
196
+ _rearq_client_lock = threading.Lock()
197
+
198
+ with _rearq_client_lock:
199
+ if _rearq_client is None:
200
+ _rearq_client = _create_client_from_settings()
201
+
202
+ return _rearq_client
203
+
204
+
205
+ def _create_client_from_settings() -> ReArqClient:
206
+ """
207
+ Create ReArq client from django-cfg config.
208
+
209
+ Returns:
210
+ ReArqClient instance
211
+
212
+ Raises:
213
+ ConfigurationError: If settings not configured
214
+ """
215
+ from ..config_helper import get_tasks_config_or_default
216
+
217
+ cfg = get_tasks_config_or_default()
218
+ logger.debug(f"Creating ReArq client from config: {cfg.rearq.redis_url}")
219
+
220
+ return ReArqClient(
221
+ redis_url=cfg.rearq.redis_url,
222
+ db_url=cfg.rearq.db_url,
223
+ max_jobs=cfg.rearq.max_jobs,
224
+ job_timeout=cfg.rearq.job_timeout,
225
+ job_retry=cfg.rearq.job_retry,
226
+ job_retry_after=cfg.rearq.job_retry_after,
227
+ keep_job_days=cfg.rearq.keep_job_days,
228
+ )
229
+
230
+
231
+ __all__ = [
232
+ "ReArqClient",
233
+ "get_rearq_client",
234
+ ]
@@ -0,0 +1,63 @@
1
+ """
2
+ Task configuration helper.
3
+
4
+ Provides access to task configuration from global state.
5
+ """
6
+ from typing import Optional
7
+
8
+ from django_cfg.modules.django_logging import get_logger
9
+
10
+ logger = get_logger("tasks.config")
11
+
12
+
13
+ def get_tasks_config():
14
+ """
15
+ Get tasks configuration from global state.
16
+
17
+ Returns:
18
+ TaskConfig instance if found, None otherwise
19
+
20
+ Example:
21
+ >>> from django_cfg.apps.tasks.services import get_tasks_config
22
+ >>> config = get_tasks_config()
23
+ >>> if config:
24
+ ... print(config.rearq.redis_url)
25
+ """
26
+ from django_cfg.core import get_current_config
27
+
28
+ config = get_current_config()
29
+
30
+ if config and hasattr(config, "tasks") and config.tasks:
31
+ return config.tasks
32
+
33
+ return None
34
+
35
+
36
+ def get_tasks_config_or_default():
37
+ """
38
+ Get tasks configuration or return default.
39
+
40
+ Returns:
41
+ TaskConfig instance (from global state or default)
42
+
43
+ Example:
44
+ >>> from django_cfg.apps.tasks.services import get_tasks_config_or_default
45
+ >>> config = get_tasks_config_or_default()
46
+ >>> print(config.rearq.redis_url)
47
+ """
48
+ config = get_tasks_config()
49
+
50
+ if config:
51
+ return config
52
+
53
+ # Fallback to default
54
+ from django_cfg.models.tasks import TaskConfig
55
+
56
+ logger.warning("Tasks config not found in global state, using default")
57
+ return TaskConfig()
58
+
59
+
60
+ __all__ = [
61
+ "get_tasks_config",
62
+ "get_tasks_config_or_default",
63
+ ]