1. wilsaj
  2. sqlalchemy

Commits

Mike Bayer  committed 2733a12

- [feature] polymorphic_on now accepts many
new kinds of values:

- standalone expressions that aren't
otherwise mapped
- column_property() objects
- string names of any column_property()
or attribute name of a mapped Column

The docs include an example using
the case() construct, which is likely to be
a common constructed used here.
[ticket:2345] and part of [ticket:2238]

  • Participants
  • Parent commits 192d715
  • Branches default

Comments (0)

Files changed (8)

File CHANGES

View file
  • Ignore whitespace
 0.7.4
 =====
 - orm
-   - [bug] Fixed backref behavior when "popping" the 
-     value off of a many-to-one in response to 
-     a removal from a stale one-to-many - the operation
-     is skipped, since the many-to-one has since
-     been updated.  [ticket:2315]
-
-   - [bug] After some years of not doing this, added
-     more granularity to the "is X a parent of Y" 
-     functionality, which is used when determining
-     if the FK on "Y" needs to be "nulled out" as well
-     as if "Y" should be deleted with delete-orphan
-     cascade.   The test now takes into account the
-     Python identity of the parent as well its identity 
-     key, to see if the last known parent of Y is
-     definitely X.   If a decision
-     can't be made, a StaleDataError is raised.  The
-     conditions where this error is raised are fairly
-     rare, requiring that the previous parent was
-     garbage collected, and previously
-     could very well inappropriately update/delete
-     a record that's since moved onto a new parent,
-     though there may be some cases where 
-     "silent success" occurred previously that will now 
-     raise in the face of ambiguity.
-     Expiring "Y" resets the "parent" tracker, meaning
-     X.remove(Y) could then end up deleting Y even 
-     if X is stale, but this is the same behavior
-     as before; it's advised to expire X also in that 
-     case.  [ticket:2264]
-
-   - [bug] fixed inappropriate evaluation of user-mapped
-     object in a boolean context within query.get()
-     [ticket:2310].  Also in 0.6.9.
-
-   - [bug] Added missing comma to PASSIVE_RETURN_NEVER_SET
-     symbol [ticket:2304]
-
-   - [bug] Cls.column.collate("some collation") now
-     works.  [ticket:1776]  Also in 0.6.9
-
-   - [bug] the value of a composite attribute is now
-     expired after an insert or update operation, instead
-     of regenerated in place.  This ensures that a 
-     column value which is expired within a flush
-     will be loaded first, before the composite
-     is regenerated using that value.  [ticket:2309]
-
-   - [bug] The fix in [ticket:2309] also emits the
-     "refresh" event when the composite value is
-     loaded on access, even if all column
-     values were already present, as is appropriate.
-     This fixes the "mutable" extension which relies
-     upon the "load" event to ensure the _parents 
-     dictionary is up to date, fixes [ticket:2308].
-     Thanks to Scott Torborg for the test case here.
-
-   - [bug] Fixed bug whereby a subclass of a subclass
-     using concrete inheritance in conjunction with
-     the new ConcreteBase or AbstractConcreteBase
-     would fail to apply the subclasses deeper than
-     one level to the "polymorphic loader" of each
-     base  [ticket:2312]
-
-   - [bug] Fixed bug whereby a subclass of a subclass
-     using the new AbstractConcreteBase would fail
-     to acquire the correct "base_mapper" attribute
-     when the "base" mapper was generated, thereby
-     causing failures later on.  [ticket:2312]
-
-   - [bug] Fixed bug whereby column_property() created
-     against ORM-level column could be treated as
-     a distinct entity when producing certain
-     kinds of joined-inh joins.  [ticket:2316]
-
-   - [bug] Fixed the error formatting raised when 
-     a tuple is inadvertently passed to session.query()
-     [ticket:2297].  Also in 0.6.9.
-
-   - [bug] Calls to query.join() to a single-table
-     inheritance subclass are now tracked, and
-     are used to eliminate the additional WHERE..
-     IN criterion normally tacked on with single
-     table inheritance, since the join should
-     accommodate it.  This allows OUTER JOIN
-     to a single table subclass to produce
-     the correct results, and overall will produce
-     fewer WHERE criterion when dealing with 
-     single table inheritance joins.  
-     [ticket:2328]
+  - [bug] Fixed backref behavior when "popping" the 
+    value off of a many-to-one in response to 
+    a removal from a stale one-to-many - the operation
+    is skipped, since the many-to-one has since
+    been updated.  [ticket:2315]
+
+  - [bug] After some years of not doing this, added
+    more granularity to the "is X a parent of Y" 
+    functionality, which is used when determining
+    if the FK on "Y" needs to be "nulled out" as well
+    as if "Y" should be deleted with delete-orphan
+    cascade.   The test now takes into account the
+    Python identity of the parent as well its identity 
+    key, to see if the last known parent of Y is
+    definitely X.   If a decision
+    can't be made, a StaleDataError is raised.  The
+    conditions where this error is raised are fairly
+    rare, requiring that the previous parent was
+    garbage collected, and previously
+    could very well inappropriately update/delete
+    a record that's since moved onto a new parent,
+    though there may be some cases where 
+    "silent success" occurred previously that will now 
+    raise in the face of ambiguity.
+    Expiring "Y" resets the "parent" tracker, meaning
+    X.remove(Y) could then end up deleting Y even 
+    if X is stale, but this is the same behavior
+    as before; it's advised to expire X also in that 
+    case.  [ticket:2264]
+
+  - [bug] fixed inappropriate evaluation of user-mapped
+    object in a boolean context within query.get()
+    [ticket:2310].  Also in 0.6.9.
+
+  - [bug] Added missing comma to PASSIVE_RETURN_NEVER_SET
+    symbol [ticket:2304]
+
+  - [bug] Cls.column.collate("some collation") now
+    works.  [ticket:1776]  Also in 0.6.9
+
+  - [bug] the value of a composite attribute is now
+    expired after an insert or update operation, instead
+    of regenerated in place.  This ensures that a 
+    column value which is expired within a flush
+    will be loaded first, before the composite
+    is regenerated using that value.  [ticket:2309]
+
+  - [bug] The fix in [ticket:2309] also emits the
+    "refresh" event when the composite value is
+    loaded on access, even if all column
+    values were already present, as is appropriate.
+    This fixes the "mutable" extension which relies
+    upon the "load" event to ensure the _parents 
+    dictionary is up to date, fixes [ticket:2308].
+    Thanks to Scott Torborg for the test case here.
+
+  - [bug] Fixed bug whereby a subclass of a subclass
+    using concrete inheritance in conjunction with
+    the new ConcreteBase or AbstractConcreteBase
+    would fail to apply the subclasses deeper than
+    one level to the "polymorphic loader" of each
+    base  [ticket:2312]
+
+  - [bug] Fixed bug whereby a subclass of a subclass
+    using the new AbstractConcreteBase would fail
+    to acquire the correct "base_mapper" attribute
+    when the "base" mapper was generated, thereby
+    causing failures later on.  [ticket:2312]
+
+  - [bug] Fixed bug whereby column_property() created
+    against ORM-level column could be treated as
+    a distinct entity when producing certain
+    kinds of joined-inh joins.  [ticket:2316]
+
+  - [bug] Fixed the error formatting raised when 
+    a tuple is inadvertently passed to session.query()
+    [ticket:2297].  Also in 0.6.9.
+
+  - [bug] Calls to query.join() to a single-table
+    inheritance subclass are now tracked, and
+    are used to eliminate the additional WHERE..
+    IN criterion normally tacked on with single
+    table inheritance, since the join should
+    accommodate it.  This allows OUTER JOIN
+    to a single table subclass to produce
+    the correct results, and overall will produce
+    fewer WHERE criterion when dealing with 
+    single table inheritance joins.
+    [ticket:2328]
 
   - [bug] __table_args__ can now be passed as 
     an empty tuple as well as an empty dict.
     this might be better as an exception but
     it's not critical either way.  [ticket:2325]
 
