Week 05 — Python Core and Your First API
A typed FastAPI service skeleton with an ADR
Ways of working: Shape Up
Phase 3 · System design #1 · Ways of working: Shape Up
From this week on, Claude Code is your pair programmer. Rule: you write the first version of each function yourself, then ask Claude to review it or extend it. Read every line it writes before you accept it.
1. Concept brief
Python is the lingua franca of data and AI work. FastAPI is the modern way to put a Python function behind an HTTP endpoint, with type hints doing double duty as validation and documentation.
Vocabulary
- HTTP methods:
GET(read),POST(create),PUT/PATCH(update),DELETE. - Status codes:
2xxsuccess,4xxthe client’s fault,5xxthe server’s fault. Learn 200, 201, 204, 400, 401, 403, 404, 409, 422, 429, 500 and 503. - REST: resources as URLs (
/tickets/42), with methods as verbs. - Type hints:
def f(x: int) -> str. They’re not enforced at runtime unless a library like pydantic does it. - Pydantic model: a class that validates and serialises data.
- OpenAPI: a machine-readable API description. FastAPI generates it for you at
/docs. - Virtual environment: isolated per-project packages.
uvmanages it for you.
2. Guided lab (~3 hrs)
cd ~/dev && uv init ticket-service && cd ticket-service
uv add fastapi uvicorn pydantic
uv add --dev pytest httpx ruff mypy
Create main.py:
from datetime import datetime
from enum import Enum
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
app = FastAPI(title="Ticket Service")
class Status(str, Enum):
open = "open"
pending = "pending"
resolved = "resolved"
escalated = "escalated"
class TicketIn(BaseModel):
account_id: str = Field(min_length=2)
customer_email: str
subject: str
class Ticket(TicketIn):
id: int
created_at: datetime
status: Status = Status.open
DB: dict[int, Ticket] = {}
@app.post("/tickets", status_code=201)
def create(req: TicketIn) -> Ticket:
new = Ticket(id=len(DB) + 1, created_at=datetime.now(), **req.model_dump())
DB[new.id] = new
return new
@app.get("/tickets/{req_id}")
def get(req_id: int) -> Ticket:
if req_id not in DB:
raise HTTPException(404, "not found")
return DB[req_id]
uv run uvicorn main:app --reload
# In a second tmux pane:
curl -s -X POST localhost:8000/tickets -H 'content-type: application/json' \
-d '{"account_id":"A12","customer_email":"sam@example.com","subject":"Cannot export report"}' | jq
curl -s localhost:8000/tickets/1 | jq
curl -s -X POST localhost:8000/tickets -H 'content-type: application/json' -d '{}' | jq # 422 — read the error
open http://localhost:8000/docs
uv run ruff check . && uv run mypy main.py
Python fundamentals to practise in a REPL (uv run python):
- lists, dicts and comprehensions
- f-strings
for/enumerate- functions with default arguments
- exceptions and
try/except withblocksdataclassvs pydantic
3. Unguided challenge (~3 hrs)
Extend the service with:
GET /tickets, with?status=filtering andlimit/offsetpaginationPATCH /tickets/{id}to update the status; return409if the ticket is alreadyresolved- a
tests/test_api.pywith at least 6 tests using FastAPI’sTestClient - a CI workflow running ruff, mypy and pytest
Acceptance criteria:
uv run pytestpasses.- CI is green.
/docsshows every endpoint with example payloads.
4. System design #1 — APIs (~1.5 hrs)
Design the public API for the ticket service, as used by partner companies that create and track tickets from their own systems. One page:
- resources and endpoints
- authentication approach
- pagination style (offset or cursor, and why)
- error format
- idempotency for
POST: what happens if the partner retries? (Look upIdempotency-Keyheaders.) - why REST rather than GraphQL or gRPC here, and when you’d choose each
Then write your first ADR (Architecture Decision Record) in docs/adr/0001-rest-api.md, with sections for Context, Decision, Consequences and Alternatives. Add a three-line exec summary for a non-technical stakeholder.
Defend it: “Claude, staff engineer mode, poke holes in this API design.”
5. Ways of working (~1 hr)
Shape Up (Basecamp): fixed time with variable scope, appetite rather than estimates, shaping before betting, 6-week cycles plus cooldown. Compare it with the sprint process you know. Where would it break at a 500-person company?
6. Check
- What’s the difference between 401 and 403?
- Why does FastAPI return 422 rather than 400 for a bad body?
- What does pydantic give you that type hints alone don’t?
- Offset vs cursor pagination: name one failure mode of offset.
- What problem does an idempotency key solve?
Recycled:
- When would you rebase rather than merge?
- What’s an environment variable?
7. Teach-back
Write “What actually happens when you call an API.”
Done when
- Service extended, tests passing, CI green
- API design doc, ADR 0001 and exec summary committed
- Design defended
- Check and teach-back done