From 4128cbb67a66510d82017c7e9d069b22b0ee5712 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoandy=20Isse=20O=C3=B1a?= Date: Sat, 18 Mar 2023 16:31:33 +0100 Subject: [PATCH 1/3] update requirements.txt --- requirements.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements.txt b/requirements.txt index df4ae42..f1ff0b6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -fastapi[all]==0.75.0 -pydantic==1.9 -pymongo[srv]==3.11.0 -python-dotenv==0.19.2 -pytest==7.0.1 +fastapi[all]==0.94.1 +pydantic==1.10.6 +pymongo[srv]==4.3.3 +python-dotenv==1.0.0 +pytest==7.2.2 \ No newline at end of file From c8954b33bdc332414f342999731c310a1f1c696d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoandy=20Isse=20O=C3=B1a?= Date: Sun, 19 Mar 2023 13:30:01 +0100 Subject: [PATCH 2/3] adding validator to disallow duplicate books --- models.py | 29 +++++++++++++++++++++++++++++ routes.py | 13 ++++++++++--- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/models.py b/models.py index 8ee7611..be03609 100644 --- a/models.py +++ b/models.py @@ -19,6 +19,35 @@ class Config: } } +class BookCreate(BaseModel): + id: str = Field(default_factory=uuid.uuid4, alias="_id") + title: str = Field(...) + author: str = Field(...) + synopsis: str = Field(...) + + class Config: + allow_population_by_field_name = True + schema_extra = { + "example": { + "title": "Don Quixote", + "author": "Miguel de Cervantes", + "synopsis": "..." + } + } +class BookCreate(BaseModel): + title: str = Field(...) + author: str = Field(...) + synopsis: str = Field(...) + + class Config: + allow_population_by_field_name = True + schema_extra = { + "example": { + "title": "Don Quixote", + "author": "Miguel de Cervantes", + "synopsis": "..." + } + } class BookUpdate(BaseModel): title: Optional[str] diff --git a/routes.py b/routes.py index f6f44e1..e7693f0 100644 --- a/routes.py +++ b/routes.py @@ -1,14 +1,21 @@ from fastapi import APIRouter, Body, Request, Response, HTTPException, status from fastapi.encoders import jsonable_encoder from typing import List - -from models import Book, BookUpdate +import uuid +from models import Book, BookUpdate, BookCreate router = APIRouter() @router.post("/", response_description="Create a new book", status_code=status.HTTP_201_CREATED, response_model=Book) -def create_book(request: Request, book: Book = Body(...)): +def create_book(request: Request, book: BookCreate = Body(...)): book = jsonable_encoder(book) + book["_id"] = str(uuid.uuid4()) + + # verify if book title already exists + if request.app.database["books"].find_one({"title": book["title"]}): + raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=f"Book with title {book['title']} already exists") + + new_book = request.app.database["books"].insert_one(book) created_book = request.app.database["books"].find_one( {"_id": new_book.inserted_id} From d3e46dcff629674139ee8247474de9d74f92848c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoandy=20Isse=20O=C3=B1a?= Date: Sun, 2 Apr 2023 01:52:35 +0200 Subject: [PATCH 3/3] Best practice for _id --- models.py | 19 ++----------------- routes.py | 20 ++++++++++++-------- 2 files changed, 14 insertions(+), 25 deletions(-) diff --git a/models.py b/models.py index be03609..9b34b72 100644 --- a/models.py +++ b/models.py @@ -3,7 +3,7 @@ from pydantic import BaseModel, Field class Book(BaseModel): - id: str = Field(default_factory=uuid.uuid4, alias="_id") + id: str = Field() title: str = Field(...) author: str = Field(...) synopsis: str = Field(...) @@ -12,28 +12,13 @@ class Config: allow_population_by_field_name = True schema_extra = { "example": { - "_id": "066de609-b04a-4b30-b46c-32537c7f1f6e", + "_id": "6428a5fffe4c6065ecf51cb7", "title": "Don Quixote", "author": "Miguel de Cervantes", "synopsis": "..." } } -class BookCreate(BaseModel): - id: str = Field(default_factory=uuid.uuid4, alias="_id") - title: str = Field(...) - author: str = Field(...) - synopsis: str = Field(...) - - class Config: - allow_population_by_field_name = True - schema_extra = { - "example": { - "title": "Don Quixote", - "author": "Miguel de Cervantes", - "synopsis": "..." - } - } class BookCreate(BaseModel): title: str = Field(...) author: str = Field(...) diff --git a/routes.py b/routes.py index e7693f0..270efc5 100644 --- a/routes.py +++ b/routes.py @@ -1,15 +1,14 @@ from fastapi import APIRouter, Body, Request, Response, HTTPException, status from fastapi.encoders import jsonable_encoder from typing import List -import uuid +from bson import ObjectId from models import Book, BookUpdate, BookCreate router = APIRouter() -@router.post("/", response_description="Create a new book", status_code=status.HTTP_201_CREATED, response_model=Book) +@router.post("/", response_description="Create a new book", status_code=status.HTTP_201_CREATED) def create_book(request: Request, book: BookCreate = Body(...)): book = jsonable_encoder(book) - book["_id"] = str(uuid.uuid4()) # verify if book title already exists if request.app.database["books"].find_one({"title": book["title"]}): @@ -21,18 +20,22 @@ def create_book(request: Request, book: BookCreate = Body(...)): {"_id": new_book.inserted_id} ) - return created_book + created_book['id'] = str(created_book['_id']) + return Book(**created_book) @router.get("/", response_description="List all books", response_model=List[Book]) def list_books(request: Request): books = list(request.app.database["books"].find(limit=100)) + for book in books: + book['id'] = str(book['_id']) return books @router.get("/{id}", response_description="Get a single book by id", response_model=Book) def find_book(id: str, request: Request): - if (book := request.app.database["books"].find_one({"_id": id})) is not None: + if (book := request.app.database["books"].find_one({"_id": ObjectId(id)})) is not None: + book['id'] = str(book['_id']) return book raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Book with ID {id} not found") @@ -44,15 +47,16 @@ def update_book(id: str, request: Request, book: BookUpdate = Body(...)): if len(book) >= 1: update_result = request.app.database["books"].update_one( - {"_id": id}, {"$set": book} + {"_id": ObjectId(id)}, {"$set": book} ) if update_result.modified_count == 0: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Book with ID {id} not found") if ( - existing_book := request.app.database["books"].find_one({"_id": id}) + existing_book := request.app.database["books"].find_one({"_id": ObjectId(id)}) ) is not None: + existing_book['id'] = str(existing_book['_id']) return existing_book raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Book with ID {id} not found") @@ -60,7 +64,7 @@ def update_book(id: str, request: Request, book: BookUpdate = Body(...)): @router.delete("/{id}", response_description="Delete a book") def delete_book(id: str, request: Request, response: Response): - delete_result = request.app.database["books"].delete_one({"_id": id}) + delete_result = request.app.database["books"].delete_one({"_id": ObjectId(id)}) if delete_result.deleted_count == 1: response.status_code = status.HTTP_204_NO_CONTENT