【问题标题】:'Essential SQLAlchemy' example question causing Duplicate entry IntegrityError“基本 SQLAlchemy”示例问题导致重复条目 IntegrityError
【发布时间】:2018-09-15 02:01:22
【问题描述】:

我在Essential SQLAlchemy这本书的帮助下学习SQLAlchemy。

其中一个示例对我想要学习的内容至关重要 - 添加相关对象。我就是无法让它工作。我希望这个社区可以帮助确定我是否遗漏了什么,或者示例中是否存在缺陷。显然前者是最有可能的。但是,无论如何,我希望这可能对其他人有所帮助。对我来说,直到这个关键时刻,这本书都让人感觉直截了当。

给出的例子来自第 7 章,麻烦的“命令”代码是例 7-25。完整代码示例在https://github.com/jasonamyers/essential-sqlalchemy-2e

我从以下设置开始:

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

engine = create_engine('mysql+pymysql://root:password@localhost/Alberta', pool_recycle=3600)

Session = sessionmaker(bind=engine)

session = Session()

from datetime import datetime
from sqlalchemy import (Table, Column, Integer, Numeric, String, DateTime, ForeignKey, Boolean, desc, func)
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, backref

Base = declarative_base()

class Cookie(Base):
    __tablename__ = 'cookies'
    cookie_id = Column(Integer, primary_key=True)
    cookie_name = Column(String(50), index=True)
    cookie_recipe_url = Column(String(255))
    cookie_sku = Column(String(55))
    quantity = Column(Integer())
    unit_cost = Column(Numeric(12, 2))

    def __repr__(self):
        return "Cookie(cookie_name='{self.cookie_name}', " \
                   "cookie_recipe_url='{self.cookie_recipe_url}', " \
                   "cookie_sku='{self.cookie_sku}', " \
                   "quantity={self.quantity}, " \
                   "unit_cost={self.unit_cost})".format(self=self)


class User(Base):
    __tablename__ = 'users'
    user_id = Column(Integer(), primary_key=True)
    username = Column(String(15), nullable=False, unique=True)
    email_address = Column(String(255), nullable=False)
    phone = Column(String(20), nullable=False)
    password = Column(String(25), nullable=False)
    created_on = Column(DateTime(), default=datetime.now)
    updated_on = Column(DateTime(), default=datetime.now, onupdate=datetime.now)

def __repr__(self):
    return "User(username='{self.username}', " \
                 "email_address='{self.email_address}', " \
                 "phone='{self.phone}', " \
                 "password='{self.password}')".format(self=self)

class Order(Base):
    __tablename__ = 'orders'
    order_id = Column(Integer(), primary_key=True)
    user_id = Column(Integer(), ForeignKey('users.user_id'))
    shipped = Column(Boolean(), default=False)

     user =  relationship("User", backref=backref('orders', order_by=order_id))

    def __repr__(self):
        return "Order(user_id={self.user_id}, " \
                  "shipped={self.shipped})".format(self=self)


class LineItem(Base):
    __tablename__ = 'line_items'
    line_item_id = Column(Integer(), primary_key=True)
    order_id = Column(Integer(), ForeignKey('orders.order_id'))
    cookie_id = Column(Integer(), ForeignKey('cookies.cookie_id'))
    quantity = Column(Integer())
    extended_cost = Column(Numeric(12, 2))

    order = relationship("Order", backref=backref('line_items', order_by=line_item_id))
    cookie = relationship("Cookie", uselist=False)

    def __repr__(self):
        return "LineItems(order_id={self.order_id}, " \
                      "cookie_id={self.cookie_id}, " \
                      "quantity={self.quantity}, " \
                      "extended_cost={self.extended_cost})".format(
                self=self)

Base.metadata.create_all(engine)

我成功运行以下示例,使用用户名 cookiemon 的用户填充数据库:

cookiemon = User(username='cookiemon',
    email_address='wellhithere@gmail.com',
    phone='416-555-1212',
    password='password'
    )

session.add(cookiemon)
session.commit()

现在在本书的下一个示例中,我被告知以下代码将起作用。

o1 = Order()
o1.user = cookiemon
session.add(o1)

cc = session.query(Cookie).filter(Cookie.cookie_name == "chocolate chip").one()
line1 = LineItem(cookie=cc, quantity=2, extended_cost=1.00)

