Форматирование кода, добавление лого, исправление тестов, улучшение эндпоинтов и документации

This commit is contained in:
2025-11-30 20:03:39 +03:00
parent a3ccd8a466
commit 99de648fa9
38 changed files with 1261 additions and 308 deletions

View File

@@ -1,6 +0,0 @@
from fastapi import APIRouter
import asyncpg
router = APIRouter(
prefix='/devices'
)

View File

@@ -0,0 +1 @@
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg"><rect x="10" y="10" width="80" height="80" rx="4" ry="4" fill="#fff" stroke="#000" stroke-width="2"/><rect x="20" y="15" width="60" height="70" rx="10" ry="10"/><rect x="20" y="15" width="60" height="66" rx="10" ry="10" fill="#fff"/><rect x="20" y="15" width="60" height="62" rx="10" ry="10"/><rect x="20" y="15" width="60" height="60" rx="10" ry="10" fill="#fff"/><rect x="20" y="15" width="60" height="56" rx="10" ry="10"/><rect x="20" y="15" width="60" height="54" rx="10" ry="10" fill="#fff"/><rect x="20" y="15" width="60" height="50" rx="10" ry="10"/><rect x="20" y="15" width="60" height="48" rx="10" ry="10" fill="#fff"/><rect x="20" y="15" width="60" height="44" rx="10" ry="10"/><rect x="22" y="21" width="2" height="58" rx="10" ry="10" stroke="#000" stroke-width="4"/><rect x="22" y="55" width="4" height="26" rx="2" ry="15"/><text x="50" y="40" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" stroke="#fff" stroke-width=".5" fill="none" font-size="20">『LiB』</text></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -2,16 +2,24 @@ from .author import Author
from .book import Book
from .genre import Genre
from .links import (
AuthorBookLink, GenreBookLink,
AuthorWithBooks, BookWithAuthors,
GenreWithBooks, BookWithGenres,
BookWithAuthorsAndGenres
AuthorBookLink,
GenreBookLink,
AuthorWithBooks,
BookWithAuthors,
GenreWithBooks,
BookWithGenres,
BookWithAuthorsAndGenres,
)
__all__ = [
'Author', 'Book', 'Genre',
'AuthorBookLink', 'AuthorWithBooks',
'BookWithAuthors', 'GenreBookLink',
'GenreWithBooks', 'BookWithGenres',
'BookWithAuthorsAndGenres'
"Author",
"Book",
"Genre",
"AuthorBookLink",
"AuthorWithBooks",
"BookWithAuthors",
"GenreBookLink",
"GenreWithBooks",
"BookWithGenres",
"BookWithAuthorsAndGenres",
]

View File

@@ -6,9 +6,9 @@ from .links import AuthorBookLink
if TYPE_CHECKING:
from .book import Book
class Author(AuthorBase, table=True):
id: Optional[int] = Field(default=None, primary_key=True, index=True)
books: List["Book"] = Relationship(
back_populates="authors",
link_model=AuthorBookLink
back_populates="authors", link_model=AuthorBookLink
)

View File

@@ -7,13 +7,12 @@ if TYPE_CHECKING:
from .author import Author
from .genre import Genre
class Book(BookBase, table=True):
id: Optional[int] = Field(default=None, primary_key=True, index=True)
authors: List["Author"] = Relationship(
back_populates="books",
link_model=AuthorBookLink
back_populates="books", link_model=AuthorBookLink
)
genres: List["Genre"] = Relationship(
back_populates="books",
link_model=GenreBookLink
back_populates="books", link_model=GenreBookLink
)

View File

@@ -6,9 +6,9 @@ from .links import GenreBookLink
if TYPE_CHECKING:
from .book import Book
class Genre(GenreBase, table=True):
id: Optional[int] = Field(default=None, primary_key=True, index=True)
books: List["Book"] = Relationship(
back_populates="genres",
link_model=GenreBookLink
back_populates="genres", link_model=GenreBookLink
)

View File

