##// END OF EJS Templates
Merge pull request #5762 from minrk/better_closure_check...
Thomas Kluyver -
r16518:161841f4 merge
parent child Browse files
Show More
@@ -0,0 +1,62 b''
1
2 import pickle
3
4 import nose.tools as nt
5 from IPython.utils import codeutil
6 from IPython.utils.pickleutil import can, uncan
7
8 def interactive(f):
9 f.__module__ = '__main__'
10 return f
11
12 def dumps(obj):
13 return pickle.dumps(can(obj))
14
15 def loads(obj):
16 return uncan(pickle.loads(obj))
17
18 def test_no_closure():
19 @interactive
20 def foo():
21 a = 5
22 return a
23
24 pfoo = dumps(foo)
25 bar = loads(pfoo)
26 nt.assert_equal(foo(), bar())
27
28 def test_generator_closure():
29 # this only creates a closure on Python 3
30 @interactive
31 def foo():
32 i = 'i'
33 r = [ i for j in (1,2) ]
34 return r
35
36 pfoo = dumps(foo)
37 bar = loads(pfoo)
38 nt.assert_equal(foo(), bar())
39
40 def test_nested_closure():
41 @interactive
42 def foo():
43 i = 'i'
44 def g():
45 return i
46 return g()
47
48 pfoo = dumps(foo)
49 bar = loads(pfoo)
50 nt.assert_equal(foo(), bar())
51
52 def test_closure():
53 i = 'i'
54 @interactive
55 def foo():
56 return i
57
58 # true closures are not supported
59 with nt.assert_raises(ValueError):
60 pfoo = dumps(foo)
61
62 No newline at end of file
@@ -1,47 +1,32 b''
1 # encoding: utf-8
1 # encoding: utf-8
2
2
3 """Utilities to enable code objects to be pickled.
3 """Utilities to enable code objects to be pickled.
4
4
5 Any process that import this module will be able to pickle code objects. This
5 Any process that import this module will be able to pickle code objects. This
6 includes the func_code attribute of any function. Once unpickled, new
6 includes the func_code attribute of any function. Once unpickled, new
7 functions can be built using new.function(code, globals()). Eventually
7 functions can be built using new.function(code, globals()). Eventually
8 we need to automate all of this so that functions themselves can be pickled.
8 we need to automate all of this so that functions themselves can be pickled.
9
9
10 Reference: A. Tremols, P Cogolo, "Python Cookbook," p 302-305
10 Reference: A. Tremols, P Cogolo, "Python Cookbook," p 302-305
11 """
11 """
12
12
13 __docformat__ = "restructuredtext en"
14
15 #-------------------------------------------------------------------------------
16 # Copyright (C) 2008-2011 The IPython Development Team
17 #
18 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
20 #-------------------------------------------------------------------------------
21
22 #-------------------------------------------------------------------------------
23 # Imports
24 #-------------------------------------------------------------------------------
25
26 import sys
13 import sys
27 import types
14 import types
28 try:
15 try:
29 import copyreg # Py 3
16 import copyreg # Py 3
30 except ImportError:
17 except ImportError:
31 import copy_reg as copyreg # Py 2
18 import copy_reg as copyreg # Py 2
32
19
33 def code_ctor(*args):
20 def code_ctor(*args):
34 return types.CodeType(*args)
21 return types.CodeType(*args)
35
22
36 def reduce_code(co):
23 def reduce_code(co):
37 if co.co_freevars or co.co_cellvars:
38 raise ValueError("Sorry, cannot pickle code objects with closures")
39 args = [co.co_argcount, co.co_nlocals, co.co_stacksize,
24 args = [co.co_argcount, co.co_nlocals, co.co_stacksize,
40 co.co_flags, co.co_code, co.co_consts, co.co_names,
25 co.co_flags, co.co_code, co.co_consts, co.co_names,
41 co.co_varnames, co.co_filename, co.co_name, co.co_firstlineno,
26 co.co_varnames, co.co_filename, co.co_name, co.co_firstlineno,
42 co.co_lnotab]
27 co.co_lnotab, co.co_freevars, co.co_cellvars]
43 if sys.version_info[0] >= 3:
28 if sys.version_info[0] >= 3:
44 args.insert(1, co.co_kwonlyargcount)
29 args.insert(1, co.co_kwonlyargcount)
45 return code_ctor, tuple(args)
30 return code_ctor, tuple(args)
46
31
47 copyreg.pickle(types.CodeType, reduce_code) No newline at end of file
32 copyreg.pickle(types.CodeType, reduce_code)
@@ -1,390 +1,385 b''
1 # encoding: utf-8
1 # encoding: utf-8
2
2
3 """Pickle related utilities. Perhaps this should be called 'can'."""
3 """Pickle related utilities. Perhaps this should be called 'can'."""
4
4
5 __docformat__ = "restructuredtext en"
5 # Copyright (c) IPython Development Team.
6
6 # Distributed under the terms of the Modified BSD License.
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008-2011 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
7
18 import copy
8 import copy
19 import logging
9 import logging
20 import sys
10 import sys
21 from types import FunctionType
11 from types import FunctionType
22
12
23 try:
13 try:
24 import cPickle as pickle
14 import cPickle as pickle
25 except ImportError:
15 except ImportError:
26 import pickle
16 import pickle
27
17
28 from . import codeutil # This registers a hook when it's imported
18 from . import codeutil # This registers a hook when it's imported
29 from . import py3compat
19 from . import py3compat
30 from .importstring import import_item
20 from .importstring import import_item
31 from .py3compat import string_types, iteritems
21 from .py3compat import string_types, iteritems
32
22
33 from IPython.config import Application
23 from IPython.config import Application
34
24
35 if py3compat.PY3:
25 if py3compat.PY3:
36 buffer = memoryview
26 buffer = memoryview
37 class_type = type
27 class_type = type
28 closure_attr = '__closure__'
38 else:
29 else:
39 from types import ClassType
30 from types import ClassType
40 class_type = (type, ClassType)
31 class_type = (type, ClassType)
32 closure_attr = 'func_closure'
41
33
42 #-------------------------------------------------------------------------------
34 #-------------------------------------------------------------------------------
43 # Functions
35 # Functions
44 #-------------------------------------------------------------------------------
36 #-------------------------------------------------------------------------------
45
37
46
38
47 def use_dill():
39 def use_dill():
48 """use dill to expand serialization support
40 """use dill to expand serialization support
49
41
50 adds support for object methods and closures to serialization.
42 adds support for object methods and closures to serialization.
51 """
43 """
52 # import dill causes most of the magic
44 # import dill causes most of the magic
53 import dill
45 import dill
54
46
55 # dill doesn't work with cPickle,
47 # dill doesn't work with cPickle,
56 # tell the two relevant modules to use plain pickle
48 # tell the two relevant modules to use plain pickle
57
49
58 global pickle
50 global pickle
59 pickle = dill
51 pickle = dill
60
52
61 try:
53 try:
62 from IPython.kernel.zmq import serialize
54 from IPython.kernel.zmq import serialize
63 except ImportError:
55 except ImportError:
64 pass
56 pass
65 else:
57 else:
66 serialize.pickle = dill
58 serialize.pickle = dill
67
59
68 # disable special function handling, let dill take care of it
60 # disable special function handling, let dill take care of it
69 can_map.pop(FunctionType, None)
61 can_map.pop(FunctionType, None)
70
62
71
63
72 #-------------------------------------------------------------------------------
64 #-------------------------------------------------------------------------------
73 # Classes
65 # Classes
74 #-------------------------------------------------------------------------------
66 #-------------------------------------------------------------------------------
75
67
76
68
77 class CannedObject(object):
69 class CannedObject(object):
78 def __init__(self, obj, keys=[], hook=None):
70 def __init__(self, obj, keys=[], hook=None):
79 """can an object for safe pickling
71 """can an object for safe pickling
80
72
81 Parameters
73 Parameters
82 ==========
74 ==========
83
75
84 obj:
76 obj:
85 The object to be canned
77 The object to be canned
86 keys: list (optional)
78 keys: list (optional)
87 list of attribute names that will be explicitly canned / uncanned
79 list of attribute names that will be explicitly canned / uncanned
88 hook: callable (optional)
80 hook: callable (optional)
89 An optional extra callable,
81 An optional extra callable,
90 which can do additional processing of the uncanned object.
82 which can do additional processing of the uncanned object.
91
83
92 large data may be offloaded into the buffers list,
84 large data may be offloaded into the buffers list,
93 used for zero-copy transfers.
85 used for zero-copy transfers.
94 """
86 """
95 self.keys = keys
87 self.keys = keys
96 self.obj = copy.copy(obj)
88 self.obj = copy.copy(obj)
97 self.hook = can(hook)
89 self.hook = can(hook)
98 for key in keys:
90 for key in keys:
99 setattr(self.obj, key, can(getattr(obj, key)))
91 setattr(self.obj, key, can(getattr(obj, key)))
100
92
101 self.buffers = []
93 self.buffers = []
102
94
103 def get_object(self, g=None):
95 def get_object(self, g=None):
104 if g is None:
96 if g is None:
105 g = {}
97 g = {}
106 obj = self.obj
98 obj = self.obj
107 for key in self.keys:
99 for key in self.keys:
108 setattr(obj, key, uncan(getattr(obj, key), g))
100 setattr(obj, key, uncan(getattr(obj, key), g))
109
101
110 if self.hook:
102 if self.hook:
111 self.hook = uncan(self.hook, g)
103 self.hook = uncan(self.hook, g)
112 self.hook(obj, g)
104 self.hook(obj, g)
113 return self.obj
105 return self.obj
114
106
115
107
116 class Reference(CannedObject):
108 class Reference(CannedObject):
117 """object for wrapping a remote reference by name."""
109 """object for wrapping a remote reference by name."""
118 def __init__(self, name):
110 def __init__(self, name):
119 if not isinstance(name, string_types):
111 if not isinstance(name, string_types):
120 raise TypeError("illegal name: %r"%name)
112 raise TypeError("illegal name: %r"%name)
121 self.name = name
113 self.name = name
122 self.buffers = []
114 self.buffers = []
123
115
124 def __repr__(self):
116 def __repr__(self):
125 return "<Reference: %r>"%self.name
117 return "<Reference: %r>"%self.name
126
118
127 def get_object(self, g=None):
119 def get_object(self, g=None):
128 if g is None:
120 if g is None:
129 g = {}
121 g = {}
130
122
131 return eval(self.name, g)
123 return eval(self.name, g)
132
124
133
125
134 class CannedFunction(CannedObject):
126 class CannedFunction(CannedObject):
135
127
136 def __init__(self, f):
128 def __init__(self, f):
137 self._check_type(f)
129 self._check_type(f)
138 self.code = f.__code__
130 self.code = f.__code__
139 if f.__defaults__:
131 if f.__defaults__:
140 self.defaults = [ can(fd) for fd in f.__defaults__ ]
132 self.defaults = [ can(fd) for fd in f.__defaults__ ]
141 else:
133 else:
142 self.defaults = None
134 self.defaults = None
135
136 if getattr(f, closure_attr, None):
137 raise ValueError("Sorry, cannot pickle functions with closures.")
143 self.module = f.__module__ or '__main__'
138 self.module = f.__module__ or '__main__'
144 self.__name__ = f.__name__
139 self.__name__ = f.__name__
145 self.buffers = []
140 self.buffers = []
146
141
147 def _check_type(self, obj):
142 def _check_type(self, obj):
148 assert isinstance(obj, FunctionType), "Not a function type"
143 assert isinstance(obj, FunctionType), "Not a function type"
149
144
150 def get_object(self, g=None):
145 def get_object(self, g=None):
151 # try to load function back into its module:
146 # try to load function back into its module:
152 if not self.module.startswith('__'):
147 if not self.module.startswith('__'):
153 __import__(self.module)
148 __import__(self.module)
154 g = sys.modules[self.module].__dict__
149 g = sys.modules[self.module].__dict__
155
150
156 if g is None:
151 if g is None:
157 g = {}
152 g = {}
158 if self.defaults:
153 if self.defaults:
159 defaults = tuple(uncan(cfd, g) for cfd in self.defaults)
154 defaults = tuple(uncan(cfd, g) for cfd in self.defaults)
160 else:
155 else:
161 defaults = None
156 defaults = None
162 newFunc = FunctionType(self.code, g, self.__name__, defaults)
157 newFunc = FunctionType(self.code, g, self.__name__, defaults)
163 return newFunc
158 return newFunc
164
159
165 class CannedClass(CannedObject):
160 class CannedClass(CannedObject):
166
161
167 def __init__(self, cls):
162 def __init__(self, cls):
168 self._check_type(cls)
163 self._check_type(cls)
169 self.name = cls.__name__
164 self.name = cls.__name__
170 self.old_style = not isinstance(cls, type)
165 self.old_style = not isinstance(cls, type)
171 self._canned_dict = {}
166 self._canned_dict = {}
172 for k,v in cls.__dict__.items():
167 for k,v in cls.__dict__.items():
173 if k not in ('__weakref__', '__dict__'):
168 if k not in ('__weakref__', '__dict__'):
174 self._canned_dict[k] = can(v)
169 self._canned_dict[k] = can(v)
175 if self.old_style:
170 if self.old_style:
176 mro = []
171 mro = []
177 else:
172 else:
178 mro = cls.mro()
173 mro = cls.mro()
179
174
180 self.parents = [ can(c) for c in mro[1:] ]
175 self.parents = [ can(c) for c in mro[1:] ]
181 self.buffers = []
176 self.buffers = []
182
177
183 def _check_type(self, obj):
178 def _check_type(self, obj):
184 assert isinstance(obj, class_type), "Not a class type"
179 assert isinstance(obj, class_type), "Not a class type"
185
180
186 def get_object(self, g=None):
181 def get_object(self, g=None):
187 parents = tuple(uncan(p, g) for p in self.parents)
182 parents = tuple(uncan(p, g) for p in self.parents)
188 return type(self.name, parents, uncan_dict(self._canned_dict, g=g))
183 return type(self.name, parents, uncan_dict(self._canned_dict, g=g))
189
184
190 class CannedArray(CannedObject):
185 class CannedArray(CannedObject):
191 def __init__(self, obj):
186 def __init__(self, obj):
192 from numpy import ascontiguousarray
187 from numpy import ascontiguousarray
193 self.shape = obj.shape
188 self.shape = obj.shape
194 self.dtype = obj.dtype.descr if obj.dtype.fields else obj.dtype.str
189 self.dtype = obj.dtype.descr if obj.dtype.fields else obj.dtype.str
195 self.pickled = False
190 self.pickled = False
196 if sum(obj.shape) == 0:
191 if sum(obj.shape) == 0:
197 self.pickled = True
192 self.pickled = True
198 elif obj.dtype == 'O':
193 elif obj.dtype == 'O':
199 # can't handle object dtype with buffer approach
194 # can't handle object dtype with buffer approach
200 self.pickled = True
195 self.pickled = True
201 elif obj.dtype.fields and any(dt == 'O' for dt,sz in obj.dtype.fields.values()):
196 elif obj.dtype.fields and any(dt == 'O' for dt,sz in obj.dtype.fields.values()):
202 self.pickled = True
197 self.pickled = True
203 if self.pickled:
198 if self.pickled:
204 # just pickle it
199 # just pickle it
205 self.buffers = [pickle.dumps(obj, -1)]
200 self.buffers = [pickle.dumps(obj, -1)]
206 else:
201 else:
207 # ensure contiguous
202 # ensure contiguous
208 obj = ascontiguousarray(obj, dtype=None)
203 obj = ascontiguousarray(obj, dtype=None)
209 self.buffers = [buffer(obj)]
204 self.buffers = [buffer(obj)]
210
205
211 def get_object(self, g=None):
206 def get_object(self, g=None):
212 from numpy import frombuffer
207 from numpy import frombuffer
213 data = self.buffers[0]
208 data = self.buffers[0]
214 if self.pickled:
209 if self.pickled:
215 # no shape, we just pickled it
210 # no shape, we just pickled it
216 return pickle.loads(data)
211 return pickle.loads(data)
217 else:
212 else:
218 return frombuffer(data, dtype=self.dtype).reshape(self.shape)
213 return frombuffer(data, dtype=self.dtype).reshape(self.shape)
219
214
220
215
221 class CannedBytes(CannedObject):
216 class CannedBytes(CannedObject):
222 wrap = bytes
217 wrap = bytes
223 def __init__(self, obj):
218 def __init__(self, obj):
224 self.buffers = [obj]
219 self.buffers = [obj]
225
220
226 def get_object(self, g=None):
221 def get_object(self, g=None):
227 data = self.buffers[0]
222 data = self.buffers[0]
228 return self.wrap(data)
223 return self.wrap(data)
229
224
230 def CannedBuffer(CannedBytes):
225 def CannedBuffer(CannedBytes):
231 wrap = buffer
226 wrap = buffer
232
227
233 #-------------------------------------------------------------------------------
228 #-------------------------------------------------------------------------------
234 # Functions
229 # Functions
235 #-------------------------------------------------------------------------------
230 #-------------------------------------------------------------------------------
236
231
237 def _logger():
232 def _logger():
238 """get the logger for the current Application
233 """get the logger for the current Application
239
234
240 the root logger will be used if no Application is running
235 the root logger will be used if no Application is running
241 """
236 """
242 if Application.initialized():
237 if Application.initialized():
243 logger = Application.instance().log
238 logger = Application.instance().log
244 else:
239 else:
245 logger = logging.getLogger()
240 logger = logging.getLogger()
246 if not logger.handlers:
241 if not logger.handlers:
247 logging.basicConfig()
242 logging.basicConfig()
248
243
249 return logger
244 return logger
250
245
251 def _import_mapping(mapping, original=None):
246 def _import_mapping(mapping, original=None):
252 """import any string-keys in a type mapping
247 """import any string-keys in a type mapping
253
248
254 """
249 """
255 log = _logger()
250 log = _logger()
256 log.debug("Importing canning map")
251 log.debug("Importing canning map")
257 for key,value in list(mapping.items()):
252 for key,value in list(mapping.items()):
258 if isinstance(key, string_types):
253 if isinstance(key, string_types):
259 try:
254 try:
260 cls = import_item(key)
255 cls = import_item(key)
261 except Exception:
256 except Exception:
262 if original and key not in original:
257 if original and key not in original:
263 # only message on user-added classes
258 # only message on user-added classes
264 log.error("canning class not importable: %r", key, exc_info=True)
259 log.error("canning class not importable: %r", key, exc_info=True)
265 mapping.pop(key)
260 mapping.pop(key)
266 else:
261 else:
267 mapping[cls] = mapping.pop(key)
262 mapping[cls] = mapping.pop(key)
268
263
269 def istype(obj, check):
264 def istype(obj, check):
270 """like isinstance(obj, check), but strict
265 """like isinstance(obj, check), but strict
271
266
272 This won't catch subclasses.
267 This won't catch subclasses.
273 """
268 """
274 if isinstance(check, tuple):
269 if isinstance(check, tuple):
275 for cls in check:
270 for cls in check:
276 if type(obj) is cls:
271 if type(obj) is cls:
277 return True
272 return True
278 return False
273 return False
279 else:
274 else:
280 return type(obj) is check
275 return type(obj) is check
281
276
282 def can(obj):
277 def can(obj):
283 """prepare an object for pickling"""
278 """prepare an object for pickling"""
284
279
285 import_needed = False
280 import_needed = False
286
281
287 for cls,canner in iteritems(can_map):
282 for cls,canner in iteritems(can_map):
288 if isinstance(cls, string_types):
283 if isinstance(cls, string_types):
289 import_needed = True
284 import_needed = True
290 break
285 break
291 elif istype(obj, cls):
286 elif istype(obj, cls):
292 return canner(obj)
287 return canner(obj)
293
288
294 if import_needed:
289 if import_needed:
295 # perform can_map imports, then try again
290 # perform can_map imports, then try again
296 # this will usually only happen once
291 # this will usually only happen once
297 _import_mapping(can_map, _original_can_map)
292 _import_mapping(can_map, _original_can_map)
298 return can(obj)
293 return can(obj)
299
294
300 return obj
295 return obj
301
296
302 def can_class(obj):
297 def can_class(obj):
303 if isinstance(obj, class_type) and obj.__module__ == '__main__':
298 if isinstance(obj, class_type) and obj.__module__ == '__main__':
304 return CannedClass(obj)
299 return CannedClass(obj)
305 else:
300 else:
306 return obj
301 return obj
307
302
308 def can_dict(obj):
303 def can_dict(obj):
309 """can the *values* of a dict"""
304 """can the *values* of a dict"""
310 if istype(obj, dict):
305 if istype(obj, dict):
311 newobj = {}
306 newobj = {}
312 for k, v in iteritems(obj):
307 for k, v in iteritems(obj):
313 newobj[k] = can(v)
308 newobj[k] = can(v)
314 return newobj
309 return newobj
315 else:
310 else:
316 return obj
311 return obj
317
312
318 sequence_types = (list, tuple, set)
313 sequence_types = (list, tuple, set)
319
314
320 def can_sequence(obj):
315 def can_sequence(obj):
321 """can the elements of a sequence"""
316 """can the elements of a sequence"""
322 if istype(obj, sequence_types):
317 if istype(obj, sequence_types):
323 t = type(obj)
318 t = type(obj)
324 return t([can(i) for i in obj])
319 return t([can(i) for i in obj])
325 else:
320 else:
326 return obj
321 return obj
327
322
328 def uncan(obj, g=None):
323 def uncan(obj, g=None):
329 """invert canning"""
324 """invert canning"""
330
325
331 import_needed = False
326 import_needed = False
332 for cls,uncanner in iteritems(uncan_map):
327 for cls,uncanner in iteritems(uncan_map):
333 if isinstance(cls, string_types):
328 if isinstance(cls, string_types):
334 import_needed = True
329 import_needed = True
335 break
330 break
336 elif isinstance(obj, cls):
331 elif isinstance(obj, cls):
337 return uncanner(obj, g)
332 return uncanner(obj, g)
338
333
339 if import_needed:
334 if import_needed:
340 # perform uncan_map imports, then try again
335 # perform uncan_map imports, then try again
341 # this will usually only happen once
336 # this will usually only happen once
342 _import_mapping(uncan_map, _original_uncan_map)
337 _import_mapping(uncan_map, _original_uncan_map)
343 return uncan(obj, g)
338 return uncan(obj, g)
344
339
345 return obj
340 return obj
346
341
347 def uncan_dict(obj, g=None):
342 def uncan_dict(obj, g=None):
348 if istype(obj, dict):
343 if istype(obj, dict):
349 newobj = {}
344 newobj = {}
350 for k, v in iteritems(obj):
345 for k, v in iteritems(obj):
351 newobj[k] = uncan(v,g)
346 newobj[k] = uncan(v,g)
352 return newobj
347 return newobj
353 else:
348 else:
354 return obj
349 return obj
355
350
356 def uncan_sequence(obj, g=None):
351 def uncan_sequence(obj, g=None):
357 if istype(obj, sequence_types):
352 if istype(obj, sequence_types):
358 t = type(obj)
353 t = type(obj)
359 return t([uncan(i,g) for i in obj])
354 return t([uncan(i,g) for i in obj])
360 else:
355 else:
361 return obj
356 return obj
362
357
363 def _uncan_dependent_hook(dep, g=None):
358 def _uncan_dependent_hook(dep, g=None):
364 dep.check_dependency()
359 dep.check_dependency()
365
360
366 def can_dependent(obj):
361 def can_dependent(obj):
367 return CannedObject(obj, keys=('f', 'df'), hook=_uncan_dependent_hook)
362 return CannedObject(obj, keys=('f', 'df'), hook=_uncan_dependent_hook)
368
363
369 #-------------------------------------------------------------------------------
364 #-------------------------------------------------------------------------------
370 # API dictionaries
365 # API dictionaries
371 #-------------------------------------------------------------------------------
366 #-------------------------------------------------------------------------------
372
367
373 # These dicts can be extended for custom serialization of new objects
368 # These dicts can be extended for custom serialization of new objects
374
369
375 can_map = {
370 can_map = {
376 'IPython.parallel.dependent' : can_dependent,
371 'IPython.parallel.dependent' : can_dependent,
377 'numpy.ndarray' : CannedArray,
372 'numpy.ndarray' : CannedArray,
378 FunctionType : CannedFunction,
373 FunctionType : CannedFunction,
379 bytes : CannedBytes,
374 bytes : CannedBytes,
380 buffer : CannedBuffer,
375 buffer : CannedBuffer,
381 class_type : can_class,
376 class_type : can_class,
382 }
377 }
383
378
384 uncan_map = {
379 uncan_map = {
385 CannedObject : lambda obj, g: obj.get_object(g),
380 CannedObject : lambda obj, g: obj.get_object(g),
386 }
381 }
387
382
388 # for use in _import_mapping:
383 # for use in _import_mapping:
389 _original_can_map = can_map.copy()
384 _original_can_map = can_map.copy()
390 _original_uncan_map = uncan_map.copy()
385 _original_uncan_map = uncan_map.copy()
General Comments 0
You need to be logged in to leave comments. Login now