From 22b6d81db487ec347b468063daaf5fb9663f1220 Mon Sep 17 00:00:00 2001 From: Smithetch Date: Tue, 31 Mar 2026 17:08:00 -0500 Subject: [PATCH 1/4] feat: Solve issue #175 --- FastAPI/book_library_api/main.py | 134 +++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 FastAPI/book_library_api/main.py diff --git a/FastAPI/book_library_api/main.py b/FastAPI/book_library_api/main.py new file mode 100644 index 0000000..06f9355 --- /dev/null +++ b/FastAPI/book_library_api/main.py @@ -0,0 +1,134 @@ +""" +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="Satanás", author="Mario Mendoza", year=2002), + Book(book_id=3, title="Satanás", author="Mario Mendoza", year=2002), +] + +# Auto-increment ID counter +next_book_id = 4 + + +@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", + ) From 8da27be3d38054b2273c081af56487ffa7720c98 Mon Sep 17 00:00:00 2001 From: Smithetch Date: Tue, 31 Mar 2026 17:23:39 -0500 Subject: [PATCH 2/4] Fix: update example DB --- FastAPI/book_library_api/main.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/FastAPI/book_library_api/main.py b/FastAPI/book_library_api/main.py index 06f9355..834fcf1 100644 --- a/FastAPI/book_library_api/main.py +++ b/FastAPI/book_library_api/main.py @@ -48,12 +48,13 @@ class Book(BaseModel): # 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="Satanás", author="Mario Mendoza", year=2002), - Book(book_id=3, 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 = 4 +next_book_id = 5 @app.get("/", tags=["Root"]) From 55bdfd9b9ecdd1ab6c276ef946c1329926e25cdc Mon Sep 17 00:00:00 2001 From: Smithetch Date: Tue, 31 Mar 2026 17:42:38 -0500 Subject: [PATCH 3/4] test: Add test for book library api --- FastAPI/book_library_api/test_main.py | 91 +++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 FastAPI/book_library_api/test_main.py diff --git a/FastAPI/book_library_api/test_main.py b/FastAPI/book_library_api/test_main.py new file mode 100644 index 0000000..5374b0e --- /dev/null +++ b/FastAPI/book_library_api/test_main.py @@ -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" From ac159e7b8320e7404c377d4f8ba75159bd0bc204 Mon Sep 17 00:00:00 2001 From: Smithetch Date: Tue, 31 Mar 2026 17:43:23 -0500 Subject: [PATCH 4/4] style: update style --- FastAPI/book_library_api/main.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/FastAPI/book_library_api/main.py b/FastAPI/book_library_api/main.py index 834fcf1..dbc7e9c 100644 --- a/FastAPI/book_library_api/main.py +++ b/FastAPI/book_library_api/main.py @@ -48,9 +48,21 @@ class Book(BaseModel): # 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), + 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