【问题标题】:sqlalchemy Session object has no attribute addsqlalchemy 会话对象没有属性添加
【发布时间】:2021-05-13 17:14:41
【问题描述】:

我正在尝试将数据插入 mysql 服务器,当我尝试将数据添加到 SQLalchemy 会话中时,我收到错误“生成器对象没有添加属性”

db=get_db()
temp = schema.User(**filtered_dict)
insert_data=models.User(**temp.dict())
db = get_db()
db.add(insert_data)
db.commit()
db.refresh()

会话生成器:

def get_db():
 
    db_session = sessionlocal()
    try:
        yield db_session
    finally:
        db_session.close()

会话创建


from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

SQLALCHEMY_DATABASE_URL = "mysql+mysqlconnector://root:password@localhost:3305/line_api"

engine = create_engine(
    SQLALCHEMY_DATABASE_URL
)
sessionlocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)


Base = declarative_base()

请帮忙...

【问题讨论】:

  • @IljaEverilä 我希望它不是生成器的问题,唯一的问题是我的对象“sessionlocal”没有添加属性
  • 您不直接调用get_db 函数,您通常使用FastAPIs Depends() 构造将其插入到需要的位置:def foo(db: SessionLocal = Depends(get_db)): - 这将在foo 端点时填充被调用。这就是yield-ing 会话的原因,因为它允许 FastAPI 处理依赖关系并在不再使用时清理它。但是,要做到这一点,您必须使用 FastAPIs 依赖注入方法,而不是直接调用该方法。

标签: python sqlalchemy fastapi


【解决方案1】:
简单的解决方案

我在自己的使用 SQLAlchemy 的 FastAPI 项目中遇到了类似的问题。问题源于从yield 关键字收集数据库会话。 yield 返回一个生成器,而不是像您期望的那样返回 sqlalchemy 数据库会话。您可以在此处阅读有关 yield 表达式的更多信息: https://docs.python.org/3/reference/expressions.html#yieldexpr

要修复代码,您需要以某种形式对从get_db 返回的generator 对象调用内部__next__ 函数。您可以通过多种方式做到这一点(有些方式可能比我的建议更准确)。

temp = schema.User(**filtered_dict)
insert_data=models.User(**temp.dict())
db_gen = get_db()
db = next(db_gen)
db.add(insert_data)
db.commit()
db.refresh()

使用对生成器的 var 引用进行编辑感谢 cmets 中的@DustinMichels。

添加next 函数调用应该可以解决问题。


使用异步生成器

由于我们没有在函数get_db 前面加上async,所以我们得到了一个普通的生成器。但是,如果我们在这个函数定义中添加async,我们就不能再使用__next__ 作为解决方案。使函数异步还有助于确保正确关闭连接(请参阅 cmets)。

如果开发人员决定添加 async 关键字,那么您有几个选择:

FastAPI 的Depends 系统与异步生成器完美配合,可以为您调用该函数,为您的连接填充一个不错的参数。

或者,您可以在异步生成器上调用asend(None) 以获取异步生成器的第一个值。 (还有更好的方法可以做到这一点,比如使用异步 for 循环或 __anext__ 函数调用)。更多内容请点击此处:https://www.python.org/dev/peps/pep-0525/#implementation-details

async def get_db():
    db_session = sessionlocal()
    try:
        yield db_session
    finally:
        db_session.close()

# ...

temp = schema.User(**filtered_dict)
insert_data=models.User(**temp.dict())
db = get_db().asend(None)
db.add(insert_data)
db.commit()
db.refresh()

【讨论】:

  • @RandallShanePhD 有没有我没有回答的问题?我可以把它编辑成解释。
  • 欣赏@TFlexSoom 的标注 - 这是一个可靠的答案!
  • 我对同样的代码感到困惑。 finally块什么时候执行,并关闭db连接?
  • @DustinMichels,感谢您的评论。这是一个非常好的观点,我将尝试添加到答案中。从个人测试来看,只要可以删除对生成器的引用,finally 似乎就会起作用。这确实意味着它可以通过当前编写代码的方式提前触发。我发现将get_db() 设置为变量确实有助于避免关闭连接。就个人而言,我建议使函数异步。这是我在自己的实现中所做的,并且在注入 FastAPI 依赖系统时效果很好。
  • 个人测试脚本如果有人对上面的评论感兴趣。 gist.github.com/TFlexSoom/485595a3d2fb7bbc5ac2e75196e46b31
【解决方案2】:

对我有用的只是调用sessionmaker中的.begin() fx:

class MainDB:
    # create connection engine
    base_db_engine = create_engine(f'sqlite:///{get_project_root()}/db/base.db')

    # init tables
    Base = declarative_base()
    Base.metadata.create_all(base_db_engine)

class GenDBInterface:
    # create db session
    session: Session = sessionmaker(bind=MainDB.base_db_engine)

    def run(self):
        acc = Accounts(email='hello', password='hi', user_token='hello')
        with self.session.begin() as s:
            s.add(acc)

不过,我没有在烧瓶/FastAPI 环境中使用它。可能会有所不同。

【讨论】:

    猜你喜欢
    • 2017-12-17
    • 1970-01-01
    • 2023-03-26
    • 1970-01-01
    • 2021-07-14
    • 2011-03-15
    • 2019-05-03
    • 2020-03-26
    • 2016-09-17
    相关资源
    最近更新 更多