the Proxy(QueryableAttribute) object as the public facing interface. This simplifies
the descriptor system and will allow straightforward integration with attribute events.
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):
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
"""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.
"""
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
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):
'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):
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:
-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
"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)"
+ )