Skip to content

Commit 0978a99

Browse files
ryanking13dom96
andauthored
[Python] Document Django support in Python workers (#32984)
* Document Django support in Python workers * Remove prefix * Address comments * Address comments * Update src/content/docs/workers/languages/python/packages/django.mdx Co-authored-by: Dominik Picheta <dominik@cloudflare.com> * Use entrypoint * changelog --------- Co-authored-by: Dominik Picheta <dominik@cloudflare.com>
1 parent 198c7b5 commit 0978a99

2 files changed

Lines changed: 323 additions & 0 deletions

File tree

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
---
2+
title: Python Workers now support WSGI web frameworks like Django and Flask
3+
description: Python Workers now support WSGI web frameworks like Django and Flask.
4+
products:
5+
- workers
6+
date: 2026-09-02
7+
---
8+
9+
10+
Python web frameworks following the [Web Server Gateway Interface (WSGI)](https://peps.python.org/pep-3333/) or [Asynchronous Server Gateway Interface (ASGI)](https://asgi.readthedocs.io/) specification can now be used in Python Workers.
11+
12+
## Using web frameworks with Python Workers
13+
14+
Based on the web framework you are using, you can use either `wsgi` or `asgi` from the `workers` module.
15+
16+
### WSGI frameworks
17+
18+
For WSGI frameworks like Django or Flask:
19+
20+
```python
21+
from workers import wsgi
22+
23+
from django.core.wsgi import get_wsgi_application
24+
25+
app = get_wsgi_application()
26+
Default = wsgi.entrypoint(app)
27+
```
28+
29+
The `wsgi.entrypoint` is equivalent to creating a `WorkerEntrypoint` class and using the `wsgi.fetch` method. If you want more control over the `WorkerEntrypoint` class, you can do so:
30+
31+
```python
32+
from workers import wsgi, WorkerEntrypoint
33+
34+
class Default(WorkerEntrypoint):
35+
async def fetch(self, request):
36+
return await wsgi.fetch(app, request, self.env)
37+
```
38+
39+
### ASGI frameworks
40+
41+
For ASGI frameworks like FastAPI or Starlette:
42+
43+
```python
44+
from workers import asgi
45+
46+
from fastapi import FastAPI
47+
48+
app = FastAPI()
49+
Default = asgi.entrypoint(app)
50+
```
51+
52+
For more information about using individual web frameworks, refer to the [packages documentation in Python Workers](/workers/languages/python/packages/).
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
---
2+
pcx_content_type: reference
3+
title: Django
4+
description: Run Django on Python Workers
5+
head:
6+
- tag: title
7+
content: Django
8+
products:
9+
- workers
10+
---
11+
12+
import { Steps, WranglerConfig } from "~/components";
13+
14+
[Django](https://www.djangoproject.com/) is supported in Python Workers.
15+
16+
Django applications use protocols called the [Web Server Gateway Interface (WSGI)](https://peps.python.org/pep-3333/)
17+
or [Asynchronous Server Gateway Interface (ASGI)](https://asgi.readthedocs.io/en/latest/).
18+
19+
This means that Django never reads from or writes to a socket itself. A WSGI/ASGI application expects to be hooked up to a
20+
WSGI/ASGI server, such as [uvicorn](https://uvicorn.dev/).
21+
The WSGI/ASGI server handles all of the raw sockets on the application’s behalf.
22+
23+
Python Workers provide adaptors for both WSGI and ASGI,
24+
so you can choose any based on whether your Django application deploys to WSGI or ASGI.
25+
26+
## Quick start
27+
28+
To get started with Django in Python Workers, follow these steps:
29+
30+
<Steps>
31+
32+
1. Create a Django project using `pywrangler init`:
33+
34+
```bash
35+
uv run pywrangler init django-worker --template https://github.com/cloudflare/python-workers-examples/tree/main/django
36+
cd django-worker
37+
```
38+
39+
2. Run your worker locally:
40+
41+
```bash
42+
uv run pywrangler dev
43+
```
44+
45+
</Steps>
46+
47+
## Choose between ASGI and WSGI
48+
49+
Your Django application needs to be served using either ASGI or WSGI.
50+
While Python workers is optimized for ASGI, you can still use WSGI which is compatible with Django.
51+
52+
### Serve a WSGI application
53+
54+
Build the application object with `get_wsgi_application()` and pass it to `workers.wsgi.fetch`:
55+
56+
```python title="src/index.py"
57+
import os
58+
59+
from django.core.wsgi import get_wsgi_application
60+
from workers import wsgi
61+
62+
# your Django settings module
63+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings")
64+
65+
app = get_wsgi_application()
66+
67+
Default = wsgi.entrypoint(app)
68+
```
69+
70+
`wsgi.fetch` takes the application object, the incoming request, and the environment.
71+
It exposes your bindings to the application through `scope["env"]`.
72+
73+
### Serve an ASGI application
74+
75+
Build the application object with `get_asgi_application()` and pass it to `workers.asgi.fetch`:
76+
77+
```python title="src/index.py"
78+
import os
79+
80+
from django.core.asgi import get_asgi_application
81+
from workers import asgi
82+
83+
# your Django settings module
84+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings")
85+
86+
app = get_asgi_application()
87+
88+
Default = asgi.entrypoint(app)
89+
```
90+
91+
`asgi.fetch` takes the application object, the incoming request, and the environment.
92+
It exposes your bindings to the application through `scope["env"]`.
93+
94+
## Configure Django settings
95+
96+
### Pass secrets
97+
98+
If you need a secret (like `SECRET_KEY`) in your Django settings, you can read it from a [Worker secret](/workers/configuration/secrets/):
99+
100+
```python title="src/app/settings.py"
101+
from workers import env
102+
103+
SECRET_KEY = env.DJANGO_SECRET_KEY
104+
```
105+
106+
Create the secret with `uv run pywrangler secret put DJANGO_SECRET_KEY`.
107+
108+
## Use Cloudflare storage as Django backends
109+
110+
You can use Cloudflare [D1](/d1/) and [Durable Objects](/durable-objects/) as Django database backends.
111+
To use them, you need to install the [`django-cf`](https://github.com/cloudflare/workers-py/tree/main/packages/django-cf) package.
112+
113+
Add `django-cf` to your dependencies:
114+
115+
```toml
116+
[project]
117+
dependencies = [
118+
"django",
119+
"django-cf",
120+
]
121+
```
122+
123+
### Database backends
124+
125+
`django-cf` provides two SQLite-compatible backends using Cloudflare's D1 and Durable Objects.
126+
Both drive the synchronous Django ORM, so serve your application through the WSGI path when you use them.
127+
128+
:::caution[Transaction support]
129+
130+
The D1 and Durable Object backends do not support transactions.
131+
132+
```python
133+
from django.db import transaction
134+
135+
# This will be a no-op, and rollback will not work
136+
@transaction.atomic
137+
def my_view():
138+
# ...
139+
```
140+
141+
:::
142+
143+
#### D1 backend
144+
145+
To use D1 as a database backend, first setup your D1 database in Wrangler:
146+
147+
<WranglerConfig>
148+
149+
```jsonc
150+
{
151+
"d1_databases": [
152+
{
153+
"binding": "DB",
154+
"database_name": "my-database",
155+
"database_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
156+
}
157+
]
158+
}
159+
```
160+
161+
</WranglerConfig>
162+
163+
164+
165+
Then, configure the backend in your Django settings:
166+
167+
```python title="src/app/settings.py"
168+
DATABASES = {
169+
"default": {
170+
"ENGINE": "django_cf.db.backends.d1",
171+
# should match the binding name in your wrangler.jsonc
172+
"CLOUDFLARE_BINDING": "DB",
173+
}
174+
}
175+
```
176+
177+
You are all set. Your Django application now uses D1 as its database backend.
178+
179+
```python title="src/index.py"
180+
import os
181+
182+
from django.core.wsgi import get_wsgi_application
183+
from workers import WorkerEntrypoint, wsgi
184+
185+
# your Django settings module
186+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings")
187+
188+
application = get_wsgi_application()
189+
190+
191+
class Default(WorkerEntrypoint):
192+
async def fetch(self, request):
193+
return await wsgi.fetch(application, request, self.env)
194+
```
195+
196+
#### Durable Objects backend
197+
198+
To use Durable Objects as a database backend, first setup your Durable Objects binding in Wrangler:
199+
200+
<WranglerConfig>
201+
202+
```jsonc
203+
{
204+
"durable_objects": {
205+
"bindings": [
206+
{
207+
"name": "DO_STORAGE",
208+
"class_name": "DjangoDurableObject"
209+
}
210+
]
211+
},
212+
"migrations": [
213+
{
214+
"tag": "v1",
215+
"new_sqlite_classes": ["DjangoDurableObject"]
216+
}
217+
]
218+
}
219+
```
220+
221+
</WranglerConfig>
222+
223+
Then, configure the backend in your Django settings:
224+
225+
```python title="src/app/settings.py"
226+
DATABASES = {
227+
"default": {
228+
"ENGINE": "django_cf.db.backends.do",
229+
}
230+
}
231+
```
232+
233+
Then, update your Python worker as follows:
234+
235+
```python title="src/index.py"
236+
import os
237+
238+
from django.core.wsgi import get_wsgi_application
239+
from django_cf.db.backends.do.storage import set_storage
240+
from workers import WorkerEntrypoint, DurableObject, wsgi
241+
242+
# your Django settings module
243+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings")
244+
245+
application = get_wsgi_application()
246+
247+
248+
class DjangoDurableObject(DurableObject):
249+
def __init__(self, ctx, env):
250+
super().__init__(ctx, env)
251+
252+
# Tell Django to use the Durable Object storage
253+
set_storage(self.ctx.storage.sql)
254+
255+
async def fetch(self, request):
256+
return await wsgi.fetch(application, request, self.env)
257+
258+
259+
class Default(WorkerEntrypoint):
260+
async def fetch(self, request):
261+
id = self.env.DO_STORAGE.idFromName("my-do-backend")
262+
stub = self.env.DO_STORAGE.get(id)
263+
return await stub.fetch(request)
264+
```
265+
266+
## More examples
267+
268+
Clone the `cloudflare/python-workers-examples` repository and run Django examples:
269+
270+
- [django](https://github.com/cloudflare/python-workers-examples/tree/main/django)
271+
- [django with D1 backend](https://github.com/cloudflare/python-workers-examples/tree/main/django-todo-d1)

0 commit comments

Comments
 (0)