+  - [feature] polymorphic_on now accepts many
+    new kinds of values:
+
+      - standalone expressions that aren't
+        otherwise mapped
+      - column_property() objects
+      - string names of any column_property()
+        or attribute name of a mapped Column
+
+    The docs include an example using 
+    the case() construct, which is likely to be
+    a common constructed used here.
+    [ticket:2345] and part of [ticket:2238]
+
   - [feature] IdentitySet supports the - operator
     as the same as difference(), handy when dealing 
     with Session.dirty etc. [ticket:2301]
      need for a "warning" if a column is attached
      to a table after it was already used in an 
      expression - the select() construct will now
-     always produce the correct expression.  
+     always produce the correct expression.
      There's probably no real-world
      performance hit here; select() objects are 
      almost always made ad-hoc, and systems that 
     import [ticket:2253]
 
   - Reinstated "comparator_factory" argument to 
-    composite(), removed when 0.7 was released.  
+    composite(), removed when 0.7 was released.
     [ticket:2248]
 
   - Fixed bug in query.join() which would occur
     ArgumentError, rather than UnmappedClassError.
     [ticket:2196]
 
-  - New event hook, MapperEvents.after_configured().  
+  - New event hook, MapperEvents.after_configured().
     Called after a configure() step has completed and
     mappers were in fact affected.   Theoretically this
     event is called once per application, unless new mappings
     long lists of bound parameter sets will be 
     compressed with an informative indicator
     of the compression taking place.  Exception
-    messages use the same improved formatting.  
+    messages use the same improved formatting.
     [ticket:2243]
 
   - Added optional "sa_pool_key" argument to 
   - The behavior of =/!= when comparing a scalar select
     to a value will no longer produce IN/NOT IN as of 0.8;
     this behavior is a little too heavy handed (use in_() if
-    you want to emit IN) and now emits a deprecation warning.   
+    you want to emit IN) and now emits a deprecation warning.
     To get the 0.8 behavior immediately and remove the warning, 
     a compiler recipe is given at 
     http://www.sqlalchemy.org/docs/07/dialects/mssql.html#scalar-select-comparisons
 
   - Added the same "columns-only" check to 
     mapper.polymorphic_on as used when
-    receiving user arguments to  
+    receiving user arguments to
     relationship.order_by, foreign_keys,
     remote_side, etc.
 
     execution.
 
   - StatementException wrapping will display the
-    original exception class in the message.  
+    original exception class in the message.
 
   - Failures on connect which raise dbapi.Error
     will forward the error to dialect.is_disconnect()
 - sqlite
   - SQLite dialect no longer strips quotes
     off of reflected default value, allowing
-    a round trip CREATE TABLE to work.  
+    a round trip CREATE TABLE to work.
     This is consistent with other dialects
     that also maintain the exact form of
     the default.  [ticket:2189]

File doc/build/orm/inheritance.rst

View file
  • Ignore whitespace
 ``employees.type`` column with ``engineer``, ``manager``, or ``employee``, as
 appropriate.
 
+.. _with_polymorphic:
+
 Basic Control of Which Tables are Queried
 ++++++++++++++++++++++++++++++++++++++++++
 

File doc/build/orm/mapper_config.rst

View file
  • Ignore whitespace
        'name': user_table.c.user_name,
     })
 
+.. _column_prefix:
+
 Naming All Columns with a Prefix
 --------------------------------
 
 
 .. autofunction:: column_property
 
+.. _include_exclude_cols:
+
 Mapping a Subset of Table Columns
 ---------------------------------
 

File doc/build/orm/relationships.rst

View file
  • Ignore whitespace
 :func:`.relationship` we must limit those columns considered as part of
 the foreign key for the purposes of joining and cross-population.
 
+.. _passive_updates:
 
 Mutable Primary Keys / Update Cascades
 ---------------------------------------

File lib/sqlalchemy/orm/__init__.py

