为啥 Flask SQL Alchemy 允许保存 None 的主键?

Posted

技术标签:

【中文标题】为啥 Flask SQL Alchemy 允许保存 None 的主键?【英文标题】:Why Flask SQL Alchemy allowing save of primary key of None?为什么 Flask SQL Alchemy 允许保存 None 的主键? 【发布时间】:2019-11-26 00:02:29 【问题描述】:

尽管主键为空并且在主键字段中添加了nullable=False(即使没有必要添加它),但 Flask SQL Alchemy 仍保存到 DB。

我正在分配 id,而不是使用正常的自动增量或 SQL Alchemy 附带的任何东西。

模型.py

class User(db.Model):
  id = db.Column(db.Integer, primary_key=True, unique=True, nullable=False)

  # passing in the ID manually
  @classmethod
  def new(cls, sender_id):
      try:
         db.create_all()
         d = cls()
         d.id = sender_id
         return d
      except Exception as e:
         print('Error in User new:', e)

   def insert(self):
      try:
          db.session.add(self)
          db.session.commit()
          print('INSERT OKAY')
      except Exception as e:
          print('RollBack', e)
          db.session.rollback()

test.py

#FLASK_ENV = 'dev_testing'
#----SETUP
# load env variables
def setup_testing_environment():
    load_dotenv(find_dotenv(".env", raise_error_if_not_found=True)

#test setup testing DB
def create_test_app():
    try:
        app = Flask(__name__)
        app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
        if os.environ['FLASK_ENV'] == 'development' or os.environ['FLASK_ENV'] == 'dev_testing':
            setup_testing_environment()
        app.config['SQLALCHEMY_DATABASE_URI'] = "sqlite:///:memory:"
        return app
    except Exception as e:
        print('Error in create_test_app', e)


#---- ACTUAL TEST
def test_should_fail(unittest.testcase):
    #assign DB and create context for test to run
    app = create_test_app()
    with app.app_context():
       db.init_app(app)
       # make ID value None;
       user = User.new(None)
       # <User id=None>
       user.insert()
      # fails - user *is* being added to DB 
       assert user in not db.session

       #end test
       db.session.remove()
       db.drop_all()

没有发生回滚。 SQL Alchemy 没有显示任何错误。用户正在保存到数据库中,但由于没有 ID,因此无法查找。 User 表完全为空。

为什么 SQL Alchemy 没有捕捉到这一点?如何分配 ID 以使其正常工作?

【问题讨论】:

这能回答你的问题吗? How does `nullable=False` work in SQLAlchemy 不,我不这么认为,当时数据库正在使用sqlite:///:memory: 创建和销毁,因此在此测试运行之前不存在。因此不能存在带有nullable=True 的表。 【参考方案1】:

SQLAlchemy 的作者注意到here

SQLAlchemy 没有内置此功能,因为它将 DBAPI/数据库视为验证和强制值的最佳和最有效的来源。

换句话说,NOT NULL 约束等的执行留给了数据库,因为它应该 - 数据库是唯一的事实来源。如果需要,您当然也可以在模型中实现 validation。

如果在transient / pending 实例上将主键属性设置为None,则本质上是无操作,并且与根本不设置它具有相同的效果,因此使用默认值。 Columnautoincrement 的默认设置是"auto",这意味着如果模型有一个没有显式默认值的单列整数主键,它会接收自动递增行为。如果您启用日志记录,您会看到发出的查询类似于

INSERT INTO user DEFAULT VALUES

换句话说,在您的情况下,您的实例在刷新到数据库时会收到自动递增的主键值。如果你在db.commit()之后访问user.id,你会得到生成的主键值。

最后,

# fails - user *is* being added to DB 
assert user not in db.session

不断言user 是否在数据库中,但如果它在当前会话中,尽管在您的测试中,在调用db.commit() 之后它们确实也在数据库中,这会隐式刷新待处理会话中的状态。

【讨论】:

如果字段是普通的非主键字段,它会捕获 nullable=False 用于其他模型中的其他项目。那是我的困惑——但我想我明白你说什么。事实上,它已被设置,因此不会引发错误。不过,我确实将它保存到持久数据库中,这与使用刷新的测试不同,并检查用户表是否没有用户。只有一张空白表,所以不知道发生了什么。然后我将添加我自己的验证。感谢您的链接。

以上是关于为啥 Flask SQL Alchemy 允许保存 None 的主键?的主要内容,如果未能解决你的问题,请参考以下文章

ORM查询多对多一,Flask sql-alchemy

数据库连接对象不是 Python Django SQL Alchemy 数据库池引发的可调用异常。为啥?

flask-restless 限制 RESTful api 访问

python Sql Alchemy样板

如何在 Flask-SQLAlchemy 中按 id 删除记录

如何获取受 SQL Alchemy 影响的行数?