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.
- django_database_task/__init__.py +27 -0
- django_database_task/admin.py +211 -0
- django_database_task/apps.py +7 -0
- django_database_task/backends.py +211 -0
- django_database_task/executor.py +181 -0
- django_database_task/locale/ja/LC_MESSAGES/django.mo +0 -0
- django_database_task/locale/ja/LC_MESSAGES/django.po +170 -0
- django_database_task/management/__init__.py +0 -0
- django_database_task/management/commands/__init__.py +0 -0
- django_database_task/management/commands/purge_completed_database_tasks.py +88 -0
- django_database_task/management/commands/run_database_tasks.py +98 -0
- django_database_task/migrations/0001_initial.py +190 -0
- django_database_task/migrations/__init__.py +0 -0
- django_database_task/models.py +120 -0
- django_database_task/urls.py +67 -0
- django_database_task/views.py +205 -0
- django_database_task-0.1.0.dist-info/METADATA +506 -0
- django_database_task-0.1.0.dist-info/RECORD +21 -0
- django_database_task-0.1.0.dist-info/WHEEL +5 -0
- django_database_task-0.1.0.dist-info/licenses/LICENSE +21 -0
- django_database_task-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -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})
|