# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
-import weakref, threading, operator
+import weakref, threading, operator, inspect
from itertools import chain
import UserDict
from sqlalchemy import util
property = property(_property, doc="the MapperProperty object associated with this attribute")
class ProxiedAttribute(InstrumentedAttribute):
- """a 'proxy' attribute which adds InstrumentedAttribute
- class-level behavior to any user-defined class property.
+ """Adds InstrumentedAttribute class-level behavior to a regular descriptor.
+
+ Obsoleted by proxied_attribute_factory.
"""
class ProxyImpl(object):
def __delete__(self, instance):
return self.user_prop.__delete__(instance)
+def proxied_attribute_factory(descriptor):
+ """Create an InstrumentedAttribute / user descriptor hybrid.
+
+ Returns a new InstrumentedAttribute type that delegates
+ descriptor behavior and getattr() to the given descriptor.
+ """
+
+ class ProxyImpl(object):
+ accepts_scalar_loader = False
+ def __init__(self, key):
+ self.key = key
+
+ class Proxy(InstrumentedAttribute):
+ """A combination of InsturmentedAttribute and a regular descriptor."""
+
+ def __init__(self, key, descriptor, comparator):
+ self.key = key
+ # maintain ProxiedAttribute.user_prop compatability.
+ self.descriptor = self.user_prop = descriptor
+ self._comparator = comparator
+ self.impl = ProxyImpl(key)
+
+ def comparator(self):
+ if callable(self._comparator):
+ self._comparator = self._comparator()
+ return self._comparator
+ comparator = property(comparator)
+
+ def __get__(self, instance, owner):
+ """Delegate __get__ to the original descriptor."""
+ if instance is None:
+ descriptor.__get__(instance, owner)
+ return self
+ return descriptor.__get__(instance, owner)
+
+ def __set__(self, instance, value):
+ """Delegate __set__ to the original descriptor."""
+ return descriptor.__set__(instance, value)
+
+ def __delete__(self, instance):
+ """Delegate __delete__ to the original descriptor."""
+ return descriptor.__delete__(instance)
+
+ def __getattr__(self, attribute):
+ """Delegate __getattr__ to the original descriptor."""
+ return getattr(descriptor, attribute)
+ Proxy.__name__ = type(descriptor).__name__ + 'Proxy'
+
+ util.monkeypatch_proxied_specials(Proxy, type(descriptor),
+ name='descriptor',
+ from_instance=descriptor)
+ return Proxy
+
class AttributeImpl(object):
"""internal implementation for instrumented attributes."""
return
if proxy_property:
- inst = ProxiedAttribute(key, proxy_property, comparator=comparator)
+ proxy_type = proxied_attribute_factory(proxy_property)
+ inst = proxy_type(key, proxy_property, comparator)
else:
inst = InstrumentedAttribute(_create_prop(class_, key, uselist, callable_, useobject=useobject,
typecallable=typecallable, mutable_scalars=mutable_scalars, **kwargs), comparator=comparator)
except:
warn("%s('%s') ignored" % sys.exc_info()[0:2])
+def monkeypatch_proxied_specials(into_cls, from_cls, skip=None, only=None,
+ name='self.proxy', from_instance=None):
+ """Automates delegation of __specials__ for a proxying type."""
+
+ if only:
+ dunders = only
+ else:
+ if skip is None:
+ skip = ('__slots__', '__del__', '__getattribute__',
+ '__metaclass__', '__getstate__', '__setstate__')
+ dunders = [m for m in dir(from_cls)
+ if (m.startswith('__') and m.endswith('__') and
+ not hasattr(into_cls, m) and m not in skip)]
+ for method in dunders:
+ try:
+ spec = inspect.getargspec(getattr(from_cls, method))
+ fn_args = inspect.formatargspec(spec[0])
+ d_args = inspect.formatargspec(spec[0][1:])
+ except TypeError:
+ fn_args = '(self, *args, **kw)'
+ d_args = '(*args, **kw)'
+
+ py = ("def %(method)s%(fn_args)s: "
+ "return %(name)s.%(method)s%(d_args)s" % locals())
+
+ env = from_instance is not None and {name: from_instance} or {}
+ exec py in env
+ setattr(into_cls, method, env[method])
+
class SimpleProperty(object):
"""A *default* property accessor."""
sess = create_session()
assert_col = []
+ class extendedproperty(property):
+ attribute = 123
+ def __getitem__(self, key):
+ return 'value'
+
class User(object):
def _get_user_name(self):
assert_col.append(('get', self.user_name))
def _set_user_name(self, name):
assert_col.append(('set', name))
self.user_name = name
- uname = property(_get_user_name, _set_user_name)
+ uname = extendedproperty(_get_user_name, _set_user_name)
mapper(User, users, properties = dict(
- addresses = relation(mapper(Address, addresses), lazy = True),
+ addresses = relation(mapper(Address, addresses), lazy=True),
uname = synonym('user_name'),
adlist = synonym('addresses', proxy=True),
adname = synonym('addresses')
assert u.user_name == "some user name"
assert u in sess.dirty
+ assert User.uname.attribute == 123
+ assert User.uname['key'] == 'value'
def test_column_synonyms(self):
"""test new-style synonyms which automatically instrument properties, set up aliased column, etc."""