Skip to content

Quickstart

Define the State, an Action and a Reducer

Httperactor is designed to work with the Unidirectional Data Flow pattern implemented by the pydepot package. For detailed guide on how to set up pydepot, visit its quickstart.

Following example defines a simple state and an action that sets a list of books:

from collections.abc import Sequence
from typing import NamedTuple

import pydepot


class Book(NamedTuple):
    title: str
    author: str


class State(NamedTuple):
    books: tuple[Book, ...]


class SetBooksAction(pydepot.Action):
    def __init__(self, books: Sequence[Book]):
        self.books: Sequence[Book] = books


class SetBooksReducer(pydepot.Reducer[SetBooksAction, State]):
    @property
    def action_type(self) -> type[SetBooksAction]:
        return SetBooksAction

    def apply(self, action: SetBooksAction, state: State) -> State:
        return State(books=tuple(action.books))

Define a Request

A Request is an object describing an HTTP request. It is also used to transform the response text to a python object.

Following example defines a GET request that fetches a list of books:

import json

import httperactor

...

class GetBooksRequest(httperactor.Request[Sequence[Book]]):
    @property
    def path(self) -> str:
        return "/books"

    def map_response(self, response: str) -> Sequence[Book]:
        return json.loads(response, object_hook=lambda res: Book(**res))

Create an Interactor

An Interactor is an object that "interacts" with some HTTP service. Usually, it is positioned below the layer responsible for capturing the user input.

The HttpInteractor is designed as a Template Method, which makes creating subclasses easy and flexible.

Basic usage consists of implementing the request property, which returns the Request to send, and the actions method, which returns a list of pydepot.Action that will be dispatched to the store.

Following example defines an interactor that will send the GetBooksRequest, and dispatch a single SetBooksAction on successful response:

import httpx

import httperactor

...

class GetBooksInteractor(httperactor.HttpInteractor[httpx.Request, Sequence[Book], State]):
    @property
    def request(self) -> httperactor.Request[Sequence[Book]]:
        return GetBooksRequest()

    def actions(self, response: Sequence[Book]) -> Sequence[pydepot.Action]:
        return [SetBooksAction(books=response)]

Send the Request

Before sending the request, let's create a StoreSubscriber that will print any changes to the State - this will help in veryfing that the interactor is doing its job:

...

class StateSubscriber:
    def on_state(self, state: State) -> None:
        print(f"[StoreSubscriber] on_state called with {state}")

The endpoint of GetBooksRequest - GET /books - returns the following json document:

$ curl localhost:5000/books

[
  {
    "author": "Alice",
    "title": "foo"
  },
  {
    "author": "Bob",
    "title": "bar"
  },
  {
    "author": "Charlie",
    "title": "baz"
  }
]

Following example sets up the store, creates the interactor and sends a request by awaiting the execute coroutine:

import asyncio

...

async def main() -> None:
    store = pydepot.Store(initial_state=State(books=()))
    store.register(SetBooksReducer())

    subscriber = StateSubscriber()
    store.subscribe(subscriber)

    interactor = GetBooksInteractor(
        http_client=HttpClient(httpx.AsyncClient(base_url="http://localhost:5000")),
        store=store,
    )

    await interactor.execute()


if __name__ == "__main__":
    asyncio.run(main())

Running the script will result in the following output:

$ python3 main.py

[StoreSubscriber] on_state called with:
    State(
        books=(
            Book(title='foo', author='Alice'),
            Book(title='bar', author='Bob'),
            Book(title='baz', author='Charlie')
        )
    )

Next steps

To see more in-depth examples, see the Advanced Usage.

To see all available properties and methods, see the API Documentation.