Skip to main content

Python FastAPI Practice Problems & Exercises

Practice: FastAPI

11 problems4 Easy4 Medium3 Hard55–75 min
← Back to lesson

#1Basic FastAPI GET RouteEasy
FastAPIGETpath operationTestClient

Create a basic FastAPI GET route and verify it with TestClient.

Solution
from fastapi import FastAPI
from fastapi.testclient import TestClient

app = FastAPI()

@app.get('/ping')
def ping():
return {'message': 'pong', 'version': '1.0'}

client = TestClient(app)

resp = client.get('/ping')
print(f"Status: {resp.status_code}")
print(f"JSON: {resp.json()}")
from fastapi import FastAPI
from fastapi.testclient import TestClient

app = FastAPI()

# TODO: Add GET /ping that returns {"message": "pong", "version": "1.0"}

client = TestClient(app)

resp = client.get('/ping')
print(f"Status: {resp.status_code}")
print(f"JSON: {resp.json()}")
Expected Output
Status: 200
JSON: {'message': 'pong', 'version': '1.0'}
Hints

Hint 1: Decorate your function with @app.get('/ping').

Hint 2: FastAPI automatically serializes dict return values to JSON.

Hint 3: TestClient from fastapi.testclient wraps the app for synchronous testing.


#2Path and Query ParametersEasy
FastAPIpath parametersquery parameterstype hints

Define a FastAPI route with both path parameters and optional query parameters using type hints.

Solution
from fastapi import FastAPI
from fastapi.testclient import TestClient
from typing import Optional

app = FastAPI()

@app.get('/items/{item_id}')
def get_item(item_id: int, category: Optional[str] = None, in_stock: bool = True):
return {'item_id': item_id, 'category': category, 'in_stock': in_stock}

client = TestClient(app)

r1 = client.get('/items/42')
print(r1.json())

r2 = client.get('/items/7?category=electronics&in_stock=false')
print(r2.json())
from fastapi import FastAPI
from fastapi.testclient import TestClient
from typing import Optional

app = FastAPI()

# TODO: GET /items/{item_id}
# Path param: item_id (int)
# Query params: category (str, optional, default None), in_stock (bool, default True)
# Return all three values in a dict

client = TestClient(app)

r1 = client.get('/items/42')
print(r1.json())

r2 = client.get('/items/7?category=electronics&in_stock=false')
print(r2.json())
Expected Output
{'item_id': 42, 'category': None, 'in_stock': True}
{'item_id': 7, 'category': 'electronics', 'in_stock': False}
Hints

Hint 1: Declare path params in the route string and as function arguments with matching names.

Hint 2: Query params are any function arguments NOT in the path — FastAPI infers them automatically.

Hint 3: Optional[str] = None makes a query param optional; bool params accept 'true'/'false'.


#3Pydantic Request BodyEasy
FastAPIPydanticBaseModelPOST body

Define a Pydantic model for a POST request body and use it in a FastAPI route.

Solution
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel
from typing import Optional

app = FastAPI()
_user_id = 0

class CreateUserRequest(BaseModel):
name: str
email: str
age: Optional[int] = None

@app.post('/users', status_code=201)
def create_user(body: CreateUserRequest):
global _user_id
_user_id += 1
return {'id': _user_id, 'name': body.name, 'email': body.email, 'age': body.age}

client = TestClient(app)

r1 = client.post('/users', json={'name': 'Alice', 'email': '[email protected]', 'age': 30})
print(f"{r1.status_code}: {r1.json()}")

r2 = client.post('/users', json={'name': 'Bob', 'email': '[email protected]'})
print(f"{r2.status_code}: {r2.json()}")
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel
from typing import Optional

app = FastAPI()

# TODO: Define a Pydantic model CreateUserRequest with:
# - name: str
# - email: str
# - age: Optional[int] = None
# POST /users accepts this body and returns:
# {"id": 1, "name": ..., "email": ..., "age": ...}

client = TestClient(app)

r1 = client.post('/users', json={'name': 'Alice', 'email': '[email protected]', 'age': 30})
print(f"{r1.status_code}: {r1.json()}")

r2 = client.post('/users', json={'name': 'Bob', 'email': '[email protected]'})
print(f"{r2.status_code}: {r2.json()}")
Expected Output
201: {'id': 1, 'name': 'Alice', 'email': '[email protected]', 'age': 30}
201: {'id': 2, 'name': 'Bob', 'email': '[email protected]', 'age': None}
Hints

Hint 1: Define a class that inherits from BaseModel with typed fields.

