##// END OF EJS Templates
adjust how canning deals with import strings...
MinRK -
Show More
@@ -1,244 +1,281 b''
1 1 # encoding: utf-8
2 2
3 3 """Pickle related utilities. Perhaps this should be called 'can'."""
4 4
5 5 __docformat__ = "restructuredtext en"
6 6
7 7 #-------------------------------------------------------------------------------
8 8 # Copyright (C) 2008-2011 The IPython Development Team
9 9 #
10 10 # Distributed under the terms of the BSD License. The full license is in
11 11 # the file COPYING, distributed as part of this software.
12 12 #-------------------------------------------------------------------------------
13 13
14 14 #-------------------------------------------------------------------------------
15 15 # Imports
16 16 #-------------------------------------------------------------------------------
17 17
18 18 import copy
19 19 import logging
20 20 import sys
21 21 from types import FunctionType
22 22
23 23 try:
24 24 import cPickle as pickle
25 25 except ImportError:
26 26 import pickle
27 27
28 28 try:
29 29 import numpy
30 30 except:
31 31 numpy = None
32 32
33 33 import codeutil
34 34 import py3compat
35 35 from importstring import import_item
36 36
37 37 from IPython.config import Application
38 38
39 39 if py3compat.PY3:
40 40 buffer = memoryview
41 41
42 42 #-------------------------------------------------------------------------------
43 43 # Classes
44 44 #-------------------------------------------------------------------------------
45 45
46 46
47 47 class CannedObject(object):
48 48 def __init__(self, obj, keys=[]):
49 49 self.keys = keys
50 50 self.obj = copy.copy(obj)
51 51 for key in keys:
52 52 setattr(self.obj, key, can(getattr(obj, key)))
53 53
54 54 self.buffers = []
55 55
56 56 def get_object(self, g=None):
57 57 if g is None:
58 58 g = {}
59 59 for key in self.keys:
60 60 setattr(self.obj, key, uncan(getattr(self.obj, key), g))
61 61 return self.obj
62 62
63 63
64 64 class Reference(CannedObject):
65 65 """object for wrapping a remote reference by name."""
66 66 def __init__(self, name):
67 67 if not isinstance(name, basestring):
68 68 raise TypeError("illegal name: %r"%name)
69 69 self.name = name
70 70 self.buffers = []
71 71
72 72 def __repr__(self):
73 73 return "<Reference: %r>"%self.name
74 74
75 75 def get_object(self, g=None):
76 76 if g is None:
77 77 g = {}
78 78
79 79 return eval(self.name, g)
80 80
81 81
82 82 class CannedFunction(CannedObject):
83 83
84 84 def __init__(self, f):
85 85 self._check_type(f)
86 86 self.code = f.func_code
87 87 if f.func_defaults:
88 88 self.defaults = [ can(fd) for fd in f.func_defaults ]
89 89 else:
90 90 self.defaults = None
91 91 self.module = f.__module__ or '__main__'
92 92 self.__name__ = f.__name__
93 93 self.buffers = []
94 94
95 95 def _check_type(self, obj):
96 96 assert isinstance(obj, FunctionType), "Not a function type"
97 97
98 98 def get_object(self, g=None):
99 99 # try to load function back into its module:
100 100 if not self.module.startswith('__'):
101 101 try:
102 102 __import__(self.module)
103 103 except ImportError:
104 104 pass
105 105 else:
106 106 g = sys.modules[self.module].__dict__
107 107
108 108 if g is None:
109 109 g = {}
110 110 if self.defaults:
111 111 defaults = tuple(uncan(cfd, g) for cfd in self.defaults)
112 112 else:
113 113 defaults = None
114 114 newFunc = FunctionType(self.code, g, self.__name__, defaults)
115 115 return newFunc
116 116
117 117
118 118 class CannedArray(CannedObject):
119 119 def __init__(self, obj):
120 120 self.shape = obj.shape
121 121 self.dtype = obj.dtype.descr if obj.dtype.fields else obj.dtype.str
122 122 if sum(obj.shape) == 0:
123 123 # just pickle it
124 124 self.buffers = [pickle.dumps(obj, -1)]
125 125 else:
126 126 # ensure contiguous
127 127 obj = numpy.ascontiguousarray(obj, dtype=None)
128 128 self.buffers = [buffer(obj)]
129 129
130 130 def get_object(self, g=None):
131 131 data = self.buffers[0]
132 132 if sum(self.shape) == 0:
133 133 # no shape, we just pickled it
134 134 return pickle.loads(data)
135 135 else:
136 136 return numpy.frombuffer(data, dtype=self.dtype).reshape(self.shape)
137 137
138 138
139 139 class CannedBytes(CannedObject):
140 140 wrap = bytes
141 141 def __init__(self, obj):
142 142 self.buffers = [obj]
143 143
144 144 def get_object(self, g=None):
145 145 data = self.buffers[0]
146 146 return self.wrap(data)
147 147
148 148 def CannedBuffer(CannedBytes):
149 149 wrap = buffer
150 150
151 151 #-------------------------------------------------------------------------------
152 152 # Functions
153 153 #-------------------------------------------------------------------------------
154 154
155 def _error(*args, **kwargs):
155 def _logger():
156 """get the logger for the current Application
157
158 the root logger will be used if no Application is running
159 """
156 160 if Application.initialized():
157 161 logger = Application.instance().log
158 162 else:
159 163 logger = logging.getLogger()
160 164 if not logger.handlers:
161 165 logging.basicConfig()
162 logger.error(*args, **kwargs)
166
167 return logger
168
169 def _import_mapping(mapping, original=None):
170 """import any string-keys in a type mapping
171
172 """
173 log = _logger()
174 log.debug("Importing canning map")
175 for key,value in mapping.items():
176 if isinstance(key, basestring):
177 try:
178 cls = import_item(key)
179 except Exception:
180 if original and key not in original:
181 # only message on user-added classes
182 log.error("cannning class not importable: %r", key, exc_info=True)
183 mapping.pop(key)
184 else:
185 mapping[cls] = mapping.pop(key)
163 186
164 187 def can(obj):
165 188 """prepare an object for pickling"""
189
190 import_needed = False
191
166 192 for cls,canner in can_map.iteritems():
167 193 if isinstance(cls, basestring):
168 try:
169 cls = import_item(cls)
170 except Exception:
171 _error("cannning class not importable: %r", cls, exc_info=True)
172 cls = None
173 continue
174 if isinstance(obj, cls):
194 import_needed = True
195 break
196 elif isinstance(obj, cls):
175 197 return canner(obj)
198
199 if import_needed:
200 # perform can_map imports, then try again
201 # this will usually only happen once
202 _import_mapping(can_map, _original_can_map)
203 return can(obj)
204
176 205 return obj
177 206
178 207 def can_dict(obj):
179 208 """can the *values* of a dict"""
180 209 if isinstance(obj, dict):
181 210 newobj = {}
182 211 for k, v in obj.iteritems():
183 212 newobj[k] = can(v)
184 213 return newobj
185 214 else:
186 215 return obj
187 216
188 217 def can_sequence(obj):
189 218 """can the elements of a sequence"""
190 219 if isinstance(obj, (list, tuple)):
191 220 t = type(obj)
192 221 return t([can(i) for i in obj])
193 222 else:
194 223 return obj
195 224
196 225 def uncan(obj, g=None):
197 226 """invert canning"""
227
228 import_needed = False
198 229 for cls,uncanner in uncan_map.iteritems():
199 230 if isinstance(cls, basestring):
200 try:
201 cls = import_item(cls)
202 except Exception:
203 _error("uncanning class not importable: %r", cls, exc_info=True)
204 cls = None
205 continue
206 if isinstance(obj, cls):
231 import_needed = True
232 break
233 elif isinstance(obj, cls):
207 234 return uncanner(obj, g)
235
236 if import_needed:
237 # perform uncan_map imports, then try again
238 # this will usually only happen once
239 _import_mapping(uncan_map, _original_uncan_map)
240 return uncan(obj, g)
241
208 242 return obj
209 243
210 244 def uncan_dict(obj, g=None):
211 245 if isinstance(obj, dict):
212 246 newobj = {}
213 247 for k, v in obj.iteritems():
214 248 newobj[k] = uncan(v,g)
215 249 return newobj
216 250 else:
217 251 return obj
218 252
219 253 def uncan_sequence(obj, g=None):
220 254 if isinstance(obj, (list, tuple)):
221 255 t = type(obj)
222 256 return t([uncan(i,g) for i in obj])
223 257 else:
224 258 return obj
225 259
226 260
227 261 #-------------------------------------------------------------------------------
228 # API dictionary
262 # API dictionaries
229 263 #-------------------------------------------------------------------------------
230 264
231 265 # These dicts can be extended for custom serialization of new objects
232 266
233 267 can_map = {
234 268 'IPython.parallel.dependent' : lambda obj: CannedObject(obj, keys=('f','df')),
235 269 'numpy.ndarray' : CannedArray,
236 270 FunctionType : CannedFunction,
237 271 bytes : CannedBytes,
238 272 buffer : CannedBuffer,
239 273 }
240 274
241 275 uncan_map = {
242 276 CannedObject : lambda obj, g: obj.get_object(g),
243 277 }
244 278
279 # for use in _import_mapping:
280 _original_can_map = can_map.copy()
281 _original_uncan_map = uncan_map.copy()
General Comments 0
You need to be logged in to leave comments. Login now