From 306e6d038d4b5ae6210425bdb6b3991ea350fa93 Mon Sep 17 00:00:00 2001 From: Achille Roussel Date: Fri, 12 Apr 2024 16:50:04 -0700 Subject: [PATCH] README: updates to include dispatch CLI Signed-off-by: Achille Roussel --- README.md | 233 +++++++++++++++++++++++------------------------------- 1 file changed, 97 insertions(+), 136 deletions(-) diff --git a/README.md b/README.md index 3c76203a..5673c44b 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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 @@ -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 @@ -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