View file
  • Ignore whitespace
 
 def mapper(class_, local_table=None, *args, **params):
     """Return a new :class:`~.Mapper` object.
+    
+        This function is typically used behind the scenes
+        via the Declarative extension.   When using Declarative,
+        many of the usual :func:`.mapper` arguments are handled
+        by the Declarative extension itself, including ``class_``,
+        ``local_table``, ``properties``, and  ``inherits``.
+        Other options are passed to :func:`.mapper` using 
+        the ``__mapper_args__`` class variable::
+   
+           class MyClass(Base):
+               __tablename__ = 'my_table'
+               id = Column(Integer, primary_key=True)
+               type = Column(String(50))
+               alt = Column("some_alt", Integer)
+               
+               __mapper_args__ = {
+                   'polymorphic_on' : type
+               }
 
-        :param class\_: The class to be mapped.
 
-        :param local_table: The table to which the class is mapped, or None if
-           this mapper inherits from another mapper using concrete table
-           inheritance.
+        Explicit use of :func:`.mapper`
+        is often referred to as *classical mapping*.  The above 
+        declarative example is equivalent in classical form to::
+        
+            my_table = Table("my_table", metadata,
+                Column('id', Integer, primary_key=True),
+                Column('type', String(50)),
+                Column("some_alt", Integer)
+            )
+            
+            class MyClass(object):
+                pass
+            
+            mapper(MyClass, my_table, 
+                polymorphic_on=my_table.c.type, 
+                properties={
+                    'alt':my_table.c.some_alt
+                })
+        
+        See also:
+        
+        :ref:`classical_mapping` - discussion of direct usage of
+        :func:`.mapper`
+
+        :param class\_: The class to be mapped.  When using Declarative,
+          this argument is automatically passed as the declared class
+          itself.
+
+        :param local_table: The :class:`.Table` or other selectable 
+           to which the class is mapped.  May be ``None`` if 
+           this mapper inherits from another mapper using single-table
+           inheritance.   When using Declarative, this argument is 
+           automatically passed by the extension, based on what
+           is configured via the ``__table__`` argument or via the :class:`.Table`
+           produced as a result of the ``__tablename__`` and :class:`.Column`
+           arguments present.
 
         :param always_refresh: If True, all query operations for this mapped
            class will overwrite all data within object instances that already
            particular primary key value. A "partial primary key" can occur if
            one has mapped to an OUTER JOIN, for example.
 
-        :param batch: Indicates that save operations of multiple entities 
-           can be batched together for efficiency. setting to False indicates
+        :param batch: Defaults to ``True``, indicating that save operations 
+           of multiple entities can be batched together for efficiency. 
+           Setting to False indicates
            that an instance will be fully saved before saving the next
-           instance, which includes inserting/updating all table rows
-           corresponding to the entity as well as calling all
-           :class:`.MapperExtension` methods corresponding to the save
-           operation.
+           instance.  This is used in the extremely rare case that a 
+           :class:`.MapperEvents` listener requires being called 
+           in between individual row persistence operations.
 
-        :param column_prefix: A string which will be prepended to the `key`
-           name of all :class:`.Column` objects when creating 
-           column-based properties from the
-           given :class:`.Table`. Does not affect explicitly specified 
-           column-based properties
+        :param column_prefix: A string which will be prepended 
+           to the mapped attribute name when :class:`.Column`
+           objects are automatically assigned as attributes to the
+           mapped class.  Does not affect explicitly specified 
+           column-based properties.   
+           
+           See the section :ref:`column_prefix` for an example.
 
         :param concrete: If True, indicates this mapper should use concrete
            table inheritance with its parent mapper.
+           
+           See the section :ref:`concrete_inheritance` for an example.
 
         :param exclude_properties: A list or set of string column names to 
-          be excluded from mapping. As of SQLAlchemy 0.6.4, this collection
-          may also include :class:`.Column` objects. Columns named or present
-          in this list will not be automatically mapped. Note that neither
-          this option nor include_properties will allow one to circumvent plan
-          Python inheritance - if mapped class ``B`` inherits from mapped
-          class ``A``, no combination of includes or excludes will allow ``B``
-          to have fewer properties than its superclass, ``A``.
+          be excluded from mapping.  
+          
+          See :ref:`include_exclude_cols` for an example.
 
         :param extension: A :class:`.MapperExtension` instance or
            list of :class:`.MapperExtension`
            :class:`.Mapper`.  **Deprecated.**  Please see :class:`.MapperEvents`.
 
         :param include_properties: An inclusive list or set of string column
-          names to map. As of SQLAlchemy 0.6.4, this collection may also
-          include :class:`.Column` objects in order to disambiguate between
-          same-named columns in a selectable (such as a
-          :func:`~.expression.join()`). If this list is not ``None``, columns
-          present in the mapped table but not named or present in this list
-          will not be automatically mapped. See also "exclude_properties".
+          names to map.   
+          
+          See :ref:`include_exclude_cols` for an example.
 
-        :param inherits: Another :class:`.Mapper` for which 
-            this :class:`.Mapper` will have an inheritance
-            relationship with.
-
+        :param inherits: A mapped class or the corresponding :class:`.Mapper` 
+          of one indicating a superclass to which this :class:`.Mapper`
+          should *inherit* from.   The mapped class here must be a subclass of the
+          other mapper's class.   When using Declarative, this argument
+          is passed automatically as a result of the natural class
+          hierarchy of the declared classes.   
+          
+          See also:
+          
+          :ref:`inheritance_toplevel`
+          
         :param inherit_condition: For joined table inheritance, a SQL
-           expression (constructed
-           :class:`.ClauseElement`) which will
+           expression which will
            define how the two tables are joined; defaults to a natural join
            between the two tables.
 
-        :param inherit_foreign_keys: When inherit_condition is used and the
-           condition contains no ForeignKey columns, specify the "foreign"
-           columns of the join condition in this list. else leave as None.
+        :param inherit_foreign_keys: When ``inherit_condition`` is used and the
+           columns present are missing a :class:`.ForeignKey` configuration, 
+           this parameter can be used to specify which columns are "foreign".  
+           In most cases can be left as ``None``.
 
-        :param non_primary: Construct a :class:`.Mapper` that will define only
-           the selection of instances, not their persistence. Any number of
-           non_primary mappers may be created for a particular class.
+        :param non_primary: Specify that this :class:`.Mapper` is in addition
+          to the "primary" mapper, that is, the one used for persistence.
+          The :class:`.Mapper` created here may be used for ad-hoc
+          mapping of the class to an alternate selectable, for loading
+          only.   
+          
+          The ``non_primary`` feature is rarely needed with modern
+          usage.
 
         :param order_by: A single :class:`.Column` or list of :class:`.Column`
            objects for which selection operations should use as the default
-           ordering for entities. Defaults to the OID/ROWID of the table if
-           any, or the first primary key column of the table.
+           ordering for entities.  By default mappers have no pre-defined 
+           ordering.
 
-        :param passive_updates: Indicates UPDATE behavior of foreign keys 
-           when a primary key changes on a joined-table inheritance or other
-           joined table mapping.
+        :param passive_updates: Indicates UPDATE behavior of foreign key
+           columns when a primary key column changes on a joined-table inheritance 
+           mapping.   Defaults to ``True``.
 
            When True, it is assumed that ON UPDATE CASCADE is configured on
            the foreign key in the database, and that the database will handle
-           propagation of an UPDATE from a source column to dependent rows.
-           Note that with databases which enforce referential integrity (i.e.
-           PostgreSQL, MySQL with InnoDB tables), ON UPDATE CASCADE is
-           required for this operation. The relationship() will update the
-           value of the attribute on related items which are locally present
-           in the session during a flush.
+           propagation of an UPDATE from a source column to dependent columns
+           on joined-table rows.
 
            When False, it is assumed that the database does not enforce
            referential integrity and will not be issuing its own CASCADE
-           operation for an update. The relationship() will issue the
-           appropriate UPDATE statements to the database in response to the
-           change of a referenced key, and items locally present in the
-           session during a flush will also be refreshed.
+           operation for an update.  The :class:`.Mapper` here will
+           emit an UPDATE statement for the dependent columns during a
+           primary key change.
+           
+           See also:
+           
+           :ref:`passive_updates` - description of a similar feature as 
+           used with :func:`.relationship`
 
-           This flag should probably be set to False if primary key changes
-           are expected and the database in use doesn't support CASCADE (i.e.
-           SQLite, MySQL MyISAM tables).
+        :param polymorphic_on: Specifies the column, attribute, or 
+          SQL expression used to determine the target class for an 
+          incoming row, when inheriting classes are present.
+          
+          This value is commonly a :class:`.Column` object that's
+          present in the mapped :class:`.Table`::
+          
+            class Employee(Base):
+                __tablename__ = 'employee'
+                
+                id = Column(Integer, primary_key=True)
+                discriminator = Column(String(50))
+                
+                __mapper_args__ = {
+                    "polymorphic_on":discriminator,
+                    "polymorphic_identity":"employee"
+                }
+        
+          As of SQLAlchemy 0.7.4, it may also be specified
+          as a SQL expression, as in this example where we 
+          use the :func:`.case` construct to provide a conditional
+          approach::
 
-            Also see the passive_updates flag on :func:`relationship()`.
+            class Employee(Base):
+                __tablename__ = 'employee'
+                
+                id = Column(Integer, primary_key=True)
+                discriminator = Column(String(50))
+                
+                __mapper_args__ = {
+                    "polymorphic_on":case([
+                        (discriminator == "EN", "engineer"),
+                        (discriminator == "MA", "manager"),
+                    ], else_="employee"),
+                    "polymorphic_identity":"employee"
+                }
+        
+          Also as of 0.7.4, it may also refer to any attribute 
+          configured with :func:`.column_property`, or to the
+          string name of one::
+            
+                class Employee(Base):
+                    __tablename__ = 'employee'
+                
+                    id = Column(Integer, primary_key=True)
+                    discriminator = Column(String(50))
+                    employee_type = column_property(
+                        case([
+                            (discriminator == "EN", "engineer"),
+                            (discriminator == "MA", "manager"),
+                        ], else_="employee")
+                    )
+                
+                    __mapper_args__ = {
+                        "polymorphic_on":employee_type,
+                        "polymorphic_identity":"employee"
+                    }
+            
+          When setting ``polymorphic_on`` to reference an
+          attribute or expression that's not present in the
+          locally mapped :class:`.Table`, yet the value 
+          of the discriminator should be persisted to the database, 
+          the value of the
+          discriminator is not automatically set on new
+          instances; this must be handled by the user,
+          either through manual means or via event listeners.
+          A typical approach to establishing such a listener
+          looks like::
 
-           A future SQLAlchemy release will provide a "detect" feature for
-           this flag.
-
-        :param polymorphic_on: Used with mappers in an inheritance
-           relationship, a :class:`.Column` which will identify the class/mapper
-           combination to be used with a particular row. Requires the
-           ``polymorphic_identity`` value to be set for all mappers in the
-           inheritance hierarchy. The column specified by ``polymorphic_on``
-           is usually a column that resides directly within the base mapper's
-           mapped table; alternatively, it may be a column that is only
-           present within the <selectable> portion of the ``with_polymorphic``
-           argument.
-
-        :param polymorphic_identity: A value which will be stored in the
-           Column denoted by polymorphic_on, corresponding to the class
-           identity of this mapper.
+                from sqlalchemy import event
+                from sqlalchemy.orm import object_mapper
+            
+                @event.listens_for(Employee, "init", propagate=True)
+                def set_identity(instance, *arg, **kw):
+                    mapper = object_mapper(instance)
+                    instance.discriminator = mapper.polymorphic_identity
+        
+          Where above, we assign the value of ``polymorphic_identity``
+          for the mapped class to the ``discriminator`` attribute,
+          thus persisting the value to the ``discriminator`` column
+          in the database.
+      
+          See also:
+      
+          :ref:`inheritance_toplevel`
+        
+        :param polymorphic_identity: Specifies the value which 
+          identifies this particular class as returned by the
+          column expression referred to by the ``polymorphic_on``
+          setting.  As rows are received, the value corresponding
+          to the ``polymorphic_on`` column expression is compared
+          to this value, indicating which subclass should 
+          be used for the newly reconstructed object.
 
         :param properties: A dictionary mapping the string names of object
-           attributes to ``MapperProperty`` instances, which define the
-           persistence behavior of that attribute. Note that the columns in
-           the mapped table are automatically converted into
-           ``ColumnProperty`` instances based on the ``key`` property of each
-           :class:`.Column` (although they can be overridden using this dictionary).
+           attributes to :class:`.MapperProperty` instances, which define the
+           persistence behavior of that attribute.  Note that :class:`.Column`
+           objects present in
+           the mapped :class:`.Table` are automatically placed into
+           ``ColumnProperty`` instances upon mapping, unless overridden.
+           When using Declarative, this argument is passed automatically,
+           based on all those :class:`.MapperProperty` instances declared
+           in the declared class body.
 
         :param primary_key: A list of :class:`.Column` objects which define the
            primary key to be used against this mapper's selectable unit.
            This is normally simply the primary key of the ``local_table``, but
            can be overridden here.
 
-        :param version_id_col: A :class:`.Column` which must have an integer type
+        :param version_id_col: A :class:`.Column` 
            that will be used to keep a running version id of mapped entities
-           in the database. this is used during save operations to ensure that
+           in the database.  This is used during save operations to ensure that
            no other thread or process has updated the instance during the
-           lifetime of the entity, else a :class:`.StaleDataError` exception is
-           thrown.
+           lifetime of the entity, else a :class:`~sqlalchemy.orm.exc.StaleDataError` 
+           exception is
+           thrown.  By default the column must be of :class:`.Integer` type,
+           unless ``version_id_generator`` specifies a new generation
+           algorithm.
 
         :param version_id_generator: A callable which defines the algorithm
             used to generate new version ids. Defaults to an integer
 
                 import uuid
 
-                mapper(Cls, table, 
-                version_id_col=table.c.version_uuid,
-                version_id_generator=lambda version:uuid.uuid4().hex
-                )
+                class MyClass(Base):
+                    __tablename__ = 'mytable'
+                    id = Column(Integer, primary_key=True)
+                    version_uuid = Column(String(32))
+                    
+                    __mapper_args__ = {
+                        'version_id_col':version_uuid,
+                        'version_id_generator':lambda version:uuid.uuid4().hex
+                    }
 
             The callable receives the current version identifier as its 
             single argument.
             ``'*'`` may be used to indicate all descending classes should be
             loaded immediately. The second tuple argument <selectable>
             indicates a selectable that will be used to query for multiple
-            classes. Normally, it is left as None, in which case this mapper
-            will form an outer join from the base mapper's table to that of
-            all desired sub-mappers. When specified, it provides the
-            selectable to be used for polymorphic loading. When
-            with_polymorphic includes mappers which load from a "concrete"
-            inheriting table, the <selectable> argument is required, since it
-            usually requires more complex UNION queries.
-
+            classes. 
+            
+            See also:
+            
+            :ref:`concrete_inheritance` - typically uses ``with_polymorphic``
+            to specify a UNION statement to select from.
+            
+            :ref:`with_polymorphic` - usage example of the related 
+            :meth:`.Query.with_polymorphic` method
+            
     """
     return Mapper(class_, local_table, *args, **params)
 

