Skip to content

Commit

Permalink
README: updates to include dispatch CLI
Browse files Browse the repository at this point in the history
Signed-off-by: Achille Roussel <[email protected]>
  • Loading branch information
achille-roussel committed Apr 12, 2024
1 parent 8729da0 commit 306e6d0
Showing 1 changed file with 97 additions and 136 deletions.
233 changes: 97 additions & 136 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,18 @@
Python package to develop applications with the Dispatch platform.

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

- [What is Dispatch?](#what-is-dispatch)
- [Installation](#installation)
- [Installing the Dispatch CLI](#installing-the-dispatch-cli)
- [Installing the Dispatch SDK](#installing-the-dispatch-sdk)
- [Usage](#usage)
- [Configuration](#configuration)
- [Integration with FastAPI](#integration-with-fastapi)
- [Local Testing](#local-testing)
- [Running Dispatch Applications](#running-dispatch-applications)
- [Distributed Coroutines for Python](#distributed-coroutines-for-python)
- [Integration with FastAPI](#integration-with-fastapi)
- [Serialization](#serialization)
- [Examples](#examples)
- [Contributing](#contributing)
Expand All @@ -32,166 +33,75 @@ Python package to develop applications with the Dispatch platform.

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

Dispatch provides a simple programming model based on *Distributed Coroutines*,
allowing complex, dynamic workflows to be expressed with regular code and
control flow.

Dispatch schedules function calls across a fleet of service instances,
incorporating **fair scheduling**, transparent **retry of failed operations**,
and **durability**.

To get started, follow the instructions to [sign up for Dispatch][signup] 🚀.

## Installation

This package is published on [PyPI][pypi] as **dispatch-py**, to install:
```sh
pip install dispatch-py
```

## Usage
### Installing the Dispatch CLI

The SDK allows Python applications to declare functions that Dispatch can
orchestrate:
As a pre-requisite, we recommend to install the Dispatch CLI to simplify the
configuration and execution of applications that use Dispatch. On OSX, this
can be done easily using the [Homebrew](https://docs.brew.sh/) tap:

```python
@dispatch.function
def action(msg):
...
```console
brew tap stealthrocket/dispatch
brew install dispatch
```

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

The SDK adds a method to the `action` object, allowing the program to
dispatch an asynchronous invocation of the function; for example:
*Note that this step is optional, applications that use Dispatch can run without
the CLI, passing configuration through environment variables or directly in the
code. However, the CLI automates the onboarding flow and simplifies the
configuration, so we recommend starting with it.*

```python
action.dispatch('hello')
```
### Installing the Dispatch SDK

### Configuration

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

| Environment Variable | Value Example |
| :-------------------------- | :--------------------------------- |
| `DISPATCH_API_KEY` | `d4caSl21a5wdx5AxMjdaMeWehaIyXVnN` |
| `DISPATCH_ENDPOINT_URL` | `https://service.domain.com` |
| `DISPATCH_VERIFICATION_KEY` | `-----BEGIN PUBLIC KEY-----...` |
## Usage

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

### Integration with FastAPI
The following snippet shows how to write a very simple Dispatch application
that does the following:

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

```python
from fastapi import FastAPI
from dispatch.fastapi import Dispatch
import requests

app = FastAPI()
dispatch = Dispatch(app)
# main.py
import dispatch

@dispatch.function
def publish(url, payload):
r = requests.post(url, data=payload)
r.raise_for_status()
def greet(msg: str):
print(f"Hello, ${msg}!")

@app.get('/')
def root():
publish.dispatch('https://httpstat.us/200', {'hello': 'world'})
return {'answer': 42}
greet.dispatch('World')
```

In this example, GET requests on the HTTP server dispatch calls to the
`publish` function. The function runs concurrently to the rest of the
program, driven by the Dispatch SDK.

The instantiation of the `Dispatch` object on the `FastAPI` application
automatically installs the HTTP route needed for Dispatch to invoke functions.

### Local Testing

#### Mock Dispatch

The SDK ships with a mock Dispatch server. It can be used to quickly test your
local functions, without requiring internet access.
Obviously, this is just an example, a real application would perform much more
interesting work, but it's a good start to get a sense of how to use Dispatch.

Note that the mock Dispatch server has very limited scheduling capabilities.
### Running Dispatch Applications

The simplest way to run a Dispatch application is to use the Dispatch CLI, first
we need to login:
```console
python -m dispatch.test $DISPATCH_ENDPOINT_URL
dispatch login
```

The command will start a mock Dispatch server and print the configuration
for the SDK.

For example, if your functions were exposed through a local endpoint
listening on `http://127.0.0.1:8000`, you could run:

Then we are ready to run the example program we wrote above:
```console
$ python -m dispatch.test http://127.0.0.1:8000
Spawned a mock Dispatch server on 127.0.0.1:4450

Dispatching function calls to the endpoint at http://127.0.0.1:8000

The Dispatch SDK can be configured with:

export DISPATCH_API_URL="http://127.0.0.1:4450"
export DISPATCH_API_KEY="test"
export DISPATCH_ENDPOINT_URL="http://127.0.0.1:8000"
export DISPATCH_VERIFICATION_KEY="Z+nTe2VRcw8t8Ihx++D+nXtbO28nwjWIOTLRgzrelYs="
```

#### Real Dispatch

To test local functions with the production instance of Dispatch, it needs
to be able to access your local endpoint.

A common approach consists of using [ngrok][ngrok] to setup a public endpoint
that forwards to the server running on localhost.

For example, assuming the server is running on port 8000 (which is the default
with FastAPI), the command to create a ngrok tunnel is:
```sh
ngrok http http://localhost:8000
```
Running this command opens a terminal interface that looks like this:
dispatch run -- main.py
```
ngrok

Build better APIs with ngrok. Early access: ngrok.com/early-access
Session Status online
Account Alice (Plan: Free)
Version 3.6.0
Region United States (California) (us-cal-1)
Latency -
Web Interface http://127.0.0.1:4040
Forwarding https://f441-2600-1700-2802-e01f-6861-dbc9-d551-ecfb.ngrok-free.app -> http://localhost:8000
```
To configure the Dispatch SDK, set the endpoint URL to the endpoint for the
**Forwarding** parameter; each ngrok instance is unique, so you would have a
different value, but in this example it would be:
```sh
export DISPATCH_ENDPOINT_URL="https://f441-2600-1700-2802-e01f-6861-dbc9-d551-ecfb.ngrok-free.app"
```

### Distributed Coroutines for Python
### Writing Transactional Applications with Dispatch

The `@dispatch.function` decorator can also be applied to Python coroutines
(a.k.a. *async* functions), in which case each `await` point becomes a
Expand Down Expand Up @@ -243,11 +153,63 @@ async def transform(msg):
```

Dispatch converts Python coroutines to *Distributed Coroutines*, which can be
suspended and resumed on any instance of a service across a fleet.
suspended and resumed on any instance of a service across a fleet. For a deep
dive on these concepts, read our blog post on
[*Distributed Coroutines with a Native Python Extension and Dispatch*](https://stealthrocket.tech/blog/distributed-coroutines-in-python).

### Integration with FastAPI

Many web applications written in Python are developed using [FastAPI][fastapi].
Dispatch can integrate with these applications by instantiating a
`dispatch.fastapi.Dispatch` object. When doing so, the Dispatch functions
declared by the program can be invoked remotely over the same HTTP interface
used for the [FastAPI][fastapi] handlers.

The following code snippet is a complete example showing how to install a
`Dispatch` instance on a [FastAPI][fastapi] server:

```python
from fastapi import FastAPI
from dispatch.fastapi import Dispatch
import requests

app = FastAPI()
dispatch = Dispatch(app)

@dispatch.function
def publish(url, payload):
r = requests.post(url, data=payload)
r.raise_for_status()

@app.get('/')
def root():
publish.dispatch('https://httpstat.us/200', {'hello': 'world'})
return {'answer': 42}
```

In this example, GET requests on the HTTP server dispatch calls to the
`publish` function. The function runs concurrently to the rest of the
program, driven by the Dispatch SDK.

### Configuration

In order for Dispatch to interact with functions remotely, the SDK needs to be
configured with the address at which the server can be reached. The Dispatch
API Key must also be set, and optionally, a public signing key should be
configured to verify that requests originated from Dispatch. These
configuration options can be passed as arguments to the
the `Dispatch` constructor, but by default they will be loaded from environment
variables:

| Environment Variable | Value Example |
| :-------------------------- | :--------------------------------- |
| `DISPATCH_API_KEY` | `d4caSl21a5wdx5AxMjdaMeWehaIyXVnN` |
| `DISPATCH_ENDPOINT_URL` | `https://service.domain.com` |
| `DISPATCH_VERIFICATION_KEY` | `-----BEGIN PUBLIC KEY-----...` |

### Serialization

Dispatch uses the [pickle] library to serialize coroutines.
Dispatch uses the [pickle][pickle] library to serialize coroutines.

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

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

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


## Examples

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

0 comments on commit 306e6d0

Please sign in to comment.