- if @classproperty is used with a regular class-bound

mapper property attribute, it will be called to get the
actual attribute value during initialization. Currently,
there's no advantage to using @classproperty on a column
or relationship attribute of a declarative class that
isn't a mixin - evaluation is at the same time as if
@classproperty weren't used. But here we at least allow
it to function as expected.
- docs for column_property() with declarative
- mixin docs in declarative made more clear - mixins
are optional - each subsection starts with, "in *declarative mixins*",
to reduce confusion
This commit is contained in:
Mike Bayer
2010-08-02 15:29:31 -04:00
parent b2c0b50bbf
commit e616c2fb3c
5 changed files with 136 additions and 20 deletions
+10
View File
@@ -66,6 +66,16 @@ CHANGES
this to appease MySQL who has a max length
of 64 for index names, separate from their
overall max length of 255. [ticket:1412]
- declarative
- if @classproperty is used with a regular class-bound
mapper property attribute, it will be called to get the
actual attribute value during initialization. Currently,
there's no advantage to using @classproperty on a column
or relationship attribute of a declarative class that
isn't a mixin - evaluation is at the same time as if
@classproperty weren't used. But here we at least allow
it to function as expected.
- mssql
- Fixed "default schema" query to work with
+10 -3
View File
@@ -204,11 +204,16 @@ And an entire "deferred group", i.e. which uses the ``group`` keyword argument t
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
)
})
@@ -221,10 +226,12 @@ Correlated subqueries may be used as well:
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
----------------------------
+81 -16
View File
@@ -233,6 +233,61 @@ Similarly, :func:`comparable_using` is a front end for the
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
===================
@@ -429,6 +484,11 @@ share some functionality, often a set of columns, across many
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::
@@ -531,12 +591,12 @@ Mixing in Relationships
~~~~~~~~~~~~~~~~~~~~~~~
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
@@ -586,9 +646,9 @@ Mixing in deferred(), column_property(), etc.
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):
@@ -607,7 +667,8 @@ Controlling table inheritance with mixins
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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
@@ -700,12 +761,13 @@ classes::
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
@@ -868,6 +930,9 @@ def _as_declarative(cls, classname, dict_):
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 "
+1 -1
View File
@@ -1813,7 +1813,7 @@ class ColumnElement(ClauseElement, _CompareMixin):
else:
name = str(self)
co = ColumnClause(self.anon_label, selectable, type_=getattr(self, 'type', None))
co.proxies = [self]
selectable.columns[name] = co
return co
+34
View File
@@ -690,6 +690,40 @@ class DeclarativeTest(DeclarativeTestBase):
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):