summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Kirtland <jek@discorporate.us>2007-06-14 00:11:51 +0000
committerJason Kirtland <jek@discorporate.us>2007-06-14 00:11:51 +0000
commit84420a1d0fe09d7a45e878e853aa9f5258561f8b (patch)
tree2e18c224be2811d527581518fb23b8dd6280d69f
parent2c65ce75360c6018ecc6160d6ef11e23ae628553 (diff)
downloadsqlalchemy-84420a1d0fe09d7a45e878e853aa9f5258561f8b.tar.gz
- Iteration over dict association proxies is now dict-like, not
InstrumentedList-like (e.g. over keys instead of values). - Don't tightly bind proxies to source collections (fixes #597) - Handle slice objects on orderinglist's __setitem__
-rw-r--r--CHANGES5
-rw-r--r--lib/sqlalchemy/ext/associationproxy.py63
-rw-r--r--lib/sqlalchemy/ext/orderinglist.py8
-rw-r--r--test/ext/associationproxy.py13
4 files changed, 65 insertions, 24 deletions
diff --git a/CHANGES b/CHANGES
index 0abb91de3..62118dab1 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,8 @@
+- ext
+ - iteration over dict association proxies is now dict-like, not
+ InstrumentedList-like (e.g. over keys instead of values)
+ - association proxies no longer bind tightly to source collections
+ [ticket:597], and are constructed with a thunk instead
- orm
- remember all that stuff about polymorphic_union ? for
joined table inheritance ? Funny thing...
diff --git a/lib/sqlalchemy/ext/associationproxy.py b/lib/sqlalchemy/ext/associationproxy.py
index d6a995450..1b363c9ac 100644
--- a/lib/sqlalchemy/ext/associationproxy.py
+++ b/lib/sqlalchemy/ext/associationproxy.py
@@ -10,6 +10,7 @@ from sqlalchemy.orm.attributes import InstrumentedList
import sqlalchemy.exceptions as exceptions
import sqlalchemy.orm as orm
import sqlalchemy.util as util
+import weakref
def association_proxy(targetcollection, attr, **kw):
"""Convenience function for use in mapped classes. Implements a Python
@@ -116,6 +117,18 @@ class AssociationProxy(object):
def _target_is_scalar(self):
return not self._get_property().uselist
+
+ def _lazy_collection(self, weakobjref):
+ target = self.target_collection
+ del self
+ def lazy_collection():
+ obj = weakobjref()
+ if obj is None:
+ raise exceptions.InvalidRequestError(
+ "stale association proxy, parent object has gone out of "
+ "scope")
+ return getattr(obj, target)
+ return lazy_collection
def __get__(self, obj, class_):
if obj is None:
@@ -130,7 +143,7 @@ class AssociationProxy(object):
try:
return getattr(obj, self.key)
except AttributeError:
- proxy = self._new(getattr(obj, self.target_collection))
+ proxy = self._new(self._lazy_collection(weakref.ref(obj)))
setattr(obj, self.key, proxy)
return proxy
@@ -153,30 +166,32 @@ class AssociationProxy(object):
def __delete__(self, obj):
delattr(obj, self.key)
- def _new(self, collection):
+ def _new(self, lazy_collection):
creator = self.creator and self.creator or self.target_class
# Prefer class typing here to spot dicts with the required append()
# method.
+ collection = lazy_collection()
if isinstance(collection.data, dict):
self.collection_class = dict
else:
self.collection_class = util.duck_type_collection(collection.data)
+ del collection
if self.proxy_factory:
- return self.proxy_factory(collection, creator, self.value_attr)
+ return self.proxy_factory(lazy_collection, creator, self.value_attr)
value_attr = self.value_attr
getter = lambda o: getattr(o, value_attr)
setter = lambda o, v: setattr(o, value_attr, v)
if self.collection_class is list:
- return _AssociationList(collection, creator, getter, setter)
+ return _AssociationList(lazy_collection, creator, getter, setter)
elif self.collection_class is dict:
kv_setter = lambda o, k, v: setattr(o, value_attr, v)
- return _AssociationDict(collection, creator, getter, setter)
+ return _AssociationDict(lazy_collection, creator, getter, setter)
elif self.collection_class is util.Set:
- return _AssociationSet(collection, creator, getter, setter)
+ return _AssociationSet(lazy_collection, creator, getter, setter)
else:
raise exceptions.ArgumentError(
'could not guess which interface to use for '
@@ -203,11 +218,11 @@ class _AssociationList(object):
converting association objects to and from a simplified value.
"""
- def __init__(self, collection, creator, getter, setter):
+ def __init__(self, lazy_collection, creator, getter, setter):
"""
- collection
- A list-based collection of entities (usually an object attribute
- managed by a SQLAlchemy relation())
+ lazy_collection
+ A callable returning a list-based collection of entities (usually
+ an object attribute managed by a SQLAlchemy relation())
creator
A function that creates new target entities. Given one parameter:
@@ -223,11 +238,13 @@ class _AssociationList(object):
that value on the object.
"""
- self.col = collection
+ self.lazy_collection = lazy_collection
self.creator = creator
self.getter = getter
self.setter = setter
+ col = property(lambda self: self.lazy_collection())
+
# For compatibility with 0.3.1 through 0.3.7- pass kw through to creator.
# (see append() below)
def _create(self, value, **kw):
@@ -320,11 +337,11 @@ class _AssociationDict(object):
converting association objects to and from a simplified value.
"""
- def __init__(self, collection, creator, getter, setter):
+ def __init__(self, lazy_collection, creator, getter, setter):
"""
- collection
- A list-based collection of entities (usually an object attribute
- managed by a SQLAlchemy relation())
+ lazy_collection
+ A callable returning a dict-based collection of entities (usually
+ an object attribute managed by a SQLAlchemy relation())
creator
A function that creates new target entities. Given two parameters:
@@ -340,11 +357,13 @@ class _AssociationDict(object):
that value on the object.
"""
- self.col = collection
+ self.lazy_collection = lazy_collection
self.creator = creator
self.getter = getter
self.setter = setter
+ col = property(lambda self: self.lazy_collection())
+
def _create(self, key, value):
return self.creator(key, value)
@@ -380,7 +399,7 @@ class _AssociationDict(object):
has_key = __contains__
def __iter__(self):
- return iter(self.col)
+ return self.col.iterkeys()
def clear(self):
self.col.clear()
@@ -465,11 +484,11 @@ class _AssociationSet(object):
converting association objects to and from a simplified value.
"""
- def __init__(self, collection, creator, getter, setter):
+ def __init__(self, lazy_collection, creator, getter, setter):
"""
collection
- A list-based collection of entities (usually an object attribute
- managed by a SQLAlchemy relation())
+ A callable returning a set-based collection of entities (usually an
+ object attribute managed by a SQLAlchemy relation())
creator
A function that creates new target entities. Given one parameter:
@@ -485,11 +504,13 @@ class _AssociationSet(object):
that value on the object.
"""
- self.col = collection
+ self.lazy_collection = lazy_collection
self.creator = creator
self.getter = getter
self.setter = setter
+ col = property(lambda self: self.lazy_collection())
+
def _create(self, value):
return self.creator(value)
diff --git a/lib/sqlalchemy/ext/orderinglist.py b/lib/sqlalchemy/ext/orderinglist.py
index 27ff408dc..e02990a26 100644
--- a/lib/sqlalchemy/ext/orderinglist.py
+++ b/lib/sqlalchemy/ext/orderinglist.py
@@ -165,8 +165,12 @@ class OrderingList(list):
return entity
def __setitem__(self, index, entity):
- super(OrderingList, self).__setitem__(index, entity)
- self._order_entity(index, entity, True)
+ if isinstance(index, slice):
+ for i in range(index.start or 0, index.stop or 0, index.step or 1):
+ self.__setitem__(i, entity[i])
+ else:
+ self._order_entity(index, entity, True)
+ super(OrderingList, self).__setitem__(index, entity)
def __delitem__(self, index):
super(OrderingList, self).__delitem__(index)
diff --git a/test/ext/associationproxy.py b/test/ext/associationproxy.py
index b6476c836..e095d285e 100644
--- a/test/ext/associationproxy.py
+++ b/test/ext/associationproxy.py
@@ -130,7 +130,10 @@ class _CollectionOperations(PersistTest):
self.assert_(len(p1._children) == 3)
self.assert_(len(p1.children) == 3)
-
+
+ p1._children = []
+ self.assert_(len(p1.children) == 0)
+
class DefaultTest(_CollectionOperations):
def __init__(self, *args, **kw):
super(DefaultTest, self).__init__(*args, **kw)
@@ -208,10 +211,15 @@ class CustomDictTest(DictTest):
self.assert_(len(p1._children) == 3)
self.assert_(len(p1.children) == 3)
+ self.assert_(set(p1.children) == set(['d','e','f']))
+
del ch
p1 = self.roundtrip(p1)
self.assert_(len(p1._children) == 3)
self.assert_(len(p1.children) == 3)
+
+ p1._children = {}
+ self.assert_(len(p1.children) == 0)
class SetTest(_CollectionOperations):
@@ -311,6 +319,9 @@ class SetTest(_CollectionOperations):
p1 = self.roundtrip(p1)
self.assert_(p1.children == set(['c']))
+ p1._children = []
+ self.assert_(len(p1.children) == 0)
+
def test_set_comparisons(self):
Parent, Child = self.Parent, self.Child