SQL Expressions as Mapped Attributes
-------------------------------------
-To add a SQL clause composed of local or external columns as a read-only, mapped column attribute, use the :func:`~sqlalchemy.orm.column_property()` function. Any scalar-returning :class:`~sqlalchemy.sql.expression.ClauseElement` may be used, as long as it has a ``name`` attribute; usually, you'll want to call ``label()`` to give it a specific name::
+To add a SQL clause composed of local or external columns as
+a read-only, mapped column attribute, use the
+:func:`~sqlalchemy.orm.column_property()` function. Any
+scalar-returning
+:class:`~sqlalchemy.sql.expression.ClauseElement` may be
+used. Unlike older versions of SQLAlchemy, there is no :func:`~.sql.expression.label` requirement::
mapper(User, users_table, properties={
'fullname': column_property(
- (users_table.c.firstname + " " + users_table.c.lastname).label('fullname')
+ users_table.c.firstname + " " + users_table.c.lastname
)
})
select(
[func.count(addresses_table.c.address_id)],
addresses_table.c.user_id==users_table.c.user_id
- ).label('address_count')
+ )
)
})
+The declarative form of the above is described in :ref:`declarative_sql_expressions`.
+
Changing Attribute Behavior
----------------------------
def uc_name(self):
return self.name.upper()
+.. _declarative_sql_expressions:
+
+Defining SQL Expressions
+========================
+
+The usage of :func:`.column_property` with Declarative is
+pretty much the same as that described in
+:ref:`mapper_sql_expressions`. Local columns within the same
+class declaration can be referenced directly::
+
+ class User(Base):
+ __tablename__ = 'user'
+ id = Column(Integer, primary_key=True)
+ firstname = Column(String)
+ lastname = Column(String)
+ fullname = column_property(
+ firstname + " " + lastname
+ )
+
+Correlated subqueries reference the :class:`Column` objects they
+need either from the local class definition or from remote
+classes::
+
+ from sqlalchemy.sql import func
+
+ class Address(Base):
+ __tablename__ = 'address'
+
+ id = Column('id', Integer, primary_key=True)
+ user_id = Column(Integer, ForeignKey('user.id'))
+
+ class User(Base):
+ __tablename__ = 'user'
+
+ id = Column(Integer, primary_key=True)
+ name = Column(String)
+
+ address_count = column_property(
+ select([func.count(Address.id)]).\\
+ where(Address.user_id==id)
+ )
+
+In the case that the ``address_count`` attribute above doesn't have access to
+``Address`` when ``User`` is defined, the ``address_count`` attribute should
+be added to ``User`` when both ``User`` and ``Address`` are available (i.e.
+there is no string based "late compilation" feature like there is with
+:func:`.relationship` at this time). Note we reference the ``id`` column
+attribute of ``User`` with its class when we are no longer in the declaration
+of the ``User`` class::
+
+ User.address_count = column_property(
+ select([func.count(Address.id)]).\\
+ where(Address.user_id==User.id)
+ )
+
Table Configuration
===================
classes. The normal Python idiom would be to put this common code into
a base class and have all the other classes subclass this class.
+.. note:: Mixins are an entirely optional feature when using declarative,
+ and are not required for any configuration. Users who don't need
+ to define sets of attributes common among many classes can
+ skip this section.
+
When using :mod:`~sqlalchemy.ext.declarative`, this need is met by
using a "mixin class". A mixin class is one that isn't mapped to a
table and doesn't subclass the declarative :class:`Base`. For example::
~~~~~~~~~~~~~~~~~~~~~~~
Relationships created by :func:`~sqlalchemy.orm.relationship` are provided
-exclusively using the :func:`~sqlalchemy.util.classproperty` approach,
-eliminating any ambiguity which could arise when copying a relationship
-and its possibly column-bound contents. Below is an example which
-combines a foreign key column and a relationship so that two classes
-``Foo`` and ``Bar`` can both be configured to reference a common
-target class via many-to-one::
+with declarative mixin classes exclusively using the
+:func:`~sqlalchemy.util.classproperty` approach, eliminating any ambiguity
+which could arise when copying a relationship and its possibly column-bound
+contents. Below is an example which combines a foreign key column and a
+relationship so that two classes ``Foo`` and ``Bar`` can both be configured to
+reference a common target class via many-to-one::
class RefTargetMixin(object):
@classproperty
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 have the
-:func:`~sqlalchemy.util.classproperty` requirement so that no reliance on
-copying is needed::
+etc. ultimately involve references to columns, and therefore, when
+used with declarative mixins, have the :func:`~sqlalchemy.util.classproperty`
+requirement so that no reliance on copying is needed::
class SomethingMixin(object):
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``__tablename__`` attribute in conjunction with the hierarchy of
-the classes involved controls what type of table inheritance, if any,
+classes involved in a declarative mixin scenario controls what type of
+table inheritance, if any,
is configured by the declarative extension.
If the ``__tablename__`` is computed by a mixin, you may need to
Combining Table/Mapper Arguments from Multiple Mixins
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-In the case of ``__table_args__`` or ``__mapper_args__``, you may want
-to combine some parameters from several mixins with those you wish to
-define on the class iteself. The
-:func:`~sqlalchemy.util.classproperty` decorator can be used here
-to create user-defined collation routines that pull from multiple
-collections::
+In the case of ``__table_args__`` or ``__mapper_args__``
+specified with declarative mixins, you may want to combine
+some parameters from several mixins with those you wish to
+define on the class iteself. The
+:func:`~sqlalchemy.util.classproperty` decorator can be used
+here to create user-defined collation routines that pull
+from multiple collections::
from sqlalchemy.util import classproperty
our_stuff = util.OrderedDict()
for k in dict_:
value = dict_[k]
+ if isinstance(value, util.classproperty):
+ value = getattr(cls, k)
+
if (isinstance(value, tuple) and len(value) == 1 and
isinstance(value[0], (Column, MapperProperty))):
util.warn("Ignoring declarative-like tuple value of attribute "
eq_(sess.query(User).all(), [User(name='u1', address_count=2,
addresses=[Address(email='one'), Address(email='two')])])
+ def test_useless_classproperty(self):
+ class Address(Base, ComparableEntity):
+
+ __tablename__ = 'addresses'
+ id = Column('id', Integer, primary_key=True,
+ test_needs_autoincrement=True)
+ email = Column('email', String(50))
+ user_id = Column('user_id', Integer, ForeignKey('users.id'))
+
+ class User(Base, ComparableEntity):
+
+ __tablename__ = 'users'
+ id = Column('id', Integer, primary_key=True,
+ test_needs_autoincrement=True)
+ name = Column('name', String(50))
+ addresses = relationship('Address', backref='user')
+
+ @classproperty
+ def address_count(cls):
+ # this doesn't really gain us anything. but if
+ # one is used, lets have it function as expected...
+ return sa.orm.column_property(sa.select([sa.func.count(Address.id)]).
+ where(Address.user_id == cls.id))
+
+ Base.metadata.create_all()
+ u1 = User(name='u1', addresses=[Address(email='one'),
+ Address(email='two')])
+ sess = create_session()
+ sess.add(u1)
+ sess.flush()
+ sess.expunge_all()
+ eq_(sess.query(User).all(), [User(name='u1', address_count=2,
+ addresses=[Address(email='one'), Address(email='two')])])
+
def test_column(self):
class User(Base, ComparableEntity):