Hint 2: Use the model as a type hint for the request body parameter in your route function.

Hint 3: FastAPI automatically parses and validates the JSON body into your model instance.


#4response_model FilteringEasy
FastAPIresponse_modelPydanticfield exclusion

Use response_model to automatically strip sensitive fields (like password hash) from API responses.

Solution
from fastapi import FastAPI, HTTPException
from fastapi.testclient import TestClient
from pydantic import BaseModel

app = FastAPI()

class UserDB(BaseModel):
id: int
name: str
email: str
password_hash: str
role: str

class UserPublic(BaseModel):
id: int
name: str
email: str
role: str

USERS_DB = {
1: UserDB(id=1, name='Alice', email='[email protected]', password_hash='$2b$hashed', role='admin'),
}

@app.get('/users/{uid}', response_model=UserPublic)
def get_user(uid: int):
user = USERS_DB.get(uid)
if user is None:
raise HTTPException(status_code=404, detail='User not found')
return user

client = TestClient(app)

resp = client.get('/users/1')
data = resp.json()
print(f"has password_hash: {'password_hash' in data}")
print(f"fields: {sorted(data.keys())}")
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel
from typing import Optional

app = FastAPI()

# Full internal user model (includes password hash)
class UserDB(BaseModel):
  id: int
  name: str
  email: str
  password_hash: str
  role: str

# TODO: Define UserPublic (id, name, email, role — no password_hash)
# GET /users/{uid} uses response_model=UserPublic so password_hash is stripped

USERS_DB = {
  1: UserDB(id=1, name='Alice', email='[email protected]', password_hash='$2b$hashed', role='admin'),
}

client = TestClient(app)

resp = client.get('/users/1')
data = resp.json()
print(f"has password_hash: {'password_hash' in data}")
print(f"fields: {sorted(data.keys())}")
Expected Output
has password_hash: False
fields: ['email', 'id', 'name', 'role']
Hints

Hint 1: Define UserPublic as a Pydantic model without the password_hash field.

Hint 2: Pass response_model=UserPublic to the @app.get() decorator.

Hint 3: FastAPI will serialize the returned object through the response_model, stripping extra fields.


#5HTTPException with Custom Error DetailMedium
FastAPIHTTPExceptionerror handlingstatus codes

Raise HTTPException with meaningful detail messages for product-not-found and insufficient-stock scenarios.

Solution
from fastapi import FastAPI, HTTPException
from fastapi.testclient import TestClient
from pydantic import BaseModel

app = FastAPI()

PRODUCTS = {
1: {'id': 1, 'name': 'Widget', 'stock': 10},
2: {'id': 2, 'name': 'Gadget', 'stock': 0},
}

class PurchaseRequest(BaseModel):
quantity: int

@app.post('/products/{product_id}/purchase')
def purchase(product_id: int, body: PurchaseRequest):
product = PRODUCTS.get(product_id)
if product is None:
raise HTTPException(status_code=404, detail=f"Product {product_id} not found")
if product['stock'] < body.quantity:
raise HTTPException(status_code=409, detail=f"Only {product['stock']} units available")
product['stock'] -= body.quantity
return {'purchased': product['name'], 'remaining_stock': product['stock']}

client = TestClient(app)

r1 = client.post('/products/1/purchase', json={'quantity': 3})
print(f"{r1.status_code}: {r1.json()}")

r2 = client.post('/products/2/purchase', json={'quantity': 1})
print(f"{r2.status_code}: {r2.json()}")

r3 = client.post('/products/99/purchase', json={'quantity': 1})
print(f"{r3.status_code}: {r3.json()}")
from fastapi import FastAPI, HTTPException
from fastapi.testclient import TestClient

app = FastAPI()

PRODUCTS = {
  1: {'id': 1, 'name': 'Widget', 'stock': 10},
  2: {'id': 2, 'name': 'Gadget', 'stock': 0},
}

# TODO: POST /products/{product_id}/purchase
# Path param: product_id (int)
# Body: {"quantity": int}
# Raise 404 if product not found: detail="Product {id} not found"
# Raise 409 if insufficient stock: detail="Only {stock} units available"
# On success return {"purchased": product_name, "remaining_stock": new_stock}

client = TestClient(app)

r1 = client.post('/products/1/purchase', json={'quantity': 3})
print(f"{r1.status_code}: {r1.json()}")

r2 = client.post('/products/2/purchase', json={'quantity': 1})
print(f"{r2.status_code}: {r2.json()}")

