【问题标题】:Working with Generator Context Manager in fastapi db session在 fastapi 数据库会话中使用生成器上下文管理器
【发布时间】:2023-01-14 22:22:39
【问题描述】:

所以我在 fastapi 会话中使用上下文管理器,下面是我的设置

from sqlmodel import create_engine, Session

from app.core.config import settings

engine = create_engine(settings.SQLALCHEMY_DATABASE_URI, pool_pre_ping=True)
SessionLocal = Session(autocommit=False, autoflush=False, bind=engine)

然后被消耗

from contextlib import contextmanager
from sqlmodel import Session
from app.db.session import SessionLocal

@contextmanager
def get_session():
    with SessionLocal as session:
        yield session

这是端点使用的

@router.post("/")
def create_book(
    *,
    db: Session = Depends(get_session),
    book_in: models.BookCreate,
    current_user: models.User = Depends(get_current_active_user),
) -> models.BookCreate:
    """
    Create new book.
    """
    # print(current_user.id)
    book = crud.book.create_with_owner(db=db, obj_in=book_in, owner_id=current_user.id)
    return book

通过上面的设置,我希望一切正常,但事实并非如此,而是出现以下错误

backend-1  |   File "/app/app/api/api_v1/endpoints/book.py", line 42, in create_book
backend-1  |     book = crud.book.create_with_owner(db=db, obj_in=book_in, owner_id=current_user.id)
backend-1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
backend-1  |   File "/app/app/crud/crud_book.py", line 23, in create_with_owner
backend-1  |     db.add(db_obj)
backend-1  |     ^^^^^^
backend-1  | AttributeError: '_GeneratorContextManager' object has no attribute 'add'

以下是来自crud_book.py的详细信息

class CRUDBook(CRUDBase[Book, BookCreate, BookUpdate]):
    def create_with_owner(
        self, db: Session, *, obj_in: BookCreate, owner_id: UUID
    ) -> Book:
        # with get_session() as db:
        obj_in_data = jsonable_encoder(obj_in)
        obj_in_data = dict(obj_in)

        print(db)
        # TODO: Check if owner_id is none

        db_obj = self.model(**obj_in_data, owner_id=owner_id)
        db.add(db_obj)
        db.commit()
        db.refresh(db_obj)
        return db_obj

当在 crud_book.py 中引入另一个带有上下文的会话时,会话确实有效,但这会带来成本,因为会话仅在 db 中创建书籍对象时存在,但在出现以下错误后会在那里关闭

