Skip to content

Commit

Permalink
Include column_property composition examples
Browse files Browse the repository at this point in the history
Add cross-linking between column_property() and ColumnProperty

Add section to describe using .expression

remove inherited-members from ColumnProperty to greatly
decrease verbosity

Fixes: #5179
Change-Id: Ic477b16350dbf551100b31d14ff3ba8ba8221a43
  • Loading branch information
zzzeek committed Mar 3, 2020
1 parent b5050be commit 4c81d99
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 13 deletions.
1 change: 0 additions & 1 deletion doc/build/orm/internals.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ sections, are listed here.

.. autoclass:: sqlalchemy.orm.properties.ColumnProperty
:members:
:inherited-members:

.. autoclass:: sqlalchemy.orm.properties.ComparableProperty
:members:
Expand Down
63 changes: 52 additions & 11 deletions doc/build/orm/mapped_sql_expr.rst
Original file line number Diff line number Diff line change
Expand Up @@ -158,20 +158,61 @@ to add an additional property after the fact::
where(Address.user_id==User.id)
)

For many-to-many relationships, use :func:`.and_` to join the fields of the
association table to both tables in a relation, illustrated
here with a classical mapping::
For a :func:`.column_property` that refers to columns linked from a
many-to-many relationship, use :func:`.and_` to join the fields of the
association table to both tables in a relationship::

from sqlalchemy import and_

mapper(Author, authors, properties={
'book_count': column_property(
select([func.count(books.c.id)],
and_(
book_authors.c.author_id==authors.c.id,
book_authors.c.book_id==books.c.id
)))
})
class Author(Base):
# ...

book_count = column_property(
select(
[func.count(books.c.id)]
).where(
and_(
book_authors.c.author_id==authors.c.id,
book_authors.c.book_id==books.c.id
)
)
)

.. _mapper_column_property_sql_expressions_composed:

Composing from Column Properties at Mapping Time
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

It is possible to create mappings that combine multiple
:class:`.ColumnProperty` objects together. The :class:`.ColumnProperty` will
be interpreted as a SQL expression when used in a Core expression context,
provided that it is targeted by an existing expression object; this works by
the Core detecting that the object has a ``__clause_element__()`` method which
returns a SQL expression. However, if the :class:`.ColumnProperty` is used as
a lead object in an expression where there is no other Core SQL expression
object to target it, the :attr:`.ColumnProperty.expression` attribute will
return the underlying SQL expression so that it can be used to build SQL
expressions consistently. Below, the ``File`` class contains an attribute
``File.path`` that concatenates a string token to the ``File.filename``
attribute, which is itself a :class:`.ColumnProperty`::


class File(Base):
__tablename__ = 'file'

id = Column(Integer, primary_key=True)
name = Column(String(64))
extension = Column(String(8))
filename = column_property(name + '.' + extension)
path = column_property('C:/' + filename.expression)

When the ``File`` class is used in expressions normally, the attributes
assigned to ``filename`` and ``path`` are usable directly. The use of the
:attr:`.ColumnProperty.expression` attribute is only necessary when using
the :class:`.ColumnProperty` directly within the mapping definition::

q = session.query(File.path).filter(File.filename == 'foo.txt')


Using a plain descriptor
------------------------
Expand Down
1 change: 1 addition & 0 deletions doc/build/orm/mapping_columns.rst
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ This approach is uncommon in modern usage. For dealing with reflected
tables, a more flexible approach is to use that described in
:ref:`mapper_automated_reflection_schemes`.

.. _column_property_options:

Using column_property for column level options
----------------------------------------------
Expand Down
28 changes: 27 additions & 1 deletion lib/sqlalchemy/orm/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,17 @@ class ColumnProperty(StrategizedProperty):
)

def __init__(self, *columns, **kwargs):
r"""Provide a column-level property for use with a Mapper.
r"""Provide a column-level property for use with a mapping.
Column-based properties can normally be applied to the mapper's
``properties`` dictionary using the :class:`.Column` element directly.
Use this function when the given column is not directly present within
the mapper's selectable; examples include SQL expressions, functions,
and scalar SELECT queries.
The :func:`.orm.column_property` function returns an instance of
:class:`.ColumnProperty`.
Columns that aren't present in the mapper's selectable won't be
persisted by the mapper and are effectively "read-only" attributes.
Expand Down Expand Up @@ -128,6 +131,14 @@ def __init__(self, *columns, **kwargs):
:ref:`deferred_raiseload`
.. seealso::
:ref:`column_property_options` - to map columns while including
mapping options
:ref:`mapper_column_property_sql_expressions` - to map SQL
expressions
"""
super(ColumnProperty, self).__init__()
self._orig_columns = [
Expand Down Expand Up @@ -206,6 +217,21 @@ def __clause_element__(self):
def expression(self):
"""Return the primary column or expression for this ColumnProperty.
E.g.::
class File(Base):
# ...
name = Column(String(64))
extension = Column(String(8))
filename = column_property(name + '.' + extension)
path = column_property('C:/' + filename.expression)
.. seealso::
:ref:`mapper_column_property_sql_expressions_composed`
"""
return self.columns[0]

Expand Down

0 comments on commit 4c81d99

Please sign in to comment.