@@ -5,26 +5,35 @@ from library_service.models.dto.author import AuthorRead
from library_service.models.dto.book import BookRead
from library_service.models.dto.genre import GenreRead
class AuthorBookLink(SQLModel, table=True):
author_id: int | None = Field(default=None, foreign_key="author.id", primary_key=True)
author_id: int | None = Field(
default=None, foreign_key="author.id", primary_key=True
)
book_id: int | None = Field(default=None, foreign_key="book.id", primary_key=True)
class GenreBookLink(SQLModel, table=True):
genre_id: int | None = Field(default=None, foreign_key="genre.id", primary_key=True)
book_id: int | None = Field(default=None, foreign_key="book.id", primary_key=True)
class AuthorWithBooks(AuthorRead):
books: List[BookRead] = Field(default_factory=list)
class BookWithAuthors(BookRead):
authors: List[AuthorRead] = Field(default_factory=list)
class BookWithGenres(BookRead):
genres: List[GenreRead] = Field(default_factory=list)
class GenreWithBooks(GenreRead):
books: List[BookRead] = Field(default_factory=list)
class BookWithAuthorsAndGenres(BookRead):
authors: List[AuthorRead] = Field(default_factory=list)
genres: List[GenreRead] = Field(default_factory=list)

View File

@@ -1,19 +1,22 @@
from .author import (
AuthorBase, AuthorCreate, AuthorUpdate,
AuthorRead, AuthorList
)
from .book import (
BookBase, BookCreate, BookUpdate,
BookRead, BookList
)
from .author import AuthorBase, AuthorCreate, AuthorUpdate, AuthorRead, AuthorList
from .book import BookBase, BookCreate, BookUpdate, BookRead, BookList
from .genre import (
GenreBase, GenreCreate, GenreUpdate,
GenreRead, GenreList
)
from .genre import GenreBase, GenreCreate, GenreUpdate, GenreRead, GenreList
__all__ = [
'AuthorBase', 'AuthorCreate', 'AuthorUpdate', 'AuthorRead', 'AuthorList',
'BookBase', 'BookCreate', 'BookUpdate', 'BookRead', 'BookList',
'GenreBase', 'GenreCreate', 'GenreUpdate', 'GenreRead', 'GenreList',
"AuthorBase",
"AuthorCreate",
"AuthorUpdate",
"AuthorRead",
"AuthorList",
"BookBase",
"BookCreate",
"BookUpdate",
"BookRead",
"BookList",
"GenreBase",
"GenreCreate",
"GenreUpdate",
"GenreRead",
"GenreList",
]

View File

