mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-06-04 23:06:24 -04:00
- Synonyms riding on top of existing descriptors are now full proxies
to those descriptors.
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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."""
|
||||
|
||||
Reference in New Issue
Block a user