使用 mixin 组合映射层次
当使用 Declarative 样式是在许多类之间共享一些功能,例如一组公共列、一些公共表选项或其他映射属性。标准的Python习惯用法是让类继承自一个包含这些常见特性的超类。
当使用声明性映射时,通过使用mixin类,以及通过增加 registry.generate_base()
方法或 declarative_base()
功能。
下面是一些常见的混合成语的例子:
from sqlalchemy.orm import declarative_mixin from sqlalchemy.orm import declared_attr @declarative_mixin class MyMixin: @declared_attr def __tablename__(cls): return cls.__name__.lower() __table_args__ = {'mysql_engine': 'InnoDB'} __mapper_args__= {'always_refresh': True} id = Column(Integer, primary_key=True) class MyModel(MyMixin, Base): name = Column(String(1000))
在上面的什么地方,班级 MyModel
将包含一个“id”列作为主键,a __tablename__
从类本身的名称派生的属性,以及 __table_args__
和 __mapper_args__
定义为 MyMixin
混入类。
小技巧
使用 declarative_mixin()
类修饰器将特定类标记为提供将SQLAlChemy声明性赋值作为其他类的混合提供的服务。此修饰符当前只需要向 Mypy plugin 这个类应该作为声明性映射的一部分来处理。
关于是否 MyMixin
先于 Base
或者没有。标准的python方法解析规则适用,上面的示例也适用于::
class MyModel(Base, MyMixin): name = Column(String(1000))
这是因为 Base
这里没有定义任何变量 MyMixin
定义,即 __tablename__
, __table_args__
, id
等,如果 Base
如果定义了相同名称的属性,则放在继承列表中第一个的类将决定在新定义的类上使用哪个属性。
扩大基地
除了使用纯混合之外,本节中的大多数技术还可以应用于基类本身,用于应用于从特定基派生的所有类的模式。这是通过使用 cls
论证 declarative_base()
功能:
from sqlalchemy.orm import declared_attr class Base: @declared_attr def __tablename__(cls): return cls.__name__.lower() __table_args__ = {'mysql_engine': 'InnoDB'} id = Column(Integer, primary_key=True) from sqlalchemy.orm import declarative_base Base = declarative_base(cls=Base) class MyModel(Base): name = Column(String(1000))
在上面, MyModel
以及所有其他类 Base
将有一个从类名派生的表名, id
主键列,以及用于MySQL的“innodb”引擎。
柱状混合
在mixin上指定列的最基本方法是通过简单声明:
@declarative_mixin class TimestampMixin: created_at = Column(DateTime, default=func.now()) class MyModel(TimestampMixin, Base): __tablename__ = 'test' id = Column(Integer, primary_key=True) name = Column(String(1000))
在上面的位置,包括 TimestampMixin
也将有一列 created_at
它将时间戳应用于所有行插入。
熟悉SQLAlchemy表达式语言的人知道子句元素的对象标识定义了它们在模式中的角色。二 Table
物体 a
和 b
可能都有一个列名为 id
但是这些区别的方式是 a.c.id
和 b.c.id
是两个不同的python对象,引用它们的父表 a
和 b
分别。
对于mixin列,似乎只有一个 Column
对象是显式创建的,但最终 created_at
对于每个单独的目标类,上面的列必须作为一个不同的python对象存在。为了实现这一点,声明性扩展创建了 copy 每一个 Column
在被检测为mixin的类上遇到的对象。
此复制机制仅限于没有外键的简单列,作为 ForeignKey
它本身包含对在此级别上无法正确重新创建的列的引用。对于具有外键的列以及需要目标显式上下文的各种映射器级别构造,可以使用 declared_attr
提供了decorator,以便可以将许多类通用的模式定义为可调用的:
from sqlalchemy.orm import declared_attr @declarative_mixin class ReferenceAddressMixin: @declared_attr def address_id(cls): return Column(Integer, ForeignKey('address.id')) class User(ReferenceAddressMixin, Base): __tablename__ = 'user' id = Column(Integer, primary_key=True)
在上面的地方, address_id
类级可调用在 User
类,声明性扩展可以使用 Column
方法返回的对象,不需要复制它。
列生成者 declared_attr
也可以被引用 __mapper_args__
在一定程度上,目前由 polymorphic_on
和 version_id_col
;声明性扩展将在类构造时解析它们:
@declarative_mixin class MyMixin: @declared_attr def type_(cls): return Column(String(50)) __mapper_args__= {'polymorphic_on':type_} class MyModel(MyMixin, Base): __tablename__='test' id = Column(Integer, primary_key=True)
混入关系
关系创建者 relationship()
提供声明性mixin类,这些类专用于 declared_attr
方法,消除复制关系及其可能的列绑定内容时可能出现的任何模糊性。下面是一个例子,它结合了一个外键列和一个关系,从而使两个类 Foo
和 Bar
两者都可以配置为通过多对一引用公共目标类::
@declarative_mixin class RefTargetMixin: @declared_attr def target_id(cls): return Column('target_id', ForeignKey('target.id')) @declared_attr def target(cls): return relationship("Target") class Foo(RefTargetMixin, Base): __tablename__ = 'foo' id = Column(Integer, primary_key=True) class Bar(RefTargetMixin, Base): __tablename__ = 'bar' id = Column(Integer, primary_key=True) class Target(Base): __tablename__ = 'target' id = Column(Integer, primary_key=True)
使用高级关系参数(例如 primaryjoin
等)
relationship()
需要显式primaryjoin、order_by等的定义。除了最简单的情况外,所有表达式都应使用 晚绑定 这些参数的形式,即使用字符串形式或函数/lambda。原因是 Column
要使用配置的对象 @declared_attr
其他人不可用 @declared_attr
属性;而方法将工作并返回新的 Column
对象,它们不是 Column
声明性将在调用方法时使用的对象,因此使用 不同的 Column
物体。
规范示例是依赖于另一个混合在列中的PrimaryJoin条件:
@declarative_mixin class RefTargetMixin: @declared_attr def target_id(cls): return Column('target_id', ForeignKey('target.id')) @declared_attr def target(cls): return relationship(Target, primaryjoin=Target.id==cls.target_id # this is *incorrect* )
使用上面的mixin映射类,我们将得到如下错误:
sqlalchemy.exc.InvalidRequestError: this ForeignKey's parent column is not yet associated with a Table.
这是因为 target_id
Column
我们已经要求 target()
方法不同 Column
这个声明性实际上将映射到我们的表。
使用lambda解决上述问题:
@declarative_mixin class RefTargetMixin: @declared_attr def target_id(cls): return Column('target_id', ForeignKey('target.id')) @declared_attr def target(cls): return relationship(Target, primaryjoin=lambda: Target.id==cls.target_id )
或者,字符串形式(最终生成lambda)::
@declarative_mixin class RefTargetMixin: @declared_attr def target_id(cls): return Column('target_id', ForeignKey('target.id')) @declared_attr def target(cls): return relationship("Target", primaryjoin="Target.id==%s.target_id" % cls.__name__ )
参见
在deferred()、column_property()和其他mapperproperty类中混合
喜欢 relationship()
,所有 MapperProperty
子类,如 deferred()
, column_property()
等等。最终涉及到对列的引用,因此,当与声明性混合使用时,具有 declared_attr
要求不依赖复制:
@declarative_mixin class SomethingMixin: @declared_attr def dprop(cls): return deferred(Column(Integer)) class Something(SomethingMixin, Base): __tablename__ = "something"
这个 column_property()
或者其他构造可以引用mixin中的其他列。这些都是提前复制的 declared_attr
调用:
@declarative_mixin class SomethingMixin: x = Column(Integer) y = Column(Integer) @declared_attr def x_plus_y(cls): return column_property(cls.x + cls.y)
在 1.0.0 版更改: 将mixin列复制到最终映射的类中,以便 declared_attr
方法可以访问将要映射的实际列。
混入关联代理和其他属性
mixin可以指定用户定义的属性以及其他扩展单元,例如 association_proxy()
. 用法 declared_attr
在那些必须针对目标子类专门定制属性的情况下是必需的。例如,当构造多个 association_proxy()
属性,每个属性都针对不同类型的子对象。下面是一个 association_proxy()
向实现类提供字符串值的标量列表的mixin示例:
from sqlalchemy import Column, Integer, ForeignKey, String from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.orm import declarative_base from sqlalchemy.orm import declarative_mixin from sqlalchemy.orm import declared_attr from sqlalchemy.orm import relationship Base = declarative_base() @declarative_mixin class HasStringCollection: @declared_attr def _strings(cls): class StringAttribute(Base): __tablename__ = cls.string_table_name id = Column(Integer, primary_key=True) value = Column(String(50), nullable=False) parent_id = Column(Integer, ForeignKey('%s.id' % cls.__tablename__), nullable=False) def __init__(self, value): self.value = value return relationship(StringAttribute) @declared_attr def strings(cls): return association_proxy('_strings', 'value') class TypeA(HasStringCollection, Base): __tablename__ = 'type_a' string_table_name = 'type_a_strings' id = Column(Integer(), primary_key=True) class TypeB(HasStringCollection, Base): __tablename__ = 'type_b' string_table_name = 'type_b_strings' id = Column(Integer(), primary_key=True)
上面, HasStringCollection
mixin生成 relationship()
它引用了一个新生成的类 StringAttribute
. 这个 StringAttribute
类是用它自己的 Table
定义,它是使用 HasStringCollection
混合蛋白。它还产生一个 association_proxy()
对象的代理引用 strings
属性到 value
每个的属性 StringAttribute
实例。
TypeA
或 TypeB
无法在给定构造函数参数的情况下实例化 strings
,字符串列表:
ta = TypeA(strings=['foo', 'bar']) tb = TypeB(strings=['bat', 'bar'])
此列表将生成 StringAttribute
对象,这些对象被持久化到一个表中,该表是 type_a_strings
或 type_b_strings
表:
>>> print(ta._strings) [<__main__.StringAttribute object at 0x10151cd90>, <__main__.StringAttribute object at 0x10151ce10>]
在构造 association_proxy()
, the declared_attr
必须使用decorator,以便 association_proxy()
对象是为每个 TypeA
和 TypeB
类。
用mixin控制表继承
这个 __tablename__
属性可用于提供一个函数,该函数将确定继承层次结构中每个类所用表的名称,以及一个类是否具有自己的不同表。
这是通过使用 declared_attr
与名为 __tablename__()
. 声明性将始终调用 declared_attr
对于特殊的名字 __tablename__
, __mapper_args__
和 __table_args__
功能 对于层次结构中的每个映射类,除非在子类中重写 . 因此,函数需要单独接收每个类,并为每个类提供正确的答案。
例如,要创建一个mixin,该mixin基于类名为每个类提供一个简单的表名:
from sqlalchemy.orm import declarative_mixin from sqlalchemy.orm import declared_attr @declarative_mixin class Tablename: @declared_attr def __tablename__(cls): return cls.__name__.lower() class Person(Tablename, Base): id = Column(Integer, primary_key=True) discriminator = Column('type', String(50)) __mapper_args__ = {'polymorphic_on': discriminator} class Engineer(Person): __tablename__ = None __mapper_args__ = {'polymorphic_identity': 'engineer'} primary_language = Column(String(50))
或者,我们可以修改 __tablename__
要返回的函数 None
对于子类,使用 has_inherited_table()
. 这会影响到那些子类被映射为针对父类的单表继承:
from sqlalchemy.orm import declarative_mixin from sqlalchemy.orm import declared_attr from sqlalchemy.orm import has_inherited_table @declarative_mixin class Tablename: @declared_attr def __tablename__(cls): if has_inherited_table(cls): return None return cls.__name__.lower() class Person(Tablename, Base): id = Column(Integer, primary_key=True) discriminator = Column('type', String(50)) __mapper_args__ = {'polymorphic_on': discriminator} class Engineer(Person): primary_language = Column(String(50)) __mapper_args__ = {'polymorphic_identity': 'engineer'}
在继承场景中混入列
与如何 __tablename__
和其他特殊名称一起使用时处理 declared_attr
,当我们在列和属性(例如关系、列属性等)中混合时,将为 仅基类 在层次结构中。下面,只有 Person
类将接收名为 id
;映射将失败 Engineer
,未给定主键::
@declarative_mixin class HasId: @declared_attr def id(cls): return Column('id', Integer, primary_key=True) class Person(HasId, Base): __tablename__ = 'person' discriminator = Column('type', String(50)) __mapper_args__ = {'polymorphic_on': discriminator} class Engineer(Person): __tablename__ = 'engineer' primary_language = Column(String(50)) __mapper_args__ = {'polymorphic_identity': 'engineer'}
在联合表继承中,我们通常希望每个子类上都有不同名称的列。但是在这种情况下,我们可能希望 id
每个表上的列,并让它们通过外键相互引用。我们可以通过使用 declared_attr.cascading
修饰符,指示应调用函数 对于层次结构中的每个类 在 几乎 (见下面的警告)与 __tablename__
::
@declarative_mixin class HasIdMixin: @declared_attr.cascading def id(cls): if has_inherited_table(cls): return Column(ForeignKey('person.id'), primary_key=True) else: return Column(Integer, primary_key=True) class Person(HasIdMixin, Base): __tablename__ = 'person' discriminator = Column('type', String(50)) __mapper_args__ = {'polymorphic_on': discriminator} class Engineer(Person): __tablename__ = 'engineer' primary_language = Column(String(50)) __mapper_args__ = {'polymorphic_identity': 'engineer'}
警告
这个 declared_attr.cascading
功能当前有 not 允许子类用不同的函数或值重写属性。这是目前在如何 @declared_attr
已解决,如果检测到此情况,将发出警告。这个限制是 not 存在特殊属性名称,例如 __tablename__
,其内部解决方式与 declared_attr.cascading
.
1.0.0 新版功能: 补充 declared_attr.cascading
.
从多个mixin组合表/映射器参数
如果是 __table_args__
或 __mapper_args__
通过声明性混合指定,您可能希望将来自多个混合的一些参数与希望在类本身上定义的参数组合在一起。这个 declared_attr
这里可以使用decorator创建从多个集合中提取的用户定义的排序规则例程::
from sqlalchemy.orm import declarative_mixin from sqlalchemy.orm import declared_attr @declarative_mixin class MySQLSettings: __table_args__ = {'mysql_engine':'InnoDB'} @declarative_mixin class MyOtherMixin: __table_args__ = {'info':'foo'} class MyModel(MySQLSettings, MyOtherMixin, Base): __tablename__='my_model' @declared_attr def __table_args__(cls): args = dict() args.update(MySQLSettings.__table_args__) args.update(MyOtherMixin.__table_args__) return args id = Column(Integer, primary_key=True)
使用mixin创建索引
定义一个命名的、可能是多列的 Index
它适用于从mixin派生的所有表,使用“inline”形式 Index
并将其作为 __table_args__
::
@declarative_mixin class MyMixin: a = Column(Integer) b = Column(Integer) @declared_attr def __table_args__(cls): return (Index('test_idx_%s' % cls.__tablename__, 'a', 'b'),) class MyModel(MyMixin, Base): __tablename__ = 'atable' c = Column(Integer,primary_key=True)