pb = session.query(Cookie).filter(Cookie.cookie_name == 
                              "peanut butter").one()
line2 = LineItem(quantity=12, extended_cost=3.00)
line2.cookie = pb 
line2.order = o1

o1.line_items.append(line1)
o1.line_items.append(line2)
session.commit()

但是,由于数据库中已经存在用户 cookiemon,我收到以下错误:

sqlalchemy.exc.IntegrityError: (由于查询调用自动刷新而引发;如果此刷新过早发生,请考虑使用 session.no_autoflush 块) (pymysql.err.IntegrityError) (1062, u"Duplicate entry 'cookiemon ' for key'username'") [SQL: u'INSERT INTO users (username, email_address, phone, password, created_on, updated_on) VALUES (%(username)s, %(email_address)s, %(phone)s, % (密码)s, %(created_on)s, %(updated_on)s)'] [参数: {'username': 'cookiemon', 'phone': '416-555-1212', 'created_on': datetime.datetime (2018, 9, 14, 13, 7, 29, 353673), 'updated_on': datetime.datetime(2018, 9, 14, 13, 7, 29, 353701), 'password': 'passwordEW', 'email_address' :'wellhithere@gmail.com'}]

如果我先删除 cookiemon 用户,上面的订单代码就像一个魅力(这并不奇怪)。但这在现实世界中很难奏效。

我可以看到有一个使用 no_autoflush 的解决方案。但这有必要吗?还是我只是在这里遗漏了一些简单但重要的东西?

这是回溯:

cd "/Users/chris/Desktop/Banff Experiments" ; env "PYTHONIOENCODING=UTF-8" "PYTHONUNBUFFERED=1" "PYTHONPATH=/Users/chris/.vscode/extensions/ms-python.python-2018.8.0/pythonFiles/experimental/ptvsd" "/Users/chris/Desktop/Banff Experiments/banff/bin/python" -m ptvsd --host localhost --port 59094 "/Users/chris/Desktop/Banff Experiments/workspace4.py"
Traceback (most recent call last):
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/runpy.py", line 162, in _run_module_as_main
"__main__", fname, loader, pkg_name)
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/runpy.py", line 72, in _run_code
exec code in run_globals
 File "/Users/chris/.vscode/extensions/ms-python.python-2018.8.0/pythonFiles/experimental/ptvsd/ptvsd/__main__.py", line 211, in <module>
singlesession=args.single_session)
 File "/Users/chris/.vscode/extensions/ms-python.python-2018.8.0/pythonFiles/experimental/ptvsd/ptvsd/__main__.py", line 205, in main
debug_main(addr, name, kind, *extra, **kwargs)
 File "/Users/chris/.vscode/extensions/ms-python.python-2018.8.0/pythonFiles/experimental/ptvsd/ptvsd/_local.py", line 30, in debug_main
run_file(address, name, *extra, **kwargs)
 File "/Users/chris/.vscode/extensions/ms-python.python-2018.8.0/pythonFiles/experimental/ptvsd/ptvsd/_local.py", line 64, in run_file
run(argv, addr, **kwargs)
 File "/Users/chris/.vscode/extensions/ms-python.python-2018.8.0/pythonFiles/experimental/ptvsd/ptvsd/_local.py", line 125, in _run
_pydevd.main()
 File "/Users/chris/.vscode/extensions/ms-python.python-2018.8.0/pythonFiles/experimental/ptvsd/ptvsd/_vendored/pydevd/pydevd.py", line 1743, in main
debugger.connect(host, port)
 File "/Users/chris/.vscode/extensions/ms-python.python-2018.8.0/pythonFiles/experimental/ptvsd/ptvsd/_vendored/pydevd/pydevd.py", line 1099, in run
return self._exec(is_module, entry_point_fn, module_name, file, globals, locals)
 File "/Users/chris/.vscode/extensions/ms-python.python-2018.8.0/pythonFiles/experimental/ptvsd/ptvsd/_vendored/pydevd/pydevd.py", line 1106, in _exec
pydev_imports.execfile(file, globals, locals)  # execute the script
 File "/Users/chris/Desktop/Banff Experiments/workspace4.py", line 94, in <module>
