]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
fixes, clarifications, removal of misleading statements
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 3 Aug 2010 16:26:56 +0000 (12:26 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 3 Aug 2010 16:26:56 +0000 (12:26 -0400)
doc/build/mappers.rst
lib/sqlalchemy/ext/declarative.py

index a6e25e7d0df6e06fd2dd3e4632886e272e10e631..97677aa080ebd5b7d593627f1f2d06169b6e8aa9 100644 (file)
@@ -272,32 +272,39 @@ Validators also receive collection events, when items are added to a collection:
 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()
 
@@ -306,7 +313,7 @@ The ``email`` attribute is now usable in the same way as any other mapped attrib
 
     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`.
@@ -1241,14 +1248,26 @@ Working with the association pattern in its direct form requires that child obje
         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::
index 162bc8d6a64d2adbbcfd0853668e91fdf78bbac2..90ce5b7798476d73feeed8b4ca9b39a85103cfd4 100755 (executable)
@@ -67,35 +67,31 @@ share the same underlying :class:`~sqlalchemy.schema.MetaData` object,
 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:: 
@@ -164,12 +160,13 @@ class after the fact::
 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,
@@ -182,10 +179,11 @@ base::
         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:
 
@@ -194,18 +192,25 @@ Defining 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::
@@ -219,16 +224,17 @@ conjunction with ``@property``::
 
     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'