Skip to content

Commit 8cd2884

Browse files
cguarinosuclaude
andcommitted
feat: Implement TodoList CRUD API with in-memory storage
Build a complete RESTful API for managing todo lists using FastAPI with full type hints and modern Python patterns. Architecture: - Clean separation of concerns (models, routers, services) - Dependency injection for service layer - In-memory storage using Python lists (data resets on restart) - Async/await patterns throughout Core components: - app/models.py: Pydantic v2 models for validation * TodoListBase - Base model with name field * TodoListCreate - DTO for creating todos * TodoListUpdate - DTO for updating todos * TodoList - Complete model with ID - app/services/todo_lists.py: Business logic service * TodoListService class with in-memory storage * CRUD operations: all(), get(), create(), update(), delete() * Singleton pattern via get_todo_list_service() - app/routers/todo_lists.py: API endpoints * GET /api/todolists - List all todos * GET /api/todolists/{id} - Get single todo * POST /api/todolists - Create new todo * PUT /api/todolists/{id} - Update todo * DELETE /api/todolists/{id} - Delete todo * Proper HTTP status codes (200, 201, 204, 404) * HTTPException handling for not found cases - app/main.py: FastAPI application setup * Application metadata (title, description, version) * Router registration * Health check endpoint at / Features: - Full type hints compatible with Python 3.9+ - Automatic API documentation at /docs and /redoc - Request/response validation via Pydantic - RESTful resource naming and HTTP verbs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent e640c2e commit 8cd2884

File tree

7 files changed

+286
-0
lines changed

7 files changed

+286
-0
lines changed

app/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""FastAPI Todo List application."""

app/main.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""FastAPI application entry point."""
2+
3+
from typing import Dict
4+
5+
from fastapi import FastAPI
6+
7+
from app.routers import todo_lists
8+
9+
# Create FastAPI application instance
10+
app = FastAPI(
11+
title="TodoList API",
12+
description="A simple Todo List API for Python/FastAPI candidates",
13+
version="1.0.0",
14+
)
15+
16+
# Include routers
17+
app.include_router(todo_lists.router)
18+
19+
20+
@app.get("/", tags=["health"])
21+
async def root() -> Dict[str, str]:
22+
"""
23+
Health check endpoint.
24+
25+
Returns:
26+
Simple message indicating the API is running
27+
"""
28+
return {"message": "TodoList API is running"}

app/models.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"""Pydantic models for TodoList API."""
2+
3+
from pydantic import BaseModel, ConfigDict, Field
4+
5+
6+
class TodoListBase(BaseModel):
7+
"""Base TodoList model with common attributes."""
8+
9+
name: str = Field(..., min_length=1, description="Name of the todo list")
10+
11+
12+
class TodoListCreate(TodoListBase):
13+
"""Model for creating a new TodoList."""
14+
15+
pass
16+
17+
18+
class TodoListUpdate(TodoListBase):
19+
"""Model for updating an existing TodoList."""
20+
21+
pass
22+
23+
24+
class TodoList(TodoListBase):
25+
"""TodoList model with all attributes including ID."""
26+
27+
id: int = Field(..., description="Unique identifier for the todo list")
28+
29+
model_config = ConfigDict(from_attributes=True)

app/routers/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""API routers."""

app/routers/todo_lists.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
"""TodoList API router with CRUD endpoints."""
2+
3+
from typing import Annotated, List
4+
5+
from fastapi import APIRouter, Depends, HTTPException, status
6+
7+
from app.models import TodoList, TodoListCreate, TodoListUpdate
8+
from app.services.todo_lists import TodoListService, get_todo_list_service
9+
10+
router = APIRouter(prefix="/api/todolists", tags=["todolists"])
11+
12+
13+
@router.get("", response_model=List[TodoList], status_code=status.HTTP_200_OK)
14+
async def index(
15+
service: Annotated[TodoListService, Depends(get_todo_list_service)],
16+
) -> List[TodoList]:
17+
"""
18+
Get all todo lists.
19+
20+
Returns:
21+
List of all TodoList objects
22+
"""
23+
return service.all()
24+
25+
26+
@router.get("/{todo_list_id}", response_model=TodoList, status_code=status.HTTP_200_OK)
27+
async def show(
28+
todo_list_id: int,
29+
service: Annotated[TodoListService, Depends(get_todo_list_service)],
30+
) -> TodoList:
31+
"""
32+
Get a specific todo list by ID.
33+
34+
Args:
35+
todo_list_id: The ID of the todo list to retrieve
36+
service: Injected TodoListService instance
37+
38+
Returns:
39+
TodoList object
40+
41+
Raises:
42+
HTTPException: 404 if todo list not found
43+
"""
44+
todo_list = service.get(todo_list_id)
45+
if todo_list is None:
46+
raise HTTPException(
47+
status_code=status.HTTP_404_NOT_FOUND,
48+
detail=f"TodoList with id {todo_list_id} not found",
49+
)
50+
return todo_list
51+
52+
53+
@router.post("", response_model=TodoList, status_code=status.HTTP_201_CREATED)
54+
async def create(
55+
todo_list_data: TodoListCreate,
56+
service: Annotated[TodoListService, Depends(get_todo_list_service)],
57+
) -> TodoList:
58+
"""
59+
Create a new todo list.
60+
61+
Args:
62+
todo_list_data: Data for creating the todo list
63+
service: Injected TodoListService instance
64+
65+
Returns:
66+
The newly created TodoList object
67+
"""
68+
return service.create(todo_list_data)
69+
70+
71+
@router.put("/{todo_list_id}", response_model=TodoList, status_code=status.HTTP_200_OK)
72+
async def update(
73+
todo_list_id: int,
74+
todo_list_data: TodoListUpdate,
75+
service: Annotated[TodoListService, Depends(get_todo_list_service)],
76+
) -> TodoList:
77+
"""
78+
Update an existing todo list.
79+
80+
Args:
81+
todo_list_id: The ID of the todo list to update
82+
todo_list_data: New data for the todo list
83+
service: Injected TodoListService instance
84+
85+
Returns:
86+
Updated TodoList object
87+
88+
Raises:
89+
HTTPException: 404 if todo list not found
90+
"""
91+
updated_todo_list = service.update(todo_list_id, todo_list_data)
92+
if updated_todo_list is None:
93+
raise HTTPException(
94+
status_code=status.HTTP_404_NOT_FOUND,
95+
detail=f"TodoList with id {todo_list_id} not found",
96+
)
97+
return updated_todo_list
98+
99+
100+
@router.delete("/{todo_list_id}", status_code=status.HTTP_204_NO_CONTENT)
101+
async def delete(
102+
todo_list_id: int,
103+
service: Annotated[TodoListService, Depends(get_todo_list_service)],
104+
) -> None:
105+
"""
106+
Delete a todo list by ID.
107+
108+
Args:
109+
todo_list_id: The ID of the todo list to delete
110+
service: Injected TodoListService instance
111+
112+
Raises:
113+
HTTPException: 404 if todo list not found
114+
"""
115+
deleted = service.delete(todo_list_id)
116+
if not deleted:
117+
raise HTTPException(
118+
status_code=status.HTTP_404_NOT_FOUND,
119+
detail=f"TodoList with id {todo_list_id} not found",
120+
)

app/services/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Business logic services."""

