django-qstash 0.0.10__tar.gz → 0.0.12__tar.gz
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-qstash might be problematic. Click here for more details.
- {django_qstash-0.0.10 → django_qstash-0.0.12}/PKG-INFO +129 -36
- {django_qstash-0.0.10 → django_qstash-0.0.12}/README.md +128 -35
- {django_qstash-0.0.10 → django_qstash-0.0.12}/pyproject.toml +1 -1
- django_qstash-0.0.12/src/django_qstash/__init__.py +8 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/app/__init__.py +2 -1
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/app/decorators.py +22 -3
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/callbacks.py +6 -0
- django_qstash-0.0.12/src/django_qstash/cron.py +21 -0
- django_qstash-0.0.12/src/django_qstash/db/models.py +12 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/handlers.py +31 -6
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/results/admin.py +4 -1
- django_qstash-0.0.12/src/django_qstash/results/migrations/0002_taskresult_function_path_alter_taskresult_status_and_more.py +46 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/results/models.py +7 -8
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/results/services.py +28 -2
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/results/tasks.py +21 -3
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/schedules/exceptions.py +6 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/schedules/forms.py +7 -0
- django_qstash-0.0.12/src/django_qstash/schedules/migrations/__init__.py +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/schedules/models.py +2 -0
- django_qstash-0.0.12/src/django_qstash/schedules/validators.py +69 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash.egg-info/PKG-INFO +129 -36
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash.egg-info/SOURCES.txt +4 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/tests/test_handlers.py +1 -1
- django_qstash-0.0.10/src/django_qstash/__init__.py +0 -7
- django_qstash-0.0.10/src/django_qstash/schedules/validators.py +0 -29
- {django_qstash-0.0.10 → django_qstash-0.0.12}/setup.cfg +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/app/base.py +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/client.py +0 -0
- {django_qstash-0.0.10/src/django_qstash/discovery → django_qstash-0.0.12/src/django_qstash/db}/__init__.py +0 -0
- {django_qstash-0.0.10/src/django_qstash/management → django_qstash-0.0.12/src/django_qstash/discovery}/__init__.py +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/discovery/fields.py +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/discovery/models.py +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/discovery/utils.py +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/discovery/validators.py +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/exceptions.py +0 -0
- {django_qstash-0.0.10/src/django_qstash/management/commands → django_qstash-0.0.12/src/django_qstash/management}/__init__.py +0 -0
- {django_qstash-0.0.10/src/django_qstash/results → django_qstash-0.0.12/src/django_qstash/management/commands}/__init__.py +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/management/commands/available_tasks.py +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/management/commands/clear_stale_results.py +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/management/commands/task_schedules.py +0 -0
- {django_qstash-0.0.10/src/django_qstash/results/migrations → django_qstash-0.0.12/src/django_qstash/results}/__init__.py +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/results/apps.py +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/results/migrations/0001_initial.py +0 -0
- {django_qstash-0.0.10/src/django_qstash/schedules → django_qstash-0.0.12/src/django_qstash/results/migrations}/__init__.py +0 -0
- {django_qstash-0.0.10/src/django_qstash/schedules/migrations → django_qstash-0.0.12/src/django_qstash/schedules}/__init__.py +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/schedules/admin.py +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/schedules/apps.py +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/schedules/formatters.py +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/schedules/migrations/0001_initial.py +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/schedules/migrations/0002_taskschedule_updated_at.py +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/schedules/services.py +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/schedules/signals.py +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/settings.py +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/utils.py +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash/views.py +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash.egg-info/dependency_links.txt +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash.egg-info/requires.txt +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/src/django_qstash.egg-info/top_level.txt +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/tests/test_callbacks.py +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/tests/test_exceptions.py +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/tests/test_results_models.py +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/tests/test_settings.py +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/tests/test_utils.py +0 -0
- {django_qstash-0.0.10 → django_qstash-0.0.12}/tests/test_views.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: django-qstash
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.12
|
|
4
4
|
Summary: A drop-in replacement for Celery's shared_task with Upstash QStash.
|
|
5
5
|
Author-email: Justin Mitchel <justin@codingforentrepreneurs.com>
|
|
6
6
|
Project-URL: Changelog, https://github.com/jmitchel3/django-qstash
|
|
@@ -33,16 +33,32 @@ Requires-Dist: requests>=2.30
|
|
|
33
33
|
|
|
34
34
|
_django-qstash_ is a drop-in replacement for Celery's `shared_task`.
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
|
|
37
|
+
## How it works
|
|
38
|
+
|
|
39
|
+
In `tasks.py` in your apps:
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from django_qstash import shared_task
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@shared_task
|
|
46
|
+
def my_task():
|
|
47
|
+
pass
|
|
48
|
+
```
|
|
49
|
+
> To use Celery too, you can use `@stashed_task` instead of `@shared_task` more below.
|
|
50
|
+
|
|
51
|
+
To do this we need:
|
|
37
52
|
|
|
38
53
|
- [Upstash QStash](https://upstash.com/docs/qstash/overall/getstarted)
|
|
39
|
-
- A single public _webhook_ to call `@
|
|
54
|
+
- A single public _webhook_ to call `@stashed_task` functions automatically
|
|
40
55
|
|
|
41
56
|
This allows us to:
|
|
42
57
|
|
|
58
|
+
- Nearly identical usage to Celery's `@shared_task` with far less configuration and overhead
|
|
43
59
|
- Focus just on Django
|
|
44
|
-
- Drop Celery
|
|
45
|
-
-
|
|
60
|
+
- Drop Celery completely, scale it down, or use it as normal. django-qstash can work hand-in-hand with Celery
|
|
61
|
+
- Unlock true serverless and scale-to-zero for Django
|
|
46
62
|
- Run background tasks through webhooks
|
|
47
63
|
- Cut costs
|
|
48
64
|
- Trigger GitHub Actions Workflows or GitLab CI/CD pipelines for handling other kinds of background tasks based on our project's code.
|
|
@@ -51,6 +67,7 @@ This allows us to:
|
|
|
51
67
|
## Table of Contents
|
|
52
68
|
|
|
53
69
|
- [django-qstash](#django-qstash)
|
|
70
|
+
- [How it works](#how-it-works)
|
|
54
71
|
- [Table of Contents](#table-of-contents)
|
|
55
72
|
- [Installation](#installation)
|
|
56
73
|
- [Using Pip](#using-pip)
|
|
@@ -62,18 +79,22 @@ This allows us to:
|
|
|
62
79
|
- [Usage](#usage)
|
|
63
80
|
- [Define a Task](#define-a-task)
|
|
64
81
|
- [Regular Task Call](#regular-task-call)
|
|
65
|
-
- [
|
|
82
|
+
- [Background Task](#background-task)
|
|
66
83
|
- [`.delay()`](#delay)
|
|
67
84
|
- [`.apply_async()`](#apply_async)
|
|
68
85
|
- [`.apply_async()` With Time Delay](#apply_async-with-time-delay)
|
|
69
|
-
- [JSON-ready
|
|
86
|
+
- [Arguments Must be JSON-ready](#arguments-must-be-json-ready)
|
|
70
87
|
- [Example Task](#example-task)
|
|
71
88
|
- [Management Commands](#management-commands)
|
|
72
|
-
- [Development
|
|
89
|
+
- [Public Domain In Development](#public-domain-in-development)
|
|
73
90
|
- [Django Settings Configuration](#django-settings-configuration)
|
|
91
|
+
- [`DJANGO_QSTASH_DOMAIN`](#django_qstash_domain)
|
|
92
|
+
- [`DJANGO_QSTASH_WEBHOOK_PATH`](#django_qstash_webhook_path)
|
|
93
|
+
- [`DJANGO_QSTASH_FORCE_HTTPS`](#django_qstash_force_https)
|
|
94
|
+
- [Example Django Settings](#example-django-settings)
|
|
74
95
|
- [Schedule Tasks (Optional)](#schedule-tasks-optional)
|
|
75
96
|
- [Installation](#installation-1)
|
|
76
|
-
|
|
97
|
+
- [Schedule a Task](#schedule-a-task)
|
|
77
98
|
- [Store Task Results (Optional)](#store-task-results-optional)
|
|
78
99
|
- [Clear Stale Results](#clear-stale-results)
|
|
79
100
|
- [Definitions](#definitions)
|
|
@@ -98,9 +119,9 @@ INSTALLED_APPS = [
|
|
|
98
119
|
##...
|
|
99
120
|
]
|
|
100
121
|
```
|
|
101
|
-
- `django_qstash` Includes the `@shared_task`
|
|
122
|
+
- `django_qstash` Includes the `@shared_task` and `@stashed_task` decorators and webhook view
|
|
102
123
|
- `django_qstash.results` (Optional): Store task results in Django DB
|
|
103
|
-
- `django_qstash.schedules` (Optional): Use QStash Schedules to run your `django_qstash` tasks. Out of the box support for _django_qstash_ `@
|
|
124
|
+
- `django_qstash.schedules` (Optional): Use QStash Schedules to run your `django_qstash` tasks. Out of the box support for _django_qstash_ `@stashed_task`. Schedule tasks using _cron_ (e.g. `0 0 * * *`) format which is required based on [QStash Schedules](https://upstash.com/docs/qstash/features/schedules). use [contrab.guru](https://crontab.guru/) for writing the cron format.
|
|
104
125
|
|
|
105
126
|
### Configure Webhook URL
|
|
106
127
|
|
|
@@ -144,7 +165,7 @@ There is a sample project in [sample_project/](sample_project/) that shows how a
|
|
|
144
165
|
|
|
145
166
|
## Usage
|
|
146
167
|
|
|
147
|
-
Django-QStash revolves around the `
|
|
168
|
+
Django-QStash revolves around the `stashed_task` decorator. The goal is to be a drop-in replacement for Celery's `shared_task` decorator.
|
|
148
169
|
|
|
149
170
|
Here's how it works:
|
|
150
171
|
- Define a Task
|
|
@@ -152,17 +173,30 @@ Here's how it works:
|
|
|
152
173
|
|
|
153
174
|
### Define a Task
|
|
154
175
|
```python
|
|
176
|
+
# from celery import shared_task
|
|
155
177
|
from django_qstash import shared_task
|
|
178
|
+
from django_qstash import stashed_task
|
|
156
179
|
|
|
157
180
|
|
|
158
|
-
@
|
|
181
|
+
@stashed_task
|
|
159
182
|
def hello_world(name: str, age: int = None, activity: str = None):
|
|
160
183
|
if age is None:
|
|
161
184
|
print(f"Hello {name}! I see you're {activity}.")
|
|
162
185
|
return
|
|
163
186
|
print(f"Hello {name}! I see you're {activity} at {age} years old.")
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@shared_task
|
|
190
|
+
def hello_world_redux(name: str, age: int = None, activity: str = None):
|
|
191
|
+
if age is None:
|
|
192
|
+
print(f"Hello {name}! I see you're {activity}.")
|
|
193
|
+
return
|
|
194
|
+
print(f"Hello {name}! I see you're {activity} at {age} years old.")
|
|
164
195
|
```
|
|
165
196
|
|
|
197
|
+
- `hello_world` and `hello_world_redux` work the same with django-qstash.
|
|
198
|
+
- If you use Celery's `@shared_task` instead, Celery would handle only `hello_world_redux` and django-qstash would handle only `hello_world`.
|
|
199
|
+
|
|
166
200
|
### Regular Task Call
|
|
167
201
|
Nothing special here. Just call the function like any other to verify it works.
|
|
168
202
|
|
|
@@ -171,9 +205,11 @@ Nothing special here. Just call the function like any other to verify it works.
|
|
|
171
205
|
hello_world("Tony Stark", age=40, activity="building in a cave with a box of scraps.")
|
|
172
206
|
```
|
|
173
207
|
|
|
174
|
-
###
|
|
208
|
+
### Background Task
|
|
175
209
|
|
|
176
|
-
Using `.delay()` or `.apply_async()` is how you
|
|
210
|
+
Using `.delay()` or `.apply_async()` is how you trigger a background task. These background tasks are actually setting up a QStash message that will be delivered via webhook to your Django application. django-qstash handles the webhook and the message delivery assuming installed correctly.
|
|
211
|
+
|
|
212
|
+
This functionality is modeled after Celery and it works as you'd expect.
|
|
177
213
|
|
|
178
214
|
|
|
179
215
|
#### `.delay()`
|
|
@@ -206,10 +242,11 @@ hello_world.apply_async(
|
|
|
206
242
|
)
|
|
207
243
|
```
|
|
208
244
|
|
|
209
|
-
### JSON-ready
|
|
245
|
+
### Arguments Must be JSON-ready
|
|
210
246
|
|
|
211
|
-
|
|
247
|
+
Arguments to django-qstash managed functions must be _JSON_ serializable.
|
|
212
248
|
|
|
249
|
+
The way you find out:
|
|
213
250
|
```python
|
|
214
251
|
import json
|
|
215
252
|
|
|
@@ -220,15 +257,23 @@ data = {
|
|
|
220
257
|
print(json.dumps(data))
|
|
221
258
|
# no errors, you're good to go.
|
|
222
259
|
```
|
|
260
|
+
If you have `errors` you'll need to fix them. Here's a few common errors you might see:
|
|
261
|
+
|
|
262
|
+
- Using a Django queryset directly as an argument
|
|
263
|
+
- Using a Django model instance directly as an argument
|
|
264
|
+
- Using a datetime object directly as an argument (e.g. `datetime.datetime` or `datetime.date`) instead of a timestamp or date string (e.g. `datetime.datetime.now().timestamp()` or `datetime.datetime.now.strftime("%Y-%m-%d")`)
|
|
223
265
|
|
|
224
266
|
### Example Task
|
|
225
267
|
|
|
226
268
|
```python
|
|
227
269
|
# from celery import shared_task
|
|
228
|
-
|
|
270
|
+
# becomes
|
|
271
|
+
# from django_qstash import shared_task
|
|
272
|
+
# or
|
|
273
|
+
from django_qstash import stashed_task
|
|
229
274
|
|
|
230
275
|
|
|
231
|
-
@
|
|
276
|
+
@stashed_task
|
|
232
277
|
def math_add_task(a, b, save_to_file=False, *args, **kwargs):
|
|
233
278
|
logger.info(f"Adding {a} and {b}")
|
|
234
279
|
if save_to_file:
|
|
@@ -261,38 +306,83 @@ The `.delay()` method does not support a countdown parameter because it simply p
|
|
|
261
306
|
|
|
262
307
|
## Management Commands
|
|
263
308
|
|
|
264
|
-
- `python manage.py available_tasks` to view all available tasks
|
|
309
|
+
- `python manage.py available_tasks` to view all available tasks found by django-qstash. Unlike Celery, django-qstash does not assign tasks to a specific Celery app (e.g. `app = Celery()`).
|
|
265
310
|
|
|
266
311
|
_Requires `django_qstash.schedules` installed._
|
|
267
312
|
- `python manage.py task_schedules --list` see all schedules relate to the `DJANGO_QSTASH_DOMAIN`
|
|
268
313
|
- `python manage.py task_schedules --sync` sync schedules based on the `DJANGO_QSTASH_DOMAIN` to store in the Django Admin.
|
|
269
314
|
|
|
270
|
-
## Development
|
|
315
|
+
## Public Domain In Development
|
|
271
316
|
|
|
272
|
-
django-qstash
|
|
317
|
+
django-qstash _requires_ a publicly accessible domain to work (e.g. `https://djangoqstash.com`). There are many ways to do this, we recommend:
|
|
273
318
|
|
|
274
319
|
- [Cloudflare Tunnels](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) with a domain name you control.
|
|
275
320
|
- [ngrok](https://ngrok.com/)
|
|
276
321
|
|
|
277
322
|
Once you have a domain name, you can configure the `DJANGO_QSTASH_DOMAIN` setting in your Django settings.
|
|
278
323
|
|
|
279
|
-
|
|
280
324
|
## Django Settings Configuration
|
|
281
325
|
|
|
282
|
-
|
|
326
|
+
Various options are available to configure django-qstash.
|
|
283
327
|
|
|
284
|
-
|
|
328
|
+
### `DJANGO_QSTASH_DOMAIN`
|
|
329
|
+
- Required: Yes
|
|
330
|
+
- Default:`None`
|
|
331
|
+
- Description: Must be a valid and publicly accessible domain. For example `https://djangoqstash.com`. Review [Development usage](#development-usage) for setting up a domain name during development.
|
|
285
332
|
|
|
286
|
-
|
|
333
|
+
### `DJANGO_QSTASH_WEBHOOK_PATH`
|
|
334
|
+
- Required: Yes
|
|
335
|
+
- Default:`/qstash/webhook/`
|
|
336
|
+
- Description: The path where QStash will send webhooks to your Django application.
|
|
287
337
|
|
|
288
|
-
|
|
338
|
+
### `DJANGO_QSTASH_FORCE_HTTPS`
|
|
339
|
+
- Required: No
|
|
340
|
+
- Default: `True`
|
|
341
|
+
- Description: Whether to force HTTPS for the webhook.
|
|
289
342
|
|
|
290
|
-
|
|
343
|
+
###`DJANGO_QSTASH_RESULT_TTL`
|
|
344
|
+
- Required: No
|
|
345
|
+
- Default:`604800`
|
|
346
|
+
- Description: A number of seconds after which task result data can be safely deleted. Defaults to 604800 seconds (7 days or 7 * 24 * 60 * 60).
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
### Example Django Settings
|
|
350
|
+
|
|
351
|
+
For a complete example, review [sample_project/settings.py](sample_project/settings.py) where [python-decouple](https://github.com/henriquebastos/python-decouple) is used to set the environment variables via the `.env` file or system environment variables (for production use).
|
|
352
|
+
|
|
353
|
+
Using `os.environ`:
|
|
354
|
+
```python
|
|
355
|
+
import os
|
|
356
|
+
|
|
357
|
+
###########################
|
|
358
|
+
# django settings
|
|
359
|
+
###########################
|
|
360
|
+
DJANGO_DEBUG = str(os.environ.get("DJANGO_DEBUG")) == "1"
|
|
361
|
+
DJANGO_SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY")
|
|
362
|
+
ALLOWED_HOSTS = [os.environ.get("ALLOWED_HOST")]
|
|
363
|
+
CSRF_TRUSTED_ORIGINS = [os.environ.get("CSRF_TRUSTED_ORIGIN")]
|
|
364
|
+
###########################
|
|
365
|
+
# qstash-py settings
|
|
366
|
+
###########################
|
|
367
|
+
QSTASH_TOKEN = os.environ.get("QSTASH_TOKEN")
|
|
368
|
+
QSTASH_CURRENT_SIGNING_KEY = os.environ.get("QSTASH_CURRENT_SIGNING_KEY")
|
|
369
|
+
QSTASH_NEXT_SIGNING_KEY = os.environ.get("QSTASH_NEXT_SIGNING_KEY")
|
|
370
|
+
|
|
371
|
+
###########################
|
|
372
|
+
# django_qstash settings
|
|
373
|
+
###########################
|
|
374
|
+
DJANGO_QSTASH_DOMAIN = os.environ.get("DJANGO_QSTASH_DOMAIN")
|
|
375
|
+
DJANGO_QSTASH_WEBHOOK_PATH = os.environ.get("DJANGO_QSTASH_WEBHOOK_PATH")
|
|
376
|
+
DJANGO_QSTASH_FORCE_HTTPS = True
|
|
377
|
+
DJANGO_QSTASH_RESULT_TTL = 604800
|
|
378
|
+
```
|
|
291
379
|
|
|
292
380
|
|
|
293
381
|
## Schedule Tasks (Optional)
|
|
294
382
|
|
|
295
|
-
|
|
383
|
+
Run background tasks on a CRON schedule.
|
|
384
|
+
|
|
385
|
+
The `django_qstash.schedules` app schedules tasks using Upstash [QStash Schedules](https://upstash.com/docs/qstash/features/schedules) via `@shared_task` or `@stashed_task` decorators along with the `TaskSchedule` model.
|
|
296
386
|
|
|
297
387
|
### Installation
|
|
298
388
|
|
|
@@ -312,8 +402,7 @@ Run migrations:
|
|
|
312
402
|
python manage.py migrate django_qstash_schedules
|
|
313
403
|
```
|
|
314
404
|
|
|
315
|
-
|
|
316
|
-
## Schedule a Task
|
|
405
|
+
### Schedule a Task
|
|
317
406
|
|
|
318
407
|
Tasks must exist before you can schedule them. Review [Define a Task](#define-a-task) for more information.
|
|
319
408
|
|
|
@@ -321,9 +410,6 @@ Here's how you can schedule a task:
|
|
|
321
410
|
- Django Admin (`/admin/django_qstash_schedules/taskschedule/add/`)
|
|
322
411
|
- Django shell (`python manage.py shell`)
|
|
323
412
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
413
|
```python
|
|
328
414
|
from django_qstash.schedules.models import TaskSchedule
|
|
329
415
|
from django_qstash.discovery.utils import discover_tasks
|
|
@@ -352,10 +438,11 @@ TaskSchedule.objects.create(
|
|
|
352
438
|
- `cron` is the cron schedule to run the task. Use [contrab.guru](https://crontab.guru/) for writing the cron format.
|
|
353
439
|
|
|
354
440
|
|
|
355
|
-
|
|
356
441
|
## Store Task Results (Optional)
|
|
357
442
|
|
|
358
|
-
|
|
443
|
+
Retain the results of background tasks in the database with clear-out functionality.
|
|
444
|
+
|
|
445
|
+
In `django_qstash.results.models` we have the `TaskResult` model class that can be used to track async task results. These entries are created via the django-qstash webhook view handler (`qstash_webhook_view`).
|
|
359
446
|
|
|
360
447
|
To install it, just add `django_qstash.results` to your `INSTALLED_APPS` setting.
|
|
361
448
|
|
|
@@ -373,6 +460,12 @@ Run migrations:
|
|
|
373
460
|
python manage.py migrate django_qstash_results
|
|
374
461
|
```
|
|
375
462
|
|
|
463
|
+
Key configuration:
|
|
464
|
+
|
|
465
|
+
- [DJANGO_QSTASH_WEBHOOK_PATH](#django-settings-configuration)
|
|
466
|
+
- [DJANGO_QSTASH_DOMAIN](#django-settings-configuration)
|
|
467
|
+
- [DJANGO_QSTASH_RESULT_TTL](#django-settings-configuration)
|
|
468
|
+
|
|
376
469
|
### Clear Stale Results
|
|
377
470
|
|
|
378
471
|
We recommend purging the `TaskResult` model after a certain amount of time.
|
|
@@ -4,16 +4,32 @@
|
|
|
4
4
|
|
|
5
5
|
_django-qstash_ is a drop-in replacement for Celery's `shared_task`.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
## How it works
|
|
9
|
+
|
|
10
|
+
In `tasks.py` in your apps:
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
from django_qstash import shared_task
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@shared_task
|
|
17
|
+
def my_task():
|
|
18
|
+
pass
|
|
19
|
+
```
|
|
20
|
+
> To use Celery too, you can use `@stashed_task` instead of `@shared_task` more below.
|
|
21
|
+
|
|
22
|
+
To do this we need:
|
|
8
23
|
|
|
9
24
|
- [Upstash QStash](https://upstash.com/docs/qstash/overall/getstarted)
|
|
10
|
-
- A single public _webhook_ to call `@
|
|
25
|
+
- A single public _webhook_ to call `@stashed_task` functions automatically
|
|
11
26
|
|
|
12
27
|
This allows us to:
|
|
13
28
|
|
|
29
|
+
- Nearly identical usage to Celery's `@shared_task` with far less configuration and overhead
|
|
14
30
|
- Focus just on Django
|
|
15
|
-
- Drop Celery
|
|
16
|
-
-
|
|
31
|
+
- Drop Celery completely, scale it down, or use it as normal. django-qstash can work hand-in-hand with Celery
|
|
32
|
+
- Unlock true serverless and scale-to-zero for Django
|
|
17
33
|
- Run background tasks through webhooks
|
|
18
34
|
- Cut costs
|
|
19
35
|
- Trigger GitHub Actions Workflows or GitLab CI/CD pipelines for handling other kinds of background tasks based on our project's code.
|
|
@@ -22,6 +38,7 @@ This allows us to:
|
|
|
22
38
|
## Table of Contents
|
|
23
39
|
|
|
24
40
|
- [django-qstash](#django-qstash)
|
|
41
|
+
- [How it works](#how-it-works)
|
|
25
42
|
- [Table of Contents](#table-of-contents)
|
|
26
43
|
- [Installation](#installation)
|
|
27
44
|
- [Using Pip](#using-pip)
|
|
@@ -33,18 +50,22 @@ This allows us to:
|
|
|
33
50
|
- [Usage](#usage)
|
|
34
51
|
- [Define a Task](#define-a-task)
|
|
35
52
|
- [Regular Task Call](#regular-task-call)
|
|
36
|
-
- [
|
|
53
|
+
- [Background Task](#background-task)
|
|
37
54
|
- [`.delay()`](#delay)
|
|
38
55
|
- [`.apply_async()`](#apply_async)
|
|
39
56
|
- [`.apply_async()` With Time Delay](#apply_async-with-time-delay)
|
|
40
|
-
- [JSON-ready
|
|
57
|
+
- [Arguments Must be JSON-ready](#arguments-must-be-json-ready)
|
|
41
58
|
- [Example Task](#example-task)
|
|
42
59
|
- [Management Commands](#management-commands)
|
|
43
|
-
- [Development
|
|
60
|
+
- [Public Domain In Development](#public-domain-in-development)
|
|
44
61
|
- [Django Settings Configuration](#django-settings-configuration)
|
|
62
|
+
- [`DJANGO_QSTASH_DOMAIN`](#django_qstash_domain)
|
|
63
|
+
- [`DJANGO_QSTASH_WEBHOOK_PATH`](#django_qstash_webhook_path)
|
|
64
|
+
- [`DJANGO_QSTASH_FORCE_HTTPS`](#django_qstash_force_https)
|
|
65
|
+
- [Example Django Settings](#example-django-settings)
|
|
45
66
|
- [Schedule Tasks (Optional)](#schedule-tasks-optional)
|
|
46
67
|
- [Installation](#installation-1)
|
|
47
|
-
|
|
68
|
+
- [Schedule a Task](#schedule-a-task)
|
|
48
69
|
- [Store Task Results (Optional)](#store-task-results-optional)
|
|
49
70
|
- [Clear Stale Results](#clear-stale-results)
|
|
50
71
|
- [Definitions](#definitions)
|
|
@@ -69,9 +90,9 @@ INSTALLED_APPS = [
|
|
|
69
90
|
##...
|
|
70
91
|
]
|
|
71
92
|
```
|
|
72
|
-
- `django_qstash` Includes the `@shared_task`
|
|
93
|
+
- `django_qstash` Includes the `@shared_task` and `@stashed_task` decorators and webhook view
|
|
73
94
|
- `django_qstash.results` (Optional): Store task results in Django DB
|
|
74
|
-
- `django_qstash.schedules` (Optional): Use QStash Schedules to run your `django_qstash` tasks. Out of the box support for _django_qstash_ `@
|
|
95
|
+
- `django_qstash.schedules` (Optional): Use QStash Schedules to run your `django_qstash` tasks. Out of the box support for _django_qstash_ `@stashed_task`. Schedule tasks using _cron_ (e.g. `0 0 * * *`) format which is required based on [QStash Schedules](https://upstash.com/docs/qstash/features/schedules). use [contrab.guru](https://crontab.guru/) for writing the cron format.
|
|
75
96
|
|
|
76
97
|
### Configure Webhook URL
|
|
77
98
|
|
|
@@ -115,7 +136,7 @@ There is a sample project in [sample_project/](sample_project/) that shows how a
|
|
|
115
136
|
|
|
116
137
|
## Usage
|
|
117
138
|
|
|
118
|
-
Django-QStash revolves around the `
|
|
139
|
+
Django-QStash revolves around the `stashed_task` decorator. The goal is to be a drop-in replacement for Celery's `shared_task` decorator.
|
|
119
140
|
|
|
120
141
|
Here's how it works:
|
|
121
142
|
- Define a Task
|
|
@@ -123,17 +144,30 @@ Here's how it works:
|
|
|
123
144
|
|
|
124
145
|
### Define a Task
|
|
125
146
|
```python
|
|
147
|
+
# from celery import shared_task
|
|
126
148
|
from django_qstash import shared_task
|
|
149
|
+
from django_qstash import stashed_task
|
|
127
150
|
|
|
128
151
|
|
|
129
|
-
@
|
|
152
|
+
@stashed_task
|
|
130
153
|
def hello_world(name: str, age: int = None, activity: str = None):
|
|
131
154
|
if age is None:
|
|
132
155
|
print(f"Hello {name}! I see you're {activity}.")
|
|
133
156
|
return
|
|
134
157
|
print(f"Hello {name}! I see you're {activity} at {age} years old.")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@shared_task
|
|
161
|
+
def hello_world_redux(name: str, age: int = None, activity: str = None):
|
|
162
|
+
if age is None:
|
|
163
|
+
print(f"Hello {name}! I see you're {activity}.")
|
|
164
|
+
return
|
|
165
|
+
print(f"Hello {name}! I see you're {activity} at {age} years old.")
|
|
135
166
|
```
|
|
136
167
|
|
|
168
|
+
- `hello_world` and `hello_world_redux` work the same with django-qstash.
|
|
169
|
+
- If you use Celery's `@shared_task` instead, Celery would handle only `hello_world_redux` and django-qstash would handle only `hello_world`.
|
|
170
|
+
|
|
137
171
|
### Regular Task Call
|
|
138
172
|
Nothing special here. Just call the function like any other to verify it works.
|
|
139
173
|
|
|
@@ -142,9 +176,11 @@ Nothing special here. Just call the function like any other to verify it works.
|
|
|
142
176
|
hello_world("Tony Stark", age=40, activity="building in a cave with a box of scraps.")
|
|
143
177
|
```
|
|
144
178
|
|
|
145
|
-
###
|
|
179
|
+
### Background Task
|
|
146
180
|
|
|
147
|
-
Using `.delay()` or `.apply_async()` is how you
|
|
181
|
+
Using `.delay()` or `.apply_async()` is how you trigger a background task. These background tasks are actually setting up a QStash message that will be delivered via webhook to your Django application. django-qstash handles the webhook and the message delivery assuming installed correctly.
|
|
182
|
+
|
|
183
|
+
This functionality is modeled after Celery and it works as you'd expect.
|
|
148
184
|
|
|
149
185
|
|
|
150
186
|
#### `.delay()`
|
|
@@ -177,10 +213,11 @@ hello_world.apply_async(
|
|
|
177
213
|
)
|
|
178
214
|
```
|
|
179
215
|
|
|
180
|
-
### JSON-ready
|
|
216
|
+
### Arguments Must be JSON-ready
|
|
181
217
|
|
|
182
|
-
|
|
218
|
+
Arguments to django-qstash managed functions must be _JSON_ serializable.
|
|
183
219
|
|
|
220
|
+
The way you find out:
|
|
184
221
|
```python
|
|
185
222
|
import json
|
|
186
223
|
|
|
@@ -191,15 +228,23 @@ data = {
|
|
|
191
228
|
print(json.dumps(data))
|
|
192
229
|
# no errors, you're good to go.
|
|
193
230
|
```
|
|
231
|
+
If you have `errors` you'll need to fix them. Here's a few common errors you might see:
|
|
232
|
+
|
|
233
|
+
- Using a Django queryset directly as an argument
|
|
234
|
+
- Using a Django model instance directly as an argument
|
|
235
|
+
- Using a datetime object directly as an argument (e.g. `datetime.datetime` or `datetime.date`) instead of a timestamp or date string (e.g. `datetime.datetime.now().timestamp()` or `datetime.datetime.now.strftime("%Y-%m-%d")`)
|
|
194
236
|
|
|
195
237
|
### Example Task
|
|
196
238
|
|
|
197
239
|
```python
|
|
198
240
|
# from celery import shared_task
|
|
199
|
-
|
|
241
|
+
# becomes
|
|
242
|
+
# from django_qstash import shared_task
|
|
243
|
+
# or
|
|
244
|
+
from django_qstash import stashed_task
|
|
200
245
|
|
|
201
246
|
|
|
202
|
-
@
|
|
247
|
+
@stashed_task
|
|
203
248
|
def math_add_task(a, b, save_to_file=False, *args, **kwargs):
|
|
204
249
|
logger.info(f"Adding {a} and {b}")
|
|
205
250
|
if save_to_file:
|
|
@@ -232,38 +277,83 @@ The `.delay()` method does not support a countdown parameter because it simply p
|
|
|
232
277
|
|
|
233
278
|
## Management Commands
|
|
234
279
|
|
|
235
|
-
- `python manage.py available_tasks` to view all available tasks
|
|
280
|
+
- `python manage.py available_tasks` to view all available tasks found by django-qstash. Unlike Celery, django-qstash does not assign tasks to a specific Celery app (e.g. `app = Celery()`).
|
|
236
281
|
|
|
237
282
|
_Requires `django_qstash.schedules` installed._
|
|
238
283
|
- `python manage.py task_schedules --list` see all schedules relate to the `DJANGO_QSTASH_DOMAIN`
|
|
239
284
|
- `python manage.py task_schedules --sync` sync schedules based on the `DJANGO_QSTASH_DOMAIN` to store in the Django Admin.
|
|
240
285
|
|
|
241
|
-
## Development
|
|
286
|
+
## Public Domain In Development
|
|
242
287
|
|
|
243
|
-
django-qstash
|
|
288
|
+
django-qstash _requires_ a publicly accessible domain to work (e.g. `https://djangoqstash.com`). There are many ways to do this, we recommend:
|
|
244
289
|
|
|
245
290
|
- [Cloudflare Tunnels](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) with a domain name you control.
|
|
246
291
|
- [ngrok](https://ngrok.com/)
|
|
247
292
|
|
|
248
293
|
Once you have a domain name, you can configure the `DJANGO_QSTASH_DOMAIN` setting in your Django settings.
|
|
249
294
|
|
|
250
|
-
|
|
251
295
|
## Django Settings Configuration
|
|
252
296
|
|
|
253
|
-
|
|
297
|
+
Various options are available to configure django-qstash.
|
|
254
298
|
|
|
255
|
-
|
|
299
|
+
### `DJANGO_QSTASH_DOMAIN`
|
|
300
|
+
- Required: Yes
|
|
301
|
+
- Default:`None`
|
|
302
|
+
- Description: Must be a valid and publicly accessible domain. For example `https://djangoqstash.com`. Review [Development usage](#development-usage) for setting up a domain name during development.
|
|
256
303
|
|
|
257
|
-
|
|
304
|
+
### `DJANGO_QSTASH_WEBHOOK_PATH`
|
|
305
|
+
- Required: Yes
|
|
306
|
+
- Default:`/qstash/webhook/`
|
|
307
|
+
- Description: The path where QStash will send webhooks to your Django application.
|
|
258
308
|
|
|
259
|
-
|
|
309
|
+
### `DJANGO_QSTASH_FORCE_HTTPS`
|
|
310
|
+
- Required: No
|
|
311
|
+
- Default: `True`
|
|
312
|
+
- Description: Whether to force HTTPS for the webhook.
|
|
260
313
|
|
|
261
|
-
|
|
314
|
+
###`DJANGO_QSTASH_RESULT_TTL`
|
|
315
|
+
- Required: No
|
|
316
|
+
- Default:`604800`
|
|
317
|
+
- Description: A number of seconds after which task result data can be safely deleted. Defaults to 604800 seconds (7 days or 7 * 24 * 60 * 60).
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
### Example Django Settings
|
|
321
|
+
|
|
322
|
+
For a complete example, review [sample_project/settings.py](sample_project/settings.py) where [python-decouple](https://github.com/henriquebastos/python-decouple) is used to set the environment variables via the `.env` file or system environment variables (for production use).
|
|
323
|
+
|
|
324
|
+
Using `os.environ`:
|
|
325
|
+
```python
|
|
326
|
+
import os
|
|
327
|
+
|
|
328
|
+
###########################
|
|
329
|
+
# django settings
|
|
330
|
+
###########################
|
|
331
|
+
DJANGO_DEBUG = str(os.environ.get("DJANGO_DEBUG")) == "1"
|
|
332
|
+
DJANGO_SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY")
|
|
333
|
+
ALLOWED_HOSTS = [os.environ.get("ALLOWED_HOST")]
|
|
334
|
+
CSRF_TRUSTED_ORIGINS = [os.environ.get("CSRF_TRUSTED_ORIGIN")]
|
|
335
|
+
###########################
|
|
336
|
+
# qstash-py settings
|
|
337
|
+
###########################
|
|
338
|
+
QSTASH_TOKEN = os.environ.get("QSTASH_TOKEN")
|
|
339
|
+
QSTASH_CURRENT_SIGNING_KEY = os.environ.get("QSTASH_CURRENT_SIGNING_KEY")
|
|
340
|
+
QSTASH_NEXT_SIGNING_KEY = os.environ.get("QSTASH_NEXT_SIGNING_KEY")
|
|
341
|
+
|
|
342
|
+
###########################
|
|
343
|
+
# django_qstash settings
|
|
344
|
+
###########################
|
|
345
|
+
DJANGO_QSTASH_DOMAIN = os.environ.get("DJANGO_QSTASH_DOMAIN")
|
|
346
|
+
DJANGO_QSTASH_WEBHOOK_PATH = os.environ.get("DJANGO_QSTASH_WEBHOOK_PATH")
|
|
347
|
+
DJANGO_QSTASH_FORCE_HTTPS = True
|
|
348
|
+
DJANGO_QSTASH_RESULT_TTL = 604800
|
|
349
|
+
```
|
|
262
350
|
|
|
263
351
|
|
|
264
352
|
## Schedule Tasks (Optional)
|
|
265
353
|
|
|
266
|
-
|
|
354
|
+
Run background tasks on a CRON schedule.
|
|
355
|
+
|
|
356
|
+
The `django_qstash.schedules` app schedules tasks using Upstash [QStash Schedules](https://upstash.com/docs/qstash/features/schedules) via `@shared_task` or `@stashed_task` decorators along with the `TaskSchedule` model.
|
|
267
357
|
|
|
268
358
|
### Installation
|
|
269
359
|
|
|
@@ -283,8 +373,7 @@ Run migrations:
|
|
|
283
373
|
python manage.py migrate django_qstash_schedules
|
|
284
374
|
```
|
|
285
375
|
|
|
286
|
-
|
|
287
|
-
## Schedule a Task
|
|
376
|
+
### Schedule a Task
|
|
288
377
|
|
|
289
378
|
Tasks must exist before you can schedule them. Review [Define a Task](#define-a-task) for more information.
|
|
290
379
|
|
|
@@ -292,9 +381,6 @@ Here's how you can schedule a task:
|
|
|
292
381
|
- Django Admin (`/admin/django_qstash_schedules/taskschedule/add/`)
|
|
293
382
|
- Django shell (`python manage.py shell`)
|
|
294
383
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
384
|
```python
|
|
299
385
|
from django_qstash.schedules.models import TaskSchedule
|
|
300
386
|
from django_qstash.discovery.utils import discover_tasks
|
|
@@ -323,10 +409,11 @@ TaskSchedule.objects.create(
|
|
|
323
409
|
- `cron` is the cron schedule to run the task. Use [contrab.guru](https://crontab.guru/) for writing the cron format.
|
|
324
410
|
|
|
325
411
|
|
|
326
|
-
|
|
327
412
|
## Store Task Results (Optional)
|
|
328
413
|
|
|
329
|
-
|
|
414
|
+
Retain the results of background tasks in the database with clear-out functionality.
|
|
415
|
+
|
|
416
|
+
In `django_qstash.results.models` we have the `TaskResult` model class that can be used to track async task results. These entries are created via the django-qstash webhook view handler (`qstash_webhook_view`).
|
|
330
417
|
|
|
331
418
|
To install it, just add `django_qstash.results` to your `INSTALLED_APPS` setting.
|
|
332
419
|
|
|
@@ -344,6 +431,12 @@ Run migrations:
|
|
|
344
431
|
python manage.py migrate django_qstash_results
|
|
345
432
|
```
|
|
346
433
|
|
|
434
|
+
Key configuration:
|
|
435
|
+
|
|
436
|
+
- [DJANGO_QSTASH_WEBHOOK_PATH](#django-settings-configuration)
|
|
437
|
+
- [DJANGO_QSTASH_DOMAIN](#django-settings-configuration)
|
|
438
|
+
- [DJANGO_QSTASH_RESULT_TTL](#django-settings-configuration)
|
|
439
|
+
|
|
347
440
|
### Clear Stale Results
|
|
348
441
|
|
|
349
442
|
We recommend purging the `TaskResult` model after a certain amount of time.
|
|
@@ -3,5 +3,6 @@ from __future__ import annotations
|
|
|
3
3
|
from .base import AsyncResult
|
|
4
4
|
from .base import QStashTask
|
|
5
5
|
from .decorators import shared_task
|
|
6
|
+
from .decorators import stashed_task
|
|
6
7
|
|
|
7
|
-
__all__ = ["AsyncResult", "QStashTask", "shared_task"]
|
|
8
|
+
__all__ = ["AsyncResult", "QStashTask", "stashed_task", "shared_task"]
|