diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-12-29 19:31:28 -0500 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-12-29 19:31:28 -0500 |
| commit | b30ef87d27898434188462455f3c850297e335d1 (patch) | |
| tree | 58eddbad639eb05e1756d81db57822e8a4465ae2 /lib/sqlalchemy | |
| parent | 559b83312c44f7ebfe94bf7bd3b2de3133c5af9e (diff) | |
| download | sqlalchemy-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.py | 17 | ||||
| -rw-r--r-- | lib/sqlalchemy/ext/hybrid.py | 35 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/attributes.py | 26 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/instrumentation.py | 20 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/interfaces.py | 65 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 37 |
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` |