r3 = client.post('/products/99/purchase', json={'quantity': 1})
print(f"{r3.status_code}: {r3.json()}")
Expected Output
200: {'purchased': 'Widget', 'remaining_stock': 7}
409: {'detail': 'Only 0 units available'}
404: {'detail': 'Product 99 not found'}
Hints

Hint 1: raise HTTPException(status_code=404, detail='...') raises an HTTP error response.

Hint 2: FastAPI wraps the detail string in {'detail': '...'} automatically.

Hint 3: Check for the product first, then check stock — order matters for correct error messages.


#6Dependency Injection with DependsMedium
FastAPIDependsdependency injectionpagination

Create a reusable pagination dependency with Depends and inject it into a route.

Solution
from fastapi import FastAPI, Depends
from fastapi.testclient import TestClient

app = FastAPI()

def get_pagination(skip: int = 0, limit: int = 10):
return {'skip': skip, 'limit': min(limit, 100)}

@app.get('/items')
def list_items(pagination: dict = Depends(get_pagination)):
skip = pagination['skip']
limit = pagination['limit']
return {'items': list(range(skip, skip + limit)), 'pagination': pagination}

client = TestClient(app)

r1 = client.get('/items')
print(r1.json())

r2 = client.get('/items?skip=10&limit=5')
print(r2.json())

r3 = client.get('/items?limit=500')
print(f"limit capped: {r3.json()['pagination']['limit']}")
from fastapi import FastAPI, Depends
from fastapi.testclient import TestClient

app = FastAPI()

# TODO: Create a pagination dependency function get_pagination(skip: int = 0, limit: int = 10)
# that returns {"skip": skip, "limit": min(limit, 100)}  (cap limit at 100)
# Use it with Depends() in a GET /items route that returns
# {"items": list(range(skip, skip + limit)), "pagination": pagination_dict}

client = TestClient(app)

r1 = client.get('/items')
print(r1.json())

r2 = client.get('/items?skip=10&limit=5')
print(r2.json())

r3 = client.get('/items?limit=500')  # should cap at 100
print(f"limit capped: {r3.json()['pagination']['limit']}")
Expected Output
{'items': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 'pagination': {'skip': 0, 'limit': 10}}
{'items': [10, 11, 12, 13, 14], 'pagination': {'skip': 10, 'limit': 5}}
limit capped: 100
Hints

Hint 1: Define a regular function get_pagination(skip: int = 0, limit: int = 10) that returns a dict.

Hint 2: In your route function, add pagination: dict = Depends(get_pagination).

Hint 3: FastAPI resolves the dependency automatically, injecting the return value.


#7Async Route HandlerMedium
FastAPIasyncawaitasync def

Write an async FastAPI route that awaits a simulated database call.

Solution
import asyncio
from fastapi import FastAPI, HTTPException
from fastapi.testclient import TestClient

app = FastAPI()

async def fetch_user_from_db(user_id: int):
await asyncio.sleep(0)
if user_id == 1:
return {'id': 1, 'name': 'Alice', 'email': '[email protected]'}
return None

@app.get('/users/{user_id}')
async def get_user(user_id: int):
user = await fetch_user_from_db(user_id)
if user is None:
return {'error': 'not found'}, 404
return user

# Note: returning tuple doesn't set status in FastAPI; use HTTPException
@app.get('/users/{user_id}')
async def get_user(user_id: int):
user = await fetch_user_from_db(user_id)
if user is None:
raise HTTPException(status_code=404, detail='not found')
return user

client = TestClient(app)

r1 = client.get('/users/1')
print(f"{r1.status_code}: {r1.json()}")

r2 = client.get('/users/99')
print(f"{r2.status_code}: {r2.json()['detail'] if r2.status_code == 404 else r2.json()}")
import asyncio
from fastapi import FastAPI
from fastapi.testclient import TestClient

app = FastAPI()

# Simulated async database call
async def fetch_user_from_db(user_id: int):
  await asyncio.sleep(0)  # simulate I/O
  if user_id == 1:
      return {'id': 1, 'name': 'Alice', 'email': '[email protected]'}
  return None

# TODO: Add async GET /users/{user_id} that:
# - awaits fetch_user_from_db
# - returns the user or {"error": "not found"} with 404

client = TestClient(app)

r1 = client.get('/users/1')
print(f"{r1.status_code}: {r1.json()}")

r2 = client.get('/users/99')
print(f"{r2.status_code}: {r2.json()}")
Expected Output
200: {'id': 1, 'name': 'Alice', 'email': '[email protected]'}
404: {'error': 'not found'}
Hints

