blob: 1533c8958a886da2e8ad236e83b82a239ce5eaf3 [file] [log] [blame]
Tor Norbye3a2425a2013-11-04 10:16:08 -08001"""
Tor Norbyec667c1f2014-05-28 17:06:51 -07002Based on the python xreload.
3
4Changes
5======================
6
71. we don't recreate the old namespace from new classes. Rather, we keep the existing namespace,
8load a new version of it and update only some of the things we can inplace. That way, we don't break
9things such as singletons or end up with a second representation of the same class in memory.
10
112. If we find it to be a __metaclass__, we try to update it as a regular class.
12
133. We don't remove old attributes (and leave them lying around even if they're no longer used).
14
154. Reload hooks were changed
16
17These changes make it more stable, especially in the common case (where in a debug session only the
18contents of a function are changed), besides providing flexibility for users that want to extend
19on it.
20
21
22
23Hooks
24======================
25
26Classes/modules can be specially crafted to work with the reload (so that it can, for instance,
27update some constant which was changed).
28
291. To participate in the change of some attribute:
30
31 In a module:
32
33 __xreload_old_new__(namespace, name, old, new)
34
35 in a class:
36
37 @classmethod
38 __xreload_old_new__(cls, name, old, new)
39
40 A class or module may include a method called '__xreload_old_new__' which is called when we're
41 unable to reload a given attribute.
42
43
44
452. To do something after the whole reload is finished:
46
47 In a module:
48
49 __xreload_after_reload_update__(namespace):
50
51 In a class:
52
53 @classmethod
54 __xreload_after_reload_update__(cls):
55
56
57 A class or module may include a method called '__xreload_after_reload_update__' which is called
58 after the reload finishes.
59
60
61Important: when providing a hook, always use the namespace or cls provided and not anything in the global
62namespace, as the global namespace are only temporarily created during the reload and may not reflect the
63actual application state (while the cls and namespace passed are).
64
65
66Current limitations
67======================
68
69
70- Attributes/constants are added, but not changed (so singletons and the application state is not
71 broken -- use provided hooks to workaround it).
72
73- Code using metaclasses may not always work.
74
75- Functions and methods using decorators (other than classmethod and staticmethod) are not handled
76 correctly.
77
78- Renamings are not handled correctly.
79
80- Dependent modules are not reloaded.
81
82- New __slots__ can't be added to existing classes.
83
84
85Info
86======================
87
88Original: http://svn.python.org/projects/sandbox/trunk/xreload/xreload.py
89Note: it seems https://github.com/plone/plone.reload/blob/master/plone/reload/xreload.py enhances it (to check later)
90
91Interesting alternative: https://code.google.com/p/reimport/
Tor Norbye3a2425a2013-11-04 10:16:08 -080092
93Alternative to reload().
94
Tor Norbyec667c1f2014-05-28 17:06:51 -070095This works by executing the module in a scratch namespace, and then patching classes, methods and
96functions in place. This avoids the need to patch instances. New objects are copied into the
97target namespace.
Tor Norbye3a2425a2013-11-04 10:16:08 -080098
Tor Norbye3a2425a2013-11-04 10:16:08 -080099"""
100
101import imp
Tor Norbyec667c1f2014-05-28 17:06:51 -0700102from pydev_imports import Exec
103import pydevd_dont_trace
Tor Norbye3a2425a2013-11-04 10:16:08 -0800104import sys
Tor Norbyec667c1f2014-05-28 17:06:51 -0700105import traceback
Tor Norbye3a2425a2013-11-04 10:16:08 -0800106import types
107
Tor Norbyec667c1f2014-05-28 17:06:51 -0700108NO_DEBUG = 0
109LEVEL1 = 1
110LEVEL2 = 2
Tor Norbye3a2425a2013-11-04 10:16:08 -0800111
Tor Norbyec667c1f2014-05-28 17:06:51 -0700112DEBUG = NO_DEBUG
113
114def write(*args):
115 new_lst = []
116 for a in args:
117 new_lst.append(str(a))
118
119 msg = ' '.join(new_lst)
120 sys.stdout.write('%s\n' % (msg,))
121
122def write_err(*args):
123 new_lst = []
124 for a in args:
125 new_lst.append(str(a))
126
127 msg = ' '.join(new_lst)
128 sys.stderr.write('pydev debugger: %s\n' % (msg,))
129
130def notify_info0(*args):
131 write_err(*args)
132
133def notify_info(*args):
134 if DEBUG >= LEVEL1:
135 write(*args)
136
137def notify_info2(*args):
138 if DEBUG >= LEVEL2:
139 write(*args)
140
141def notify_error(*args):
142 write_err(*args)
143
144
145
146#=======================================================================================================================
147# code_objects_equal
148#=======================================================================================================================
149def code_objects_equal(code0, code1):
150 for d in dir(code0):
151 if d.startswith('_') or 'lineno' in d:
152 continue
153 if getattr(code0, d) != getattr(code1, d):
154 return False
155 return True
156
157
158#=======================================================================================================================
159# xreload
160#=======================================================================================================================
Tor Norbye3a2425a2013-11-04 10:16:08 -0800161def xreload(mod):
162 """Reload a module in place, updating classes, methods and functions.
163
Tor Norbyec667c1f2014-05-28 17:06:51 -0700164 mod: a module object
Tor Norbye3a2425a2013-11-04 10:16:08 -0800165
Tor Norbyec667c1f2014-05-28 17:06:51 -0700166 Returns a boolean indicating whether a change was done.
Tor Norbye3a2425a2013-11-04 10:16:08 -0800167 """
Tor Norbyec667c1f2014-05-28 17:06:51 -0700168 r = Reload(mod)
169 r.apply()
170 found_change = r.found_change
171 r = None
172 pydevd_dont_trace.clear_trace_filter_cache()
173 return found_change
Tor Norbye3a2425a2013-11-04 10:16:08 -0800174
175
Tor Norbyec667c1f2014-05-28 17:06:51 -0700176# This isn't actually used... Initially I planned to reload variables which are immutable on the
177# namespace, but this can destroy places where we're saving state, which may not be what we want,
178# so, we're being conservative and giving the user hooks if he wants to do a reload.
179#
180# immutable_types = [int, str, float, tuple] #That should be common to all Python versions
181#
182# for name in 'long basestr unicode frozenset'.split():
183# try:
184# immutable_types.append(__builtins__[name])
185# except:
186# pass #Just ignore: not all python versions are created equal.
187# immutable_types = tuple(immutable_types)
Tor Norbye3a2425a2013-11-04 10:16:08 -0800188
189
Tor Norbyec667c1f2014-05-28 17:06:51 -0700190#=======================================================================================================================
191# Reload
192#=======================================================================================================================
193class Reload:
194
195 def __init__(self, mod):
196 self.mod = mod
197 self.found_change = False
198
199 def apply(self):
200 mod = self.mod
201 self._on_finish_callbacks = []
202 try:
203 # Get the module name, e.g. 'foo.bar.whatever'
204 modname = mod.__name__
205 # Get the module namespace (dict) early; this is part of the type check
206 modns = mod.__dict__
207 # Parse it into package name and module name, e.g. 'foo.bar' and 'whatever'
208 i = modname.rfind(".")
209 if i >= 0:
210 pkgname, modname = modname[:i], modname[i + 1:]
211 else:
212 pkgname = None
213 # Compute the search path
214 if pkgname:
215 # We're not reloading the package, only the module in it
216 pkg = sys.modules[pkgname]
217 path = pkg.__path__ # Search inside the package
218 else:
219 # Search the top-level module path
220 pkg = None
221 path = None # Make find_module() uses the default search path
222 # Find the module; may raise ImportError
223 (stream, filename, (suffix, mode, kind)) = imp.find_module(modname, path)
224 # Turn it into a code object
225 try:
226 # Is it Python source code or byte code read from a file?
227 if kind not in (imp.PY_COMPILED, imp.PY_SOURCE):
228 # Fall back to built-in reload()
229 notify_error('Could not find source to reload (mod: %s)' % (modname,))
230 return
231 if kind == imp.PY_SOURCE:
232 source = stream.read()
233 code = compile(source, filename, "exec")
234 else:
235 import marshal
236 code = marshal.load(stream)
237 finally:
238 if stream:
239 stream.close()
240 # Execute the code. We copy the module dict to a temporary; then
241 # clear the module dict; then execute the new code in the module
242 # dict; then swap things back and around. This trick (due to
243 # Glyph Lefkowitz) ensures that the (readonly) __globals__
244 # attribute of methods and functions is set to the correct dict
245 # object.
246 new_namespace = modns.copy()
247 new_namespace.clear()
248 new_namespace["__name__"] = modns["__name__"]
249 Exec(code, new_namespace)
250 # Now we get to the hard part
251 oldnames = set(modns)
252 newnames = set(new_namespace)
253
254 # Create new tokens (note: not deleting existing)
255 for name in newnames - oldnames:
256 notify_info0('Added:', name, 'to namespace')
257 self.found_change = True
258 modns[name] = new_namespace[name]
259
260 # Update in-place what we can
261 for name in oldnames & newnames:
262 self._update(modns, name, modns[name], new_namespace[name])
263
264 self._handle_namespace(modns)
265
266 for c in self._on_finish_callbacks:
267 c()
268 del self._on_finish_callbacks[:]
269 except:
270 traceback.print_exc()
Tor Norbye3a2425a2013-11-04 10:16:08 -0800271
272
Tor Norbyec667c1f2014-05-28 17:06:51 -0700273 def _handle_namespace(self, namespace, is_class_namespace=False):
274 on_finish = None
275 if is_class_namespace:
276 xreload_after_update = getattr(namespace, '__xreload_after_reload_update__', None)
277 if xreload_after_update is not None:
278 self.found_change = True
279 on_finish = lambda: xreload_after_update()
280
281 elif '__xreload_after_reload_update__' in namespace:
282 xreload_after_update = namespace['__xreload_after_reload_update__']
283 self.found_change = True
284 on_finish = lambda: xreload_after_update(namespace)
Tor Norbye3a2425a2013-11-04 10:16:08 -0800285
286
Tor Norbyec667c1f2014-05-28 17:06:51 -0700287 if on_finish is not None:
288 # If a client wants to know about it, give him a chance.
289 self._on_finish_callbacks.append(on_finish)
Tor Norbye3a2425a2013-11-04 10:16:08 -0800290
291
Tor Norbyec667c1f2014-05-28 17:06:51 -0700292
293 def _update(self, namespace, name, oldobj, newobj, is_class_namespace=False):
294 """Update oldobj, if possible in place, with newobj.
295
296 If oldobj is immutable, this simply returns newobj.
297
298 Args:
299 oldobj: the object to be updated
300 newobj: the object used as the source for the update
301 """
302 try:
303 notify_info2('Updating: ', oldobj)
304 if oldobj is newobj:
305 # Probably something imported
306 return
307
308 if type(oldobj) is not type(newobj):
309 # Cop-out: if the type changed, give up
310 notify_error('Type of: %s changed... Skipping.' % (oldobj,))
311 return
312
313 if isinstance(newobj, types.FunctionType):
314 self._update_function(oldobj, newobj)
315 return
316
317 if isinstance(newobj, types.MethodType):
318 self._update_method(oldobj, newobj)
319 return
320
321 if isinstance(newobj, classmethod):
322 self._update_classmethod(oldobj, newobj)
323 return
324
325 if isinstance(newobj, staticmethod):
326 self._update_staticmethod(oldobj, newobj)
327 return
328
329 if hasattr(types, 'ClassType'):
330 classtype = (types.ClassType, type) #object is not instance of types.ClassType.
331 else:
332 classtype = type
333
334 if isinstance(newobj, classtype):
335 self._update_class(oldobj, newobj)
336 return
337
338 # New: dealing with metaclasses.
339 if hasattr(newobj, '__metaclass__') and hasattr(newobj, '__class__') and newobj.__metaclass__ == newobj.__class__:
340 self._update_class(oldobj, newobj)
341 return
342
343 if namespace is not None:
344
345 if oldobj != newobj and str(oldobj) != str(newobj) and repr(oldobj) != repr(newobj):
346 xreload_old_new = None
347 if is_class_namespace:
348 xreload_old_new = getattr(namespace, '__xreload_old_new__', None)
349 if xreload_old_new is not None:
350 self.found_change = True
351 xreload_old_new(name, oldobj, newobj)
352
353 elif '__xreload_old_new__' in namespace:
354 xreload_old_new = namespace['__xreload_old_new__']
355 xreload_old_new(namespace, name, oldobj, newobj)
356 self.found_change = True
357
358 # Too much information to the user...
359 # else:
360 # notify_info0('%s NOT updated. Create __xreload_old_new__(name, old, new) for custom reload' % (name,))
361
362 except:
363 notify_error('Exception found when updating %s. Proceeding for other items.' % (name,))
364 traceback.print_exc()
Tor Norbye3a2425a2013-11-04 10:16:08 -0800365
366
Tor Norbyec667c1f2014-05-28 17:06:51 -0700367 # All of the following functions have the same signature as _update()
Tor Norbye3a2425a2013-11-04 10:16:08 -0800368
369
Tor Norbyec667c1f2014-05-28 17:06:51 -0700370 def _update_function(self, oldfunc, newfunc):
371 """Update a function object."""
372 oldfunc.__doc__ = newfunc.__doc__
373 oldfunc.__dict__.update(newfunc.__dict__)
374
375 try:
376 newfunc.__code__
377 attr_name = '__code__'
378 except AttributeError:
379 newfunc.func_code
380 attr_name = 'func_code'
381
382 old_code = getattr(oldfunc, attr_name)
383 new_code = getattr(newfunc, attr_name)
384 if not code_objects_equal(old_code, new_code):
385 notify_info0('Updated function code:', oldfunc)
386 setattr(oldfunc, attr_name, new_code)
387 self.found_change = True
388
389 try:
390 oldfunc.__defaults__ = newfunc.__defaults__
391 except AttributeError:
392 oldfunc.func_defaults = newfunc.func_defaults
393
394 return oldfunc
395
396
397 def _update_method(self, oldmeth, newmeth):
398 """Update a method object."""
399 # XXX What if im_func is not a function?
400 if hasattr(oldmeth, 'im_func') and hasattr(newmeth, 'im_func'):
401 self._update(None, None, oldmeth.im_func, newmeth.im_func)
402 elif hasattr(oldmeth, '__func__') and hasattr(newmeth, '__func__'):
403 self._update(None, None, oldmeth.__func__, newmeth.__func__)
404 return oldmeth
405
406
407 def _update_class(self, oldclass, newclass):
408 """Update a class object."""
409 olddict = oldclass.__dict__
410 newdict = newclass.__dict__
411
412 oldnames = set(olddict)
413 newnames = set(newdict)
414
415 for name in newnames - oldnames:
416 setattr(oldclass, name, newdict[name])
417 notify_info0('Added:', name, 'to', oldclass)
418 self.found_change = True
419
420 # Note: not removing old things...
421 # for name in oldnames - newnames:
422 # notify_info('Removed:', name, 'from', oldclass)
423 # delattr(oldclass, name)
424
425 for name in (oldnames & newnames) - set(['__dict__', '__doc__']):
426 self._update(oldclass, name, olddict[name], newdict[name], is_class_namespace=True)
427
428 old_bases = getattr(oldclass, '__bases__', None)
429 new_bases = getattr(newclass, '__bases__', None)
430 if str(old_bases) != str(new_bases):
431 notify_error('Changing the hierarchy of a class is not supported. %s may be inconsistent.' % (oldclass,))
432
433 self._handle_namespace(oldclass, is_class_namespace=True)
434
435
436 def _update_classmethod(self, oldcm, newcm):
437 """Update a classmethod update."""
438 # While we can't modify the classmethod object itself (it has no
439 # mutable attributes), we *can* extract the underlying function
440 # (by calling __get__(), which returns a method object) and update
441 # it in-place. We don't have the class available to pass to
442 # __get__() but any object except None will do.
443 self._update(None, None, oldcm.__get__(0), newcm.__get__(0))
444
445
446 def _update_staticmethod(self, oldsm, newsm):
447 """Update a staticmethod update."""
448 # While we can't modify the staticmethod object itself (it has no
449 # mutable attributes), we *can* extract the underlying function
450 # (by calling __get__(), which returns it) and update it in-place.
451 # We don't have the class available to pass to __get__() but any
452 # object except None will do.
453 self._update(None, None, oldsm.__get__(0), newsm.__get__(0))