File lib/sqlalchemy/orm/mapper.py

View file
  • Ignore whitespace
         self.batch = batch
         self.eager_defaults = eager_defaults
         self.column_prefix = column_prefix
-        self.polymorphic_on = expression._only_column_elements_or_none(
-                                        polymorphic_on, 
-                                        "polymorphic_on")
+        self.polymorphic_on = polymorphic_on
         self._dependency_processors = []
         self.validators = util.immutabledict()
         self.passive_updates = passive_updates
         if self.polymorphic_on is not None:
             setter = True
 
-            if self.polymorphic_on not in self._columntoproperty:
+            if isinstance(self.polymorphic_on, basestring):
+                try:
+                    self.polymorphic_on = self._props[self.polymorphic_on]
+                except KeyError:
+                    raise sa_exc.ArgumentError(
+                                "Can't determine polymorphic_on "
+                                "value '%s' - no attribute is "
+                                "mapped to this name." % self.polymorphic_on)
+
+            if self.polymorphic_on in self._columntoproperty:
+                prop = self._columntoproperty[self.polymorphic_on]
+                polymorphic_key = prop.key
+                self.polymorphic_on = prop.columns[0]
+            elif isinstance(self.polymorphic_on, MapperProperty):
+                if not isinstance(self.polymorphic_on, properties.ColumnProperty):
+                    raise sa_exc.ArgumentError(
+                            "Only direct column-mapped "
+                            "property or SQL expression "
+                            "can be passed for polymorphic_on")
+                prop = self.polymorphic_on
+                self.polymorphic_on = prop.columns[0]
+                polymorphic_key = prop.key
+            elif not expression.is_column(self.polymorphic_on):
+                raise sa_exc.ArgumentError(
+                    "Only direct column-mapped "
+                    "property or SQL expression "
+                    "can be passed for polymorphic_on"
+                )
+            else:
                 col = self.mapped_table.corresponding_column(self.polymorphic_on)
                 if col is None:
                     setter = False
                     instrument = False
                     col = self.polymorphic_on
