]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- document the inspection system
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 18 Jul 2012 18:13:18 +0000 (14:13 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 18 Jul 2012 18:13:18 +0000 (14:13 -0400)
15 files changed:
doc/build/core/index.rst
doc/build/core/inspection.rst [new file with mode: 0644]
doc/build/index.rst
doc/build/orm/internals.rst
doc/build/orm/mapper_config.rst
doc/build/orm/query.rst
doc/build/orm/session.rst
lib/sqlalchemy/engine/reflection.py
lib/sqlalchemy/exc.py
lib/sqlalchemy/inspection.py
lib/sqlalchemy/orm/attributes.py
lib/sqlalchemy/orm/state.py
lib/sqlalchemy/orm/util.py
lib/sqlalchemy/util/langhelpers.py
test/sql/test_inspect.py [new file with mode: 0644]

index 7f9ad945a9d83d0718001194d6a140a758862eba..079a4b97acfde8286be322336560567c584c80a6 100644 (file)
@@ -21,6 +21,7 @@ Language provides a schema-centric usage paradigm.
     event
     events
     compiler
+    inspection
     serializer
     interfaces
     exceptions
diff --git a/doc/build/core/inspection.rst b/doc/build/core/inspection.rst
new file mode 100644 (file)
index 0000000..2fc371d
--- /dev/null
@@ -0,0 +1,29 @@
+.. _core_inspection_toplevel:
+
+Runtime Inpection API
+=====================
+
+.. automodule:: sqlalchemy.inspection
+    :members:
+
+Available Inspection Targets
+----------------------------
+
+Following is a listing of all inspection targets.
+
+* :class:`.Connectable` (i.e. :class:`.Engine`,
+  :class:`.Connection`) - returns an :class:`.Inspector` object.
+* :class:`.ClauseElement` - all SQL expression components, including
+  :class:`.Table`, :class:`.Column`, serve as their own inspection objects,
+  meaning any of these objects passed to :func:`.inspect` return themselves.
+* ``object`` - an object given will be checked by the ORM for a mapping -
+  if so, an :class:`.InstanceState` is returned representing the mapped
+  state of the object.
+* ``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`.
+* :class:`.AliasedClass` - returns an :class:`.AliasedInsp` object.
+
index 3435d290a59fee9c161cd462219f0d63f2629698..48e558af2aeb45546a51aa9d89f9b6b1cb3d073d 100644 (file)
@@ -9,7 +9,7 @@ Getting Started
 
 A high level view and getting set up.
 
-:ref:`Overview <overview>` | 
+:ref:`Overview <overview>` |
 :ref:`Installation Guide <installation>` |
 :ref:`Migration from 0.7 <migration>`
 
@@ -57,7 +57,7 @@ SQLAlchemy Core
 ===============
 
 The breadth of SQLAlchemy's SQL rendering engine, DBAPI
-integration, transaction integration, and schema description services 
+integration, transaction integration, and schema description services
 are documented here.  In contrast to the ORM's domain-centric mode of usage, the SQL Expression Language provides a schema-centric usage paradigm.
 
 * **Read this first:**
@@ -76,15 +76,15 @@ are documented here.  In contrast to the ORM's domain-centric mode of usage, the
   :ref:`Database Introspection (Reflection) <metadata_reflection>` |
   :ref:`Insert/Update Defaults <metadata_defaults>` |
   :ref:`Constraints and Indexes <metadata_constraints>` |
-  :ref:`Using Data Definition Language (DDL) <metadata_ddl>` 
+  :ref:`Using Data Definition Language (DDL) <metadata_ddl>`
 
 * **Datatypes:**
-  :ref:`Overview <types_toplevel>` | 
-  :ref:`Generic Types <types_generic>` | 
+  :ref:`Overview <types_toplevel>` |
+  :ref:`Generic Types <types_generic>` |
   :ref:`SQL Standard Types <types_sqlstandard>` |
   :ref:`Vendor Specific Types <types_vendor>` |
   :ref:`Building Custom Types <types_custom>` |
-  :ref:`API <types_api>` 
+  :ref:`API <types_api>`
 
 * **Extending the Core:**
   :doc:`SQLAlchemy Events <core/event>` |
@@ -93,6 +93,7 @@ are documented here.  In contrast to the ORM's domain-centric mode of usage, the
   :doc:`Internals API <core/internals>`
 
 * **Other:**
+  :doc:`Runtime Inspection API <core/inspection>` |
   :doc:`Serializing Expressions <core/serializer>` |
   :doc:`core/interfaces` |
   :doc:`core/exceptions`
index c23e5f2f732850a9c946bd2285a826094b6e0268..301156f8b5343d5907637ea62a52828baf120114 100644 (file)
@@ -23,6 +23,10 @@ Some key internal constructs are listed here.
     :members:
     :show-inheritance:
 
+.. autoclass:: sqlalchemy.orm.state.InspectAttr
+    :members:
+    :show-inheritance:
+
 .. autoclass:: sqlalchemy.orm.interfaces.MapperProperty
     :members:
     :show-inheritance:
index 7dc882d21eae3ce32327b5926da946e16ceeb999..31e1d1fcdb6e796690f2425f0f9ec89a44c0dc9a 100644 (file)
@@ -51,7 +51,7 @@ construct, then associated with the ``User`` class via the :func:`.mapper` funct
     mapper(User, user)
 
 Information about mapped attributes, such as relationships to other classes, are provided
-via the ``properties`` dictionary.  The example below illustrates a second :class:`.Table` 
+via the ``properties`` dictionary.  The example below illustrates a second :class:`.Table`
 object, mapped to a class called ``Address``, then linked to ``User`` via :func:`.relationship`::
 
     address = Table('address', metadata,
@@ -73,8 +73,8 @@ 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 approaches are **fully interchangeable**.  Both 
-systems ultimately create the same configuration, consisting of a :class:`.Table`, 
+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, 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.
@@ -125,7 +125,7 @@ with the desired key::
 Naming All Columns with a Prefix
 --------------------------------
 
-A way to automate the assignment of a prefix to 
+A way to automate the assignment of a prefix to
 the mapped attribute names relative to the column name
 is to use ``column_prefix``::
 
@@ -161,7 +161,7 @@ result in the former value being loaded first::
         id = Column(Integer, primary_key=True)
         name = column_property(Column(String(50)), active_history=True)
 
-:func:`.column_property` is also used to map a single attribute to 
+:func:`.column_property` is also used to map a single attribute to
 multiple columns.  This use case arises when mapping to a :func:`~.expression.join`
 which has attributes which are equated to each other::
 
@@ -174,7 +174,7 @@ which has attributes which are equated to each other::
 
 For more examples featuring this usage, see :ref:`maptojoin`.
 
-Another place where :func:`.column_property` is needed is to specify SQL expressions as 
+Another place where :func:`.column_property` is needed is to specify SQL expressions as
 mapped attributes, such as below where we create an attribute ``fullname``
 that is the string concatenation of the ``firstname`` and ``lastname``
 columns::
@@ -195,11 +195,11 @@ See examples of this usage at :ref:`mapper_sql_expressions`.
 Mapping a Subset of Table Columns
 ---------------------------------
 
-Sometimes, a :class:`.Table` object was made available using the 
-reflection process described at :ref:`metadata_reflection` to load 
+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`` 
+in the application, the ``include_properties`` or ``exclude_properties``
 arguments can specify that only a subset of columns should be mapped.
 For example::
 
@@ -241,7 +241,7 @@ should be included or excluded::
             'primary_key' : [user_table.c.id]
         }
 
-.. note:: 
+.. note::
 
    insert and update defaults configured on individual
    :class:`.Column` objects, i.e. those described at :ref:`metadata_defaults`
@@ -262,11 +262,11 @@ Deferred Column Loading
 ========================
 
 This feature allows particular columns of a table be loaded only
-upon direct access, instead of when the entity is queried using 
+upon direct access, instead of when the entity is queried using
 :class:`.Query`.  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, using the :func:`.orm.deferred` function to 
+lazy-load together, using the :func:`.orm.deferred` function to
 mark them as "deferred". In the example below, we define a mapping that will load each of
 ``.excerpt`` and ``.photo`` in separate, individual-row SELECT statements when each
 attribute is first referenced on the individual object instance::
@@ -341,8 +341,8 @@ Column Deferral API
 SQL Expressions as Mapped Attributes
 =====================================
 
-Attributes on a mapped class can be linked to SQL expressions, which can 
-be used in queries. 
+Attributes on a mapped class can be linked to SQL expressions, which can
+be used in queries.
 
 Using a Hybrid
 --------------
@@ -350,7 +350,7 @@ Using a Hybrid
 The easiest and most flexible way to link relatively simple SQL expressions to a class is to use a so-called
 "hybrid attribute",
 described in the section :ref:`hybrids_toplevel`.  The hybrid provides
-for an expression that works at both the Python level as well as at the 
+for an expression that works at both the Python level as well as at the
 SQL expression level.  For example, below we map a class ``User``,
 containing attributes ``firstname`` and ``lastname``, and include a hybrid that
 will provide for us the ``fullname``, which is the string concatenation of the two::
@@ -367,7 +367,7 @@ will provide for us the ``fullname``, which is the string concatenation of the t
         def fullname(self):
             return self.firstname + " " + self.lastname
 
-Above, the ``fullname`` attribute is interpreted at both the instance and 
+Above, the ``fullname`` attribute is interpreted at both the instance and
 class level, so that it is available from an instance::
 
     some_user = session.query(User).first()
@@ -410,21 +410,21 @@ Using column_property
 ---------------------
 
 The :func:`.orm.column_property` function can be used to map a SQL
-expression in a manner similar to a regularly mapped :class:`.Column`.   
+expression in a manner similar to a regularly mapped :class:`.Column`.
 With this technique, the attribute is loaded
 along with all other column-mapped attributes at load time.  This is in some
 cases an advantage over the usage of hybrids, as the value can be loaded
 up front at the same time as the parent row of the object, particularly if
 the expression is one which links to other tables (typically as a correlated
-subquery) to access data that wouldn't normally be 
+subquery) to access data that wouldn't normally be
 available on an already loaded object.
 
-Disadvantages to using :func:`.orm.column_property` for SQL expressions include that 
-the expression must be compatible with the SELECT statement emitted for the class 
-as a whole, and there are also some configurational quirks which can occur 
+Disadvantages to using :func:`.orm.column_property` for SQL expressions include that
+the expression must be compatible with the SELECT statement emitted for the class
+as a whole, and there are also some configurational quirks which can occur
 when using :func:`.orm.column_property` from declarative mixins.
 
-Our "fullname" example can be expressed using :func:`.orm.column_property` as 
+Our "fullname" example can be expressed using :func:`.orm.column_property` as
 follows::
 
     from sqlalchemy.orm import column_property
@@ -469,7 +469,7 @@ to add an additional property after the fact::
     User.address_count = column_property(
             select([func.count(Address.id)]).\
                 where(Address.user_id==User.id)
-        ) 
+        )
 
 For many-to-many relationships, use :func:`.and_` to join the fields of the
 association table to both tables in a relation, illustrated
@@ -479,7 +479,7 @@ here with a classical mapping::
 
     mapper(Author, authors, properties={
         'book_count': column_property(
-                            select([func.count(books.c.id)], 
+                            select([func.count(books.c.id)],
                                 and_(
                                     book_authors.c.author_id==authors.c.id,
                                     book_authors.c.book_id==books.c.id
@@ -490,9 +490,9 @@ Using a plain descriptor
 -------------------------
 
 In cases where a SQL query more elaborate than what :func:`.orm.column_property`
-or :class:`.hybrid_property` can provide must be emitted, a regular Python 
+or :class:`.hybrid_property` can provide must be emitted, a regular Python
 function accessed as an attribute can be used, assuming the expression
-only needs to be available on an already-loaded instance.   The function 
+only needs to be available on an already-loaded instance.   The function
 is decorated with Python's own ``@property`` decorator to mark it as a read-only
 attribute.   Within the function, :func:`.object_session`
 is used to locate the :class:`.Session` corresponding to the current object,
@@ -562,7 +562,7 @@ collection::
             assert '@' in address.email
             return address
 
-Note that the :func:`~.validates` decorator is a convenience function built on 
+Note that the :func:`~.validates` decorator is a convenience function built on
 top of attribute events.   An application that requires more control over
 configuration of attribute change behavior can make use of this system,
 described at :class:`~.AttributeEvents`.
@@ -635,8 +635,8 @@ that is, from the ``EmailAddress`` class directly:
     {sql}address = session.query(EmailAddress).\
                      filter(EmailAddress.email == 'address@example.com').\
                      one()
-    SELECT address.email AS address_email, address.id AS address_id 
-    FROM address 
+    SELECT address.email AS address_email, address.id AS address_id
+    FROM address
     WHERE address.email = ?
     ('address@example.com',)
     {stop}
@@ -664,21 +664,21 @@ logic::
 
         @hybrid_property
         def email(self):
-            """Return the value of _email up until the last twelve 
+            """Return the value of _email up until the last twelve
             characters."""
 
             return self._email[:-12]
 
         @email.setter
         def email(self, email):
-            """Set the value of _email, tacking on the twelve character 
+            """Set the value of _email, tacking on the twelve character
             value @example.com."""
 
             self._email = email + "@example.com"
 
         @email.expression
         def email(cls):
-            """Produce a SQL expression that represents the value 
+            """Produce a SQL expression that represents the value
             of the _email column, minus the last twelve characters."""
 
             return func.substr(cls._email, 0, func.length(cls._email) - 12)
@@ -691,8 +691,8 @@ attribute, a SQL function is rendered which produces the same effect:
 .. sourcecode:: python+sql
 
     {sql}address = session.query(EmailAddress).filter(EmailAddress.email == 'address').one()
-    SELECT address.email AS address_email, address.id AS address_id 
-    FROM address 
+    SELECT address.email AS address_email, address.id AS address_id
+    FROM address
     WHERE substr(address.email, ?, length(address.email) - ?) = ?
     (0, 12, 'address')
     {stop}
@@ -717,20 +717,20 @@ Custom Comparators
 
 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. 
+explicitly defines each comparison method needed.
 
-This is a relatively rare use case which generally applies only to 
-highly customized types.  Usually, custom SQL behaviors can be 
+This is a relatively rare use case which generally applies only to
+highly customized types.  Usually, custom SQL behaviors can be
 associated with a mapped class by composing together the classes'
-existing mapped attributes with other expression components, 
-using the techniques described in :ref:`mapper_sql_expressions`.  
+existing mapped attributes with other expression components,
+using the techniques described in :ref:`mapper_sql_expressions`.
 Those approaches should be considered first before resorting to custom comparison objects.
 
 Each of :func:`.orm.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. 
+such as ``__eq__()``, ``__ne__()``, ``__lt__()``, and so on.
 
 It's best to subclass the :class:`.PropComparator` subclass provided by
 each type of property.  For example, to allow a column-mapped attribute to
@@ -758,7 +758,7 @@ function to produce case-insensitive matching::
     lower(address.email) = lower(:lower_1)
 
 When building a :class:`.PropComparator`, the ``__clause_element__()`` method
-should be used in order to acquire the underlying mapped column.  This will 
+should be used in order to acquire the underlying mapped column.  This will
 return a column that is appropriately wrapped in any kind of subquery
 or aliasing that has been applied in the context of the generated SQL statement.
 
@@ -774,7 +774,7 @@ provides a single attribute which represents the group of columns using the
 class you provide.
 
 .. versionchanged:: 0.7
-    Composites have been simplified such that 
+    Composites have been simplified such that
     they no longer "conceal" the underlying column based attributes.  Additionally,
     in-place mutation is no longer automatic; see the section below on
     enabling mutability to support tracking of in-place changes.
@@ -851,12 +851,12 @@ using the ``.start`` and ``.end`` attributes against ad-hoc ``Point`` instances:
     BEGIN (implicit)
     INSERT INTO vertice (x1, y1, x2, y2) VALUES (?, ?, ?, ?)
     (3, 4, 5, 6)
-    SELECT vertice.id AS vertice_id, 
-            vertice.x1 AS vertice_x1, 
-            vertice.y1 AS vertice_y1, 
-            vertice.x2 AS vertice_x2, 
-            vertice.y2 AS vertice_y2 
-    FROM vertice 
+    SELECT vertice.id AS vertice_id,
+            vertice.x1 AS vertice_x1,
+            vertice.y1 AS vertice_y1,
+            vertice.x2 AS vertice_x2,
+            vertice.y2 AS vertice_y2
+    FROM vertice
     WHERE vertice.x1 = ? AND vertice.y1 = ?
      LIMIT ? OFFSET ?
     (3, 4, 1, 0)
@@ -867,9 +867,9 @@ using the ``.start`` and ``.end`` attributes against ad-hoc ``Point`` instances:
 Tracking In-Place Mutations on Composites
 -----------------------------------------
 
-In-place changes to an existing composite value are 
+In-place changes to an existing composite value are
 not tracked automatically.  Instead, the composite class needs to provide
-events to its parent object explicitly.   This task is largely automated 
+events to its parent object explicitly.   This task is largely automated
 via the usage of the :class:`.MutableComposite` mixin, which uses events
 to associate each user-defined composite object with all parent associations.
 Please see the example in :ref:`mutable_composites`.
@@ -883,7 +883,7 @@ Redefining Comparison Operations for Composites
 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 
+Below we illustrate the "greater than" operator, implementing
 the same expression that the base "greater than" does::
 
     from sqlalchemy.orm.properties import CompositeProperty
@@ -906,9 +906,9 @@ the same expression that the base "greater than" does::
         x2 = Column(Integer)
         y2 = Column(Integer)
 
-        start = composite(Point, x1, y1, 
+        start = composite(Point, x1, y1,
                             comparator_factory=PointComparator)
-        end = composite(Point, x2, y2, 
+        end = composite(Point, x2, y2,
                             comparator_factory=PointComparator)
 
 .. _maptojoin:
@@ -959,22 +959,22 @@ In the example above, the join expresses columns for both the
 ``user`` and the ``address`` table.  The ``user.id`` and ``address.user_id``
 columns are equated by foreign key, so in the mapping they are defined
 as one attribute, ``AddressUser.id``, using :func:`.column_property` to
-indicate a specialized column mapping.   Based on this part of the 
+indicate a specialized column mapping.   Based on this part of the
 configuration, the mapping will copy
 new primary key values from ``user.id`` into the ``address.user_id`` column
 when a flush occurs.
 
-Additionally, the ``address.id`` column is mapped explicitly to 
-an attribute named ``address_id``.   This is to **disambiguate** the 
-mapping of the ``address.id`` column from the same-named ``AddressUser.id`` 
+Additionally, the ``address.id`` column is mapped explicitly to
+an attribute named ``address_id``.   This is to **disambiguate** the
+mapping of the ``address.id`` column from the same-named ``AddressUser.id``
 attribute, which here has been assigned to refer to the ``user`` table
 combined with the ``address.user_id`` foreign key.
 
 The natural primary key of the above mapping is the composite of
 ``(user.id, address.id)``, as these are the primary key columns of the
-``user`` and ``address`` table combined together.  The identity of an 
+``user`` and ``address`` table combined together.  The identity of an
 ``AddressUser`` object will be in terms of these two values, and
-is represented from an ``AddressUser`` object as 
+is represented from an ``AddressUser`` object as
 ``(AddressUser.id, AddressUser.address_id)``.
 
 
@@ -983,14 +983,14 @@ Mapping a Class against Arbitrary Selects
 
 Similar to mapping against a join, a plain :func:`~.expression.select` object can be used with a
 mapper as well.  The example fragment below illustrates mapping a class
-called ``Customer`` to a :func:`~.expression.select` which includes a join to a 
+called ``Customer`` to a :func:`~.expression.select` which includes a join to a
 subquery::
 
     from sqlalchemy import select, func
 
     subq = select([
-                func.count(orders.c.id).label('order_count'), 
-                func.max(orders.c.price).label('highest_order'), 
+                func.count(orders.c.id).label('order_count'),
+                func.max(orders.c.price).label('highest_order'),
                 orders.c.customer_id
                 ]).group_by(orders.c.customer_id).alias()
 
@@ -1002,12 +1002,12 @@ subquery::
 
 Above, the full row represented by ``customer_select`` will be all the
 columns of the ``customers`` table, in addition to those columns
-exposed by the ``subq`` subquery, which are ``order_count``, 
+exposed by the ``subq`` subquery, which are ``order_count``,
 ``highest_order``, and ``customer_id``.  Mapping the ``Customer``
 class to this selectable then creates a class which will contain
 those attributes.
 
-When the ORM persists new instances of ``Customer``, only the 
+When the ORM persists new instances of ``Customer``, only the
 ``customers`` table will actually receive an INSERT.  This is because the
 primary key of the ``orders`` table is not represented in the mapping;  the ORM
 will only emit an INSERT into a table for which it has mapped the primary
@@ -1022,19 +1022,19 @@ persisting it towards a particular :class:`.Table`, but also *instrumenting*
 attributes upon the class which are structured specifically according to the
 table metadata.
 
-One potential use case for another mapper to exist at the same time is if we 
+One potential use case for another mapper to exist at the same time is if we
 wanted to load instances of our class not just from the immediate :class:`.Table`
 to which it is mapped, but from another selectable that is a derivation of that
 :class:`.Table`.   While there technically is a way to create such a :func:`.mapper`,
 using the ``non_primary=True`` option, this approach is virtually never needed.
-Instead, we use the functionality of the :class:`.Query` object to achieve this, 
+Instead, we use the functionality of the :class:`.Query` object to achieve this,
 using a method such as :meth:`.Query.select_from`
 or :meth:`.Query.from_statement` to specify a derived selectable.
 
 Another potential use is if we genuinely want instances of our class to
-be persisted into different tables at different times; certain kinds of 
+be persisted into different tables at different times; certain kinds of
 data sharding configurations may persist a particular class into tables
-that are identical in structure except for their name.   For this kind of 
+that are identical in structure except for their name.   For this kind of
 pattern, Python offers a better approach than the complexity of mapping
 the same class multiple times, which is to instead create new mapped classes
 for each target table.    SQLAlchemy refers to this as the "entity name"
@@ -1090,7 +1090,7 @@ next flush() operation, so the activity within a reconstructor should be
 conservative.
 
 :func:`~sqlalchemy.orm.reconstructor` is a shortcut into a larger system
-of "instance level" events, which can be subscribed to using the 
+of "instance level" events, which can be subscribed to using the
 event API - see :class:`.InstanceEvents` for the full API description
 of these events.
 
@@ -1105,8 +1105,6 @@ Class Mapping API
 
 .. autofunction:: class_mapper
 
-.. autofunction:: compile_mappers
-
 .. autofunction:: configure_mappers
 
 .. autofunction:: clear_mappers
index 0f273e4b5729e22d82c552a1acd17f8689f9f3f1..d143908eb7e21f734ca34a394150b714d295975a 100644 (file)
@@ -32,6 +32,8 @@ The public name of the :class:`.AliasedClass` class.
 
 .. autoclass:: sqlalchemy.orm.util.AliasedClass
 
+.. autoclass:: sqlalchemy.orm.util.AliasedInsp
+
 .. autofunction:: join
 
 .. autofunction:: outerjoin
index 4c376b47bbe7cf49facc973df4b45d9fd171fc66..2c1651fcbb24964fbb95e9ef7a219301bb3ffa13 100644 (file)
@@ -1720,6 +1720,10 @@ with instances, attribute values, and history.  Some of them
 are useful when constructing event listener functions, such as
 those described in :ref:`events_orm_toplevel`.
 
+.. currentmodule:: sqlalchemy.orm.util
+
+.. autofunction:: object_state
+
 .. currentmodule:: sqlalchemy.orm.attributes
 
 .. autofunction:: del_attribute
@@ -1734,13 +1738,17 @@ those described in :ref:`events_orm_toplevel`.
 
 .. function:: instance_state
 
-    Return the :class:`.InstanceState` for a given object.
-
-.. autofunction:: is_instrumented
+    Return the :class:`.InstanceState` for a given
+    mapped object.
 
-.. function:: manager_of_class
+    This function is the internal version
+    of :func:`.object_state`.   The
+    :func:`.object_state` and/or the
+    :func:`.inspect` function is preferred here
+    as they each emit an informative exception
+    if the given object is not mapped.
 
-    Return the :class:`.ClassManager` for a given class.
+.. autofunction:: is_instrumented
 
 .. autofunction:: set_attribute
 
@@ -1749,14 +1757,3 @@ those described in :ref:`events_orm_toplevel`.
 .. autoclass:: History
     :members:
 
-.. autodata:: PASSIVE_NO_INITIALIZE
-
-.. autodata:: PASSIVE_NO_FETCH
-
-.. autodata:: PASSIVE_NO_FETCH_RELATED
-
-.. autodata:: PASSIVE_ONLY_PERSISTENT
-
-.. autodata:: PASSIVE_OFF
-
-
index 3a12819f122b13a04e1ea3c5af085bf01af04e22..e1d70214657efd09c8a931d3683c360d952aa26d 100644 (file)
@@ -40,8 +40,8 @@ def cache(fn, self, con, *args, **kw):
     if info_cache is None:
         return fn(self, con, *args, **kw)
     key = (
-            fn.__name__, 
-            tuple(a for a in args if isinstance(a, basestring)), 
+            fn.__name__,
+            tuple(a for a in args if isinstance(a, basestring)),
             tuple((k, v) for k, v in kw.iteritems() if isinstance(v, (basestring, int, float)))
         )
     ret = info_cache.get(key)
@@ -59,8 +59,15 @@ class Inspector(object):
     consistent interface as well as caching support for previously
     fetched metadata.
 
-    The preferred method to construct an :class:`.Inspector` is via the
-    :meth:`Inspector.from_engine` method.   I.e.::
+    A :class:`.Inspector` object is usually created via the
+    :func:`.inspect` function::
+
+        from sqlalchemy import inspect, create_engine
+        engine = create_engine('...')
+        insp = inspect(engine)
+
+    The inspection method above is equivalent to using the
+    :meth:`.Inspector.from_engine` method, i.e.::
 
         engine = create_engine('...')
         insp = Inspector.from_engine(engine)
@@ -74,9 +81,9 @@ class Inspector(object):
     def __init__(self, bind):
         """Initialize a new :class:`.Inspector`.
 
-        :param bind: a :class:`~sqlalchemy.engine.base.Connectable`, 
-          which is typically an instance of 
-          :class:`~sqlalchemy.engine.base.Engine` or 
+        :param bind: a :class:`~sqlalchemy.engine.base.Connectable`,
+          which is typically an instance of
+          :class:`~sqlalchemy.engine.base.Engine` or
           :class:`~sqlalchemy.engine.base.Connection`.
 
         For a dialect-specific instance of :class:`.Inspector`, see
@@ -103,9 +110,9 @@ class Inspector(object):
     def from_engine(cls, bind):
         """Construct a new dialect-specific Inspector object from the given engine or connection.
 
-        :param bind: a :class:`~sqlalchemy.engine.base.Connectable`, 
-          which is typically an instance of 
-          :class:`~sqlalchemy.engine.base.Engine` or 
+        :param bind: a :class:`~sqlalchemy.engine.base.Connectable`,
+          which is typically an instance of
+          :class:`~sqlalchemy.engine.base.Engine` or
           :class:`~sqlalchemy.engine.base.Connection`.
 
         This method differs from direct a direct constructor call of :class:`.Inspector`
@@ -322,7 +329,7 @@ class Inspector(object):
     def reflecttable(self, table, include_columns, exclude_columns=()):
         """Given a Table object, load its internal constructs based on introspection.
 
-        This is the underlying method used by most dialects to produce 
+        This is the underlying method used by most dialects to produce
         table reflection.  Direct usage is like::
 
             from sqlalchemy import create_engine, MetaData, Table
@@ -416,11 +423,11 @@ class Inspector(object):
         # Primary keys
         pk_cons = self.get_pk_constraint(table_name, schema, **tblkw)
         if pk_cons:
-            pk_cols = [table.c[pk] 
-                        for pk in pk_cons['constrained_columns'] 
+            pk_cols = [table.c[pk]
+                        for pk in pk_cons['constrained_columns']
                         if pk in table.c and pk not in exclude_columns
                     ] + [pk for pk in table.primary_key if pk.key in exclude_columns]
-            primary_key_constraint = sa_schema.PrimaryKeyConstraint(name=pk_cons.get('name'), 
+            primary_key_constraint = sa_schema.PrimaryKeyConstraint(name=pk_cons.get('name'),
                 *pk_cols
             )
 
@@ -457,7 +464,7 @@ class Inspector(object):
             table.append_constraint(
                 sa_schema.ForeignKeyConstraint(constrained_columns, refspec,
                                                conname, link_to_name=True))
-        # Indexes 
+        # Indexes
         indexes = self.get_indexes(table_name, schema)
         for index_d in indexes:
             name = index_d['name']
@@ -470,5 +477,5 @@ class Inspector(object):
                     "Omitting %s KEY for (%s), key covers omitted columns." %
                     (flavor, ', '.join(columns)))
                 continue
-            sa_schema.Index(name, *[table.columns[c] for c in columns], 
+            sa_schema.Index(name, *[table.columns[c] for c in columns],
                          **dict(unique=unique))
index 0d69795d19a5b5b3725be6d7a2d002b1a4835023..f9f97718c119511d1115636f3a9d5ac62ba92e2a 100644 (file)
@@ -96,7 +96,7 @@ class InvalidRequestError(SQLAlchemyError):
     """
 
 class NoInspectionAvailable(InvalidRequestError):
-    """A class to :func:`sqlalchemy.inspection.inspect` produced
+    """A subject passed to :func:`sqlalchemy.inspection.inspect` produced
     no context for inspection."""
 
 class ResourceClosedError(InvalidRequestError):
index 8b0a751ad5517e2bca3e43c8aeea1144d3558d7d..34a47217b2269bb9cbf85591f726067ca2adb4d2 100644 (file)
@@ -4,22 +4,54 @@
 # This module is part of SQLAlchemy and is released under
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
 
-"""Base inspect API.
+"""The inspection module provides the :func:`.inspect` function,
+which delivers runtime information about a wide variety
+of SQLAlchemy objects, both within the Core as well as the
+ORM.
 
-:func:`.inspect` provides access to a contextual object
-regarding a subject.
+The :func:`.inspect` function is the entry point to SQLAlchemy's
+public API for viewing the configuration and construction
+of in-memory objects.   Depending on the type of object
+passed to :func:`.inspect`, the return value will either be
+a related object which provides a known interface, or in many
+cases it will return the object itself.
+
+The rationale for :func:`.inspect` is twofold.  One is that
+it replaces the need to be aware of a large variety of "information
+getting" functions in SQLAlchemy, such as :meth:`.Inspector.from_engine`,
+:func:`.orm.attributes.instance_state`, :func:`.orm.class_mapper`,
+and others.    The other is that the return value of :func:`.inspect`
+is guaranteed to obey a documented API, thus allowing third party
+tools which build on top of SQLAlchemy configurations to be constructed
+in a forwards-compatible way.
+
+.. versionadded:: 0.8 The :func:`.inspect` system is introduced
+   as of version 0.8.
 
-Various subsections of SQLAlchemy,
-such as the :class:`.Inspector`, :class:`.Mapper`, and
-others register themselves with the "inspection registry" here
-so that they may return a context object given a certain kind
-of argument.
 """
 
 from . import util, exc
 _registrars = util.defaultdict(list)
 
 def inspect(subject, raiseerr=True):
+    """Produce an inspection object for the given target.
+
+    The returned value in some cases may be the
+    same object as the one given, such as if a
+    :class:`.orm.Mapper` object is passed.   In other
+    cases, it will be an instance of the registered
+    inspection type for the given object, such as
+    if a :class:`.engine.Engine` is passed, an
+    :class:`.engine.Inspector` object is returned.
+
+    :param subject: the subject to be inspected.
+    :param raiseerr: When ``True``, if the given subject
+     does not
+     correspond to a known SQLAlchemy inspected type,
+     :class:`sqlalchemy.exc.NoInspectionAvailable`
+     is raised.  If ``False``, ``None`` is returned.
+
+     """
     type_ = type(subject)
     for cls in type_.__mro__:
         if cls in _registrars:
index 0bf9ea4384c2d4432abb777a1d0012a9c4f7df61..9a1c60aa78d055bc3377634b0b37b8e2567c6a5c 100644 (file)
@@ -52,7 +52,7 @@ indicating that the attribute had not been assigned to previously.
 """
 )
 
-NO_CHANGE = util.symbol("NO_CHANGE", 
+NO_CHANGE = util.symbol("NO_CHANGE",
 """No callables or SQL should be emitted on attribute access
 and no state should change""", canonical=0
 )
@@ -80,29 +80,42 @@ value can be obtained.
 )
 
 NON_PERSISTENT_OK = util.symbol("NON_PERSISTENT_OK",
-"""callables can be emitted if the parent is not persistent.""", 
+"""callables can be emitted if the parent is not persistent.""",
 canonical=16
 )
 
-
 # pre-packaged sets of flags used as inputs
-PASSIVE_OFF = RELATED_OBJECT_OK | \
-                NON_PERSISTENT_OK | \
-                INIT_OK | \
-                CALLABLES_OK | \
-                SQL_OK
-
-PASSIVE_RETURN_NEVER_SET = PASSIVE_OFF  ^ INIT_OK
-PASSIVE_NO_INITIALIZE = PASSIVE_RETURN_NEVER_SET ^ CALLABLES_OK
-PASSIVE_NO_FETCH = PASSIVE_OFF ^ SQL_OK
-PASSIVE_NO_FETCH_RELATED = PASSIVE_OFF ^ RELATED_OBJECT_OK
-PASSIVE_ONLY_PERSISTENT = PASSIVE_OFF ^ NON_PERSISTENT_OK
+PASSIVE_OFF = util.symbol("PASSIVE_OFF",
+    "Callables can be emitted in all cases.",
+    canonical=(RELATED_OBJECT_OK | NON_PERSISTENT_OK |
+                    INIT_OK | CALLABLES_OK | SQL_OK)
+)
+PASSIVE_RETURN_NEVER_SET = util.symbol("PASSIVE_RETURN_NEVER_SET",
+        """PASSIVE_OFF ^ INIT_OK""",
+        canonical=PASSIVE_OFF ^ INIT_OK
+)
+PASSIVE_NO_INITIALIZE = util.symbol("PASSIVE_NO_INITIALIZE",
+        "PASSIVE_RETURN_NEVER_SET ^ CALLABLES_OK",
+        canonical=PASSIVE_RETURN_NEVER_SET ^ CALLABLES_OK
+)
+PASSIVE_NO_FETCH = util.symbol("PASSIVE_NO_FETCH",
+        "PASSIVE_OFF ^ SQL_OK",
+        canonical=PASSIVE_OFF ^ SQL_OK
+)
+PASSIVE_NO_FETCH_RELATED = util.symbol("PASSIVE_NO_FETCH_RELATED",
+        "PASSIVE_OFF ^ RELATED_OBJECT_OK",
+        canonical=PASSIVE_OFF ^ RELATED_OBJECT_OK
+)
+PASSIVE_ONLY_PERSISTENT = util.symbol("PASSIVE_ONLY_PERSISTENT",
+        "PASSIVE_OFF ^ NON_PERSISTENT_OK",
+        canonical=PASSIVE_OFF ^ NON_PERSISTENT_OK
+)
 
 
 class QueryableAttribute(interfaces.PropComparator):
     """Base class for class-bound attributes. """
 
-    def __init__(self, class_, key, impl=None, 
+    def __init__(self, class_, key, impl=None,
                         comparator=None, parententity=None,
                         of_type=None):
         self.class_ = class_
@@ -115,7 +128,7 @@ class QueryableAttribute(interfaces.PropComparator):
         manager = manager_of_class(class_)
         # manager is None in the case of AliasedClass
         if manager:
-            # propagate existing event listeners from 
+            # propagate existing event listeners from
             # immediate superclass
             for base in manager._bases:
                 if key in base:
@@ -166,8 +179,8 @@ class QueryableAttribute(interfaces.PropComparator):
         except AttributeError:
             raise AttributeError(
                     'Neither %r object nor %r object has an attribute %r' % (
-                    type(self).__name__, 
-                    type(self.comparator).__name__, 
+                    type(self).__name__,
+                    type(self.comparator).__name__,
                     key)
             )
 
@@ -187,7 +200,7 @@ class InstrumentedAttribute(QueryableAttribute):
     """Class bound instrumented attribute which adds descriptor methods."""
 
     def __set__(self, instance, value):
-        self.impl.set(instance_state(instance), 
+        self.impl.set(instance_state(instance),
                         instance_dict(instance), value, None)
 
     def __delete__(self, instance):
@@ -215,13 +228,13 @@ def create_proxied_attribute(descriptor):
 
     class Proxy(QueryableAttribute):
         """Presents the :class:`.QueryableAttribute` interface as a
-        proxy on top of a Python descriptor / :class:`.PropComparator` 
+        proxy on top of a Python descriptor / :class:`.PropComparator`
         combination.
 
         """
 
-        def __init__(self, class_, key, descriptor, 
-                                comparator, 
+        def __init__(self, class_, key, descriptor,
+                                comparator,
                                 adapter=None, doc=None,
                                 original_property=None):
             self.class_ = class_
@@ -272,8 +285,8 @@ def create_proxied_attribute(descriptor):
                 except AttributeError:
                     raise AttributeError(
                     'Neither %r object nor %r object has an attribute %r' % (
-                    type(descriptor).__name__, 
-                    type(self.comparator).__name__, 
+                    type(descriptor).__name__,
+                    type(self.comparator).__name__,
                     attribute)
                     )
 
@@ -289,7 +302,7 @@ class AttributeImpl(object):
 
     def __init__(self, class_, key,
                     callable_, dispatch, trackparent=False, extension=None,
-                    compare_function=None, active_history=False, 
+                    compare_function=None, active_history=False,
                     parent_token=None, expire_missing=True,
                     **kwargs):
         """Construct an AttributeImpl.
@@ -326,12 +339,12 @@ class AttributeImpl(object):
         parent_token
           Usually references the MapperProperty, used as a key for
           the hasparent() function to identify an "owning" attribute.
-          Allows multiple AttributeImpls to all match a single 
+          Allows multiple AttributeImpls to all match a single
           owner attribute.
 
         expire_missing
           if False, don't add an "expiry" callable to this attribute
-          during state.expire_attributes(None), if no value is present 
+          during state.expire_attributes(None), if no value is present
           for this key.
 
         """
@@ -370,7 +383,7 @@ class AttributeImpl(object):
 
 
     def hasparent(self, state, optimistic=False):
-        """Return the boolean value of a `hasparent` flag attached to 
+        """Return the boolean value of a `hasparent` flag attached to
         the given state.
 
         The `optimistic` flag determines what the default return value
@@ -414,8 +427,8 @@ class AttributeImpl(object):
                             "state %s along attribute '%s', "
                             "but the parent record "
                             "has gone stale, can't be sure this "
-                            "is the most recent parent." % 
-                            (orm_util.state_str(state), 
+                            "is the most recent parent." %
+                            (orm_util.state_str(state),
                             orm_util.state_str(parent_state),
                             self.key))
 
@@ -445,8 +458,8 @@ class AttributeImpl(object):
         raise NotImplementedError()
 
     def get_all_pending(self, state, dict_):
-        """Return a list of tuples of (state, obj) 
-        for all objects in this attribute's current state 
+        """Return a list of tuples of (state, obj)
+        for all objects in this attribute's current state
         + history.
 
         Only applies to object-based attributes.
@@ -455,8 +468,8 @@ class AttributeImpl(object):
         which roughly corresponds to:
 
             get_state_history(
-                        state, 
-                        key, 
+                        state,
+                        key,
                         passive=PASSIVE_NO_INITIALIZE).sum()
 
         """
@@ -517,14 +530,14 @@ class AttributeImpl(object):
         self.set(state, dict_, value, initiator, passive=passive)
 
     def remove(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
-        self.set(state, dict_, None, initiator, 
+        self.set(state, dict_, None, initiator,
                     passive=passive, check_old=value)
 
     def pop(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
-        self.set(state, dict_, None, initiator, 
+        self.set(state, dict_, None, initiator,
                     passive=passive, check_old=value, pop=True)
 
-    def set(self, state, dict_, value, initiator, 
+    def set(self, state, dict_, value, initiator,
                 passive=PASSIVE_OFF, check_old=None, pop=False):
         raise NotImplementedError()
 
@@ -571,7 +584,7 @@ class ScalarAttributeImpl(AttributeImpl):
         return History.from_scalar_attribute(
             self, state, dict_.get(self.key, NO_VALUE))
 
-    def set(self, state, dict_, value, initiator, 
+    def set(self, state, dict_, value, initiator,
                 passive=PASSIVE_OFF, check_old=None, pop=False):
         if initiator and initiator.parent_token is self.parent_token:
             return
@@ -582,7 +595,7 @@ class ScalarAttributeImpl(AttributeImpl):
             old = dict_.get(self.key, NO_VALUE)
 
         if self.dispatch.set:
-            value = self.fire_replace_event(state, dict_, 
+            value = self.fire_replace_event(state, dict_,
                                                 value, old, initiator)
         state._modified_event(dict_, self, old)
         dict_[self.key] = value
@@ -604,7 +617,7 @@ class ScalarAttributeImpl(AttributeImpl):
 
 
 class ScalarObjectAttributeImpl(ScalarAttributeImpl):
-    """represents a scalar-holding InstrumentedAttribute, 
+    """represents a scalar-holding InstrumentedAttribute,
        where the target object is also instrumented.
 
        Adds events to delete/set operations.
@@ -650,7 +663,7 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl):
         else:
             return []
 
-    def set(self, state, dict_, value, initiator, 
+    def set(self, state, dict_, value, initiator,
                 passive=PASSIVE_OFF, check_old=None, pop=False):
         """Set a value on the given InstanceState.
 
@@ -729,12 +742,12 @@ class CollectionAttributeImpl(AttributeImpl):
                     typecallable=None, trackparent=False, extension=None,
                     copy_function=None, compare_function=None, **kwargs):
         super(CollectionAttributeImpl, self).__init__(
-                                            class_, 
-                                            key, 
+                                            class_,
+                                            key,
                                             callable_, dispatch,
                                             trackparent=trackparent,
                                             extension=extension,
-                                            compare_function=compare_function, 
+                                            compare_function=compare_function,
                                             **kwargs)
 
         if copy_function is None:
@@ -762,11 +775,11 @@ class CollectionAttributeImpl(AttributeImpl):
         if self.key in state.committed_state:
             original = state.committed_state[self.key]
             if original is not NO_VALUE:
-                current_states = [((c is not None) and 
-                                    instance_state(c) or None, c) 
+                current_states = [((c is not None) and
+                                    instance_state(c) or None, c)
                                     for c in current]
-                original_states = [((c is not None) and 
-                                    instance_state(c) or None, c) 
+                original_states = [((c is not None) and
+                                    instance_state(c) or None, c)
                                     for c in original]
 
                 current_set = dict(current_states)
@@ -853,13 +866,13 @@ class CollectionAttributeImpl(AttributeImpl):
     def pop(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
         try:
             # TODO: better solution here would be to add
-            # a "popper" role to collections.py to complement 
+            # a "popper" role to collections.py to complement
             # "remover".
             self.remove(state, dict_, value, initiator, passive=passive)
         except (ValueError, KeyError, IndexError):
             pass
 
-    def set(self, state, dict_, value, initiator, 
+    def set(self, state, dict_, value, initiator,
                     passive=PASSIVE_OFF, pop=False):
         """Set a value on the given object.
 
@@ -938,7 +951,7 @@ class CollectionAttributeImpl(AttributeImpl):
 
         return user_data
 
-    def get_collection(self, state, dict_, 
+    def get_collection(self, state, dict_,
                             user_data=None, passive=PASSIVE_OFF):
         """Retrieve the CollectionAdapter associated with the given state.
 
@@ -967,19 +980,19 @@ def backref_listeners(attribute, key, uselist):
             old_state, old_dict = instance_state(oldchild),\
                                     instance_dict(oldchild)
             impl = old_state.manager[key].impl
-            impl.pop(old_state, 
-                        old_dict, 
-                        state.obj(), 
+            impl.pop(old_state,
+                        old_dict,
+                        state.obj(),
                         initiator, passive=PASSIVE_NO_FETCH)
 
         if child is not None:
             child_state, child_dict = instance_state(child),\
                                         instance_dict(child)
             child_state.manager[key].impl.append(
-                                            child_state, 
-                                            child_dict, 
-                                            state.obj(), 
-                                            initiator, 
+                                            child_state,
+                                            child_dict,
+                                            state.obj(),
+                                            initiator,
                                             passive=PASSIVE_NO_FETCH)
         return child
 
@@ -987,10 +1000,10 @@ def backref_listeners(attribute, key, uselist):
         child_state, child_dict = instance_state(child), \
                                     instance_dict(child)
         child_state.manager[key].impl.append(
-                                            child_state, 
-                                            child_dict, 
-                                            state.obj(), 
-                                            initiator, 
+                                            child_state,
+                                            child_dict,
+                                            state.obj(),
+                                            initiator,
                                             passive=PASSIVE_NO_FETCH)
         return child
 
@@ -999,29 +1012,29 @@ def backref_listeners(attribute, key, uselist):
             child_state, child_dict = instance_state(child),\
                                         instance_dict(child)
             child_state.manager[key].impl.pop(
-                                            child_state, 
-                                            child_dict, 
-                                            state.obj(), 
+                                            child_state,
+                                            child_dict,
+                                            state.obj(),
                                             initiator,
                                             passive=PASSIVE_NO_FETCH)
 
     if uselist:
-        event.listen(attribute, "append", 
-                    emit_backref_from_collection_append_event, 
+        event.listen(attribute, "append",
+                    emit_backref_from_collection_append_event,
                     retval=True, raw=True)
     else:
-        event.listen(attribute, "set", 
-                    emit_backref_from_scalar_set_event, 
+        event.listen(attribute, "set",
+                    emit_backref_from_scalar_set_event,
                     retval=True, raw=True)
     # TODO: need coverage in test/orm/ of remove event
-    event.listen(attribute, "remove", 
-                    emit_backref_from_collection_remove_event, 
+    event.listen(attribute, "remove",
+                    emit_backref_from_collection_remove_event,
                     retval=True, raw=True)
 
 _NO_HISTORY = util.symbol('NO_HISTORY')
 _NO_STATE_SYMBOLS = frozenset([
-                        id(PASSIVE_NO_RESULT), 
-                        id(NO_VALUE), 
+                        id(PASSIVE_NO_RESULT),
+                        id(NO_VALUE),
                         id(NEVER_SET)])
 class History(tuple):
     """A 3-tuple of added, unchanged and deleted values,
@@ -1062,7 +1075,7 @@ class History(tuple):
         return not bool(
                         (self.added or self.deleted)
                         or self.unchanged and self.unchanged != [None]
-                    ) 
+                    )
 
     def sum(self):
         """Return a collection of added + unchanged + deleted."""
@@ -1114,7 +1127,7 @@ class History(tuple):
         elif attribute.is_equal(current, original) is True:
             return cls((), [current], ())
         else:
-            # current convention on native scalars is to not 
+            # current convention on native scalars is to not
             # include information
             # about missing previous value in "deleted", but
             # we do include None, which helps in some primary
@@ -1140,11 +1153,11 @@ class History(tuple):
         elif current is original:
             return cls((), [current], ())
         else:
-            # current convention on related objects is to not 
+            # current convention on related objects is to not
             # include information
             # about missing previous value in "deleted", and
             # to also not include None - the dependency.py rules
-            # ignore the None in any case.  
+            # ignore the None in any case.
             if id(original) in _NO_STATE_SYMBOLS or original is None:
                 deleted = ()
             else:
@@ -1165,11 +1178,11 @@ class History(tuple):
             return cls((), list(current), ())
         else:
 
-            current_states = [((c is not None) and instance_state(c) or None, c) 
-                                for c in current 
+            current_states = [((c is not None) and instance_state(c) or None, c)
+                                for c in current
                                 ]
-            original_states = [((c is not None) and instance_state(c) or None, c) 
-                                for c in original 
+            original_states = [((c is not None) and instance_state(c) or None, c)
+                                for c in original
                                 ]
 
             current_set = dict(current_states)
@@ -1184,7 +1197,7 @@ class History(tuple):
 HISTORY_BLANK = History(None, None, None)
 
 def get_history(obj, key, passive=PASSIVE_OFF):
-    """Return a :class:`.History` record for the given object 
+    """Return a :class:`.History` record for the given object
     and attribute key.
 
     :param obj: an object whose class is instrumented by the
@@ -1192,10 +1205,11 @@ def get_history(obj, key, passive=PASSIVE_OFF):
 
     :param key: string attribute name.
 
-    :param passive: indicates if the attribute should be
-      loaded from the database if not already present (:attr:`.PASSIVE_NO_FETCH`), and
-      if the attribute should be not initialized to a blank value otherwise
-      (:attr:`.PASSIVE_NO_INITIALIZE`). Default is :attr:`PASSIVE_OFF`.
+    :param passive: indicates loading behavior for the attribute
+       if the value is not already present.   This is a
+       bitflag attribute, which defaults to the symbol
+       :attr:`.PASSIVE_OFF` indicating all necessary SQL
+       should be emitted.
 
     """
     if passive is True:
@@ -1223,14 +1237,14 @@ def register_attribute(class_, key, **kw):
     comparator = kw.pop('comparator', None)
     parententity = kw.pop('parententity', None)
     doc = kw.pop('doc', None)
-    desc = register_descriptor(class_, key, 
+    desc = register_descriptor(class_, key,
                             comparator, parententity, doc=doc)
     register_attribute_impl(class_, key, **kw)
     return desc
 
 def register_attribute_impl(class_, key,
-        uselist=False, callable_=None, 
-        useobject=False,  
+        uselist=False, callable_=None,
+        useobject=False,
         impl_class=None, backref=None, **kw):
 
     manager = manager_of_class(class_)
@@ -1262,7 +1276,7 @@ def register_attribute_impl(class_, key,
     manager.post_configure_attribute(key)
     return manager[key]
 
-def register_descriptor(class_, key, comparator=None, 
+def register_descriptor(class_, key, comparator=None,
                                 parententity=None, doc=None):
     manager = manager_of_class(class_)
 
@@ -1288,10 +1302,10 @@ def init_collection(obj, key):
             collection_adapter.append_without_event(elem)
 
     For an easier way to do the above, see
-     :func:`~sqlalchemy.orm.attributes.set_committed_value`.
+    :func:`~sqlalchemy.orm.attributes.set_committed_value`.
 
     obj is an instrumented object instance.  An InstanceState
-    is accepted directly for backwards compatibility but 
+    is accepted directly for backwards compatibility but
     this usage is deprecated.
 
     """
@@ -1309,7 +1323,7 @@ def init_state_collection(state, dict_, key):
 def set_committed_value(instance, key, value):
     """Set the value of an attribute with no history events.
 
-    Cancels any previous history present.  The value should be 
+    Cancels any previous history present.  The value should be
     a scalar value for scalar-holding attributes, or
     an iterable for any collection-holding attribute.
 
@@ -1366,7 +1380,7 @@ def del_attribute(instance, key):
 def flag_modified(instance, key):
     """Mark an attribute on an instance as 'modified'.
 
-    This sets the 'modified' flag on the instance and 
+    This sets the 'modified' flag on the instance and
     establishes an unconditional change event for the given attribute.
 
     """
index 2b846832e70492e4be64f23efaa1ed289ce01ce9..17ffa9e7a7dba2d031723b4839d73c8ccddac3c6 100644 (file)
@@ -50,6 +50,13 @@ class InstanceState(interfaces._InspectionAttr):
 
     @util.memoized_property
     def attr(self):
+        """Return a namespace representing each attribute on
+        the mapped object, including its current value
+        and history.
+
+        The returned object is an instance of :class:`.InspectAttr`.
+
+        """
         return util.ImmutableProperties(
             dict(
                 (key, InspectAttr(self, key))
@@ -59,21 +66,25 @@ class InstanceState(interfaces._InspectionAttr):
 
     @property
     def transient(self):
+        """Return true if the object is transient."""
         return self.key is None and \
             not self._attached
 
     @property
     def pending(self):
+        """Return true if the object is pending."""
         return self.key is None and \
             self._attached
 
     @property
     def persistent(self):
+        """Return true if the object is persistent."""
         return self.key is not None and \
             self._attached
 
     @property
     def detached(self):
+        """Return true if the object is detached."""
         return self.key is not None and \
             not self._attached
 
@@ -84,14 +95,32 @@ class InstanceState(interfaces._InspectionAttr):
 
     @property
     def session(self):
+        """Return the owning :class:`.Session` for this instance,
+        or ``None`` if none available."""
+
         return sessionlib._state_session(self)
 
     @property
     def object(self):
+        """Return the mapped object represented by this
+        :class:`.InstanceState`."""
         return self.obj()
 
     @property
     def identity(self):
+        """Return the mapped identity of the mapped object.
+        This is the primary key identity as persisted by the ORM
+        which can always be passed directly to
+        :meth:`.Query.get`.
+
+        Returns ``None`` if the object has no primary key identity.
+
+        .. note::
+            An object which is transient or pending
+            does **not** have a mapped identity until it is flushed,
+            even if its attributes include primary key values.
+
+        """
         if self.key is None:
             return None
         else:
@@ -99,6 +128,14 @@ class InstanceState(interfaces._InspectionAttr):
 
     @property
     def identity_key(self):
+        """Return the identity key for the mapped object.
+
+        This is the key used to locate the object within
+        the :attr:`.Session.identity_map` mapping.   It contains
+        the identity as returned by :attr:`.identity` within it.
+
+
+        """
         # TODO: just change .key to .identity_key across
         # the board ?  probably
         return self.key
@@ -113,10 +150,17 @@ class InstanceState(interfaces._InspectionAttr):
 
     @util.memoized_property
     def mapper(self):
+        """Return the :class:`.Mapper` used for this mapepd object."""
         return self.manager.mapper
 
     @property
     def has_identity(self):
+        """Return ``True`` if this object has an identity key.
+
+        This should always have the same value as the
+        expression ``state.persistent or state.detached``.
+
+        """
         return bool(self.key)
 
     def _detach(self):
@@ -465,7 +509,14 @@ class InstanceState(interfaces._InspectionAttr):
             state._strong_obj = None
 
 class InspectAttr(object):
-    """Provide inspection interface to an object's state."""
+    """Provide an inspection interface corresponding
+    to a particular attribute on a particular mapped object.
+
+    The :class:`.InspectAttr` object is created by
+    accessing the :attr:`.InstanceState.attr`
+    collection.
+
+    """
 
     def __init__(self, state, key):
         self.state = state
@@ -473,15 +524,32 @@ class InspectAttr(object):
 
     @property
     def loaded_value(self):
+        """The current value of this attribute as loaded from the database.
+
+        If the value has not been loaded, or is otherwise not present
+        in the object's dictionary, returns NO_VALUE.
+
+        """
         return self.state.dict.get(self.key, NO_VALUE)
 
     @property
     def value(self):
+        """Return the value of this attribute.
+
+        This operation is equivalent to accessing the object's
+        attribute directly or via ``getattr()``, and will fire
+        off any pending loader callables if needed.
+
+        """
         return self.state.manager[self.key].__get__(
                         self.state.obj(), self.state.class_)
 
     @property
     def history(self):
+        """Return the current pre-flush change history for
+        this attribute, via the :class:`.History` interface.
+
+        """
         return self.state.get_history(self.key,
                     PASSIVE_NO_INITIALIZE)
 
index 517f1acb48e4364c19c364f2ba41d6579239dce7..97bf4e7a943cb9b8daf7ce9b75099c5dd0c7aaf4 100644 (file)
@@ -563,7 +563,40 @@ AliasedInsp = util.namedtuple("AliasedInsp", [
     ])
 
 class AliasedInsp(_InspectionAttr, AliasedInsp):
+    """Provide an inspection interface for an
+    :class:`.AliasedClass` object.
+
+    The :class:`.AliasedInsp` object is returned
+    given an :class:`.AliasedClass` using the
+    :func:`.inspect` function::
+
+        from sqlalchemy import inspect
+        from sqlalchemy.orm import aliased
+
+        my_alias = aliased(MyMappedClass)
+        insp = inspect(my_alias)
+
+    Attributes on :class:`.AliasedInsp`
+    include:
+
+    * ``entity`` - the :class:`.AliasedClass` represented.
+    * ``mapper`` - the :class:`.Mapper` mapping the underlying class.
+    * ``selectable`` - the :class:`.Alias` construct which ultimately
+      represents an aliased :class:`.Table` or :class:`.Select`
+      construct.
+    * ``name`` - the name of the alias.  Also is used as the attribute
+      name when returned in a result tuple from :class:`.Query`.
+    * ``with_polymorphic_mappers`` - collection of :class:`.Mapper` objects
+      indicating all those mappers expressed in the select construct
+      for the :class:`.AliasedClass`.
+    * ``polymorphic_on`` - an alternate column or SQL expression which
+      will be used as the "discriminator" for a polymorphic load.
+
+    """
+
     is_aliased_class = True
+    "always returns True"
+
 
 inspection._inspects(AliasedClass)(lambda target: target._aliased_insp)
 
@@ -864,28 +897,35 @@ def object_mapper(instance):
     """Given an object, return the primary Mapper associated with the object
     instance.
 
-    Raises UnmappedInstanceError if no mapping is configured.
+    Raises :class:`sqlalchemy.orm.exc.UnmappedInstanceError`
+    if no mapping is configured.
 
     This function is available via the inspection system as::
 
         inspect(instance).mapper
 
+    Using the inspection system will raise plain
+    :class:`sqlalchemy.exc.NoInspectionAvailable` if the instance is
+    not part of a mapping.
+
     """
     return object_state(instance).mapper
 
 def object_state(instance):
-    """Given an object, return the primary Mapper associated with the object
-    instance.
+    """Given an object, return the :class:`.InstanceState`
+    associated with the object.
 
-    Raises UnmappedInstanceError if no mapping is configured.
+    Raises :class:`sqlalchemy.orm.exc.UnmappedInstanceError`
+    if no mapping is configured.
 
-    Equivalent functionality is available via the inspection system as::
+    Equivalent functionality is available via the :func:`.inspect`
+    function as::
 
         inspect(instance)
 
-    Using the inspection system will raise plain
-    :class:`.InvalidRequestError` if the instance is not part of
-    a mapping.
+    Using the inspection system will raise
+    :class:`sqlalchemy.exc.NoInspectionAvailable` if the instance is
+    not part of a mapping.
 
     """
     state = _inspect_mapped_object(instance)
@@ -902,7 +942,8 @@ def class_mapper(class_, configure=True):
     on the given class, or :class:`.ArgumentError` if a non-class
     object is passed.
 
-    Equivalent functionality is available via the inspection system as::
+    Equivalent functionality is available via the :func:`.inspect`
+    function as::
 
         inspect(some_mapped_class)
 
index 8d08da4d85a33e424678f3d40fcf4b7f4862e22f..e9e8e35c2f22c679830098b6bf921877291341b6 100644 (file)
@@ -15,7 +15,8 @@ import re
 import sys
 import types
 import warnings
-from compat import update_wrapper, set_types, threading, callable, inspect_getfullargspec, py3k_warning
+from compat import update_wrapper, set_types, threading, \
+    callable, inspect_getfullargspec, py3k_warning
 from sqlalchemy import exc
 
 def _unique_symbols(used, *bases):
@@ -80,7 +81,7 @@ class PluginLoader(object):
 
         from sqlalchemy import exc
         raise exc.ArgumentError(
-                "Can't load plugin: %s:%s" % 
+                "Can't load plugin: %s:%s" %
                 (self.group, name))
 
     def register(self, name, modulepath, objname):
@@ -202,7 +203,8 @@ def format_argspec_plus(fn, grouped=True):
         self_arg = None
 
     # Py3K
-    #apply_pos = inspect.formatargspec(spec[0], spec[1], spec[2], None, spec[4])
+    #apply_pos = inspect.formatargspec(spec[0], spec[1],
+    #    spec[2], None, spec[4])
     #num_defaults = 0
     #if spec[3]:
     #    num_defaults += len(spec[3])
@@ -218,11 +220,12 @@ def format_argspec_plus(fn, grouped=True):
     # end Py2K
 
     if num_defaults:
-        defaulted_vals = name_args[0-num_defaults:]
+        defaulted_vals = name_args[0 - num_defaults:]
     else:
         defaulted_vals = ()
 
-    apply_kw = inspect.formatargspec(name_args, spec[1], spec[2], defaulted_vals,
+    apply_kw = inspect.formatargspec(name_args, spec[1], spec[2],
+                                        defaulted_vals,
                                      formatvalue=lambda x: '=' + x)
     if grouped:
         return dict(args=args, self_arg=self_arg,
@@ -281,7 +284,7 @@ def unbound_method_to_callable(func_or_cls):
 def generic_repr(obj):
     """Produce a __repr__() based on direct association of the __init__()
     specification vs. same-named attributes present.
-    
+
     """
     def genargs():
         try:
@@ -590,10 +593,10 @@ class importlater(object):
         from mypackage.somemodule import somesubmod
 
     except evaluted upon attribute access to "somesubmod".
-    
+
     importlater() currently requires that resolve_all() be
     called, typically at the bottom of a package's __init__.py.
-    This is so that __import__ still called only at 
+    This is so that __import__ still called only at
     module import time, and not potentially within
     a non-main thread later on.
 
@@ -623,7 +626,7 @@ class importlater(object):
         if self in importlater._unresolved:
             raise ImportError(
                     "importlater.resolve_all() hasn't "
-                    "been called (this is %s %s)" 
+                    "been called (this is %s %s)"
                     % (self._il_path, self._il_addtl))
 
         m = self._initial_import
@@ -638,14 +641,14 @@ class importlater(object):
         importlater._unresolved.discard(self)
         if self._il_addtl:
             self._initial_import = __import__(
-                                self._il_path, globals(), locals(), 
+                                self._il_path, globals(), locals(),
                                 [self._il_addtl])
         else:
             self._initial_import = __import__(self._il_path)
 
     def __getattr__(self, key):
         if key == 'module':
-            raise ImportError("Could not resolve module %s" 
+            raise ImportError("Could not resolve module %s"
                                 % self._full_path)
         try:
             attr = getattr(self.module, key)
@@ -918,8 +921,8 @@ def warn(msg, stacklevel=3):
     If msg is a string, :class:`.exc.SAWarning` is used as
     the category.
 
-    .. note:: 
-     
+    .. note::
+
        This function is swapped out when the test suite
        runs, with a compatible version that uses
        warnings.warn_explicit, so that the warnings registry can
diff --git a/test/sql/test_inspect.py b/test/sql/test_inspect.py
new file mode 100644 (file)
index 0000000..9fcba16
--- /dev/null
@@ -0,0 +1,33 @@
+"""test the inspection registry system."""
+
+from sqlalchemy import inspect
+from sqlalchemy import Table, Column, Integer, MetaData
+from test.lib import fixtures
+from test.lib.testing import is_
+
+class TestCoreInspection(fixtures.TestBase):
+
+    def test_table(self):
+        t = Table('t', MetaData(),
+            Column('x', Integer)
+            )
+
+        is_(inspect(t), t)
+        assert t.is_selectable
+        is_(t.selectable, t)
+
+    def test_select(self):
+        t = Table('t', MetaData(),
+            Column('x', Integer)
+            )
+        s = t.select()
+
+        is_(inspect(s), s)
+        assert s.is_selectable
+        is_(s.selectable, s)
+
+    def test_column_expr(self):
+        c = Column('x', Integer)
+        is_(inspect(c), c)
+        assert not c.is_selectable
+        assert not hasattr(c, 'selectable')
\ No newline at end of file