版本:1.1.0b2 |发布日期:2016年7月1日

SQLAlchemy 1.1文档

混合、自定义实体公共类

使用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确实定义了一个具有相同名称的属性,则首先放置在继承列表中的类将确定在新定义的类上使用哪个属性。

通过传参来定制化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对象ab可能都有一个名为id的列,但这些区别的方式是a.c.idb.c.id是两个不同的Python对象,分别引用它们的父表ab

对于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. 下面是一个结合外键列和关系的例子,这样两个类FooBar都可以配置为通过多对一引用共同的目标类:

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 ColumnColumn该声明实际上是要映射到我们的表。

上面的条件使用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__
        )

在deferred(),column_property()和其他MapperProperty类中混合

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属性的引用代理到每个StringAttributevalue实例。

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_stringstype_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()

通过mixins控制表继承

__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 / Mapper参数

在声明性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)

用Mixins创建索引

要定义适用于从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)