summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2012-10-26 19:25:23 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2012-10-26 19:25:23 -0400
commitd68bf88fa9147591a245f23562154d9562e4b744 (patch)
tree609931e247dcff1b5f0012b31020afc190e8092b
parent7f043a9666eecdecc54fe779ffdd50a7d5bb0086 (diff)
downloadsqlalchemy-d68bf88fa9147591a245f23562154d9562e4b744.tar.gz
- add class_ to AliasedInsp
- redefine inspect(Class.attrname).parent to be always an inspectable target; either Mapper or AliasedInsp - add most major features to 08 migration, document, link
-rw-r--r--doc/build/changelog/migration_08.rst577
-rw-r--r--doc/build/conf.py5
-rw-r--r--doc/build/core/inspection.rst10
-rw-r--r--doc/build/core/tutorial.rst2
-rw-r--r--doc/build/core/types.rst2
-rw-r--r--doc/build/glossary.rst59
-rw-r--r--doc/build/index.rst1
-rw-r--r--doc/build/orm/extensions/declarative.rst4
-rw-r--r--doc/build/orm/inheritance.rst2
-rw-r--r--doc/build/orm/internals.rst8
-rw-r--r--doc/build/orm/session.rst2
-rw-r--r--lib/sqlalchemy/orm/attributes.py21
-rw-r--r--lib/sqlalchemy/orm/properties.py14
-rw-r--r--lib/sqlalchemy/orm/query.py1
-rw-r--r--lib/sqlalchemy/orm/util.py9
-rw-r--r--lib/sqlalchemy/sql/expression.py17
-rw-r--r--test/orm/test_inspect.py10
17 files changed, 514 insertions, 230 deletions
diff --git a/doc/build/changelog/migration_08.rst b/doc/build/changelog/migration_08.rst
index a02c15f39..33264860c 100644
--- a/doc/build/changelog/migration_08.rst
+++ b/doc/build/changelog/migration_08.rst
@@ -49,8 +49,8 @@ SQLAlchemy will eventually drop 2.5 support as well - when
``2to3`` tool and maintaining a source base that works with
Python 2 and 3 at the same time.
-New Features
-============
+New ORM Features
+================
.. _feature_relationship_08:
@@ -179,8 +179,10 @@ entities. The new system includes these features:
:ticket:`1401` :ticket:`610`
-New Class Inspection System
----------------------------
+.. _feature_orminspection_08:
+
+New Class/Object Inspection System
+----------------------------------
Lots of SQLAlchemy users are writing systems that require
the ability to inspect the attributes of a mapped class,
@@ -207,131 +209,111 @@ as :class:`.Mapper`, :class:`.InstanceState`, and :class:`.MapperProperty`:
::
- class User(Base):
- __tablename__ = 'user'
-
- id = Column(Integer, primary_key=True)
- name = Column(String)
- name_syn = synonym(name)
- addresses = relationship(Address)
+ >>> class User(Base):
+ ... __tablename__ = 'user'
+ ... id = Column(Integer, primary_key=True)
+ ... name = Column(String)
+ ... name_syn = synonym(name)
+ ... addresses = relationship("Address")
+ ...
- # universal entry point is inspect()
+ >>> # universal entry point is inspect()
>>> b = inspect(User)
- # column collection
- >>> b.columns
- [<id column>, <name column>]
+ >>> # b in this case is the Mapper
+ >>> b
+ <Mapper at 0x101521950; User>
- # its a ColumnCollection
+ >>> # Column namespace
>>> b.columns.id
- <id column>
+ Column('id', Integer(), table=<user>, primary_key=True, nullable=False)
- # i.e. from mapper
+ >>> # mapper's perspective of the primary key
>>> b.primary_key
- (<id column>, )
+ (Column('id', Integer(), table=<user>, primary_key=True, nullable=False),)
- # ColumnProperty
- >>> b.attr.id.columns
- [<id column>]
+ >>> # MapperProperties available from .attrs
+ >>> b.attrs.keys()
+ ['name_syn', 'addresses', 'id', 'name']
- # get only column attributes
- >>> b.column_attrs
- [<id prop>, <name prop>]
+ >>> # .column_attrs, .relationships, etc. filter this collection
+ >>> b.column_attrs.keys()
+ ['id', 'name']
- # its a namespace
- >>> b.column_attrs.id
- <id prop>
+ >>> list(b.relationships)
+ [<sqlalchemy.orm.properties.RelationshipProperty object at 0x1015212d0>]
- # get only relationships
- >>> b.relationships
- [<addresses prop>]
+ >>> # they are also namespaces
+ >>> b.column_attrs.id
+ <sqlalchemy.orm.properties.ColumnProperty object at 0x101525090>
- # its a namespace
>>> b.relationships.addresses
- <addresses prop>
+ <sqlalchemy.orm.properties.RelationshipProperty object at 0x1015212d0>
- # point inspect() at a class level attribute,
- # basically returns ".property"
+ >>> # point inspect() at a mapped, class level attribute,
+ >>> # returns the attribute itself
>>> b = inspect(User.addresses)
>>> b
- <addresses prop>
+ <sqlalchemy.orm.attributes.InstrumentedAttribute object at 0x101521fd0>
- # mapper
+ >>> # From here we can get the mapper:
>>> b.mapper
- <Address mapper>
+ <Mapper at 0x101525810; Address>
- # None columns collection, just like columnprop has empty mapper
- >>> b.columns
- None
-
- # the parent
+ >>> # the parent inspector, in this case a mapper
>>> b.parent
- <User mapper>
-
- # __clause_element__()
- >>> b.expression
- User.id==Address.user_id
+ <Mapper at 0x101521950; User>
- >>> inspect(User.id).expression
- <id column with ORM annotations>
+ >>> # an expression
+ >>> print b.expression
+ "user".id = address.user_id
- # inspect works on instances !
+ >>> # inspect works on instances
>>> u1 = User(id=3, name='x')
>>> b = inspect(u1)
- # what's b here ? probably InstanceState
+ >>> # it returns the InstanceState
>>> b
- <InstanceState>
+ <sqlalchemy.orm.state.InstanceState object at 0x10152bed0>
- >>> b.attr.keys()
- ['id', 'name', 'name_syn', 'addresses']
+ >>> # similar attrs accessor refers to the
+ >>> b.attrs.keys()
+ ['id', 'name_syn', 'addresses', 'name']
- # attribute interface
- >>> b.attr.id
- <magic attribute inspect thing>
+ >>> # attribute interface - from attrs, you get a state object
+ >>> b.attrs.id
+ <sqlalchemy.orm.state.AttributeState object at 0x10152bf90>
- # value
- >>> b.attr.id.value
+ >>> # this object can give you, current value...
+ >>> b.attrs.id.value
3
- # history
- >>> b.attr.id.history
- <history object>
+ >>> # ... current history
+ >>> b.attrs.id.history
+ History(added=[3], unchanged=(), deleted=())
- >>> b.attr.id.history.unchanged
- 3
-
- >>> b.attr.id.history.deleted
- None
-
- # lets assume the object is persistent
+ >>> # InstanceState can also provide session state information
+ >>> # lets assume the object is persistent
>>> s = Session()
>>> s.add(u1)
>>> s.commit()
- # big one - the primary key identity ! always
- # works in query.get()
+ >>> # now we can get primary key identity, always
+ >>> # works in query.get()
>>> b.identity
- [3]
+ (3,)
- # the mapper level key
+ >>> # the mapper level key
>>> b.identity_key
- (User, [3])
-
- >>> b.persistent
- True
+ (<class '__main__.User'>, (3,))
- >>> b.transient
- False
-
- >>> b.deleted
- False
-
- >>> b.detached
- False
+ >>> # state within the session
+ >>> b.persistent, b.transient, b.deleted, b.detached
+ (True, False, False, False)
+ >>> # owning session
>>> b.session
- <session>
+ <sqlalchemy.orm.session.Session object at 0x101701150>
.. seealso::
@@ -339,79 +321,6 @@ as :class:`.Mapper`, :class:`.InstanceState`, and :class:`.MapperProperty`:
:ticket:`2208`
-Fully extensible, type-level operator support in Core
------------------------------------------------------
-
-The Core has to date never had any system of adding support
-for new SQL operators to Column and other expression
-constructs, other than the :meth:`.ColumnOperators.op` method
-which is "just enough" to make things work. There has also
-never been any system in place for Core which allows the
-behavior of existing operators to be overridden. Up until
-now, the only way operators could be flexibly redefined was
-in the ORM layer, using :func:`.column_property` given a
-``comparator_factory`` argument. Third party libraries
-like GeoAlchemy therefore were forced to be ORM-centric and
-rely upon an array of hacks to apply new opertions as well
-as to get them to propagate correctly.
-
-The new operator system in Core adds the one hook that's
-been missing all along, which is to associate new and
-overridden operators with *types*. Since after all, it's
-not really a column, CAST operator, or SQL function that
-really drives what kinds of operations are present, it's the
-*type* of the expression. The implementation details are
-minimal - only a few extra methods are added to the core
-:class:`.ColumnElement` type so that it consults it's
-:class:`.TypeEngine` object for an optional set of operators.
-New or revised operations can be associated with any type,
-either via subclassing of an existing type, by using
-:class:`.TypeDecorator`, or "globally across-the-board" by
-attaching a new :class:`.TypeEngine.Comparator` object to an existing type
-class.
-
-For example, to add logarithm support to :class:`.Numeric` types:
-
-::
-
-
- from sqlalchemy.types import Numeric
- from sqlalchemy.sql import func
-
- class CustomNumeric(Numeric):
- class comparator_factory(Numeric.Comparator):
- def log(self, other):
- return func.log(self.expr, other)
-
-The new type is usable like any other type:
-
-::
-
-
- data = Table('data', metadata,
- Column('id', Integer, primary_key=True),
- Column('x', CustomNumeric(10, 5)),
- Column('y', CustomNumeric(10, 5))
- )
-
- stmt = select([data.c.x.log(data.c.y)]).where(data.c.x.log(2) < value)
- print conn.execute(stmt).fetchall()
-
-
-New features which should come from this immediately are
-support for Postgresql's HSTORE type, which is ready to go
-in a separate library which may be merged, as well as all
-the special operations associated with Postgresql's ARRAY
-type. It also paves the way for existing types to acquire
-lots more operators that are specific to those types, such
-as more string, integer and date operators.
-
- .. seealso::
-
- `Postgresql HSTORE <https://bitbucket.org/audriusk/hstore>`_ - support for HSTORE in SQLAlchemy
-
-:ticket:`2547`
-
New with_polymorphic() feature, can be used anywhere
----------------------------------------------------
@@ -451,7 +360,6 @@ constructs; also works with most relationship functions like
::
-
# use eager loading in conjunction with with_polymorphic targets
Job_P = with_polymorphic(Job, SubJob, aliased=True)
q = s.query(DataContainer).\
@@ -471,8 +379,13 @@ constructs; also works with most relationship functions like
q = s.query(Job).join(DataContainer.jobs).\
filter(
DataContainer.jobs.of_type(Job_A).\
- any(and_(Job_A.id < Job.id, Job_A.type=='fred'))
+ any(and_(Job_A.id < Job.id, Job_A.type=='fred')
+ )
+ )
+.. seealso::
+
+ :ref:`of_type`
:ticket:`2438` :ticket:`1106`
@@ -511,8 +424,237 @@ in one step:
ReflectedOne.prepare(engine_one)
ReflectedTwo.prepare(engine_two)
+.. seealso::
+
+ :class:`.DeferredReflection`
+
:ticket:`2485`
+ORM Classes Now Accepted by Core Constructs
+-------------------------------------------
+
+While the SQL expressions used with :class:`.Query.filter`,
+such as ``User.id == 5``, have always been compatible for
+use with core constructs such as :func:`.select`, the mapped
+class itself would not be recognized when passed to :func:`.select`,
+:meth:`.Select.select_from`, or :meth:`.Select.correlate`.
+A new SQL registration system allows a mapped class to be
+accepted as a FROM clause within the core::
+
+ from sqlalchemy import select
+
+ stmt = select([User]).where(User.id == 5)
+
+Above, the mapped ``User`` class will expand into
+:class:`.Table` to which :class:`.User` is mapped.
+
+:ticket:`2245`
+
+Query.update() supports UPDATE..FROM
+-------------------------------------
+
+The new UPDATE..FROM mechanics work in query.update().
+Below, we emit an UPDATE against ``SomeEntity``, adding
+a FROM clause (or equivalent, depending on backend)
+against ``SomeOtherEntity``::
+
+ query(SomeEntity).\
+ filter(SomeEntity.id==SomeOtherEntity.id).\
+ filter(SomeOtherEntity.foo=='bar').\
+ update({"data":"x"})
+
+In particular, updates to joined-inheritance
+entities are supported, provided the target of the UPDATE is local to the
+table being filtered on, or if the parent and child tables
+are mixed, they are joined explicitly in the query. Below,
+given ``Engineer`` as a joined subclass of ``Person``:
+
+::
+
+ query(Engineer).\
+ filter(Person.id==Engineer.id).\
+ filter(Person.name=='dilbert').\
+ update({"engineer_data":"java"})
+
+would produce:
+
+::
+
+ UPDATE engineer SET engineer_data='java' FROM person
+ WHERE person.id=engineer.id AND person.name='dilbert'
+
+:ticket:`2365`
+
+rollback() will only roll back "dirty" objects from a begin_nested()
+--------------------------------------------------------------------
+
+A behavioral change that should improve efficiency for those
+users using SAVEPOINT via ``Session.begin_nested()`` - upon
+``rollback()``, only those objects that were made dirty
+since the last flush will be expired, the rest of the
+``Session`` remains intact. This because a ROLLBACK to a
+SAVEPOINT does not terminate the containing transaction's
+isolation, so no expiry is needed except for those changes
+that were not flushed in the current transaction.
+
+:ticket:`2452`
+
+Caching Example now uses dogpile.cache
+---------------------------------------
+
+The caching example now uses `dogpile.cache <http://dogpilecache.readthedocs.org/>`_.
+Dogpile.cache is a rewrite of the caching portion
+of Beaker, featuring vastly simpler and faster operation,
+as well as support for distributed locking.
+
+.. seealso::
+
+ :mod:`dogpile_caching`
+
+:ticket:`2589`
+
+New Core Features
+==================
+
+Fully extensible, type-level operator support in Core
+-----------------------------------------------------
+
+The Core has to date never had any system of adding support
+for new SQL operators to Column and other expression
+constructs, other than the :meth:`.ColumnOperators.op` method
+which is "just enough" to make things work. There has also
+never been any system in place for Core which allows the
+behavior of existing operators to be overridden. Up until
+now, the only way operators could be flexibly redefined was
+in the ORM layer, using :func:`.column_property` given a
+``comparator_factory`` argument. Third party libraries
+like GeoAlchemy therefore were forced to be ORM-centric and
+rely upon an array of hacks to apply new opertions as well
+as to get them to propagate correctly.
+
+The new operator system in Core adds the one hook that's
+been missing all along, which is to associate new and
+overridden operators with *types*. Since after all, it's
+not really a column, CAST operator, or SQL function that
+really drives what kinds of operations are present, it's the
+*type* of the expression. The implementation details are
+minimal - only a few extra methods are added to the core
+:class:`.ColumnElement` type so that it consults it's
+:class:`.TypeEngine` object for an optional set of operators.
+New or revised operations can be associated with any type,
+either via subclassing of an existing type, by using
+:class:`.TypeDecorator`, or "globally across-the-board" by
+attaching a new :class:`.TypeEngine.Comparator` object to an existing type
+class.
+
+For example, to add logarithm support to :class:`.Numeric` types:
+
+::
+
+
+ from sqlalchemy.types import Numeric
+ from sqlalchemy.sql import func
+
+ class CustomNumeric(Numeric):
+ class comparator_factory(Numeric.Comparator):
+ def log(self, other):
+ return func.log(self.expr, other)
+
+The new type is usable like any other type:
+
+::
+
+
+ data = Table('data', metadata,
+ Column('id', Integer, primary_key=True),
+ Column('x', CustomNumeric(10, 5)),
+ Column('y', CustomNumeric(10, 5))
+ )
+
+ stmt = select([data.c.x.log(data.c.y)]).where(data.c.x.log(2) < value)
+ print conn.execute(stmt).fetchall()
+
+
+New features which should come from this immediately are
+support for Postgresql's HSTORE type, which is ready to go
+in a separate library which may be merged, as well as all
+the special operations associated with Postgresql's ARRAY
+type. It also paves the way for existing types to acquire
+lots more operators that are specific to those types, such
+as more string, integer and date operators.
+
+.. seealso::
+
+ :ref:`types_operators`
+
+ `Postgresql HSTORE <https://bitbucket.org/audriusk/hstore>`_ - support for HSTORE in SQLAlchemy
+
+:ticket:`2547`
+
+Type Expressions
+-----------------
+
+SQL expressions can now be associated with types. Historically,
+:class:`.TypeEngine` has always allowed Python-side functions which
+receive both bound parameters as well as result row values, passing
+them through a Python side conversion function on the way to/back from
+the database. The new feature allows similar
+functionality, except on the database side::
+
+ from sqlalchemy.types import String
+ from sqlalchemy import func, Table, Column, MetaData
+
+ class LowerString(String):
+ def bind_expression(self, bindvalue):
+ return func.lower(bindvalue)
+
+ def column_expression(self, col):
+ return func.lower(col)
+
+ metadata = MetaData()
+ test_table = Table(
+ 'test_table',
+ metadata,
+ Column('data', LowerString)
+ )
+
+Above, the ``LowerString`` type defines a SQL expression that will be emitted
+whenever the ``test_table.c.data`` column is rendered in the columns
+clause of a SELECT statement::
+
+ >>> print select([test_table]).where(test_table.c.data == 'HI')
+ SELECT lower(test_table.data) AS data
+ FROM test_table
+ WHERE test_table.data = lower(:data_1)
+
+This feature is also used heavily by the new release of GeoAlchemy,
+to embed PostGIS expressions inline in SQL based on type rules.
+
+.. seealso::
+
+ :ref:`types_sql_value_processing`
+
+:ticket:`1534`
+
+Core Inspection System
+-----------------------
+
+The :func:`.inspect` function introduced in :ref:`feature_orminspection_08`
+also applies to the core. Applied to an :class:`.Engine` it produces
+an :class:`.Inspector` object::
+
+ from sqlalchemy import inspect
+ from sqlalchemy import create_engine
+
+ engine = create_engine("postgresql://scott:tiger@localhost/test")
+ insp = inspect(engine)
+ print insp.get_table_names()
+
+It can also be applied to any :class:`.ClauseElement`, which returns
+the :class:`.ClauseElement` itself, such as :class:`.Table`, :class:`.Column`,
+:class:`.Select`, etc. This allows it to work fluently between Core
+and ORM constructs.
+
New, configurable DATE, TIME types for SQLite
---------------------------------------------
@@ -541,46 +683,49 @@ everything else.
)
)
+Huge thanks to Nate Dub for the sprinting on this at Pycon 2012.
-Huge thanks to Nate Dub for the sprinting on this at Pycon
-'12.
+.. seealso::
-:ticket:`2363`
+ :class:`.sqlite.DATETIME`
-Query.update() will support UPDATE..FROM
-----------------------------------------
+ :class:`.sqlite.DATE`
-Not 100% sure if this will make it in, the new UPDATE..FROM
-mechanics should work in query.update():
+ :class:`.sqlite.TIME`
-::
+:ticket:`2363`
- query(SomeEntity).\
- filter(SomeEntity.id==SomeOtherEntity.id).\
- filter(SomeOtherEntity.foo=='bar').\
- update({"data":"x"})
+New Method :meth:`.Select.correlate_except`
+-------------------------------------------
-Should also work when used against a joined-inheritance
-entity, provided the target of the UPDATE is local to the
-table being filtered on, or if the parent and child tables
-are mixed, they are joined explicitly in the query. Below,
-given ``Engineer`` as a joined subclass of ``Person``:
+:func:`.select` now has a method :meth:`.Select.correlate_except`
+which specifies "correlate on all FROM clauses except those
+specified". It can be used for mapping scenarios where
+a related subquery should correlate normally, except
+against a particular target selectable::
-::
+ class SnortEvent(Base):
+ __tablename__ = "event"
- query(Engineer).\
- filter(Person.id==Engineer.id).\
- filter(Person.name=='dilbert').\
- update({"engineer_data":"java"})
+ id = Column(Integer, primary_key=True)
+ signature = Column(Integer, ForeignKey("signature.id"))
-would produce:
+ signatures = relationship("Signature", lazy=False)
-::
+ class Signature(Base):
+ __tablename__ = "signature"
- UPDATE engineer SET engineer_data='java' FROM person
- WHERE person.id=engineer.id AND person.name='dilbert'
+ id = Column(Integer, primary_key=True)
-:ticket:`2365`
+ sig_count = column_property(
+ select([func.count('*')]).\
+ where(SnortEvent.signature == id).
+ correlate_except(SnortEvent)
+ )
+
+.. seealso::
+
+ :meth:`.Select.correlate_except`
Enhanced Postgresql ARRAY type
------------------------------
@@ -600,21 +745,53 @@ results:
# to guess how many levels deep to go
Column("my_array", postgresql.ARRAY(Integer, dimensions=2))
+.. seealso::
+
+ :class:`.postgresql.ARRAY`
+
:ticket:`2441`
-rollback() will only roll back "dirty" objects from a begin_nested()
---------------------------------------------------------------------
+"COLLATE" supported across all dialects; in particular MySQL, Postgresql, SQLite
+--------------------------------------------------------------------------------
-A behavioral change that should improve efficiency for those
-users using SAVEPOINT via ``Session.begin_nested()`` - upon
-``rollback()``, only those objects that were made dirty
-since the last flush will be expired, the rest of the
-``Session`` remains intact. This because a ROLLBACK to a
-SAVEPOINT does not terminate the containing transaction's
-isolation, so no expiry is needed except for those changes
-that were not flushed in the current transaction.
+The "collate" keyword, long accepted by the MySQL dialect, is now established
+on all :class:`.String` types and will render on any backend, including
+when features such as :meth:`.MetaData.create_all` and :func:`.cast` is used::
+
+ >>> stmt = select([cast(sometable.c.somechar, String(20, collation='utf8'))])
+ >>> print stmt
+ SELECT CAST(sometable.somechar AS VARCHAR(20) COLLATE "utf8") AS anon_1
+ FROM sometable
+
+.. seealso::
+
+ :class:`.String`
+
+:ticket:`2276`
+
+"Prefixes" now supported for :func:`.insert`, :func:`.update`, :func:`.delete`
+-------------------------------------------------------------------------------
+
+Geared towards MySQL, a "prefix" can be rendered within any of these
+statements::
+
+ stmt = table.insert().prefix_with("LOW_PRIORITY", dialect="mysql")
+
+
+ stmt = table.update().prefix_with("LOW_PRIORITY", dialect="mysql")
+
+.. seealso::
+
+ :meth:`.Insert.prefix_with`
+
+ :meth:`.Update.prefix_with`
+
+ :meth:`.Delete.prefix_with`
+
+ :meth:`.Select.prefix_with`
+
+:ticket:`2431`
-:ticket:`2452`
Behavioral Changes
==================
diff --git a/doc/build/conf.py b/doc/build/conf.py
index b89b81c0e..ca6292adc 100644
--- a/doc/build/conf.py
+++ b/doc/build/conf.py
@@ -31,8 +31,9 @@ import sqlalchemy
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = ['sphinx.ext.autodoc',
- 'sphinx.ext.doctest',
+extensions = [
+ 'sphinx.ext.autodoc',
+ # 'sphinx.ext.doctest',
'builder.autodoc_mods',
'builder.changelog',
'builder.dialect_info',
diff --git a/doc/build/core/inspection.rst b/doc/build/core/inspection.rst
index 777ee944e..6d36f1015 100644
--- a/doc/build/core/inspection.rst
+++ b/doc/build/core/inspection.rst
@@ -1,4 +1,5 @@
.. _core_inspection_toplevel:
+.. _inspection_toplevel:
Runtime Inpection API
=====================
@@ -25,8 +26,11 @@ Following is a listing of all inspection targets.
* ``type`` (i.e. a class) - a class given will be checked by the ORM for a
mapping - if so, a :class:`.Mapper` for that class is returned.
* mapped attribute - passing a mapped attribute to :func:`.inspect`, such
- as ``inspect(MyClass.some_attribute)``, returns a :class:`.MapperProperty`
- object, which usually is an instance of :class:`.ColumnProperty`
- or :class:`.RelationshipProperty`.
+ as ``inspect(MyClass.some_attribute)``, returns a :class:`.QueryableAttribute`
+ object, which is the :term:`descriptor` associated with a mapped class.
+ This descriptor refers to a :class:`.MapperProperty`, which is usually
+ an instance of :class:`.ColumnProperty`
+ or :class:`.RelationshipProperty`, via its :attr:`.QueryableAttribute.property`
+ attribute.
* :class:`.AliasedClass` - returns an :class:`.AliasedInsp` object.
diff --git a/doc/build/core/tutorial.rst b/doc/build/core/tutorial.rst
index e6e3dc689..e9ed5c27a 100644
--- a/doc/build/core/tutorial.rst
+++ b/doc/build/core/tutorial.rst
@@ -1310,6 +1310,8 @@ well:
()
{stop}[(u'jack', 2), (u'wendy', 2), (u'fred', 0), (u'mary', 0)]
+.. _correlated_subqueries:
+
Correlated Subqueries
---------------------
diff --git a/doc/build/core/types.rst b/doc/build/core/types.rst
index 2838b722f..745752f0e 100644
--- a/doc/build/core/types.rst
+++ b/doc/build/core/types.rst
@@ -753,8 +753,6 @@ Base Type API
:members:
:show-inheritance:
- .. autoclass:: TypeEngine.Comparator
- .. automethod:: _adapt_expression
.. autoclass:: Concatenable
:members:
diff --git a/doc/build/glossary.rst b/doc/build/glossary.rst
index 8c40fcec0..b47ae7131 100644
--- a/doc/build/glossary.rst
+++ b/doc/build/glossary.rst
@@ -12,6 +12,65 @@ Glossary
.. glossary::
+ descriptor
+ In Python, a descriptor is an object attribute with “binding behavior”, one whose attribute access has been overridden by methods in the `descriptor protocol <http://docs.python.org/howto/descriptor.html>`_.
+ Those methods are __get__(), __set__(), and __delete__(). If any of those methods are defined
+ for an object, it is said to be a descriptor.
+
+ In SQLAlchemy, descriptors are used heavily in order to provide attribute behavior
+ on mapped classes. When a class is mapped as such::
+
+ class MyClass(Base):
+ __tablename__ = 'foo'
+
+ id = Column(Integer, primary_key=True)
+ data = Column(String)
+
+ The ``MyClass`` class will be :term:`mapped` when its definition
+ is complete, at which point the ``id`` and ``data`` attributes,
+ starting out as :class:`.Column` objects, will be replaced
+ by the :term:`instrumentation` system with instances
+ of :class:`.InstrumentedAttribute`, which are descriptors that
+ provide the above mentioned ``__get__()``, ``__set__()`` and
+ ``__delete__()`` methods. The :class:`.InstrumentedAttribute`
+ will generate a SQL expression when used at the class level::
+
+ >>> print MyClass.data == 5
+ data = :data_1
+
+ and at the instance level, keeps track of changes to values,
+ and also :term:`lazy loads` unloaded attributes
+ from the database::
+
+ >>> m1 = MyClass()
+ >>> m1.id = 5
+ >>> m1.data = "some data"
+
+ >>> from sqlalchemy import inspect
+ >>> inspect(m1).attrs.data.history.added
+ "some data"
+
+ instrumentation
+ instrumented
+ Instrumentation refers to the process of augmenting the functionality
+ and attribute set of a particular class. Ideally, the
+ behavior of the class should remain close to a regular
+ class, except that additional behviors and features are
+ made available. The SQLAlchemy :term:`mapping` process,
+ among other things, adds database-enabled :term:`descriptors`
+ to a mapped
+ class which each represent a particular database column
+ or relationship to a related class.
+
+ mapping
+ mapped
+ We say a class is "mapped" when it has been passed through the
+ :func:`.orm.mapper` function. This process associates the
+ class with a database table or other :term:`selectable`
+ construct, so that instances of it can be persisted
+ using a :class:`.Session` as well as loaded using a
+ :class:`.Query`.
+
release
releases
released
diff --git a/doc/build/index.rst b/doc/build/index.rst
index 0c8c19b28..cacbf570c 100644
--- a/doc/build/index.rst
+++ b/doc/build/index.rst
@@ -12,6 +12,7 @@ A high level view and getting set up.
:ref:`Overview <overview>` |
:ref:`Installation Guide <installation>` |
:doc:`Migration from 0.7 <changelog/migration_08>` |
+:doc:`Glossary <glossary>` |
:doc:`Changelog catalog <changelog/index>`
SQLAlchemy ORM
diff --git a/doc/build/orm/extensions/declarative.rst b/doc/build/orm/extensions/declarative.rst
index c2ca4eabc..35895e8df 100644
--- a/doc/build/orm/extensions/declarative.rst
+++ b/doc/build/orm/extensions/declarative.rst
@@ -4,7 +4,7 @@ Declarative
===========
.. automodule:: sqlalchemy.ext.declarative
-
+
API Reference
-------------
@@ -12,7 +12,7 @@ API Reference
.. autoclass:: declared_attr
-.. autofunction:: _declarative_constructor
+.. autofunction:: sqlalchemy.ext.declarative.api._declarative_constructor
.. autofunction:: has_inherited_table
diff --git a/doc/build/orm/inheritance.rst b/doc/build/orm/inheritance.rst
index 50d5c85d0..cf199841b 100644
--- a/doc/build/orm/inheritance.rst
+++ b/doc/build/orm/inheritance.rst
@@ -330,6 +330,8 @@ what's specified in the :meth:`.Session.query`, :meth:`.Query.filter`, or
session.query(engineer.c.id).\
filter(engineer.c.engineer_info==manager.c.manager_data)
+.. _of_type:
+
Creating Joins to Specific Subtypes
+++++++++++++++++++++++++++++++++++
diff --git a/doc/build/orm/internals.rst b/doc/build/orm/internals.rst
index 4f640b265..94f4bfc0f 100644
--- a/doc/build/orm/internals.rst
+++ b/doc/build/orm/internals.rst
@@ -28,6 +28,10 @@ sections, are listed here.
:members:
:show-inheritance:
+.. autoclass:: sqlalchemy.orm.attributes.InstrumentedAttribute
+ :members:
+ :show-inheritance:
+
.. autoclass:: sqlalchemy.orm.interfaces.MapperProperty
:members:
:show-inheritance:
@@ -47,3 +51,7 @@ sections, are listed here.
.. autoclass:: sqlalchemy.orm.query.QueryContext
:members:
:show-inheritance:
+
+.. autoclass:: sqlalchemy.orm.attributes.QueryableAttribute
+ :members:
+ :show-inheritance: \ No newline at end of file
diff --git a/doc/build/orm/session.rst b/doc/build/orm/session.rst
index 79fdebae0..52cee92fb 100644
--- a/doc/build/orm/session.rst
+++ b/doc/build/orm/session.rst
@@ -1971,7 +1971,7 @@ those described in :ref:`events_orm_toplevel`.
as they each emit an informative exception
if the given object is not mapped.
-.. autofunction:: is_instrumented
+.. autofunction:: sqlalchemy.orm.instrumentation.is_instrumented
.. autofunction:: set_attribute
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py
index 7effd8e58..983b4dfec 100644
--- a/lib/sqlalchemy/orm/attributes.py
+++ b/lib/sqlalchemy/orm/attributes.py
@@ -158,9 +158,17 @@ class QueryableAttribute(interfaces._MappedAttribute,
# TODO: conditionally attach this method based on clause_element ?
return self
- @property
+ @util.memoized_property
def parent(self):
- return self._parententity
+ """Return an inspection instance representing the parent.
+
+ This will be either an instance of :class:`.Mapper`
+ or :class:`.AliasedInsp`, depending upon the nature
+ of the parent entity which this attribute is associated
+ with.
+
+ """
+ return inspection.inspect(self._parententity)
@property
def expression(self):
@@ -208,6 +216,15 @@ class QueryableAttribute(interfaces._MappedAttribute,
@util.memoized_property
def property(self):
+ """Return the :class:`.MapperProperty` associated with this
+ :class:`.QueryableAttribute`.
+
+
+ Return values here will commonly be instances of
+ :class:`.ColumnProperty` or :class:`.RelationshipProperty`.
+
+
+ """
return self.comparator.property
inspection._self_inspects(QueryableAttribute)
diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py
index a0abb2743..e2b5e94e0 100644
--- a/lib/sqlalchemy/orm/properties.py
+++ b/lib/sqlalchemy/orm/properties.py
@@ -386,20 +386,8 @@ class RelationshipProperty(StrategizedProperty):
return self.property.mapper
@util.memoized_property
- def parent(self):
- """The parent :class:`.Mapper` or :class:`.AliasedClass`
- referred to by this
- :class:`.RelationshipProperty.Comparator.
-
- This is the "parent" or "local" side of the
- :func:`.relationship`.
-
- """
- return self.property.parent
-
- @util.memoized_property
def _parententity(self):
- return self.parent
+ return self.property.parent
def _source_selectable(self):
elem = self.property.parent._with_polymorphic_selectable
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py
index 02fb7d4f7..9289c01e1 100644
--- a/lib/sqlalchemy/orm/query.py
+++ b/lib/sqlalchemy/orm/query.py
@@ -1751,7 +1751,6 @@ class Query(object):
right_entity = onclause.property.mapper
left_entity = onclause._parententity
- assert left_entity is onclause.parent
prop = onclause.property
if not isinstance(onclause, attributes.QueryableAttribute):
diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py
index fb4197c58..cf3238b15 100644
--- a/lib/sqlalchemy/orm/util.py
+++ b/lib/sqlalchemy/orm/util.py
@@ -581,11 +581,20 @@ class AliasedInsp(_InspectionAttr, AliasedInsp):
* ``polymorphic_on`` - an alternate column or SQL expression which
will be used as the "discriminator" for a polymorphic load.
+ .. seealso::
+
+ :ref:`inspection_toplevel`
+
"""
is_aliased_class = True
"always returns True"
+ @property
+ def class_(self):
+ """Return the mapped class ultimately represented by this
+ :class:`.AliasedInsp`."""
+ return self.mapper.class_
inspection._inspects(AliasedClass)(lambda target: target._aliased_insp)
diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py
index 1681b26f4..84fe9a82e 100644
--- a/lib/sqlalchemy/sql/expression.py
+++ b/lib/sqlalchemy/sql/expression.py
@@ -5535,6 +5535,12 @@ class Select(HasPrefixes, SelectBase):
If the fromclause is None, correlation is disabled for the returned
select().
+ .. seealso::
+
+ :meth:`.Select.correlate_except`
+
+ :ref:`correlated_subqueries`
+
"""
self._should_correlate = False
if fromclauses and fromclauses[0] is None:
@@ -5545,6 +5551,17 @@ class Select(HasPrefixes, SelectBase):
@_generative
def correlate_except(self, *fromclauses):
+ """"Return a new select() construct which will auto-correlate
+ on FROM clauses of enclosing selectables, except for those FROM
+ clauses specified here.
+
+ .. seealso::
+
+ :meth:`.Select.correlate`
+
+ :ref:`correlated_subqueries`
+
+ """
self._should_correlate = False
if fromclauses and fromclauses[0] is None:
self._correlate_except = ()
diff --git a/test/orm/test_inspect.py b/test/orm/test_inspect.py
index cd9a30b1a..fc2d2181f 100644
--- a/test/orm/test_inspect.py
+++ b/test/orm/test_inspect.py
@@ -191,9 +191,10 @@ class TestORMInspection(_fixtures.FixtureTest):
prop = inspect(ua.addresses)
is_(prop, ua.addresses)
- is_(prop.property.parent, class_mapper(User))
+ is_(prop.property.parent.mapper, class_mapper(User))
is_(prop.property.mapper, class_mapper(Address))
- is_(prop.parent, ua)
+ is_(prop.parent.entity, ua)
+ is_(prop.parent.class_, User)
is_(prop._parentmapper, class_mapper(User))
is_(prop.mapper, class_mapper(Address))
@@ -213,9 +214,10 @@ class TestORMInspection(_fixtures.FixtureTest):
prop = inspect(ua.name)
is_(prop, ua.name)
- is_(prop.property.parent, class_mapper(User))
+ is_(prop.property.parent.mapper, class_mapper(User))
assert not hasattr(prop.property, "mapper")
- is_(prop.parent, ua)
+ is_(prop.parent.entity, ua)
+ is_(prop.parent.class_, User)
is_(prop._parentmapper, class_mapper(User))
assert not hasattr(prop, "mapper")