typestar

api_service.py in Python

A small FastAPI service: models, dependency-injected store, routes, tests.

from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field


class TourIn(BaseModel):
    slug: str = Field(pattern=r"^[a-z][a-z0-9_-]{1,31}$")
    title: str = Field(max_length=60)
    steps: int = Field(default=0, ge=0)


class TourOut(TourIn):
    id: int


class Store:
    """An in-memory stand-in for a database."""

    def __init__(self):
        self._rows: dict[int, TourOut] = {}
        self._next = 1

    def add(self, tour: TourIn) -> TourOut:
        if any(row.slug == tour.slug for row in self._rows.values()):
            raise KeyError(tour.slug)
        row = TourOut(id=self._next, **tour.model_dump())
        self._rows[self._next] = row
        self._next += 1
        return row

    def all(self) -> list[TourOut]:
        return list(self._rows.values())

    def get(self, tour_id: int) -> TourOut | None:
        return self._rows.get(tour_id)


store = Store()
app = FastAPI(title="tours")


def get_store() -> Store:
    return store


@app.get("/healthz")
def healthz():
    return {"status": "ok"}


@app.get("/tours", response_model=list[TourOut])
def list_tours(shop: Store = Depends(get_store)):
    return shop.all()


@app.post("/tours", response_model=TourOut,
          status_code=status.HTTP_201_CREATED)
def create_tour(tour: TourIn, shop: Store = Depends(get_store)):
    try:
        return shop.add(tour)
    except KeyError:
        raise HTTPException(status.HTTP_409_CONFLICT, f"{tour.slug} exists")


@app.get("/tours/{tour_id}", response_model=TourOut)
def read_tour(tour_id: int, shop: Store = Depends(get_store)):
    found = shop.get(tour_id)
    if found is None:
        raise HTTPException(status.HTTP_404_NOT_FOUND, "no such tour")
    return found


def main():
    client = TestClient(app)
    print(client.get("/healthz").json())

    made = client.post("/tours", json={"slug": "basics",
                                       "title": "Language basics",
                                       "steps": 60})
    print(made.status_code, made.json())

    again = client.post("/tours", json={"slug": "basics", "title": "Dup"})
    print(again.status_code, again.json()["detail"])

    bad = client.post("/tours", json={"slug": "Bad Slug", "title": "x"})
    print(bad.status_code, bad.json()["detail"][0]["loc"])

    print(client.get("/tours/1").json()["title"])
    print(client.get("/tours/99").status_code)
    print(len(client.get("/tours").json()))


if __name__ == "__main__":
    main()

How it works

  1. Pydantic models define the request and response shapes.
  2. The store arrives through Depends, so a test can replace it.
  3. The TestClient exercises every route at the bottom.

Keywords and builtins used here

The run, in numbers

Lines
94
Characters to type
2223
Tokens
668
Three-star pace
115 tpm

At the three-star pace of 115 tokens a minute, this run takes about 349 seconds.

Type this snippet

Step 1 of 2 in Encore, step 18 of 19 in Web services & data access.

← Previous Next →