我有一个数据库,其中每个表都有一个所有列的公共前缀(可能是为了避免别名).像这样:
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_'},它的前缀是我的属性.
这样做的恰当方法是什么?我是否必须捕获一些映射事件?用自定义基类做一些事情?前缀的长度各不相同,但它们都以下划线结尾,因此通用的不需要指定的确切表前缀的东西也可以工作.
如果可以轻松计算列前缀,则可以使用列反射事件侦听器来完成修剪,将每列的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'))必须使用数据库中的实际列名。