Hint 1: Use async def for the route function to make it async.

Hint 2: Use await inside the function to call the async database function.

Hint 3: FastAPI's TestClient handles async routes transparently.


#8Dependency Chain for Auth + DBMedium
FastAPIDependsdependency chainauthdatabase

Build a two-level dependency chain: token extraction then user lookup, both injected via Depends.

Solution
from fastapi import FastAPI, Depends, HTTPException, Header
from fastapi.testclient import TestClient

app = FastAPI()

FAKE_DB = {
'user-1': {'id': 1, 'name': 'Alice', 'role': 'admin'},
'user-2': {'id': 2, 'name': 'Bob', 'role': 'user'},
}

def get_token(authorization: str = Header(...)):
if not authorization.startswith('Bearer '):
raise HTTPException(status_code=401, detail='Invalid authorization format')
return authorization[len('Bearer '):]

def get_current_user(token: str = Depends(get_token)):
user = FAKE_DB.get(token)
if user is None:
raise HTTPException(status_code=401, detail='Invalid or expired token')
return user

@app.get('/profile')
def get_profile(user: dict = Depends(get_current_user)):
return {'profile': user}

client = TestClient(app)

r1 = client.get('/profile', headers={'Authorization': 'Bearer user-1'})
print(f"{r1.status_code}: {r1.json()}")

r2 = client.get('/profile', headers={'Authorization': 'Bearer unknown'})
print(f"{r2.status_code}: {r2.json()['detail']}")

r3 = client.get('/profile')
print(f"{r3.status_code}")
from fastapi import FastAPI, Depends, HTTPException, Header
from fastapi.testclient import TestClient
from typing import Optional

app = FastAPI()

# Simulated DB
FAKE_DB = {
  'user-1': {'id': 1, 'name': 'Alice', 'role': 'admin'},
  'user-2': {'id': 2, 'name': 'Bob', 'role': 'user'},
}

# TODO: Implement a two-level dependency chain:
# 1. get_token(authorization: str = Header(...)) -> str
#    Extract Bearer token from Authorization header; raise 401 if missing/invalid format
# 2. get_current_user(token: str = Depends(get_token)) -> dict
#    Look up token in FAKE_DB; raise 401 if not found
# 3. GET /profile uses Depends(get_current_user) to return {"profile": user}

client = TestClient(app)

r1 = client.get('/profile', headers={'Authorization': 'Bearer user-1'})
print(f"{r1.status_code}: {r1.json()}")

r2 = client.get('/profile', headers={'Authorization': 'Bearer unknown'})
print(f"{r2.status_code}: {r2.json()['detail']}")

r3 = client.get('/profile')
print(f"{r3.status_code}")
Expected Output
200: {'profile': {'id': 1, 'name': 'Alice', 'role': 'admin'}}
401: Invalid or expired token
422
Hints

Hint 1: Header(...) (with ... as default) makes the header required — FastAPI raises 422 if missing.

Hint 2: Chain dependencies: get_current_user depends on get_token using Depends(get_token).

Hint 3: Dependency functions can themselves have dependencies — FastAPI resolves the full chain.


#9Background TasksHard
FastAPIBackgroundTasksasynctask queue

Use FastAPI's BackgroundTasks to send a welcome email after returning the signup response immediately.

Solution
import time
from fastapi import FastAPI, BackgroundTasks
from fastapi.testclient import TestClient
from pydantic import BaseModel

app = FastAPI()
task_log = []

def send_welcome_email(email: str, username: str):
time.sleep(0.01)
task_log.append(f"email sent to {email} for {username}")

class SignupRequest(BaseModel):
username: str
email: str

@app.post('/users/signup', status_code=201)
def signup(body: SignupRequest, background_tasks: BackgroundTasks):
background_tasks.add_task(send_welcome_email, body.email, body.username)
return {'message': 'signed up', 'username': body.username}

client = TestClient(app)

start = time.time()
resp = client.post('/users/signup', json={'username': 'alice', 'email': '[email protected]'})
elapsed = time.time() - start

print(f"{resp.status_code}: {resp.json()}")
print(f"Response returned quickly: {elapsed < 0.05}")
print(f"Background task ran: {len(task_log) > 0}")
print(f"Task: {task_log[0] if task_log else 'none'}")
import time
from fastapi import FastAPI, BackgroundTasks
from fastapi.testclient import TestClient

app = FastAPI()
task_log = []

