django-qstash 0.0.8__py3-none-any.whl → 0.0.10__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-qstash might be problematic. Click here for more details.
- django_qstash/__init__.py +1 -1
- django_qstash/discovery/fields.py +6 -6
- django_qstash/discovery/utils.py +10 -2
- django_qstash/discovery/validators.py +2 -2
- django_qstash/management/commands/available_tasks.py +37 -0
- django_qstash/schedules/services.py +3 -1
- {django_qstash-0.0.8.dist-info → django_qstash-0.0.10.dist-info}/METADATA +99 -14
- {django_qstash-0.0.8.dist-info → django_qstash-0.0.10.dist-info}/RECORD +10 -9
- {django_qstash-0.0.8.dist-info → django_qstash-0.0.10.dist-info}/WHEEL +0 -0
- {django_qstash-0.0.8.dist-info → django_qstash-0.0.10.dist-info}/top_level.txt +0 -0
django_qstash/__init__.py
CHANGED
|
@@ -16,10 +16,10 @@ class TaskChoiceField(forms.ChoiceField):
|
|
|
16
16
|
kwargs.pop("max_length", None)
|
|
17
17
|
|
|
18
18
|
# Get tasks before calling parent to set choices
|
|
19
|
-
tasks = discover_tasks()
|
|
19
|
+
tasks = discover_tasks(locations_only=False)
|
|
20
20
|
|
|
21
21
|
# Convert tasks to choices using (task_name, task_name) format
|
|
22
|
-
task_choices = [(
|
|
22
|
+
task_choices = [(task["location"], task["field_label"]) for task in tasks]
|
|
23
23
|
|
|
24
24
|
kwargs["choices"] = task_choices
|
|
25
25
|
kwargs["validators"] = [task_exists_validator] + kwargs.get("validators", [])
|
|
@@ -30,9 +30,9 @@ class TaskChoiceField(forms.ChoiceField):
|
|
|
30
30
|
Returns the actual task dot notation path for the selected value
|
|
31
31
|
"""
|
|
32
32
|
if self.data:
|
|
33
|
-
tasks = discover_tasks()
|
|
33
|
+
tasks = discover_tasks(locations_only=False)
|
|
34
34
|
|
|
35
|
-
for
|
|
36
|
-
if
|
|
37
|
-
return
|
|
35
|
+
for task in tasks:
|
|
36
|
+
if task["field_label"] == self.data:
|
|
37
|
+
return task["location"]
|
|
38
38
|
return None
|
django_qstash/discovery/utils.py
CHANGED
|
@@ -19,7 +19,7 @@ DJANGO_QSTASH_DISCOVER_INCLUDE_SETTINGS_DIR = getattr(
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
@lru_cache(maxsize=None)
|
|
22
|
-
def discover_tasks() -> list[
|
|
22
|
+
def discover_tasks(locations_only: bool = False) -> list[str] | list[dict]:
|
|
23
23
|
"""
|
|
24
24
|
Automatically discover tasks in Django apps and return them as a list of tuples.
|
|
25
25
|
Each tuple contains (dot_notation_path, task_name).
|
|
@@ -72,13 +72,21 @@ def discover_tasks() -> list[tuple[str, str]]:
|
|
|
72
72
|
label = value
|
|
73
73
|
else:
|
|
74
74
|
label = f"{attr.name} ({package}.tasks)"
|
|
75
|
-
discovered_tasks.append(
|
|
75
|
+
discovered_tasks.append(
|
|
76
|
+
{
|
|
77
|
+
"name": attr.name,
|
|
78
|
+
"field_label": label,
|
|
79
|
+
"location": f"{package}.tasks.{attr_name}",
|
|
80
|
+
}
|
|
81
|
+
)
|
|
76
82
|
except Exception as e:
|
|
77
83
|
warnings.warn(
|
|
78
84
|
f"Failed to import tasks from {package}: {str(e)}",
|
|
79
85
|
RuntimeWarning,
|
|
80
86
|
stacklevel=2,
|
|
81
87
|
)
|
|
88
|
+
if locations_only:
|
|
89
|
+
return [x["location"] for x in discovered_tasks]
|
|
82
90
|
return discovered_tasks
|
|
83
91
|
|
|
84
92
|
|
|
@@ -15,8 +15,8 @@ def task_exists_validator(task_name):
|
|
|
15
15
|
Raises:
|
|
16
16
|
ValidationError: If the task cannot be found
|
|
17
17
|
"""
|
|
18
|
-
|
|
19
|
-
available_tasks =
|
|
18
|
+
discover_tasks.cache_clear()
|
|
19
|
+
available_tasks = discover_tasks(locations_only=True)
|
|
20
20
|
|
|
21
21
|
if task_name not in available_tasks:
|
|
22
22
|
raise ValidationError(
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django.core.management.base import BaseCommand
|
|
4
|
+
|
|
5
|
+
from django_qstash.discovery.utils import discover_tasks
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Command(BaseCommand):
|
|
9
|
+
help = "View all available tasks"
|
|
10
|
+
|
|
11
|
+
def add_arguments(self, parser):
|
|
12
|
+
parser.add_argument(
|
|
13
|
+
"--locations",
|
|
14
|
+
action="store_true",
|
|
15
|
+
help="Only show task paths",
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
def handle(self, *args, **options):
|
|
19
|
+
locations_only = options["locations"] or False
|
|
20
|
+
self.stdout.write("Available tasks:")
|
|
21
|
+
discover_tasks.cache_clear()
|
|
22
|
+
if locations_only:
|
|
23
|
+
tasks = discover_tasks(locations_only=locations_only)
|
|
24
|
+
for task in tasks:
|
|
25
|
+
self.stdout.write(f"\t- {self.style.SQL_FIELD(task)}")
|
|
26
|
+
else:
|
|
27
|
+
tasks = discover_tasks(locations_only=False)
|
|
28
|
+
for task in tasks:
|
|
29
|
+
name = task["name"]
|
|
30
|
+
field_label = task["field_label"]
|
|
31
|
+
location = task["location"]
|
|
32
|
+
self.stdout.write(
|
|
33
|
+
f" Name: {self.style.SQL_FIELD(name)}\n"
|
|
34
|
+
f" Location: {self.style.SQL_FIELD(location)}\n"
|
|
35
|
+
f" Field Label: {self.style.SQL_FIELD(field_label)}"
|
|
36
|
+
)
|
|
37
|
+
self.stdout.write("")
|
|
@@ -20,7 +20,9 @@ def sync_task_schedule_instance_to_qstash(instance: TaskSchedule) -> TaskSchedul
|
|
|
20
20
|
and updates existing schedules when active.
|
|
21
21
|
"""
|
|
22
22
|
data = format_task_schedule_for_qstash(instance)
|
|
23
|
-
qstash_client.schedule.create(**data)
|
|
23
|
+
schedule_id = qstash_client.schedule.create(**data)
|
|
24
|
+
if not instance.schedule_id:
|
|
25
|
+
TaskSchedule.objects.filter(id=instance.id).update(schedule_id=schedule_id)
|
|
24
26
|
|
|
25
27
|
sync_state_changes(instance)
|
|
26
28
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: django-qstash
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.10
|
|
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
|
|
@@ -68,8 +68,13 @@ This allows us to:
|
|
|
68
68
|
- [`.apply_async()` With Time Delay](#apply_async-with-time-delay)
|
|
69
69
|
- [JSON-ready Arguments](#json-ready-arguments)
|
|
70
70
|
- [Example Task](#example-task)
|
|
71
|
-
- [
|
|
72
|
-
- [
|
|
71
|
+
- [Management Commands](#management-commands)
|
|
72
|
+
- [Development Usage](#development-usage)
|
|
73
|
+
- [Django Settings Configuration](#django-settings-configuration)
|
|
74
|
+
- [Schedule Tasks (Optional)](#schedule-tasks-optional)
|
|
75
|
+
- [Installation](#installation-1)
|
|
76
|
+
- [Schedule a Task](#schedule-a-task)
|
|
77
|
+
- [Store Task Results (Optional)](#store-task-results-optional)
|
|
73
78
|
- [Clear Stale Results](#clear-stale-results)
|
|
74
79
|
- [Definitions](#definitions)
|
|
75
80
|
- [Motivation](#motivation)
|
|
@@ -89,12 +94,13 @@ INSTALLED_APPS = [
|
|
|
89
94
|
##...
|
|
90
95
|
"django_qstash",
|
|
91
96
|
"django_qstash.results",
|
|
97
|
+
"django_qstash.schedules",
|
|
92
98
|
##...
|
|
93
99
|
]
|
|
94
100
|
```
|
|
95
101
|
- `django_qstash` Includes the `@shared_task` decorator and webhook view
|
|
96
102
|
- `django_qstash.results` (Optional): Store task results in Django DB
|
|
97
|
-
|
|
103
|
+
- `django_qstash.schedules` (Optional): Use QStash Schedules to run your `django_qstash` tasks. Out of the box support for _django_qstash_ `@shared_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.
|
|
98
104
|
|
|
99
105
|
### Configure Webhook URL
|
|
100
106
|
|
|
@@ -157,7 +163,6 @@ def hello_world(name: str, age: int = None, activity: str = None):
|
|
|
157
163
|
print(f"Hello {name}! I see you're {activity} at {age} years old.")
|
|
158
164
|
```
|
|
159
165
|
|
|
160
|
-
|
|
161
166
|
### Regular Task Call
|
|
162
167
|
Nothing special here. Just call the function like any other to verify it works.
|
|
163
168
|
|
|
@@ -254,22 +259,101 @@ math_add_task.apply_async(
|
|
|
254
259
|
The `.delay()` method does not support a countdown parameter because it simply passes the arguments (*args, **kwargs) to the `apply_async()` method.
|
|
255
260
|
|
|
256
261
|
|
|
257
|
-
##
|
|
262
|
+
## Management Commands
|
|
263
|
+
|
|
264
|
+
- `python manage.py available_tasks` to view all available tasks
|
|
265
|
+
|
|
266
|
+
_Requires `django_qstash.schedules` installed._
|
|
267
|
+
- `python manage.py task_schedules --list` see all schedules relate to the `DJANGO_QSTASH_DOMAIN`
|
|
268
|
+
- `python manage.py task_schedules --sync` sync schedules based on the `DJANGO_QSTASH_DOMAIN` to store in the Django Admin.
|
|
269
|
+
|
|
270
|
+
## Development Usage
|
|
271
|
+
|
|
272
|
+
django-qstash requires a public domain to work (e.g. `https://djangoqstash.com`). There are many ways to do this, we recommend:
|
|
273
|
+
|
|
274
|
+
- [Cloudflare Tunnels](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) with a domain name you control.
|
|
275
|
+
- [ngrok](https://ngrok.com/)
|
|
276
|
+
|
|
277
|
+
Once you have a domain name, you can configure the `DJANGO_QSTASH_DOMAIN` setting in your Django settings.
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
## Django Settings Configuration
|
|
258
281
|
|
|
259
282
|
In Django settings, you can configure the following:
|
|
260
283
|
|
|
261
|
-
`DJANGO_QSTASH_DOMAIN`: Must be a valid and publicly accessible domain. For example `https://djangoqstash.com
|
|
284
|
+
- `DJANGO_QSTASH_DOMAIN`: 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
|
+
|
|
286
|
+
- `DJANGO_QSTASH_WEBHOOK_PATH` (default:`/qstash/webhook/`): The path where QStash will send webhooks to your Django application.
|
|
262
287
|
|
|
263
|
-
|
|
288
|
+
- `DJANGO_QSTASH_FORCE_HTTPS` (default:`True`): Whether to force HTTPS for the webhook.
|
|
264
289
|
|
|
265
|
-
`
|
|
290
|
+
- `DJANGO_QSTASH_RESULT_TTL` (default:`604800`): A number of seconds after which task result data can be safely deleted. Defaults to 604800 seconds (7 days or 7 * 24 * 60 * 60).
|
|
266
291
|
|
|
267
|
-
`DJANGO_QSTASH_FORCE_HTTPS` (default:`True`): Whether to force HTTPS for the webhook.
|
|
268
292
|
|
|
269
|
-
|
|
293
|
+
## Schedule Tasks (Optional)
|
|
270
294
|
|
|
295
|
+
The `django_qstash.schedules` app schedules tasks using Upstash [QStash Schedules](https://upstash.com/docs/qstash/features/schedules) and the django-qstash `@shared_task` decorator.
|
|
271
296
|
|
|
272
|
-
|
|
297
|
+
### Installation
|
|
298
|
+
|
|
299
|
+
Update your `INSTALLED_APPS` setting to include `django_qstash.schedules`.
|
|
300
|
+
|
|
301
|
+
```python
|
|
302
|
+
INSTALLED_APPS = [
|
|
303
|
+
# ...
|
|
304
|
+
"django_qstash", # required
|
|
305
|
+
"django_qstash.schedules",
|
|
306
|
+
# ...
|
|
307
|
+
]
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Run migrations:
|
|
311
|
+
```bash
|
|
312
|
+
python manage.py migrate django_qstash_schedules
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
## Schedule a Task
|
|
317
|
+
|
|
318
|
+
Tasks must exist before you can schedule them. Review [Define a Task](#define-a-task) for more information.
|
|
319
|
+
|
|
320
|
+
Here's how you can schedule a task:
|
|
321
|
+
- Django Admin (`/admin/django_qstash_schedules/taskschedule/add/`)
|
|
322
|
+
- Django shell (`python manage.py shell`)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
```python
|
|
328
|
+
from django_qstash.schedules.models import TaskSchedule
|
|
329
|
+
from django_qstash.discovery.utils import discover_tasks
|
|
330
|
+
|
|
331
|
+
all_available_tasks = discover_tasks(paths_only=True)
|
|
332
|
+
|
|
333
|
+
desired_task = "django_qstash.results.clear_stale_results_task"
|
|
334
|
+
# or desired_task = "example_app.tasks.my_task"
|
|
335
|
+
|
|
336
|
+
task_to_use = desired_task
|
|
337
|
+
if desired_task not in available_task_locations:
|
|
338
|
+
task_to_use = available_task_locations[0]
|
|
339
|
+
|
|
340
|
+
print(f"Using task: {task_to_use}")
|
|
341
|
+
|
|
342
|
+
TaskSchedule.objects.create(
|
|
343
|
+
name="My Schedule",
|
|
344
|
+
cron="0 0 * * *",
|
|
345
|
+
task_name=task_to_use,
|
|
346
|
+
args=["arg1", "arg2"],
|
|
347
|
+
kwargs={"kwarg1": "value1", "kwarg2": "value2"},
|
|
348
|
+
)
|
|
349
|
+
```
|
|
350
|
+
- `django_qstash.results.clear_stale_results_task` is a built-in task that `django_qstash.results` provides
|
|
351
|
+
- `args` and `kwargs` are the arguments to pass to the task
|
|
352
|
+
- `cron` is the cron schedule to run the task. Use [contrab.guru](https://crontab.guru/) for writing the cron format.
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
## Store Task Results (Optional)
|
|
273
357
|
|
|
274
358
|
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 webhooks.
|
|
275
359
|
|
|
@@ -278,6 +362,7 @@ To install it, just add `django_qstash.results` to your `INSTALLED_APPS` setting
|
|
|
278
362
|
```python
|
|
279
363
|
INSTALLED_APPS = [
|
|
280
364
|
# ...
|
|
365
|
+
"django_qstash",
|
|
281
366
|
"django_qstash.results",
|
|
282
367
|
# ...
|
|
283
368
|
]
|
|
@@ -285,14 +370,14 @@ INSTALLED_APPS = [
|
|
|
285
370
|
|
|
286
371
|
Run migrations:
|
|
287
372
|
```bash
|
|
288
|
-
python manage.py migrate
|
|
373
|
+
python manage.py migrate django_qstash_results
|
|
289
374
|
```
|
|
290
375
|
|
|
291
376
|
### Clear Stale Results
|
|
292
377
|
|
|
293
378
|
We recommend purging the `TaskResult` model after a certain amount of time.
|
|
294
379
|
```bash
|
|
295
|
-
python manage.py clear_stale_results
|
|
380
|
+
python manage.py clear_stale_results --since 604800
|
|
296
381
|
```
|
|
297
382
|
Args:
|
|
298
383
|
- `--since` is the number of seconds ago to clear results for. Defaults to 604800 seconds (7 days or the `DJANGO_QSTASH_RESULT_TTL` setting).
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
django_qstash/__init__.py,sha256=
|
|
1
|
+
django_qstash/__init__.py,sha256=LX7-S0Qx-tBkMzcbIy-Fv4WyLP_wY0GnNsO2YG1bJoA,129
|
|
2
2
|
django_qstash/callbacks.py,sha256=VG5tGdNzAmUEh7NlpghrxhWvnpRNXZucWmWwxaemw0M,530
|
|
3
3
|
django_qstash/client.py,sha256=cgHf-g6lDAltY_Vt6GUVJNY2JSz1czBOHL-WVkkLs2M,149
|
|
4
4
|
django_qstash/exceptions.py,sha256=pH6kKRJFIVFkDHUJQ9yRWmtGdBBSXpNAwMSFuNzMgPw,392
|
|
@@ -10,12 +10,13 @@ django_qstash/app/__init__.py,sha256=353w1JyvIinkr3aHjyT7I01utFM56lrgIjSSfqtJkA4
|
|
|
10
10
|
django_qstash/app/base.py,sha256=gM7GIJh_omZcxbmsrwAEadA-N6EuUJbPzh0CflOIVRg,3864
|
|
11
11
|
django_qstash/app/decorators.py,sha256=Gn0TAxUHfS4fKUdKdArvHeWlxrRvj9_NOVnC1a6pGTQ,732
|
|
12
12
|
django_qstash/discovery/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
-
django_qstash/discovery/fields.py,sha256=
|
|
13
|
+
django_qstash/discovery/fields.py,sha256=h-31sysbIU05KGKGBAu7uQo9bZnZg3kgjN_ZhPPMTGU,1260
|
|
14
14
|
django_qstash/discovery/models.py,sha256=9ml9lTKEqEKx2uqYvejZw_BjdnowgFOPE7rYNt_8E9A,685
|
|
15
|
-
django_qstash/discovery/utils.py,sha256=
|
|
16
|
-
django_qstash/discovery/validators.py,sha256=
|
|
15
|
+
django_qstash/discovery/utils.py,sha256=OW-sbHWSnA1Hop5X0ys1hlxoYi5fNTXfAg0rKku5sx0,3561
|
|
16
|
+
django_qstash/discovery/validators.py,sha256=50-GxdSNj00eY-EGxDR8u1kUQDj0g8PoeWHAGG1cvzk,657
|
|
17
17
|
django_qstash/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
18
|
django_qstash/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
+
django_qstash/management/commands/available_tasks.py,sha256=l-do7Mry83NxbCdyMLcLZKtwE_T428i8wA42FIn5fq0,1295
|
|
19
20
|
django_qstash/management/commands/clear_stale_results.py,sha256=mxXXqIy6pnvsN8JVE0xe3mypqtkaZbpqdBjpox-MDik,1402
|
|
20
21
|
django_qstash/management/commands/task_schedules.py,sha256=b9lJ1vjQKHyGzWAo9csGwE_oaKfgcSC8bPFLt9Ry6WE,4278
|
|
21
22
|
django_qstash/results/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -33,13 +34,13 @@ django_qstash/schedules/exceptions.py,sha256=fvXArrK24ZBGITijuJ_I5xixURmFrJpQ6Db
|
|
|
33
34
|
django_qstash/schedules/formatters.py,sha256=KMt1457z4Mp0Mob2IxgTBHmye_mRfcLn7Rdf7ThvbEg,1157
|
|
34
35
|
django_qstash/schedules/forms.py,sha256=S5Wy2mZha2-th4oAQLjkC9JytfXFNJcL1j18u6rslxI,524
|
|
35
36
|
django_qstash/schedules/models.py,sha256=WS4b5ssh07ekiTe5_y4phLKGl7uTXHJBiF4W47vkGec,4357
|
|
36
|
-
django_qstash/schedules/services.py,sha256=
|
|
37
|
+
django_qstash/schedules/services.py,sha256=U3c2cZgdKGWgpmhRD0j0wT_T43w7K4pXm552M2sTr_4,2186
|
|
37
38
|
django_qstash/schedules/signals.py,sha256=g1aRAbZx-stnvD589mZagR6I27E56064fUyWsxKitR4,696
|
|
38
39
|
django_qstash/schedules/validators.py,sha256=i8IEjnRVk-iysmqvT_kbPYpxTKCQWoX9P1JHcL3zkhI,925
|
|
39
40
|
django_qstash/schedules/migrations/0001_initial.py,sha256=66cA8xnJV3h7QgzCaOiv-Nu3Xl9IdZQPgQKhxyW3bs4,4516
|
|
40
41
|
django_qstash/schedules/migrations/0002_taskschedule_updated_at.py,sha256=6hZO0a9P2ZpOROkk7O5UXBhahghU0QfxZl4E-c3HKGw,459
|
|
41
42
|
django_qstash/schedules/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
42
|
-
django_qstash-0.0.
|
|
43
|
-
django_qstash-0.0.
|
|
44
|
-
django_qstash-0.0.
|
|
45
|
-
django_qstash-0.0.
|
|
43
|
+
django_qstash-0.0.10.dist-info/METADATA,sha256=MBBDbS-vk7yxaIa4XKKj4GHAO2xWVNIzg3s63PKoggk,15412
|
|
44
|
+
django_qstash-0.0.10.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
|
45
|
+
django_qstash-0.0.10.dist-info/top_level.txt,sha256=AlV3WSK1A0ZvKuCLsINtIJhJW8zo7SEB-D3_RAjZ0hI,14
|
|
46
|
+
django_qstash-0.0.10.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|