From 4f8f6b3989327398c048fa55bc2ed8f26fb022bd Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Tue, 28 Dec 2010 17:50:36 -0500 Subject: [PATCH] - 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. --- lib/sqlalchemy/orm/attributes.py | 26 +++++++++++------ lib/sqlalchemy/orm/descriptor_props.py | 40 ++++++++------------------ lib/sqlalchemy/orm/util.py | 9 +++--- 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 e2d2c559b0..d6dc10ad47 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 4c4bc821fb..347f9bce9a 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 52e250239b..4a8b1713c7 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 b36735e8e7..a09e130471 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)" + ) -- 2.47.2