定义后更改SQLAlchemy主键

时间:2018-07-30 04:10:26

标签: python orm sqlalchemy

问题:简而言之,我试图在定义SQLAlchemy ORM表的主键后重新定义它。

示例

bearer your_jwt_token

*请注意,这是我要解决的真正问题的简化。

可能的解决方案:您的第一个想法可能是做这样的事情:

class Base:

    @declared_attr
    def __tablename__(cls):
        return f"{cls.__name__}"


    @declared_attr
    def id(cls):
        return Column(Integer, cls.seq, unique=True, 
                      autoincrement=True, primary_key=True)

Base = declarative_base(cls=Base)

class A_Table(Base):
    newPrimaryKeyColumnsDerivedFromAnotherFunction = []
    # Please Note: as the variable name tries to say,
    # these columns are auto-generated and not known until after all
    # ORM classes (models) are defined

# OTHER CLASSES


def changePriKeyFunc(model):
    pass # DO STUFF

# Then do
Base.metadata.create_all(bind=arbitraryEngine)
# After everything has been altered and tied into a little bow

但是,这不起作用,因为在尝试添加,刷新和提交时,会引发错误:

def possibleSolution(model):
    for pricol in model.__table__.primary_key:
        pricol.primary_key = False

    model.__table__.primary_key = PrimaryKeyConstraint(
        *model.newPrimaryKeyColumnsDerivedFromAnotherFunction,

        # TODO: ADD all the columns that are in the model that are also a primary key
        # *[col for col in model.__table__.c if col.primary_key]
        )

即使这样:

InvalidRequestError: Instance <B_Table at 0x104aa1d68> cannot be refreshed - 
it's not persistent and does not contain a full primary key.

以及此:

In [2]: B_Table.__table__.primary_key
Out[2]: PrimaryKeyConstraint(Column('a_TableId', Integer(),
                                    ForeignKey('A_Table.id'), table=<B_Table>, 
                                    primary_key=True, nullable=False))

最后:

In [3]: B_Table.__table__
Out[3]: Table('B_Table', MetaData(bind=None), 
              Column('id', Integer(), table=<B_Table>, nullable=False,
                     default=Sequence('test_1', start=1, increment=1, 
                                      metadata=MetaData(bind=None))), 
              Column('a_TableId', Integer(), 
                     ForeignKey('A_Table.id'), table=<B_Table>, 
                     primary_key=True, nullable=False), 
              schema=None)

还要注意,数据库实际上反映了更改后的(和真实的)主键,所以我知道ORM / SQLAlchemy发生了某些事情。

问题:总而言之,在定义了模型之后,如何更改模型的主键?

编辑:参见下面的完整代码(相同类型的错误,仅在SQLite中)

In [5]: b.a_TableId
Out[5]: 1

1 个答案:

答案 0 :(得分:0)

最初,您说PK重新分配引起的错误是:

InvalidRequestError: Instance <B_Table at 0x104aa1d68> cannot be refreshed - 
it's not persistent and does not contain a full primary key.

我没有得到让您运行MCVE的信息,相反,我首先得到一个非常有用的警告:

  

SAWarning:列'B_Table.A_TableId'被标记为   表'B_Table'的主键,但没有Python端或服务器端   指示了默认生成器,也未指示“ autoincrement = True”   或'nullable = True',并且不传递任何显式值。首要的关键   列通常可能不存储NULL。

以及脚本失败时的非常详细的异常消息:

  

sqlalchemy.orm.exc.FlushError:实例具有   NULL身份密钥。如果这是一个自动生成的值,请检查   数据库表允许生成新的主键值,以及   映射的Column对象配置为期望生成这些   价值观。还要确保此flush()不会在   不适当的时间,例如在load()事件中。

因此,假设该示例准确地描述了您的问题,那么答案很简单。主键不能为空。

A_Table继承自Base

class A_Table(Base):
    pass

Base通过A_Table autoincrementdeclared_attr提供了一个id() PK:

@declared_attr
def id(cls):
    return Column(Integer, cls.seq, unique=True, autoincrement=True, primary_key=True)

类似地,B_Table是在Base上定义的,但是PK在possibleSolution()中被覆盖,因此它变成了ForeignKeyA_Table

PrimaryKeyConstraint(Column('A_TableId', Integer(), ForeignKey('A_Table.id'), table=<B_Table>, primary_key=True, nullable=False))

然后,我们实例化A_Table的实例而没有任何变节,并在构造id时立即将实例a的{​​{1}}属性分配给字段A_TableId

b

此时,我们可以停止并检查每个属性的值:

a = A_Table()
b = B_Table(A_TableId=a.id)

print(a.id, b.A_TableId) # None None a.id,因为它是一个None,需要由数据库而不是ORM填充。因此,SQLAlchemy直到将实例刷新到数据库后才知道它的值。

因此,如果在将实例autoincrement添加到flush()之后包含a操作,会发生什么情况:

session

因此,通过首先发出a = A_Table() session.add(a) session.flush() b = B_Table(A_TableId=a.id) print(a.id, b.A_TableId) # 1 1 ,我们获得了flush的值,这意味着我们也具有b.A_TableId的值。

a.id