| Tor Norbye | 3a2425a | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 1 | """ |
| Tor Norbye | c667c1f | 2014-05-28 17:06:51 -0700 | [diff] [blame] | 2 | Based on the python xreload. |
| 3 | |
| 4 | Changes |
| 5 | ====================== |
| 6 | |
| 7 | 1. we don't recreate the old namespace from new classes. Rather, we keep the existing namespace, |
| 8 | load a new version of it and update only some of the things we can inplace. That way, we don't break |
| 9 | things such as singletons or end up with a second representation of the same class in memory. |
| 10 | |
| 11 | 2. If we find it to be a __metaclass__, we try to update it as a regular class. |
| 12 | |
| 13 | 3. We don't remove old attributes (and leave them lying around even if they're no longer used). |
| 14 | |
| 15 | 4. Reload hooks were changed |
| 16 | |
| 17 | These changes make it more stable, especially in the common case (where in a debug session only the |
| 18 | contents of a function are changed), besides providing flexibility for users that want to extend |
| 19 | on it. |
| 20 | |
| 21 | |
| 22 | |
| 23 | Hooks |
| 24 | ====================== |
| 25 | |
| 26 | Classes/modules can be specially crafted to work with the reload (so that it can, for instance, |
| 27 | update some constant which was changed). |
| 28 | |
| 29 | 1. 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 | |
| 45 | 2. 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 | |
| 61 | Important: when providing a hook, always use the namespace or cls provided and not anything in the global |
| 62 | namespace, as the global namespace are only temporarily created during the reload and may not reflect the |
| 63 | actual application state (while the cls and namespace passed are). |
| 64 | |
| 65 | |
| 66 | Current 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 | |
| 85 | Info |
| 86 | ====================== |
| 87 | |
| 88 | Original: http://svn.python.org/projects/sandbox/trunk/xreload/xreload.py |
| 89 | Note: it seems https://github.com/plone/plone.reload/blob/master/plone/reload/xreload.py enhances it (to check later) |
| 90 | |
| 91 | Interesting alternative: https://code.google.com/p/reimport/ |
| Tor Norbye | 3a2425a | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 92 | |
| 93 | Alternative to reload(). |
| 94 | |
| Tor Norbye | c667c1f | 2014-05-28 17:06:51 -0700 | [diff] [blame] | 95 | This works by executing the module in a scratch namespace, and then patching classes, methods and |
| 96 | functions in place. This avoids the need to patch instances. New objects are copied into the |
| 97 | target namespace. |
| Tor Norbye | 3a2425a | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 98 | |
| Tor Norbye | 3a2425a | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 99 | """ |
| 100 | |
| 101 | import imp |
| Tor Norbye | c667c1f | 2014-05-28 17:06:51 -0700 | [diff] [blame] | 102 | from pydev_imports import Exec |
| 103 | import pydevd_dont_trace |
| Tor Norbye | 3a2425a | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 104 | import sys |
| Tor Norbye | c667c1f | 2014-05-28 17:06:51 -0700 | [diff] [blame] | 105 | import traceback |
| Tor Norbye | 3a2425a | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 106 | import types |
| 107 | |
| Tor Norbye | c667c1f | 2014-05-28 17:06:51 -0700 | [diff] [blame] | 108 | NO_DEBUG = 0 |
| 109 | LEVEL1 = 1 |
| 110 | LEVEL2 = 2 |
| Tor Norbye | 3a2425a | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 111 | |
| Tor Norbye | c667c1f | 2014-05-28 17:06:51 -0700 | [diff] [blame] | 112 | DEBUG = NO_DEBUG |
| 113 | |
| 114 | def 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 | |
| 122 | def 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 | |
| 130 | def notify_info0(*args): |
| 131 | write_err(*args) |
| 132 | |
| 133 | def notify_info(*args): |
| 134 | if DEBUG >= LEVEL1: |
| 135 | write(*args) |
| 136 | |
| 137 | def notify_info2(*args): |
| 138 | if DEBUG >= LEVEL2: |
| 139 | write(*args) |
| 140 | |
| 141 | def notify_error(*args): |
| 142 | write_err(*args) |
| 143 | |
| 144 | |
| 145 | |
| 146 | #======================================================================================================================= |
| 147 | # code_objects_equal |
| 148 | #======================================================================================================================= |
| 149 | def 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 Norbye | 3a2425a | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 161 | def xreload(mod): |
| 162 | """Reload a module in place, updating classes, methods and functions. |
| 163 | |
| Tor Norbye | c667c1f | 2014-05-28 17:06:51 -0700 | [diff] [blame] | 164 | mod: a module object |
| Tor Norbye | 3a2425a | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 165 | |
| Tor Norbye | c667c1f | 2014-05-28 17:06:51 -0700 | [diff] [blame] | 166 | Returns a boolean indicating whether a change was done. |
| Tor Norbye | 3a2425a | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 167 | """ |
| Tor Norbye | c667c1f | 2014-05-28 17:06:51 -0700 | [diff] [blame] | 168 | 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 Norbye | 3a2425a | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 174 | |
| 175 | |
| Tor Norbye | c667c1f | 2014-05-28 17:06:51 -0700 | [diff] [blame] | 176 | # 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 Norbye | 3a2425a | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 188 | |
| 189 | |
| Tor Norbye | c667c1f | 2014-05-28 17:06:51 -0700 | [diff] [blame] | 190 | #======================================================================================================================= |
| 191 | # Reload |
| 192 | #======================================================================================================================= |
| 193 | class 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 Norbye | 3a2425a | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 271 | |
| 272 | |
| Tor Norbye | c667c1f | 2014-05-28 17:06:51 -0700 | [diff] [blame] | 273 | 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 Norbye | 3a2425a | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 285 | |
| 286 | |
| Tor Norbye | c667c1f | 2014-05-28 17:06:51 -0700 | [diff] [blame] | 287 | 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 Norbye | 3a2425a | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 290 | |
| 291 | |
| Tor Norbye | c667c1f | 2014-05-28 17:06:51 -0700 | [diff] [blame] | 292 | |
| 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 Norbye | 3a2425a | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 365 | |
| 366 | |
| Tor Norbye | c667c1f | 2014-05-28 17:06:51 -0700 | [diff] [blame] | 367 | # All of the following functions have the same signature as _update() |
| Tor Norbye | 3a2425a | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 368 | |
| 369 | |
| Tor Norbye | c667c1f | 2014-05-28 17:06:51 -0700 | [diff] [blame] | 370 | 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)) |