Addon: fix wheel loading module separation
The loading of modules from wheels wasn't properly separated from the rest of Python yet. Now `load_wheel()` properly cleans up after itself, making it impossible for other code to do `import the_module_from_the_wheel`.
This commit is contained in:
parent
5be9985e3b
commit
55752c87a2
@ -3,25 +3,31 @@
|
|||||||
"""External dependencies loader."""
|
"""External dependencies loader."""
|
||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
|
import importlib
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import Iterator, Optional
|
from typing import Iterator
|
||||||
|
|
||||||
_my_dir = Path(__file__).parent
|
_my_dir = Path(__file__).parent
|
||||||
_log = logging.getLogger(__name__)
|
_log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def load_wheel(module_name: str, fname_prefix: str) -> ModuleType:
|
def load_wheel(module_name: str, fname_prefix: str = "") -> ModuleType:
|
||||||
"""Loads a wheel from 'fname_prefix*.whl', unless the named module can be imported.
|
"""Loads a wheel from 'fname_prefix*.whl', unless the named module can be imported.
|
||||||
|
|
||||||
This allows us to use system-installed packages before falling back to the shipped wheels.
|
This allows us to use system-installed packages before falling back to the shipped wheels.
|
||||||
This is useful for development, less so for deployment.
|
This is useful for development, less so for deployment.
|
||||||
|
|
||||||
|
If `fname_prefix` is the empty string, it will use the module name.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if not fname_prefix:
|
||||||
|
fname_prefix = _fname_prefix_from_module_name(module_name)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
module = __import__(module_name)
|
module = importlib.import_module(module_name)
|
||||||
except ImportError as ex:
|
except ImportError as ex:
|
||||||
_log.debug("Unable to import %s directly, will try wheel: %s", module_name, ex)
|
_log.debug("Unable to import %s directly, will try wheel: %s", module_name, ex)
|
||||||
else:
|
else:
|
||||||
@ -42,7 +48,7 @@ def load_wheel(module_name: str, fname_prefix: str) -> ModuleType:
|
|||||||
# this add-on from other add-ons.
|
# this add-on from other add-ons.
|
||||||
with _sys_path_mod_backup(wheel):
|
with _sys_path_mod_backup(wheel):
|
||||||
try:
|
try:
|
||||||
module = __import__(module_name)
|
module = importlib.import_module(module_name)
|
||||||
except ImportError as ex:
|
except ImportError as ex:
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
"Unable to load %r from %s: %s" % (module_name, wheel, ex)
|
"Unable to load %r from %s: %s" % (module_name, wheel, ex)
|
||||||
@ -53,15 +59,21 @@ def load_wheel(module_name: str, fname_prefix: str) -> ModuleType:
|
|||||||
return module
|
return module
|
||||||
|
|
||||||
|
|
||||||
def load_wheel_global(module_name: str, fname_prefix: str) -> ModuleType:
|
def load_wheel_global(module_name: str, fname_prefix: str = "") -> ModuleType:
|
||||||
"""Loads a wheel from 'fname_prefix*.whl', unless the named module can be imported.
|
"""Loads a wheel from 'fname_prefix*.whl', unless the named module can be imported.
|
||||||
|
|
||||||
This allows us to use system-installed packages before falling back to the shipped wheels.
|
This allows us to use system-installed packages before falling back to the shipped wheels.
|
||||||
This is useful for development, less so for deployment.
|
This is useful for development, less so for deployment.
|
||||||
|
|
||||||
|
If `fname_prefix` is the empty string, it will use the first package from `module_name`.
|
||||||
|
In other words, `module_name="pkg.subpkg"` will result in `fname_prefix="pkg"`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if not fname_prefix:
|
||||||
|
fname_prefix = _fname_prefix_from_module_name(module_name)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
module = __import__(module_name)
|
module = importlib.import_module(module_name)
|
||||||
except ImportError as ex:
|
except ImportError as ex:
|
||||||
_log.debug("Unable to import %s directly, will try wheel: %s", module_name, ex)
|
_log.debug("Unable to import %s directly, will try wheel: %s", module_name, ex)
|
||||||
else:
|
else:
|
||||||
@ -80,7 +92,7 @@ def load_wheel_global(module_name: str, fname_prefix: str) -> ModuleType:
|
|||||||
sys.path.insert(0, wheel_filepath)
|
sys.path.insert(0, wheel_filepath)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
module = __import__(module_name)
|
module = importlib.import_module(module_name)
|
||||||
except ImportError as ex:
|
except ImportError as ex:
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
"Unable to load %r from %s: %s" % (module_name, wheel, ex)
|
"Unable to load %r from %s: %s" % (module_name, wheel, ex)
|
||||||
@ -92,16 +104,24 @@ def load_wheel_global(module_name: str, fname_prefix: str) -> ModuleType:
|
|||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def _sys_path_mod_backup(wheel_file: Path) -> Iterator[None]:
|
def _sys_path_mod_backup(wheel_file: Path) -> Iterator[None]:
|
||||||
|
"""Temporarily inserts a wheel onto sys.path.
|
||||||
|
|
||||||
|
When the context exits, it restores sys.path and sys.modules, so that
|
||||||
|
anything that was imported within the context remains unimportable by other
|
||||||
|
modules.
|
||||||
|
"""
|
||||||
old_syspath = sys.path[:]
|
old_syspath = sys.path[:]
|
||||||
|
old_sysmod = sys.modules.copy()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sys.path.insert(0, str(wheel_file))
|
sys.path.insert(0, str(wheel_file))
|
||||||
yield
|
yield
|
||||||
finally:
|
finally:
|
||||||
# Restore without assigning new instances. That way references held by
|
# Restore without assigning a new list instance. That way references
|
||||||
# other code will stay valid.
|
# held by other code will stay valid.
|
||||||
|
|
||||||
sys.path[:] = old_syspath
|
sys.path[:] = old_syspath
|
||||||
|
sys.modules.clear()
|
||||||
|
sys.modules.update(old_sysmod)
|
||||||
|
|
||||||
|
|
||||||
def _wheel_filename(fname_prefix: str) -> Path:
|
def _wheel_filename(fname_prefix: str) -> Path:
|
||||||
@ -119,6 +139,10 @@ def _wheel_filename(fname_prefix: str) -> Path:
|
|||||||
return wheels[-1]
|
return wheels[-1]
|
||||||
|
|
||||||
|
|
||||||
|
def _fname_prefix_from_module_name(module_name: str) -> str:
|
||||||
|
return module_name.split(".", 1)[0]
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
wheel = _wheel_filename("python_dateutil")
|
wheel = _wheel_filename("python_dateutil")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user