Skip to content

Commit 420d0dc

Browse files
Merge pull request hackdartstorm#2686 from Smithech/issue-175-smithech
Solve [PRACTICE] [fastapi-E1] Book Library API hackdartstorm#175
2 parents 79b9e5b + ac159e7 commit 420d0dc

2 files changed

Lines changed: 238 additions & 0 deletions

File tree

‎FastAPI/book_library_api/main.py‎

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
"""
2+
FastAPI Book Library Management API
3+
4+
A complete REST API for managing a book library.
5+
Supports CRUD operations: Create, Read, Update, Delete.
6+
7+
Run with: uvicorn main:app --reload
8+
Docs: http://127.0.0.1:8000/docs
9+
"""
10+
11+
from fastapi import FastAPI, HTTPException, status
12+
from pydantic import BaseModel, Field
13+
from typing import Optional
14+
15+
app = FastAPI(
16+
title="Book Library API",
17+
description="A simple API to manage a book library with CRUD operations",
18+
version="1.0.0",
19+
)
20+
21+
22+
# Pydantic models for request/response validation
23+
class BookCreate(BaseModel):
24+
"""Schema for creating a new book"""
25+
26+
title: str = Field(..., min_length=1, description="Book title")
27+
author: str = Field(..., min_length=1, description="Author of the book")
28+
year: int = Field(..., description="Publication year")
29+
30+
31+
class BookUpdate(BaseModel):
32+
"""Schema for updating an existing book"""
33+
34+
title: Optional[str] = Field(None, min_length=1)
35+
author: Optional[str] = Field(None, min_length=1)
36+
year: Optional[int] = Field(None, ge=1000, le=9999)
37+
38+
39+
class Book(BaseModel):
40+
"""Schema for book response"""
41+
42+
book_id: int
43+
title: str
44+
author: str
45+
year: int
46+
47+
48+
# In-memory data storage (replace with database in production)
49+
books_db: list[Book] = [
50+
Book(book_id=1, title="Satanás", author="Mario Mendoza", year=2002),
51+
Book(
52+
book_id=2,
53+
title="Cien años de soledad",
54+
author="Gabriel Garcia Marquez",
55+
year=1967,
56+
),
57+
Book(
58+
book_id=3,
59+
title="El olvido que seremos",
60+
author="Hector Abad Faciolince",
61+
year=2006,
62+
),
63+
Book(
64+
book_id=4, title="Lo que no tiene nombre ", author="Piedad Bonnett", year=2013
65+
),
66+
]
67+
68+
# Auto-increment ID counter
69+
next_book_id = 5
70+
71+
72+
@app.get("/", tags=["Root"])
73+
async def root():
74+
"""Root endpoint - API information"""
75+
return {
76+
"message": "Welcome to Book Library API",
77+
"docs": "/docs",
78+
"endpoints": {"books": "/books"},
79+
}
80+
81+
82+
@app.get("/books", response_model=list[Book], tags=["Books"])
83+
async def list_books():
84+
"""Get all books"""
85+
return books_db
86+
87+
88+
@app.get("/books/{book_id}", response_model=Book, tags=["Books"])
89+
async def get_book(book_id: int):
90+
"""Get a specific book by ID"""
91+
for book in books_db:
92+
if book.book_id == book_id:
93+
return book
94+
raise HTTPException(
95+
status_code=status.HTTP_404_NOT_FOUND,
96+
detail=f"Book with ID {book_id} not found",
97+
)
98+
99+
100+
@app.post(
101+
"/books", response_model=Book, status_code=status.HTTP_201_CREATED, tags=["Books"]
102+
)
103+
async def create_book(book_data: BookCreate):
104+
"""Create a new book"""
105+
global next_book_id
106+
107+
new_book = Book(
108+
book_id=next_book_id,
109+
title=book_data.title,
110+
author=book_data.author,
111+
year=book_data.year,
112+
)
113+
books_db.append(new_book)
114+
next_book_id += 1
115+
116+
return new_book
117+
118+
119+
@app.put("/books/{book_id}", response_model=Book, tags=["Books"])
120+
async def update_book(book_id: int, book_update: BookUpdate):
121+
"""Update an existing book"""
122+
for book in books_db:
123+
if book.book_id == book_id:
124+
if book_update.title is not None:
125+
book.title = book_update.title
126+
if book_update.author is not None:
127+
book.author = book_update.author
128+
if book_update.year is not None:
129+
book.year = book_update.year
130+
return book
131+
raise HTTPException(
132+
status_code=status.HTTP_404_NOT_FOUND,
133+
detail=f"Book with ID {book_id} not found",
134+
)
135+
136+
137+
@app.delete("/books/{book_id}", status_code=status.HTTP_204_NO_CONTENT, tags=["Books"])
138+
async def delete_book(book_id: int):
139+
"""Delete a book"""
140+
for book_index, book in enumerate(books_db):
141+
if book.book_id == book_id:
142+
books_db.pop(book_index)
143+
return
144+
raise HTTPException(
145+
status_code=status.HTTP_404_NOT_FOUND,
146+
detail=f"Book with ID {book_id} not found",
147+
)
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
from fastapi.testclient import TestClient
2+
from main import app
3+
4+
client = TestClient(app)
5+
6+
7+
def test_get_root():
8+
"""Test the root endpoint"""
9+
10+
response = client.get("/")
11+
12+
assert response.status_code == 200
13+
14+
15+
def test_get_books():
16+
"""
17+
Test the /books endpoint.
18+
Ensures it returns a list of books with status code 200.
19+
Checks that the first book has the expected title.
20+
"""
21+
response = client.get("/books")
22+
23+
assert response.status_code == 200
24+
assert isinstance(response.json(), list)
25+
assert response.json()[0]["title"] == "Satanás"
26+
27+
28+
def test_get_book_by_id():
29+
"""
30+
Test the /books/{book_id} endpoint.
31+
Fetches book with ID 1 and verifies its book_id and author.
32+
"""
33+
response = client.get("/books/1")
34+
35+
assert response.status_code == 200
36+
data = response.json()
37+
assert data["book_id"] == 1
38+
assert data["author"] == "Mario Mendoza"
39+
40+
41+
def test_get_book_not_found():
42+
"""
43+
Test the /books/{book_id} endpoint with a non-existent book.
44+
Checks that the error message is returned.
45+
"""
46+
response = client.get("/books/999")
47+
48+
assert response.status_code == 404
49+
assert response.json()["detail"] == "Book with ID 999 not found"
50+
51+
52+
def test_create_book():
53+
"""
54+
Test the POST /books endpoint.
55+
Creates a new book and verifies the returned data matches the input.
56+
"""
57+
new_book = {"title": "Delirio", "author": "Laura Restrepo", "year": 2004}
58+
59+
response = client.post("/books", json=new_book)
60+
61+
assert response.status_code == 201
62+
assert response.json()["book_id"] == 5
63+
assert response.json()["title"] == "Delirio"
64+
65+
66+
def test_update_book():
67+
"""
68+
Test the PUT /books/{book_id} endpoint.
69+
Updates the title of book with ID 1 and verifies the change.
70+
"""
71+
72+
update_data = {"title": "La melancolía de los feos"}
73+
response = client.put("/books/1", json=update_data)
74+
75+
assert response.status_code == 200
76+
assert response.json()["title"] == "La melancolía de los feos"
77+
78+
79+
def test_delete_book():
80+
"""
81+
Test the DELETE /books/{book_id} endpoint.
82+
Deletes a book and ensures it no longer exists in the list.
83+
"""
84+
response = client.delete("/books/5") # Book ID created in test_create_book
85+
86+
assert response.status_code == 204
87+
88+
# Verify that the book was actually deleted
89+
get_response = client.get("/books/5")
90+
assert get_response.status_code == 404
91+
assert get_response.json()["detail"] == "Book with ID 5 not found"

0 commit comments

Comments
 (0)