ProvenPin Install

httpx async API client + respx mocks: working versions (tested 2026-10-15)

A typed async API client on httpx with retries on 5xx and Pydantic parsing, tested offline with respx route mocks.

passed3 passed · 0 failed2026-10-15 13:08 UTC
Status
passed
Last passed
2026-10-15 13:08 UTC
Tests
3 / 3
Language
Python
Runtime
Python 3.13.16
pinned versions5
httpx@0.28.1respx@0.23.1pydantic@2.14.1pytest-asyncio@1.4.0pytest@9.1.1
run it
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt
.venv/bin/python -m pytest -q
test environment
Python 3.13.16
linux 6.17.0-1022-azure
x64
runner: github-actions 38738874208

The code

Exactly the files that passed. Your agent can pull them with get_recipe("httpx-respx-api-client") (install), or see them on GitHub.

requirements.txt
httpx==0.28.1
respx==0.23.1
pydantic==2.14.1
pytest-asyncio==1.4.0
pytest==9.1.1
client.py
import httpx
from pydantic import BaseModel

class Repo(BaseModel):
    id: int
    full_name: str
    stargazers_count: int

class ApiClient:
    def __init__(self, base_url: str = "https://api.example.com", retries: int = 2):
        self.http = httpx.AsyncClient(base_url=base_url, timeout=10)
        self.retries = retries

    async def get_repo(self, owner: str, name: str) -> Repo:
        for attempt in range(self.retries + 1):
            r = await self.http.get(f"/repos/{owner}/{name}")
            if r.status_code < 500 or attempt == self.retries:
                break
        r.raise_for_status()
        return Repo.model_validate(r.json())

    async def aclose(self):
        await self.http.aclose()
tests/test_client.py
import httpx, pytest, respx
from client import ApiClient

@pytest.mark.asyncio
@respx.mock
async def test_parses_repo():
    respx.get("https://api.example.com/repos/a/b").mock(return_value=httpx.Response(200, json={"id": 1, "full_name": "a/b", "stargazers_count": 5, "extra": True}))
    c = ApiClient()
    repo = await c.get_repo("a", "b")
    assert repo.full_name == "a/b" and repo.stargazers_count == 5
    await c.aclose()

@pytest.mark.asyncio
@respx.mock
async def test_retries_then_succeeds():
    route = respx.get("https://api.example.com/repos/a/b").mock(side_effect=[httpx.Response(503), httpx.Response(200, json={"id": 1, "full_name": "a/b", "stargazers_count": 0})])
    c = ApiClient()
    await c.get_repo("a", "b")
    assert route.call_count == 2
    await c.aclose()

@pytest.mark.asyncio
@respx.mock
async def test_404_raises():
    respx.get("https://api.example.com/repos/a/x").mock(return_value=httpx.Response(404))
    c = ApiClient()
    with pytest.raises(httpx.HTTPStatusError):
        await c.get_repo("a", "x")
    await c.aclose()
pytest.ini
[pytest]
pythonpath = .

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.