使用declarative
时的一个常见需求是跨多个类共享某些功能,例如一组公共列,一些常用表选项或其他映射属性。标准的Python成语就是让这些类从包含这些常用特征的基础继承而来。
使用declarative
时,通过使用自定义声明式基类以及除主基础之外还继承的“mixin”类,可以使用该惯用法。声明包括几个帮助器功能,以便如何声明映射。下面是一些常用的混合成语的例子:
from sqlalchemy.ext.declarative import declared_attr
class MyMixin(object):
@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))
Where above, the class MyModel
will contain an “id” column as the primary key, a __tablename__
attribute that derives from the name of the class itself, as well as __table_args__
and __mapper_args__
defined by the MyMixin
mixin class.
There’s no fixed convention over whether MyMixin
precedes Base
or not. 正常的Python方法解决规则适用,上面的例子也适用于:
class MyModel(Base, MyMixin):
name = Column(String(1000))
这是有效的,因为Base
在这里没有定义MyMixin
定义的任何变量,即__tablename__
,__table_args__
id
等如果Base
确实定义了一个具有相同名称的属性,则首先放置在继承列表中的类将确定在新定义的类上使用哪个属性。
除了使用MixIn类这种方法外,上文提到的技术也是完全可以使用到Base基类本身的,实现方法就是通过给declarative_base()
参数传一个 cls
参数
from sqlalchemy.ext.declarative import declared_attr
class Base(object):
@declared_attr
def __tablename__(cls):
return cls.__name__.lower()
__table_args__ = {'mysql_engine': 'InnoDB'}
id = Column(Integer, primary_key=True)
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base(cls=Base)
class MyModel(Base):
name = Column(String(1000))
如上,MyModel
以及其他继承自Base
的实体类就会拥有从类名(id
主键列)派生的表名,“巴拉巴拉”的
在mixin上指定列的最基本方法是通过简单的声明:
class TimestampMixin(object):
created_at = Column(DateTime, default=func.now())
class MyModel(TimestampMixin, Base):
__tablename__ = 'test'
id = Column(Integer, primary_key=True)
name = Column(String(1000))
Where above, all declarative classes that include TimestampMixin
will also have a column created_at
that applies a timestamp to all row insertions.
熟悉SQLAlchemy表达式语言的人知道id(object identity)唯一标识了一个对象实例在一张表(schema)中的身份两个Table
对象a
和b
可能都有一个名为id
的列,但这些区别的方式是a.c.id
和b.c.id
是两个不同的Python对象,分别引用它们的父表a
和b
。
对于mixin列,似乎只有一个Column
对象被显式创建,但上面的最终created_at
列必须作为每个独立目标的独立Python对象存在类。为了达到这个目的,声明性扩展创建了一个被检测为mixin的类上遇到的每个Column
对象的copy。
此复制机制仅限于没有外键的简单列,因为ForeignKey
本身包含对在此级别无法正确重新创建的列的引用。对于具有外键的列以及需要目标显式上下文的各种映射级构造,提供了declared_attr
修饰符,以便可以将许多类通用的模式定义为可调用对象:
from sqlalchemy.ext.declarative import declared_attr
class ReferenceAddressMixin(object):
@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
在版本0.6.5中更改:将sqlalchemy.util.classproperty
重命名为declared_attr
。
Columns generated by declared_attr
can also be referenced by __mapper_args__
to a limited degree, currently by polymorphic_on
and version_id_col
; the declarative extension will resolve them at class construction time:
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)
Relationships created by relationship()
are provided with declarative mixin classes exclusively using the declared_attr
approach, eliminating any ambiguity which could arise when copying a relationship and its possibly column-bound contents. 下面是一个结合外键列和关系的例子,这样两个类Foo
和Bar
都可以配置为通过多对一引用共同的目标类:
class RefTargetMixin(object):
@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()
定义需要显式的主要连接,order_by等除了最简单的情况外,所有表达式都应该使用后缀形式表示这些参数,即使用字符串形式或lambda表达式。这是因为要使用@declared_attr
配置的相关Column
对象不可用于其他@declared_attr
属性;虽然这些方法将工作并返回新的Column
对象,但这些对象并不是Declarative将在使用它自己调用方法时使用的Column
对象,因此使用不同的 Column
对象。
规范的例子是依赖于另一个混合列的主连接条件:
class RefTargetMixin(object):
@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()
方法中调用的target_id
Column
与Column
该声明实际上是要映射到我们的表。
上面的条件使用lambda来解决:
class RefTargetMixin(object):
@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
)
或者可选地,字符串形式(其最终生成拉姆达):
class RefTargetMixin(object):
@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__
)
Like relationship()
, all MapperProperty
subclasses such as deferred()
, column_property()
, etc. 最终涉及对列的引用,因此在与声明性mixin一起使用时,必须具有declared_attr
要求,以便不需要依赖复制:
class SomethingMixin(object):
@declared_attr
def dprop(cls):
return deferred(Column(Integer))
class Something(SomethingMixin, Base):
__tablename__ = "something"
column_property()
或其他构造可以引用来自mixin的其他列。在declared_attr
被调用之前,它们被提前复制:
class SomethingMixin(object):
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
方法可以访问将要映射的实际列。
Mixins可以指定用户定义的属性以及其他扩展单元,如association_proxy()
。在属性必须专门针对目标子类定制的情况下,declared_attr
的用法是必需的。一个示例是构建多个association_proxy()
属性,每个属性都针对不同类型的子对象。下面是一个association_proxy()
/ mixin示例,它为实现类提供了字符串值的标量列表:
from sqlalchemy import Column, Integer, ForeignKey, String
from sqlalchemy.orm import relationship
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import declarative_base, declared_attr
Base = declarative_base()
class HasStringCollection(object):
@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
mixin的父类是本地的。它还生成一个association_proxy()
对象,该对象将对strings
属性的引用代理到每个StringAttribute
的value
实例。
TypeA
or TypeB
can be instantiated given the constructor argument strings
, a list of strings:
ta = TypeA(strings=['foo', 'bar'])
tb = TypeA(strings=['bat', 'bar'])
该列表将生成StringAttribute
对象的集合,该对象保存到type_a_strings
或type_b_strings
表的本地表中:
>>> print(ta._strings)
[<__main__.StringAttribute object at 0x10151cd90>,
<__main__.StringAttribute object at 0x10151ce10>]
When constructing the association_proxy()
, the declared_attr
decorator must be used so that a distinct association_proxy()
object is created for each of the TypeA
and TypeB
classes.
版本0.8中的新功能 declared_attr
可用于非映射属性,包括用户定义的属性以及association_proxy()
。
__tablename__
属性可用于提供一个函数,该函数将确定继承层次结构中每个类所用表的名称,以及某个类是否具有其自己的不同表。
这是通过使用declared_attr
指标和名为__tablename__()
的方法实现的。对于每个映射的类,声明式将始终为特殊名称__tablename__
,__mapper_args__
和__table_args__
函数调用declared_attr
在层次结构中。因此,该功能需要单独接收每个班级,并为每个班级提供正确的答案。
例如,要创建一个mixin,为每个类提供一个基于类名的简单表名:
from sqlalchemy.ext.declarative import declared_attr
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))
或者,我们可以使用has_inherited_table()
修改我们的__tablename__
函数为子类返回None
。这具有将这些子类映射为父表单继承的效果:
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.ext.declarative import has_inherited_table
class Tablename(object):
@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'}
与declared_attr
结合使用时,如何处理__tablename__
和其他特殊名称,当我们混合使用列和属性时(例如关系,列属性等)),该函数仅针对层次结构中的基类调用。下面,只有Person
类会收到一个名为id
的列。 Engineer
中的映射将失败,该工程没有给出主键:
class HasId(object):
@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
列,并让它们通过外键相互引用。We can achieve this as a mixin by using the declared_attr.cascading
modifier, which indicates that the function should be invoked for each class in the hierarchy, just like it does for __tablename__
:
class HasId(object):
@declared_attr.cascading
def id(cls):
if has_inherited_table(cls):
return Column('id',
Integer,
ForeignKey('person.id'), primary_key=True)
else:
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'}
版本1.0.0新增:新增declared_attr.cascading
。
在声明性mixin指定的__table_args__
或__mapper_args__
的情况下,您可能希望将几个mixin的一些参数与您希望在类iteself上定义的参数结合起来。这里可以使用declared_attr
装饰器来创建从多个集合中抽取的用户定义的整理例程:
from sqlalchemy.ext.declarative import declared_attr
class MySQLSettings(object):
__table_args__ = {'mysql_engine':'InnoDB'}
class MyOtherMixin(object):
__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
,请使用Index
的“inline”形式,并将它建立为__table_args__
class MyMixin(object):
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)