using a new @declared_attr usage described
in the documentation. [ticket:2472]
+ - [feature] declared_attr can now be used
+ on non-mixin classes, even though this is generally
+ only useful for single-inheritance subclass
+ column conflict resolution. [ticket:2472]
+
+ - [feature] declared_attr can now be used with
+ attributes that are not Column or MapperProperty;
+ including any user-defined value as well
+ as association proxy objects. [ticket:2517]
+
- [feature] *Very limited* support for
inheriting mappers to be GC'ed when the
class itself is deferenced. The mapper
declared on a single-table inheritance subclass
up to the parent class' table, when the parent
class is itself mapped to a join() or select()
- statement, directly or via joined inheritane,
+ statement, directly or via joined inheritance,
and not just a Table. [ticket:2549]
- [bug] An error is emitted when uselist=False
primaryjoin="Target.id==%s.target_id" % cls.__name__
)
-Mixing in deferred(), column_property(), etc.
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Mixing in deferred(), column_property(), and other MapperProperty classes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Like :func:`~sqlalchemy.orm.relationship`, all
:class:`~sqlalchemy.orm.interfaces.MapperProperty` subclasses such as
:func:`~sqlalchemy.orm.deferred`, :func:`~sqlalchemy.orm.column_property`,
etc. ultimately involve references to columns, and therefore, when
-used with declarative mixins, have the :func:`.declared_attr`
+used with declarative mixins, have the :class:`.declared_attr`
requirement so that no reliance on copying is needed::
class SomethingMixin(object):
class Something(SomethingMixin, Base):
__tablename__ = "something"
+Mixing in Association Proxy and Other Attributes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Mixins can specify user-defined attributes as well as other extension
+units such as :func:`.association_proxy`. The usage of :class:`.declared_attr`
+is required in those cases where the attribute must be tailored specifically
+to the target subclass. An example is when constructing multiple
+:func:`.association_proxy` attributes which each target a different type
+of child object. Below is an :func:`.association_proxy` / mixin example
+which provides a scalar list of string values to an implementing class::
+
+ 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)
+
+Above, the ``HasStringCollection`` mixin produces a :func:`.relationship`
+which refers to a newly generated class called ``StringAttribute``. The
+``StringAttribute`` class is generated with it's own :class:`.Table`
+definition which is local to the parent class making usage of the
+``HasStringCollection`` mixin. It also produces an :func:`.association_proxy`
+object which proxies references to the ``strings`` attribute onto the ``value``
+attribute of each ``StringAttribute`` instance.
+
+``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'])
+
+This list will generate a collection
+of ``StringAttribute`` objects, which are persisted into a table that's
+local to either the ``type_a_strings`` or ``type_b_strings`` table::
+
+ >>> print ta._strings
+ [<__main__.StringAttribute object at 0x10151cd90>,
+ <__main__.StringAttribute object at 0x10151ce10>]
+
+When constructing the :func:`.association_proxy`, the
+:class:`.declared_attr` decorator must be used so that a distinct
+:func:`.association_proxy` object is created for each of the ``TypeA``
+and ``TypeB`` classes.
+
+.. versionadded:: 0.8 :class:`.declared_attr` is usable with non-mapped
+ attributes, including user-defined attributes as well as
+ :func:`.association_proxy`.
+
Controlling table inheritance with mixins
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"""Mark a class-level method as representing the definition of
a mapped property or special declarative member name.
- .. versionchanged:: 0.6.{2,3,4}
- ``@declared_attr`` is available as
- ``sqlalchemy.util.classproperty`` for SQLAlchemy versions
- 0.6.2, 0.6.3, 0.6.4.
-
@declared_attr turns the attribute into a scalar-like
property that can be invoked from the uninstantiated class.
Declarative treats attributes specifically marked with
else:
return {"polymorphic_identity":cls.__name__}
+ .. versionchanged:: 0.8 :class:`.declared_attr` can be used with
+ non-ORM or extension attributes, such as user-defined attributes
+ or :func:`.association_proxy` objects, which will be assigned
+ to the class at class construction time.
+
+
"""
def __init__(self, fget, *arg, **kw):
assert C().x() == 'hi'
+ def test_arbitrary_attrs_one(self):
+ class HasMixin(object):
+ @declared_attr
+ def some_attr(cls):
+ return cls.__name__ + "SOME ATTR"
+
+ class Mapped(HasMixin, Base):
+ __tablename__ = 't'
+ id = Column(Integer, primary_key=True)
+
+ eq_(Mapped.some_attr, "MappedSOME ATTR")
+ eq_(Mapped.__dict__['some_attr'], "MappedSOME ATTR")
+
+ def test_arbitrary_attrs_two(self):
+ from sqlalchemy.ext.associationproxy import association_proxy
+
+ class FilterA(Base):
+ __tablename__ = 'filter_a'
+ id = Column(Integer(), primary_key=True)
+ parent_id = Column(Integer(),
+ ForeignKey('type_a.id'))
+ filter = Column(String())
+ def __init__(self, filter_, **kw):
+ self.filter = filter_
+
+ class FilterB(Base):
+ __tablename__ = 'filter_b'
+ id = Column(Integer(), primary_key=True)
+ parent_id = Column(Integer(),
+ ForeignKey('type_b.id'))
+ filter = Column(String())
+ def __init__(self, filter_, **kw):
+ self.filter = filter_
+
+ class FilterMixin(object):
+ @declared_attr
+ def _filters(cls):
+ return relationship(cls.filter_class,
+ cascade='all,delete,delete-orphan')
+
+ @declared_attr
+ def filters(cls):
+ return association_proxy('_filters', 'filter')
+
+ class TypeA(Base, FilterMixin):
+ __tablename__ = 'type_a'
+ filter_class = FilterA
+ id = Column(Integer(), primary_key=True)
+
+ class TypeB(Base, FilterMixin):
+ __tablename__ = 'type_b'
+ filter_class = FilterB
+ id = Column(Integer(), primary_key=True)
+
+ TypeA(filters=[u'foo'])
+ TypeB(filters=[u'foo'])
class DeclarativeMixinPropertyTest(DeclarativeTestBase):