Skip to content

Commit bfdd64b

Browse files
Merge pull request #154 from stealthrocket/dispatch-without-fastapi
Dispatch without FastAPI
2 parents bccc800 + 54ae0f5 commit bfdd64b

File tree

11 files changed

+715
-321
lines changed

11 files changed

+715
-321
lines changed

.dockerignore

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Dockerfile
2+
__pycache__
3+
*.md
4+
*.yaml
5+
*.yml
6+
dist/*

Dockerfile

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
FROM python:3.12
2+
WORKDIR /usr/src/dispatch-py
3+
4+
COPY pyproject.toml .
5+
RUN python -m pip install -e .[dev]
6+
7+
COPY . .
8+
RUN python -m pip install -e .[dev]
9+
10+
ENTRYPOINT ["python"]

README.md

+103-137
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,19 @@
1313
Python package to develop applications with the Dispatch platform.
1414

1515
[fastapi]: https://fastapi.tiangolo.com/tutorial/first-steps/
16-
[ngrok]: https://ngrok.com/
1716
[pypi]: https://pypi.org/project/dispatch-py/
1817
[signup]: https://console.dispatch.run/
1918

2019
- [What is Dispatch?](#what-is-dispatch)
2120
- [Installation](#installation)
21+
- [Installing the Dispatch CLI](#installing-the-dispatch-cli)
22+
- [Installing the Dispatch SDK](#installing-the-dispatch-sdk)
2223
- [Usage](#usage)
23-
- [Configuration](#configuration)
24+
- [Writing Dispatch Applications](#writing-dispatch-applications)
25+
- [Running Dispatch Applications](#running-dispatch-applications)
26+
- [Writing Transactional Applications with Dispatch](#writing-transactional-applications-with-dispatch)
2427
- [Integration with FastAPI](#integration-with-fastapi)
25-
- [Local Testing](#local-testing)
26-
- [Distributed Coroutines for Python](#distributed-coroutines-for-python)
28+
- [Configuration](#configuration)
2729
- [Serialization](#serialization)
2830
- [Examples](#examples)
2931
- [Contributing](#contributing)
@@ -32,166 +34,75 @@ Python package to develop applications with the Dispatch platform.
3234

3335
Dispatch is a platform for developing scalable & reliable distributed systems.
3436

35-
Dispatch provides a simple programming model based on *Distributed Coroutines*,
36-
allowing complex, dynamic workflows to be expressed with regular code and
37-
control flow.
38-
39-
Dispatch schedules function calls across a fleet of service instances,
40-
incorporating **fair scheduling**, transparent **retry of failed operations**,
41-
and **durability**.
42-
4337
To get started, follow the instructions to [sign up for Dispatch][signup] 🚀.
4438

4539
## Installation
4640

47-
This package is published on [PyPI][pypi] as **dispatch-py**, to install:
48-
```sh
49-
pip install dispatch-py
50-
```
51-
52-
## Usage
41+
### Installing the Dispatch CLI
5342

54-
The SDK allows Python applications to declare functions that Dispatch can
55-
orchestrate:
43+
As a pre-requisite, we recommend installing the Dispatch CLI to simplify the
44+
configuration and execution of applications that use Dispatch. On macOS, this
45+
can be done easily using [Homebrew](https://docs.brew.sh/):
5646

57-
```python
58-
@dispatch.function
59-
def action(msg):
60-
...
47+
```console
48+
brew tap stealthrocket/dispatch
49+
brew install dispatch
6150
```
6251

63-
The **@dispatch.function** decorator declares a function that can be run by
64-
Dispatch. The call has durable execution semantics; if the function fails
65-
with a temporary error, it is automatically retried, even if the program is
66-
restarted, or if multiple instances are deployed.
52+
Alternatively, you can download the latest `dispatch` binary from the
53+
[Releases](https://github.com/stealthrocket/dispatch/releases) page.
6754

68-
The SDK adds a method to the `action` object, allowing the program to
69-
dispatch an asynchronous invocation of the function; for example:
70-
71-
```python
72-
action.dispatch('hello')
73-
```
55+
*Note that this step is optional, applications that use Dispatch can run without
56+
the CLI, passing configuration through environment variables or directly in the
57+
code. However, the CLI automates the onboarding flow and simplifies the
58+
configuration, so we recommend starting with it.*
7459

75-
### Configuration
60+
### Installing the Dispatch SDK
7661

77-
In order for Dispatch to interact with functions remotely, the SDK needs to be
78-
configured with the address at which the server can be reached. The Dispatch
79-
API Key must also be set, and optionally, a public signing key should be
80-
configured to verify that requests originated from Dispatch. These
81-
configuration options can be passed as arguments to the
82-
the `Dispatch` constructor, but by default they will be loaded from environment
83-
variables:
62+
The Python package is published on [PyPI][pypi] as **dispatch-py**, to install:
63+
```console
64+
pip install dispatch-py
65+
```
8466

85-
| Environment Variable | Value Example |
86-
| :-------------------------- | :--------------------------------- |
87-
| `DISPATCH_API_KEY` | `d4caSl21a5wdx5AxMjdaMeWehaIyXVnN` |
88-
| `DISPATCH_ENDPOINT_URL` | `https://service.domain.com` |
89-
| `DISPATCH_VERIFICATION_KEY` | `-----BEGIN PUBLIC KEY-----...` |
67+
## Usage
9068

91-
Finally, the `Dispatch` instance needs to mount a route on a HTTP server in to
92-
receive requests from Dispatch. At this time, the SDK integrates with
93-
FastAPI; adapters for other popular Python frameworks will be added in the
94-
future.
69+
### Writing Dispatch Applications
9570

96-
### Integration with FastAPI
71+
The following snippet shows how to write a very simple Dispatch application
72+
that does the following:
9773

98-
The following code snippet is a complete example showing how to install a
99-
`Dispatch` instance on a [FastAPI][fastapi] server:
74+
1. declare a dispatch function named `greet` which can run asynchronously
75+
2. schedule a call to `greet` with the argument `World`
76+
3. run until all dispatched calls have completed
10077

10178
```python
102-
from fastapi import FastAPI
103-
from dispatch.fastapi import Dispatch
104-
import requests
105-
106-
app = FastAPI()
107-
dispatch = Dispatch(app)
79+
# main.py
80+
import dispatch
10881

10982
@dispatch.function
110-
def publish(url, payload):
111-
r = requests.post(url, data=payload)
112-
r.raise_for_status()
83+
def greet(msg: str):
84+
print(f"Hello, ${msg}!")
11385

114-
@app.get('/')
115-
def root():
116-
publish.dispatch('https://httpstat.us/200', {'hello': 'world'})
117-
return {'answer': 42}
86+
dispatch.run(lambda: greet.dispatch('World'))
11887
```
11988

120-
In this example, GET requests on the HTTP server dispatch calls to the
121-
`publish` function. The function runs concurrently to the rest of the
122-
program, driven by the Dispatch SDK.
123-
124-
The instantiation of the `Dispatch` object on the `FastAPI` application
125-
automatically installs the HTTP route needed for Dispatch to invoke functions.
89+
Obviously, this is just an example, a real application would perform much more
90+
interesting work, but it's a good start to get a sense of how to use Dispatch.
12691

127-
### Local Testing
128-
129-
#### Mock Dispatch
130-
131-
The SDK ships with a mock Dispatch server. It can be used to quickly test your
132-
local functions, without requiring internet access.
133-
134-
Note that the mock Dispatch server has very limited scheduling capabilities.
92+
### Running Dispatch Applications
13593

94+
The simplest way to run a Dispatch application is to use the Dispatch CLI, first
95+
we need to login:
13696
```console
137-
python -m dispatch.test $DISPATCH_ENDPOINT_URL
97+
dispatch login
13898
```
13999

140-
The command will start a mock Dispatch server and print the configuration
141-
for the SDK.
142-
143-
For example, if your functions were exposed through a local endpoint
144-
listening on `http://127.0.0.1:8000`, you could run:
145-
100+
Then we are ready to run the example program we wrote above:
146101
```console
147-
$ python -m dispatch.test http://127.0.0.1:8000
148-
Spawned a mock Dispatch server on 127.0.0.1:4450
149-
150-
Dispatching function calls to the endpoint at http://127.0.0.1:8000
151-
152-
The Dispatch SDK can be configured with:
153-
154-
export DISPATCH_API_URL="http://127.0.0.1:4450"
155-
export DISPATCH_API_KEY="test"
156-
export DISPATCH_ENDPOINT_URL="http://127.0.0.1:8000"
157-
export DISPATCH_VERIFICATION_KEY="Z+nTe2VRcw8t8Ihx++D+nXtbO28nwjWIOTLRgzrelYs="
158-
```
159-
160-
#### Real Dispatch
161-
162-
To test local functions with the production instance of Dispatch, it needs
163-
to be able to access your local endpoint.
164-
165-
A common approach consists of using [ngrok][ngrok] to setup a public endpoint
166-
that forwards to the server running on localhost.
167-
168-
For example, assuming the server is running on port 8000 (which is the default
169-
with FastAPI), the command to create a ngrok tunnel is:
170-
```sh
171-
ngrok http http://localhost:8000
102+
dispatch run -- python3 main.py
172103
```
173-
Running this command opens a terminal interface that looks like this:
174-
```
175-
ngrok
176104

177-
Build better APIs with ngrok. Early access: ngrok.com/early-access
178-
179-
Session Status online
180-
Account Alice (Plan: Free)
181-
Version 3.6.0
182-
Region United States (California) (us-cal-1)
183-
Latency -
184-
Web Interface http://127.0.0.1:4040
185-
Forwarding https://f441-2600-1700-2802-e01f-6861-dbc9-d551-ecfb.ngrok-free.app -> http://localhost:8000
186-
```
187-
To configure the Dispatch SDK, set the endpoint URL to the endpoint for the
188-
**Forwarding** parameter; each ngrok instance is unique, so you would have a
189-
different value, but in this example it would be:
190-
```sh
191-
export DISPATCH_ENDPOINT_URL="https://f441-2600-1700-2802-e01f-6861-dbc9-d551-ecfb.ngrok-free.app"
192-
```
193-
194-
### Distributed Coroutines for Python
105+
### Writing Transactional Applications with Dispatch
195106

196107
The `@dispatch.function` decorator can also be applied to Python coroutines
197108
(a.k.a. *async* functions), in which case each `await` point becomes a
@@ -243,11 +154,67 @@ async def transform(msg):
243154
```
244155

245156
Dispatch converts Python coroutines to *Distributed Coroutines*, which can be
246-
suspended and resumed on any instance of a service across a fleet.
157+
suspended and resumed on any instance of a service across a fleet. For a deep
158+
dive on these concepts, read our blog post on
159+
[*Distributed Coroutines with a Native Python Extension and Dispatch*](https://stealthrocket.tech/blog/distributed-coroutines-in-python).
160+
161+
### Integration with FastAPI
162+
163+
Many web applications written in Python are developed using [FastAPI][fastapi].
164+
Dispatch can integrate with these applications by instantiating a
165+
`dispatch.fastapi.Dispatch` object. When doing so, the Dispatch functions
166+
declared by the program can be invoked remotely over the same HTTP interface
167+
used for the [FastAPI][fastapi] handlers.
168+
169+
The following code snippet is a complete example showing how to install a
170+
`Dispatch` instance on a [FastAPI][fastapi] server:
171+
172+
```python
173+
from fastapi import FastAPI
174+
from dispatch.fastapi import Dispatch
175+
import requests
176+
177+
app = FastAPI()
178+
dispatch = Dispatch(app)
179+
180+
@dispatch.function
181+
def publish(url, payload):
182+
r = requests.post(url, data=payload)
183+
r.raise_for_status()
184+
185+
@app.get('/')
186+
def root():
187+
publish.dispatch('https://httpstat.us/200', {'hello': 'world'})
188+
return {'answer': 42}
189+
```
190+
191+
In this example, GET requests on the HTTP server dispatch calls to the
192+
`publish` function. The function runs concurrently to the rest of the
193+
program, driven by the Dispatch SDK.
194+
195+
### Configuration
196+
197+
The Dispatch CLI automatically configures the SDK, so manual configuration is
198+
usually not required when running Dispatch applications. However, in some
199+
advanced cases, it might be useful to explicitly set configuration options.
200+
201+
In order for Dispatch to interact with functions remotely, the SDK needs to be
202+
configured with the address at which the server can be reached. The Dispatch
203+
API Key must also be set, and optionally, a public signing key should be
204+
configured to verify that requests originated from Dispatch. These
205+
configuration options can be passed as arguments to the
206+
the `Dispatch` constructor, but by default they will be loaded from environment
207+
variables:
208+
209+
| Environment Variable | Value Example |
210+
| :-------------------------- | :--------------------------------- |
211+
| `DISPATCH_API_KEY` | `d4caSl21a5wdx5AxMjdaMeWehaIyXVnN` |
212+
| `DISPATCH_ENDPOINT_URL` | `https://service.domain.com` |
213+
| `DISPATCH_VERIFICATION_KEY` | `-----BEGIN PUBLIC KEY-----...` |
247214

248215
### Serialization
249216

250-
Dispatch uses the [pickle] library to serialize coroutines.
217+
Dispatch uses the [pickle][pickle] library to serialize coroutines.
251218

252219
[pickle]: https://docs.python.org/3/library/pickle.html
253220

@@ -266,7 +233,6 @@ For help with a serialization issues, please submit a [GitHub issue][issues].
266233

267234
[issues]: https://github.com/stealthrocket/dispatch-py/issues
268235

269-
270236
## Examples
271237

272238
Check out the [examples](examples/) directory for code samples to help you get

0 commit comments

Comments
 (0)