SQLAlchemy:修剪常见的数据库列前缀

mhd*_*mhd 5 python sqlalchemy

我有一个数据库,其中每个表都有一个所有列的公共前缀(可能是为了避免别名).像这样:

CREATE TABLE
    PERSON
    (
       PER_GUID RAW(16) DEFAULT SYS_GUID() NOT NULL,
       PER_FIRSTNAME NVARCHAR2(50),
       PER_LASTNAME NVARCHAR2(50),
       PRIMARY KEY (PER_GUID)
     )
Run Code Online (Sandbox Code Playgroud)

现在,当将此模式映射到SQLAlchemy ORM(使用declarative_base)时,我想在将其映射到对象时删除前缀.

我当然可以手动完成:

class Person(Base):
    __tablename__ = 'person'

    guid = Column('per_guid', RAW, primary_key = True)
    firstname = Column('per_firstname', String)
    lastname = Column('per_lastname', String)
Run Code Online (Sandbox Code Playgroud)

但除了所有冗余类型之外,这将绕过声明性映射的一些好处.基本上我需要反转__mapper_args__ = {'column_prefix': 'per_'},它的前缀是我的属性.

这样做的恰当方法是什么?我是否必须捕获一些映射事件?用自定义基类做一些事情?前缀的长度各不相同,但它们都以下划线结尾,因此通用的不需要指定的确切表前缀的东西也可以工作.

sna*_*erb 1

如果可以轻松计算列前缀,则可以使用列反射事件侦听器来完成修剪,将每列的key属性更改为所需的值。该属性用于将 ORM 模型列映射到表列。

请注意,要使其工作,必须显式反映表 -Base.metadata.create_all不会触发侦听器。

import re
import sqlalchemy as sa
from sqlalchemy import orm

engine = sa.create_engine('postgresql+psycopg2:///test')


class Base(orm.DeclarativeBase):
    pass


@sa.event.listens_for(Base.metadata, "column_reflect")
def column_reflect(inspector, table, column_info):
    # Determine the prefix; here we assume all characters up to 
    # and including the first underscore in the column name.
    prefix = re.match(r'^([a-z]+_)', column_info['name'])[1]
    column_info["key"] = column_info["name"].removeprefix(prefix)


# Reflect tables of interest.
Base.metadata.reflect(engine, only=['person'])


class Person(Base):
    __table__ = Base.metadata.tables['person']


q = sa.select(Person)
print(q)
Run Code Online (Sandbox Code Playgroud)

输出

SELECT person.per_raw, person.per_firstname, person.per_lastname 
FROM person
Run Code Online (Sandbox Code Playgroud)

前缀可能不容易计算,例如给定单个列名,foo_bar_baz前缀可能是foo_or foo_bar_。在这种情况下,显而易见的解决方案是比较表中的所有列名以查找公共前缀,但列反射侦听器最初无法访问所有列。为了解决这个问题,我们可以等到映射器即将被检测,使用 Instrument_class 监听器而不是列反射监听器。

import os.path
...
@sa.event.listens_for(Base, 'instrument_class', propagate=True)
def receive_instrument_class(mapper, class_):
    table = class_.__table__
    column_names = [c.name for c in table.columns]
    prefix = os.path.commonprefix(column_names)
    for c in table.columns:
        c.key = c.name.removeprefix(prefix)
Run Code Online (Sandbox Code Playgroud)

请注意,此方法不会影响映射类之外的表,而列反射方法会影响表的访问方式。无论使用哪种方法,字符串化外键引用都Column(Integer, ForeignKey('table.pk_col'))必须使用数据库中的实际列名。