From 8e10af0bfd39d1560eb6f2d98431d530f352f835 Mon Sep 17 00:00:00 2001 From: wowlikon Date: Wed, 25 Feb 2026 00:12:09 +0300 Subject: [PATCH] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=D0=B0=D1=86=D0=B8=D0=B8=20swagger=20=D0=B8=20?= =?UTF-8?q?=D0=BE=D1=88=D0=B8=D0=B1=D0=BE=D0=BA=20=D0=BE=D0=B1=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B4=D0=B0=D0=BD=D0=BD?= =?UTF-8?q?=D1=8B=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + library_service/models/dto/author.py | 2 +- library_service/models/dto/book.py | 8 ++++---- library_service/models/dto/genre.py | 2 +- library_service/models/dto/misc.py | 2 +- library_service/models/dto/user.py | 6 +++--- library_service/routers/authors.py | 2 ++ library_service/routers/books.py | 22 +++++++++++++++++++--- library_service/routers/genres.py | 2 ++ pyproject.toml | 6 ++++-- uv.lock | 2 +- 11 files changed, 39 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index 03acf24..885507d 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,7 @@ coverage.xml *.py,cover .hypothesis/ .pytest_cache/ +.ruff_cache/ # pyenv .python-version diff --git a/library_service/models/dto/author.py b/library_service/models/dto/author.py index 0386850..3ac60b5 100644 --- a/library_service/models/dto/author.py +++ b/library_service/models/dto/author.py @@ -25,7 +25,7 @@ class AuthorCreate(AuthorBase): class AuthorUpdate(SQLModel): """Модель автора для обновления""" - name: str | None = Field(None, description="Псевдоним") + name: str | None = Field(None, description="Псевдоним", schema_extra={"examples": [None]}) class AuthorRead(AuthorBase): diff --git a/library_service/models/dto/book.py b/library_service/models/dto/book.py index 495944a..5c2dc3c 100644 --- a/library_service/models/dto/book.py +++ b/library_service/models/dto/book.py @@ -35,10 +35,10 @@ class BookCreate(BookBase): class BookUpdate(SQLModel): """Модель книги для обновления""" - title: str | None = Field(None, description="Название") - description: str | None = Field(None, description="Описание") - page_count: int | None = Field(None, description="Количество страниц") - status: BookStatus | None = Field(None, description="Статус") + title: str | None = Field(None, description="Название", schema_extra={"examples": [None]}) + description: str | None = Field(None, description="Описание", schema_extra={"examples": [None]}) + page_count: int | None = Field(None, description="Количество страниц", schema_extra={"examples": [None]}) + status: BookStatus | None = Field(None, description="Статус", schema_extra={"examples": [None]}) class BookRead(BookBase): diff --git a/library_service/models/dto/genre.py b/library_service/models/dto/genre.py index 7249f26..b1a5bd2 100644 --- a/library_service/models/dto/genre.py +++ b/library_service/models/dto/genre.py @@ -25,7 +25,7 @@ class GenreCreate(GenreBase): class GenreUpdate(SQLModel): """Модель жанра для обновления""" - name: str | None = Field(None, description="Название") + name: str | None = Field(None, description="Название", schema_extra={"examples": [None]}) class GenreRead(GenreBase): diff --git a/library_service/models/dto/misc.py b/library_service/models/dto/misc.py index a02d4dc..c9d5cb7 100644 --- a/library_service/models/dto/misc.py +++ b/library_service/models/dto/misc.py @@ -104,7 +104,7 @@ class UserUpdateByAdmin(UserUpdate): """Обновление пользователя администратором""" is_active: bool = Field(True, description="Не является ли заблокированным") - roles: list[str] | None = Field(None, description="Роли") + roles: list[str] | None = Field(None, description="Роли", schema_extra={"examples": [None]}) class LoginResponse(SQLModel): diff --git a/library_service/models/dto/user.py b/library_service/models/dto/user.py index b28501c..693ce74 100644 --- a/library_service/models/dto/user.py +++ b/library_service/models/dto/user.py @@ -71,9 +71,9 @@ class UserRead(UserBase): class UserUpdate(SQLModel): """Модель пользователя для обновления""" - email: EmailStr | None = Field(None, description="Email") - full_name: str | None = Field(None, description="Полное имя") - password: str | None = Field(None, description="Пароль") + email: EmailStr | None = Field(None, description="Email", schema_extra={"examples": [None]}) + full_name: str | None = Field(None, description="Полное имя", schema_extra={"examples": [None]}) + password: str | None = Field(None, description="Пароль", schema_extra={"examples": [None]}) class UserList(SQLModel): diff --git a/library_service/routers/authors.py b/library_service/routers/authors.py index bb4dbd7..0142af1 100644 --- a/library_service/routers/authors.py +++ b/library_service/routers/authors.py @@ -105,6 +105,8 @@ def update_author( update_data = author.model_dump(exclude_unset=True) for field, value in update_data.items(): + if value is None: + continue setattr(db_author, field, value) session.commit() diff --git a/library_service/routers/books.py b/library_service/routers/books.py index c0dffda..d3b509d 100644 --- a/library_service/routers/books.py +++ b/library_service/routers/books.py @@ -62,7 +62,12 @@ from sqlalchemy import select, func, distinct, case, exists from sqlalchemy.orm import selectinload -@router.get("/filter", response_model=BookFilteredList) +@router.get( + "/filter", + response_model=BookFilteredList, + summary="Поиск по книгам", + description="Фильтрует и ищет книги", +) def filter_books( current_user: OptionalAuth, session: Session = Depends(get_session), @@ -74,6 +79,7 @@ def filter_books( page: int = Query(1, gt=0), size: int = Query(20, gt=0, le=100), ): + """Выполняет поиск книги в системе""" statement = select(Book).options( selectinload(Book.authors), selectinload(Book.genres), defer(Book.embedding) # ty: ignore ) @@ -315,13 +321,18 @@ def delete_book( session.commit() return book_read -@router.post("/{book_id}/preview") +@router.post( + "/{book_id}/preview", + summary="Утановить обложку книги", + description="Меняет обложку книги в системе", +) async def upload_book_preview( current_user: RequireStaff, file: UploadFile = File(...), book_id: int = Path(..., gt=0), session: Session = Depends(get_session) ): + """Загружает обложку книги в систему""" if not (file.content_type or "").startswith("image/"): raise HTTPException( status_code=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE, @@ -369,12 +380,17 @@ async def upload_book_preview( } -@router.delete("/{book_id}/preview") +@router.delete( + "/{book_id}/preview", + summary="Удалить обложку книги", + description="Удаляет обложку книги в системе", +) async def remove_book_preview( current_user: RequireStaff, book_id: int = Path(..., gt=0), session: Session = Depends(get_session) ): + """Убирает обложку книги в системе""" book = session.get(Book, book_id) if not book: raise HTTPException(status.HTTP_404_NOT_FOUND, "Book not found") diff --git a/library_service/routers/genres.py b/library_service/routers/genres.py index 2e06999..fbc12e3 100644 --- a/library_service/routers/genres.py +++ b/library_service/routers/genres.py @@ -104,6 +104,8 @@ def update_genre( update_data = genre.model_dump(exclude_unset=True) for field, value in update_data.items(): + if value is None: + continue setattr(db_genre, field, value) session.commit() diff --git a/pyproject.toml b/pyproject.toml index da02e43..3523350 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,9 @@ [project] name = "LiB" -version = "0.9.0" -description = "Это простое API для управления авторами, книгами и их жанрами." +version = "0.9.1" +description = """REST API библиотечной системы. Позволяет вести каталог книг с авторами и жанрами, отслеживать выдачи и возвраты книг читателями. +С ролевой моделью: читатель, библиотекарь, администратор. Для авторизованных пользователей доступен векторный поиск книг по названию и описанию. +Для безопасности используется шифрование, CAPTCH, 2FA и одноразовые коды коды восстановления пароля.""" authors = [{ name = "wowlikon" }] readme = "README.md" requires-python = ">=3.12" diff --git a/uv.lock b/uv.lock index b4f9b13..a45e81a 100644 --- a/uv.lock +++ b/uv.lock @@ -627,7 +627,7 @@ sdist = { url = "https://files.pythonhosted.org/packages/e8/ef/324f4a28ed0152a32 [[package]] name = "lib" -version = "0.9.0" +version = "0.9.1" source = { editable = "." } dependencies = [ { name = "aiofiles" },