mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-06-04 23:06:24 -04:00
- 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:
@@ -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
|
||||
|
||||
Vendored
+10
-3
@@ -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
|
||||
----------------------------
|
||||
|
||||
|
||||
@@ -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 "
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user