Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

πŸ› Bug Fix: Inconsistent API Dev project #695

Merged
merged 4 commits into from
Oct 19, 2024
Merged
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
19 changes: 8 additions & 11 deletions api_dev/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,19 @@
from sqlalchemy.ext.declarative import declarative_base

""" create_engine is used to establish connection to database
session_maker is like a fcatory that produce new Session object when called
Session are used to interact with database
declarative_base is used to define base class from which our all model classes will inhert
sessionmaker is like a factory that produces new Session objects when called
Session is used to interact with the database
declarative_base is used to define a base class from which our model classes will inherit
"""

""" here sqlite is used as database for simplicity but one can use mysql ,postgress also
for using mysql following steps need to be done
1.install pymysql (pip install pymysql)
2.create a new schema using mysql workbench
3.mysql+pymysql://root:<your-password>@localhost:3306/<database-name> paste this instead of sqlite:///./test.db
"""
# Use SQLite for simplicity, but MySQL/PostgreSQL can also be used
URL_DATABASE = "sqlite:///./test.db"


engine = create_engine(URL_DATABASE)

SessionLocal = sessionmaker(autocommit=False ,autoflush=False ,bind=engine)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

Base = declarative_base()

# Bug Fix: Ensure database tables are created when the engine is initialized
Base.metadata.create_all(bind=engine)
156 changes: 46 additions & 110 deletions api_dev/main.py
Original file line number Diff line number Diff line change
@@ -1,160 +1,96 @@
""" imports """
from fastapi import FastAPI ,HTTPException ,Depends ,status
from fastapi import FastAPI, HTTPException, Depends, status
from pydantic import BaseModel
from typing import Annotated, List
import models
from database import engine ,SessionLocal
from database import engine, SessionLocal
from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError
from datetime import datetime

""" instantiating an object of FastAPI class"""
""" Instantiating an object of FastAPI class """
app = FastAPI()
models.Base.metadata.create_all(bind=engine)

""" Defining class BookBase which is inheriting from pydantic BaseModel
pydantic model are used to define the structure and data validation rules for
incoming and outgoing and data
"""
""" Pydantic model for data validation """
class BookBase(BaseModel):
title:str
author:str
published_at:datetime
publication:str
pages:int

""" this method monitors the life cycle of database session."""
title: str
author: str
published_at: datetime
publication: str
pages: int

class Config:
orm_mode = True

""" Database session lifecycle management """
def get_database():
database = SessionLocal()
try:
yield database
finally:
database.close()

database_dependency = Annotated[Session ,Depends(get_database)]

""" API endpoints """
#On reaching this /books/ with post request create_book() method will be called
@app.post('/books/',status_code=status.HTTP_201_CREATED ,response_model=BookBase)
async def create_book(book:BookBase ,db:database_dependency):
"""
Create a new book entry in the database.

Parameters:
- book (BookBase): An object that contains the data required to create a book. This is validated by the BookBase Pydantic model.
- db (database_dependency): A database session object used to interact with the database.
database_dependency = Annotated[Session, Depends(get_database)]

Returns:
- BookBase: The newly created book object, returned as a response in the format defined by the BookBase model.
""" API Endpoints """

Process:
1. Receives book data from the request.
2. Converts the Pydantic `BookBase` object into a dictionary.
3. Creates a new `Book` model instance with the data and adds it to the database session.
4. Commits the changes to persist the new book in the database.
5. Refreshes the instance to update it with any changes made by the database (e.g., the auto-generated `id`).
6. Returns the newly created book to the client.
"""
new_book = models.Book(**book.dict())
db.add(new_book)
db.commit()
db.refresh(new_book)
return new_book
#On reaching this /books/ with get request create_book() method will be called
@app.get('/books/',response_model=List[BookBase])
async def get_all_books(db:database_dependency):
# Create a new book
@app.post('/books/', status_code=status.HTTP_201_CREATED, response_model=BookBase)
async def create_book(book: BookBase, db: database_dependency):
"""
Retrieve all book from database

parameters:
- db (database_dependency): A database session object used to interact with the database.

Returns:
- List[BookBase]: A list of all book objects in the database, returned in the format defined by the BookBase model.

Process:
1. Queries the database to retrieve all book entries using SQLAlchemy.
2. Returns the list of book objects, which FastAPI will serialize into JSON format.
Create a new book entry, handle duplicate entries
"""
try:
new_book = models.Book(**book.dict())
db.add(new_book)
db.commit()
db.refresh(new_book)
return new_book
except IntegrityError:
db.rollback()
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Book with this title or author already exists")

