繁体   English   中英

运行时实体上的 sqlalchemy 动态模式

[英]sqlalchemy dynamic schema on entity at runtime

我正在使用 SQL Alchemy 并且有一些特定于帐户的架构。 架构的名称是使用帐户 ID 派生的,因此在我访问应用程序服务或存储库层之前,我没有架构的名称。 我想知道是否可以针对在运行时动态设置其架构的实体运行查询?

我知道我需要设置__table_args__['schema']并尝试使用内置的 type() 来做到这一点,但我总是收到以下错误:

could not assemble any primary key columns for mapped table

我准备放弃,直接写 sql,但我真的很讨厌那样做。 知道如何做到这一点吗? 我正在使用 SA 0.99 并且我确实有一个 PK 映射。

谢谢

从 sqlalchemy 1.1 开始,这可以使用 schema_translation_map 轻松完成。

https://docs.sqlalchemy.org/en/11/changelog/migration_11.html#multi-tenancy-schema-translation-for-table-objects

一种选择是反映特定的帐户相关表。 这是有关此事的SqlAlchemy 文档

或者,您可以使用静态schema属性创建表并在运行时根据需要更新它并运行您需要的查询。 我想不出一个不麻烦的方法来做到这一点。 所以这是凌乱的选择

每当切换帐户时,使用循环来更新每个表定义中的架构属性。

  1. 将所有特定于帐户的表添加到列表中。

    • 如果表以声明性语法表示,则必须修改DeclarativeName.__table__.schema属性。 我不确定您是否还需要修改DeclarativeName.__table_args__['schema'] ,但我想它不会受到伤害。
    • 如果表以旧样式表语法表示,则必须修改Table.schema属性。
  2. 如果您对任何关系或外键使用文本,那么这将中断,您必须检查每个表是否有这种硬编码的用法并更改它们
    例子

    • user_id = Column(ForeignKey('my_schema.user.id'))需要写成user_id = Column(ForeignKey(User.id)) 然后您可以将User的架构更改为my_new_schema 否则,在查询时 sqlalchemy 会感到困惑,因为外键将指向my_schema.user.id而查询将指向my_new_schema.user
  3. 我不确定不使用纯文本是否可以表达更复杂的关系,所以我想这是我提出的解决方案的限制。

这是我在终端中写的一个例子:

>>> from sqlalchemy import Column, Table, Integer, String, select, ForeignKey
>>> from sqlalchemy.orm import relationship, backref
>>> from sqlalchemy.ext.declarative import declarative_base
>>> B = declarative_base()
>>> 
>>> class User(B):
...   __tablename__ = 'user'
...   __table_args__ = {'schema': 'first_schema'}
...   id = Column(Integer, primary_key=True)
...   name = Column(String)
...   email = Column(String)
... 
>>> class Posts(B):
...   __tablename__ = 'posts'
...   __table_args__ = {'schema':'first_schema'}
...   id = Column(Integer, primary_key=True)
...   user_id = Column(ForeignKey(User.id))
...   text = Column(String)
... 
>>> str(select([User.id, Posts.text]).select_from(User.__table__.join(Posts)))
'SELECT first_schema."user".id, first_schema.posts.text \nFROM first_schema."user" JOIN first_schema.posts ON first_schema."user".id = first_schema.posts.user_id'
>>> account_specific = [User, Posts]
>>> for Tbl in account_specific:
...   Tbl.__table__.schema = 'second_schema'
... 
>>> str(select([User.id, Posts.text]).select_from(User.__table__.join(Posts)))
'SELECT second_schema."user".id, second_schema.posts.text \nFROM second_schema."user" JOIN second_schema.posts ON second_schema."user".id = second_schema.posts.user_id'

如您所见,在我更新表的架构属性后,相同的查询引用了second_schema

编辑:虽然你可以做我在这里做的事情,但使用下面的答案中显示的模式转换映射是正确的方法。

它们是静态设置的。 外键需要相同的处理,我还有一个问题,因为我有多个包含多个表的模式,所以我这样做了:

from sqlalchemy.ext.declarative import declarative_base

staging_dbase = declarative_base()
model_dbase = declarative_base()


def adjust_schemas(staging, model):
    for vv in staging_dbase.metadata.tables.values():
        vv.schema = staging
    for vv in model_dbase.metadata.tables.values():
        vv.schema = model


def all_tables():
    return staging_dbase.metadata.tables.union(model_dbase.metadata.tables)

然后在我的启动代码中:

adjust_schemas(staging=staging_name, model=model_name)

您可以为单个声明性基础修改它。

我正在开发一个项目,在该项目中我必须动态创建 postgres 模式和表,然后在适当的模式中插入数据。 这是我所做的事情,也许它会帮助某人:

import sqlalchemy
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from app.models.user import User

engine_uri = "postgres://someusername:somepassword@localhost:5432/users"
engine = create_engine(engine_uri, pool_pre_ping=True)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def create_schema(schema_name: str):
    """
        Creates a new postgres schema
        - **schema_name**: name of the new schema to create
    """
    if not engine.dialect.has_schema(engine, schema_name):
        engine.execute(sqlalchemy.schema.CreateSchema(schema_name))


def create_tables(schema_name: str):
    """
        Create new tables for postgres schema
        - **schema_name**: schema in which tables are to be created
    """
    if (
        engine.dialect.has_schema(engine, schema_name) and
        not engine.dialect.has_table(engine, str(User.__table__.name))
    ):
        User.__table__.schema = schema_name
        User.__table__.create(engine)


def add_data(schema_name: str):
    """
        Add data to a particular postgres schema
        - **schema_name**: schema in which data is to be added
    """
    if engine.dialect.has_table(engine, str(User.__table__.name)):
        db = SessionLocal()
        db.connection(execution_options={
            "schema_translate_map": {None: schema_name}},
        )
        user = User()
        user.name = "Moin"
        user.salary = 10000
        db.add(user)
        db.commit()

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM