Python 如何防止SQLAlchemy一对多关系尝试插入重复数据?

Python 如何防止SQLAlchemy一对多关系尝试插入重复数据?,python,sql,orm,sqlalchemy,Python,Sql,Orm,Sqlalchemy,我在SQLAlchemy中有以下关系: class Post(db.Base): __tablename__ = 'posts' id = Column(Integer, primary_key=True) url = Column(String(512), unique=True) timestamp = Column(DateTime) comments = relationship("Comment", backref=backref("post",

我在SQLAlchemy中有以下关系:

class Post(db.Base):
    __tablename__ = 'posts'
    id = Column(Integer, primary_key=True)
    url = Column(String(512), unique=True)
    timestamp = Column(DateTime)
    comments = relationship("Comment", backref=backref("post", cascade="save-update"))

    @classmethod
    def merge(cls, dst, src):
        # self.url doesn't change (if it does, we consider it a new post)
        dst.title = src.title
        dst.author = src.author
        dst.timestamp = src.timestamp
        dst.comments = src.comments

class Comment(db.Base):
    __tablename__ = 'comments'
    id = Column(Integer, primary_key=True)
    post_id = Column(Integer, ForeignKey('posts.id'))
    # switched to using backref, which obviates the need for following line:
    # post = relationship("Post", back_populates="comments")
    text = Column(UnicodeText)
    author = Column(String(128))
    timestamp = Column(DateTime)
    score = Column(Integer)

    def __init__(self, text, author, timestamp):
        self.text = text
        self.author = author
        self.timestamp = timestamp
我首先创建并插入帖子(以及相关评论),然后稍后再回来从网站上重新提取帖子和评论。出于某种原因,当我在session.commit()中输入时,只有在更新注释时才会出现错误,但更改其他字段是可以的

我的问题是:我应该如何更新这些帖子?有人告诉我,某种UPSERT功能并不理想,内置的.merge()也没有达到我的预期(即,它尝试重新创建帖子)

以下是我现在正在做的:

# update existing posts if they exist (look up by url), otherwise insert
for p in posts:
    existing_post = db.session.query(post.Post).filter(post.Post.url==p.url).first()
    if existing_post:
        post.Post.merge(existing_post, p)
    else:
        pass
        db.session.add(p)

db.session.commit()
如果我从类方法.merge(dst,src)中注释掉dst.comments=src.comments,那么我就没有问题了

Traceback (most recent call last):
  File "/Users/p/src/python-envs/app/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 1139, in _execute_context
context)
  File "/Users/p/src/python-envs/app/lib/python3.5/site-packages/sqlalchemy/engine/default.py", line 450, in do_execute
cursor.execute(statement, parameters)
psycopg2.IntegrityError: duplicate key value violates unique constraint "posts_url_key"
DETAIL:  Key (url)=(http://www.example/post) already exists.

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Applications/PyCharm.app/Contents/helpers/pydev/pydevd.py", line 2407, in <module>
    globals = debugger.run(setup['file'], None, None, is_module)
  File "/Applications/PyCharm.app/Contents/helpers/pydev/pydevd.py", line 1798, in run
    launch(file, globals, locals)  # execute the script
  File "/Applications/PyCharm.app/Contents/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
    exec(compile(contents+"\n", file, 'exec'), glob, loc) 
  File "/Users/p/src/bss/slurper.py", line 75, in <module>
    db.session.commit()
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/orm/session.py", line 813, in commit
    self.transaction.commit()
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/orm/session.py", line 392, in commit
    self._prepare_impl()
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/orm/session.py", line 372, in _prepare_impl
    self.session.flush()
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/orm/session.py", line 2027, in flush
    self._flush(objects)
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/orm/session.py", line 2145, in _flush
    transaction.rollback(_capture_exception=True)
  File "/Users/p/src/python-envs/bss/lib/python3.5/sitepackages/sqlalchemy/util/langhelpers.py", line 60, in __exit__
    compat.reraise(exc_type, exc_value, exc_tb)
  File "/Users/p/src/python-envs/bss/lib/python3.5/sitepackages/sqlalchemy/util/compat.py", line 183, in reraise
    raise value
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/orm/session.py", line 2109, in _flush
    flush_context.execute()
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/orm/unitofwork.py", line 373, in execute
    rec.execute(self)
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/orm/unitofwork.py", line 532, in execute
uow
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/orm/persistence.py", line 174, in save_obj
mapper, table, insert)
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/orm/persistence.py", line 800, in _emit_insert_statements
execute(statement, params)
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 914, in execute
return meth(self, multiparams, params)
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/sql/elements.py", line 323, in _execute_on_connection
return connection._execute_clauseelement(self, multiparams, params)
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 1010, in _execute_clauseelement
compiled_sql, distilled_params
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 1146, in _execute_context
context)
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 1341, in _handle_dbapi_exception
exc_info
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/util/compat.py", line 189, in raise_from_cause
reraise(type(exception), exception, tb=exc_tb, cause=exc_value)
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/util/compat.py", line 182, in reraise
raise value.with_traceback(tb)
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 1139, in _execute_context
context)
  File "/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/engine/default.py", line 450, in do_execute
      cursor.execute(statement, parameters)
  sqlalchemy.exc.IntegrityError: (psycopg2.IntegrityError) duplicate key value violates unique constraint "posts_url_key"
  DETAIL:  Key (url)=(http://www.example.com/post) already exists.
   [SQL: 'INSERT INTO posts (url, title, author, timestamp) VALUES (%(url)s, %(title)s, %(author)s, %(timestamp)s) RETURNING posts.id'] [parameters: {'timestamp': datetime.datetime(2016, 1, 13, 21, 0), 'title': ‘Blog Post Title’, 'url': 'http://www.example.com/post', 'author': ‘authorname’}]

  Process finished with exit code 1
回溯(最近一次呼叫最后一次):
文件“/Users/p/src/python envs/app/lib/python3.5/site packages/sqlalchemy/engine/base.py”,第1139行,在执行上下文中
(上下文)
文件“/Users/p/src/python envs/app/lib/python3.5/site packages/sqlalchemy/engine/default.py”,第450行,在do_execute中
cursor.execute(语句、参数)
psycopg2.IntegrityError:重复的键值违反唯一约束“posts\u url\u key”
详细信息:键(url)=(http://www.example/post)已经存在。
上述异常是以下异常的直接原因:
回溯(最近一次呼叫最后一次):
文件“/Applications/PyCharm.app/Contents/helpers/pydev/pydevd.py”,第2407行,在
globals=debugger.run(setup['file'],None,None,is_模块)
文件“/Applications/PyCharm.app/Contents/helpers/pydev/pydev.py”,第1798行,运行中
启动(文件、全局、局部)#执行脚本
文件“/Applications/PyCharm.app/Contents/helpers/pydev/_pydev_imps/_pydev_execfile.py”,execfile中第18行
exec(编译(内容+“\n”,文件,'exec'),全局,loc)
文件“/Users/p/src/bss/slurper.py”,第75行,在
db.session.commit()
文件“/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/orm/session.py”,提交中第813行
self.transaction.commit()
文件“/Users/p/src/python envs/bss/lib/python3.5/site packages/sqlalchemy/orm/session.py”,第392行,在提交中
self.\u prepare\u impl()
文件“/Users/p/src/python envs/bss/lib/python3.5/site packages/sqlalchemy/orm/session.py”,第372行,在
self.session.flush()
文件“/Users/p/src/python envs/bss/lib/python3.5/site packages/sqlalchemy/orm/session.py”,第2027行,刷新
自冲洗(对象)
文件“/Users/p/src/python envs/bss/lib/python3.5/site packages/sqlalchemy/orm/session.py”,第2145行,在
事务.rollback(\u capture\u exception=True)
文件“/Users/p/src/python envs/bss/lib/python3.5/sitepackages/sqlalchemy/util/langhelpers.py”,第60行,在退出时__
兼容性(exc_类型、exc_值、exc_tb)
文件“/Users/p/src/python envs/bss/lib/python3.5/sitepackages/sqlalchemy/util/compat.py”,第183行,在reraise中
增值
文件“/Users/p/src/python envs/bss/lib/python3.5/site packages/sqlalchemy/orm/session.py”,第2109行,在
flush_context.execute()
文件“/Users/p/src/python envs/bss/lib/python3.5/site packages/sqlalchemy/orm/unitofwork.py”,第373行,在execute中
rec.execute(self)
文件“/Users/p/src/python envs/bss/lib/python3.5/site packages/sqlalchemy/orm/unitofwork.py”,第532行,在execute中
uow
文件“/Users/p/src/python envs/bss/lib/python3.5/site packages/sqlalchemy/orm/persistence.py”,第174行,在save_obj中
映射器、表、插入)
文件“/Users/p/src/python envs/bss/lib/python3.5/site packages/sqlalchemy/orm/persistence.py”,第800行,在发出插入语句中
执行(语句,参数)
文件“/Users/p/src/python envs/bss/lib/python3.5/site packages/sqlalchemy/engine/base.py”,第914行,在execute中
返回方法(自身、多线程、参数)
文件“/Users/p/src/python envs/bss/lib/python3.5/site packages/sqlalchemy/sql/elements.py”,第323行,在连接上执行
返回连接。_execute_clauseelement(self、multiparams、params)
文件“/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/engine/base.py”,第1010行,在执行条款元素中
编译的sql,提取的参数
文件“/Users/p/src/python envs/bss/lib/python3.5/site packages/sqlalchemy/engine/base.py”,第1146行,在执行上下文中
(上下文)
文件“/Users/p/src/python envs/bss/lib/python3.5/site packages/sqlalchemy/engine/base.py”,第1341行,在_handle_dbapi_exception中
exc_信息
文件“/Users/p/src/python-envs/bss/lib/python3.5/site-packages/sqlalchemy/util/compat.py”,第189行,在raise\u from\u cause中
重新释放(类型(异常)、异常、tb=exc\U tb、原因=exc\U值)
文件“/Users/p/src/python envs/bss/lib/python3.5/site packages/sqlalchemy/util/compat.py”,第182行,在reraise中
通过_回溯(tb)提升值
文件“/Users/p/src/python envs/bss/lib/python3.5/site packages/sqlalchemy/engine/base.py”,第1139行,在执行上下文中
(上下文)
文件“/Users/p/src/python envs/bss/lib/python3.5/site packages/sqlalchemy/engine/default.py”,第450行,在do_execute中
cursor.execute(语句、参数)
sqlalchemy.exc.IntegrityError:(psycopg2.IntegrityError)重复的键值违反了唯一约束“posts\u url\u key”
详细信息:键(url)=(http://www.example.com/post)已经存在。
[SQL:'插入帖子(url、标题、作者、时间戳)值(%(url)s、%(标题)s、%(作者)s、%(时间戳)s)返回帖子。id'][参数:{'timestamp':datetime.datetime(2016,1,13,21,0),'title':'Blog Post title','url':'http://www.example.com/post“,”作者“:”作者姓名“}]
进程已完成,退出代码为1
您应该使用内置的SQLALchemy。这对我有用

class Post(Base):
    __tablename__ = 'posts'
    id = Column(Integer, primary_key=True)
    url = Column(String(512), unique=True)
    timestamp = Column(DateTime)
    comments = relationship("Comment", backref=backref("post", cascade="save-update"))

    @hybrid_method
    def merge(self, dst):
        # self.url doesn't change (if it does, we consider it a new post)
        self.timestamp = dst.timestamp
        self.comments = dst.comments

class Comment(Base):
    __tablename__ = 'comments'
    id = Column(Integer, primary_key=True)
    post_id = Column(Integer, ForeignKey('posts.id'))
    text = Column(UnicodeText)
    author = Column(String(128))
    timestamp = Column(DateTime)
    score = Column(Integer)

c1 = Comment()
c2 = Comment()
c3 = Comment(text=u"comment3")

p1 = Post(url="foo")
p1.comments = [c1, c2]
session.add(p1)
session.commit()

p1 = session.query(Post).filter_by(url="foo").one()
p2 = Post()
p2.comments = [c3]
p1.merge(p2)

session.commit()

p1 = session.query(Post).filter_by(url="foo").one()
print p1.comments[0].text # comment3
您应该使用内置的SQLALchemy。这对我有用

class Post(Base):
    __tablename__ = 'posts'
    id = Column(Integer, primary_key=True)
    url = Column(String(512), unique=True)
    timestamp = Column(DateTime)
    comments = relationship("Comment", backref=backref("post", cascade="save-update"))

    @hybrid_method
    def merge(self, dst):
        # self.url doesn't change (if it does, we consider it a new post)
        self.timestamp = dst.timestamp
        self.comments = dst.comments

class Comment(Base):
    __tablename__ = 'comments'
    id = Column(Integer, primary_key=True)
    post_id = Column(Integer, ForeignKey('posts.id'))
    text = Column(UnicodeText)
    author = Column(String(128))
    timestamp = Column(DateTime)
    score = Column(Integer)

c1 = Comment()
c2 = Comment()
c3 = Comment(text=u"comment3")

p1 = Post(url="foo")
p1.comments = [c1, c2]
session.add(p1)
session.commit()

p1 = session.query(Post).filter_by(url="foo").one()
p2 = Post()
p2.comments = [c3]
p1.merge(p2)

session.commit()

p1 = session.query(Post).filter_by(url="foo").one()
print p1.comments[0].text # comment3