django-qstash 0.2.1__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.
Files changed (64) hide show
  1. django_qstash-0.2.1/PKG-INFO +569 -0
  2. django_qstash-0.2.1/README.md +539 -0
  3. django_qstash-0.2.1/pyproject.toml +123 -0
  4. django_qstash-0.2.1/setup.cfg +4 -0
  5. django_qstash-0.2.1/src/django_qstash/__init__.py +8 -0
  6. django_qstash-0.2.1/src/django_qstash/app/__init__.py +8 -0
  7. django_qstash-0.2.1/src/django_qstash/app/base.py +120 -0
  8. django_qstash-0.2.1/src/django_qstash/app/decorators.py +48 -0
  9. django_qstash-0.2.1/src/django_qstash/callbacks.py +21 -0
  10. django_qstash-0.2.1/src/django_qstash/client.py +29 -0
  11. django_qstash-0.2.1/src/django_qstash/cron.py +21 -0
  12. django_qstash-0.2.1/src/django_qstash/db/__init__.py +0 -0
  13. django_qstash-0.2.1/src/django_qstash/db/models.py +12 -0
  14. django_qstash-0.2.1/src/django_qstash/discovery/__init__.py +0 -0
  15. django_qstash-0.2.1/src/django_qstash/discovery/fields.py +38 -0
  16. django_qstash-0.2.1/src/django_qstash/discovery/models.py +25 -0
  17. django_qstash-0.2.1/src/django_qstash/discovery/utils.py +101 -0
  18. django_qstash-0.2.1/src/django_qstash/discovery/validators.py +24 -0
  19. django_qstash-0.2.1/src/django_qstash/exceptions.py +25 -0
  20. django_qstash-0.2.1/src/django_qstash/handlers.py +168 -0
  21. django_qstash-0.2.1/src/django_qstash/management/__init__.py +0 -0
  22. django_qstash-0.2.1/src/django_qstash/management/commands/__init__.py +0 -0
  23. django_qstash-0.2.1/src/django_qstash/management/commands/available_tasks.py +37 -0
  24. django_qstash-0.2.1/src/django_qstash/management/commands/clear_stale_results.py +42 -0
  25. django_qstash-0.2.1/src/django_qstash/management/commands/task_schedules.py +116 -0
  26. django_qstash-0.2.1/src/django_qstash/results/__init__.py +0 -0
  27. django_qstash-0.2.1/src/django_qstash/results/admin.py +24 -0
  28. django_qstash-0.2.1/src/django_qstash/results/apps.py +10 -0
  29. django_qstash-0.2.1/src/django_qstash/results/migrations/0001_initial.py +62 -0
  30. django_qstash-0.2.1/src/django_qstash/results/migrations/0002_taskresult_function_path_alter_taskresult_status_and_more.py +46 -0
  31. django_qstash-0.2.1/src/django_qstash/results/migrations/__init__.py +0 -0
  32. django_qstash-0.2.1/src/django_qstash/results/models.py +35 -0
  33. django_qstash-0.2.1/src/django_qstash/results/services.py +76 -0
  34. django_qstash-0.2.1/src/django_qstash/results/tasks.py +85 -0
  35. django_qstash-0.2.1/src/django_qstash/schedules/__init__.py +0 -0
  36. django_qstash-0.2.1/src/django_qstash/schedules/admin.py +66 -0
  37. django_qstash-0.2.1/src/django_qstash/schedules/apps.py +13 -0
  38. django_qstash-0.2.1/src/django_qstash/schedules/exceptions.py +15 -0
  39. django_qstash-0.2.1/src/django_qstash/schedules/formatters.py +37 -0
  40. django_qstash-0.2.1/src/django_qstash/schedules/forms.py +31 -0
  41. django_qstash-0.2.1/src/django_qstash/schedules/migrations/0001_initial.py +122 -0
  42. django_qstash-0.2.1/src/django_qstash/schedules/migrations/0002_taskschedule_updated_at.py +20 -0
  43. django_qstash-0.2.1/src/django_qstash/schedules/migrations/0003_alter_taskschedule_cron.py +30 -0
  44. django_qstash-0.2.1/src/django_qstash/schedules/migrations/__init__.py +0 -0
  45. django_qstash-0.2.1/src/django_qstash/schedules/models.py +122 -0
  46. django_qstash-0.2.1/src/django_qstash/schedules/services.py +66 -0
  47. django_qstash-0.2.1/src/django_qstash/schedules/signals.py +21 -0
  48. django_qstash-0.2.1/src/django_qstash/schedules/validators.py +69 -0
  49. django_qstash-0.2.1/src/django_qstash/settings.py +17 -0
  50. django_qstash-0.2.1/src/django_qstash/urls.py +9 -0
  51. django_qstash-0.2.1/src/django_qstash/utils.py +37 -0
  52. django_qstash-0.2.1/src/django_qstash/views.py +21 -0
  53. django_qstash-0.2.1/src/django_qstash.egg-info/PKG-INFO +569 -0
  54. django_qstash-0.2.1/src/django_qstash.egg-info/SOURCES.txt +62 -0
  55. django_qstash-0.2.1/src/django_qstash.egg-info/dependency_links.txt +1 -0
  56. django_qstash-0.2.1/src/django_qstash.egg-info/requires.txt +3 -0
  57. django_qstash-0.2.1/src/django_qstash.egg-info/top_level.txt +1 -0
  58. django_qstash-0.2.1/tests/test_callbacks.py +62 -0
  59. django_qstash-0.2.1/tests/test_exceptions.py +56 -0
  60. django_qstash-0.2.1/tests/test_handlers.py +151 -0
  61. django_qstash-0.2.1/tests/test_results_models.py +76 -0
  62. django_qstash-0.2.1/tests/test_settings.py +63 -0
  63. django_qstash-0.2.1/tests/test_utils.py +49 -0
  64. django_qstash-0.2.1/tests/test_views.py +62 -0
