codekingpro/portable-devtools
114k
1# ext/serializer.py
2# Copyright (C) 2005-2024 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 metadata = MetaData(bind=some_engine)
32 Session = scoped_session(sessionmaker())
33
34 # ... define mappers
35
36 query = Session.query(MyClass).
37 filter(MyClass.somedata=='foo').order_by(MyClass.sortkey)
38
39 # pickle the query
40 serialized = dumps(query)
41
42 # unpickle. Pass in metadata + scoped_session
43 query2 = loads(serialized, metadata, Session)
44
45 print query2.all()
46
47Similar restrictions as when using raw pickle apply; mapped classes must be
48themselves be pickleable, meaning they are importable from a module-level
49namespace.
50
51The serializer module is only appropriate for query structures. It is not
52needed for:
53
54* instances of user-defined classes. These contain no references to engines,
55 sessions or expression constructs in the typical case and can be serialized
56 directly.
57
58* Table metadata that is to be loaded entirely from the serialized structure
59 (i.e. is not already declared in the application). Regular
60 pickle.loads()/dumps() can be used to fully dump any ``MetaData`` object,
61 typically one which was reflected from an existing database at some previous
62 point in time. The serializer module is specifically for the opposite case,
63 where the Table metadata is already present in memory.
64
65"""
66
67from io import BytesIO
68import pickle
69import re
70
71from .. import Column
72from .. import Table
73from ..engine import Engine
74from ..orm import class_mapper
75from ..orm.interfaces import MapperProperty
76from ..orm.mapper import Mapper
77from ..orm.session import Session
78from ..util import b64decode
79from ..util import b64encode
80
81
82__all__ = ["Serializer", "Deserializer", "dumps", "loads"]
83
84
85def Serializer(*args, **kw):
86 pickler = pickle.Pickler(*args, **kw)
87
88 def persistent_id(obj):
89 # print "serializing:", repr(obj)
90 if isinstance(obj, Mapper) and not obj.non_primary:
91 id_ = "mapper:" + b64encode(pickle.dumps(obj.class_))
92 elif isinstance(obj, MapperProperty) and not obj.parent.non_primary:
93 id_ = (
94 "mapperprop:"
95 + b64encode(pickle.dumps(obj.parent.class_))
96 + ":"
97 + obj.key
98 )
99 elif isinstance(obj, Table):
100 if "parententity" in obj._annotations:
101 id_ = "mapper_selectable:" + b64encode(
102 pickle.dumps(obj._annotations["parententity"].class_)
103 )
104 else:
105 id_ = f"table:{obj.key}"
106 elif isinstance(obj, Column) and isinstance(obj.table, Table):
107 id_ = f"column:{obj.table.key}:{obj.key}"
108 elif isinstance(obj, Session):
109 id_ = "session:"
110 elif isinstance(obj, Engine):
111 id_ = "engine:"
112 else:
113 return None
114 return id_
115
116 pickler.persistent_id = persistent_id
117 return pickler
118
119
120our_ids = re.compile(
121 r"(mapperprop|mapper|mapper_selectable|table|column|"
122 r"session|attribute|engine):(.*)"
123)
124
125
126def Deserializer(file, metadata=None, scoped_session=None, engine=None):
127 unpickler = pickle.Unpickler(file)
128
129 def get_engine():
130 if engine:
131 return engine
132 elif scoped_session and scoped_session().bind:
133 return scoped_session().bind
134 elif metadata and metadata.bind:
135 return metadata.bind
136 else:
137 return None
138
139 def persistent_load(id_):
140 m = our_ids.match(str(id_))
141 if not m:
142 return None
143 else:
144 type_, args = m.group(1, 2)
145 if type_ == "attribute":
146 key, clsarg = args.split(":")
147 cls = pickle.loads(b64decode(clsarg))
148 return getattr(cls, key)
149 elif type_ == "mapper":
150 cls = pickle.loads(b64decode(args))
151 return class_mapper(cls)
152 elif type_ == "mapper_selectable":
153 cls = pickle.loads(b64decode(args))
154 return class_mapper(cls).__clause_element__()
155 elif type_ == "mapperprop":
156 mapper, keyname = args.split(":")
157 cls = pickle.loads(b64decode(mapper))
158 return class_mapper(cls).attrs[keyname]
159 elif type_ == "table":
160 return metadata.tables[args]
161 elif type_ == "column":
162 table, colname = args.split(":")
163 return metadata.tables[table].c[colname]
164 elif type_ == "session":
165 return scoped_session()
166 elif type_ == "engine":
167 return get_engine()
168 else:
169 raise Exception("Unknown token: %s" % type_)
170
171 unpickler.persistent_load = persistent_load
172 return unpickler
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 