Add-on: load all BAT submodules in one go
Adjust the loading of BAT from a wheel file in such a way that all submodules are loaded in one go. This ensures that they're still isolated from the rest of Blender (so other add-ons won't find our BAT), but not from each other (so that there is only one copy of each submodule). In practice, this solves an issue where calling `blender_asset_tracer.blendfile.set_strict_pointer_mode(False)` had no effect. This was caused by each loaded submodule having a different copy of `blendfile`. Also loaded modules are logged more explicitly (at INFO level) to aid in debugging later on.
This commit is contained in:
parent
e576c5669f
commit
2215ed2d85
@ -9,13 +9,7 @@ import queue
|
|||||||
import threading
|
import threading
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from .. import wheels
|
from . import submodules
|
||||||
|
|
||||||
blendfile = wheels.load_wheel("blender_asset_tracer.blendfile")
|
|
||||||
pack = wheels.load_wheel("blender_asset_tracer.pack")
|
|
||||||
progress = wheels.load_wheel("blender_asset_tracer.pack.progress")
|
|
||||||
transfer = wheels.load_wheel("blender_asset_tracer.pack.transfer")
|
|
||||||
shaman = wheels.load_wheel("blender_asset_tracer.pack.shaman")
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -58,7 +52,7 @@ class MsgDone(Message):
|
|||||||
|
|
||||||
|
|
||||||
# MyPy doesn't understand the way BAT subpackages are imported.
|
# MyPy doesn't understand the way BAT subpackages are imported.
|
||||||
class BatProgress(progress.Callback): # type: ignore
|
class BatProgress(submodules.progress.Callback): # type: ignore
|
||||||
"""Report progress of BAT Packing to the given queue."""
|
"""Report progress of BAT Packing to the given queue."""
|
||||||
|
|
||||||
def __init__(self, queue: queue.SimpleQueue[Message]) -> None:
|
def __init__(self, queue: queue.SimpleQueue[Message]) -> None:
|
||||||
@ -128,7 +122,7 @@ class PackThread(threading.Thread):
|
|||||||
queue: queue.SimpleQueue[Message]
|
queue: queue.SimpleQueue[Message]
|
||||||
|
|
||||||
# MyPy doesn't understand the way BAT subpackages are imported.
|
# MyPy doesn't understand the way BAT subpackages are imported.
|
||||||
def __init__(self, packer: pack.Packer) -> None: # type: ignore
|
def __init__(self, packer: submodules.pack.Packer) -> None: # type: ignore
|
||||||
# Quitting Blender should abort the transfer (instead of hanging until
|
# Quitting Blender should abort the transfer (instead of hanging until
|
||||||
# the transfer is done), hence daemon=True.
|
# the transfer is done), hence daemon=True.
|
||||||
super().__init__(daemon=True, name="PackThread")
|
super().__init__(daemon=True, name="PackThread")
|
||||||
@ -197,7 +191,7 @@ def copy( # type: ignore
|
|||||||
exclusion_filter: str,
|
exclusion_filter: str,
|
||||||
*,
|
*,
|
||||||
relative_only: bool,
|
relative_only: bool,
|
||||||
packer_class=pack.Packer,
|
packer_class=submodules.pack.Packer,
|
||||||
packer_kwargs: Optional[dict[Any, Any]] = None,
|
packer_kwargs: Optional[dict[Any, Any]] = None,
|
||||||
) -> PackThread:
|
) -> PackThread:
|
||||||
"""Use BAT to copy the given file and dependencies to the target location.
|
"""Use BAT to copy the given file and dependencies to the target location.
|
||||||
@ -214,7 +208,7 @@ def copy( # type: ignore
|
|||||||
# Due to issues with library overrides and unsynced pointers, it's quite
|
# Due to issues with library overrides and unsynced pointers, it's quite
|
||||||
# common for the Blender Animation Studio to get crashes of BAT. To avoid
|
# common for the Blender Animation Studio to get crashes of BAT. To avoid
|
||||||
# these, Strict Pointer Mode is disabled.
|
# these, Strict Pointer Mode is disabled.
|
||||||
blendfile.set_strict_pointer_mode(False)
|
submodules.blendfile.set_strict_pointer_mode(False)
|
||||||
|
|
||||||
if packer_kwargs is None:
|
if packer_kwargs is None:
|
||||||
packer_kwargs = {}
|
packer_kwargs = {}
|
||||||
|
@ -7,13 +7,7 @@ from collections import deque
|
|||||||
from pathlib import Path, PurePath, PurePosixPath
|
from pathlib import Path, PurePath, PurePosixPath
|
||||||
from typing import TYPE_CHECKING, Optional, Any, Iterable, Iterator
|
from typing import TYPE_CHECKING, Optional, Any, Iterable, Iterator
|
||||||
|
|
||||||
from .. import wheels
|
from . import cache, submodules
|
||||||
from . import cache
|
|
||||||
|
|
||||||
bat_pack = wheels.load_wheel("blender_asset_tracer.pack")
|
|
||||||
bat_transfer = wheels.load_wheel("blender_asset_tracer.pack.transfer")
|
|
||||||
bat_bpathlib = wheels.load_wheel("blender_asset_tracer.bpathlib")
|
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..manager import ApiClient as _ApiClient
|
from ..manager import ApiClient as _ApiClient
|
||||||
@ -37,8 +31,8 @@ MAX_FAILED_PATHS = 8
|
|||||||
HashableShamanFileSpec = tuple[str, int, str]
|
HashableShamanFileSpec = tuple[str, int, str]
|
||||||
"""Tuple of the 'sha', 'size', and 'path' fields of a ShamanFileSpec."""
|
"""Tuple of the 'sha', 'size', and 'path' fields of a ShamanFileSpec."""
|
||||||
|
|
||||||
# Mypy doesn't understand that bat_pack.Packer exists.
|
# Mypy doesn't understand that submodules.pack.Packer exists.
|
||||||
class Packer(bat_pack.Packer): # type: ignore
|
class Packer(submodules.pack.Packer): # type: ignore
|
||||||
"""Creates BAT Packs on a Shaman server."""
|
"""Creates BAT Packs on a Shaman server."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@ -60,8 +54,8 @@ class Packer(bat_pack.Packer): # type: ignore
|
|||||||
self.api_client = api_client
|
self.api_client = api_client
|
||||||
self.shaman_transferrer: Optional[Transferrer] = None
|
self.shaman_transferrer: Optional[Transferrer] = None
|
||||||
|
|
||||||
# Mypy doesn't understand that bat_transfer.FileTransferer exists.
|
# Mypy doesn't understand that submodules.transfer.FileTransferer exists.
|
||||||
def _create_file_transferer(self) -> bat_transfer.FileTransferer: # type: ignore
|
def _create_file_transferer(self) -> submodules.transfer.FileTransferer: # type: ignore
|
||||||
self.shaman_transferrer = Transferrer(
|
self.shaman_transferrer = Transferrer(
|
||||||
self.api_client, self.project, self.checkout_path
|
self.api_client, self.project, self.checkout_path
|
||||||
)
|
)
|
||||||
@ -90,7 +84,7 @@ class Packer(bat_pack.Packer): # type: ignore
|
|||||||
self._check_aborted()
|
self._check_aborted()
|
||||||
|
|
||||||
|
|
||||||
class Transferrer(bat_transfer.FileTransferer): # type: ignore
|
class Transferrer(submodules.transfer.FileTransferer): # type: ignore
|
||||||
"""Sends files to a Shaman server."""
|
"""Sends files to a Shaman server."""
|
||||||
|
|
||||||
class AbortUpload(Exception):
|
class AbortUpload(Exception):
|
||||||
@ -213,7 +207,7 @@ class Transferrer(bat_transfer.FileTransferer): # type: ignore
|
|||||||
try:
|
try:
|
||||||
checksum = cache.compute_cached_checksum(src)
|
checksum = cache.compute_cached_checksum(src)
|
||||||
filesize = src.stat().st_size
|
filesize = src.stat().st_size
|
||||||
relpath = str(bat_bpathlib.strip_root(dst))
|
relpath = str(submodules.bpathlib.strip_root(dst))
|
||||||
|
|
||||||
filespec = ShamanFileSpec(
|
filespec = ShamanFileSpec(
|
||||||
sha=checksum,
|
sha=checksum,
|
||||||
@ -223,7 +217,7 @@ class Transferrer(bat_transfer.FileTransferer): # type: ignore
|
|||||||
filespecs.append(filespec)
|
filespecs.append(filespec)
|
||||||
self._rel_to_local_path[relpath] = src
|
self._rel_to_local_path[relpath] = src
|
||||||
|
|
||||||
if act == bat_transfer.Action.MOVE:
|
if act == submodules.transfer.Action.MOVE:
|
||||||
self._delete_when_done.append(src)
|
self._delete_when_done.append(src)
|
||||||
except Exception:
|
except Exception:
|
||||||
# We have to catch exceptions in a broad way, as this is running in
|
# We have to catch exceptions in a broad way, as this is running in
|
||||||
|
7
addon/flamenco/bat/submodules.py
Normal file
7
addon/flamenco/bat/submodules.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from .. import wheels
|
||||||
|
|
||||||
|
# Load all the submodules we need from BAT in one go.
|
||||||
|
_bat_modules = wheels.load_wheel(
|
||||||
|
"blender_asset_tracer",
|
||||||
|
("blendfile", "pack", "pack.progress", "pack.transfer", "pack.shaman", "bpathlib"))
|
||||||
|
bat_toplevel, blendfile, pack, progress, transfer, shaman, bpathlib = _bat_modules
|
@ -13,50 +13,42 @@ 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, submodules: tuple[str]) -> list[ModuleType]:
|
||||||
|
"""Loads modules from a wheel file 'module_name*.whl'.
|
||||||
|
|
||||||
def load_wheel(module_name: str, fname_prefix: str = "") -> ModuleType:
|
Loads `module_name`, and if submodules are given, loads
|
||||||
"""Loads a wheel from 'fname_prefix*.whl', unless the named module can be imported.
|
`module_name.submodule` for each of the submodules. This allows loading all
|
||||||
|
required modules from the same wheel in one session, ensuring that
|
||||||
|
inter-submodule references are correct.
|
||||||
|
|
||||||
This allows us to use system-installed packages before falling back to the shipped wheels.
|
Returns the loaded modules, so [module, submodule, submodule, ...].
|
||||||
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)
|
||||||
fname_prefix = _fname_prefix_from_module_name(module_name)
|
|
||||||
|
|
||||||
try:
|
|
||||||
module = importlib.import_module(module_name)
|
|
||||||
except ImportError as ex:
|
|
||||||
_log.debug("Unable to import %s directly, will try wheel: %s", module_name, ex)
|
|
||||||
else:
|
|
||||||
_log.debug(
|
|
||||||
"Was able to load %s from %s, no need to load wheel %s",
|
|
||||||
module_name,
|
|
||||||
module.__file__,
|
|
||||||
fname_prefix,
|
|
||||||
)
|
|
||||||
assert isinstance(module, ModuleType)
|
|
||||||
return module
|
|
||||||
|
|
||||||
wheel = _wheel_filename(fname_prefix)
|
wheel = _wheel_filename(fname_prefix)
|
||||||
|
|
||||||
|
loaded_modules: list[ModuleType] = []
|
||||||
|
to_load = [module_name] + [f"{module_name}.{submodule}" for submodule in submodules]
|
||||||
|
|
||||||
# Load the module from the wheel file. Keep a backup of sys.path so that it
|
# Load the module from the wheel file. Keep a backup of sys.path so that it
|
||||||
# can be restored later. This should ensure that future import statements
|
# can be restored later. This should ensure that future import statements
|
||||||
# cannot find this wheel file, increasing the separation of dependencies of
|
# cannot find this wheel file, increasing the separation of dependencies of
|
||||||
# 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:
|
for modname in to_load:
|
||||||
module = importlib.import_module(module_name)
|
try:
|
||||||
except ImportError as ex:
|
module = importlib.import_module(modname)
|
||||||
raise ImportError(
|
except ImportError as ex:
|
||||||
"Unable to load %r from %s: %s" % (module_name, wheel, ex)
|
raise ImportError(
|
||||||
) from None
|
"Unable to load %r from %s: %s" % (modname, wheel, ex)
|
||||||
|
) from None
|
||||||
|
assert isinstance(module, ModuleType)
|
||||||
|
loaded_modules.append(module)
|
||||||
|
_log.info("Loaded %s from %s", modname, module.__file__)
|
||||||
|
|
||||||
_log.debug("Loaded %s from %s", module_name, module.__file__)
|
assert len(loaded_modules) == len(to_load), \
|
||||||
assert isinstance(module, ModuleType)
|
f"expecting to load {len(to_load)} modules, but only have {len(loaded_modules)}: {loaded_modules}"
|
||||||
return module
|
return loaded_modules
|
||||||
|
|
||||||
|
|
||||||
def load_wheel_global(module_name: str, fname_prefix: str = "") -> ModuleType:
|
def load_wheel_global(module_name: str, fname_prefix: str = "") -> ModuleType:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user