Skip to main content

Fast Actions

Fast Actions generates opinionated, class-based action routers for FastAPI.

It is intended for typed, action-oriented RPC APIs where every operation:

  • uses POST;
  • accepts exactly one Pydantic request object;
  • returns exactly one Pydantic response object;
  • has no path, query, header, or cookie parameters in its action signature; and
  • receives shared FastAPI dependencies through controller fields.

Fast Actions only generates endpoints. It does not replace the FastAPI application, install middleware, register global exception handlers, or change dependency injection and request validation behavior.

Installation

pip install fast-actions

Python 3.12 or newer is required.

Example

import typing as t

from fastapi import FastAPI
from pydantic import BaseModel

from fast_actions import Controller, Depends, Error, errors


class CreateUserRequest(BaseModel):
    email: str


class CreateUserResponse(BaseModel):
    user_id: str


class EmailAlreadyExists(Error):
    pass


async def provide_user_service() -> 'UserService':
    ...


class Users(Controller):
    users: t.Annotated['UserService', Depends(provide_user_service)]

    @errors(EmailAlreadyExists)
    async def create(self, request: CreateUserRequest) -> CreateUserResponse:
        if await self.users.email_exists(request.email):
            message = 'A user already exists with this email.'
            raise EmailAlreadyExists(message)

        user = await self.users.create(request.email)
        return CreateUserResponse(user_id=user.id)


app = FastAPI()
Users.mount_on(app, '/users')

This defines POST /users/create.

mount_on() is a convenience for regular FastAPI router inclusion. The following forms are equivalent:

Users.mount_on(app, '/users')
app.include_router(Users.to_router('/users'))

Both FastAPI and APIRouter instances can be mounting targets.

Actions

Every public asynchronous instance method declared on a controller is an action. Method names are converted from snake case to kebab case:

class Users(Controller):
    async def reset_password(
        self, request: ResetPasswordRequest
    ) -> ResetPasswordResponse:
        ...

This defines POST /users/reset-password when mounted on /users.

The method named exactly _ defines an action at the controller mount path itself:

class Users(Controller):
    async def _(self, request: ListUsersRequest) -> ListUsersResponse:
        ...

This defines POST /users. Other names beginning with _ are private and are not registered, so they can be used for controller helpers.

Action methods must:

  • be asynchronous instance methods;
  • accept exactly self and one required request argument; and
  • annotate the request and response with concrete Pydantic BaseModel subclasses or None.

Invalid controller definitions raise ControllerDefinitionError when converted to a router. Inherited controller actions are included. Action names that normalize to the same route are rejected rather than being registered ambiguously.

Dependencies

Controller dependencies use Annotated and Depends without a constructor:

class Users(Controller):
    users: t.Annotated[UserService, Depends(provide_user_service)]
    actor: t.Annotated[Actor, Depends(authenticate)]

Fast Actions creates one controller instance per request and assigns the resolved dependencies to its fields. Controllers cannot define __init__ or __new__, and action methods cannot declare additional dependencies.

All controller dependencies run for every action on that controller. Split a controller when its actions need materially different dependencies or authentication policies.

Depends is re-exported by fast_actions; it is FastAPI's normal dependency marker, so dependency overrides and dependency cleanup continue to work normally.

Empty Objects

Annotate a request or response as None to represent an empty JSON object:

class Cache(Controller):
    async def clear(self, request: None) -> None:
        ...

The client must send {}, the action receives None, the action returns None, and the HTTP response is {}. Extra request properties are rejected by FastAPI validation.

Errors

Raise Error from an action to return a JSON error response:

class PermissionDenied(Error):
    @classmethod
    def get_status(cls) -> int:
        return 403


raise PermissionDenied('You cannot perform this action.')

The response is:

{
  "code": "PERMISSION_DENIED",
  "message": "You cannot perform this action."
}

Error.get_code() derives the stable code from the class name. Renaming an error class is therefore an API-breaking change. Error.get_status() defaults to 400 and may return any status from 400 through 599 except 422, which remains reserved for FastAPI request and dependency validation.

Declare errors for OpenAPI with the metadata-only @errors decorator:

@errors(EmailAlreadyExists, PermissionDenied)
async def create(self, request: CreateUserRequest) -> CreateUserResponse:
    ...

An undeclared Error raised by the action is still returned using its code, message, and status; it is simply absent from OpenAPI.

Fast Actions catches Error only around the controller method call. An Error raised by a dependency is not converted. FastAPI HTTPException, request validation, dependency validation, response validation, and unexpected exceptions keep their normal FastAPI behavior. In particular, invalid requests remain 422 responses.

Business Outcomes

Business outcomes that should all return 200 belong in the application's response model. Fast Actions has no special result or detailed-error abstraction:

class Created(BaseModel):
    user_id: str


class EmailUnavailable(BaseModel):
    code: t.Literal['EMAIL_UNAVAILABLE']
    message: str


class CreateUserResponse(BaseModel):
    result: Created | EmailUnavailable

Return these models normally instead of raising Error.

OpenAPI Names

Fast Actions creates private request and response model copies for every action. The names are derived from the path known when to_router() or mount_on() is called:

/users                 -> UsersRequest, UsersResponse
/users/create          -> UsersCreateRequest, UsersCreateResponse
/api/v1/users/create   -> ApiV1UsersCreateRequest, ApiV1UsersCreateResponse

Reusing the same application model in multiple actions still produces distinct OpenAPI components.

Prefixes added later when a parent router is included are intentionally not part of these names:

Users.mount_on(api_router, '/users')
app.include_router(api_router, prefix='/api/v1')

The final route is /api/v1/users, but its components are named UsersRequest and UsersResponse.

Scope

Fast Actions is a good fit for internal APIs, backend-for-frontend services, and APIs consumed by generated clients. POST-only reads do not have normal HTTP cache or safe-method semantics. File uploads, streaming responses, and transport-oriented APIs should use FastAPI routes directly.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fast_actions-0.3.0.tar.gz (12.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fast_actions-0.3.0-py3-none-any.whl (13.6 kB view details)

Uploaded Python 3

File details

Details for the file fast_actions-0.3.0.tar.gz.

File metadata

  • Download URL: fast_actions-0.3.0.tar.gz
  • Upload date:
  • Size: 12.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.12.13

File hashes

Hashes for fast_actions-0.3.0.tar.gz
Algorithm Hash digest
SHA256 2d197ecae74e53fdc6951d0fdafadccec6e8e0367c620c4312d62f0203bd558a
MD5 698c31cd62079d186c04983669ed0c10
BLAKE2b-256 2a5bf85298d810dcbc46611fb05776b05079df4cca732d4fc416ff0751090a4f

See more details on using hashes here.

File details

Details for the file fast_actions-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: fast_actions-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 13.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.12.13

File hashes

Hashes for fast_actions-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0548040bdfb46363541564c2632c556c709df72f5975c4c5c1c9ead2ee4ac2d7
MD5 2f5d961587d24da0453adb5b92be5ebc
BLAKE2b-256 175a391fb5c9a0ad994e86e95ca47ad5ee677fc072eec1906d1d46ece649b456

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 files

0.1.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page