Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions changes/vercel-django-tasks/initial.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add a Vercel Queues backend for Django Tasks and use it by default when no
task backends are configured.
21 changes: 21 additions & 0 deletions integrations/vercel-django-tasks/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Vercel, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
44 changes: 44 additions & 0 deletions integrations/vercel-django-tasks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# vercel-django-tasks

Django task backend backed by Vercel Queue Service. The installer uses
`VercelQueuesBackend` as Django's default task backend when no `TASKS`
backends are configured and registers generated queue subscribers.

Register push subscribers during application startup:

```python
from vercel.integrations.django import install_vercel_django_task_integration

install_vercel_django_task_integration()
```

No `TASKS` setting is required. Configure one explicitly to customize the
backend or to use a different backend:

```python
TASKS = {
"default": {
"BACKEND": "vercel.integrations.django.VercelQueuesBackend",
"QUEUES": ["default"],
"OPTIONS": {
"result_namespace": "django-task-results",
"result_ttl_seconds": 86400,
},
},
}
```

Declare the module that loads your Django application in `pyproject.toml` so
Vercel generates a queue subscriber function:

```toml
[[tool.vercel.subscribers]]
entrypoint = "my_project.wsgi"
```

No manual queue endpoint is required.

Task result state is stored in Vercel Runtime Cache. Results are cache-backed
and expire according to `result_ttl_seconds`; they are not durable storage.

This package depends on Django, `vercel-queue`, and `vercel-cache`.
34 changes: 34 additions & 0 deletions integrations/vercel-django-tasks/examples/chunks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Django tasks chunks app

Deploy this directory as a Vercel app:

```bash
cd integrations/vercel-django-tasks/examples/chunks
vc link
vc deploy
```

Queue the chunks by requesting the Django view on the deployed app:

```bash
vc curl /send_chunks/
```

Expected output includes:

```text
queued 100 add tasks
results: [0, 2, 4, 6
```

`pyproject.toml` declares the Django WSGI application as a queue subscriber:

```toml
[[tool.vercel.subscribers]]
entrypoint = "chunks_project.wsgi"
```

The Vercel build imports the Django application, introspects the generated
task subscriber registered during startup, and compiles it into a
queue-triggered function. No manual queue view or `vercel.json` trigger
configuration is needed.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Chunks example app."""
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from __future__ import annotations

from django.apps import AppConfig


class ChunksConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "chunks"
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from __future__ import annotations

from django.tasks import task


@task
def add(left: int, right: int) -> int:
return left + right
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from __future__ import annotations

from django.urls import path

from . import views

urlpatterns = [
path("send_chunks/", views.send_chunks, name="send_chunks"),
]
42 changes: 42 additions & 0 deletions integrations/vercel-django-tasks/examples/chunks/chunks/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from __future__ import annotations

import time
from itertools import starmap

from django.http import HttpRequest, HttpResponse
from django.tasks import TaskResultStatus

from .tasks import add

RESULT_TIMEOUT_SECONDS = 30


def send_chunks(request: HttpRequest) -> HttpResponse:
del request
pairs = zip(range(100), range(100), strict=False)
results = list(starmap(add.enqueue, pairs))
deadline = time.monotonic() + RESULT_TIMEOUT_SECONDS

while time.monotonic() < deadline:
for result in results:
if not result.is_finished:
result.refresh()
if all(result.is_finished for result in results):
break
time.sleep(0.25)

if not all(result.is_finished for result in results):
finished = sum(result.is_finished for result in results)
return HttpResponse(
f"queued {len(results)} add tasks\nfinished {finished}/{len(results)} before timeout\n",
content_type="text/plain",
status=504,
)

values = [
result.return_value for result in results if result.status == TaskResultStatus.SUCCESSFUL
]
return HttpResponse(
f"queued {len(results)} add tasks\nresults: {values}\n",
content_type="text/plain",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Django project for the vercel-django-tasks chunks example."""
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from __future__ import annotations

import os

from django.core.asgi import get_asgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "chunks_project.settings")

application = get_asgi_application()
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from __future__ import annotations

SECRET_KEY = "vercel-django-tasks-example-chunks" # noqa: S105
DEBUG = False
ALLOWED_HOSTS = ["*"]

INSTALLED_APPS = ["chunks"]
MIDDLEWARE: list[str] = []
ROOT_URLCONF = "chunks_project.urls"
ASGI_APPLICATION = "chunks_project.asgi.application"
WSGI_APPLICATION = "chunks_project.wsgi.application"
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from __future__ import annotations

