Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ long-running applications which run close to your users. For more info see
You can define a Durable Object in Python in a similar way to JavaScript:

```python
from workers import DurableObject, Response, handler
from workers import DurableObject, Response, WorkerEntrypoint

from urllib.parse import urlparse

Expand All @@ -27,17 +27,17 @@ class MyDurableObject(DurableObject):
self.ctx = ctx
self.env = env

def on_fetch(self, request):
def fetch(self, request):
result = self.ctx.storage.sql.exec("SELECT 'Hello, World!' as greeting").one()
return Response(result.greeting)

@handler
async def on_fetch(request, env, ctx):
url = urlparse(request.url)
id = env.MY_DURABLE_OBJECT.idFromName(url.path)
stub = env.MY_DURABLE_OBJECT.get(id)
greeting = await stub.fetch(request.url)
return greeting
class Default(WorkerEntrypoint):
async def fetch(self, request, env, ctx):
url = urlparse(request.url)
id = env.MY_DURABLE_OBJECT.idFromName(url.path)
stub = env.MY_DURABLE_OBJECT.get(id)
greeting = await stub.fetch(request.url)
return greeting
```

Define the Durable Object in your Wrangler configuration file:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
title: Python Workers handlers now live in an entrypoint class
description: We are changing how Python Workers are structured by default.
products:
- workers
date: 2025-08-14
---

import { WranglerConfig } from "~/components";

We are changing how Python Workers are structured by default. Previously, handlers were defined at the top-level of a module as `on_fetch`, `on_scheduled`, etc. methods, but now they live in an entrypoint class.

Here's an example of how to now define a Worker with a fetch handler:

```python
from workers import Response, WorkerEntrypoint

class Default(WorkerEntrypoint):
async def fetch(self, request, env, ctx):
return Response("Hello World!")
```

To keep using the old-style handlers, you can specify the `disable_python_no_global_handlers` compatibility flag in your wrangler file:

<WranglerConfig>

```toml
compatibility_flags = [ "disable_python_no_global_handlers" ]
```

</WranglerConfig>

Consult the [Python Workers documentation](/workers/languages/python/) for more details.
15 changes: 8 additions & 7 deletions src/content/docs/d1/examples/query-d1-from-python-workers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,16 @@ The value of `binding` is how you will refer to your database from within your W
To create a Python Worker, create an empty file at `src/entry.py`, matching the value of `main` in your Wrangler file with the contents below:

```python
from workers import Response
from workers import Response, WorkerEntrypoint

async def on_fetch(request, env):
# Do anything else you'd like on request here!
class Default(WorkerEntrypoint):
async def fetch(self, request, env):
# Do anything else you'd like on request here!

# Query D1 - we'll list all tables in our database in this example
results = await env.DB.prepare("PRAGMA table_list").run()
# Return a JSON response
return Response.json(results)
# Query D1 - we'll list all tables in our database in this example
results = await env.DB.prepare("PRAGMA table_list").run()
# Return a JSON response
return Response.json(results)

```

