- Synonyms riding on top of existing descriptors are now full proxies

to those descriptors.
This commit is contained in:
Jason Kirtland
2008-03-06 14:12:22 +00:00
parent 06d55b8e1d
commit 4f6d0ff71e
4 changed files with 101 additions and 7 deletions
+4 -1
View File
@@ -97,7 +97,10 @@ CHANGES
many-to-many "secondary" table will now work with eager
loading, previously the "order by" wasn't aliased against
the secondary table's alias.
- Synonyms riding on top of existing descriptors are now
full proxies to those descriptors.
- dialects
- Invalid SQLite connection URLs now raise an error.
+59 -4
View File
@@ -4,7 +4,7 @@
# 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
@@ -67,8 +67,9 @@ class InstrumentedAttribute(interfaces.PropComparator):
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):
@@ -101,6 +102,59 @@ class ProxiedAttribute(InstrumentedAttribute):
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."""
@@ -1233,7 +1287,8 @@ def register_attribute(class_, key, uselist, useobject, callable_=None, proxy_pr
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)
+29
View File
@@ -344,6 +344,35 @@ def warn_exception(func, *args, **kwargs):
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."""
+9 -2
View File
@@ -545,6 +545,11 @@ class MapperTest(MapperSuperTest):
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))
@@ -552,10 +557,10 @@ class MapperTest(MapperSuperTest):
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')
@@ -585,6 +590,8 @@ class MapperTest(MapperSuperTest):
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."""