Alembic迁移脚本:
def upgrade():
uuid_gen = saexp.text("UUID GENERATE V1MC()")
op.create_table(
'foo',
sa.Column('uuid', UUID, primary_key=True, server_default=uuid_gen),
sa.Column(
'inserted',
sa.DateTime(timezone=True),
server_default=sa.text("not null now()"))
sa.Column('data', sa.Text)
)
这是我的SQL炼金术基类:
Class Foo(Base):
__tablename__ = 'foo'
inserted = Column(TIMESTAMP)
uuid = Column(UUID, primary_key=True)
data = Column(TEXT)
它有一个用于插入的静态方法:
@staticmethod
def insert(session, jsondata):
foo = Foo()
foo.data = jsondata['data']
if 'inserted' in jsondata:
foo.inserted = jsondata['inserted']
if 'uuid' in jsondata:
foo.uuid = jsondata['uuid']
session.add(foo)
return foo
2个if的目的是简化测试。通过这种方式,我可以"注入"一个uuid和插入的日期,为我的测试获得可预测的数据
尝试插入数据时
foo = Foo()
foo.insert(session, {"data": "foo bar baz"})
session.commit()
我得到一个完整性错误:
[SQL: 'INSERT INTO foo (inserted, data) VALUES (%(inserted)s, %(data)s) RETURNING foo.uuid'] [parameters: {'data': 'foo bar baz', 'inserted': None}]
wich在我看来很正常,因为插入违反了postgres数据库中的"not null"约束。
如何防止sql alchemy将None值插入到插入的字段中?
在玩和测试时,我发现如果"inserted"列被定义为主键,那么sql alchemy就不包括insert语句中的字段。
def upgrade():
uuid_gen = saexp.text("UUID GENERATE V1MC()")
op.create_table(
'foo',
sa.Column('uuid', UUID, primary_key=True, server_default=uuid_gen),
sa.Column(
'inserted',
primary_key=True,
sa.DateTime(timezone=True),
server_default=sa.text("not null now()"))
sa.Column('data', sa.Text)
)
但这不是我想要的。
主要问题是server_default
,它在类Foo
的inserted
成员中丢失。它只出现在alembic脚本中。请注意,alembic定义仅在运行迁移时使用。它们不会影响应用程序。因此,最好将完全相同的定义从alembic脚本复制到您的应用程序中(反之亦然)。
因为模型定义中没有定义任何值,所以当类被实例化时,sqlalchemy似乎将其设置为None
。这将被发送到DB,DB将进行投诉。要解决此问题,请在模型定义(继承自Base
的类)上设置default
或server_default
。
一些附加说明/问题:
UUID GENERATE V1MC()
来自哪里?官方文件看起来不一样。我用func.uuid_generate_v1mc()
替换了它- 您案例中的
server_default
值包含不正确的not null
。您应该在列属性上设置nullable=False
(请参见下文)
alembic脚本
# revision identifiers, used by Alembic.
revision = THIS_IS_DIFFERENT_ON_EACH_INSTANCE! # '1b7e145f2138'
down_revision = None
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects.postgresql import UUID
def upgrade():
op.create_table(
'foo',
sa.Column('uuid', UUID, primary_key=True,
server_default=sa.func.uuid_generate_v1mc()),
sa.Column(
'inserted',
sa.DateTime(timezone=True),
nullable=False,
server_default=sa.text("now()")),
sa.Column('data', sa.Text)
)
def downgrade():
op.drop_table('foo')
tester.py
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, create_engine, func
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.dialects.postgresql import (
TEXT,
TIMESTAMP,
UUID,
)
engine = create_engine('postgresql://michel@/michel')
Session = scoped_session(sessionmaker(autocommit=False,
autoflush=False,
bind=engine))
Base = declarative_base()
class Foo(Base):
__tablename__ = 'foo'
inserted = Column(TIMESTAMP, nullable=False,
server_default=func.now())
uuid = Column(UUID, primary_key=True,
server_default=func.uuid_generate_v1mc()),
data = Column(TEXT)
@staticmethod
def insert(session, jsondata):
foo = Foo()
foo.data = jsondata['data']
if 'inserted' in jsondata:
foo.inserted = jsondata['inserted']
if 'uuid' in jsondata:
foo.uuid = jsondata['uuid']
session.add(foo)
return foo
if __name__ == '__main__':
session = Session()
Foo.insert(session, {"data": "foo bar baz"})
session.commit()
session.close()
执行后输出
[9:43:54] michel@BBS-nexus [1 background job(s)]
/home/users/michel/tmp› psql -c "select * from foo"
uuid | inserted | data
--------------------------------------+-------------------------------+-------------
71f5fd32-0602-11e6-aebb-27be4bbac26e | 2016-04-19 09:43:45.297191+02 | foo bar baz
(1 row)