summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2006-11-03 01:17:28 +0000
committerMike Bayer <mike_mp@zzzcomputing.com>2006-11-03 01:17:28 +0000
commit695f65db853a7b74a1ce2da75d8e3c55bbafae81 (patch)
treedd66a8f126fa1c44cfb7349c319168ab6c3b0a0f /lib/sqlalchemy
parent14845494113b5327b2ed7f8ed13aed9bc9ce27b2 (diff)
downloadsqlalchemy-695f65db853a7b74a1ce2da75d8e3c55bbafae81.tar.gz
- added an assertion within the "cascade" step of ORM relationships to check
that the class of object attached to a parent object is appropriate (i.e. if A.items stores B objects, raise an error if a C is appended to A.items) - new extension sqlalchemy.ext.associationproxy, provides transparent "association object" mappings. new example examples/association/proxied_association.py illustrates. - some example cleanup
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/ext/associationproxy.py96
-rw-r--r--lib/sqlalchemy/orm/__init__.py2
-rw-r--r--lib/sqlalchemy/orm/mapper.py2
-rw-r--r--lib/sqlalchemy/orm/properties.py4
4 files changed, 102 insertions, 2 deletions
diff --git a/lib/sqlalchemy/ext/associationproxy.py b/lib/sqlalchemy/ext/associationproxy.py
new file mode 100644
index 000000000..644427902
--- /dev/null
+++ b/lib/sqlalchemy/ext/associationproxy.py
@@ -0,0 +1,96 @@
+"""contains the AssociationProxy class, a Python property object which
+provides transparent proxied access to the endpoint of an association object.
+
+See the example examples/association/proxied_association.py.
+"""
+
+from sqlalchemy.orm import class_mapper
+
+class AssociationProxy(object):
+ """a property object that automatically sets up AssociationLists on a parent object."""
+ def __init__(self, targetcollection, attr, creator=None):
+ """create a new association property.
+
+ targetcollection - the attribute name which stores the collection of Associations
+
+ attr - name of the attribute on the Association in which to get/set target values
+
+ creator - optional callable which is used to create a new association object. this
+ callable is given a single argument which is an instance of the "proxied" object.
+ if creator is not given, the association object is created using the class associated
+ with the targetcollection attribute, using its __init__() constructor and setting
+ the proxied attribute.
+ """
+ self.targetcollection = targetcollection
+ self.attr = attr
+ self.creator = creator
+ def __init_deferred(self):
+ prop = class_mapper(self._owner_class).props[self.targetcollection]
+ self._cls = prop.mapper.class_
+ self._uselist = prop.uselist
+ def _get_class(self):
+ try:
+ return self._cls
+ except AttributeError:
+ self.__init_deferred()
+ return self._cls
+ def _get_uselist(self):
+ try:
+ return self._uselist
+ except AttributeError:
+ self.__init_deferred()
+ return self._uselist
+ cls = property(_get_class)
+ uselist = property(_get_uselist)
+ def create(self, target):
+ if self.creator is not None:
+ return self.creator(target)
+ else:
+ assoc = self.cls()
+ setattr(assoc, self.attr, target)
+ return assoc
+ def __get__(self, obj, owner):
+ self._owner_class = owner
+ if obj is None:
+ return self
+ storage_key = '_AssociationProxy_%s' % self.targetcollection
+ if self.uselist:
+ try:
+ return getattr(obj, storage_key)
+ except AttributeError:
+ a = _AssociationList(self, obj)
+ setattr(obj, storage_key, a)
+ return a
+ else:
+ return getattr(getattr(obj, self.targetcollection), self.attr)
+ def __set__(self, obj, value):
+ if self.uselist:
+ setattr(obj, self.targetcollection, [self.create(x) for x in value])
+ else:
+ setattr(obj, self.targetcollection, self.create(value))
+ def __del__(self, obj):
+ delattr(obj, self.targetcollection)
+
+class _AssociationList(object):
+ """generic proxying list which proxies list operations to a different
+ list-holding attribute of the parent object, converting Association objects
+ to and from a target attribute on each Association object."""
+ def __init__(self, proxy, parent):
+ """create a new AssociationList."""
+ self.proxy = proxy
+ self.parent = parent
+ def append(self, item):
+ a = self.proxy.create(item)
+ getattr(self.parent, self.proxy.targetcollection).append(a)
+ def __iter__(self):
+ return iter([getattr(x, self.proxy.attr) for x in getattr(self.parent, self.proxy.targetcollection)])
+ def __repr__(self):
+ return repr([getattr(x, self.proxy.attr) for x in getattr(self.parent, self.proxy.targetcollection)])
+ def __len__(self):
+ return len(getattr(self.parent, self.proxy.targetcollection))
+ def __getitem__(self, index):
+ return getattr(getattr(self.parent, self.proxy.targetcollection)[index], self.proxy.attr)
+ def __setitem__(self, index, value):
+ a = self.proxy.create(item)
+ getattr(self.parent, self.proxy.targetcollection)[index] = a
+
diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py
index 35ddacf15..2ae9bd3aa 100644
--- a/lib/sqlalchemy/orm/__init__.py
+++ b/lib/sqlalchemy/orm/__init__.py
@@ -10,7 +10,7 @@ packages and tying operations to class properties and constructors.
"""
from sqlalchemy import exceptions
from sqlalchemy.orm.mapper import *
-from sqlalchemy.orm.mapper import mapper_registry, ExtensionOption
+from sqlalchemy.orm import mapper as mapperlib
from sqlalchemy.orm.query import Query
from sqlalchemy.orm.util import polymorphic_union
from sqlalchemy.orm import properties, strategies
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py
index 58865242d..84a2540b7 100644
--- a/lib/sqlalchemy/orm/mapper.py
+++ b/lib/sqlalchemy/orm/mapper.py
@@ -11,7 +11,7 @@ from sqlalchemy.orm import sync
from sqlalchemy.orm.interfaces import MapperProperty, MapperOption, OperationContext
import weakref
-__all__ = ['Mapper', 'MapperExtension', 'class_mapper', 'object_mapper', 'EXT_PASS']
+__all__ = ['Mapper', 'MapperExtension', 'class_mapper', 'object_mapper', 'EXT_PASS', 'mapper_registry', 'ExtensionOption']
# a dictionary mapping classes to their primary mappers
mapper_registry = weakref.WeakKeyDictionary()
diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py
index 176989976..6d1143611 100644
--- a/lib/sqlalchemy/orm/properties.py
+++ b/lib/sqlalchemy/orm/properties.py
@@ -128,6 +128,8 @@ class PropertyLoader(StrategizedProperty):
mapper = self.mapper.primary_mapper()
for c in childlist.added_items() + childlist.deleted_items() + childlist.unchanged_items():
if c is not None and c not in recursive:
+ if not isinstance(c, self.mapper.class_):
+ raise exceptions.AssertionError("Attribute '%s' on class '%s' doesn't handle objects of type '%s'" % (self.key, str(self.parent.class_), str(c.__class__)))
recursive.add(c)
yield c
for c2 in mapper.cascade_iterator(type, c, recursive):
@@ -141,6 +143,8 @@ class PropertyLoader(StrategizedProperty):
passive = type != 'delete' or self.passive_deletes
for c in sessionlib.attribute_manager.get_as_list(object, self.key, passive=passive):
if c is not None and c not in recursive:
+ if not isinstance(c, self.mapper.class_):
+ raise exceptions.AssertionError("Attribute '%s' on class '%s' doesn't handle objects of type '%s'" % (self.key, str(self.parent.class_), str(c.__class__)))
recursive.add(c)
callable_(c, mapper.entity_name)
mapper.cascade_callable(type, c, callable_, recursive)