ASGI/FastAPI Middleware
Use this tutorial when your app is a long-running ASGI service and you do not want to add
handle.capture(...) inside every endpoint.
ReplayLab still works the same way:
- initialize the SDK once at app startup
- keep normal provider code in your endpoint
- call
replaylab.instrument_app(app, handle=handle) - replay the captured provider capsule locally
The middleware opens one request-scoped capture for HTTP requests that call supported providers. Health checks and provider-free requests do not write capsules by default.
Setup
From the repo checkout, install the development environment:
uv sync --all-packages --all-groups
For a no-network proof of the workflow, run the maintained scenario:
python scripts/run_scenario.py run auto-instrumentation-local --keep-workspace
Expected ending:
ReplayLab scenario passed.
Scenario: auto-instrumentation-local
Tier: loopback
Boundaries: 1
Payloads: 2
Providers: requests
This means ReplayLab captured one provider boundary during a FastAPI request, stopped the provider server, replayed the same app flow without that provider, compared the report, generated pytest, and ran the generated test.
For a richer maintainer check of the same adapter, run:
python scripts/run_scenario.py run asgi-lifecycle-local --keep-workspace
That scenario sends an ignored /health request, a provider-free /ready request, and a
provider-backed /tickets/123 request carrying request ID, authorization, and cookie headers. It
expects only the provider-backed request to write a capsule, and it verifies that only the configured
request ID is stored.
Add Middleware
In your app, initialize ReplayLab near startup and add the middleware:
from fastapi import FastAPI
import replaylab
from replaylab import CapturePayloadPolicy
handle = replaylab.init(
project_name="support-bot",
auto_patch_integrations="auto",
capture_payload_policy=CapturePayloadPolicy.FULL,
)
app = FastAPI()
replaylab.instrument_app(app, handle=handle, ignored_paths=("/health",))
"auto" enables all supported provider patchers. In production you can narrow the patch surface
with an explicit tuple such as ("requests",).
Direct ReplayLabASGIMiddleware registration remains supported when you want explicit framework
configuration.
Your endpoint code stays normal:
import requests
@app.get("/tickets/{ticket_id}")
async def classify_ticket(ticket_id: str) -> dict[str, str]:
response = requests.get(
f"https://support.example.test/tickets/{ticket_id}",
timeout=5,
)
response.raise_for_status()
return {"ticket_id": ticket_id, "provider": "requests"}
You do not call handle.capture(...) in the endpoint.
The middleware creates the request capture scope and provider wrappers attach boundaries to it.
Inspect The Capsule
After a request that calls a supported provider, list capsules:
uv run replaylab capsule list --local-store-root .replaylab
Look for a capsule with integrations like:
requests, asgi, auto_patch, same_process
Inspect it:
uv run replaylab capsule inspect <capsule_id> --local-store-root .replaylab
You should see one HTTP boundary. The capsule includes safe framework metadata such as request method, path, best-effort route path, endpoint name, status code, and optional request ID. ReplayLab does not store framework request bodies, response bodies, cookies, or authorization header values from the ASGI layer.
Replay
Replay runs the same app command under ReplayLab's replay runtime:
uv run replaylab replay <capsule_id> \
--local-store-root .replaylab \
--auto-patch-integrations auto \
--report-id replay_fastapi_request \
-- python app.py
Compare the report:
uv run replaylab report compare \
<capsule_id> \
.replaylab/replays/replay_fastapi_request/report.json \
--local-store-root .replaylab
Expected result:
Status: succeeded
Boundaries: expected=1, replayed=1, problems=0
This means the provider call was served from the capsule rather than from the live dependency.
Generate A Regression
Generate a pytest provider replay guard from the capsule:
uv run replaylab generate-test <capsule_id> \
--output tests/regression/test_fastapi_request_replay.py \
--fixture-root tests/fixtures/replaylab/capsules \
--app-root . \
--auto-patch-integrations auto \
-- python app.py
Run it:
uv run pytest tests/regression/test_fastapi_request_replay.py
The generated test runs your app command through replaylab replay, asserts the replay report,
and fails if the request no longer matches the captured provider boundary.
Current Boundaries
- HTTP requests only. Lifespan and websocket scopes pass through unchanged.
- Provider-free requests do not write capsules unless
write_empty_captures=True. - Framework request/response bodies are not captured by the middleware.
- Replay still uses
replaylab replayfor local regression execution. - Celery, worker middleware, cloud upload, and hosted issue grouping are future work.