diff options
| -rw-r--r-- | lib/sqlalchemy/orm/attributes.py | 26 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/descriptor_props.py | 40 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/util.py | 9 | ||||
| -rw-r--r-- | test/orm/test_descriptor.py | 31 |
4 files changed, 63 insertions, 43 deletions
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index e2d2c559b..d6dc10ad4 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -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 diff --git a/lib/sqlalchemy/orm/descriptor_props.py b/lib/sqlalchemy/orm/descriptor_props.py index 4c4bc821f..347f9bce9 100644 --- a/lib/sqlalchemy/orm/descriptor_props.py +++ b/lib/sqlalchemy/orm/descriptor_props.py @@ -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): diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index 52e250239..4a8b1713c 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -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: diff --git a/test/orm/test_descriptor.py b/test/orm/test_descriptor.py index b36735e8e..a09e13047 100644 --- a/test/orm/test_descriptor.py +++ b/test/orm/test_descriptor.py @@ -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)" + ) |
