From: Mike Bayer Date: Sat, 26 Nov 2011 16:32:40 +0000 (-0500) Subject: more fixes, more coming X-Git-Tag: rel_0_7_4~47 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d2f3f12f7ae801243b2ba120f9f7daf3989d145e;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git more fixes, more coming --- diff --git a/doc/build/orm/mapper_config.rst b/doc/build/orm/mapper_config.rst index e31f178abd..662de1d2fa 100644 --- a/doc/build/orm/mapper_config.rst +++ b/doc/build/orm/mapper_config.rst @@ -16,8 +16,8 @@ Classical Mappings ================== A *Classical Mapping* refers to the configuration of a mapped class using the -:func:`.mapper` function, without using the Declarative system. An example -would be the declarative mapping introduced in :ref:`ormtutorial_toplevel`:: +:func:`.mapper` function, without using the Declarative system. As an example, +start with the declarative mapping introduced in :ref:`ormtutorial_toplevel`:: class User(Base): __tablename__ = 'users' @@ -68,19 +68,25 @@ object, mapped to a class called ``Address``, then linked to ``User`` via :func: When using classical mappings, classes must be provided directly without the benefit of the "string lookup" system provided by Declarative. SQL expressions are typically -specified in terms of table metadata. +specified in terms of the :class:`.Table` objects, i.e. ``address.c.id`` above +for the ``Address`` relationship, and not ``Address.id``, as ``Address`` may not +yet be linked to table metadata, nor can we specify a string here. Some examples in the documentation still use the classical approach, but note that -the classical as well as Declarative are **fully interchangeable** approaches. Both +the classical as well as Declarative approaches are **fully interchangeable**. Both systems ultimately create the same configuration, consisting of a :class:`.Table`, -user-defined class, and a :func:`.mapper` in between. +user-defined class, linked together with a :func:`.mapper`. When we talk about +"the behavior of :func:`.mapper`", this includes when using the Declarative system +as well - it's still used, just behind the scenes. Customizing Column Properties ============================== The default behavior of :func:`~.orm.mapper` is to assemble all the columns in -the mapped :class:`.Table` into mapped object attributes. This behavior can be -modified in several ways, as well as enhanced by SQL expressions. +the mapped :class:`.Table` into mapped object attributes, each of which are +named according to the name of the column itself (specifically, the ``key`` +attribute of :class:`.Column`). This behavior can be +modified in several ways. Naming Columns Distinctly from Attribute Names ---------------------------------------------- @@ -132,110 +138,83 @@ The classical version of the above:: mapper(User, user_table, column_prefix='_') -Mapping Multiple Columns to a Single Attribute ----------------------------------------------- - -To place multiple columns which are known to be "synonymous" based on foreign -key relationship or join condition into the same mapped attribute, -they can be mapped as a list. Below we map to a :func:`~.expression.join`:: +Using column_property for column level options +----------------------------------------------- - from sqlalchemy import join, Table, Column, String, Integer, ForeignKey - from sqlalchemy.ext.declarative import declarative_base +Options can be specified when mapping a :class:`.Column` using the +:func:`.column_property` function. This function +explicitly creates the :class:`.ColumnProperty` used by the +:func:`.mapper` to keep track of the :class:`.Column`; normally, the +:func:`.mapper` creates this automatically. Using :func:`.column_property`, +we can pass additional arguments about how we'd like the :class:`.Column` +to be mapped. Below, we pass an option ``active_history``, +which specifies that a change to this column's value should +result in the former value being loaded first:: - Base = declarative_base() + from sqlalchemy.orm import column_property - user_table = Table('user', Base.metadata, - Column('id', Integer, primary_key=True), - Column('name', String(50)), - Column('fullname', String(50)), - Column('password', String(12)) - ) + class User(Base): + __tablename__ = 'user' - address_table = Table('address', Base.metadata, - Column('id', Integer, primary_key=True), - Column('user_id', Integer, ForeignKey('user.id')), - Column('email_address', String(50)) - ) + id = Column(Integer, primary_key=True) + name = column_property(Column(String(50)), active_history=True) - # "user JOIN address ON user.id=address.user_id" - useraddress = join(user_table, address_table, \ - user_table.c.id == address_table.c.user_id) +:func:`.column_property` is also used to map a single attribute to +multiple columns. This use case arises when mapping to a :func:`.join` +which has attributes which are equated to each other:: class User(Base): - __table__ = useraddress + __table__ = user.join(address) # assign "user.id", "address.user_id" to the # "id" attribute - id = [user_table.c.id, address_table.c.user_id] - - # assign "address.id" to the "address_id" - # attribute, to avoid name conflicts - address_id = address_table.c.id - -In the above mapping, the value assigned to ``user.id`` will -also be persisted to the ``address.user_id`` column during a -flush. The two columns are also not independently queryable -from the perspective of the mapped class (they of course are -still available from their original tables). - -Classical version:: - - mapper(User, useraddress, properties={ - 'id':[user_table.c.id, address_table.c.user_id], - 'address_id':address_table.c.id - }) - -For further examples on this particular use case, see :ref:`maptojoin`. - -Using column_property for column level options ------------------------------------------------ + id = column_property(user_table.c.id, address_table.c.user_id) -The mapping of a :class:`.Column` with a particular :func:`.mapper` can be -customized using the :func:`.orm.column_property` function. This function -explicitly creates the :class:`.ColumnProperty` object which handles the job of -mapping a :class:`.Column`, instead of relying upon the :func:`.mapper` -function to create it automatically. Used with Declarative, -the :class:`.Column` can be embedded directly into the -function:: +For more examples about this pattern, see :ref:`maptojoin`. - from sqlalchemy.orm import column_property +Another place where :func:`.column_property` is needed is to specify SQL expressions as +mapped attributes, such as:: class User(Base): __tablename__ = 'user' - id = Column(Integer, primary_key=True) - name = column_property(Column(String(50)), active_history=True) - -Or with a classical mapping, in the ``properties`` dictionary:: - - from sqlalchemy.orm import column_property - - mapper(User, user, properties={ - 'name':column_property(user.c.name, active_history=True) - }) + firstname = Column(String(50)) + lastname = Column(String(50)) + fullname = column_property(firstname + " " + lastname) -Further examples of :func:`.orm.column_property` are at :ref:`mapper_sql_expressions`. +See examples of this usage at :ref:`mapper_sql_expressions`. .. autofunction:: column_property Mapping a Subset of Table Columns --------------------------------- -To reference a subset of columns referenced by a table as mapped attributes, -use the ``include_properties`` or ``exclude_properties`` arguments. For -example:: +Sometimes, a :class:`.Table` object was made available using the +reflection process described at :ref:`metadata_reflection` to load +the table's structure from the database. +For such a table that has lots of columns that don't need to be referenced +in the application, the ``include_properties`` or ``exclude_properties`` +arguments can specify that only a subset of columns should be mapped. +For example:: - mapper(User, user_table, include_properties=['user_id', 'user_name']) + class User(Base): + __table__ = user_table + __mapper_args__ = { + 'include_properties' :['user_id', 'user_name'] + } ...will map the ``User`` class to the ``user_table`` table, only including -the "user_id" and "user_name" columns - the rest are not refererenced. +the ``user_id`` and ``user_name`` columns - the rest are not referenced. Similarly:: - mapper(Address, address_table, - exclude_properties=['street', 'city', 'state', 'zip']) + class Address(Base): + __table__ = address_table + __mapper_args__ = { + 'exclude_properties' : ['street', 'city', 'state', 'zip'] + } ...will map the ``Address`` class to the ``address_table`` table, including -all columns present except "street", "city", "state", and "zip". +all columns present except ``street``, ``city``, ``state``, and ``zip``. When this mapping is used, the columns that are not included will not be referenced in any SELECT statements emitted by :class:`.Query`, nor will there @@ -244,22 +223,30 @@ assigning an attribute of that name will have no effect beyond that of a normal Python attribute assignment. In some cases, multiple columns may have the same name, such as when -mapping to a join of two or more tables that share some column name. To -exclude or include individual columns, :class:`.Column` objects -may also be placed within the "include_properties" and "exclude_properties" -collections (new feature as of 0.6.4):: - - mapper(UserAddress, user_table.join(addresse_table), - exclude_properties=[address_table.c.id], - primary_key=[user_table.c.id] - ) +mapping to a join of two or more tables that share some column name. +``include_properties`` and ``exclude_properties`` can also accommodate +:class:`.Column` objects to more accurately describe which columns +should be included or excluded:: + + class UserAddress(Base): + __table__ = user_table.join(addresses_table) + __mapper_args__ = { + 'exclude_properties' :[address_table.c.id], + 'primary_key' : [user_table.c.id] + } + +.. note:: insert and update defaults configured on individual + :class:`.Column` objects, i.e. those described at :ref:`metadata_defaults` + including those configured by the ``default``, ``update``, + ``server_default`` and ``server_onupdate`` arguments, will continue to + function normally even if those :class:`.Column` objects are not mapped. + This is because in the case of ``default`` and ``update``, the + :class:`.Column` object is still present on the underlying + :class:`.Table`, thus allowing the default functions to take place when + the ORM emits an INSERT or UPDATE, and in the case of ``server_default`` + and ``server_onupdate``, the relational database itself maintains these + functions. -It should be noted that insert and update defaults configured on individal -:class:`.Column` objects, such as those configured by the "default", -"update", "server_default" and "server_onupdate" arguments, will continue -to function normally even if those :class:`.Column` objects are not mapped. -This functionality is part of the SQL expression and execution system and -occurs below the level of the ORM. .. _deferred: @@ -672,16 +659,19 @@ each type of property. For example, to allow a column-mapped attribute to do case-insensitive comparison:: from sqlalchemy.orm.properties import ColumnProperty - from sqlalchemy.sql import func + from sqlalchemy.sql import func, Column, Integer, String class MyComparator(ColumnProperty.Comparator): def __eq__(self, other): return func.lower(self.__clause_element__()) == func.lower(other) - mapper(EmailAddress, address_table, properties={ - 'email':column_property(address_table.c.email, - comparator_factory=MyComparator) - }) + class EmailAddress(Base): + __tablename__ = 'address' + id = Column(Integer, primary_key=True) + email = column_property( + Column('email', String), + comparator_factory=MyComparator + ) Above, comparisons on the ``email`` column are wrapped in the SQL lower() function to produce case-insensitive matching:: @@ -846,49 +836,25 @@ Mapping a Class against Multiple Tables ======================================== Mappers can be constructed against arbitrary relational units (called -``Selectables``) as well as plain ``Tables``. For example, The ``join`` -keyword from the SQL package creates a neat selectable unit comprised of +*selectables*) as well as plain tables. For example, The :func:`join` +function from the SQL package creates a neat selectable unit comprised of multiple tables, complete with its own composite primary key, which can be -passed in to a mapper as the table. - -.. sourcecode:: python+sql +passed in to a mapper as the table:: from sqlalchemy.orm import mapper from sqlalchemy.sql import join - class AddressUser(object): - pass - # define a Join j = join(user_table, address_table) # map to it - the identity of an AddressUser object will be - # based on (user_id, address_id) since those are the primary keys involved - mapper(AddressUser, j, properties={ - 'user_id': [user_table.c.user_id, address_table.c.user_id] - }) - -Note that the list of columns is equivalent to the usage of :func:`.orm.column_property` -with multiple columns:: - - from sqlalchemy.orm import mapper, column_property - - mapper(AddressUser, j, properties={ - 'user_id': column_property(user_table.c.user_id, address_table.c.user_id) - }) - -The usage of :func:`.orm.column_property` is required when using declarative to map -to multiple columns, since the declarative class parser won't recognize a plain -list of columns:: - - from sqlalchemy.ext.declarative import declarative_base - - Base = declarative_base() + # based on (user.id, address.id) since those are the primary keys + # involved. class AddressUser(Base): __table__ = j - user_id = column_property(user_table.c.user_id, address_table.c.user_id) + id = column_property(user_table.c.id, address_table.c.user_id) A second example::