From: Mike Bayer Date: Mon, 2 Aug 2010 00:55:44 +0000 (-0400) Subject: - worked through about 25% of mappers.rst to implement X-Git-Tag: rel_0_6_4~66 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2f844f231cbcd86dad5d4094565858424ea2c3c7;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - worked through about 25% of mappers.rst to implement up to date and clear explanations of things, including as much context and description as possible --- diff --git a/doc/build/conf.py b/doc/build/conf.py index 64d5c3ed32..175330437f 100644 --- a/doc/build/conf.py +++ b/doc/build/conf.py @@ -36,6 +36,8 @@ extensions = ['sphinx.ext.autodoc', # Add any paths that contain templates here, relative to this directory. templates_path = ['templates'] +nitpicky = True + # The suffix of source filenames. source_suffix = '.rst' diff --git a/doc/build/mappers.rst b/doc/build/mappers.rst index 81d67f2178..701e95fac5 100644 --- a/doc/build/mappers.rst +++ b/doc/build/mappers.rst @@ -3,49 +3,146 @@ ==================== Mapper Configuration ==================== -This section references most major configurational patterns involving the :func:`~sqlalchemy.orm.mapper` and :func:`~sqlalchemy.orm.relationship` functions. It assumes you've worked through :ref:`ormtutorial_toplevel` and know how to construct and use rudimentary mappers and relationships. +This section references most major configurational patterns involving the +:func:`~.orm.mapper` and :func:`.relationship` functions. It assumes you've +worked through :ref:`ormtutorial_toplevel` and know how to construct and use +rudimentary mappers and relationships. Mapper Configuration ==================== +This section describes a variety of configurational patterns that are usable +with mappers. Most of these examples apply equally well +to the usage of distinct :func:`~.orm.mapper` and :class:`.Table` objects +as well as when using the :mod:`sqlalchemy.ext.declarative` extension. + +Any example in this section which takes a form such as:: + + mapper(User, users_table, primary_key=[users_table.c.id]) + +Would translate into declarative as:: + + class User(Base): + __table__ = users_table + __mapper_args__ = { + 'primary_key':users_table.c.id + } + +Or if using ``__tablename__``, :class:`.Column` objects are declared inline +with the class definition. These are usable as is within ``__mapper_args__``:: + + class User(Base): + __tablename__ = 'users' + + id = Column(Integer) + + __mapper_args__ = { + 'primary_key':id + } + +For a full reference of all options available on mappers, please see the API +description of :func:`~.orm.mapper`. + Customizing Column Properties ------------------------------ -The default behavior of a ``mapper`` is to assemble all the columns in the mapped :class:`~sqlalchemy.schema.Table` into mapped object attributes. This behavior can be modified in several ways, as well as enhanced by SQL expressions. +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. + +Mapping a Subset of Table Columns +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -To load only a part of the columns referenced by a table as attributes, use the ``include_properties`` and ``exclude_properties`` arguments:: +To reference a subset of columns referenced by a table as mapped attributes, +use the ``include_properties`` or ``exclude_properties`` arguments. For +example:: mapper(User, users_table, include_properties=['user_id', 'user_name']) + +Will map the ``User`` class to the ``users_table`` table, only including +the "user_id" and "user_name" columns - the rest are not refererenced. +Similarly:: + + mapper(Address, addresses_table, + exclude_properties=['street', 'city', 'state', 'zip']) + +will map the ``Address`` class to the ``addresses_table`` table, including +all columns present except "street", "city", "state", and "zip". - mapper(Address, addresses_table, exclude_properties=['street', 'city', 'state', '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 +be any mapped attribute on the mapped class which represents the column; +setting a value on the mapped class to a name which matches an un-mapped +column will have no effect. -To change the name of the attribute mapped to a particular column, place the :class:`~sqlalchemy.schema.Column` object in the ``properties`` dictionary with the desired key:: +It should be noted however that "default", "on_update", "server_default" and +"server_onupdate" attributes configured on the :class:`.Column` *will* continue to function normally. The columns are ignored only at the mapper +level, but not at the SQL expression level. The ORM uses the SQL expression +system to emit SQL to the database. + +Attribute Names for Mapped Columns +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To change the name of the attribute mapped to a particular column, place the +:class:`~sqlalchemy.schema.Column` object in the ``properties`` dictionary +with the desired key:: mapper(User, users_table, properties={ 'id': users_table.c.user_id, 'name': users_table.c.user_name, }) -To change the names of all attributes using a prefix, use the ``column_prefix`` option. This is useful for classes which wish to add their own ``property`` accessors:: +When using :mod:`~sqlalchemy.ext.declarative`, the above configuration is more +succinct - place the full column name in the :class:`.Column` definition, +using the desired attribute name in the class definition:: + + from sqlalchemy.ext.declarative import declarative_base + Base = declarative_base() + + class User(Base): + __tablename__ = 'user' + id = Column('user_id', Integer, primary_key=True) + name = Column('user_name', String(50)) + +To change the names of all attributes using a prefix, use the +``column_prefix`` option. This is useful for some schemes that would like +to declare alternate attributes:: mapper(User, users_table, column_prefix='_') -The above will place attribute names such as ``_user_id``, ``_user_name``, ``_password`` etc. on the mapped ``User`` class. +The above will place attribute names such as ``_user_id``, ``_user_name``, +``_password`` etc. on the mapped ``User`` class. + -To place multiple columns which are known to be "synonymous" based on foreign key relationship or join condition into the same mapped attribute, put them together using a list, as below where we map to a :class:`~sqlalchemy.sql.expression.Join`:: +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, put them +together using a list, as below where we map to a :func:`~.expression.join`:: + + from sqlalchemy.sql import join + # join users and addresses - usersaddresses = sql.join(users_table, addresses_table, \ + usersaddresses = join(users_table, addresses_table, \ users_table.c.user_id == addresses_table.c.user_id) + # user_id columns are equated under the 'user_id' attribute mapper(User, usersaddresses, properties={ 'id':[users_table.c.user_id, addresses_table.c.user_id], }) +For further examples on this particular use case, see :ref:`maptojoin`. + Deferred Column Loading ------------------------ -This feature allows particular columns of a table to not be loaded by default, instead being loaded later on when first referenced. It is essentially "column-level lazy loading". This feature is useful when one wants to avoid loading a large text or binary field into memory when it's not needed. Individual columns can be lazy loaded by themselves or placed into groups that lazy-load together:: +This feature allows particular columns of a table to not be loaded by default, +instead being loaded later on when first referenced. It is essentially +"column-level lazy loading". This feature is useful when one wants to avoid +loading a large text or binary field into memory when it's not needed. +Individual columns can be lazy loaded by themselves or placed into groups that +lazy-load together:: book_excerpts = Table('books', db, Column('book_id', Integer, primary_key=True), @@ -91,17 +188,19 @@ Deferred columns can be placed into groups so that they load together:: 'photo3': deferred(book_excerpts.c.photo3, group='photos') }) -You can defer or undefer columns at the :class:`~sqlalchemy.orm.query.Query` level using the ``defer`` and ``undefer`` options:: +You can defer or undefer columns at the :class:`~sqlalchemy.orm.query.Query` level using the :func:`.defer` and :func:`.undefer` query options:: query = session.query(Book) query.options(defer('summary')).all() query.options(undefer('excerpt')).all() -And an entire "deferred group", i.e. which uses the ``group`` keyword argument to :func:`~sqlalchemy.orm.deferred()`, can be undeferred using :func:`~sqlalchemy.orm.undefer_group()`, sending in the group name:: +And an entire "deferred group", i.e. which uses the ``group`` keyword argument to :func:`~sqlalchemy.orm.deferred()`, can be undeferred using :func:`.undefer_group()`, sending in the group name:: query = session.query(Book) query.options(undefer_group('photos')).all() +.. _mapper_sql_expressions: + SQL Expressions as Mapped Attributes ------------------------------------- @@ -129,15 +228,15 @@ Correlated subqueries may be used as well: Changing Attribute Behavior ---------------------------- - Simple Validators ~~~~~~~~~~~~~~~~~~ - -A quick way to add a "validation" routine to an attribute is to use the :func:`~sqlalchemy.orm.validates` decorator. This is a shortcut for using the :class:`sqlalchemy.orm.util.Validator` attribute extension with individual column or relationship based attributes. An attribute validator can raise an exception, halting the process of mutating the attribute's value, or can change the given value into something different. Validators, like all attribute extensions, are only called by normal userland code; they are not issued when the ORM is populating the object. +A quick way to add a "validation" routine to an attribute is to use the :func:`~sqlalchemy.orm.validates` decorator. An attribute validator can raise an exception, halting the process of mutating the attribute's value, or can change the given value into something different. Validators, like all attribute extensions, are only called by normal userland code; they are not issued when the ORM is populating the object. .. sourcecode:: python+sql - + + from sqlalchemy.orm import validates + addresses_table = Table('addresses', metadata, Column('id', Integer, primary_key=True), Column('email', String) @@ -202,51 +301,77 @@ The ``email`` attribute is now usable in the same way as any other mapped attrib If the mapped class does not provide a property, the :func:`~sqlalchemy.orm.synonym` construct will create a default getter/setter object automatically. +To use synonyms with :mod:`~sqlalchemy.ext.declarative`, see the section +:ref:`declarative_synonyms`. + .. _custom_comparators: Custom Comparators ~~~~~~~~~~~~~~~~~~~ -The expressions returned by comparison operations, such as ``User.name=='ed'``, can be customized. SQLAlchemy attributes generate these expressions using :class:`~sqlalchemy.orm.interfaces.PropComparator` objects, which provide common Python expression overrides including ``__eq__()``, ``__ne__()``, ``__lt__()``, and so on. Any mapped attribute can be passed a user-defined class via the ``comparator_factory`` keyword argument, which subclasses the appropriate :class:`~sqlalchemy.orm.interfaces.PropComparator` in use, which can provide any or all of these methods: - -.. sourcecode:: python+sql +The expressions returned by comparison operations, such as +``User.name=='ed'``, can be customized, by implementing an object that +explicitly defines each comparison method needed. This is a relatively rare +use case. For most needs, the approach in :ref:`mapper_sql_expressions` will +often suffice, or alternatively a scheme like that of the +:mod:`.derived_attributes` example. Those approaches should be tried first +before resorting to custom comparison objects. + +Each of :func:`.column_property`, :func:`~.composite`, :func:`.relationship`, and :func:`.comparable_property` accept an argument called ``comparator_factory``. A subclass of :class:`.PropComparator` can be +provided for this argument, which can then reimplement basic Python comparison +methods such as ``__eq__()``, ``__ne__()``, ``__lt__()``, and so on. See +each of those functions for subclassing guidelines, as it's usually best to +subclass the :class:`.PropComparator` subclass used by that type of +property, so that all methods remain implemented. For example, to +allow a column-mapped attribute to do case-insensitive +comparison:: from sqlalchemy.orm.properties import ColumnProperty + from sqlalchemy.sql import func + class MyComparator(ColumnProperty.Comparator): def __eq__(self, other): return func.lower(self.__clause_element__()) == func.lower(other) mapper(EmailAddress, addresses_table, properties={ - 'email':column_property(addresses_table.c.email, comparator_factory=MyComparator) + 'email':column_property(addresses_table.c.email, + comparator_factory=MyComparator) }) -Above, comparisons on the ``email`` column are wrapped in the SQL lower() function to produce case-insensitive matching: - -.. sourcecode:: python+sql +Above, comparisons on the ``email`` column are wrapped in the SQL lower() function to produce case-insensitive matching:: >>> str(EmailAddress.email == 'SomeAddress@foo.com') lower(addresses.email) = lower(:lower_1) -The ``__clause_element__()`` method is provided by the base ``Comparator`` class in use, and represents the SQL element which best matches what this attribute represents. For a column-based attribute, it's the mapped column. For a composite attribute, it's a :class:`~sqlalchemy.sql.expression.ClauseList` consisting of each column represented. For a relationship, it's the table mapped by the local mapper (not the remote mapper). ``__clause_element__()`` should be honored by the custom comparator class in most cases since the resulting element will be applied any translations which are in effect, such as the correctly aliased member when using an ``aliased()`` construct or certain :func:`~sqlalchemy.orm.query.Query.with_polymorphic` scenarios. - -There are four kinds of ``Comparator`` classes which may be subclassed, as according to the type of mapper property configured: - - * :func:`~sqlalchemy.orm.column_property` attribute - ``sqlalchemy.orm.properties.ColumnProperty.Comparator`` - * :func:`~sqlalchemy.orm.composite` attribute - ``sqlalchemy.orm.properties.CompositeProperty.Comparator`` - * :func:`~sqlalchemy.orm.relationship` attribute - ``sqlalchemy.orm.properties.RelationshipProperty.Comparator`` - * :func:`~sqlalchemy.orm.comparable_property` attribute - ``sqlalchemy.orm.interfaces.PropComparator`` +In contrast, a similar effect is more easily accomplished, although +with less control of it's behavior, using a column-mapped expression:: + + from sqlachemy.orm import column_property + from sqlalchemy.sql import func + + mapper(EmailAddress, addresses_table, properties={ + 'email':column_property(func.lower(addresses_table.c.email)) + }) -When using :func:`~sqlalchemy.orm.comparable_property`, which is a mapper property that isn't tied to any column or mapped table, the ``__clause_element__()`` method of :class:`~sqlalchemy.orm.interfaces.PropComparator` should also be implemented. +In the above case, the "email" attribute will be rendered as ``lower(email)`` +in all queries, including in the columns clause of the SELECT statement. +This means the value of "email" will be loaded as lower case, not just in +comparisons. It's up to the user to decide if the finer-grained control +but more upfront work of a custom :class:`.PropComparator` is necessary. -The ``comparator_factory`` argument is accepted by all ``MapperProperty``-producing functions: :func:`~sqlalchemy.orm.column_property`, :func:`~sqlalchemy.orm.composite`, :func:`~sqlalchemy.orm.comparable_property`, :func:`~sqlalchemy.orm.synonym`, :func:`~sqlalchemy.orm.relationship`, :func:`~sqlalchemy.orm.backref`, :func:`~sqlalchemy.orm.deferred`, and :func:`~sqlalchemy.orm.dynamic_loader`. +.. _mapper_composite: Composite Column Types ----------------------- -Sets of columns can be associated with a single datatype. The ORM treats the group of columns like a single column which accepts and returns objects using the custom datatype you provide. In this example, we'll create a table ``vertices`` which stores a pair of x/y coordinates, and a custom datatype ``Point`` which is a composite type of an x and y column: +Sets of columns can be associated with a single user-defined datatype. The ORM provides a single attribute which represents the group of columns +using the class you provide. -.. sourcecode:: python+sql +A simple example represents pairs of columns as a "Point" object. +Starting with a table that represents two points as x1/y1 and x2/y2:: + from sqlalchemy import Table, Column + vertices = Table('vertices', metadata, Column('id', Integer, primary_key=True), Column('x1', Integer), @@ -255,25 +380,39 @@ Sets of columns can be associated with a single datatype. The ORM treats the gr Column('y2', Integer), ) -The requirements for the custom datatype class are that it have a constructor which accepts positional arguments corresponding to its column format, and also provides a method ``__composite_values__()`` which returns the state of the object as a list or tuple, in order of its column-based attributes. It also should supply adequate ``__eq__()`` and ``__ne__()`` methods which test the equality of two instances, and may optionally provide a ``__set_composite_values__`` method which is used to set internal state in some cases (typically when default values have been generated during a flush):: +We create a new class, ``Point``, that will represent each x/y as a +pair:: class Point(object): def __init__(self, x, y): self.x = x self.y = y def __composite_values__(self): - return [self.x, self.y] + return self.x, self.y def __set_composite_values__(self, x, y): self.x = x self.y = y def __eq__(self, other): - return other.x == self.x and other.y == self.y + return other is not None and \ + other.x == self.x and \ + other.y == self.y def __ne__(self, other): return not self.__eq__(other) -If ``__set_composite_values__()`` is not provided, the names of the mapped columns are taken as the names of attributes on the object, and ``setattr()`` is used to set data. +The requirements for the custom datatype class are that it have a +constructor which accepts positional arguments corresponding to its column +format, and also provides a method ``__composite_values__()`` which +returns the state of the object as a list or tuple, in order of its +column-based attributes. It also should supply adequate ``__eq__()`` and +``__ne__()`` methods which test the equality of two instances. + +The ``__set_composite_values__()`` method is optional. If it's not +provided, the names of the mapped columns are taken as the names of +attributes on the object, and ``setattr()`` is used to set data. + +The :func:`.composite` function is then used in the mapping:: -Setting up the mapping uses the :func:`~sqlalchemy.orm.composite()` function:: + from sqlalchemy.orm import mapper, composite class Vertex(object): pass @@ -283,30 +422,37 @@ Setting up the mapping uses the :func:`~sqlalchemy.orm.composite()` function:: 'end': composite(Point, vertices.c.x2, vertices.c.y2) }) -We can now use the ``Vertex`` instances as well as querying as though the ``start`` and ``end`` attributes are regular scalar attributes:: +We can now use the ``Vertex`` instances as well as querying as though the +``start`` and ``end`` attributes are regular scalar attributes:: session = Session() v = Vertex(Point(3, 4), Point(5, 6)) - session.save(v) + session.add(v) v2 = session.query(Vertex).filter(Vertex.start == Point(3, 4)) -The "equals" comparison operation by default produces an AND of all corresponding columns equated to one another. This can be changed using the ``comparator_factory``, described in :ref:`custom_comparators`:: +The "equals" comparison operation by default produces an AND of all +corresponding columns equated to one another. This can be changed using +the ``comparator_factory``, described in :ref:`custom_comparators`. +Below we illustrate the "greater than" operator, implementing +the same expression that the base "greater than" does:: from sqlalchemy.orm.properties import CompositeProperty from sqlalchemy import sql class PointComparator(CompositeProperty.Comparator): def __gt__(self, other): - """define the 'greater than' operation""" + """redefine the 'greater than' operation""" return sql.and_(*[a>b for a, b in zip(self.__clause_element__().clauses, other.__composite_values__())]) maper(Vertex, vertices, properties={ - 'start': composite(Point, vertices.c.x1, vertices.c.y1, comparator_factory=PointComparator), - 'end': composite(Point, vertices.c.x2, vertices.c.y2, comparator_factory=PointComparator) + 'start': composite(Point, vertices.c.x1, vertices.c.y1, + comparator_factory=PointComparator), + 'end': composite(Point, vertices.c.x2, vertices.c.y2, + comparator_factory=PointComparator) }) Controlling Ordering @@ -744,6 +890,8 @@ The big limitation with concrete table inheritance is that :func:`~sqlalchemy.or }) +.. _maptojoin: + Mapping a Class against Multiple Tables ---------------------------------------- @@ -751,7 +899,8 @@ Mappers can be constructed against arbitrary relational units (called ``Selectab .. sourcecode:: python+sql - # a class + from sqlalchemy.sql import join + class AddressUser(object): pass @@ -768,6 +917,8 @@ A second example: .. sourcecode:: python+sql + from sqlalchemy.sql import join + # many-to-many join on an association table j = join(users_table, userkeywords, users_table.c.user_id==userkeywords.c.user_id).join(keywords, @@ -789,11 +940,12 @@ In both examples above, "composite" columns were added as properties to the mapp Mapping a Class against Arbitrary Selects ------------------------------------------ - Similar to mapping against a join, a plain select() object can be used with a mapper as well. Below, an example select which contains two aggregate functions and a group_by is mapped to a class: .. sourcecode:: python+sql + from sqlalchemy.sql import select + s = select([customers, func.count(orders).label('order_count'), func.max(orders.price).label('highest_order')], diff --git a/doc/build/reference/orm/query.rst b/doc/build/reference/orm/query.rst index 98ebdee59f..931bbf064c 100644 --- a/doc/build/reference/orm/query.rst +++ b/doc/build/reference/orm/query.rst @@ -8,7 +8,7 @@ Querying The Query Object ---------------- -:class:`~sqlalchemy.orm.query.Query` is produced in terms of a given :class:`~sqlalchemy.orm.session.Session`, using the :func:`~sqlalchemy.orm.query.Query.query` function:: +:class:`~.Query` is produced in terms of a given :class:`~.Session`, using the :func:`~.Query.query` function:: q = session.query(SomeMappedClass) @@ -21,7 +21,9 @@ Following is the full interface for the :class:`Query` object. ORM-Specific Query Constructs ----------------------------- -.. autoclass:: aliased +.. class:: aliased + +The public name of the :class:`.AliasedClass` class. .. autoclass:: sqlalchemy.orm.util.AliasedClass @@ -58,3 +60,5 @@ Options which are passed to ``query.options()``, to affect the behavior of loadi .. autofunction:: undefer +.. autofunction:: undefer_group + diff --git a/lib/sqlalchemy/ext/declarative.py b/lib/sqlalchemy/ext/declarative.py index 909fb5cbaf..3370a764ca 100755 --- a/lib/sqlalchemy/ext/declarative.py +++ b/lib/sqlalchemy/ext/declarative.py @@ -180,6 +180,8 @@ a many-to-many relationship, since the ORM may issue duplicate INSERT and DELETE statements. +.. _declarative_synonyms: + Defining Synonyms ================= diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py index 4c9efb714b..77281ee567 100644 --- a/lib/sqlalchemy/orm/__init__.py +++ b/lib/sqlalchemy/orm/__init__.py @@ -519,27 +519,27 @@ def column_property(*args, **kwargs): Columns that aren't present in the mapper's selectable won't be persisted by the mapper and are effectively "read-only" attributes. - \*cols + :param \*cols: list of Column objects to be mapped. - comparator_factory - a class which extends ``sqlalchemy.orm.properties.ColumnProperty.Comparator`` + :param comparator_factory: + a class which extends :class:`sqlalchemy.orm.properties.ColumnProperty.Comparator` which provides custom SQL clause generation for comparison operations. - group - a group name for this property when marked as deferred. + :param group: + a group name for this property when marked as deferred. - deferred + :param deferred: when True, the column property is "deferred", meaning that it does not load immediately, and is instead loaded when the attribute is first accessed on an instance. See also :func:`~sqlalchemy.orm.deferred`. - doc + :param doc: optional string that will be applied as the doc on the class-bound descriptor. - extension + :param extension: an :class:`~sqlalchemy.orm.interfaces.AttributeExtension` instance, or list of extensions, which will be prepended to the list of attribute listeners for the resulting descriptor placed on the class. @@ -553,70 +553,33 @@ def column_property(*args, **kwargs): def composite(class_, *cols, **kwargs): """Return a composite column-based property for use with a Mapper. - - This is very much like a column-based property except the given class is - used to represent "composite" values composed of one or more columns. - - The class must implement a constructor with positional arguments matching - the order of columns supplied here, as well as a __composite_values__() - method which returns values in the same order. - - A simple example is representing separate two columns in a table as a - single, first-class "Point" object:: - - class Point(object): - def __init__(self, x, y): - self.x = x - self.y = y - def __composite_values__(self): - return self.x, self.y - def __eq__(self, other): - return other is not None and self.x == other.x and self.y == other.y - - # and then in the mapping: - ... composite(Point, mytable.c.x, mytable.c.y) ... - - The composite object may have its attributes populated based on the names - of the mapped columns. To override the way internal state is set, - additionally implement ``__set_composite_values__``:: - - class Point(object): - def __init__(self, x, y): - self.some_x = x - self.some_y = y - def __composite_values__(self): - return self.some_x, self.some_y - def __set_composite_values__(self, x, y): - self.some_x = x - self.some_y = y - def __eq__(self, other): - return other is not None and self.some_x == other.x and self.some_y == other.y - - Arguments are: - - class\_ + + See the mapping documention section :ref:`mapper_composite` for a full + usage example. + + :param class\_: The "composite type" class. - \*cols + :param \*cols: List of Column objects to be mapped. - group + :param group: A group name for this property when marked as deferred. - deferred + :param deferred: When True, the column property is "deferred", meaning that it does not load immediately, and is instead loaded when the attribute is first accessed on an instance. See also :func:`~sqlalchemy.orm.deferred`. - comparator_factory - a class which extends :class:`~sqlalchemy.orm.properties.CompositeProperty.Comparator` + :param comparator_factory: + a class which extends :class:`sqlalchemy.orm.properties.CompositeProperty.Comparator` which provides custom SQL clause generation for comparison operations. - doc + :param doc: optional string that will be applied as the doc on the class-bound descriptor. - extension + :param extension: an :class:`~sqlalchemy.orm.interfaces.AttributeExtension` instance, or list of extensions, which will be prepended to the list of attribute listeners for the resulting descriptor placed on the class. @@ -869,25 +832,34 @@ def comparable_property(comparator_factory, descriptor=None): (__eq__) to the supplied comparator but proxies everything else through to the original descriptor:: + from sqlalchemy.orm import mapper, comparable_property + from sqlalchemy.orm.interfaces import PropComparator + from sqlalchemy.sql import func + class MyClass(object): @property def myprop(self): return 'foo' - class MyComparator(sqlalchemy.orm.interfaces.PropComparator): + class MyComparator(PropComparator): def __eq__(self, other): - .... + return func.lower(other) == foo - mapper(MyClass, mytable, properties=dict( - 'myprop': comparable_property(MyComparator))) + mapper(MyClass, mytable, properties={ + 'myprop': comparable_property(MyComparator)}) Used with the ``properties`` dictionary sent to :func:`~sqlalchemy.orm.mapper`. - - comparator_factory + + Note that :func:`comparable_property` is usually not needed for basic + needs. The recipe at :mod:`.derived_attributes` offers a simpler pure-Python + method of achieving a similar result using class-bound attributes with + SQLAlchemy expression constructs. + + :param comparator_factory: A PropComparator subclass or factory that defines operator behavior for this property. - descriptor + :param descriptor: Optional when used in a ``properties={}`` declaration. The Python descriptor or property to layer comparison behavior on top of. diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 3f8300a097..cc6d15a74b 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -54,7 +54,20 @@ def _generative(*assertions): return generate class Query(object): - """ORM-level SQL construction object.""" + """ORM-level SQL construction object. + + :class:`.Query` is the source of all SELECT statements generated by the + ORM, both those formulated by end-user query operations as well as by + high level internal operations such as related collection loading. It + features a generative interface whereby successive calls return a new + :class:`.Query` object, a copy of the former with additional + criteria and options associated with it. + + :class:`.Query` objects are normally initially generated using the + :meth:`~.Session.query` method of :class:`.Session`. For a full walkthrough + of :class:`.Query` usage, see the :ref:`ormtutorial_toplevel`. + + """ _enable_eagerloads = True _enable_assertions = True @@ -732,10 +745,14 @@ class Query(object): # given arg is a FROM clause self._setup_aliasizers(self._entities[l:]) - @util.pending_deprecation("add_column() superceded by add_columns()") + @util.pending_deprecation("0.7", "add_column() is superceded by add_columns()", False) def add_column(self, column): - """Add a column expression to the list of result columns - to be returned.""" + """Add a column expression to the list of result columns to be returned. + + Pending deprecation: :meth:`.add_column` will be superceded by + :meth:`.add_columns`. + + """ return self.add_columns(column) diff --git a/lib/sqlalchemy/util.py b/lib/sqlalchemy/util.py index ae45e17036..d274e36350 100644 --- a/lib/sqlalchemy/util.py +++ b/lib/sqlalchemy/util.py @@ -1668,7 +1668,7 @@ def pending_deprecation(version, message=None, """ if add_deprecation_to_docstring: - header = message is not None and message or 'Deprecated.' + header = ".. deprecated:: %s (pending) %s" % (version, (message or '')) else: header = None