def send_welcome_email(email: str, username: str):
  """Simulated slow email sending (background task)."""
  time.sleep(0.01)
  task_log.append(f"email sent to {email} for {username}")

# TODO: POST /users/signup
# Body: {"username": str, "email": str}
# Register the background task (send_welcome_email)
# Return {"message": "signed up", "username": ...} IMMEDIATELY
# The email should be sent in the background after the response is returned

from pydantic import BaseModel

client = TestClient(app)

start = time.time()
resp = client.post('/users/signup', json={'username': 'alice', 'email': '[email protected]'})
elapsed = time.time() - start

print(f"{resp.status_code}: {resp.json()}")
print(f"Response returned quickly: {elapsed < 0.05}")
print(f"Background task ran: {len(task_log) > 0}")
print(f"Task: {task_log[0] if task_log else 'none'}")
Expected Output
201: {'message': 'signed up', 'username': 'alice'}
Response returned quickly: True
Background task ran: True
Task: email sent to [email protected] for alice
Hints

Hint 1: Add background_tasks: BackgroundTasks as a parameter to your route function.

Hint 2: Call background_tasks.add_task(send_welcome_email, email, username) to queue the task.

Hint 3: Return the response immediately — FastAPI runs the background task after sending the response.


#10Nested Pydantic Models with ValidationHard
FastAPIPydanticnested modelsFieldvalidator

Define nested Pydantic models with field constraints and compute a derived total in the response.

Solution
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field
from typing import List, Annotated

app = FastAPI()

class Address(BaseModel):
street: str
city: str
country: str = Field(min_length=2, max_length=2)

class OrderItem(BaseModel):
product_id: int
quantity: int = Field(ge=1)
unit_price: float = Field(gt=0)

class CreateOrderRequest(BaseModel):
customer_name: str = Field(min_length=1)
items: List[OrderItem] = Field(min_length=1)
shipping_address: Address
discount_pct: float = Field(default=0.0, ge=0, le=100)

@app.post('/orders', status_code=201)
def create_order(body: CreateOrderRequest):
subtotal = sum(item.quantity * item.unit_price for item in body.items)
total = round(subtotal * (1 - body.discount_pct / 100), 3)
return {
'customer_name': body.customer_name,
'item_count': len(body.items),
'discount_pct': body.discount_pct,
'total': total,
}

client = TestClient(app)

valid_order = {
'customer_name': 'Alice',
'items': [
{'product_id': 1, 'quantity': 2, 'unit_price': 19.99},
{'product_id': 2, 'quantity': 1, 'unit_price': 49.99},
],
'shipping_address': {'street': '123 Main St', 'city': 'Anytown', 'country': 'US'},
'discount_pct': 10.0,
}

r1 = client.post('/orders', json=valid_order)
print(f"{r1.status_code}: total={r1.json().get('total')}")

invalid_order = dict(valid_order)
invalid_order['items'] = []
r2 = client.post('/orders', json=invalid_order)
print(f"empty items: {r2.status_code}")
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field, field_validator
from typing import List, Optional

app = FastAPI()

# TODO: Define these Pydantic models:
# Address: street (str), city (str), country (str, 2-char ISO code)
# OrderItem: product_id (int), quantity (int, min=1), unit_price (float, gt=0)
# CreateOrderRequest:
#   - customer_name: str (min_length=1)
#   - items: List[OrderItem] (min 1 item)
#   - shipping_address: Address
#   - discount_pct: float (default 0.0, between 0 and 100)
# POST /orders returns the order with a computed total field
# total = sum(item.quantity * item.unit_price for item in items) * (1 - discount_pct/100)

client = TestClient(app)

valid_order = {
  'customer_name': 'Alice',
  'items': [
      {'product_id': 1, 'quantity': 2, 'unit_price': 19.99},
      {'product_id': 2, 'quantity': 1, 'unit_price': 49.99},
  ],
  'shipping_address': {'street': '123 Main St', 'city': 'Anytown', 'country': 'US'},
  'discount_pct': 10.0,
}

r1 = client.post('/orders', json=valid_order)
print(f"{r1.status_code}: total={r1.json().get('total')}")

invalid_order = dict(valid_order)
invalid_order['items'] = []
r2 = client.post('/orders', json=invalid_order)
print(f"empty items: {r2.status_code}")
Expected Output
201: total=80.982
empty items: 422
Hints

Hint 1: Use Field(min_length=1) for string constraints and Field(ge=1) for int minimum.

Hint 2: Field(min_items=1) or annotated List validation can enforce minimum list length in Pydantic v2.

