|
20 | 20 | from .version import version as __version__ # NOQA:F401
|
21 | 21 |
|
22 | 22 |
|
| 23 | +_PY2 = sys.version_info[0] == 2 |
| 24 | +_PRESERVED_MODULE_ATTRS = { |
| 25 | + "__file__", |
| 26 | + "__version__", |
| 27 | + "__loader__", |
| 28 | + "__path__", |
| 29 | + "__package__", |
| 30 | + "__doc__", |
| 31 | + "__spec__", |
| 32 | + "__dict__", |
| 33 | +} |
| 34 | + |
| 35 | + |
23 | 36 | def _py_abspath(path):
|
24 | 37 | """
|
25 | 38 | special version of abspath
|
@@ -48,33 +61,85 @@ def distribution_version(name):
|
48 | 61 | def initpkg(pkgname, exportdefs, attr=None, eager=False):
|
49 | 62 | """ initialize given package from the export definitions. """
|
50 | 63 | attr = attr or {}
|
51 |
| - oldmod = sys.modules.get(pkgname) |
| 64 | + mod = sys.modules.get(pkgname) |
| 65 | + |
| 66 | + if _PY2: |
| 67 | + mod = _initpkg_py2(mod, pkgname, exportdefs, attr=attr) |
| 68 | + else: |
| 69 | + mod = _initpkg_py3(mod, pkgname, exportdefs, attr=attr) |
| 70 | + |
| 71 | + # eagerload in bypthon to avoid their monkeypatching breaking packages |
| 72 | + if "bpython" in sys.modules or eager: |
| 73 | + for module in list(sys.modules.values()): |
| 74 | + if isinstance(module, ApiModule): |
| 75 | + module.__dict__ |
| 76 | + |
| 77 | + return mod |
| 78 | + |
| 79 | + |
| 80 | +def _initpkg_py2(mod, pkgname, exportdefs, attr=None): |
| 81 | + """Python 2 helper for initpkg. |
| 82 | +
|
| 83 | + In Python 2 we can't update __class__ for an instance of types.Module, and |
| 84 | + imports are protected by the global import lock anyway, so it is safe for a |
| 85 | + module to replace itself during import. |
| 86 | +
|
| 87 | + """ |
52 | 88 | d = {}
|
53 |
| - f = getattr(oldmod, "__file__", None) |
| 89 | + f = getattr(mod, "__file__", None) |
54 | 90 | if f:
|
55 | 91 | f = _py_abspath(f)
|
56 | 92 | d["__file__"] = f
|
57 |
| - if hasattr(oldmod, "__version__"): |
58 |
| - d["__version__"] = oldmod.__version__ |
59 |
| - if hasattr(oldmod, "__loader__"): |
60 |
| - d["__loader__"] = oldmod.__loader__ |
61 |
| - if hasattr(oldmod, "__path__"): |
62 |
| - d["__path__"] = [_py_abspath(p) for p in oldmod.__path__] |
63 |
| - if hasattr(oldmod, "__package__"): |
64 |
| - d["__package__"] = oldmod.__package__ |
65 |
| - if "__doc__" not in exportdefs and getattr(oldmod, "__doc__", None): |
66 |
| - d["__doc__"] = oldmod.__doc__ |
67 |
| - d["__spec__"] = getattr(oldmod, "__spec__", None) |
| 93 | + if hasattr(mod, "__version__"): |
| 94 | + d["__version__"] = mod.__version__ |
| 95 | + if hasattr(mod, "__loader__"): |
| 96 | + d["__loader__"] = mod.__loader__ |
| 97 | + if hasattr(mod, "__path__"): |
| 98 | + d["__path__"] = [_py_abspath(p) for p in mod.__path__] |
| 99 | + if hasattr(mod, "__package__"): |
| 100 | + d["__package__"] = mod.__package__ |
| 101 | + if "__doc__" not in exportdefs and getattr(mod, "__doc__", None): |
| 102 | + d["__doc__"] = mod.__doc__ |
| 103 | + d["__spec__"] = getattr(mod, "__spec__", None) |
68 | 104 | d.update(attr)
|
69 |
| - if hasattr(oldmod, "__dict__"): |
70 |
| - oldmod.__dict__.update(d) |
| 105 | + if hasattr(mod, "__dict__"): |
| 106 | + mod.__dict__.update(d) |
71 | 107 | mod = ApiModule(pkgname, exportdefs, implprefix=pkgname, attr=d)
|
72 | 108 | sys.modules[pkgname] = mod
|
73 |
| - # eagerload in bypthon to avoid their monkeypatching breaking packages |
74 |
| - if "bpython" in sys.modules or eager: |
75 |
| - for module in list(sys.modules.values()): |
76 |
| - if isinstance(module, ApiModule): |
77 |
| - module.__dict__ |
| 109 | + return mod |
| 110 | + |
| 111 | + |
| 112 | +def _initpkg_py3(mod, pkgname, exportdefs, attr=None): |
| 113 | + """Python 3 helper for initpkg. |
| 114 | +
|
| 115 | + Python 3.3+ uses finer grained locking for imports, and checks sys.modules before |
| 116 | + acquiring the lock to avoid the overhead of the fine-grained locking. This |
| 117 | + introduces a race condition when a module is imported by multiple threads |
| 118 | + concurrently - some threads will see the initial module and some the replacement |
| 119 | + ApiModule. We avoid this by updating the existing module in-place. |
| 120 | +
|
| 121 | + """ |
| 122 | + if mod is None: |
| 123 | + d = {"__file__": None, "__spec__": None} |
| 124 | + d.update(attr) |
| 125 | + mod = ApiModule(pkgname, exportdefs, implprefix=pkgname, attr=d) |
| 126 | + sys.modules[pkgname] = mod |
| 127 | + else: |
| 128 | + f = getattr(mod, "__file__", None) |
| 129 | + if f: |
| 130 | + f = _py_abspath(f) |
| 131 | + mod.__file__ = f |
| 132 | + if hasattr(mod, "__path__"): |
| 133 | + mod.__path__ = [_py_abspath(p) for p in mod.__path__] |
| 134 | + if "__doc__" in exportdefs and hasattr(mod, "__doc__"): |
| 135 | + del mod.__doc__ |
| 136 | + for name in dir(mod): |
| 137 | + if name not in _PRESERVED_MODULE_ATTRS: |
| 138 | + delattr(mod, name) |
| 139 | + |
| 140 | + # Updating class of existing module as per importlib.util.LazyLoader |
| 141 | + mod.__class__ = ApiModule |
| 142 | + mod.__init__(pkgname, exportdefs, implprefix=pkgname, attr=attr) |
78 | 143 | return mod
|
79 | 144 |
|
80 | 145 |
|
|
0 commit comments