summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2012-12-29 19:31:28 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2012-12-29 19:31:28 -0500
commitb30ef87d27898434188462455f3c850297e335d1 (patch)
tree58eddbad639eb05e1756d81db57822e8a4465ae2 /lib/sqlalchemy
parent559b83312c44f7ebfe94bf7bd3b2de3133c5af9e (diff)
downloadsqlalchemy-b30ef87d27898434188462455f3c850297e335d1.tar.gz
Extended the :doc:`/core/inspection` system so that all Python descriptors
associated with the ORM or its extensions can be retrieved. This fulfills the common request of being able to inspect all :class:`.QueryableAttribute` descriptors in addition to extension types such as :class:`.hybrid_property` and :class:`.AssociationProxy`. See :attr:`.Mapper.all_orm_descriptors`.
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/ext/associationproxy.py17
-rw-r--r--lib/sqlalchemy/ext/hybrid.py35
-rw-r--r--lib/sqlalchemy/orm/attributes.py26
-rw-r--r--lib/sqlalchemy/orm/instrumentation.py20
-rw-r--r--lib/sqlalchemy/orm/interfaces.py65
-rw-r--r--lib/sqlalchemy/orm/mapper.py37
6 files changed, 191 insertions, 9 deletions
diff --git a/lib/sqlalchemy/ext/associationproxy.py b/lib/sqlalchemy/ext/associationproxy.py
index f6c0764e4..793a5fde9 100644
--- a/lib/sqlalchemy/ext/associationproxy.py
+++ b/lib/sqlalchemy/ext/associationproxy.py
@@ -16,7 +16,7 @@ import itertools
import operator
import weakref
from .. import exc, orm, util
-from ..orm import collections
+from ..orm import collections, interfaces
from ..sql import not_
@@ -75,9 +75,22 @@ def association_proxy(target_collection, attr, **kw):
return AssociationProxy(target_collection, attr, **kw)
-class AssociationProxy(object):
+ASSOCIATION_PROXY = util.symbol('ASSOCIATION_PROXY')
+"""Symbol indicating an :class:`_InspectionAttr` that's
+ of type :class:`.AssociationProxy`.
+
+ Is assigned to the :attr:`._InspectionAttr.extension_type`
+ attibute.
+
+"""
+
+class AssociationProxy(interfaces._InspectionAttr):
"""A descriptor that presents a read/write view of an object attribute."""
+ is_attribute = False
+ extension_type = ASSOCIATION_PROXY
+
+
def __init__(self, target_collection, attr, creator=None,
getset_factory=None, proxy_factory=None,
proxy_bulk_set=None):
diff --git a/lib/sqlalchemy/ext/hybrid.py b/lib/sqlalchemy/ext/hybrid.py
index 047b2ff95..b274aa766 100644
--- a/lib/sqlalchemy/ext/hybrid.py
+++ b/lib/sqlalchemy/ext/hybrid.py
@@ -628,13 +628,41 @@ there's probably a whole lot of amazing things it can be used for.
from .. import util
from ..orm import attributes, interfaces
+HYBRID_METHOD = util.symbol('HYBRID_METHOD')
+"""Symbol indicating an :class:`_InspectionAttr` that's
+ of type :class:`.hybrid_method`.
-class hybrid_method(object):
+ Is assigned to the :attr:`._InspectionAttr.extension_type`
+ attibute.
+
+ .. seealso::
+
+ :attr:`.Mapper.all_orm_attributes`
+
+"""
+
+HYBRID_PROPERTY = util.symbol('HYBRID_PROPERTY')
+"""Symbol indicating an :class:`_InspectionAttr` that's
+ of type :class:`.hybrid_method`.
+
+ Is assigned to the :attr:`._InspectionAttr.extension_type`
+ attibute.
+
+ .. seealso::
+
+ :attr:`.Mapper.all_orm_attributes`
+
+"""
+
+class hybrid_method(interfaces._InspectionAttr):
"""A decorator which allows definition of a Python object method with both
instance-level and class-level behavior.
"""
+ is_attribute = True
+ extension_type = HYBRID_METHOD
+
def __init__(self, func, expr=None):
"""Create a new :class:`.hybrid_method`.
@@ -669,12 +697,15 @@ class hybrid_method(object):
return self
-class hybrid_property(object):
+class hybrid_property(interfaces._InspectionAttr):
"""A decorator which allows definition of a Python descriptor with both
instance-level and class-level behavior.
"""
+ is_attribute = True
+ extension_type = HYBRID_PROPERTY
+
def __init__(self, fget, fset=None, fdel=None, expr=None):
"""Create a new :class:`.hybrid_property`.
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py
index d2f20e94d..c3a297119 100644
--- a/lib/sqlalchemy/orm/attributes.py
+++ b/lib/sqlalchemy/orm/attributes.py
@@ -120,7 +120,23 @@ PASSIVE_ONLY_PERSISTENT = util.symbol("PASSIVE_ONLY_PERSISTENT",
class QueryableAttribute(interfaces._MappedAttribute,
interfaces._InspectionAttr,
interfaces.PropComparator):
- """Base class for class-bound attributes. """
+ """Base class for :term:`descriptor` objects that intercept
+ attribute events on behalf of a :class:`.MapperProperty`
+ object. The actual :class:`.MapperProperty` is accessible
+ via the :attr:`.QueryableAttribute.property`
+ attribute.
+
+
+ .. seealso::
+
+ :class:`.InstrumentedAttribute`
+
+ :class:`.MapperProperty`
+
+ :attr:`.Mapper.all_orm_descriptors`
+
+ :attr:`.Mapper.attrs`
+ """
is_attribute = True
@@ -231,7 +247,13 @@ inspection._self_inspects(QueryableAttribute)
class InstrumentedAttribute(QueryableAttribute):
- """Class bound instrumented attribute which adds descriptor methods."""
+ """Class bound instrumented attribute which adds basic
+ :term:`descriptor` methods.
+
+ See :class:`.QueryableAttribute` for a description of most features.
+
+
+ """
def __set__(self, instance, value):
self.impl.set(instance_state(instance),
diff --git a/lib/sqlalchemy/orm/instrumentation.py b/lib/sqlalchemy/orm/instrumentation.py
index 5a4fc2093..cfd5b600c 100644
--- a/lib/sqlalchemy/orm/instrumentation.py
+++ b/lib/sqlalchemy/orm/instrumentation.py
@@ -29,7 +29,7 @@ alternate instrumentation forms.
"""
-from . import exc, collections, events
+from . import exc, collections, events, interfaces
from operator import attrgetter
from .. import event, util
state = util.importlater("sqlalchemy.orm", "state")
@@ -83,6 +83,24 @@ class ClassManager(dict):
# raises unless self.mapper has been assigned
raise exc.UnmappedClassError(self.class_)
+ def _all_sqla_attributes(self, exclude=None):
+ """return an iterator of all classbound attributes that are
+ implement :class:`._InspectionAttr`.
+
+ This includes :class:`.QueryableAttribute` as well as extension
+ types such as :class:`.hybrid_property` and :class:`.AssociationProxy`.
+
+ """
+ if exclude is None:
+ exclude = set()
+ for supercls in self.class_.__mro__:
+ for key in set(supercls.__dict__).difference(exclude):
+ exclude.add(key)
+ val = supercls.__dict__[key]
+ if isinstance(val, interfaces._InspectionAttr):
+ yield key, val
+
+
def _attr_has_impl(self, key):
"""Return True if the given attribute is fully initialized.
diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py
index 55a980b2e..654bc40cf 100644
--- a/lib/sqlalchemy/orm/interfaces.py
+++ b/lib/sqlalchemy/orm/interfaces.py
@@ -53,18 +53,79 @@ from .deprecated_interfaces import AttributeExtension, \
MapperExtension
+NOT_EXTENSION = util.symbol('NOT_EXTENSION')
+"""Symbol indicating an :class:`_InspectionAttr` that's
+ not part of sqlalchemy.ext.
+
+ Is assigned to the :attr:`._InspectionAttr.extension_type`
+ attibute.
+
+"""
+
class _InspectionAttr(object):
- """Define a series of attributes that all ORM inspection
- targets need to have."""
+ """A base class applied to all ORM objects that can be returned
+ by the :func:`.inspect` function.
+
+ The attributes defined here allow the usage of simple boolean
+ checks to test basic facts about the object returned.
+
+ While the boolean checks here are basically the same as using
+ the Python isinstance() function, the flags here can be used without
+ the need to import all of these classes, and also such that
+ the SQLAlchemy class system can change while leaving the flags
+ here intact for forwards-compatibility.
+
+ """
is_selectable = False
+ """Return True if this object is an instance of :class:`.Selectable`."""
+
is_aliased_class = False
+ """True if this object is an instance of :class:`.AliasedClass`."""
+
is_instance = False
+ """True if this object is an instance of :class:`.InstanceState`."""
+
is_mapper = False
+ """True if this object is an instance of :class:`.Mapper`."""
+
is_property = False
+ """True if this object is an instance of :class:`.MapperProperty`."""
+
is_attribute = False
+ """True if this object is a Python :term:`descriptor`.
+
+ This can refer to one of many types. Usually a
+ :class:`.QueryableAttribute` which handles attributes events on behalf
+ of a :class:`.MapperProperty`. But can also be an extension type
+ such as :class:`.AssociationProxy` or :class:`.hybrid_property`.
+ The :attr:`._InspectionAttr.extension_type` will refer to a constant
+ identifying the specific subtype.
+
+ .. seealso::
+
+ :attr:`.Mapper.all_orm_descriptors`
+
+ """
+
is_clause_element = False
+ """True if this object is an instance of :class:`.ClauseElement`."""
+
+ extension_type = NOT_EXTENSION
+ """The extension type, if any.
+ Defaults to :data:`.interfaces.NOT_EXTENSION`
+ .. versionadded:: 0.8.0
+
+ .. seealso::
+
+ :data:`.HYBRID_METHOD`
+
+ :data:`.HYBRID_PROPERTY`
+
+ :data:`.ASSOCIATION_PROXY`
+
+ """
class _MappedAttribute(object):
"""Mixin for attributes which should be replaced by mapper-assigned
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py
index 626105b5e..6d8fa4dbb 100644
--- a/lib/sqlalchemy/orm/mapper.py
+++ b/lib/sqlalchemy/orm/mapper.py
@@ -1502,12 +1502,49 @@ class Mapper(_InspectionAttr):
returned, inclding :attr:`.synonyms`, :attr:`.column_attrs`,
:attr:`.relationships`, and :attr:`.composites`.
+ .. seealso::
+
+ :attr:`.Mapper.all_orm_descriptors`
"""
if _new_mappers:
configure_mappers()
return util.ImmutableProperties(self._props)
+ @util.memoized_property
+ def all_orm_descriptors(self):
+ """A namespace of all :class:`._InspectionAttr` attributes associated
+ with the mapped class.
+
+ These attributes are in all cases Python :term:`descriptors` associated
+ with the mapped class or its superclasses.
+
+ This namespace includes attributes that are mapped to the class
+ as well as attributes declared by extension modules.
+ It includes any Python descriptor type that inherits from
+ :class:`._InspectionAttr`. This includes :class:`.QueryableAttribute`,
+ as well as extension types such as :class:`.hybrid_property`,
+ :class:`.hybrid_method` and :class:`.AssociationProxy`.
+
+ To distinguish between mapped attributes and extension attributes,
+ the attribute :attr:`._InspectionAttr.extension_type` will refer
+ to a constant that distinguishes between different extension types.
+
+ When dealing with a :class:`.QueryableAttribute`, the
+ :attr:`.QueryableAttribute.property` attribute refers to the
+ :class:`.MapperProperty` property, which is what you get when referring
+ to the collection of mapped properties via :attr:`.Mapper.attrs`.
+
+ .. versionadded:: 0.8.0
+
+ .. seealso::
+
+ :attr:`.Mapper.attrs`
+
+ """
+ return util.ImmutableProperties(
+ dict(self.class_manager._all_sqla_attributes()))
+
@_memoized_configured_property
def synonyms(self):
"""Return a namespace of all :class:`.SynonymProperty`