| Tor Norbye | 3a2425a | 2013-11-04 10:16:08 -0800 | [diff] [blame^] | 1 | """ |
| 2 | Copied from the python xreload (available for change) |
| 3 | |
| 4 | Alternative to reload(). |
| 5 | |
| 6 | This works by executing the module in a scratch namespace, and then |
| 7 | patching classes, methods and functions in place. This avoids the |
| 8 | need to patch instances. New objects are copied into the target |
| 9 | namespace. |
| 10 | |
| 11 | Some of the many limitations include: |
| 12 | |
| 13 | - Global mutable objects other than classes are simply replaced, not patched |
| 14 | |
| 15 | - Code using metaclasses is not handled correctly |
| 16 | |
| 17 | - Code creating global singletons is not handled correctly |
| 18 | |
| 19 | - Functions and methods using decorators (other than classmethod and |
| 20 | staticmethod) is not handled correctly |
| 21 | |
| 22 | - Renamings are not handled correctly |
| 23 | |
| 24 | - Dependent modules are not reloaded |
| 25 | |
| 26 | - When a dependent module contains 'from foo import bar', and |
| 27 | reloading foo deletes foo.bar, the dependent module continues to use |
| 28 | the old foo.bar object rather than failing |
| 29 | |
| 30 | - Frozen modules and modules loaded from zip files aren't handled |
| 31 | correctly |
| 32 | |
| 33 | - Classes involving __slots__ are not handled correctly |
| 34 | """ |
| 35 | |
| 36 | import imp |
| 37 | import sys |
| 38 | import types |
| 39 | |
| 40 | |
| 41 | def xreload(mod): |
| 42 | """Reload a module in place, updating classes, methods and functions. |
| 43 | |
| 44 | Args: |
| 45 | mod: a module object |
| 46 | |
| 47 | Returns: |
| 48 | The (updated) input object itself. |
| 49 | """ |
| 50 | # Get the module name, e.g. 'foo.bar.whatever' |
| 51 | modname = mod.__name__ |
| 52 | # Get the module namespace (dict) early; this is part of the type check |
| 53 | modns = mod.__dict__ |
| 54 | # Parse it into package name and module name, e.g. 'foo.bar' and 'whatever' |
| 55 | i = modname.rfind(".") |
| 56 | if i >= 0: |
| 57 | pkgname, modname = modname[:i], modname[i+1:] |
| 58 | else: |
| 59 | pkgname = None |
| 60 | # Compute the search path |
| 61 | if pkgname: |
| 62 | # We're not reloading the package, only the module in it |
| 63 | pkg = sys.modules[pkgname] |
| 64 | path = pkg.__path__ # Search inside the package |
| 65 | else: |
| 66 | # Search the top-level module path |
| 67 | pkg = None |
| 68 | path = None # Make find_module() uses the default search path |
| 69 | # Find the module; may raise ImportError |
| 70 | (stream, filename, (suffix, mode, kind)) = imp.find_module(modname, path) |
| 71 | # Turn it into a code object |
| 72 | try: |
| 73 | # Is it Python source code or byte code read from a file? |
| 74 | if kind not in (imp.PY_COMPILED, imp.PY_SOURCE): |
| 75 | # Fall back to built-in reload() |
| 76 | return reload(mod) |
| 77 | if kind == imp.PY_SOURCE: |
| 78 | source = stream.read() |
| 79 | code = compile(source, filename, "exec") |
| 80 | else: |
| 81 | import marshal |
| 82 | code = marshal.load(stream) |
| 83 | finally: |
| 84 | if stream: |
| 85 | stream.close() |
| 86 | # Execute the code. We copy the module dict to a temporary; then |
| 87 | # clear the module dict; then execute the new code in the module |
| 88 | # dict; then swap things back and around. This trick (due to |
| 89 | # Glyph Lefkowitz) ensures that the (readonly) __globals__ |
| 90 | # attribute of methods and functions is set to the correct dict |
| 91 | # object. |
| 92 | tmpns = modns.copy() |
| 93 | modns.clear() |
| 94 | modns["__name__"] = tmpns["__name__"] |
| 95 | exec(code, modns) |
| 96 | # Now we get to the hard part |
| 97 | oldnames = set(tmpns) |
| 98 | newnames = set(modns) |
| 99 | # Update attributes in place |
| 100 | for name in oldnames & newnames: |
| 101 | modns[name] = _update(tmpns[name], modns[name]) |
| 102 | # Done! |
| 103 | return mod |
| 104 | |
| 105 | |
| 106 | def _update(oldobj, newobj): |
| 107 | """Update oldobj, if possible in place, with newobj. |
| 108 | |
| 109 | If oldobj is immutable, this simply returns newobj. |
| 110 | |
| 111 | Args: |
| 112 | oldobj: the object to be updated |
| 113 | newobj: the object used as the source for the update |
| 114 | |
| 115 | Returns: |
| 116 | either oldobj, updated in place, or newobj. |
| 117 | """ |
| 118 | if oldobj is newobj: |
| 119 | # Probably something imported |
| 120 | return newobj |
| 121 | if type(oldobj) is not type(newobj): |
| 122 | # Cop-out: if the type changed, give up |
| 123 | return newobj |
| 124 | if hasattr(newobj, "__reload_update__"): |
| 125 | # Provide a hook for updating |
| 126 | return newobj.__reload_update__(oldobj) |
| 127 | |
| 128 | if hasattr(types, 'ClassType'): |
| 129 | classtype = types.ClassType |
| 130 | else: |
| 131 | classtype = type |
| 132 | |
| 133 | if isinstance(newobj, classtype): |
| 134 | return _update_class(oldobj, newobj) |
| 135 | if isinstance(newobj, types.FunctionType): |
| 136 | return _update_function(oldobj, newobj) |
| 137 | if isinstance(newobj, types.MethodType): |
| 138 | return _update_method(oldobj, newobj) |
| 139 | if isinstance(newobj, classmethod): |
| 140 | return _update_classmethod(oldobj, newobj) |
| 141 | if isinstance(newobj, staticmethod): |
| 142 | return _update_staticmethod(oldobj, newobj) |
| 143 | # Not something we recognize, just give up |
| 144 | return newobj |
| 145 | |
| 146 | |
| 147 | # All of the following functions have the same signature as _update() |
| 148 | |
| 149 | |
| 150 | def _update_function(oldfunc, newfunc): |
| 151 | """Update a function object.""" |
| 152 | oldfunc.__doc__ = newfunc.__doc__ |
| 153 | oldfunc.__dict__.update(newfunc.__dict__) |
| 154 | |
| 155 | try: |
| 156 | oldfunc.__code__ = newfunc.__code__ |
| 157 | except AttributeError: |
| 158 | oldfunc.func_code = newfunc.func_code |
| 159 | try: |
| 160 | oldfunc.__defaults__ = newfunc.__defaults__ |
| 161 | except AttributeError: |
| 162 | oldfunc.func_defaults = newfunc.func_defaults |
| 163 | |
| 164 | return oldfunc |
| 165 | |
| 166 | |
| 167 | def _update_method(oldmeth, newmeth): |
| 168 | """Update a method object.""" |
| 169 | # XXX What if im_func is not a function? |
| 170 | _update(oldmeth.im_func, newmeth.im_func) |
| 171 | return oldmeth |
| 172 | |
| 173 | |
| 174 | def _update_class(oldclass, newclass): |
| 175 | """Update a class object.""" |
| 176 | olddict = oldclass.__dict__ |
| 177 | newdict = newclass.__dict__ |
| 178 | oldnames = set(olddict) |
| 179 | newnames = set(newdict) |
| 180 | for name in newnames - oldnames: |
| 181 | setattr(oldclass, name, newdict[name]) |
| 182 | for name in oldnames - newnames: |
| 183 | delattr(oldclass, name) |
| 184 | for name in oldnames & newnames - set(['__dict__', '__doc__']): |
| 185 | setattr(oldclass, name, _update(olddict[name], newdict[name])) |
| 186 | return oldclass |
| 187 | |
| 188 | |
| 189 | def _update_classmethod(oldcm, newcm): |
| 190 | """Update a classmethod update.""" |
| 191 | # While we can't modify the classmethod object itself (it has no |
| 192 | # mutable attributes), we *can* extract the underlying function |
| 193 | # (by calling __get__(), which returns a method object) and update |
| 194 | # it in-place. We don't have the class available to pass to |
| 195 | # __get__() but any object except None will do. |
| 196 | _update(oldcm.__get__(0), newcm.__get__(0)) |
| 197 | return newcm |
| 198 | |
| 199 | |
| 200 | def _update_staticmethod(oldsm, newsm): |
| 201 | """Update a staticmethod update.""" |
| 202 | # While we can't modify the staticmethod object itself (it has no |
| 203 | # mutable attributes), we *can* extract the underlying function |
| 204 | # (by calling __get__(), which returns it) and update it in-place. |
| 205 | # We don't have the class available to pass to __get__() but any |
| 206 | # object except None will do. |
| 207 | _update(oldsm.__get__(0), newsm.__get__(0)) |
| 208 | return newsm |