session.commit()
 File "/Users/chris/Desktop/Banff Experiments/banff/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 943, in commit
self.transaction.commit()
 File "/Users/chris/Desktop/Banff Experiments/banff/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 467, in commit
self._prepare_impl()
 File "/Users/chris/Desktop/Banff Experiments/banff/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 447, in _prepare_impl
self.session.flush()
 File "/Users/chris/Desktop/Banff Experiments/banff/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 2254, in flush
self._flush(objects)
 File "/Users/chris/Desktop/Banff Experiments/banff/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 2380, in _flush
transaction.rollback(_capture_exception=True)
 File "/Users/chris/Desktop/Banff Experiments/banff/lib/python2.7/site-packages/sqlalchemy/util/langhelpers.py", line 66, in __exit__
compat.reraise(exc_type, exc_value, exc_tb)
 File "/Users/chris/Desktop/Banff Experiments/banff/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 2344, in _flush
flush_context.execute()
 File "/Users/chris/Desktop/Banff Experiments/banff/lib/python2.7/site-packages/sqlalchemy/orm/unitofwork.py", line 391, in execute
rec.execute(self)
 File "/Users/chris/Desktop/Banff Experiments/banff/lib/python2.7/site-packages/sqlalchemy/orm/unitofwork.py", line 556, in execute
uow
 File "/Users/chris/Desktop/Banff Experiments/banff/lib/python2.7/site-packages/sqlalchemy/orm/persistence.py", line 181, in save_obj
mapper, table, insert)
 File "/Users/chris/Desktop/Banff Experiments/banff/lib/python2.7/site-packages/sqlalchemy/orm/persistence.py", line 866, in _emit_insert_statements
execute(statement, params)
File "/Users/chris/Desktop/Banff Experiments/banff/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 948, in execute
return meth(self, multiparams, params)
 File "/Users/chris/Desktop/Banff Experiments/banff/lib/python2.7/site-packages/sqlalchemy/sql/elements.py", line 269, in _execute_on_connection
return connection._execute_clauseelement(self, multiparams, params)
 File "/Users/chris/Desktop/Banff Experiments/banff/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 1060, in _execute_clauseelement
compiled_sql, distilled_params
 File "/Users/chris/Desktop/Banff Experiments/banff/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 1200, in _execute_context
context)
 File "/Users/chris/Desktop/Banff Experiments/banff/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 1413, in _handle_dbapi_exception
exc_info
 File "/Users/chris/Desktop/Banff Experiments/banff/lib/python2.7/site-packages/sqlalchemy/util/compat.py", line 265, in raise_from_cause
reraise(type(exception), exception, tb=exc_tb, cause=cause)
 File "/Users/chris/Desktop/Banff Experiments/banff/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 1193, in _execute_context
context)
 File "/Users/chris/Desktop/Banff Experiments/banff/lib/python2.7/site-packages/sqlalchemy/engine/default.py", line 509, in do_execute
cursor.execute(statement, parameters)
 File "/Users/chris/Desktop/Banff Experiments/banff/lib/python2.7/site-packages/pymysql/cursors.py", line 170, in execute
result = self._query(query)
 File "/Users/chris/Desktop/Banff Experiments/banff/lib/python2.7/site-packages/pymysql/cursors.py", line 328, in _query
conn.query(q)
 File "/Users/chris/Desktop/Banff Experiments/banff/lib/python2.7/site-packages/pymysql/connections.py", line 516, in query
self._affected_rows = self._read_query_result(unbuffered=unbuffered)
 File "/Users/chris/Desktop/Banff Experiments/banff/lib/python2.7/site-packages/pymysql/connections.py", line 727, in _read_query_result
result.read()
 File "/Users/chris/Desktop/Banff Experiments/banff/lib/python2.7/site-packages/pymysql/connections.py", line 1066, in read
first_packet = self.connection._read_packet()
 File "/Users/chris/Desktop/Banff Experiments/banff/lib/python2.7/site-packages/pymysql/connections.py", line 683, in _read_packet
packet.check_error()
 File "/Users/chris/Desktop/Banff Experiments/banff/lib/python2.7/site-packages/pymysql/protocol.py", line 220, in check_error
err.raise_mysql_exception(self._data)
 File "/Users/chris/Desktop/Banff Experiments/banff/lib/python2.7/site-packages/pymysql/err.py", line 109, in raise_mysql_exception