-                    if self.with_polymorphic is None \
-                        or self.with_polymorphic[1].corresponding_column(col) \
-                        is None:
-                        raise sa_exc.InvalidRequestError("Could not map polymorphic_on column "
-                                  "'%s' to the mapped table - polymorphic "
-                                  "loads will not function properly"
-                                  % col.description)
+                    if isinstance(col, schema.Column) and (
+                        self.with_polymorphic is  None or \
+                       self.with_polymorphic[1].corresponding_column(col) is None
+                        ):
+                        raise sa_exc.InvalidRequestError(
+                            "Could not map polymorphic_on column "
+                            "'%s' to the mapped table - polymorphic "
+                            "loads will not function properly"
+                                 % col.description)
                 else:
                     instrument = True
 
-                if self._should_exclude(col.key, col.key, False, col):
-                    raise sa_exc.InvalidRequestError(
+                key = getattr(col, 'key', None)
+                if key:
+                    if self._should_exclude(col.key, col.key, False, col):
+                        raise sa_exc.InvalidRequestError(
                         "Cannot exclude or override the discriminator column %r" %
                         col.key)
+                else:
+                    col = col.label(None)
+                    key = col.key
 
                 self._configure_property(
-                                col.key, 
+                                key, 
                                 properties.ColumnProperty(col, _instrument=instrument),
                                 init=init, setparent=True)
-                polymorphic_key = col.key
-            else:
-                polymorphic_key = self._columntoproperty[self.polymorphic_on].key
+                polymorphic_key = key
 
         if setter:
             def _set_polymorphic_identity(state):
                                     prop.columns[0] is self.polymorphic_on)
 
             self.columns[key] = col
