将 SQLAlchemy 声明式基础与 SQL 模型一起使用

Posted

技术标签:

【中文标题】将 SQLAlchemy 声明式基础与 SQL 模型一起使用【英文标题】:Using SQLAlchemy declarative base with SQL Model 【发布时间】:2022-01-05 08:31:17 【问题描述】:

在我们的一个项目中,我们多年来(而且有很多)对所有模型都使用了 SQL Alchemy 声明性基础。 我们想尝试使用新的SQLModel library 来获取我们最新的模型声明。

为此,我们尝试将它与 Base 对象分开声明,并为两者调用 create_all 方法。

即:Base.metadata.create_all()SQLModel.metadata.create_all()

但是用 SQLModel 声明的模型不能识别用 Base 声明的表。

目前,我们无法将之前的所有模型声明从 Base 更改为 SQLModel。

这是一个可重现的代码:

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column
from sqlalchemy import Integer
from typing import Optional
from sqlmodel import Field, SQLModel
from sqlalchemy import String

# Declarative base object
Base = declarative_base()

class DummySATable(Base):
    __tablename__ = 'dummy_table'
    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(32))


class DummyModelTable(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    dummy_table_id : int = Field(default=None, foreign_key='dummy_table.id')
    name: str


Base.metadata.create_all(engine)
SQLModel.metadata.create_all(engine)

这是回溯:

NoReferencedTableError                    Traceback (most recent call last)

/tmp/ipykernel_307893/3665898561.py in <module>
     24 
     25 Base.metadata.create_all(engine)
---> 26 SQLModel.metadata.create_all(engine)
     27 

~/project/venv/lib/python3.9/site-packages/sqlalchemy/sql/schema.py in create_all(self, bind, tables, checkfirst)
   4783         if bind is None:
   4784             bind = _bind_or_error(self)
-> 4785         bind._run_ddl_visitor(
   4786             ddl.SchemaGenerator, self, checkfirst=checkfirst, tables=tables
   4787         )

~/project/venv/lib/python3.9/site-packages/sqlalchemy/engine/base.py in _run_ddl_visitor(self, visitorcallable, element, **kwargs)
   3108     def _run_ddl_visitor(self, visitorcallable, element, **kwargs):
   3109         with self.begin() as conn:
-> 3110             conn._run_ddl_visitor(visitorcallable, element, **kwargs)
   3111 
   3112     @util.deprecated_20(

~/project/venv/lib/python3.9/site-packages/sqlalchemy/engine/base.py in _run_ddl_visitor(self, visitorcallable, element, **kwargs)
   2111 
   2112         """
-> 2113         visitorcallable(self.dialect, self, **kwargs).traverse_single(element)
   2114 
   2115     @util.deprecated(

~/project/venv/lib/python3.9/site-packages/sqlalchemy/sql/visitors.py in traverse_single(self, obj, **kw)
    522             meth = getattr(v, "visit_%s" % obj.__visit_name__, None)
    523             if meth:
--> 524                 return meth(obj, **kw)
    525 
    526     def iterate(self, obj):

~/project/venv/lib/python3.9/site-packages/sqlalchemy/sql/ddl.py in visit_metadata(self, metadata)
    820             tables = list(metadata.tables.values())
    821 
--> 822         collection = sort_tables_and_constraints(
    823             [t for t in tables if self._can_create_table(t)]
    824         )

~/project/venv/lib/python3.9/site-packages/sqlalchemy/sql/ddl.py in sort_tables_and_constraints(tables, filter_fn, extra_dependencies, _warn_for_cycles)
   1284                     continue
   1285 
-> 1286             dependent_on = fkc.referred_table
   1287             if dependent_on is not table:
   1288                 mutable_dependencies.add((dependent_on, table))

~/project/venv/lib/python3.9/site-packages/sqlalchemy/sql/schema.py in referred_table(self)
   3703 
   3704         """
-> 3705         return self.elements[0].column.table
   3706 
   3707     def _validate_dest_table(self, table):

~/project/venv/lib/python3.9/site-packages/sqlalchemy/util/langhelpers.py in __get__(self, obj, cls)
   1111         if obj is None:
   1112             return self
-> 1113         obj.__dict__[self.__name__] = result = self.fget(obj)
   1114         return result
   1115 

~/project/venv/lib/python3.9/site-packages/sqlalchemy/sql/schema.py in column(self)
   2408 
   2409             if tablekey not in parenttable.metadata:
-> 2410                 raise exc.NoReferencedTableError(
   2411                     "Foreign key associated with column '%s' could not find "
   2412                     "table '%s' with which to generate a "

NoReferencedTableError: Foreign key associated with column 'dummymodeltable.dummy_table_id' could not find table 'dummy_table' with which to generate a foreign key to target column 'id'

我错过了什么?有没有可能(有什么解决方法吗?)

【问题讨论】:

【参考方案1】:

根据this thread,我终于找到了一种简单的方法。由于 SQLModel 继承了 SQLAlchemy 的 Metadata 对象,我们可以简单地将 SQLModel 的元数据对象绑定到 SQLAlchemy 的元数据对象:

# Declarative base object
Base = declarative_base()
SQLModel.metadata = Base.metadata

# Table declaration....

SQLModel.metadata.create_all(engine)

【讨论】:

以上是关于将 SQLAlchemy 声明式基础与 SQL 模型一起使用的主要内容,如果未能解决你的问题,请参考以下文章

使用 SQL Alchemy 声明性基础处理元类冲突

在 SQLAlchemy 中处理插入时重复的主键(声明式样式)

SQLAlchemy 声明式:定义触发器和索引 (Postgres 9)

sqlalchemy 查询:结果列名

SQLAlchemy 中的动态类创建

基础入门_Python-模块和包.深入SQLAlchemy之列级别约束与表级别约束?