-
Notifications
You must be signed in to change notification settings - Fork 671
Implement FastAPI Library Management API with CRUD operations #639
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
base: main
Are you sure you want to change the base?
Changes from all commits
471662c
7f51e40
20fa285
b5f27de
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,8 +28,6 @@ share/python-wheels/ | |
| MANIFEST | ||
|
|
||
| # PyInstaller | ||
| # Usually these files are written by a python script from a template | ||
| # before PyInstaller builds the exe, so as to inject date/other infos into it. | ||
| *.manifest | ||
| *.spec | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider also adding |
||
|
|
@@ -85,13 +83,9 @@ ipython_config.py | |
| .python-version | ||
|
|
||
| # pipenv | ||
| # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. | ||
| # However, in case of collaboration, if having platform-specific dependencies or dependencies | ||
| # having no cross-platform support, pipenv may install dependencies that don't work, or not | ||
| # install all needed dependencies. | ||
| #Pipfile.lock | ||
|
|
||
| # PEP 582; used by e.g. github.com/David-OConnor/pyflow | ||
| # PEP 582 | ||
| __pypackages__/ | ||
|
|
||
| # Celery stuff | ||
|
|
@@ -128,3 +122,6 @@ dmypy.json | |
| # Pyre type checker | ||
| .pyre/ | ||
| .idea/ | ||
|
|
||
| # SQLite database for FastAPI project | ||
| library.db | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| from sqlalchemy.orm import Session | ||
| from app import models, schemas | ||
|
|
||
| def get_author(db: Session, author_id: int): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The app imports |
||
| return db.query(models.Author).filter(models.Author.id == author_id).first() | ||
|
|
||
| def get_authors(db: Session, skip: int = 0, limit: int = 10): | ||
| return db.query(models.Author).offset(skip).limit(limit).all() | ||
|
|
||
| def create_author(db: Session, author: schemas.AuthorCreate): | ||
| db_author = models.Author(name=author.name, bio=author.bio) | ||
| db.add(db_author) | ||
| db.commit() | ||
| db.refresh(db_author) | ||
| return db_author | ||
|
|
||
| def get_books(db: Session, skip: int = 0, limit: int = 10): | ||
| return db.query(models.Book).offset(skip).limit(limit).all() | ||
|
|
||
| def get_books_by_author(db: Session, author_id: int, skip: int = 0, limit: int = 10): | ||
| return ( | ||
| db.query(models.Book) | ||
| .filter(models.Book.author_id == author_id) | ||
| .offset(skip) | ||
| .limit(limit) | ||
| .all() | ||
| ) | ||
|
|
||
| def create_book(db: Session, book: schemas.BookCreate, author_id: int): | ||
| db_book = models.Book(**book.dict(), author_id=author_id) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In create_book you build the ORM object with |
||
| db.add(db_book) | ||
| db.commit() | ||
| db.refresh(db_book) | ||
| return db_book | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| from sqlalchemy import create_engine | ||
| from sqlalchemy.orm import sessionmaker, declarative_base | ||
|
|
||
| SQLALCHEMY_DATABASE_URL = "sqlite:///./library.db" | ||
|
|
||
| engine = create_engine( | ||
| SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} | ||
| ) | ||
| SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) | ||
|
|
||
| Base = declarative_base() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| from fastapi import FastAPI, Depends, HTTPException | ||
| from sqlalchemy.orm import Session | ||
| from typing import Optional | ||
| from app import models, schemas, crud | ||
| from app.database import engine, SessionLocal, Base | ||
|
|
||
| Base.metadata.create_all(bind=engine) | ||
|
|
||
| app = FastAPI() | ||
|
|
||
| def get_db(): | ||
| db = SessionLocal() | ||
| try: | ||
|
Comment on lines
+10
to
+13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In create_author you commit immediately without handling possible DB integrity errors (for example duplicate |
||
| yield db | ||
| finally: | ||
| db.close() | ||
|
|
||
| @app.post("/authors/", response_model=schemas.Author) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This endpoint (and others using |
||
| def create_author(author: schemas.AuthorCreate, db: Session = Depends(get_db)): | ||
| return crud.create_author(db=db, author=author) | ||
|
|
||
| @app.get("/authors/", response_model=list[schemas.Author]) | ||
|
Comment on lines
+21
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The CRUD helper |
||
| def read_authors(skip: int = 0, limit: int = 10, db: Session = Depends(get_db)): | ||
| return crud.get_authors(db, skip=skip, limit=limit) | ||
|
|
||
| @app.get("/authors/{author_id}", response_model=schemas.Author) | ||
| def read_author(author_id: int, db: Session = Depends(get_db)): | ||
| db_author = crud.get_author(db, author_id=author_id) | ||
| if db_author is None: | ||
| raise HTTPException(status_code=404, detail="Author not found") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The CRUD helper uses There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line uses |
||
| return db_author | ||
|
|
||
| @app.post("/authors/{author_id}/books/", response_model=schemas.Book) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor: consider adding |
||
| def create_book_for_author(author_id: int, book: schemas.BookCreate, db: Session = Depends(get_db)): | ||
| db_author = crud.get_author(db, author_id=author_id) | ||
| if db_author is None: | ||
| raise HTTPException(status_code=404, detail="Author not found") | ||
| return crud.create_book(db=db, book=book, author_id=author_id) | ||
|
Comment on lines
+34
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The endpoint that creates a book for a given author doesn't check whether the author exists before creating the book. To meet expected behavior and avoid orphaned references, call |
||
|
|
||
| @app.get("/books/", response_model=list[schemas.Book]) | ||
| def read_books(skip: int = 0, limit: int = 10, author_id: Optional[int] = None, db: Session = Depends(get_db)): | ||
| if author_id is not None: | ||
| return crud.get_books_by_author(db, author_id=author_id, skip=skip, limit=limit) | ||
| return crud.get_books(db, skip=skip, limit=limit) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| from sqlalchemy import Column, Integer, String, Date, ForeignKey | ||
| from sqlalchemy.orm import relationship | ||
| from app.database import Base | ||
|
|
||
| class Author(Base): | ||
| __tablename__ = "authors" | ||
|
|
||
| id = Column(Integer, primary_key=True, index=True) | ||
| name = Column(String, unique=True, index=True) | ||
| bio = Column(String) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| books = relationship("Book", back_populates="author") | ||
|
Comment on lines
+6
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Book schema defines |
||
|
|
||
| class Book(Base): | ||
| __tablename__ = "books" | ||
|
|
||
| id = Column(Integer, primary_key=True, index=True) | ||
| title = Column(String, index=True) | ||
| summary = Column(String) | ||
| publication_date = Column(Date) | ||
|
Comment on lines
+18
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The SQLAlchemy Book model declares There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This sets Pydantic ORM behaviour using Pydantic v2 syntax ( |
||
| author_id = Column(Integer, ForeignKey("authors.id")) | ||
| author = relationship("Author", back_populates="books") | ||
|
Comment on lines
+20
to
+21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This helper returns all books for an author without pagination. The task requires listing books with pagination (skip, limit); consider adding skip and limit parameters and applying offset/limit here (or ensure the endpoint uses a CRUD function that supports pagination). |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| from typing import List, Optional | ||
| from datetime import date | ||
| from pydantic import BaseModel, Field | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because the DB model includes |
||
|
|
||
| # ---------- Book Schemas ---------- | ||
|
|
||
| class BookBase(BaseModel): | ||
| title: str | ||
| summary: Optional[str] = None | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
| publication_date: date | ||
|
|
||
| class BookCreate(BookBase): | ||
| pass | ||
|
|
||
| class Book(BookBase): | ||
| id: int | ||
| author_id: int | ||
|
|
||
| model_config = {"from_attributes": True} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This enables Pydantic ORM-mode using Pydantic v2 ( |
||
|
|
||
|
|
||
| # ---------- Author Schemas ---------- | ||
|
|
||
| class AuthorBase(BaseModel): | ||
| name: str | ||
| bio: Optional[str] = None | ||
|
|
||
| class AuthorCreate(AuthorBase): | ||
| pass | ||
|
|
||
| class Author(AuthorBase): | ||
| id: int | ||
| books: List[Book] = Field(default_factory=list) | ||
|
|
||
| model_config = {"from_attributes": True} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same Pydantic-compatibility note for the Author schema: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above for the Author schema: this |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
create_bookhelper usesmodels.Book(**book.dict(), author_id=author_id), which is fine now because yourBookschemas includesummaryandpublication_dateto match the ORM model. Keep in mind this approach relies on schema field names matching model attributes; if you later change schema names consider explicit mapping instead to avoid unexpected kwargs.