codekingpro/portable-devtools
115k
1# ext/serializer.py
2# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: https://www.opensource.org/licenses/mit-license.php
7# mypy: ignore-errors
8
9"""Serializer/Deserializer objects for usage with SQLAlchemy query structures,
10allowing "contextual" deserialization.
11
12.. legacy::
13
14 The serializer extension is **legacy** and should not be used for
15 new development.
16
17Any SQLAlchemy query structure, either based on sqlalchemy.sql.*
18or sqlalchemy.orm.* can be used. The mappers, Tables, Columns, Session
19etc. which are referenced by the structure are not persisted in serialized
20form, but are instead re-associated with the query structure
21when it is deserialized.
22
23.. warning:: The serializer extension uses pickle to serialize and
24 deserialize objects, so the same security consideration mentioned
25 in the `python documentation
26 <https://docs.python.org/3/library/pickle.html>`_ apply.
27
28Usage is nearly the same as that of the standard Python pickle module::
29
30 from sqlalchemy.ext.serializer import loads, dumps
31
32 metadata = MetaData(bind=some_engine)
33 Session = scoped_session(sessionmaker())
34
35 # ... define mappers
36
37 query = (
38 Session.query(MyClass)
39 .filter(MyClass.somedata == "foo")
40 .order_by(MyClass.sortkey)
41 )
42
43 # pickle the query
44 serialized = dumps(query)
45
46 # unpickle. Pass in metadata + scoped_session
47 query2 = loads(serialized, metadata, Session)
48
49 print(query2.all())
50
51Similar restrictions as when using raw pickle apply; mapped classes must be
52themselves be pickleable, meaning they are importable from a module-level
53namespace.
54
55The serializer module is only appropriate for query structures. It is not
56needed for:
57
58* instances of user-defined classes. These contain no references to engines,
59 sessions or expression constructs in the typical case and can be serialized
60 directly.
61
62* Table metadata that is to be loaded entirely from the serialized structure
63 (i.e. is not already declared in the application). Regular
64 pickle.loads()/dumps() can be used to fully dump any ``MetaData`` object,
65 typically one which was reflected from an existing database at some previous
66 point in time. The serializer module is specifically for the opposite case,
67 where the Table metadata is already present in memory.
68
69"""
70
71from io import BytesIO
72import pickle
73import re
74
75from .. import Column
76from .. import Table
77from ..engine import Engine
78from ..orm import class_mapper
79from ..orm.interfaces import MapperProperty
80from ..orm.mapper import Mapper
81from ..orm.session import Session
82from ..util import b64decode
83from ..util import b64encode
84
85
86__all__ = ["Serializer", "Deserializer", "dumps", "loads"]
87
88
89class Serializer(pickle.Pickler):
90
91 def persistent_id(self, obj):
92 # print "serializing:", repr(obj)
93 if isinstance(obj, Mapper) and not obj.non_primary:
94 id_ = "mapper:" + b64encode(pickle.dumps(obj.class_))
95 elif isinstance(obj, MapperProperty) and not obj.parent.non_primary:
96 id_ = (
97 "mapperprop:"
98 + b64encode(pickle.dumps(obj.parent.class_))
99 + ":"
100 + obj.key
101 )
102 elif isinstance(obj, Table):
103 if "parententity" in obj._annotations:
104 id_ = "mapper_selectable:" + b64encode(
105 pickle.dumps(obj._annotations["parententity"].class_)
106 )
107 else:
108 id_ = f"table:{obj.key}"
109 elif isinstance(obj, Column) and isinstance(obj.table, Table):
110 id_ = f"column:{obj.table.key}:{obj.key}"
111 elif isinstance(obj, Session):
112 id_ = "session:"
113 elif isinstance(obj, Engine):
114 id_ = "engine:"
115 else:
116 return None
117 return id_
118
119
120our_ids = re.compile(
121 r"(mapperprop|mapper|mapper_selectable|table|column|"
122 r"session|attribute|engine):(.*)"
123)
124
125
126class Deserializer(pickle.Unpickler):
127
128 def __init__(self, file, metadata=None, scoped_session=None, engine=None):
129 super().__init__(file)
130 self.metadata = metadata
131 self.scoped_session = scoped_session
132 self.engine = engine
133
134 def get_engine(self):
135 if self.engine:
136 return self.engine
137 elif self.scoped_session and self.scoped_session().bind:
138 return self.scoped_session().bind
139 else:
140 return None
141
142 def persistent_load(self, id_):
143 m = our_ids.match(str(id_))
144 if not m:
145 return None
146 else:
147 type_, args = m.group(1, 2)
148 if type_ == "attribute":
149 key, clsarg = args.split(":")
150 cls = pickle.loads(b64decode(clsarg))
151 return getattr(cls, key)
152 elif type_ == "mapper":
153 cls = pickle.loads(b64decode(args))
154 return class_mapper(cls)
155 elif type_ == "mapper_selectable":
156 cls = pickle.loads(b64decode(args))
157 return class_mapper(cls).__clause_element__()
158 elif type_ == "mapperprop":
159 mapper, keyname = args.split(":")
160 cls = pickle.loads(b64decode(mapper))
161 return class_mapper(cls).attrs[keyname]
162 elif type_ == "table":
163 return self.metadata.tables[args]
164 elif type_ == "column":
165 table, colname = args.split(":")
166 return self.metadata.tables[table].c[colname]
167 elif type_ == "session":
168 return self.scoped_session()
169 elif type_ == "engine":
170 return self.get_engine()
171 else:
172 raise Exception("Unknown token: %s" % type_)
173
174
175def dumps(obj, protocol=pickle.HIGHEST_PROTOCOL):
176 buf = BytesIO()
177 pickler = Serializer(buf, protocol)
178 pickler.dump(obj)
179 return buf.getvalue()
180
181
182def loads(data, metadata=None, scoped_session=None, engine=None):
183 buf = BytesIO(data)
184 unpickler = Deserializer(buf, metadata, scoped_session, engine)
185 return unpickler.load()
186 