backend-1  |   File "/usr/local/lib/python3.11/site-packages/sqlmodel/main.py", line 597, in validate
backend-1  |     return cls.from_orm(value)
backend-1  |            ^^^^^^^^^^^^^^^^^^^
backend-1  |   File "/usr/local/lib/python3.11/site-packages/sqlmodel/main.py", line 552, in from_orm
backend-1  |     values, fields_set, validation_error = validate_model(cls, obj)
backend-1  |                                            ^^^^^^^^^^^^^^^^^^^^^^^^
backend-1  |   File "pydantic/main.py", line 1056, in pydantic.main.validate_model
backend-1  |   File "pydantic/utils.py", line 441, in pydantic.utils.GetterDict.get
backend-1  |   File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/attributes.py", line 482, in __get__
backend-1  |     return self.impl.get(state, dict_)
backend-1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^
backend-1  |   File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/attributes.py", line 942, in get
backend-1  |     value = self._fire_loader_callables(state, key, passive)
backend-1  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
backend-1  |   File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/attributes.py", line 978, in _fire_loader_callables
backend-1  |     return self.callable_(state, passive)
backend-1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
backend-1  |   File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/strategies.py", line 863, in _load_for_state
backend-1  |     raise orm_exc.DetachedInstanceError(
backend-1  | sqlalchemy.orm.exc.DetachedInstanceError: Parent instance <Book at 0x7fc8a3087b90> is not bound to a Session; lazy load operation of attribute 'authors' cannot proceed (Background on this error at: https://sqlalche.me/e/14/bhk3)

以防万一以下是 bookcreate 期间预期的详细信息

import datetime
from uuid import uuid4, UUID
from typing import TYPE_CHECKING

from sqlmodel import SQLModel, Field, Relationship

# if TYPE_CHECKING:
from .genre import Genre  # noqa: F401
from .author import Author  # noqa: F401
from .narrator import Narrator  # noqa: F401
from .publisher import Publisher  # noqa: F401
from .user import User, UserRead  # noqa: F401
from .book_genre_link import BookGenreLink  # noqa: F401
from .book_author_link import BookAuthorLink  # noqa: F401
from .book_narrator_link import BookNarratorLink  # noqa: F401
from .book_publisher_link import BookPublisherLink  # noqa: F401


class BookBase(SQLModel):
    title: str = Field(index=True)
    subtitle: str | None = Field(default=None, index=True)
    description: str = Field(index=True)
    runtime: datetime.time | None = Field(default=None)
    rating: float | None = Field(default=None)
    published_date: datetime.date | None = Field(default=None)
    cover_image: str | None = Field(default=None)

class Book(BookBase, table=True):
    id: UUID = Field(
        default_factory=uuid4,
        primary_key=True,
        index=True,
        nullable=False,
    )
    owner: list["User"] = Relationship(back_populates="books")
    owner_id: UUID | None = Field(default=None, foreign_key="user.id")
    authors: list["Author"] = Relationship(
        back_populates="books", link_model=BookAuthorLink
    )
    publishers: list["Publisher"] = Relationship(
        back_populates="books", link_model=BookPublisherLink
    )
    genres: list["Genre"] = Relationship(
        back_populates="books", link_model=BookGenreLink
    )
    narrators: list["Narrator"] = Relationship(
        back_populates="books", link_model=BookNarratorLink
    )
    # creation_date: datetime = Field(default=datetime.utcnow())
    # update_date: datetime = Field(default=datetime.utcnow())


class BookCreate(BookBase):
    authors: list["Author"] = []
    publishers: list["Publisher"] = []
    genres: list["Genre"] = []
    narrators: list["Narrator"] = []

那么为什么我需要两个多上下文管理器来处理会话,或者我哪里出错了。

谢谢。

【问题讨论】:

  • 你不需要@contextmanager以上get_session
  • 摆脱@contextmanager后,我还需要第二个上下文管理器吗?
  • 你必须能够在没有它的情况下使用它
  • 现在得到一个单独的错误生成器类型错误audible-backend-1 | File "/usr/local/lib/python3.11/contextlib.py", line 222, in __aexit__ backend-1 | await self.gen.athrow(typ, value, traceback) backend-1 | File "/usr/local/lib/python3.11/site-packages/fastapi/concurrency.py", line 36, in contextmanager_in_threadpool backend-1 | raise e backend-1 | TypeError: 'generator' object does not support the context manager protocol
  • 我很清楚,你必须删除 get_session 上面的 contextmanager 然后只使用 db 因为它是一个会话,不需要其他上下文管理器

标签: sqlalchemy fastapi pydantic sqlmodel


【解决方案1】:

出于某种原因,FastAPI 依赖项应该是生成器,而不是上下文管理器(在 documentation 中提到)。我通常做的是

from contextlib import contextmanager

def get_db():
    # gaenerator using yield

db_context = contextmanager(get_db)

然后我可以在我的 FastAPI 端点中使用 Depends(get_db),在其他地方使用 with db_context() as db:(例如 Celery 任务、CRON 作业等)。

【讨论】:

    猜你喜欢
    • 2018-12-23
    • 1970-01-01
    • 1970-01-01
    • 2021-09-11
    • 2021-11-30
    • 2021-09-19
    • 1970-01-01
    • 2019-06-11
    • 2016-11-16
    相关资源
    最近更新 更多