在fastapi db会话中使用生成器上下文管理器



我正在使用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中创建book对象时存在,但在此之后被关闭,从而以以下错误结束

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"] = []

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

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

from contextlib import contextmanager
def get_db():
# gaenerator using yield
db_context = contextmanager(get_db)

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