# Get all books
@app.get('/books/', response_model=List[BookBase])
async def get_all_books(db: database_dependency):
""" Retrieve all books from the database """
books = db.query(models.Book).all()
return books

@app.get('/books/{book_id}')
async def get_book(book_id:int ,db:database_dependency):
"""
Retrieve Book with given book id

parameters:
- book_id : id of book
- db (database_dependency): A database session object used to interact with the database

Returns:
- book object if it exist

Process:
1. Queries the database to retrive book with given book_id using SQLAlchemy
2. Checks if book exist then return it and if not then raise HTTPException
"""
# Get a specific book by ID
@app.get('/books/{book_id}', response_model=BookBase)
async def get_book(book_id: int, db: database_dependency):
""" Retrieve a book with the given book ID """
book = db.query(models.Book).filter(models.Book.id == book_id).first()
if book:
return book
else:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Book not found")


# Update a book by ID
@app.patch('/books/{book_id}', response_model=BookBase)
async def update_book(book_id: int, book_data: BookBase, db: database_dependency):
"""
Update the details of a specific book identified by its ID.

Parameters:
- book_id (int): The ID of the book to be updated.
- book_data (BookBase): An object containing the updated book data.
- db (database_dependency): A database session object used to interact with the database.

Returns:
- BookBase: The updated book object.

Process:
1. Queries the database to find the book with the given book_id.
2. Checks if the book exists; if not, raises an HTTPException with a 404 status code.
3. Updates the book's attributes with the provided data, excluding any unset fields.
4. Commits the changes to the database and refreshes the book object to reflect the latest data.
5. Returns the updated book object.
"""
""" Update book details by ID """
book = db.query(models.Book).filter(models.Book.id == book_id).first()
if not book:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Book not found")


for key, value in book_data.dict(exclude_unset=True).items():
setattr(book, key, value)

db.commit()
db.refresh(book)
return book


# Delete a book by ID
@app.delete('/books/{book_id}', status_code=status.HTTP_204_NO_CONTENT)
async def delete_book(book_id: int, db: database_dependency):
"""
Delete the book with the given ID from the database.

Parameters:
- book_id (int): The ID of the book to be deleted.
- db (database_dependency): A database session object used to interact with the database.

Returns:
- None: Returns a 204 No Content response upon successful deletion.

Process:
1. Queries the database to find the book with the given book_id.
2. Checks if the book exists; if not, raises an HTTPException with a 404 status code.
3. Deletes the book from the database and commits the changes.
4. Returns a success message
"""
""" Delete a book with the given ID """
book = db.query(models.Book).filter(models.Book.id == book_id).first()
if not book:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Book not found")

db.delete(book)
db.commit()
return {"message": "Book deleted successfully"}
return None # Fixed: Return None to respect 204 No Content status code
18 changes: 6 additions & 12 deletions api_dev/models.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
""" imports """
from sqlalchemy import Boolean ,Column ,Integer ,String ,DateTime
from sqlalchemy import Boolean, Column, Integer, String, DateTime
from database import Base

""" Column: as the name suggest it is used to create new column in the database
Boolean ,Integer ,String ,Datetime :- these are the datatype which will be stored in the database
"""

"""class Book will be table inside the database and it is inheriting from class Base
__tablename__ : name of the table
id ,title ,author .. these are the columns inside table
"""
Book model for the 'books' table in the database
"""
class Book(Base):
__tablename__ = 'books'

id = Column(Integer ,primary_key=True ,index=True)
title = Column(String(50) ,unique=True)
author = Column(String(50) ,unique=True)
id = Column(Integer, primary_key=True, index=True)
title = Column(String(100), unique=True) # Fixed: Increased string length for title
author = Column(String(100)) # Fixed: Removed unique constraint on author
published_at = Column(DateTime)
publication = Column(String(100))
pages = Column(Integer)