diff --git a/crud.py b/crud.py new file mode 100644 index 00000000..91f0abc0 --- /dev/null +++ b/crud.py @@ -0,0 +1,55 @@ +from sqlalchemy.orm import Session + +import models +import schemas + + +def get_author(db: Session, author_id: int): + return db.query(models.Author).filter(models.Author.id == author_id).first() + + +def get_author_by_name(db: Session, name: str): + return db.query(models.Author).filter(models.Author.name == name).first() + + +def get_authors(db: Session, skip: int = 0, limit: int = 100): + 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_book(db: Session, book_id: int): + return db.query(models.Book).filter(models.Book.id == book_id).first() + + +def get_books(db: Session, skip: int = 0, limit: int = 100): + 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 = 100): + 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( + title=book.title, + summary=book.summary, + publication_date=book.publication_date, + author_id=author_id, + ) + db.add(db_book) + db.commit() + db.refresh(db_book) + return db_book diff --git a/database.py b/database.py new file mode 100644 index 00000000..ae8ff98a --- /dev/null +++ b/database.py @@ -0,0 +1,13 @@ +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker + +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() diff --git a/main.py b/main.py new file mode 100644 index 00000000..568429a6 --- /dev/null +++ b/main.py @@ -0,0 +1,64 @@ +from fastapi import Depends, FastAPI, HTTPException, Query +from sqlalchemy.orm import Session + +import crud +import models +import schemas +from database import SessionLocal, engine + +models.Base.metadata.create_all(bind=engine) + +app = FastAPI(title="Library Management API") + + +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() + + +@app.post("/authors/", response_model=schemas.AuthorRead, status_code=201) +def create_author(author: schemas.AuthorCreate, db: Session = Depends(get_db)): + existing = crud.get_author_by_name(db, name=author.name) + if existing: + raise HTTPException(status_code=400, detail="Author with this name already exists") + return crud.create_author(db=db, author=author) + + +@app.get("/authors/", response_model=list[schemas.AuthorRead]) +def read_authors( + skip: int = Query(0, ge=0), limit: int = Query(100, ge=1), db: Session = Depends(get_db) +): + return crud.get_authors(db, skip=skip, limit=limit) + + +@app.get("/authors/{author_id}", response_model=schemas.AuthorRead) +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") + return db_author + + +@app.post("/authors/{author_id}/books/", response_model=schemas.BookRead, status_code=201) +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) + + +@app.get("/books/", response_model=list[schemas.BookRead]) +def read_books( + author_id: int | None = Query(None, alias="authorId"), + skip: int = Query(0, ge=0), + limit: int = Query(100, ge=1), + 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) diff --git a/models.py b/models.py new file mode 100644 index 00000000..2ac13548 --- /dev/null +++ b/models.py @@ -0,0 +1,26 @@ +from sqlalchemy import Column, Date, ForeignKey, Integer, String +from sqlalchemy.orm import relationship + +from database import Base + + +class Author(Base): + __tablename__ = "authors" + + id = Column(Integer, primary_key=True, index=True) + name = Column(String, unique=True, nullable=False, index=True) + bio = Column(String, nullable=True) + + books = relationship("Book", back_populates="author", cascade="all, delete-orphan") + + +class Book(Base): + __tablename__ = "books" + + id = Column(Integer, primary_key=True, index=True) + title = Column(String, nullable=False, index=True) + summary = Column(String, nullable=True) + publication_date = Column(Date, nullable=False) + author_id = Column(Integer, ForeignKey("authors.id"), nullable=False) + + author = relationship("Author", back_populates="books") diff --git a/schemas.py b/schemas.py new file mode 100644 index 00000000..5947757e --- /dev/null +++ b/schemas.py @@ -0,0 +1,39 @@ +from datetime import date +from typing import List, Optional + +from pydantic import BaseModel + + +class BookBase(BaseModel): + title: str + summary: Optional[str] = None + publication_date: date + + +class BookCreate(BookBase): + pass + + +class BookRead(BookBase): + id: int + author_id: int + + class Config: + orm_mode = True + + +class AuthorBase(BaseModel): + name: str + bio: Optional[str] = None + + +class AuthorCreate(AuthorBase): + pass + + +class AuthorRead(AuthorBase): + id: int + books: List[BookRead] = [] + + class Config: + orm_mode = True