- simplified the descriptor system to no longer use the hybrid extension, instead presenting

the Proxy(QueryableAttribute) object as the public facing interface.   This simplifies
the descriptor system and will allow straightforward integration with attribute events.
This commit is contained in:
Mike Bayer
2010-12-28 17:50:36 -05:00
parent 7f8ca36036
commit 4f8f6b3989
4 changed files with 63 additions and 43 deletions
+18 -8
View File
@@ -145,16 +145,25 @@ def create_proxied_attribute(descriptor):
Returns a new QueryableAttribute type that delegates descriptor
behavior and getattr() to the given descriptor.
"""
# TODO: can move this to descriptor_props if the need for this
# function is removed from ext/hybrid.py
class Proxy(QueryableAttribute):
"""A combination of InsturmentedAttribute and a regular descriptor."""
"""Presents the :class:`.QueryableAttribute` interface as a
proxy on top of a Python descriptor / :class:`.PropComparator`
combination.
"""
def __init__(self, class_, key, descriptor, comparator, adapter=None):
def __init__(self, class_, key, descriptor, comparator,
adapter=None, doc=None):
self.class_ = class_
self.key = key
self.descriptor = descriptor
self._comparator = comparator
self.adapter = adapter
self.__doc__ = doc
@util.memoized_property
def comparator(self):
@@ -164,11 +173,12 @@ def create_proxied_attribute(descriptor):
self._comparator = self._comparator.adapted(self.adapter)
return self._comparator
def adapted(self, adapter):
return self.__class__(self.class_, self.key, self.descriptor,
self._comparator,
adapter)
def __get__(self, instance, owner):
if instance is None:
return self
else:
return self.descriptor.__get__(instance, owner)
def __str__(self):
return self.key
+12 -28
View File
@@ -7,8 +7,7 @@
"""Descriptor proprerties are more "auxilliary" properties
that exist as configurational elements, but don't participate
as actively in the load/persist ORM loop. They all
build on the "hybrid" extension to produce class descriptors.
as actively in the load/persist ORM loop.
"""
@@ -22,13 +21,12 @@ properties = util.importlater('sqlalchemy.orm', 'properties')
class DescriptorProperty(MapperProperty):
""":class:`MapperProperty` which proxies access to a
user-defined descriptor."""
def instrument_class(self, mapper):
from sqlalchemy.ext import hybrid
doc = None
def instrument_class(self, mapper):
prop = self
# hackety hack hack
class _ProxyImpl(object):
accepts_scalar_loader = False
expire_missing = True
@@ -52,39 +50,25 @@ class DescriptorProperty(MapperProperty):
delattr(obj, self.name)
def fget(obj):
return getattr(obj, self.name)
fget.__doc__ = self.doc
descriptor = hybrid.property_(
self.descriptor = property(
fget=fget,
fset=fset,
fdel=fdel,
)
elif isinstance(self.descriptor, property):
descriptor = hybrid.property_(
fget=self.descriptor.fget,
fset=self.descriptor.fset,
fdel=self.descriptor.fdel,
)
else:
descriptor = hybrid.property_(
fget=self.descriptor.__get__,
fset=self.descriptor.__set__,
fdel=self.descriptor.__delete__,
)
proxy_attr = attributes.\
create_proxied_attribute(self.descriptor or descriptor)\
create_proxied_attribute(self.descriptor)\
(
self.parent.class_,
self.key,
self.descriptor or descriptor,
lambda: self._comparator_factory(mapper)
self.descriptor,
lambda: self._comparator_factory(mapper),
doc=self.doc
)
def get_comparator(owner):
return util.update_wrapper(proxy_attr, descriptor)
descriptor.expr = get_comparator
descriptor.impl = _ProxyImpl(self.key)
mapper.class_manager.instrument_attribute(self.key, descriptor)
proxy_attr.impl = _ProxyImpl(self.key)
mapper.class_manager.instrument_attribute(self.key, proxy_attr)
class CompositeProperty(DescriptorProperty):
+4 -5
View File
@@ -247,13 +247,12 @@ class AliasedClass(object):
'parentmapper':self.__mapper}
)
def __adapt_prop(self, prop):
existing = getattr(self.__target, prop.key)
def __adapt_prop(self, existing, key):
comparator = existing.comparator.adapted(self.__adapt_element)
queryattr = attributes.QueryableAttribute(self, prop.key,
queryattr = attributes.QueryableAttribute(self, key,
impl=existing.impl, parententity=self, comparator=comparator)
setattr(self, prop.key, queryattr)
setattr(self, key, queryattr)
return queryattr
def __getattr__(self, key):
@@ -268,7 +267,7 @@ class AliasedClass(object):
raise AttributeError(key)
if isinstance(attr, attributes.QueryableAttribute):
return self.__adapt_prop(attr.property)
return self.__adapt_prop(attr, key)
elif hasattr(attr, 'func_code'):
is_method = getattr(self.__target, key, None)
if is_method and is_method.im_self is not None:
+29 -2
View File
@@ -1,7 +1,8 @@
from sqlalchemy.orm import descriptor_props
from sqlalchemy.orm import descriptor_props, aliased
from sqlalchemy.orm.interfaces import PropComparator
from sqlalchemy.orm.properties import ColumnProperty
from sqlalchemy.sql import column
from sqlalchemy import Column, Integer, func
from sqlalchemy import Column, Integer, func, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.util import partial
from test.orm import _base
@@ -104,3 +105,29 @@ class DescriptorInstrumentationTest(_base.ORMTest):
"foo = upper(:upper_1)"
)
def test_aliased_comparator(self):
class Comparator(ColumnProperty.Comparator):
__hash__ = None
def __eq__(self, other):
return func.foobar(self.__clause_element__()) ==\
func.foobar(other)
Foo = self._fixture()
Foo._name = Column('name', String)
def comparator_factory(self, mapper):
prop = mapper._props['_name']
return Comparator(prop, mapper)
d = TestDescriptor(Foo, 'foo', comparator_factory=comparator_factory)
d.instrument_class(Foo.__mapper__)
eq_(
str(Foo.foo == 'ed'),
"foobar(foo.name) = foobar(:foobar_1)"
)
eq_(
str(aliased(Foo).foo == 'ed'),
"foobar(foo_1.name) = foobar(:foobar_1)"
)