Skip to content

First PyPI Project

This tutorial starts from an empty directory and installs ReplayLab from PyPI. It captures one real requests call against a local loopback service, replays the app with that service stopped, exports the local React viewer, and generates a pytest provider replay guard.

Use this when you want the shortest public-alpha check that behaves like a normal user project but does not require API keys.

1. Create A Project

mkdir replaylab-first-project
cd replaylab-first-project
python -m venv .venv
source .venv/bin/activate
python -m pip install replaylab==0.1.0a4 requests pytest

Check the install:

replaylab version
replaylab doctor --project-name first-project

Expected output includes:

replaylab 0.1.0a4
replaylab-schemas 0.1.0a4
Project: first-project

2. Add A Local Provider

Create provider.py:

from __future__ import annotations

import json
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import urlparse


class Handler(BaseHTTPRequestHandler):
    def do_GET(self) -> None:
        parsed = urlparse(self.path)
        ticket_id = parsed.path.rstrip("/").split("/")[-1]
        body = json.dumps(
            {
                "ticket_id": ticket_id,
                "priority": "high",
                "category": "support",
                "private_note": "this payload body should stay out of the viewer",
            },
            sort_keys=True,
        ).encode("utf-8")
        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def log_message(self, _format: str, *_args: object) -> None:
        return


if __name__ == "__main__":
    ThreadingHTTPServer(("127.0.0.1", 8765), Handler).serve_forever()

Start it in one terminal:

source .venv/bin/activate
python provider.py

Leave that process running for the capture step.

3. Add The App

Create app.py:

from __future__ import annotations

import json
import os

import replaylab
import requests
from replaylab import CapturePayloadPolicy

BASE_URL = os.environ.get("REPLAYLAB_ONBOARDING_BASE_URL", "http://127.0.0.1:8765")
TICKET_ID = "ticket-123"


def classify_ticket(ticket_id: str) -> dict[str, str]:
    response = requests.get(
        f"{BASE_URL}/tickets/{ticket_id}",
        params={"source": "first-pypi-project"},
        headers={"X-ReplayLab-Onboarding": "docs"},
        timeout=5,
    )
    response.raise_for_status()
    payload = response.json()
    return {
        "ticket_id": str(payload["ticket_id"]),
        "priority": str(payload["priority"]),
        "category": str(payload["category"]),
    }


def main() -> None:
    handle = replaylab.init(
        project_name="first-pypi-project",
        auto_patch_integrations="auto",
        capture_payload_policy=CapturePayloadPolicy.FULL,
    )

    with handle.capture("classify_ticket", session_id=TICKET_ID) as capture:
        result = classify_ticket(TICKET_ID)
        print(json.dumps(result, sort_keys=True))

    if capture.capsule is not None:
        print(f"capsule_path={capture.capsule.capsule_path}")


if __name__ == "__main__":
    main()

This is the production-style integration model:

  • initialize ReplayLab once near startup
  • keep normal provider code
  • scope the user workflow with handle.capture(...)

4. Capture

In a second terminal, run:

source .venv/bin/activate
python app.py

Expected output:

{"category": "support", "priority": "high", "ticket_id": "ticket-123"}
capsule_path=.replaylab/capsules/cap_...

List and inspect the capsule:

replaylab capsule list --local-store-root .replaylab
replaylab capsule inspect <capsule_id> --local-store-root .replaylab

The inspection output should show one requests HTTP boundary and payload refs. It should not print the provider response body.

5. Replay With The Provider Stopped

Stop provider.py with Ctrl-C, then run:

replaylab replay <capsule_id> \
  --local-store-root .replaylab \
  --auto-patch-integrations auto \
  --report-id replay_first_project \
  -- python app.py

Expected output includes:

ReplayLab replay finished
Command exit code: 0
Next steps
- Inspect report: replaylab report inspect .replaylab/replays/replay_first_project/report.json
- Compare against capsule: replaylab report compare <capsule_id> .replaylab/replays/replay_first_project/report.json --local-store-root .replaylab
- Open local viewer: replaylab report view report .replaylab/replays/replay_first_project/report.json --capsule <capsule_id> --output replay-viewer.html --local-store-root .replaylab
- Generate provider replay guard: replaylab generate-test <capsule_id> --output tests/regression/test_replay.py --fixture-root tests/fixtures/replaylab/capsules --app-root . --auto-patch-integrations auto --local-store-root .replaylab -- <app command>

Replay is meaningful here because the local provider is no longer running. If ReplayLab misses the provider call, the app tries to connect to 127.0.0.1:8765 and fails. The Next steps block is deliberately safe: it points to the follow-up commands without printing payload bodies or the original app command arguments.

6. Compare And View

replaylab report inspect .replaylab/replays/replay_first_project/report.json

replaylab report compare \
  <capsule_id> \
  .replaylab/replays/replay_first_project/report.json \
  --local-store-root .replaylab

replaylab report view report \
  .replaylab/replays/replay_first_project/report.json \
  --capsule <capsule_id> \
  --local-store-root .replaylab \
  --output replay-viewer.html

The command writes replay-viewer.html and opens it in your browser. It shows the replay status, boundary count, provider, request hash, payload availability, and next commands without rendering payload bodies or secrets.

7. Generate A Regression

replaylab generate-test <capsule_id> \
  --output tests/regression/test_first_project_replay.py \
  --fixture-root tests/fixtures/replaylab/capsules \
  --app-root . \
  --auto-patch-integrations auto \
  -- python app.py

pytest tests/regression/test_first_project_replay.py

The generated pytest should pass with provider.py still stopped. That means your regression test uses the captured capsule instead of the live local service.

What Happened

ReplayLab installed provider wrappers in the current Python process when replaylab.init(...) ran. The handle.capture(...) scope opened a run and default step for the app work. The requests.get(...) call recorded a request/response boundary with full payload refs. During replay, ReplayLab served the recorded response from the capsule and wrote a replay report.

The next step is to replace the loopback provider with your real provider call, or use the HTTP Capture And Replay and OpenAI Responses tutorials for provider-specific examples.