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
FastAPIapplication.
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)
| File | Size | Uploaded | |
|---|---|---|---|
| fastapi_runner-0.0.0.tar.gz | 14.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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 logRelease 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