app/services/todo_lists.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
"""TodoList service with in-memory storage."""
2+
3+
from typing import List, Optional
4+
5+
from app.models import TodoList, TodoListCreate, TodoListUpdate
6+
7+
8+
class TodoListService:
9+
"""Service for managing TodoLists with in-memory storage."""
10+
11+
def __init__(self) -> None:
12+
"""Initialize the service with empty storage."""
13+
self._storage: List[TodoList] = []
14+
self._next_id: int = 1
15+
16+
def all(self) -> List[TodoList]:
17+
"""
18+
Get all todo lists.
19+
20+
Returns:
21+
List of all TodoList objects
22+
"""
23+
return self._storage.copy()
24+
25+
def get(self, todo_list_id: int) -> Optional[TodoList]:
26+
"""
27+
Get a specific todo list by ID.
28+
29+
Args:
30+
todo_list_id: The ID of the todo list to retrieve
31+
32+
Returns:
33+
TodoList object if found, None otherwise
34+
"""
35+
for todo_list in self._storage:
36+
if todo_list.id == todo_list_id:
37+
return todo_list
38+
return None
39+
40+
def create(self, todo_list_data: TodoListCreate) -> TodoList:
41+
"""
42+
Create a new todo list.
43+
44+
Args:
45+
todo_list_data: Data for creating the todo list
46+
47+
Returns:
48+
The newly created TodoList object
49+
"""
50+
new_todo_list = TodoList(id=self._next_id, name=todo_list_data.name)
51+
self._storage.append(new_todo_list)
52+
self._next_id += 1
53+
return new_todo_list
54+
55+
def update(self, todo_list_id: int, todo_list_data: TodoListUpdate) -> Optional[TodoList]:
56+
"""
57+
Update an existing todo list.
58+
59+
Args:
60+
todo_list_id: The ID of the todo list to update
61+
todo_list_data: New data for the todo list
62+
63+
Returns:
64+
Updated TodoList object if found, None otherwise
65+
"""
66+
for i, todo_list in enumerate(self._storage):
67+
if todo_list.id == todo_list_id:
68+
updated_todo_list = TodoList(id=todo_list_id, name=todo_list_data.name)
69+
self._storage[i] = updated_todo_list
70+
return updated_todo_list
71+
return None
72+
73+
def delete(self, todo_list_id: int) -> bool:
74+
"""
75+
Delete a todo list by ID.
76+
77+
Args:
78+
todo_list_id: The ID of the todo list to delete
79+
80+
Returns:
81+
True if deleted, False if not found
82+
"""
83+
for i, todo_list in enumerate(self._storage):
84+
if todo_list.id == todo_list_id:
85+
self._storage.pop(i)
86+
return True
87+
return False
88+
89+
90+
# Global singleton instance
91+
_todo_list_service: Optional[TodoListService] = None
92+
93+
94+
def get_todo_list_service() -> TodoListService:
95+
"""
96+
Get or create the singleton TodoListService instance.
97+
98+
This function is used for dependency injection in FastAPI.
99+
100+
Returns:
101+
The singleton TodoListService instance
102+
"""
103+
global _todo_list_service
104+
if _todo_list_service is None:
105+
_todo_list_service = TodoListService()
106+
return _todo_list_service

0 commit comments

Comments
 (0)