django-database-task 0.1.0__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.
@@ -0,0 +1,67 @@
1
+ """
2
+ URL configuration for django_database_task.
3
+
4
+ Optional URL patterns for HTTP-based task execution.
5
+ Include these in your project's urls.py if you need HTTP endpoints.
6
+
7
+ Example:
8
+ # Basic usage (no authentication)
9
+ from django.urls import path, include
10
+
11
+ urlpatterns = [
12
+ path("tasks/", include("django_database_task.urls")),
13
+ ]
14
+
15
+ # With authentication (recommended for production)
16
+ from django.contrib.admin.views.decorators import staff_member_required
17
+ from django_database_task.views import RunTasksView, RunOneTaskView, TaskStatusView
18
+
19
+ urlpatterns = [
20
+ path(
21
+ "tasks/run/",
22
+ staff_member_required(RunTasksView.as_view()),
23
+ name="run_tasks",
24
+ ),
25
+ path(
26
+ "tasks/run-one/",
27
+ staff_member_required(RunOneTaskView.as_view()),
28
+ name="run_one_task",
29
+ ),
30
+ path(
31
+ "tasks/status/",
32
+ staff_member_required(TaskStatusView.as_view()),
33
+ name="task_status",
34
+ ),
35
+ ]
36
+
37
+ # With token-based authentication
38
+ from django.http import HttpResponseForbidden
39
+
40
+ def require_token(view_func):
41
+ def wrapper(request, *args, **kwargs):
42
+ token = request.headers.get("Authorization", "").replace("Bearer ", "")
43
+ if token != settings.TASK_API_TOKEN:
44
+ return HttpResponseForbidden("Invalid token")
45
+ return view_func(request, *args, **kwargs)
46
+ return wrapper
47
+
48
+ urlpatterns = [
49
+ path(
50
+ "tasks/run/",
51
+ require_token(RunTasksView.as_view()),
52
+ name="run_tasks",
53
+ ),
54
+ ]
55
+ """
56
+
57
+ from django.urls import path
58
+
59
+ from .views import RunOneTaskView, RunTasksView, TaskStatusView
60
+
61
+ app_name = "django_database_task"
62
+
63
+ urlpatterns = [
64
+ path("run/", RunTasksView.as_view(), name="run_tasks"),
65
+ path("run-one/", RunOneTaskView.as_view(), name="run_one_task"),
66
+ path("status/", TaskStatusView.as_view(), name="task_status"),
67
+ ]
@@ -0,0 +1,205 @@
1
+ """
2
+ HTTP endpoints for task execution.
3
+
4
+ These views provide an alternative way to trigger task processing
5
+ when cron or direct command execution is not available.
6
+
7
+ Usage:
8
+ # In your project's urls.py
9
+ from django.urls import path, include
10
+
11
+ urlpatterns = [
12
+ path("tasks/", include("django_database_task.urls")),
13
+ ]
14
+
15
+ # Then POST to /tasks/run/ to process tasks
16
+ """
17
+
18
+ import json
19
+
20
+ from django.http import JsonResponse
21
+ from django.utils.decorators import method_decorator
22
+ from django.views import View
23
+ from django.views.decorators.csrf import csrf_exempt
24
+
25
+ from .executor import get_pending_task_count, process_one_task, process_tasks
26
+
27
+
28
+ @method_decorator(csrf_exempt, name="dispatch")
29
+ class RunTasksView(View):
30
+ """
31
+ Process pending tasks via HTTP POST.
32
+
33
+ This view is useful when you need to trigger task processing
34
+ from external systems (e.g., cloud schedulers, webhooks) that
35
+ cannot execute management commands directly.
36
+
37
+ POST parameters (JSON body):
38
+ max_tasks: Maximum number of tasks to process (default: 10)
39
+ queue_name: Optional queue name to filter tasks
40
+ backend_name: Backend name (default: "default")
41
+
42
+ Response:
43
+ {
44
+ "processed": 3,
45
+ "results": [
46
+ {"id": "...", "status": "SUCCESSFUL", "task_path": "..."},
47
+ ...
48
+ ]
49
+ }
50
+
51
+ Security:
52
+ - Only accepts POST requests
53
+ - CSRF exempt (intended for API/webhook use)
54
+ - Consider adding authentication in your URL configuration:
55
+
56
+ from django.contrib.admin.views.decorators import staff_member_required
57
+
58
+ urlpatterns = [
59
+ path(
60
+ "tasks/run/",
61
+ staff_member_required(RunTasksView.as_view()),
62
+ ),
63
+ ]
64
+ """
65
+
66
+ http_method_names = ["post"]
67
+
68
+ def post(self, request):
69
+ # Parse JSON body if present
70
+ try:
71
+ if request.body:
72
+ data = json.loads(request.body)
73
+ else:
74
+ data = {}
75
+ except json.JSONDecodeError:
76
+ return JsonResponse({"error": "Invalid JSON"}, status=400)
77
+
78
+ max_tasks = data.get("max_tasks", 10)
79
+ queue_name = data.get("queue_name")
80
+ backend_name = data.get("backend_name", "default")
81
+
82
+ # Validate max_tasks
83
+ if not isinstance(max_tasks, int) or max_tasks < 1:
84
+ return JsonResponse(
85
+ {"error": "max_tasks must be a positive integer"}, status=400
86
+ )
87
+ if max_tasks > 100:
88
+ return JsonResponse({"error": "max_tasks cannot exceed 100"}, status=400)
89
+
90
+ results = process_tasks(
91
+ queue_name=queue_name,
92
+ backend_name=backend_name,
93
+ max_tasks=max_tasks,
94
+ )
95
+
96
+ return JsonResponse(
97
+ {
98
+ "processed": len(results),
99
+ "results": [
100
+ {
101
+ "id": str(r.id),
102
+ "status": r.status.value,
103
+ "task_path": r.task.func.__module__
104
+ + "."
105
+ + r.task.func.__qualname__
106
+ if hasattr(r.task, "func")
107
+ else str(r.task),
108
+ }
109
+ for r in results
110
+ ],
111
+ }
112
+ )
113
+
114
+
115
+ @method_decorator(csrf_exempt, name="dispatch")
116
+ class RunOneTaskView(View):
117
+ """
118
+ Process a single pending task via HTTP POST.
119
+
120
+ POST parameters (JSON body):
121
+ queue_name: Optional queue name to filter tasks
122
+ backend_name: Backend name (default: "default")
123
+
124
+ Response (task processed):
125
+ {
126
+ "processed": true,
127
+ "result": {
128
+ "id": "...",
129
+ "status": "SUCCESSFUL",
130
+ "task_path": "..."
131
+ }
132
+ }
133
+
134
+ Response (no task available):
135
+ {
136
+ "processed": false,
137
+ "result": null
138
+ }
139
+ """
140
+
141
+ http_method_names = ["post"]
142
+
143
+ def post(self, request):
144
+ # Parse JSON body if present
145
+ try:
146
+ if request.body:
147
+ data = json.loads(request.body)
148
+ else:
149
+ data = {}
150
+ except json.JSONDecodeError:
151
+ return JsonResponse({"error": "Invalid JSON"}, status=400)
152
+
153
+ queue_name = data.get("queue_name")
154
+ backend_name = data.get("backend_name", "default")
155
+
156
+ result = process_one_task(
157
+ queue_name=queue_name,
158
+ backend_name=backend_name,
159
+ )
160
+
161
+ if result is None:
162
+ return JsonResponse({"processed": False, "result": None})
163
+
164
+ return JsonResponse(
165
+ {
166
+ "processed": True,
167
+ "result": {
168
+ "id": str(result.id),
169
+ "status": result.status.value,
170
+ "task_path": result.task.func.__module__
171
+ + "."
172
+ + result.task.func.__qualname__
173
+ if hasattr(result.task, "func")
174
+ else str(result.task),
175
+ },
176
+ }
177
+ )
178
+
179
+
180
+ class TaskStatusView(View):
181
+ """
182
+ Get pending task count via HTTP GET.
183
+
184
+ Query parameters:
185
+ queue_name: Optional queue name to filter tasks
186
+ backend_name: Backend name (default: "default")
187
+
188
+ Response:
189
+ {
190
+ "pending_count": 5
191
+ }
192
+ """
193
+
194
+ http_method_names = ["get"]
195
+
196
+ def get(self, request):
197
+ queue_name = request.GET.get("queue_name")
198
+ backend_name = request.GET.get("backend_name", "default")
199
+
200
+ count = get_pending_task_count(
201
+ queue_name=queue_name,
202
+ backend_name=backend_name,
203
+ )
204
+
205
+ return JsonResponse({"pending_count": count})