【发布时间】:2020-01-04 23:01:21
【问题描述】:
我有一个应用程序,我希望用户能够为博客添加书签/取消书签,但取消书签后,我不想删除该书签记录。因此,我的 Bookmark 模型上有一个 is_bookmarked 属性来确定书签是否处于活动状态。
在我的测试文件中,我有
def test_unbookmark_a_blog_do_assign(session):
blog = create_blog(session)
bookmark = toggle_bookmark(session, blog_id=blog.id)
assert len(blog.bookmarks) == 1
toggle_bookmark(session, blog_id=blog.id)
assert len(blog.bookmarks) == 0
此测试通过。但是,以下不会。 (唯一的区别是我没有为toggle_bookmark 的结果分配变量。)
def test_unbookmark_a_blog_no_assign(session):
blog = create_blog(session)
toggle_bookmark(session, blog_id=blog.id)
assert len(blog.bookmarks) == 1
toggle_bookmark(session, blog_id=blog.id)
assert len(blog.bookmarks) == 0
在第二个断言 assert len(blog.bookmarks) == 0 处失败。原因是blog._bookmarks[0].is_bookmarked 没有在toggle_bookmark 函数之外更新,仍然是True,使其在blog.bookmarks 中可用。 (定义见下文)
对于上下文,我使用的是经典映射:
@dataclass
class Bookmark:
is_bookmarked: bool = True
blog_id: Optional[int] = None
@dataclass
class Blog:
_bookmarks: List[Bookmark] = field(default_factory=list)
def add_bookmark(self, bookmark):
self._bookmarks.append(bookmark)
@property
def bookmarks(self):
return [bookmark for bookmark in self._bookmarks if bookmark.is_bookmarked]
...
blog_table = Table(
"blog",
metadata,
Column("id", Integer, primary_key=True, index=True))
bookmark_table = Table(
"bookmark",
metadata,
Column("id", Integer, primary_key=True, index=True),
Column("is_bookmarked", Boolean, default=True),
Column("blog_id", ForeignKey("blog.id"), nullable=True),
)
...
mapper(
Blog,
blog_table,
properties={
"_bookmarks": relationship(Bookmark, back_populates="blog"),
},
)
mapper(
Bookmark,
bookmark_table,
properties={
"blog": relationship(Blog, back_populates="_bookmarks"),
},
)
toggle_bookmark 函数:
def toggle_bookmark(db_session, *, blog_id):
blog = db_session.query(Blog).get(blog_id)
bookmark = db_session.query(Bookmark).filter(
Bookmark.blog_id == blog_id
).one_or_none()
if bookmark is None:
bookmark = Bookmark()
blog.add_bookmark(bookmark)
db_session.add(blog)
db_session.commit()
return bookmark
bookmark.is_bookmarked = not bookmark.is_bookmarked
db_session.add(bookmark)
db_session.commit()
return bookmark
我真的很困惑......我的直觉告诉我,当查询被评估时它有一些事情要做,但我还没有找到任何证据来支持它。任何帮助表示赞赏。提前致谢!
一个完整的例子:
from dataclasses import dataclass, field
from typing import Optional, List
from sqlalchemy import (
create_engine, MetaData, Table, Column, Integer, Boolean, ForeignKey)
from sqlalchemy.orm import mapper, relationship, sessionmaker
@dataclass
class Bookmark:
is_bookmarked: bool = True
blog_id: Optional[int] = None
@dataclass
class Blog:
_bookmarks: List[Bookmark] = field(default_factory=list)
def add_bookmark(self, bookmark):
self._bookmarks.append(bookmark)
@property
def bookmarks(self):
return [bookmark for bookmark in self._bookmarks if bookmark.is_bookmarked]
engine = create_engine("sqlite:///")
metadata = MetaData(bind=engine)
blog_table = Table(
"blog",
metadata,
Column("id", Integer, primary_key=True, index=True))
bookmark_table = Table(
"bookmark",
metadata,
Column("id", Integer, primary_key=True, index=True),
Column("is_bookmarked", Boolean, default=True),
Column("blog_id", ForeignKey("blog.id"), nullable=True),
)
metadata.create_all()
mapper(
Blog,
blog_table,
properties={
"_bookmarks": relationship(Bookmark, back_populates="blog"),
},
)
mapper(
Bookmark,
bookmark_table,
properties={
"blog": relationship(Blog, back_populates="_bookmarks"),
},
)
def toggle_bookmark(db_session, *, blog_id):
blog = db_session.query(Blog).get(blog_id)
bookmark = db_session.query(Bookmark).filter(
Bookmark.blog_id == blog_id
).one_or_none()
if bookmark is None:
bookmark = Bookmark()
blog.add_bookmark(bookmark)
db_session.add(blog)
db_session.commit()
return bookmark
bookmark.is_bookmarked = not bookmark.is_bookmarked
db_session.add(bookmark)
db_session.commit()
return bookmark
def create_blog(session):
blog = Blog()
session.add(blog)
session.commit()
return blog
def test_unbookmark_a_blog_do_assign(session):
blog = create_blog(session)
bookmark = toggle_bookmark(session, blog_id=blog.id)
assert len(blog.bookmarks) == 1
toggle_bookmark(session, blog_id=blog.id)
assert len(blog.bookmarks) == 0
def test_unbookmark_a_blog_no_assign(session):
blog = create_blog(session)
toggle_bookmark(session, blog_id=blog.id)
assert len(blog.bookmarks) == 1
toggle_bookmark(session, blog_id=blog.id)
assert len(blog.bookmarks) == 0
Session = sessionmaker()
test_unbookmark_a_blog_do_assign(Session())
test_unbookmark_a_blog_no_assign(Session())
【问题讨论】:
-
好的,我想我找到了一些东西:joinedload & lazy-loading
-
如果我通过在第二个
crud.blog.toggle_bookmark之前添加bookmarks = blog.bookmarks来访问blog._bookmarks,则测试将通过。但是,在Blog的映射配置中设置"_bookmarks": relationship(Bookmark, back_populates="blog", lazy="joined"),后仍然出现同样的错误。 -
@IljaEverilä 添加了映射代码。感谢您的提示。
-
我的猜测是您需要返回书签,因为它超出范围并在您的 crud 函数返回时被垃圾收集,但是如果您将它分配给一个变量,它会增加引用计数。他们的文档有一些示例,说明如何使用和检查会话状态以解决问题docs.sqlalchemy.org/en/13/orm/session_state_management.html
-
@LucasScott 感谢您的建议!这听起来可能。我会尝试通过检查会话来查看是否可以得到有用的东西。
标签: python sqlalchemy