diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2008-11-06 23:07:47 +0000 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2008-11-06 23:07:47 +0000 |
| commit | c3352e5542001d1a5614af260d74ad8757a59f26 (patch) | |
| tree | 109a2af43b80ad3b244a3d1d8a395435d37eff3d /lib/sqlalchemy/sql/util.py | |
| parent | 84003a8d402c5d7539cf2d53f4061cde62d04413 (diff) | |
| download | sqlalchemy-c3352e5542001d1a5614af260d74ad8757a59f26.tar.gz | |
- Fixed bug in Query involving order_by() in conjunction with
multiple aliases of the same class (will add tests in
[ticket:1218])
- Added a new extension sqlalchemy.ext.serializer. Provides
Serializer/Deserializer "classes" which mirror Pickle/Unpickle,
as well as dumps() and loads(). This serializer implements
an "external object" pickler which keeps key context-sensitive
objects, including engines, sessions, metadata, Tables/Columns,
and mappers, outside of the pickle stream, and can later
restore the pickle using any engine/metadata/session provider.
This is used not for pickling regular object instances, which are
pickleable without any special logic, but for pickling expression
objects and full Query objects, such that all mapper/engine/session
dependencies can be restored at unpickle time.
Diffstat (limited to 'lib/sqlalchemy/sql/util.py')
| -rw-r--r-- | lib/sqlalchemy/sql/util.py | 29 |
1 files changed, 26 insertions, 3 deletions
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) |