raise errorclass(errno, errval)
sqlalchemy.exc.IntegrityError: (pymysql.err.IntegrityError) (1062, u"Duplicate entry 'cookiemon' for key 'username'") [SQL: u'INSERT INTO users (username, email_address, phone, password, created_on, updated_on) VALUES (%(username)s, %(email_address)s, %(phone)s, %(password)s, %(created_on)s, %(updated_on)s)'] [parameters: {'username': 'cookiemon', 'phone': '416-555-1212', 'created_on': datetime.datetime(2018, 9, 14, 13, 44, 46, 916687), 'updated_on': datetime.datetime(2018, 9, 14, 13, 44, 46, 916712), 'password': 'passwordEW', 'email_address': 'wellhithere@gmail.com'}] (Background on this error at: http://sqlalche.me/e/gkpj)

【问题讨论】:

  • 尝试捕获完整性错误有什么问题,然后向用户发送一条消息以通知他们用户名已被占用?我在这里错过了什么吗?
  • 很抱歉在没有足够背景的情况下对上面的事情进行了简要介绍。据我了解,此示例与用户订购某些 cookie 的情况相匹配,但他们已经在两个月前订购了 cookie。结果,它们在数据库中。现在,两个月过去了,他们饿了。 :) 所以他们想下一个新订单。所以,它是同一个用户,但有一个新订单。
  • 关于使用 SQLAlchemy 的对象数据库的非常有用的教程在这里 blog.miguelgrinberg.com/post/…
  • 我正在尝试重现您的问题,但您的代码在这里运行良好。我唯一添加的是创建“巧克力片”和“花生酱”Cookie() 对象并将它们添加到会话中。您能否提供完整的错误回溯以及行号?另外请将其格式化为代码,否则将很难阅读。
  • 你能幽默一下并检查你的数据库模式中的 cookie 表吗?我相信你在某个地方有一个你不应该有的独特约束。它没有显示在您的模型中,但是如果您创建了数据库并说用户 ID 字段必须是唯一的,那么无论您的类做什么,它都会失败或稍后导致更多问题。

标签: python sqlalchemy


【解决方案1】:

现在阅读 cmets,我想我了解您的问题。这段代码:

cookiemon = User(username='cookiemon',
    email_address='wellhithere@gmail.com',
    phone='416-555-1212',
    password='password'
    )

session.add(cookiemon)
session.commit()

创建一个新用户并将其存储在数据库中。您不能运行它两次,因为它已经存储在数据库中。您只能执行一次,否则会出现错误。

现在以后,如果您想使用同一个用户,您将无法再次创建它。如果内存中仍然有相同的 cookiemon 变量,则可以直接使用它,但如果没有,则必须从数据库中获取现有用户,而不是创建新用户:

cookiemon = session.query(User).filter(User.username=='cookiemon').one()

您可以在一次执行中将两者结合起来:

# try to get the user from the databae
cookiemon = session.query(User).filter(User.username=='cookiemon').first()
if cookiemon is None: # not found, create new one:
    cookiemon = User(
        username='cookiemon',
        email_address='wellhithere@gmail.com',
        phone='416-555-1212',
        password='password'
    )
    session.add(cookiemon)
    session.commit()

【讨论】:

  • 是的。啊,一千感谢!有效!虽然我做了 filter(User.username == 'cookiemon').one() 我希望在示例 7-24 和示例 7-25 之间已经提到了这一点,我坚信这将有助于未来的读者。太感谢了。哦,组合。非常非常酷。非常感谢您的帮助。
  • 是的。你明白了,伙计:)
  • @Chris 这本书似乎假设您使用与上一个示例相同的cookiemon 变量,就像在单个交互式连续练习中一样。如果关闭python并再次运行,该变量将丢失,并且您无法创建另一个同名用户。
  • @nosklo。公平和真实。 :) 而且,这表明我需要花更多时间来理解会话和变量。是时候多学习了!干杯。
猜你喜欢
  • 2020-04-01
  • 2011-11-11
  • 2011-01-13
  • 2012-07-23
  • 2019-08-01
  • 1970-01-01
  • 2012-06-04
  • 2021-04-28
  • 2013-02-27
相关资源
最近更新 更多