Using Descriptors
~~~~~~~~~~~~~~~~~~
-A more comprehensive way to produce modified behavior for an attribute is to use descriptors. These are commonly used in Python using the ``property()`` function. The standard SQLAlchemy technique for descriptors is to create a plain descriptor, and to have it read/write from a mapped attribute with a different name. To have the descriptor named the same as a column, map the column under a different name, i.e.:
-
-.. sourcecode:: python+sql
+A more comprehensive way to produce modified behavior for an attribute is to use descriptors. These are commonly used in Python using the ``property()`` function. The standard SQLAlchemy technique for descriptors is to create a plain descriptor, and to have it read/write from a mapped attribute with a different name. Below we illustrate
+this using Python 2.6-style properties::
class EmailAddress(object):
- def _set_email(self, email):
- self._email = email
- def _get_email(self):
- return self._email
- email = property(_get_email, _set_email)
+
+ @property
+ def email(self):
+ return self._email
+
+ @email.setter
+ def email(self, email):
+ self._email = email
- mapper(MyAddress, addresses_table, properties={
+ mapper(EmailAddress, addresses_table, properties={
'_email': addresses_table.c.email
})
-However, the approach above is not complete. While our ``EmailAddress`` object will shuttle the value through the ``email`` descriptor and into the ``_email`` mapped attribute, the class level ``EmailAddress.email`` attribute does not have the usual expression semantics usable with :class:`~sqlalchemy.orm.query.Query`. To provide these, we instead use the :func:`~sqlalchemy.orm.synonym` function as follows:
-
-.. sourcecode:: python+sql
+The approach above will work, but there's more we can add.
+While our ``EmailAddress`` object will shuttle the value
+through the ``email`` descriptor and into the ``_email``
+mapped attribute, the class level ``EmailAddress.email``
+attribute does not have the usual expression semantics
+usable with :class:`.Query`. To provide
+these, we instead use the :func:`.synonym`
+function as follows::
mapper(EmailAddress, addresses_table, properties={
'email': synonym('_email', map_column=True)
})
-The ``email`` attribute is now usable in the same way as any other mapped attribute, including filter expressions, get/set operations, etc.:
-
-.. sourcecode:: python+sql
+The ``email`` attribute is now usable in the same way as any
+other mapped attribute, including filter expressions,
+get/set operations, etc.::
address = session.query(EmailAddress).filter(EmailAddress.email == 'some address').one()
q = session.query(EmailAddress).filter_by(email='some other address')
-If the mapped class does not provide a property, the :func:`~sqlalchemy.orm.synonym` construct will create a default getter/setter object automatically.
+If the mapped class does not provide a property, the :func:`.synonym` construct will create a default getter/setter object automatically.
To use synonyms with :mod:`~sqlalchemy.ext.declarative`, see the section
:ref:`declarative_synonyms`.
print assoc.data
print assoc.child
-To enhance the association object pattern such that direct access to the ``Association`` object is optional, SQLAlchemy provides the :ref:`associationproxy`.
-
-**Important Note**: it is strongly advised that the ``secondary`` table argument not be combined with the Association Object pattern, unless the :func:`~sqlalchemy.orm.relationship` which contains the ``secondary`` argument is marked ``viewonly=True``. Otherwise, SQLAlchemy may persist conflicting data to the underlying association table since it is represented by two conflicting mappings. The Association Proxy pattern should be favored in the case where access to the underlying association data is only sometimes needed.
+To enhance the association object pattern such that direct
+access to the ``Association`` object is optional, SQLAlchemy
+provides the :ref:`associationproxy` extension. This
+extension allows the configuration of attributes which will
+access two "hops" with a single access, one "hop" to the
+associated object, and a second to a target attribute.
+
+.. note:: When using the association object pattern, it is
+ advisable that the association-mapped table not be used
+ as the ``secondary`` argument on a :func:`.relationship`
+ elsewhere, unless that :func:`.relationship` contains
+ the option ``viewonly=True``. SQLAlchemy otherwise
+ may attempt to emit redundant INSERT and DELETE
+ statements on the same table, if similar state is detected
+ on the related attribute as well as the associated
+ object.
Adjacency List Relationships
-----------------------------
-
The **adjacency list** pattern is a common relational pattern whereby a table contains a foreign key reference to itself. This is the most common and simple way to represent hierarchical data in flat tables. The other way is the "nested sets" model, sometimes called "modified preorder". Despite what many online articles say about modified preorder, the adjacency list model is probably the most appropriate pattern for the large majority of hierarchical storage needs, for reasons of concurrency, reduced complexity, and that modified preorder has little advantage over an application which can fully load subtrees into the application space.
SQLAlchemy commonly refers to an adjacency list relationship as a **self-referential mapper**. In this example, we'll work with a single table called ``treenodes`` to represent a tree structure::
so that string-configured :class:`~sqlalchemy.schema.ForeignKey`
references can be resolved without issue.
-Association of Metadata and Engine
-==================================
+Accessing the MetaData
+=======================
The :func:`declarative_base` base class contains a
-:class:`~sqlalchemy.schema.MetaData` object where newly
-defined :class:`~sqlalchemy.schema.Table` objects are collected. This
-is accessed via the :class:`~sqlalchemy.schema.MetaData` class level
-accessor, so to create tables we can say::
+:class:`.MetaData` object where newly defined
+:class:`.Table` objects are collected. This object is
+intended to be accessed directly for
+:class:`.MetaData`-specific operations. Such as, to issue
+CREATE statements for all tables::
engine = create_engine('sqlite://')
Base.metadata.create_all(engine)
-The :class:`~sqlalchemy.engine.base.Engine` created above may also be
-directly associated with the declarative base class using the ``bind``
-keyword argument, where it will be associated with the underlying
-:class:`~sqlalchemy.schema.MetaData` object and allow SQL operations
-involving that metadata and its tables to make use of that engine
-automatically::
+The usual techniques of associating :class:`.MetaData:` with :class:`.Engine`
+apply, such as assigning to the ``bind`` attribute::
- Base = declarative_base(bind=create_engine('sqlite://'))
+ Base.metadata.bind = create_engine('sqlite://')
-Alternatively, by way of the normal
-:class:`~sqlalchemy.schema.MetaData` behavior, the ``bind`` attribute
-of the class level accessor can be assigned at any time as follows::
+To associate the engine with the :func:`declarative_base` at time
+of construction, the ``bind`` argument is accepted::
- Base.metadata.bind = create_engine('sqlite://')
+ Base = declarative_base(bind=create_engine('sqlite://'))
-The :func:`declarative_base` can also receive a pre-created
-:class:`~sqlalchemy.schema.MetaData` object, which allows a
+:func:`declarative_base` can also receive a pre-existing
+:class:`.MetaData` object, which allows a
declarative setup to be associated with an already
existing traditional collection of :class:`~sqlalchemy.schema.Table`
objects::
Configuring Many-to-Many Relationships
======================================
-There's nothing special about many-to-many with declarative. The
-``secondary`` argument to :func:`~sqlalchemy.orm.relationship` still
-requires a :class:`~sqlalchemy.schema.Table` object, not a declarative
-class. The :class:`~sqlalchemy.schema.Table` should share the same
-:class:`~sqlalchemy.schema.MetaData` object used by the declarative
-base::
+Many-to-many relationships are also declared in the same way
+with declarative as with traditional mappings. The
+``secondary`` argument to
+:func:`.relationship` is as usual passed a
+:class:`.Table` object, which is typically declared in the
+traditional way. The :class:`.Table` usually shares
+the :class:`.MetaData` object used by the declarative base::
keywords = Table(
'keywords', Base.metadata,
id = Column(Integer, primary_key=True)
keywords = relationship("Keyword", secondary=keywords)
-You should generally **not** map a class and also specify its table in
-a many-to-many relationship, since the ORM may issue duplicate INSERT and
-DELETE statements.
-
+As with traditional mapping, its generally not a good idea to use
+a :class:`.Table` as the "secondary" argument which is also mapped to
+a class, unless the :class:`.relationship` is declared with ``viewonly=True``.
+Otherwise, the unit-of-work system may attempt duplicate INSERT and
+DELETE statements against the underlying table.
.. _declarative_synonyms:
Synonyms are introduced in :ref:`synonyms`. To define a getter/setter
which proxies to an underlying attribute, use
-:func:`~sqlalchemy.orm.synonym` with the ``descriptor`` argument::
+:func:`~.synonym` with the ``descriptor`` argument. Here we present
+using Python 2.6 style properties::
class MyClass(Base):
__tablename__ = 'sometable'
+ id = Column(Integer, primary_key=True)
+
_attr = Column('attr', String)
- def _get_attr(self):
- return self._some_attr
- def _set_attr(self, attr):
- self._some_attr = attr
- attr = synonym('_attr', descriptor=property(_get_attr, _set_attr))
+ @property
+ def attr(self):
+ return self._attr
+
+ @attr.setter
+ def attr(self, attr):
+ self._attr = attr
+
+ attr = synonym('_attr', descriptor=attr)
The above synonym is then usable as an instance attribute as well as a
class-level expression construct::
class MyClass(Base):
__tablename__ = 'sometable'
-
+
+ id = Column(Integer, primary_key=True)
_attr = Column('attr', String)
@synonym_for('_attr')
@property
def attr(self):
- return self._some_attr
+ return self._attr
Similarly, :func:`comparable_using` is a front end for the
-:func:`~sqlalchemy.orm.comparable_property` ORM function::
+:func:`~.comparable_property` ORM function::
class MyClass(Base):
__tablename__ = 'sometable'