diff --git a/api_dev/database.py b/api_dev/database.py index 3722d8b4..45c04d8a 100644 --- a/api_dev/database.py +++ b/api_dev/database.py @@ -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:@localhost:3306/ 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) diff --git a/api_dev/main.py b/api_dev/main.py index 8dd8c028..e8bab4fc 100644 --- a/api_dev/main.py +++ b/api_dev/main.py @@ -1,28 +1,29 @@ """ 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: @@ -30,101 +31,51 @@ def get_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) @@ -132,29 +83,14 @@ async def update_book(book_id: int, book_data: BookBase, db: database_dependency 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"} \ No newline at end of file + return None # Fixed: Return None to respect 204 No Content status code diff --git a/api_dev/models.py b/api_dev/models.py index 7d73089f..cd3eea88 100644 --- a/api_dev/models.py +++ b/api_dev/models.py @@ -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) -