From 748016bdab0a3c5e12ced2b92ebc63d53b4e6246 Mon Sep 17 00:00:00 2001 From: maryhipp Date: Wed, 14 Jun 2023 11:20:23 -0700 Subject: [PATCH] routes working --- invokeai/app/api/routers/board_images.py | 69 +++++++++ invokeai/app/api/routers/boards.py | 143 +++++++++--------- invokeai/app/api_app.py | 6 +- invokeai/app/services/board_images.py | 2 +- invokeai/app/services/board_record_storage.py | 35 +---- invokeai/app/services/boards.py | 4 +- invokeai/app/services/image_record_storage.py | 11 -- invokeai/app/services/models/board_record.py | 57 +++++++ 8 files changed, 203 insertions(+), 124 deletions(-) create mode 100644 invokeai/app/api/routers/board_images.py create mode 100644 invokeai/app/services/models/board_record.py diff --git a/invokeai/app/api/routers/board_images.py b/invokeai/app/api/routers/board_images.py new file mode 100644 index 0000000000..b206ab500d --- /dev/null +++ b/invokeai/app/api/routers/board_images.py @@ -0,0 +1,69 @@ +from fastapi import Body, HTTPException, Path, Query +from fastapi.routing import APIRouter +from invokeai.app.services.board_record_storage import BoardRecord, BoardChanges +from invokeai.app.services.image_record_storage import OffsetPaginatedResults +from invokeai.app.services.models.board_record import BoardDTO +from invokeai.app.services.models.image_record import ImageDTO + +from ..dependencies import ApiDependencies + +board_images_router = APIRouter(prefix="/v1/board_images", tags=["boards"]) + + +@board_images_router.post( + "/", + operation_id="create_board_image", + responses={ + 201: {"description": "The image was added to a board successfully"}, + }, + status_code=201, +) +async def create_board_image( + board_id: str = Body(description="The id of the board to add to"), + image_name: str = Body(description="The name of the image to add"), +): + """Creates a board_image""" + try: + result = ApiDependencies.invoker.services.board_images.add_image_to_board(board_id=board_id, image_name=image_name) + return result + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to add to board") + +@board_images_router.delete( + "/", + operation_id="remove_board_image", + responses={ + 201: {"description": "The image was removed from the board successfully"}, + }, + status_code=201, +) +async def remove_board_image( + board_id: str = Body(description="The id of the board"), + image_name: str = Body(description="The name of the image to remove"), +): + """Deletes a board_image""" + try: + result = ApiDependencies.invoker.services.board_images.remove_image_from_board(board_id=board_id, image_name=image_name) + return result + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to update board") + + + +@board_images_router.get( + "/{board_id}", + operation_id="list_board_images", + response_model=OffsetPaginatedResults[ImageDTO], +) +async def list_board_images( + board_id: str = Path(description="The id of the board"), + offset: int = Query(default=0, description="The page offset"), + limit: int = Query(default=10, description="The number of boards per page"), +) -> OffsetPaginatedResults[ImageDTO]: + """Gets a list of images for a board""" + + results = ApiDependencies.invoker.services.board_images.get_images_for_board( + board_id, + ) + return results + diff --git a/invokeai/app/api/routers/boards.py b/invokeai/app/api/routers/boards.py index f3a76e08d3..618e8f990b 100644 --- a/invokeai/app/api/routers/boards.py +++ b/invokeai/app/api/routers/boards.py @@ -1,86 +1,79 @@ -# from fastapi import Body, HTTPException, Path, Query -# from fastapi.routing import APIRouter -# from invokeai.app.services.board_record_storage import BoardRecord, BoardChanges -# from invokeai.app.services.image_record_storage import OffsetPaginatedResults +from fastapi import Body, HTTPException, Path, Query +from fastapi.routing import APIRouter +from invokeai.app.services.board_record_storage import BoardRecord, BoardChanges +from invokeai.app.services.image_record_storage import OffsetPaginatedResults +from invokeai.app.services.models.board_record import BoardDTO -# from ..dependencies import ApiDependencies +from ..dependencies import ApiDependencies -# boards_router = APIRouter(prefix="/v1/boards", tags=["boards"]) +boards_router = APIRouter(prefix="/v1/boards", tags=["boards"]) -# @boards_router.post( -# "/", -# operation_id="create_board", -# responses={ -# 201: {"description": "The board was created successfully"}, -# }, -# status_code=201, -# ) -# async def create_board( -# board_name: str = Body(description="The name of the board to create"), -# ): -# """Creates a board""" -# try: -# result = ApiDependencies.invoker.services.boards.save(board_name=board_name) -# return result -# except Exception as e: -# raise HTTPException(status_code=500, detail="Failed to create board") +@boards_router.post( + "/", + operation_id="create_board", + responses={ + 201: {"description": "The board was created successfully"}, + }, + status_code=201, +) +async def create_board( + board_name: str = Body(description="The name of the board to create"), +): + """Creates a board""" + try: + result = ApiDependencies.invoker.services.boards.create(board_name=board_name) + return result + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to create board") + +@boards_router.patch( + "/{board_id}", + operation_id="update_board", + responses={ + 201: {"description": "The board was updated successfully"}, + }, + status_code=201, +) +async def update_board( + board_id: str = Path(description="The id of board to update"), + changes: BoardChanges = Body(description="The changes to apply to the board"), +): + """Creates a board""" + try: + result = ApiDependencies.invoker.services.boards.update(board_id=board_id, changes=changes) + return result + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to update board") -# @boards_router.delete("/{board_id}", operation_id="delete_board") -# async def delete_board( -# board_id: str = Path(description="The id of board to delete"), -# ) -> None: -# """Deletes a board""" +@boards_router.delete("/{board_id}", operation_id="delete_board") +async def delete_board( + board_id: str = Path(description="The id of board to delete"), +) -> None: + """Deletes a board""" -# try: -# ApiDependencies.invoker.services.boards.delete(board_id=board_id) -# except Exception as e: -# # TODO: Does this need any exception handling at all? -# pass + try: + ApiDependencies.invoker.services.boards.delete(board_id=board_id) + except Exception as e: + # TODO: Does this need any exception handling at all? + pass -# @boards_router.get( -# "/", -# operation_id="list_boards", -# response_model=OffsetPaginatedResults[BoardRecord], -# ) -# async def list_boards( -# offset: int = Query(default=0, description="The page offset"), -# limit: int = Query(default=10, description="The number of boards per page"), -# ) -> OffsetPaginatedResults[BoardRecord]: -# """Gets a list of boards""" +@boards_router.get( + "/", + operation_id="list_boards", + response_model=OffsetPaginatedResults[BoardRecord], +) +async def list_boards( + offset: int = Query(default=0, description="The page offset"), + limit: int = Query(default=10, description="The number of boards per page"), +) -> OffsetPaginatedResults[BoardDTO]: + """Gets a list of boards""" -# results = ApiDependencies.invoker.services.boards.get_many( -# offset, -# limit, -# ) + results = ApiDependencies.invoker.services.boards.get_many( + offset, + limit, + ) + return results -# boards = list( -# map( -# lambda r: board_record_to_dto( -# r, -# generate_cover_photo_url(r.id) -# ), -# results.boards, -# ) -# ) - -# return boards - - - -# def board_record_to_dto( -# board_record: BoardRecord, cover_image_url: str -# ) -> BoardDTO: -# """Converts an image record to an image DTO.""" -# return BoardDTO( -# **board_record.dict(), -# cover_image_url=cover_image_url, -# ) - -# def generate_cover_photo_url(board_id: str) -> str | None: -# cover_photo = ApiDependencies.invoker.services.images._services.records.get_board_cover_photo(board_id) -# if cover_photo is not None: -# url = ApiDependencies.invoker.services.images._services.urls.get_image_url(cover_photo.image_origin, cover_photo.image_name) -# return url diff --git a/invokeai/app/api_app.py b/invokeai/app/api_app.py index 50228edf7e..22b4efec74 100644 --- a/invokeai/app/api_app.py +++ b/invokeai/app/api_app.py @@ -24,7 +24,7 @@ logger = InvokeAILogger.getLogger(config=app_config) import invokeai.frontend.web as web_dir from .api.dependencies import ApiDependencies -from .api.routers import sessions, models, images, boards +from .api.routers import sessions, models, images, boards, board_images from .api.sockets import SocketIO from .invocations.baseinvocation import BaseInvocation @@ -78,7 +78,9 @@ app.include_router(models.models_router, prefix="/api") app.include_router(images.images_router, prefix="/api") -# app.include_router(boards.boards_router, prefix="/api") +app.include_router(boards.boards_router, prefix="/api") + +app.include_router(board_images.board_images_router, prefix="/api") # Build a custom OpenAPI to include all outputs # TODO: can outputs be included on metadata of invocation schemas somehow? diff --git a/invokeai/app/services/board_images.py b/invokeai/app/services/board_images.py index dd2e104180..df2af4bbcf 100644 --- a/invokeai/app/services/board_images.py +++ b/invokeai/app/services/board_images.py @@ -2,7 +2,6 @@ from abc import ABC, abstractmethod from logging import Logger from invokeai.app.services.board_image_record_storage import BoardImageRecordStorageBase from invokeai.app.services.board_record_storage import ( - BoardDTO, BoardRecord, BoardRecordStorageBase, ) @@ -11,6 +10,7 @@ from invokeai.app.services.image_record_storage import ( ImageRecordStorageBase, OffsetPaginatedResults, ) +from invokeai.app.services.models.board_record import BoardDTO from invokeai.app.services.models.image_record import ImageDTO, image_record_to_dto from invokeai.app.services.urls import UrlServiceBase diff --git a/invokeai/app/services/board_record_storage.py b/invokeai/app/services/board_record_storage.py index a954fe7ac4..8207470e6d 100644 --- a/invokeai/app/services/board_record_storage.py +++ b/invokeai/app/services/board_record_storage.py @@ -6,41 +6,11 @@ import threading from typing import Optional, Union import uuid from invokeai.app.services.image_record_storage import OffsetPaginatedResults +from invokeai.app.services.models.board_record import BoardRecord, deserialize_board_record from pydantic import BaseModel, Field, Extra -class BoardRecord(BaseModel): - """Deserialized board record.""" - - board_id: str = Field(description="The unique ID of the board.") - """The unique ID of the board.""" - board_name: str = Field(description="The name of the board.") - """The name of the board.""" - created_at: Union[datetime, str] = Field( - description="The created timestamp of the board." - ) - """The created timestamp of the image.""" - updated_at: Union[datetime, str] = Field( - description="The updated timestamp of the board." - ) - """The updated timestamp of the image.""" - cover_image_name: Optional[str] = Field( - description="The name of the cover image of the board." - ) - """The name of the cover image of the board.""" - - -class BoardDTO(BoardRecord): - """Deserialized board record with cover image URL and image count.""" - - cover_image_url: Optional[str] = Field( - description="The URL of the thumbnail of the board's cover image." - ) - """The URL of the thumbnail of the most recent image in the board.""" - image_count: int = Field(description="The number of images in the board.") - """The number of images in the board.""" - class BoardChanges(BaseModel, extra=Extra.forbid): board_name: Optional[str] = Field(description="The board's new name.") @@ -221,6 +191,7 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase): return BoardRecord(**result) except sqlite3.Error as e: self._conn.rollback() + print(e) raise BoardRecordSaveException from e finally: self._lock.release() @@ -307,7 +278,7 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase): ) result = cast(list[sqlite3.Row], self._cursor.fetchall()) - boards = [BoardRecord(**dict(row)) for row in result] + boards = list(map(lambda r: deserialize_board_record(dict(r)), result)) # Get the total number of boards self._cursor.execute( diff --git a/invokeai/app/services/boards.py b/invokeai/app/services/boards.py index 07d64e655a..ddfaacf79c 100644 --- a/invokeai/app/services/boards.py +++ b/invokeai/app/services/boards.py @@ -5,8 +5,6 @@ from invokeai.app.services.board_image_record_storage import BoardImageRecordSto from invokeai.app.services.board_images import board_record_to_dto from invokeai.app.services.board_record_storage import ( - BoardDTO, - BoardRecord, BoardChanges, BoardRecordStorageBase, ) @@ -14,7 +12,7 @@ from invokeai.app.services.image_record_storage import ( ImageRecordStorageBase, OffsetPaginatedResults, ) -from invokeai.app.services.models.image_record import ImageDTO +from invokeai.app.services.models.board_record import BoardDTO from invokeai.app.services.urls import UrlServiceBase diff --git a/invokeai/app/services/image_record_storage.py b/invokeai/app/services/image_record_storage.py index 2ca9ad66ca..677e1d3445 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -261,17 +261,6 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): (changes.is_intermediate, image_name), ) - # Change the image's `is_intermediate`` flag - if changes.is_intermediate is not None: - self._cursor.execute( - f"""--sql - UPDATE images - SET board_id = ? - WHERE image_name = ?; - """, - (changes.is_intermediate, image_name), - ) - self._conn.commit() except sqlite3.Error as e: self._conn.rollback() diff --git a/invokeai/app/services/models/board_record.py b/invokeai/app/services/models/board_record.py new file mode 100644 index 0000000000..35c4cea9b0 --- /dev/null +++ b/invokeai/app/services/models/board_record.py @@ -0,0 +1,57 @@ +from typing import Optional, Union +from datetime import datetime +from pydantic import BaseModel, Extra, Field, StrictBool, StrictStr +from invokeai.app.util.misc import get_iso_timestamp + +class BoardRecord(BaseModel): + """Deserialized board record.""" + + board_id: str = Field(description="The unique ID of the board.") + """The unique ID of the board.""" + board_name: str = Field(description="The name of the board.") + """The name of the board.""" + created_at: Union[datetime, str] = Field( + description="The created timestamp of the board." + ) + """The created timestamp of the image.""" + updated_at: Union[datetime, str] = Field( + description="The updated timestamp of the board." + ) + """The updated timestamp of the image.""" + cover_image_name: Optional[str] = Field( + description="The name of the cover image of the board." + ) + """The name of the cover image of the board.""" + + +class BoardDTO(BoardRecord): + """Deserialized board record with cover image URL and image count.""" + + cover_image_url: Optional[str] = Field( + description="The URL of the thumbnail of the board's cover image." + ) + """The URL of the thumbnail of the most recent image in the board.""" + image_count: int = Field(description="The number of images in the board.") + """The number of images in the board.""" + + + + +def deserialize_board_record(board_dict: dict) -> BoardRecord: + """Deserializes a board record.""" + + # Retrieve all the values, setting "reasonable" defaults if they are not present. + + board_id = board_dict.get("board_id", "unknown") + board_name = board_dict.get("board_name", "unknown") + created_at = board_dict.get("created_at", get_iso_timestamp()) + updated_at = board_dict.get("updated_at", get_iso_timestamp()) + deleted_at = board_dict.get("deleted_at", get_iso_timestamp()) + + return BoardRecord( + board_id=board_id, + board_name=board_name, + created_at=created_at, + updated_at=updated_at, + deleted_at=deleted_at, + )