summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/sql')
-rw-r--r--lib/sqlalchemy/sql/expression.py17
-rw-r--r--lib/sqlalchemy/sql/util.py29
2 files changed, 42 insertions, 4 deletions
diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py
index 85f229ba0..5206dc5fa 100644
--- a/lib/sqlalchemy/sql/expression.py
+++ b/lib/sqlalchemy/sql/expression.py
@@ -1002,6 +1002,11 @@ class ClauseElement(Visitable):
yield f
f = getattr(f, '_is_clone_of', None)
+ def __getstate__(self):
+ d = self.__dict__.copy()
+ d.pop('_is_clone_of', None)
+ return d
+
def _get_from_objects(self, **modifiers):
"""Return objects represented in this ``ClauseElement`` that
should be added to the ``FROM`` list of a query, when this
@@ -1959,7 +1964,17 @@ class _BindParamClause(ColumnElement):
"""
return isinstance(other, _BindParamClause) and other.type.__class__ == self.type.__class__
-
+
+ def __getstate__(self):
+ """execute a deferred value for serialization purposes."""
+
+ d = self.__dict__.copy()
+ v = self.value
+ if callable(v):
+ v = v()
+ d['value'] = v
+ return d
+
def __repr__(self):
return "_BindParamClause(%s, %s, type_=%s)" % (repr(self.key), repr(self.value), repr(self.type))
diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py
index 2a510906b..d5f2417c2 100644
--- a/lib/sqlalchemy/sql/util.py
+++ b/lib/sqlalchemy/sql/util.py
@@ -121,6 +121,7 @@ def join_condition(a, b, ignore_nonexistent_tables=False):
else:
return sql.and_(*crit)
+
class Annotated(object):
"""clones a ClauseElement and applies an 'annotations' dictionary.
@@ -133,14 +134,17 @@ class Annotated(object):
hash value may be reused, causing conflicts.
"""
+
def __new__(cls, *args):
if not args:
+ # clone constructor
return object.__new__(cls)
else:
element, values = args
- return object.__new__(
- type.__new__(type, "Annotated%s" % element.__class__.__name__, (Annotated, element.__class__), {})
- )
+ # pull appropriate subclass from this module's
+ # namespace (see below for rationale)
+ cls = eval("Annotated%s" % element.__class__.__name__)
+ return object.__new__(cls)
def __init__(self, element, values):
# force FromClause to generate their internal
@@ -180,6 +184,17 @@ class Annotated(object):
def __cmp__(self, other):
return cmp(hash(self.__element), hash(other))
+# hard-generate Annotated subclasses. this technique
+# is used instead of on-the-fly types (i.e. type.__new__())
+# so that the resulting objects are pickleable.
+from sqlalchemy.sql import expression
+for cls in expression.__dict__.values() + [schema.Column, schema.Table]:
+ if isinstance(cls, type) and issubclass(cls, expression.ClauseElement):
+ exec "class Annotated%s(Annotated, cls):\n" \
+ " __visit_name__ = cls.__visit_name__\n"\
+ " pass" % (cls.__name__, ) in locals()
+
+
def _deep_annotate(element, annotations, exclude=None):
"""Deep copy the given ClauseElement, annotating each element with the given annotations dictionary.
@@ -495,3 +510,11 @@ class ColumnAdapter(ClauseAdapter):
def adapted_row(self, row):
return AliasedRow(row, self.columns)
+ def __getstate__(self):
+ d = self.__dict__.copy()
+ del d['columns']
+ return d
+
+ def __setstate__(self, state):
+ self.__dict__.update(state)
+ self.columns = util.PopulateDict(self._locate_col)