SQLAlchemy 动态/编程更新表定义

Posted

技术标签:

【中文标题】SQLAlchemy 动态/编程更新表定义【英文标题】:SQLAlchemy dynamically/programatically updating table definitions 【发布时间】:2021-11-06 00:57:00 【问题描述】:

我正在使用如下所示的 SQLAlchemy 模型:

class mytableclass(Base):
   __tablename__ = "table_name"

   __table__ = Table(
       __tablename__,
       Base.metadata,
      Column("Customer_id", String, primary_key=True),
      Column("Full_name", String),
      Column("Occupation", String),
 )

我有大约 50-60 个带有 sqlalchemy 表定义的 python 文件,我需要对它们进行一些更新。我有一个 csv,其中定义了类名和需要更新的内容,例如添加列、更改列类型或向列添加一些元数据。

现在,由于我正在处理如此大量的数据,我想知道如何以编程方式实现这一点,而不需要手动完成,因为将来会需要它。

一种方法是将 python 文件视为字符串并围绕它构建一些逻辑。但我有一种感觉,既然它是一个 python 数据结构,那么应该有一些“python”的方式来实现这一点。欢迎所有建议。

【问题讨论】:

【参考方案1】:

您可以像下面的原型一样动态加载整个模型集。它不能处理很多边缘情况,例如将更多信息传递给表或建立关系。

我认为真正的问题是当您添加一列时,您必须更改表并可能操作/迁移现有数据以使其有意义。像 alembic 这样的东西可以尝试自动生成迁移,但大多数时候您必须自定义这些迁移或添加无法自动检测到的部分。因此,您可以从 csv 重新加载所有模型,然后运行手动编码迁移以更新数据库中的实际数据。与手动更改模型文件相比,我认为这真的不值得,因为您仍然需要编写迁移代码。

即使模型存储在 JSON 中并且您的迁移同时更改了 db 和 JSON,这也将是您一直在解决的一个巨大的自定义问题。 IE。这不处理没有相应类的关联表。会有很多边缘情况。不过这个想法很有趣。

最后有一些链接。

无论如何,这是我的想法:

#necessary sqlalchemy imports ...


Base = declarative_base()


engine = create_engine("sqlite://", echo=False)

# Constrain allowed types by filtering them through whitelist.
allowed_types = dict([(t.__name__, t) for t in (String, Integer,)])

# Extract this from CSV.
models = [
    'classname': 'Customer',
    'tablename': 'customers',
    'cols': [
        'name': 'customer_id', 'type': 'String', 'extra_kwargs': 'primary_key': True,
        'name': 'full_name', 'type': 'String',
        'name': 'occupation', 'type': 'String',
    ],
]

# Create class and table for each model and then shove the class
# into the globals()
for model in models:
    cols = [Column(col['name'], allowed_types[col['type']], **col.get('extra_kwargs', )) for col in model['cols']]
    globals()[model['classname']] = type(model['classname'], (Base,), 
        '__tablename__': model['tablename'],
        '__table__': Table(
            model['tablename'],
            Base.metadata,
            *cols),
    )

Base.metadata.create_all(engine)

with Session(engine) as session:
    customer = Customer(customer_id='some-guy', full_name='Some Guy', occupation='Some Job')
    session.add(customer)
    customers = session.query(Customer).all()
    assert len(customers) == 1
    assert customers[0].customer_id == 'some-guy'

alembic operations

dynamic class generation

type built-in

【讨论】:

嗨,Ian,感谢您的回答,这绝对可以帮助我走上正轨。不过有一些澄清:我没有完整的列列表,我只知道需要编辑的列,其余列需要以某种方式从类和 tabledefinition 中读取。最后,我如何将这个东西转储到 .py 文件中?还是这个 globals() 以某种方式实现它?我对此不熟悉,需要仔细阅读。 @Anton 我对此进行了更多思考,但这并不是您真正要问的。我认为操纵 python 代码本身将非常困难。这个解决方案只是从 JSON 样式的定义动态创建 Python sqlalchemy 类。因此,它会将所有模型文件替换为单个文件,该文件包含从 JSON 生成的所有模型。我不确定这是否会节省时间,因为实施尚未完成并且可能难以完全完成。

以上是关于SQLAlchemy 动态/编程更新表定义的主要内容,如果未能解决你的问题,请参考以下文章

Python 学习笔记 - SQLAlchemy(下)

在SQLAlchemy ORM中动态变更表名

基于表库动态创建sqlAlchemy Metaclass

flask-sqlalchemy 外连接的 Flask-marshmallow 转储返回空

flask mysql sqlalchemy教程

Sqlalchemy 动态创建表和映射类