from django.urls import include, path

urlpatterns = [
path("", include("chunks.urls")),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from __future__ import annotations

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "chunks_project.settings")

application = get_wsgi_application()

from chunks import tasks # noqa: E402

from vercel.integrations.django import install_vercel_django_task_integration # noqa: E402

_ = tasks
install_vercel_django_task_integration()
16 changes: 16 additions & 0 deletions integrations/vercel-django-tasks/examples/chunks/manage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env python
from __future__ import annotations

import os
import sys


def main() -> None:
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "chunks_project.settings")
from django.core.management import execute_from_command_line # noqa: PLC0415

execute_from_command_line(sys.argv)


if __name__ == "__main__":
main()
19 changes: 19 additions & 0 deletions integrations/vercel-django-tasks/examples/chunks/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[project]
name = "vercel-django-tasks-example-chunks"
version = "0.0.0"
requires-python = ">=3.12"
dependencies = [
"django",
"vercel-cache",
"vercel-django-tasks",
]

# Compiles the Django WSGI application into a queue-triggered function. With
# no "topics" filter, the subscriber consumes every task queue registered
# while Django starts up.
[[tool.vercel.subscribers]]
entrypoint = "chunks_project.wsgi"

[tool.uv.sources]
vercel-cache = { path = "../../../../src/vercel-cache", editable = true }
vercel-django-tasks = { path = "../..", editable = true }
75 changes: 75 additions & 0 deletions integrations/vercel-django-tasks/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
[build-system]
requires = ["hatchling>=1.27.0,<2"]
build-backend = "hatchling.build"

[project]
name = "vercel-django-tasks"
dynamic = ["version"]
description = "Django task backend backed by Vercel Queue Service"
readme = "README.md"
requires-python = ">=3.10"
license = "MIT"
license-files = ["LICENSE", "LICENSE.*"]
dependencies = [
"Django>=6.0; python_version >= '3.12'",
"vercel-cache",
"vercel-queue>=0.6.0",
]

[tool.uv.sources]
vercel-cache = { workspace = true }
vercel-queue = { workspace = true }

[tool.hatch.version]
path = "vercel/integrations/django/version.py"

[tool.hatch.build.targets.sdist]
include = [
"/vercel/integrations/django/**/*.py",
"/vercel/integrations/django/py.typed",
"/README.md",
"/pyproject.toml",
"/LICENSE",
]
exclude = [
"/**/__pycache__",
]

[tool.hatch.build.targets.wheel]
dev-mode-dirs = ["."]
only-include = [
"/vercel/integrations/django",
]
exclude = [
"/**/__pycache__",
]

[tool.ruff]
extend = "../ruff.toml"

[tool.ty.environment]
python-version = "3.12"

[tool.ty.rules]
unresolved-attribute = "ignore"

[tool.pytest.ini_options]
addopts = "--no-header --capture=tee-sys"
asyncio_mode = "auto"
testpaths = ["tests"]

[tool.poe]
include = "../../scripts/poe/poe.toml"
verbosity = -1

[tool.poe.tasks.test]
shell = """
if python -c 'import sys; raise SystemExit(sys.version_info < (3, 12))'; then
$PYTEST
else
echo "Skipping vercel-django-tasks tests: Django 6 requires Python 3.12+"
fi
"""

[tool.poe.tasks.typecheck-mypy]
cmd = "$MYPY --python-version 3.12"
1 change: 1 addition & 0 deletions integrations/vercel-django-tasks/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

5 changes: 5 additions & 0 deletions integrations/vercel-django-tasks/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from __future__ import annotations

import os

os.environ["DJANGO_SETTINGS_MODULE"] = "tests.settings"
13 changes: 13 additions & 0 deletions integrations/vercel-django-tasks/tests/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
SECRET_KEY = "vercel-django-tasks-tests"
USE_TZ = True
INSTALLED_APPS: list[str] = []
TASKS = {
"default": {
"BACKEND": "vercel.integrations.django.VercelQueuesBackend",
"QUEUES": ["default"],
"OPTIONS": {
"result_namespace": "django.results",
"result_ttl_seconds": 120,
},
}
}
1 change: 1 addition & 0 deletions integrations/vercel-django-tasks/tests/unit/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Loading