@@ -2,24 +2,27 @@ from sqlmodel import SQLModel
from pydantic import ConfigDict
from typing import Optional, List
class AuthorBase(SQLModel):
name: str
model_config = ConfigDict( #pyright: ignore
json_schema_extra={
"example": {"name": "author_name"}
}
model_config = ConfigDict( # pyright: ignore
json_schema_extra={"example": {"name": "author_name"}}
)
class AuthorCreate(AuthorBase):
pass
class AuthorUpdate(SQLModel):
name: Optional[str] = None
class AuthorRead(AuthorBase):
id: int
class AuthorList(SQLModel):
authors: List[AuthorRead]
total: int

View File

@@ -2,29 +2,31 @@ from sqlmodel import SQLModel
from pydantic import ConfigDict
from typing import Optional, List
class BookBase(SQLModel):
title: str
description: str
model_config = ConfigDict( #pyright: ignore
model_config = ConfigDict( # pyright: ignore
json_schema_extra={
"example": {
"title": "book_title",
"description": "book_description"
}
"example": {"title": "book_title", "description": "book_description"}
}
)
class BookCreate(BookBase):
pass
class BookUpdate(SQLModel):
title: Optional[str] = None
description: Optional[str] = None
class BookRead(BookBase):
id: int
class BookList(SQLModel):
books: List[BookRead]
total: int

View File

@@ -2,24 +2,27 @@ from sqlmodel import SQLModel
from pydantic import ConfigDict
from typing import Optional, List
class GenreBase(SQLModel):
name: str
model_config = ConfigDict( #pyright: ignore
json_schema_extra={
"example": {"name": "genre_name"}
}
model_config = ConfigDict( # pyright: ignore
json_schema_extra={"example": {"name": "genre_name"}}
)
class GenreCreate(GenreBase):
pass
class GenreUpdate(SQLModel):
name: Optional[str] = None
class GenreRead(GenreBase):
id: int
class GenreList(SQLModel):
genres: List[GenreRead]
total: int

View File

@@ -1,17 +1,27 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Path, Depends, HTTPException
from sqlmodel import Session, select
from library_service.settings import get_session
from library_service.models.db import Author, AuthorBookLink, Book, AuthorWithBooks
from library_service.models.dto import (
AuthorCreate, AuthorUpdate, AuthorRead,
AuthorList, BookRead
AuthorCreate,
AuthorUpdate,
AuthorRead,
AuthorList,
BookRead,
)
router = APIRouter(prefix="/authors", tags=["authors"])
# Create an author
@router.post("/", response_model=AuthorRead)
@router.post(
"/",
response_model=AuthorRead,
summary="Создать автора",
description="Добавляет автора в систему",
)
def create_author(author: AuthorCreate, session: Session = Depends(get_session)):
db_author = Author(**author.model_dump())
session.add(db_author)
@@ -19,41 +29,60 @@ def create_author(author: AuthorCreate, session: Session = Depends(get_session))
session.refresh(db_author)
return AuthorRead(**db_author.model_dump())
# Read authors
@router.get("/", response_model=AuthorList)
@router.get(
"/",
response_model=AuthorList,
summary="Получить список авторов",
description="Возвращает список всех авторов в системе",
)
def read_authors(session: Session = Depends(get_session)):
authors = session.exec(select(Author)).all()
return AuthorList(
authors=[AuthorRead(**author.model_dump()) for author in authors],
total=len(authors)
total=len(authors),
)
# Read an author with their books
@router.get("/{author_id}", response_model=AuthorWithBooks)
def get_author(author_id: int, session: Session = Depends(get_session)):
@router.get(
"/{author_id}",
response_model=AuthorWithBooks,
summary="Получить информацию об авторе",
description="Возвращает информацию об авторе и его книгах",
)
def get_author(
author_id: int = Path(..., description="ID автора (целое число, > 0)", gt=0),
session: Session = Depends(get_session),
):
author = session.get(Author, author_id)
if not author:
raise HTTPException(status_code=404, detail="Author not found")
books = session.exec(
select(Book)
.join(AuthorBookLink)
.where(AuthorBookLink.author_id == author_id)
select(Book).join(AuthorBookLink).where(AuthorBookLink.author_id == author_id)
).all()
book_reads = [BookRead(**book.model_dump()) for book in books]
author_data = author.model_dump()
author_data['books'] = book_reads
author_data["books"] = book_reads
return AuthorWithBooks(**author_data)
# Update an author
@router.put("/{author_id}", response_model=AuthorRead)
@router.put(
"/{author_id}",
response_model=AuthorRead,
summary="Обновить информацию об авторе",
description="Обновляет информацию об авторе в системе",
)
def update_author(
author_id: int,
author: AuthorUpdate,
session: Session = Depends(get_session)
author_id: int = Path(..., description="ID автора (целое число, > 0)", gt=0),
session: Session = Depends(get_session),
):
db_author = session.get(Author, author_id)
if not db_author:
@@ -67,9 +96,18 @@ def update_author(
session.refresh(db_author)
return AuthorRead(**db_author.model_dump())
# Delete an author
@router.delete("/{author_id}", response_model=AuthorRead)
def delete_author(author_id: int, session: Session = Depends(get_session)):
@router.delete(
"/{author_id}",
response_model=AuthorRead,
summary="Удалить автора",
description="Удаляет автора из системы",
)
def delete_author(
author_id: int = Path(..., description="ID автора (целое число, > 0)", gt=0),
session: Session = Depends(get_session),
):
author = session.get(Author, author_id)
if not author:
raise HTTPException(status_code=404, detail="Author not found")

View File

@@ -1,17 +1,28 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Path, Depends, HTTPException
from sqlmodel import Session, select
from library_service.models.db.links import BookWithAuthorsAndGenres
from library_service.settings import get_session
from library_service.models.db import Author, Book, BookWithAuthors, AuthorBookLink
from library_service.models.dto import (
AuthorRead, BookList, BookRead,
BookCreate, BookUpdate
AuthorRead,
BookList,
BookRead,
BookCreate,
BookUpdate,
)
router = APIRouter(prefix="/books", tags=["books"])
# Create a book
@router.post("/", response_model=Book)
@router.post(
"/",
response_model=Book,
summary="Создать книгу",
description="Добавляет книгу в систему",
)
def create_book(book: BookCreate, session: Session = Depends(get_session)):
db_book = Book(**book.model_dump())
session.add(db_book)
@@ -19,38 +30,67 @@ def create_book(book: BookCreate, session: Session = Depends(get_session)):
session.refresh(db_book)
return BookRead(**db_book.model_dump())
# Read books
@router.get("/", response_model=BookList)
@router.get(
"/",
response_model=BookList,
summary="Получить список книг",
description="Возвращает список всех книг в системе",
)
def read_books(session: Session = Depends(get_session)):
books = session.exec(select(Book)).all()
return BookList(
books=[BookRead(**book.model_dump()) for book in books],
total=len(books)
books=[BookRead(**book.model_dump()) for book in books], total=len(books)
)
# Read a book with their authors
@router.get("/{book_id}", response_model=BookWithAuthors)
def get_book(book_id: int, session: Session = Depends(get_session)):
# Read a book with their authors and genres
@router.get(
"/{book_id}",
response_model=BookWithAuthorsAndGenres,
summary="Получить информацию о книге",
description="Возвращает информацию о книге, её авторах и жанрах",
)
def get_book(
book_id: int = Path(..., description="ID книги (целое число, > 0)", gt=0),
session: Session = Depends(get_session),
):
book = session.get(Book, book_id)
if not book:
raise HTTPException(status_code=404, detail="Book not found")
authors = session.exec(
select(Author)
.join(AuthorBookLink)
.where(AuthorBookLink.book_id == book_id)
select(Author).join(AuthorBookLink).where(AuthorBookLink.book_id == book_id)
).all()
author_reads = [AuthorRead(**author.model_dump()) for author in authors]
genres = session.exec(
select(Genre).join(GenreBookLink).where(GenreBookLink.book_id == book_id)
).all()
genre_reads = [GenreRead(**genre.model_dump()) for genre in genres]
book_data = book.model_dump()
book_data['authors'] = author_reads
book_data["authors"] = author_reads
book_data["genres"] = genre_reads
return BookWithAuthors(**book_data)
# Update a book
@router.put("/{book_id}", response_model=Book)
def update_book(book_id: int, book: BookUpdate, session: Session = Depends(get_session)):
@router.put(
"/{book_id}",
response_model=Book,
summary="Обновить информацию о книге",
description="Обновляет информацию о книге в системе",
)
def update_book(
book: BookUpdate,
book_id: int = Path(..., description="ID книги (целое число, > 0)", gt=0),
session: Session = Depends(get_session),
):
db_book = session.get(Book, book_id)
if not db_book:
raise HTTPException(status_code=404, detail="Book not found")
@@ -61,13 +101,24 @@ def update_book(book_id: int, book: BookUpdate, session: Session = Depends(get_s
session.refresh(db_book)
return db_book
# Delete a book
@router.delete("/{book_id}", response_model=BookRead)
def delete_book(book_id: int, session: Session = Depends(get_session)):
@router.delete(
"/{book_id}",
response_model=BookRead,
summary="Удалить книгу",
description="Удаляет книгу их системы",
)
def delete_book(
book_id: int = Path(..., description="ID книги (целое число, > 0)", gt=0),
session: Session = Depends(get_session),
):
book = session.get(Book, book_id)
if not book:
raise HTTPException(status_code=404, detail="Book not found")
book_read = BookRead(id=(book.id or 0), title=book.title, description=book.description)
book_read = BookRead(
id=(book.id or 0), title=book.title, description=book.description
)
session.delete(book)
session.commit()
return book_read

View File

@@ -1,17 +1,27 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Path, Depends, HTTPException
from sqlmodel import Session, select
from library_service.settings import get_session
from library_service.models.db import Genre, GenreBookLink, Book, GenreWithBooks
from library_service.models.dto import (
GenreCreate, GenreUpdate, GenreRead,
GenreList, BookRead
GenreCreate,
GenreUpdate,
GenreRead,
GenreList,
BookRead,
)
router = APIRouter(prefix="/genres", tags=["genres"])
# Create a genre
@router.post("/", response_model=GenreRead)
@router.post(
"/",
response_model=GenreRead,
summary="Создать жанр",
description="Добавляет жанр книг в систему",
)
def create_genre(genre: GenreCreate, session: Session = Depends(get_session)):
db_genre = Genre(**genre.model_dump())
session.add(db_genre)
@@ -19,41 +29,59 @@ def create_genre(genre: GenreCreate, session: Session = Depends(get_session)):
session.refresh(db_genre)
return GenreRead(**db_genre.model_dump())
# Read genres
@router.get("/", response_model=GenreList)
@router.get(
"/",
response_model=GenreList,
summary="Получить список жанров",
description="Возвращает список всех жанров в системе",
)
def read_genres(session: Session = Depends(get_session)):
genres = session.exec(select(Genre)).all()
return GenreList(
genres=[GenreRead(**genre.model_dump()) for genre in genres],
total=len(genres)
genres=[GenreRead(**genre.model_dump()) for genre in genres], total=len(genres)
)
# Read a genre with their books
@router.get("/{genre_id}", response_model=GenreWithBooks)
def get_genre(genre_id: int, session: Session = Depends(get_session)):
@router.get(
"/{genre_id}",
response_model=GenreWithBooks,
summary="Получить информацию о жанре",
description="Возвращает информацию о жанре и книгах с ним",
)
def get_genre(
genre_id: int = Path(..., description="ID жанра (целое число, > 0)", gt=0),
session: Session = Depends(get_session),
):
genre = session.get(Genre, genre_id)
if not genre:
raise HTTPException(status_code=404, detail="Genre not found")
books = session.exec(
select(Book)
.join(GenreBookLink)
.where(GenreBookLink.genre_id == genre_id)
select(Book).join(GenreBookLink).where(GenreBookLink.genre_id == genre_id)
).all()
book_reads = [BookRead(**book.model_dump()) for book in books]
genre_data = genre.model_dump()
genre_data['books'] = book_reads
genre_data["books"] = book_reads
return GenreWithBooks(**genre_data)
# Update a genre
@router.put("/{genre_id}", response_model=GenreRead)
@router.put(
"/{genre_id}",
response_model=GenreRead,
summary="Обновляет информацию о жанре",
description="Обновляет информацию о жанре в системе",
)
def update_genre(
genre_id: int,
genre: GenreUpdate,
session: Session = Depends(get_session)
genre_id: int = Path(..., description="ID жанра (целое число, > 0)", gt=0),
session: Session = Depends(get_session),
):
db_genre = session.get(Genre, genre_id)
if not db_genre:
@@ -67,9 +95,18 @@ def update_genre(
session.refresh(db_genre)
return GenreRead(**db_genre.model_dump())
# Delete a genre
@router.delete("/{genre_id}", response_model=GenreRead)
def delete_genre(genre_id: int, session: Session = Depends(get_session)):
@router.delete(
"/{genre_id}",
response_model=GenreRead,
summary="Удалить жанр",
description="Удаляет автора из системы",
)
def delete_genre(
genre_id: int = Path(..., description="ID жанра (целое число, > 0)", gt=0),
session: Session = Depends(get_session),
):
genre = session.get(Genre, genre_id)
if not genre:
raise HTTPException(status_code=404, detail="Genre not found")

View File

@@ -1,13 +1,11 @@
from fastapi import APIRouter, Request, FastAPI
from fastapi import APIRouter, Path, Request, FastAPI
from fastapi.params import Depends
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.responses import FileResponse, HTMLResponse, JSONResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
from pathlib import Path
from datetime import datetime
from typing import Dict
from httpx import get
from library_service.settings import get_app
# Загрузка шаблонов
@@ -30,12 +28,28 @@ def get_info(app) -> Dict:
# Эндпоинт главной страницы
@router.get("/", response_class=HTMLResponse)
@router.get("/", include_in_schema=False)
async def root(request: Request, app=Depends(get_app)):
return templates.TemplateResponse(request, "index.html", get_info(app))
# Редирект иконки вкладки
@router.get("/favicon.ico", include_in_schema=False)
def redirect_favicon():
return RedirectResponse("/favicon.svg")
# Эндпоинт иконки вкладки
@router.get("/favicon.svg", include_in_schema=False)
async def favicon():
return FileResponse("library_service/favicon.svg", media_type="image/svg+xml")
# Эндпоинт информации об API
@router.get("/api/info")
@router.get(
"/api/info",
summary="Информация о сервисе",
description="Возвращает информацию о системе",
)
async def api_info(app=Depends(get_app)):
return JSONResponse(content=get_info(app))

View File

@@ -8,9 +8,17 @@ from library_service.models.dto import AuthorRead, BookRead, GenreRead
router = APIRouter(tags=["relations"])
# Add author to book
@router.post("/relationships/author-book", response_model=AuthorBookLink)
def add_author_to_book(author_id: int, book_id: int, session: Session = Depends(get_session)):
@router.post(
"/relationships/author-book",
response_model=AuthorBookLink,
summary="Связать автора и книгу",
description="Добавляет связь между автором и книгой в систему",
)
def add_author_to_book(
author_id: int, book_id: int, session: Session = Depends(get_session)
):
author = session.get(Author, author_id)
if not author:
raise HTTPException(status_code=404, detail="Author not found")
@@ -34,9 +42,17 @@ def add_author_to_book(author_id: int, book_id: int, session: Session = Depends(
session.refresh(link)
return link
# Remove author from book
@router.delete("/relationships/author-book", response_model=Dict[str, str])
def remove_author_from_book(author_id: int, book_id: int, session: Session = Depends(get_session)):
@router.delete(
"/relationships/author-book",
response_model=Dict[str, str],
summary="Разделить автора и книгу",
description="Удаляет связь между автором и книгой в системе",
)
def remove_author_from_book(
author_id: int, book_id: int, session: Session = Depends(get_session)
):
link = session.exec(
select(AuthorBookLink)
.where(AuthorBookLink.author_id == author_id)
@@ -50,15 +66,55 @@ def remove_author_from_book(author_id: int, book_id: int, session: Session = Dep
session.commit()
return {"message": "Relationship removed successfully"}
# Get relationships
@router.get("/relationships/genre-book", response_model=List[GenreBookLink])
def get_relationships(session: Session = Depends(get_session)):
relationships = session.exec(select(GenreBookLink)).all()
return relationships
# Add author to book
@router.post("/relationships/genre-book", response_model=GenreBookLink)
def add_genre_to_book(genre_id: int, book_id: int, session: Session = Depends(get_session)):
# Get author's books
@router.get(
"/authors/{author_id}/books/",
response_model=List[BookRead],
summary="Получить книги, написанные автором",
description="Возвращает все книги в системе, написанные автором",
)
def get_books_for_author(author_id: int, session: Session = Depends(get_session)):
author = session.get(Author, author_id)
if not author:
raise HTTPException(status_code=404, detail="Author not found")
books = session.exec(
select(Book).join(AuthorBookLink).where(AuthorBookLink.author_id == author_id)
).all()
return [BookRead(**book.model_dump()) for book in books]
# Get book's authors
@router.get(
"/books/{book_id}/authors/",
response_model=List[AuthorRead],
summary="Получить авторов книги",
description="Возвращает всех авторов книги в системе",
)
def get_authors_for_book(book_id: int, session: Session = Depends(get_session)):
book = session.get(Book, book_id)
if not book:
raise HTTPException(status_code=404, detail="Book not found")
authors = session.exec(
select(Author).join(AuthorBookLink).where(AuthorBookLink.book_id == book_id)
).all()
return [AuthorRead(**author.model_dump()) for author in authors]
# Add genre to book
@router.post(
"/relationships/genre-book",
response_model=GenreBookLink,
summary="Связать книгу и жанр",
description="Добавляет связь между книгой и жанром в систему",
)
def add_genre_to_book(
genre_id: int, book_id: int, session: Session = Depends(get_session)
):
genre = session.get(Genre, genre_id)
if not genre:
raise HTTPException(status_code=404, detail="Genre not found")
@@ -82,9 +138,17 @@ def add_genre_to_book(genre_id: int, book_id: int, session: Session = Depends(ge
session.refresh(link)
return link
# Remove author from book
@router.delete("/relationships/genre-book", response_model=Dict[str, str])
def remove_genre_from_book(genre_id: int, book_id: int, session: Session = Depends(get_session)):
@router.delete(
"/relationships/genre-book",
response_model=Dict[str, str],
summary="Разделить жанр и книгу",
description="Удаляет связь между жанром и книгой в системе",
)
def remove_genre_from_book(
genre_id: int, book_id: int, session: Session = Depends(get_session)
):
link = session.exec(
select(GenreBookLink)
.where(GenreBookLink.genre_id == genre_id)
@@ -98,38 +162,40 @@ def remove_genre_from_book(genre_id: int, book_id: int, session: Session = Depen
session.commit()
return {"message": "Relationship removed successfully"}
# Get relationships
@router.get("/relationships/genre-book", response_model=List[GenreBookLink])
def get__genre_relationships(session: Session = Depends(get_session)):
relationships = session.exec(select(GenreBookLink)).all()
return relationships
# Get author's books
@router.get("/authors/{author_id}/books/", response_model=List[BookRead])
def get_books_for_author(author_id: int, session: Session = Depends(get_session)):
author = session.get(Author, author_id)
if not author:
raise HTTPException(status_code=404, detail="Author not found")
# Get genre's books
@router.get(
"/genres/{author_id}/books/",
response_model=List[BookRead],
summary="Получить книги, написанные в жанре",
description="Возвращает все книги в системе в этом жанре",
)
def get_books_for_genre(genre_id: int, session: Session = Depends(get_session)):
genre = session.get(Genre, genre_id)
if not genre:
raise HTTPException(status_code=404, detail="Genre not found")
books = session.exec(
select(Book)
.join(AuthorBookLink)
.where(AuthorBookLink.author_id == author_id)
select(Book).join(GenreBookLink).where(GenreBookLink.author_id == genre_id)
).all()
return [BookRead(**book.model_dump()) for book in books]
# Get book's authors
@router.get("/books/{book_id}/authors/", response_model=List[AuthorRead])
# Get book's genres
@router.get(
"/books/{book_id}/genres/",
response_model=List[GenreRead],
summary="Получить жанры книги",
description="Возвращает все жанры книги в системе",
)
def get_authors_for_book(book_id: int, session: Session = Depends(get_session)):
book = session.get(Book, book_id)
if not book:
raise HTTPException(status_code=404, detail="Book not found")
authors = session.exec(
select(Author)
.join(AuthorBookLink)
.where(AuthorBookLink.book_id == book_id)
genres = session.exec(
select(Genre).join(GenreBookLink).where(GenreBookLink.book_id == book_id)
).all()
return [AuthorRead(**author.model_dump()) for author in authors]
return [GenreRead(**author.model_dump()) for genre in genres]

View File

@@ -9,6 +9,7 @@ load_dotenv()
with open("pyproject.toml") as f:
config = load(f)
# Dependency to get the FastAPI application instance
def get_app() -> FastAPI:
return FastAPI(
@@ -35,10 +36,11 @@ def get_app() -> FastAPI:
{
"name": "misc",
"description": "Miscellaneous operations.",
}
]
},
],
)
USER = os.getenv("POSTGRES_USER")
PASSWORD = os.getenv("POSTGRES_PASSWORD")
DATABASE = os.getenv("POSTGRES_DB")
@@ -50,6 +52,7 @@ if not USER or not PASSWORD or not DATABASE or not HOST:
POSTGRES_DATABASE_URL = f"postgresql://{USER}:{PASSWORD}@{HOST}:5432/{DATABASE}"
engine = create_engine(POSTGRES_DATABASE_URL, echo=True, future=True)
# Dependency to get a database session
def get_session():
with Session(engine) as session:

View File

@@ -43,6 +43,7 @@
</style>
</head>
<body>
<img src="/favicon.ico" />
<h1>Welcome to {{ app_info.title }}!</h1>
<p>Description: {{ app_info.description }}</p>
<p>Version: {{ app_info.version }}</p>
@@ -51,6 +52,9 @@
<ul>
<li><a href="/docs">Swagger UI</a></li>
<li><a href="/redoc">ReDoc</a></li>
<li>
<a href="https://github.com/wowlikon/LibraryAPI">Source Code</a>
</li>
</ul>
</body>
</html>