Skip to content
Closed
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
13 changes: 13 additions & 0 deletions examples/deployment/cloudflare/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.venv*/
.wrangler/
node_modules/
python_modules/
.runtime-cache/
__pycache__/
.dev.vars*
.env
output/
wrangler.*.jsonc
package-lock.json
pylock.toml
uv.lock
95 changes: 95 additions & 0 deletions examples/deployment/cloudflare/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->

# Deploy Burr to Cloudflare Workers

This example deploys the same counter application as the
[Vercel example](../vercel/) using Cloudflare's Python Workers runtime.
`POST /api/counter` runs the Burr application and returns its serialized State.

## Prerequisites

- Node.js 22 or later, Python 3.13 or later, and [uv](https://docs.astral.sh/uv/).
- A Cloudflare account for deployment. Local testing needs no account.

## Local setup

From this directory:

```bash
npm install --package-lock=false
uv run pywrangler dev
```

`pywrangler` installs the Python dependencies and starts the local Worker at
`http://localhost:8787`. Direct dependency versions are specified in
`package.json` and `pyproject.toml`; generated lock files are ignored by Git.

In another terminal, test the counter:

```bash
curl -X POST http://localhost:8787/api/counter \
-H 'Content-Type: application/json' \
-d '{"number": 5}'
```

Expected response:

```json
{"counter": 5, "counter_limit": 5, "__SEQUENCE_ID": 5, "__PRIOR_STEP": "result"}
```

Each request starts a new counter. Requests with a missing, invalid or
nonpositive `number` return HTTP 400; other HTTP methods return HTTP 405.

## Deploy

Log in to Cloudflare:

```bash
npx wrangler login
```

Choose a Worker name in `wrangler.jsonc`, then deploy:

```bash
uv run pywrangler deploy
```

Use the HTTPS URL printed by Wrangler to test the deployed Worker:

```bash
curl -X POST https://your-worker.your-subdomain.workers.dev/api/counter \
-H 'Content-Type: application/json' \
-d '{"number": 5}'
```

The response should match the local result above.

## Troubleshooting

- For HTTP 404, check that the request path is `/api/counter`.
- For HTTP 405, send a POST request with the JSON body shown above.
- To inspect deployed Worker logs, run `npx wrangler tail`.

## Resources

- [Cloudflare Python Workers](https://developers.cloudflare.com/workers/languages/python/)
- [Python package support](https://developers.cloudflare.com/workers/languages/python/packages/)
- [Wrangler commands](https://developers.cloudflare.com/workers/wrangler/commands/)
6 changes: 6 additions & 0 deletions examples/deployment/cloudflare/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"private": true,
"devDependencies": {
"wrangler": "4.151.2"
}
}
36 changes: 36 additions & 0 deletions examples/deployment/cloudflare/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

[project]
name = "burr-cloudflare-workers"
version = "0.1.0"
description = "Burr counter deployed to Cloudflare Python Workers"
requires-python = ">=3.13"
dependencies = ["apache-burr==0.43.0", "workers-runtime-sdk==1.9.5"]

[dependency-groups]
dev = ["workers-py==1.17.8"]

# Use PyPI for these pure-Python packages.
[[tool.uv.index]]
name = "pypi"
url = "https://pypi.org/simple"
explicit = true

[tool.uv.sources]
apache-burr = { index = "pypi" }
workers-runtime-sdk = { index = "pypi" }
16 changes: 16 additions & 0 deletions examples/deployment/cloudflare/src/app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
56 changes: 56 additions & 0 deletions examples/deployment/cloudflare/src/app/counter_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

"""
This is a very simple counting application.

It's here to help you get the mechanics of deploying a Burr application to Cloudflare Workers.
"""

import burr.core
from burr.core import Application, Result, State, default, expr
from burr.core.action import action
from burr.core.graph import GraphBuilder


@action(reads=["counter"], writes=["counter"])
def counter(state: State) -> State:
result = {"counter": state["counter"] + 1}
return state.update(**result)


# our graph.
graph = (
GraphBuilder()
.with_actions(counter=counter, result=Result("counter"))
.with_transitions(
("counter", "counter", expr("counter < counter_limit")),
("counter", "result", default),
)
.build()
)


def application(count_up_to: int = 10) -> Application:
"""Return a Burr Application that counts from zero to ``count_up_to``."""
return (
burr.core.ApplicationBuilder()
.with_graph(graph)
.with_state(**{"counter": 0, "counter_limit": count_up_to})
.with_entrypoint("counter")
.build()
)
50 changes: 50 additions & 0 deletions examples/deployment/cloudflare/src/entry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

"""HTTP entrypoint for the Burr counter example in Python Workers."""

import json
from urllib.parse import urlparse

from app.counter_app import application
from workers import Response, WorkerEntrypoint


def respond(data, status=200):
"""Return JSON ``data`` with HTTP ``status``."""
return Response(json.dumps(data), status=status, headers={"Content-Type": "application/json"})


class Default(WorkerEntrypoint):
async def fetch(self, request):
"""Run the counter for a POST ``request`` and return its serialized Burr State."""
if urlparse(request.url).path != "/api/counter":
return respond({"error": "Not found"}, 404)
if request.method != "POST":
return respond({"error": "Only POST method is allowed"}, 405)
try:
data = json.loads(await request.text())
if not isinstance(data, dict):
return respond({"error": "JSON object required"}, 400)
count_up_to = int(data.get("number", 0))
if count_up_to <= 0:
return respond({"error": "number must be greater than 0"}, 400)
except (TypeError, ValueError):
return respond({"error": "Invalid JSON or number"}, 400)
app = application(count_up_to)
_, _, state = app.run(halt_after=["result"])
return respond(state.serialize())
23 changes: 23 additions & 0 deletions examples/deployment/cloudflare/wrangler.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "burr-cloudflare-counter",
"main": "src/entry.py",
"compatibility_date": "2026-10-20",
"compatibility_flags": ["python_workers"]
}
Loading