@@ -0,0 +1,569 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-qstash
3
+ Version: 0.2.1
4
+ Summary: A drop-in replacement for Celery's shared_task with Upstash QStash.
5
+ Author-email: Justin Mitchel <justin@codingforentrepreneurs.com>
6
+ Project-URL: Changelog, https://github.com/jmitchel3/django-qstash
7
+ Project-URL: Documentation, https://github.com/jmitchel3/django-qstash
8
+ Project-URL: Funding, https://github.com/jmitchel3/django-qstash
9
+ Project-URL: Repository, https://github.com/jmitchel3/django-qstash
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Framework :: Django :: 4.2
12
+ Classifier: Framework :: Django :: 5.0
13
+ Classifier: Framework :: Django :: 5.1
14
+ Classifier: Framework :: Django :: 5.2
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Natural Language :: English
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Programming Language :: Python :: Implementation :: CPython
25
+ Requires-Python: >=3.10
26
+ Description-Content-Type: text/markdown
27
+ Requires-Dist: django>=4.2
28
+ Requires-Dist: qstash<3,>=2
29
+ Requires-Dist: requests>=2.30
30
+
31
+ # django-qstash
32
+
33
+ Run background tasks with Django through webhooks and Upstash QStash.
34
+
35
+ _django-qstash_ is designed to be a drop-in replacement for Celery's `shared_task` or run alongside Celery.
36
+
37
+
38
+ ## How it works
39
+
40
+ In `tasks.py` in your apps:
41
+
42
+ ```python
43
+ from django_qstash import shared_task
44
+
45
+
46
+ @shared_task
47
+ def my_task():
48
+ pass
49
+ ```
50
+ > To use django-qstash with Celery, you can use `@stashed_task` instead of `@shared_task` (more below).
51
+
52
+ To do this we need:
53
+
54
+ - [Upstash QStash](https://upstash.com/docs/qstash/overall/getstarted)
55
+ - A single public _webhook_ to call `@stashed_task` functions automatically
56
+
57
+ This allows us to:
58
+
59
+ - Nearly identical usage to Celery's `@shared_task` with far less configuration and overhead
60
+ - Focus just on Django
61
+ - Drop Celery completely, scale it down, or use it as normal. django-qstash can work hand-in-hand with Celery
62
+ - Unlock true serverless and scale-to-zero for Django
63
+ - Run background tasks through webhooks
64
+ - Cut costs
65
+ - Trigger GitHub Actions Workflows or GitLab CI/CD pipelines for handling other kinds of background tasks based on our project's code.
66
+
67
+
68
+ ## Demo
69
+
70
+
71
+ ### Django QStash in 3 Minutes on YouTube
72
+
73
+ [![Django QStash Demo in 3 Minutes](https://img.youtube.com/vi/e-hloBp4eVQ/0.jpg)](https://www.youtube.com/watch?v=e-hloBp4eVQ)
74
+
75
+
76
+ ### Mini Course on YouTube
77
+
78
+ Step-by-Step [tutorial playlist on YouTube](https://www.youtube.com/playlist?list=PLEsfXFp6DpzQgNC8Q_ijgqxCVRtSC4_-L) on my [@CodingEntrepreneurs](https://cfe.sh/youtube) Youtube channel.
79
+
80
+ ## Table of Contents
81
+
82
+ - [django-qstash](#django-qstash)
83
+ - [How it works](#how-it-works)
84
+ - [Demo](#demo)
85
+ - [Django QStash in 3 Minutes on YouTube](#django-qstash-in-3-minutes-on-youtube)
86
+ - [Mini Course on YouTube](#mini-course-on-youtube)
87
+ - [Table of Contents](#table-of-contents)
88
+ - [Installation](#installation)
89
+ - [Using Pip](#using-pip)
90
+ - [Update Settings (`settings.py`)](#update-settings-settingspy)
91
+ - [Configure QStash Webhook Handler](#configure-qstash-webhook-handler)
92
+ - [Required Environment Variables](#required-environment-variables)
93
+ - [Sample Project](#sample-project)
94
+ - [Dependencies](#dependencies)
95
+ - [Usage](#usage)
96
+ - [Define a Task](#define-a-task)
97
+ - [Regular Task Call](#regular-task-call)
98
+ - [Background Task](#background-task)
99
+ - [`.delay()`](#delay)
100
+ - [`.apply_async()`](#apply_async)
101
+ - [`.apply_async()` With Time Delay](#apply_async-with-time-delay)
102
+ - [Arguments Must be JSON-ready](#arguments-must-be-json-ready)
103
+ - [Example Task](#example-task)
104
+ - [Management Commands](#management-commands)
105
+ - [Development](#development)
106
+ - [Development with a Public Domain](#development-with-a-public-domain)
107
+ - [Development with Docker Compose](#development-with-docker-compose)
108
+ - [Django Settings Configuration](#django-settings-configuration)
109
+ - [`DJANGO_QSTASH_DOMAIN`](#django_qstash_domain)
110
+ - [`DJANGO_QSTASH_WEBHOOK_PATH`](#django_qstash_webhook_path)
111
+ - [`DJANGO_QSTASH_FORCE_HTTPS`](#django_qstash_force_https)
112
+ - [Example Django Settings](#example-django-settings)
113
+ - [Schedule Tasks (Optional)](#schedule-tasks-optional)
114
+ - [Installation](#installation-1)
115
+ - [Schedule a Task](#schedule-a-task)
116
+ - [Store Task Results (Optional)](#store-task-results-optional)
117
+ - [Clear Stale Results](#clear-stale-results)
118
+ - [Definitions](#definitions)
119
+ - [Motivation](#motivation)
120
+
121
+
122
+ ## Installation
123
+
124
+ ### Using Pip
125
+ ```bash
126
+ pip install django-qstash
127
+ ```
128
+
129
+ ### Update Settings (`settings.py`)
130
+
131
+ ```python
132
+ INSTALLED_APPS = [
133
+ ##...
134
+ "django_qstash",
135
+ "django_qstash.results",
136
+ "django_qstash.schedules",
137
+ ##...
138
+ ]
139
+ ```
140
+ - `django_qstash` Includes the `@shared_task` and `@stashed_task` decorators and webhook view
141
+ - `django_qstash.results` (Optional): Store task results in Django DB
142
+ - `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.
143
+
144
+ ### Configure QStash Webhook Handler
145
+
146
+ Set it and forget it. `django_qstash` will handle the webhook from qstash automatically for you.
147
+
148
+ In your `ROOT_URLCONF` (e.g. `urls.py`), add the following:
149
+
150
+ ```python
151
+ from django.urls import include
152
+
153
+ urlpatterns = [
154
+ # ...
155
+ path("qstash/webhook/", include("django_qstash.urls")),
156
+ # ...
157
+ ]
158
+ ```
159
+ Be sure to use this path in your `DJANGO_QSTASH_WEBHOOK_PATH` environment variable.
160
+
161
+
162
+ The `django_qstash` webhook handler runs your `@shared_task` or `@stashed_task` functions via the `importlib` module. In other words, you should not need to modify the webhook handler.
163
+
164
+
165
+ ### Required Environment Variables
166
+
167
+ Get your QStash token and signing keys from [Upstash](https://upstash.com/).
168
+
169
+ ```python
170
+ QSTASH_TOKEN = "your_token"
171
+ QSTASH_CURRENT_SIGNING_KEY = "your_current_signing_key"
172
+ QSTASH_NEXT_SIGNING_KEY = "your_next_signing_key"
173
+
174
+ # required for django-qstash
175
+ DJANGO_QSTASH_DOMAIN = "https://example.com"
176
+ DJANGO_QSTASH_WEBHOOK_PATH = "/qstash/webhook/"
177
+ ```
178
+ > Review [.env.sample](.env.sample) to see all the environment variables you need to set.
179
+
180
+
181
+ ## Sample Project
182
+ There is a sample project in [sample_project/](sample_project/) that shows how all this is implemented.
183
+
184
+ ## Dependencies
185
+
186
+ - [Python 3.10+](https://www.python.org/)
187
+ - [Django 5+](https://docs.djangoproject.com/)
188
+ - [qstash-py](https://github.com/upstash/qstash-py)
189
+ - [Upstash](https://upstash.com/) account
190
+
191
+ ## Usage
192
+
193
+ Django-QStash revolves around the `stashed_task` decorator. The goal is to be a drop-in replacement for Celery's `shared_task` decorator.
194
+
195
+ Here's how it works:
196
+ - Define a Task
197
+ - Call a Task with `.delay()` or `.apply_async()`
198
+
199
+ ### Define a Task
200
+ ```python
201
+ # from celery import shared_task
202
+ from django_qstash import shared_task
203
+ from django_qstash import stashed_task
204
+
205
+
206
+ @stashed_task
207
+ def hello_world(name: str, age: int = None, activity: str = None):
208
+ if age is None:
209
+ print(f"Hello {name}! I see you're {activity}.")
210
+ return
211
+ print(f"Hello {name}! I see you're {activity} at {age} years old.")
212
+
213
+
214
+ @shared_task
215
+ def hello_world_redux(name: str, age: int = None, activity: str = None):
216
+ if age is None:
217
+ print(f"Hello {name}! I see you're {activity}.")
218
+ return
219
+ print(f"Hello {name}! I see you're {activity} at {age} years old.")
220
+ ```
221
+
222
+ - `hello_world` and `hello_world_redux` work the same with django-qstash.
223
+ - If you use Celery's `@shared_task` instead, Celery would handle only `hello_world_redux` and django-qstash would handle only `hello_world`.
224
+
225
+ ### Regular Task Call
226
+ Nothing special here. Just call the function like any other to verify it works.
227
+
228
+ ```python
229
+ # normal function call
230
+ hello_world("Tony Stark", age=40, activity="building in a cave with a box of scraps.")
231
+ ```
232
+
233
+ ### Background Task
234
+
235
+ 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.
236
+
237
+ This functionality is modeled after Celery and it works as you'd expect.
238
+
239
+
240
+ #### `.delay()`
241
+ ```python
242
+ hello_world.delay(
243
+ "Tony Stark", age=40, activity="building in a cave with a box of scraps."
244
+ )
245
+ ```
246
+
247
+ #### `.apply_async()`
248
+ ```python
249
+ hello_world.apply_async(
250
+ args=("Tony Stark",),
251
+ kwargs={"activity": "building in a cave with a box of scraps."},
252
+ )
253
+ ```
254
+
255
+ #### `.apply_async()` With Time Delay
256
+
257
+ Just use the `countdown` parameter to delay the task by N seconds. (always in seconds): `.apply_async(*args, **kwargs, countdown=N)`
258
+
259
+
260
+ ```python
261
+ # async task delayed 35 seconds
262
+ delay_35_seconds = 35
263
+ hello_world.apply_async(
264
+ args=("Tony Stark",),
265
+ kwargs={"activity": "building in a cave with a box of scraps."},
266
+ countdown=delay_35_seconds,
267
+ )
268
+ ```
269
+
270
+ ### Arguments Must be JSON-ready
271
+
272
+ Arguments to django-qstash managed functions must be _JSON_ serializable.
273
+
274
+ The way you find out:
275
+ ```python
276
+ import json
277
+
278
+ data = {
279
+ "args": ("Tony Stark",),
280
+ "kwargs": {"activity": "building in a cave with a box of scraps."},
281
+ }
282
+ print(json.dumps(data))
283
+ # no errors, you're good to go.
284
+ ```
285
+ If you have `errors` you'll need to fix them. Here's a few common errors you might see:
286
+
287
+ - Using a Django queryset directly as an argument
288
+ - Using a Django model instance directly as an argument
289
+ - 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")`)
290
+
291
+ ### Example Task
292
+
293
+ ```python
294
+ # from celery import shared_task
295
+ # becomes
296
+ # from django_qstash import shared_task
297
+ # or
298
+ from django_qstash import stashed_task
299
+
300
+
301
+ @stashed_task
302
+ def math_add_task(a, b, save_to_file=False, *args, **kwargs):
303
+ logger.info(f"Adding {a} and {b}")
304
+ if save_to_file:
305
+ with open("math-add-result.txt", "w") as f:
306
+ f.write(f"{a} + {b} = {a + b}")
307
+ return a + b
308
+ ```
309
+
310
+
311
+ Calling:
312
+ ```python
313
+ math_add_task.apply_async(args=(12, 454), save_to_file=True)
314
+ ```
315
+ is the same as
316
+ ```python
317
+ math_add_task.delay(12, 454, save_to_file=True)
318
+ ```
319
+
320
+ But if you need to delay the task, use `.apply_async()` with the `countdown` parameter.
321
+
322
+ ```python
323
+ five_hours = 5 * 60 * 60
324
+ math_add_task.apply_async(
325
+ args=(12, 454), kwargs={"save_to_file": True}, countdown=five_hours
326
+ )
327
+ ```
328
+
329
+ The `.delay()` method does not support a countdown parameter because it simply passes the arguments (*args, **kwargs) to the `apply_async()` method.
330
+
331
+
332
+ ## Management Commands
333
+
334
+ - `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()`).
335
+
336
+ _Requires `django_qstash.schedules` installed._
337
+ - `python manage.py task_schedules --list` see all schedules relate to the `DJANGO_QSTASH_DOMAIN`
338
+ - `python manage.py task_schedules --sync` sync schedules based on the `DJANGO_QSTASH_DOMAIN` to store in the Django Admin.
339
+
340
+ ## Development
341
+
342
+ During development, you have two options:
343
+
344
+ - Use Upstash.com with a publicly accessible domain (preferred)
345
+ - Use Docker Compose with [compose.dev.yaml](./compose.dev.yaml)
346
+
347
+ ### Development with a Public Domain
348
+
349
+ The closer your development environment is to production the better. For that reason, using a publicly accessible domain is the preferred with to develop with _django-qstash_.
350
+
351
+ To get a public domain during development, we recommend any of the following:
352
+
353
+ - [Cloudflare Tunnels](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) with a domain name you control.
354
+ - [ngrok](https://ngrok.com/)
355
+
356
+ Once you have a domain name, you can configure the `DJANGO_QSTASH_DOMAIN` setting in your Django settings.
357
+
358
+ ### Development with Docker Compose
359
+ Upstash covers how to run QStash during development on [this guide](https://upstash.com/docs/qstash/howto/local-development)
360
+
361
+ In our case we need to the following things:
362
+
363
+ - `docker compose -f compose.dev.yaml up`
364
+ - Add `QSTASH_URL=http://127.0.0.1:8585` to your `.env` file.
365
+ - Use the `QSTASH_TOKEN`, `QSTASH_CURRENT_SIGNING_KEY`, and `QSTASH_NEXT_SIGNING_KEY` the terminal output of Docker compose _or_ the values listed in [compose.dev.yaml](./compose.dev.yaml).
366
+
367
+ ## Django Settings Configuration
368
+
369
+ Various options are available to configure django-qstash.
370
+
371
+ ### `DJANGO_QSTASH_DOMAIN`
372
+ - Required: Yes
373
+ - Default:`None`
374
+ - 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.
375
+
376
+ ### `DJANGO_QSTASH_WEBHOOK_PATH`
377
+ - Required: Yes
378
+ - Default:`/qstash/webhook/`
379
+ - Description: The path where QStash will send webhooks to your Django application.
380
+
381
+ ### `DJANGO_QSTASH_FORCE_HTTPS`
382
+ - Required: No
383
+ - Default: `True`
384
+ - Description: Whether to force HTTPS for the webhook.
385
+
386
+ ###`DJANGO_QSTASH_RESULT_TTL`
387
+ - Required: No
388
+ - Default:`604800`
389
+ - 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).
390
+
391
+
392
+ ### Example Django Settings
393
+
394
+ For a complete example, review [sample_project/cfehome/settings.py](sample_project/cfehome/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).
395
+
396
+ Using `os.environ`:
397
+ ```python
398
+ import os
399
+
400
+ ###########################
401
+ # django settings
402
+ ###########################
403
+ DJANGO_DEBUG = str(os.environ.get("DJANGO_DEBUG")) == "1"
404
+ DJANGO_SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY")
405
+ ALLOWED_HOSTS = [os.environ.get("ALLOWED_HOST")]
406
+ CSRF_TRUSTED_ORIGINS = [os.environ.get("CSRF_TRUSTED_ORIGIN")]
407
+ ###########################
408
+ # qstash-py settings
409
+ ###########################
410
+ USE_LOCAL_QSTASH = str(os.environ.get("USE_LOCAL_QSTASH")) == "1"
411
+ QSTASH_TOKEN = os.environ.get("QSTASH_TOKEN")
412
+ QSTASH_CURRENT_SIGNING_KEY = os.environ.get("QSTASH_CURRENT_SIGNING_KEY")
413
+ QSTASH_NEXT_SIGNING_KEY = os.environ.get("QSTASH_NEXT_SIGNING_KEY")
414
+ if DJANGO_DEBUG and USE_LOCAL_QSTASH:
415
+ # connect to the docker compose qstash instance
416
+ os.environ["QSTASH_URL"] = "http://127.0.0.1:8585"
417
+ ###########################
418
+ # django_qstash settings
419
+ ###########################
420
+ DJANGO_QSTASH_DOMAIN = os.environ.get("DJANGO_QSTASH_DOMAIN")
421
+ DJANGO_QSTASH_WEBHOOK_PATH = os.environ.get("DJANGO_QSTASH_WEBHOOK_PATH")
422
+ DJANGO_QSTASH_FORCE_HTTPS = True
423
+ DJANGO_QSTASH_RESULT_TTL = 604800
424
+ ```
425
+
426
+
427
+ ## Schedule Tasks (Optional)
428
+
429
+ Run background tasks on a CRON schedule.
430
+
431
+ 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.
432
+
433
+ ### Installation
434
+
435
+ Update your `INSTALLED_APPS` setting to include `django_qstash.schedules`.
436
+
437
+ ```python
438
+ INSTALLED_APPS = [
439
+ # ...
440
+ "django_qstash", # required
441
+ "django_qstash.schedules",
442
+ # ...
443
+ ]
444
+ ```
445
+
446
+ Run migrations:
447
+ ```bash
448
+ python manage.py migrate django_qstash_schedules
449
+ ```
450
+
451
+ ### Schedule a Task
452
+
453
+ Tasks must exist before you can schedule them. Review [Define a Task](#define-a-task) for more information.
454
+
455
+ Here's how you can schedule a task:
456
+ - Django Admin (`/admin/django_qstash_schedules/taskschedule/add/`)
457
+ - Django shell (`python manage.py shell`)
458
+
459
+ ```python
460
+ from django_qstash.schedules.models import TaskSchedule
461
+ from django_qstash.discovery.utils import discover_tasks
462
+
463
+ all_available_tasks = discover_tasks(paths_only=True)
464
+
465
+ desired_task = "django_qstash.results.clear_stale_results_task"
466
+ # or desired_task = "example_app.tasks.my_task"
467
+
468
+ task_to_use = desired_task
469
+ if desired_task not in all_available_tasks:
470
+ task_to_use = all_available_tasks[0]
471
+
472
+ print(f"Using task: {task_to_use}")
473
+
474
+ TaskSchedule.objects.create(
475
+ name="My Schedule",
476
+ cron="0 0 * * *",
477
+ task_name=task_to_use,
478
+ args=["arg1", "arg2"],
479
+ kwargs={"kwarg1": "value1", "kwarg2": "value2"},
480
+ )
481
+ ```
482
+ - `django_qstash.results.clear_stale_results_task` is a built-in task that `django_qstash.results` provides
483
+ - `args` and `kwargs` are the arguments to pass to the task
484
+ - `cron` is the cron schedule to run the task. Use [contrab.guru](https://crontab.guru/) for writing the cron format.
485
+
486
+
487
+ ## Store Task Results (Optional)
488
+
489
+ Retain the results of background tasks in the database with clear-out functionality.
490
+
491
+ 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`).
492
+
493
+ To install it, just add `django_qstash.results` to your `INSTALLED_APPS` setting.
494
+
495
+ ```python
496
+ INSTALLED_APPS = [
497
+ # ...
498
+ "django_qstash",
499
+ "django_qstash.results",
500
+ # ...
501
+ ]
502
+ ```
503
+
504
+ Run migrations:
505
+ ```bash
506
+ python manage.py migrate django_qstash_results
507
+ ```
508
+
509
+ Key configuration:
510
+
511
+ - [DJANGO_QSTASH_WEBHOOK_PATH](#django-settings-configuration)
512
+ - [DJANGO_QSTASH_DOMAIN](#django-settings-configuration)
513
+ - [DJANGO_QSTASH_RESULT_TTL](#django-settings-configuration)
514
+
515
+ ### Clear Stale Results
516
+
517
+ We recommend purging the `TaskResult` model after a certain amount of time.
518
+ ```bash
519
+ python manage.py clear_stale_results --since 604800
520
+ ```
521
+ Args:
522
+ - `--since` is the number of seconds ago to clear results for. Defaults to 604800 seconds (7 days or the `DJANGO_QSTASH_RESULT_TTL` setting).
523
+ - `--no-input` is a flag to skip the confirmation prompt to delete the results.
524
+
525
+
526
+
527
+ ## Definitions
528
+
529
+ - **Background Task**: A function or task that is not part of the request/response cycle.
530
+ - Examples include as sending an email, running a report, or updating a database.
531
+ - Pro: Background tasks can drastically improve the end-user experience since they can move on with their day while the task runs in the background.
532
+ - Con: Processes that run background tasks (like Celery) typically have to run 24/7.
533
+ - **Scale-to-Zero**: Depending on the amount of traffic, Django can be effectively turned off. If done right, when more traffic comes in, Django can be turned back on very quickly.
534
+ - **Serverless**: A cloud computing model where code runs without server management, with scaling and billing tied to usage. Often used interchangeably with "scale-to-zero".
535
+
536
+
537
+ ## Motivation
538
+
539
+ TLDR - Celery cannot be serverless. I want serverless "Celery" so I only pay for the apps that have attention and traffic. Upstash created QStash to help solve the problem of message queues in a serverless environment. django-qstash is the goldilocks that combines the functionality of Celery with the functionality of QStash all to unlock fully serverless Django.
540
+
541
+ I run a lot of side projects with Django. Some as demos for tutorials based on my work at [@codingforentrepreneurs](https://cfe.sh/github) and some are new businesses that haven't found much traction yet.
542
+
543
+ Most web apps can benefit from async background tasks such as sending emails, running reports, or updating databases.
544
+
545
+ But how?
546
+
547
+ Traditionally, I'd reach for Celery but that can get expensive really quick. Running a lot of Django projects can add up too -- "death by a thousand cuts" if you will. A server for Django, for celery worker, for celery beat scheduler, and so on. It adds up fast.
548
+
549
+ I think serverless is the answer. Pay for what you use and scale to zero when you don't need it and scale up when you do -- all automated.
550
+
551
+ Django can be serverless and is pretty easy to do thanks to Docker and the countless hosting options and services out there. Celery cannot be serverless, at least yet.
552
+
553
+ Let's face it. Celery is a powerful tool to run async background tasks but it comes at a cost. It needs at least one server running 24/7. For best performance, it needs 2 (one worker, one beat). It also needs Redis or RabbitMQ. In a traditional Django setup with Celery and Redis, you need to run 3 to 4 different processes. Most background processes that are tied to web apps are not serverless; they have to "listen" for their next task.
554
+
555
+ To make Django truly scale-to-zero and serverless, we need to drop Celery.
556
+
557
+ Enter __django-qstash__.
558
+
559
+ django-qstash is designed to be a near drop-in replacement for Celery's `shared_task` decorator.
560
+
561
+ It works by leveraging Upstash QStash to deliver messages about your tasks (e.g. the function's arguments) via webhooks to your Django application. In the QStash [docs](https://upstash.com/docs/qstash/overall/getstarted), it is described as:
562
+
563
+ > QStash is a serverless messaging and scheduling solution. It fits easily into your existing workflow and allows you to build reliable systems without managing infrastructure.
564
+ >
565
+ > Instead of calling an endpoint directly, QStash acts as a middleman between you and an API to guarantee delivery, perform automatic retries on failure, and more.
566
+
567
+ Compared to a traditional setup with Django, Celery, and Redis, which requires 3 to 4 processes, you only need to run a single process and can delegate the rest to Upstash QStash, significantly simplifying your infrastructure.
568
+
569
+ django-qstash has a webhook handler that converts a QStash message to run a specific `@shared_task` function (the one that called `.delay()` or `.apply_async()`). It's easy, it's cheap, it's effective, and best of all, it unlocks the scale-to-zero potential of Django as a serverless app.