Skip to main content

fastapi-runner

fastapi-runner provides shared FastAPI application setup and a Uvicorn application factory. It keeps cross-cutting behavior in a reusable library while the application package owns its routes and application-specific lifecycle hooks.

This package is a member of the repository's uv workspace, but it is otherwise freestanding: its package metadata, source tree, tests, and README are all under this directory. It can be built as its own source distribution and wheel for a future PyPI release.

Application discovery

app_factory() selects an installed application using the FASTAPI_RUNNER_APPLICATION environment variable. The value is a Python distribution name, not a module path. That distribution must define exactly one configure entry point in the fastapi_runner group and may define a lifespans entry point.

For example, an application package can declare:

[project.entry-points.fastapi_runner]
configure = "my_app.app:configure"
lifespans = "my_app.app:lifespans"

The hooks are called as follows:

  • configure(app) registers routes and other application-specific behavior.
  • lifespans() yields async context-manager hooks for startup and shutdown.
  • Each lifespan hook may accept no arguments or the FastAPI application.

Run the generic factory with Uvicorn:

FASTAPI_RUNNER_APPLICATION=my-app uvicorn \
  --factory fastapi_runner.entrypoint:app_factory

The distribution named by FASTAPI_RUNNER_APPLICATION and fastapi-runner must both be installed in the environment.

Creating an application directly

Applications and tests that do not need distribution-based discovery can use create_app() directly:

from collections import abc

import fastapi
import fastapi_runner.entrypoint
import fastapi_runner.lifespan


def configure(app: fastapi.FastAPI) -> None:
    """Add application-specific behavior."""


def lifespans() -> abc.Generator[fastapi_runner.lifespan.LifespanHook]:
    """Yield the application lifespan hooks."""


app = fastapi_runner.entrypoint.create_app(
    configure=configure,
    lifespan_generator=lifespans,
)

Both entry points install the same shared application behavior:

  • CORS middleware configured through the FASTAPI_RUNNER_CORS_ environment prefix;
  • problem-detail responses for HTTP and validation errors;
  • an application state hook for customizing problem-detail bodies;
  • access logging, with the documentation routes excluded by default; and
  • a composable lifespan for application startup and shutdown hooks.

Applications can use fastapi_runner.lifespan.LifespanMap as a FastAPI dependency to retrieve the value returned by a registered lifespan hook:

import contextlib
import typing as t

import fastapi

from fastapi_runner.lifespan import LifespanMap


class State:
    """State that is maintained for the application lifetime."""


@contextlib.asynccontextmanager
async def state_lifespan() -> t.AsyncGenerator[State]:
    yield State()


def get_state(*, lifespan: LifespanMap) -> t.Any:
    return lifespan.get_state(state_lifespan)


def status(*, state: t.Annotated[State, fastapi.Depends(get_state)]) -> State:
    return state

The application may raise fastapi_runner.errors.ProblemDetailError when it needs to provide a complete problem-detail document. The default error handlers also allow an application to update the generated body through the fastapi_runner_error_updater callback on app.state.

Use fastapi_runner.middleware.disable_access_log on an endpoint when its requests should not produce an access-log record.

Deployment

The dave-shawley/fastapi-builder image is a suitable base image for deploying applications built using fastapi-runner. The packaging/Dockerfile contains a multi-stage build that uses fastapi-runner to build the application and then copies the resulting artifacts into a minimal image. You should use a similar approach when deploying your application.

The included Dockerfile also includes support for running the application with opentelemetry enabled though you will need to configure the exporter. I use opentelemetry-instrument to automatically instrument the application process. You simply need to include additional instrumentation libraries like opentelemetry-instrumentation-psycopg and the instrument command will pick it up and instrument the process.

Logging

The default logging configuration is based on the Common Logging Format. The fastapi_runner.middleware.AccessLogMiddleware uses the logger named api-runner.access to log access records. I decided to push the field configuration into the logging configuration (see packaging/log-config.json) though I may change that in the future depending on ergonomics. The current implemention creates a log record with he following custom fields that are accessed using %(name)s in the format record.

Field Description
%(bytes_sent)s Number of bytes sent in the response
%(client)s Formatted client address
%(duration)s Formatted duration of the request in milliseconds
%(http_version)s HTTP version
%(method)s HTTP request method
%(path)s Request path
%(status_code)s Numeric status code or -
%(user_agent)s User agent string or -

The formatted lines as configured by packaging/log-config.json look like:

2026-10-08 11:44:26,328 WARNING api-runner.access: 192.168.215.1:52540 - "GET /missing HTTP/1.1" 404 22

I moved the date out of the log message so that it fits into the general log format. You can customize the message format to fit your needs by adjusting the formatter attached to the api-runner.access logger.

The FASTAPI_RUNNER_ACCESS_LOG environment variable can be set to no to disable access logging. There is currently no way to change the access logger name.

Development

From the repository root, the shared justfile provides the normal development commands:

just test
just analyze
just format

Run only this library's tests directly when iterating on the package:

uv run --package fastapi-runner pytest libraries/fastapi-runner/tests -q

Build its release artifacts from the repository root:

uv build --package=fastapi-runner --sdist --wheel --clear

The artifacts are placed in the repository's dist/ directory. Publishing is intentionally separate from the development recipes so each library can adopt its own release and PyPI workflow.

Metadata

Release files for fastapi-runner 0.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fastapi-runner 0.0.0
File Size Uploaded
fastapi_runner-0.0.0.tar.gz 14.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for fastapi-runner 0.0.0
File Interpreter ABI Platform
fastapi_runner-0.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 27.0 kB

Release files / fastapi_runner-0.0.0.tar.gz

Download URL fastapi_runner-0.0.0.tar.gz
Size 14.7 kB
Tags Source
SHA-256 checksum
How to use checksums
22b3036e144f59e7fd81717f8da925ac640878639a384bb55a8a75d3fd972d6c
BLAKE2b-256 checksum
How to use checksums
8df4b44b169398c45139e8cfdaddde9af582ee3466a59e5dbfe9e17b1d72036a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.

Transparency log

Release files / fastapi_runner-0.0.0-py3-none-any.whl

Download URL fastapi_runner-0.0.0-py3-none-any.whl
Size 12.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6228c2a3db7a0ec8fc1b8e9ac8398679cd573227896ea2facdec26e61fa752e1
BLAKE2b-256 checksum
How to use checksums
536be4017555717f48b8cc7c04b0a3145b62665b5344211c6be22fa146a43cee
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.0.0 This release

2 release 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