Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions FastAPI/book_library_api/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
"""
FastAPI Book Library Management API

A complete REST API for managing a book library.
Supports CRUD operations: Create, Read, Update, Delete.

Run with: uvicorn main:app --reload
Docs: http://127.0.0.1:8000/docs
"""

from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, Field
from typing import Optional

app = FastAPI(
title="Book Library API",
description="A simple API to manage a book library with CRUD operations",
version="1.0.0",
)


# Pydantic models for request/response validation
class BookCreate(BaseModel):
"""Schema for creating a new book"""

title: str = Field(..., min_length=1, description="Book title")
author: str = Field(..., min_length=1, description="Author of the book")
year: int = Field(..., description="Publication year")


class BookUpdate(BaseModel):
"""Schema for updating an existing book"""

title: Optional[str] = Field(None, min_length=1)
author: Optional[str] = Field(None, min_length=1)
year: Optional[int] = Field(None, ge=1000, le=9999)


class Book(BaseModel):
"""Schema for book response"""

book_id: int
title: str
author: str
year: int


# In-memory data storage (replace with database in production)
books_db: list[Book] = [
Book(book_id=1, title="Satanás", author="Mario Mendoza", year=2002),
Book(
book_id=2,
title="Cien años de soledad",
author="Gabriel Garcia Marquez",
year=1967,
),
Book(
book_id=3,
title="El olvido que seremos",
author="Hector Abad Faciolince",
year=2006,
),
Book(
book_id=4, title="Lo que no tiene nombre ", author="Piedad Bonnett", year=2013
),
]

# Auto-increment ID counter
next_book_id = 5


@app.get("/", tags=["Root"])
async def root():
"""Root endpoint - API information"""
return {
"message": "Welcome to Book Library API",
"docs": "/docs",
"endpoints": {"books": "/books"},
}


@app.get("/books", response_model=list[Book], tags=["Books"])
async def list_books():
"""Get all books"""
return books_db


@app.get("/books/{book_id}", response_model=Book, tags=["Books"])
async def get_book(book_id: int):
"""Get a specific book by ID"""
for book in books_db:
if book.book_id == book_id:
return book
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Book with ID {book_id} not found",
)


@app.post(
"/books", response_model=Book, status_code=status.HTTP_201_CREATED, tags=["Books"]
)
async def create_book(book_data: BookCreate):
"""Create a new book"""
global next_book_id

new_book = Book(
book_id=next_book_id,
title=book_data.title,
author=book_data.author,
year=book_data.year,
)
books_db.append(new_book)
next_book_id += 1

return new_book


@app.put("/books/{book_id}", response_model=Book, tags=["Books"])
async def update_book(book_id: int, book_update: BookUpdate):
"""Update an existing book"""
for book in books_db:
if book.book_id == book_id:
if book_update.title is not None:
book.title = book_update.title
if book_update.author is not None:
book.author = book_update.author
if book_update.year is not None:
book.year = book_update.year
return book
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Book with ID {book_id} not found",
)


@app.delete("/books/{book_id}", status_code=status.HTTP_204_NO_CONTENT, tags=["Books"])
async def delete_book(book_id: int):
"""Delete a book"""
for book_index, book in enumerate(books_db):
if book.book_id == book_id:
books_db.pop(book_index)
return
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Book with ID {book_id} not found",
)
91 changes: 91 additions & 0 deletions FastAPI/book_library_api/test_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
from fastapi.testclient import TestClient
from main import app

client = TestClient(app)


def test_get_root():
"""Test the root endpoint"""

response = client.get("/")

assert response.status_code == 200


def test_get_books():
"""
Test the /books endpoint.
Ensures it returns a list of books with status code 200.
Checks that the first book has the expected title.
"""
response = client.get("/books")

assert response.status_code == 200
assert isinstance(response.json(), list)
assert response.json()[0]["title"] == "Satanás"


def test_get_book_by_id():
"""
Test the /books/{book_id} endpoint.
Fetches book with ID 1 and verifies its book_id and author.
"""
response = client.get("/books/1")

assert response.status_code == 200
data = response.json()
assert data["book_id"] == 1
assert data["author"] == "Mario Mendoza"


def test_get_book_not_found():
"""
Test the /books/{book_id} endpoint with a non-existent book.
Checks that the error message is returned.
"""
response = client.get("/books/999")

assert response.status_code == 404
assert response.json()["detail"] == "Book with ID 999 not found"


def test_create_book():
"""
Test the POST /books endpoint.
Creates a new book and verifies the returned data matches the input.
"""
new_book = {"title": "Delirio", "author": "Laura Restrepo", "year": 2004}

response = client.post("/books", json=new_book)

assert response.status_code == 201
assert response.json()["book_id"] == 5
assert response.json()["title"] == "Delirio"


def test_update_book():
"""
Test the PUT /books/{book_id} endpoint.
Updates the title of book with ID 1 and verifies the change.
"""

update_data = {"title": "La melancolía de los feos"}
response = client.put("/books/1", json=update_data)

assert response.status_code == 200
assert response.json()["title"] == "La melancolía de los feos"


def test_delete_book():
"""
Test the DELETE /books/{book_id} endpoint.
Deletes a book and ensures it no longer exists in the list.
"""
response = client.delete("/books/5") # Book ID created in test_create_book

assert response.status_code == 204

# Verify that the book was actually deleted
get_response = client.get("/books/5")
assert get_response.status_code == 404
assert get_response.json()["detail"] == "Book with ID 5 not found"