Metadata-Version: 2.1
Name: dispatch-py
Version: 0.6.0
Summary: Develop reliable distributed systems on the Dispatch platform.
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: grpcio >=1.60.0
Requires-Dist: protobuf >=4.24.0
Requires-Dist: types-protobuf >=4.24.0.20240129
Requires-Dist: grpc-stubs >=1.53.0.5
Requires-Dist: http-message-signatures >=0.4.4
Requires-Dist: tblib >=3.0.0
Requires-Dist: httpx >=0.27.0
Requires-Dist: typing-extensions >=4.10
Provides-Extra: dev
Requires-Dist: black >=24.1.0 ; extra == 'dev'
Requires-Dist: isort >=5.13.2 ; extra == 'dev'
Requires-Dist: mypy >=1.8.0 ; extra == 'dev'
Requires-Dist: pytest ==8.0.0 ; extra == 'dev'
Requires-Dist: fastapi >=0.109.0 ; extra == 'dev'
Requires-Dist: coverage >=7.4.1 ; extra == 'dev'
Requires-Dist: requests >=2.31.0 ; extra == 'dev'
Requires-Dist: types-requests >=2.31.0.20240125 ; extra == 'dev'
Requires-Dist: uvicorn >=0.28.0 ; extra == 'dev'
Requires-Dist: awslambdaric-stubs ; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs ==1.5.3 ; extra == 'docs'
Requires-Dist: mkdocstrings[python] ==0.24.0 ; extra == 'docs'
Requires-Dist: mkdocs-material ==9.5.9 ; extra == 'docs'
Requires-Dist: mkdocs-gen-files ==0.5.0 ; extra == 'docs'
Requires-Dist: mkdocs-literate-nav ==0.6.1 ; extra == 'docs'
Requires-Dist: mike ==2.0.0 ; extra == 'docs'
Provides-Extra: fastapi
Requires-Dist: fastapi ; extra == 'fastapi'
Requires-Dist: httpx ; extra == 'fastapi'
Provides-Extra: lambda
Requires-Dist: awslambdaric ; extra == 'lambda'

<p align="center">
<img src="https://github.com/stealthrocket/dispatch-proto/assets/865510/87162355-e184-4058-a733-650eee53f333" width="160"/>
</p>

# dispatch-py

[![Docs](https://github.com/stealthrocket/dispatch-py/actions/workflows/docs.yml/badge.svg?branch=)](https://github.com/stealthrocket/dispatch-py/actions/workflows/docs.yml)
[![PyPI](https://github.com/stealthrocket/dispatch-py/actions/workflows/pypi.yml/badge.svg?branch=)](https://github.com/stealthrocket/dispatch-py/actions/workflows/pypi.yml)
[![Test](https://github.com/stealthrocket/dispatch-py/actions/workflows/test.yml/badge.svg?branch=)](https://github.com/stealthrocket/dispatch-py/actions/workflows/test.yml)
[![PyPI version](https://badge.fury.io/py/dispatch-py.svg)](https://badge.fury.io/py/dispatch-py)
[![Reference](https://img.shields.io/badge/API-Reference-lightblue.svg)](https://python.dispatch.run/main/reference/dispatch/)

Python package to develop applications with the Dispatch platform.

[fastapi]: https://fastapi.tiangolo.com/tutorial/first-steps/
[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)
  - [Writing Dispatch Applications](#writing-dispatch-applications)
  - [Running Dispatch Applications](#running-dispatch-applications)
  - [Writing Transactional Applications with Dispatch](#writing-transactional-applications-with-dispatch)
  - [Integration with FastAPI](#integration-with-fastapi)
  - [Configuration](#configuration)
  - [Serialization](#serialization)
- [Examples](#examples)
- [Contributing](#contributing)

## What is Dispatch?

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

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

## Installation

### Installing the Dispatch CLI

As a pre-requisite, we recommend installing the Dispatch CLI to simplify the
configuration and execution of applications that use Dispatch. On macOS, this
can be done easily using [Homebrew](https://docs.brew.sh/):

```console
brew tap stealthrocket/dispatch
brew install dispatch
```

Alternatively, you can download the latest `dispatch` binary from the
[Releases](https://github.com/stealthrocket/dispatch/releases) page.

*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.*

### Installing the Dispatch SDK

The Python package is published on [PyPI][pypi] as **dispatch-py**, to install:
```console
pip install dispatch-py
```

## Usage

### Writing Dispatch Applications

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

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
# main.py
import dispatch

@dispatch.function
def greet(msg: str):
    print(f"Hello, ${msg}!")

dispatch.run(lambda: greet.dispatch('World'))
```

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.

### Running Dispatch Applications

The simplest way to run a Dispatch application is to use the Dispatch CLI, first
we need to login:
```console
dispatch login
```

Then we are ready to run the example program we wrote above:
```console
dispatch run -- python3 main.py
```

### 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
durability step in the execution. If the awaited operation fails, it is
automatically retried, and the parent function is paused until the result are
available or a permanent error is raised.

```python
@dispatch.function
async def pipeline(msg):
    # Each await point is a durability step, the functions can be run across the
    # fleet of service instances and retried as needed without losing track of
    # progress through the function execution.
    msg = await transform1(msg)
    msg = await transform2(msg)
    await publish(msg)

@dispatch.function
async def publish(msg):
    # Each dispatch function runs concurrently to the others, even if it does
    # blocking operations like this POST request, it does not prevent other
    # concurrent operations from carrying on in the program.
    r = requests.post("https://somewhere.com/", data=msg)
    r.raise_for_status()

@dispatch.function
async def transform1(msg):
    ...

@dispatch.function
async def transform2(msg):
    ...
```

This model is composable and can be used to create fan-out/fan-in control flows.
`gather` can be used to wait on multiple concurrent calls:

```python
from dispatch import gather

@dispatch.function
async def process(msgs):
    concurrent_calls = [transform(msg) for msg in msgs]
    return await gather(*concurrent_calls)

@dispatch.function
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. 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

The Dispatch CLI automatically configures the SDK, so manual configuration is
usually not required when running Dispatch applications. However, in some
advanced cases, it might be useful to explicitly set configuration options.

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][pickle] library to serialize coroutines.

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

Serialization of coroutines is enabled by a CPython extension.

The user must ensure that the contents of their stack frames are
serializable. That is, users should avoid using variables inside
coroutines that cannot be pickled.

If a pickle error is encountered, serialization tracing can be enabled
with the `DISPATCH_TRACE=1` environment variable to debug the issue. The
stacks of coroutines and generators will be printed to stdout before
the pickle library attempts serialization.

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
started with the SDK.

## Contributing

Contributions are always welcome! Would you spot a typo or anything that needs
to be improved, feel free to send a pull request.

Pull requests need to pass all CI checks before getting merged. Anything that
isn't a straightforward change would benefit from being discussed in an issue
before submitting a change.

Remember to be respectful and open minded!