Expand Down
30 changes: 15 additions & 15 deletions src/content/docs/durable-objects/get-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -185,16 +185,16 @@ export default {
<TabItem label="Python" icon="seti:python">

```python
from workers import handler, Response
from workers import handler, Response, WorkerEntrypoint
from urllib.parse import urlparse

@handler
async def on_fetch(request, env, ctx):
url = urlparse(request.url)
id = env.MY_DURABLE_OBJECT.idFromName(url.path)
stub = env.MY_DURABLE_OBJECT.get(id)
greeting = await stub.say_hello()
return Response(greeting)
class Default(WorkerEntrypoint):
async def fetch(request, env, ctx):
url = urlparse(request.url)
id = env.MY_DURABLE_OBJECT.idFromName(url.path)
stub = env.MY_DURABLE_OBJECT.get(id)
greeting = await stub.say_hello()
return Response(greeting)
```

</TabItem>
Expand Down Expand Up @@ -328,13 +328,13 @@ class MyDurableObject(DurableObject):

return result.greeting

@handler
async def on_fetch(request, env, ctx):
url = urlparse(request.url)
id = env.MY_DURABLE_OBJECT.idFromName(url.path)
stub = env.MY_DURABLE_OBJECT.get(id)
greeting = await stub.say_hello()
return Response(greeting)
class Default(WorkerEntrypoint):
async def fetch(self, request, env, ctx):
url = urlparse(request.url)
id = env.MY_DURABLE_OBJECT.idFromName(url.path)
stub = env.MY_DURABLE_OBJECT.get(id)
greeting = await stub.say_hello()
return Response(greeting)
```

</TabItem>
Expand Down
16 changes: 9 additions & 7 deletions src/content/docs/workers/examples/103-early-hints.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export default {

```py
import re
from workers import Response
from workers import Response, WorkerEntrypoint

CSS = "body { color: red; }"
HTML = """
Expand All @@ -132,12 +132,14 @@ HTML = """
</body>
</html>
"""
def on_fetch(request):
if re.search("test.css", request.url):
headers = {"content-type": "text/css"}
return Response(CSS, headers=headers)
else:
headers = {"content-type": "text/html","link": "</test.css>; rel=preload; as=style"}

class Default(WorkerEntrypoint):
async def fetch(self, request):
if re.search("test.css", request.url):
headers = {"content-type": "text/css"}
return Response(CSS, headers=headers)
else:
headers = {"content-type": "text/html","link": "</test.css>; rel=preload; as=style"}
return Response(HTML, headers=headers)
```

Expand Down
65 changes: 33 additions & 32 deletions src/content/docs/workers/examples/ab-testing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -100,41 +100,42 @@ export default {
```py
import random
from urllib.parse import urlparse, urlunparse
from workers import Response, fetch
from workers import Response, fetch, WorkerEntrypoint

NAME = "myExampleWorkersABTest"

async def on_fetch(request):
url = urlparse(request.url)
# Uncomment below when testing locally
# url = url._replace(netloc="example.com") if "localhost" in url.netloc else url

# Enable Passthrough to allow direct access to control and test routes.
if url.path.startswith("/control") or url.path.startswith("/test"):
return fetch(urlunparse(url))

# Determine which group this requester is in.
cookie = request.headers.get("cookie")

if cookie and f'{NAME}=control' in cookie:
url = url._replace(path="/control" + url.path)
elif cookie and f'{NAME}=test' in cookie:
url = url._replace(path="/test" + url.path)
else:
# If there is no cookie, this is a new client. Choose a group and set the cookie.
group = "test" if random.random() < 0.5 else "control"
if group == "control":
url = url._replace(path="/control" + url.path)
else:
url = url._replace(path="/test" + url.path)

# Reconstruct response to avoid immutability
res = await fetch(urlunparse(url))
headers = dict(res.headers)
headers["Set-Cookie"] = f'{NAME}={group}; path=/'
return Response(res.body, headers=headers)

return fetch(urlunparse(url))
class Default(WorkerEntrypoint):
async def fetch(self, request):
url = urlparse(request.url)
# Uncomment below when testing locally
# url = url._replace(netloc="example.com") if "localhost" in url.netloc else url

# Enable Passthrough to allow direct access to control and test routes.
if url.path.startswith("/control") or url.path.startswith("/test"):
return fetch(urlunparse(url))

# Determine which group this requester is in.
cookie = request.headers.get("cookie")

if cookie and f'{NAME}=control' in cookie:
url = url._replace(path="/control" + url.path)
elif cookie and f'{NAME}=test' in cookie:
url = url._replace(path="/test" + url.path)
else:
# If there is no cookie, this is a new client. Choose a group and set the cookie.
group = "test" if random.random() < 0.5 else "control"
if group == "control":
url = url._replace(path="/control" + url.path)
else:
url = url._replace(path="/test" + url.path)

# Reconstruct response to avoid immutability
res = await fetch(urlunparse(url))
headers = dict(res.headers)
headers["Set-Cookie"] = f'{NAME}={group}; path=/'
return Response(res.body, headers=headers)

return fetch(urlunparse(url))
```

</TabItem> <TabItem label="Hono" icon="seti:typescript">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,15 @@ export default app;

```py
import json
from workers import Response
from workers import Response, WorkerEntrypoint
from js import JSON

def on_fetch(request):
error = json.dumps({ "error": "The `cf` object is not available inside the preview." })
data = request.cf if request.cf is not None else error
headers = {"content-type":"application/json"}
return Response(JSON.stringify(data, None, 2), headers=headers)
class Default(WorkerEntrypoint):
async def fetch(self, request):
error = json.dumps({ "error": "The `cf` object is not available inside the preview." })
data = request.cf if request.cf is not None else error
headers = {"content-type":"application/json"}
return Response(JSON.stringify(data, None, 2), headers=headers)
```

</TabItem> </Tabs>
21 changes: 11 additions & 10 deletions src/content/docs/workers/examples/aggregate-requests.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -93,21 +93,22 @@ export default app;
</TabItem> <TabItem label="Python" icon="seti:python">

```py
from workers import Response, fetch
from workers import Response, fetch, WorkerEntrypoint
import asyncio
import json

async def on_fetch(request):
# some_host is set up to return JSON responses
some_host = "https://jsonplaceholder.typicode.com"
url1 = some_host + "/todos/1"
url2 = some_host + "/todos/2"
class Default(WorkerEntrypoint):
async def fetch(self, request):
# some_host is set up to return JSON responses
some_host = "https://jsonplaceholder.typicode.com"
url1 = some_host + "/todos/1"
url2 = some_host + "/todos/2"

responses = await asyncio.gather(fetch(url1), fetch(url2))
results = await asyncio.gather(*(r.json() for r in responses))
responses = await asyncio.gather(fetch(url1), fetch(url2))
results = await asyncio.gather(*(r.json() for r in responses))

headers = {"content-type": "application/json;charset=UTF-8"}
return Response.json(results, headers=headers)
headers = {"content-type": "application/json;charset=UTF-8"}
return Response.json(results, headers=headers)
```

</TabItem> </Tabs>
31 changes: 16 additions & 15 deletions src/content/docs/workers/examples/alter-headers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -85,27 +85,28 @@ export default {
</TabItem> <TabItem label="Python" icon="seti:python">

```py
from workers import Response, fetch
from workers import Response, fetch, WorkerEntrypoint

async def on_fetch(request):
response = await fetch("https://example.com")
class Default(WorkerEntrypoint):
async def fetch(self, request):
response = await fetch("https://example.com")

# Grab the response headers so they can be modified
new_headers = response.headers
# Grab the response headers so they can be modified
new_headers = response.headers

# Add a custom header with a value
new_headers["x-workers-hello"] = "Hello from Cloudflare Workers"
# Add a custom header with a value
new_headers["x-workers-hello"] = "Hello from Cloudflare Workers"

# Delete headers
if "x-header-to-delete" in new_headers:
del new_headers["x-header-to-delete"]
if "x-header2-to-delete" in new_headers:
del new_headers["x-header2-to-delete"]
# Delete headers
if "x-header-to-delete" in new_headers:
del new_headers["x-header-to-delete"]
if "x-header2-to-delete" in new_headers:
del new_headers["x-header2-to-delete"]

# Adjust the value for an existing header
new_headers["x-header-to-change"] = "NewValue"
# Adjust the value for an existing header
new_headers["x-header-to-change"] = "NewValue"

return Response(response.body, headers=new_headers)
return Response(response.body, headers=new_headers)
```

</TabItem> <TabItem label="Hono" icon="seti:typescript">
Expand Down
21 changes: 11 additions & 10 deletions src/content/docs/workers/examples/auth-with-headers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -78,20 +78,21 @@ export default {
</TabItem> <TabItem label="Python" icon="seti:python">

```py
from workers import Response, fetch
from workers import WorkerEntrypoint, Response, fetch

async def on_fetch(request):
PRESHARED_AUTH_HEADER_KEY = "X-Custom-PSK"
PRESHARED_AUTH_HEADER_VALUE = "mypresharedkey"
class Default(WorkerEntrypoint):
async def fetch(self, request):
PRESHARED_AUTH_HEADER_KEY = "X-Custom-PSK"
PRESHARED_AUTH_HEADER_VALUE = "mypresharedkey"

psk = request.headers[PRESHARED_AUTH_HEADER_KEY]
psk = request.headers[PRESHARED_AUTH_HEADER_KEY]

if psk == PRESHARED_AUTH_HEADER_VALUE:
# Correct preshared header key supplied. Fetch request from origin.
return fetch(request)
if psk == PRESHARED_AUTH_HEADER_VALUE:
# Correct preshared header key supplied. Fetch request from origin.
return fetch(request)

# Incorrect key supplied. Reject the request.
return Response("Sorry, you have supplied an invalid key.", status=403)
# Incorrect key supplied. Reject the request.
return Response("Sorry, you have supplied an invalid key.", status=403)
```

</TabItem> <TabItem label="Hono" icon="seti:typescript">
Expand Down
15 changes: 8 additions & 7 deletions src/content/docs/workers/examples/block-on-tls.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,14 @@ export default app;
</TabItem> <TabItem label="Python" icon="seti:python">

```py
from workers import Response, fetch

async def on_fetch(request):
tls_version = request.cf.tlsVersion
if tls_version not in ("TLSv1.2", "TLSv1.3"):
return Response("Please use TLS version 1.2 or higher.", status=403)
return fetch(request)
from workers import WorkerEntrypoint, Response, fetch

class Default(WorkerEntrypoint):
async def fetch(self, request):
tls_version = request.cf.tlsVersion
if tls_version not in ("TLSv1.2", "TLSv1.3"):
return Response("Please use TLS version 1.2 or higher.", status=403)
return fetch(request)
```

</TabItem> </Tabs>
Loading
Loading