为啥 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
,则本质上是无操作,并且与根本不设置它具有相同的效果,因此使用默认值。 Column
中autoincrement
的默认设置是"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 的主键?的主要内容,如果未能解决你的问题,请参考以下文章
数据库连接对象不是 Python Django SQL Alchemy 数据库池引发的可调用异常。为啥?
flask-restless 限制 RESTful api 访问