-            for col in prop.columns:
+            for col in prop.columns + prop._orig_columns:
                 for col in col.proxy_set:
                     self._columntoproperty[col] = prop
 

File lib/sqlalchemy/orm/properties.py

View file
  • Ignore whitespace
         :param extension:
 
         """
+        self._orig_columns = [expression._labeled(c) for c in columns]
         self.columns = [expression._labeled(_orm_deannotate(c)) 
                             for c in columns]
         self.group = kwargs.pop('group', None)

File test/orm/inheritance/test_basic.py

View file
  • Ignore whitespace
 import warnings
 from test.lib.testing import eq_, assert_raises, assert_raises_message
 from sqlalchemy import *
-from sqlalchemy import exc as sa_exc, util
+from sqlalchemy import exc as sa_exc, util, event
 from sqlalchemy.orm import *
 from sqlalchemy.orm import exc as orm_exc, attributes
 from test.lib.assertsql import AllOf, CompiledSQL
                 Column('y', String(10)), 
                 Column('xid', ForeignKey('t1.id')))
 
+    @classmethod
+    def setup_classes(cls):
+        class Parent(cls.Comparable):
+            pass
+        class Child(Parent):
+            pass
+
     def test_non_col_polymorphic_on(self):
-        class InterfaceBase(object):
-            pass
+        Parent = self.classes.Parent
+        t2 = self.tables.t2
+        assert_raises_message(
+            sa_exc.ArgumentError,
+            "Can't determine polymorphic_on "
+            "value 'im not a column' - no "
+            "attribute is mapped to this name.",
+            mapper,
+            Parent, t2, polymorphic_on="im not a column"
+        )
+
+    def test_polymorphic_on_non_expr_prop(self):
+        t2, t1 = self.tables.t2, self.tables.t1
+        Parent = self.classes.Parent
+
+        t1t2_join = select([t1.c.x], from_obj=[t1.join(t2)]).alias()
+        def go():
+            interface_m = mapper(Parent, t2,
+                                polymorphic_on=lambda:"hi",
+                                polymorphic_identity=0)
 
         assert_raises_message(
             sa_exc.ArgumentError,
-            "Column-based expression object expected "
-            "for argument 'polymorphic_on'; got: "
-            "'im not a column', type",
-            mapper,
-            InterfaceBase, t2, polymorphic_on="im not a column"
+            "Only direct column-mapped property or "
+            "SQL expression can be passed for polymorphic_on",
+            go
         )
 
-    def test_bad_polymorphic_on(self):
+    def test_polymorphic_on_not_present_col(self):
         t2, t1 = self.tables.t2, self.tables.t1
-
-        class InterfaceBase(object):
-            pass
-
+        Parent = self.classes.Parent
         t1t2_join = select([t1.c.x], from_obj=[t1.join(t2)]).alias()
         def go():
-            interface_m = mapper(InterfaceBase, t2,
-                                polymorphic_on=t1t2_join.c.x,
-                                polymorphic_identity=0)
-
-        assert_raises_message(
-            sa_exc.InvalidRequestError,
-            "Could not map polymorphic_on column 'x' to the mapped table - "
-            "polymorphic loads will not function properly",
-            go
-        )
-        clear_mappers()
-
-        # if its in the with_polymorphic, then its OK
-        interface_m = mapper(InterfaceBase, t2,
-                                polymorphic_on=t1t2_join.c.x,
-                                with_polymorphic=('*', t1t2_join),
-                                polymorphic_identity=0)
-        configure_mappers()
-
-        clear_mappers()
-
-        # if with_polymorphic, but its not present, not OK
-        def go():
             t1t2_join_2 = select([t1.c.q], from_obj=[t1.join(t2)]).alias()
-            interface_m = mapper(InterfaceBase, t2,
+            interface_m = mapper(Parent, t2,
                                 polymorphic_on=t1t2_join.c.x,
                                 with_polymorphic=('*', t1t2_join_2),
                                 polymorphic_identity=0)
             go
         )
 
+    def test_polymorphic_on_only_in_with_poly(self):
+        t2, t1 = self.tables.t2, self.tables.t1
+        Parent = self.classes.Parent
+        t1t2_join = select([t1.c.x], from_obj=[t1.join(t2)]).alias()
+        # if its in the with_polymorphic, then its OK
+        mapper(Parent, t2,
+                                polymorphic_on=t1t2_join.c.x,
+                                with_polymorphic=('*', t1t2_join),
+                                polymorphic_identity=0)
+
+    def test_polymorpic_on_not_in_with_poly(self):
+        t2, t1 = self.tables.t2, self.tables.t1
+        Parent = self.classes.Parent
+
+        t1t2_join = select([t1.c.x], from_obj=[t1.join(t2)]).alias()
+
+        # if with_polymorphic, but its not present, not OK
+        def go():
+            t1t2_join_2 = select([t1.c.q], from_obj=[t1.join(t2)]).alias()
+            interface_m = mapper(Parent, t2,
+                                polymorphic_on=t1t2_join.c.x,
+                                with_polymorphic=('*', t1t2_join_2),
+                                polymorphic_identity=0)
+        assert_raises_message(
+            sa_exc.InvalidRequestError,
+            "Could not map polymorphic_on column 'x' "
+            "to the mapped table - "
+            "polymorphic loads will not function properly",
+            go
+        )
+
+    def test_polymorphic_on_expr_explicit_map(self):
+        t2, t1 = self.tables.t2, self.tables.t1
+        Parent, Child = self.classes.Parent, self.classes.Child
+        expr = case([
+            (t1.c.x=="p", "parent"),
+            (t1.c.x=="c", "child"),
+        ],else_ = t1.c.x)
+        mapper(Parent, t1, properties={
+            "discriminator":column_property(expr)
+        }, polymorphic_identity="parent",
+            polymorphic_on=expr)
+        mapper(Child, t2, inherits=Parent, 
+                polymorphic_identity="child")
+
+        self._roundtrip()
+
+    def test_polymorphic_on_expr_implicit_map(self):
+        t2, t1 = self.tables.t2, self.tables.t1
+        Parent, Child = self.classes.Parent, self.classes.Child
+        expr = case([
+            (t1.c.x=="p", "parent"),
+            (t1.c.x=="c", "child"),
+        ],else_ = t1.c.x).label("foo")
+        mapper(Parent, t1, polymorphic_identity="parent",
+            polymorphic_on=expr)
+        mapper(Child, t2, inherits=Parent, polymorphic_identity="child")
+
+        self._roundtrip()
+
+    def test_polymorphic_on_column_prop(self):
+        t2, t1 = self.tables.t2, self.tables.t1
+        Parent, Child = self.classes.Parent, self.classes.Child
+        expr = case([
+            (t1.c.x=="p", "parent"),
+            (t1.c.x=="c", "child"),
+        ],else_ = t1.c.x)
+        cprop = column_property(expr)
+        mapper(Parent, t1, properties={
+            "discriminator":cprop
+        }, polymorphic_identity="parent",
+            polymorphic_on=cprop)
+        mapper(Child, t2, inherits=Parent, 
+                polymorphic_identity="child")
+
+        self._roundtrip()
+
+    def test_polymorphic_on_column_str_prop(self):
+        t2, t1 = self.tables.t2, self.tables.t1
+        Parent, Child = self.classes.Parent, self.classes.Child
+        expr = case([
+            (t1.c.x=="p", "parent"),
+            (t1.c.x=="c", "child"),
+        ],else_ = t1.c.x)
+        cprop = column_property(expr)
+        mapper(Parent, t1, properties={
+            "discriminator":cprop
+        }, polymorphic_identity="parent",
+            polymorphic_on="discriminator")
+        mapper(Child, t2, inherits=Parent, 
+                polymorphic_identity="child")
+
+        self._roundtrip()
+
+    def test_polymorphic_on_synonym(self):
+        t2, t1 = self.tables.t2, self.tables.t1
+        Parent, Child = self.classes.Parent, self.classes.Child
+        cprop = column_property(t1.c.x)
+        assert_raises_message(
+            sa_exc.ArgumentError,
+            "Only direct column-mapped property or "
+            "SQL expression can be passed for polymorphic_on",
+            mapper, Parent, t1, properties={
+            "discriminator":cprop,
+            "discrim_syn":synonym(cprop)
+        }, polymorphic_identity="parent",
+            polymorphic_on="discrim_syn")
+
+    def _roundtrip(self, set_event=True):
+        Parent, Child = self.classes.Parent, self.classes.Child
+
+        if set_event:
+            @event.listens_for(Parent, "init", propagate=True)
+            def set_identity(instance, *arg, **kw):
+                instance.x = object_mapper(instance).polymorphic_identity
+
+        s = Session(testing.db)
+        s.add_all([
+            Parent(q="p1"),
+            Child(q="c1", y="c1"),
+            Parent(q="p2"),
+        ])
+        s.commit()
+        s.close()
+
+        eq_(
+            [type(t) for t in s.query(Parent).order_by(Parent.id)],
+            [Parent, Child, Parent]
+        )
+
 
 class FalseDiscriminatorTest(fixtures.MappedTest):
     @classmethod