Hint 3: Compute the total in the route handler after receiving the validated model.


#11FastAPI App with Full Lifecycle TestingHard
FastAPITestClientlifespanintegration testCRUD

Implement a full CRUD FastAPI app with lifespan startup seeding, verified by integration tests.

Solution
from fastapi import FastAPI, HTTPException
from fastapi.testclient import TestClient
from fastapi.responses import Response
from pydantic import BaseModel
from typing import Dict
from contextlib import asynccontextmanager

db: Dict[int, dict] = {}
_id_counter = 0

@asynccontextmanager
async def lifespan(app: FastAPI):
global _id_counter
db[1] = {'id': 1, 'title': 'Seeded Task', 'done': False}
_id_counter = 1
yield
db.clear()

app = FastAPI(lifespan=lifespan)

class TaskCreate(BaseModel):
title: str
done: bool = False

class TaskPatch(BaseModel):
done: bool

@app.get('/tasks')
def list_tasks():
return list(db.values())

@app.post('/tasks', status_code=201)
def create_task(body: TaskCreate):
global _id_counter
_id_counter += 1
task = {'id': _id_counter, 'title': body.title, 'done': body.done}
db[_id_counter] = task
return task

@app.get('/tasks/{tid}')
def get_task(tid: int):
if tid not in db:
raise HTTPException(status_code=404, detail='Not found')
return db[tid]

@app.patch('/tasks/{tid}')
def update_task(tid: int, body: TaskPatch):
if tid not in db:
raise HTTPException(status_code=404, detail='Not found')
db[tid]['done'] = body.done
return db[tid]

@app.delete('/tasks/{tid}', status_code=204)
def delete_task(tid: int):
if tid not in db:
raise HTTPException(status_code=404, detail='Not found')
del db[tid]
return Response(status_code=204)

with TestClient(app) as client:
r = client.get('/tasks')
print(f"initial: {[t['title'] for t in r.json()]}")

r = client.post('/tasks', json={'title': 'New Task'})
tid = r.json()['id']
print(f"created: id={tid}, status={r.status_code}")

r = client.patch(f'/tasks/{tid}', json={'done': True})
print(f"updated: done={r.json()['done']}")

r = client.delete(f'/tasks/{tid}')
print(f"deleted: {r.status_code}")

r = client.get(f'/tasks/{tid}')
print(f"after delete: {r.status_code}")
from fastapi import FastAPI, HTTPException, Depends
from fastapi.testclient import TestClient
from pydantic import BaseModel
from typing import Dict, Optional
from contextlib import asynccontextmanager

# Simulated database
db: Dict[int, dict] = {}
_id_counter = 0

@asynccontextmanager
async def lifespan(app: FastAPI):
  # startup: seed data
  global _id_counter
  db[1] = {'id': 1, 'title': 'Seeded Task', 'done': False}
  _id_counter = 1
  yield
  # shutdown: clear
  db.clear()

app = FastAPI(lifespan=lifespan)

class TaskCreate(BaseModel):
  title: str
  done: bool = False

# TODO: Implement CRUD routes using the db dict above:
# GET    /tasks          -> list all tasks
# POST   /tasks          -> create task (201)
# GET    /tasks/{tid}    -> get task (404 if missing)
# PATCH  /tasks/{tid}    -> update done status: body {"done": bool} (404 if missing)
# DELETE /tasks/{tid}    -> delete (204 or 404)

with TestClient(app) as client:
  # List (includes seeded task)
  r = client.get('/tasks')
  print(f"initial: {[t['title'] for t in r.json()]}")

  # Create
  r = client.post('/tasks', json={'title': 'New Task'})
  tid = r.json()['id']
  print(f"created: id={tid}, status={r.status_code}")

  # Update
  r = client.patch(f'/tasks/{tid}', json={'done': True})
  print(f"updated: done={r.json()['done']}")

  # Delete
  r = client.delete(f'/tasks/{tid}')
  print(f"deleted: {r.status_code}")

  # 404 after delete
  r = client.get(f'/tasks/{tid}')
  print(f"after delete: {r.status_code}")
Expected Output
initial: ['Seeded Task']
created: id=2, status=201
updated: done=True
deleted: 204
after delete: 404
Hints

Hint 1: Use the module-level `db` dict and `_id_counter` as your in-memory store.

Hint 2: The lifespan context manager runs startup code before yield and shutdown after.

Hint 3: For PATCH, only update the 'done' field — merge with the existing task dict.

© 2026 EngineersOfAI. All rights reserved.