4.2LTS: Extensions: forward any errors setting up wheels to reports
When actions on extensions failed setup extensions, errors weren't
forwarded to the user and were only visible in the terminal.
---
Back ported from fb3fe458bf
Pull Request: https://projects.blender.org/blender/blender/pulls/139017
This commit is contained in:
committed by
Philipp Oeser
parent
0269725011
commit
7629abe435
@@ -1116,7 +1116,12 @@ def _extensions_enabled_from_repo_directory_and_pkg_id_sequence(repo_directory_a
|
||||
return extensions_enabled_pending
|
||||
|
||||
|
||||
def _extensions_repo_sync_wheels(repo_cache_store, extensions_enabled):
|
||||
def _extensions_repo_sync_wheels(
|
||||
repo_cache_store, # `bl_extension_utils.RepoCacheStore`
|
||||
extensions_enabled, # `set[tuple[str, str]]`
|
||||
*,
|
||||
error_fn, # `Callable[[Exception], None]`
|
||||
): # `-> None`
|
||||
"""
|
||||
This function collects all wheels from all packages and ensures the packages are either extracted or removed
|
||||
when they are no longer used.
|
||||
@@ -1128,7 +1133,7 @@ def _extensions_repo_sync_wheels(repo_cache_store, extensions_enabled):
|
||||
wheel_list = []
|
||||
|
||||
for repo_index, pkg_manifest_local in enumerate(repo_cache_store.pkg_manifest_from_local_ensure(
|
||||
error_fn=print,
|
||||
error_fn=error_fn,
|
||||
ignore_missing=True,
|
||||
)):
|
||||
repo = repos_all[repo_index]
|
||||
@@ -1157,13 +1162,25 @@ def _extensions_repo_sync_wheels(repo_cache_store, extensions_enabled):
|
||||
local_dir=local_dir,
|
||||
wheel_list=wheel_list,
|
||||
debug=bpy.app.debug_python,
|
||||
error_fn=error_fn,
|
||||
)
|
||||
|
||||
|
||||
def _extensions_repo_refresh_on_change(repo_cache_store, *, extensions_enabled, compat_calc, stats_calc):
|
||||
def _extensions_repo_refresh_on_change(
|
||||
repo_cache_store, # `bl_extension_utils.RepoCacheStore`
|
||||
*,
|
||||
extensions_enabled, # `set[tuple[str, str]] | None`
|
||||
compat_calc, # `bool`
|
||||
stats_calc, # `bool`
|
||||
error_fn, # `Callable[[Exception], None]`
|
||||
): # `-> None`
|
||||
import addon_utils
|
||||
if extensions_enabled is not None:
|
||||
_extensions_repo_sync_wheels(repo_cache_store, extensions_enabled)
|
||||
_extensions_repo_sync_wheels(
|
||||
repo_cache_store,
|
||||
extensions_enabled,
|
||||
error_fn=error_fn,
|
||||
)
|
||||
# Wheel sync handled above.
|
||||
|
||||
if compat_calc:
|
||||
@@ -1181,6 +1198,7 @@ def _extensions_repo_refresh_on_change(repo_cache_store, *, extensions_enabled,
|
||||
addon_utils.extensions_refresh(
|
||||
ensure_wheels=False,
|
||||
addon_modules_pending=addon_modules_pending,
|
||||
handle_error=error_fn,
|
||||
)
|
||||
|
||||
if stats_calc:
|
||||
@@ -1773,7 +1791,10 @@ class EXTENSIONS_OT_repo_refresh_all(Operator):
|
||||
# In-line `bpy.ops.preferences.addon_refresh`.
|
||||
addon_utils.modules_refresh()
|
||||
# Ensure compatibility info and wheels is up to date.
|
||||
addon_utils.extensions_refresh(ensure_wheels=True)
|
||||
addon_utils.extensions_refresh(
|
||||
ensure_wheels=True,
|
||||
handle_error=lambda ex: self.report({'ERROR'}, str(ex)),
|
||||
)
|
||||
|
||||
_preferences_ui_redraw()
|
||||
_preferences_ui_refresh_addons()
|
||||
@@ -2128,6 +2149,7 @@ class EXTENSIONS_OT_package_upgrade_all(Operator, _ExtCmdMixIn):
|
||||
),
|
||||
compat_calc=True,
|
||||
stats_calc=True,
|
||||
error_fn=handle_error,
|
||||
)
|
||||
|
||||
_preferences_ensure_enabled_all(
|
||||
@@ -2240,6 +2262,10 @@ class EXTENSIONS_OT_package_install_marked(Operator, _ExtCmdMixIn):
|
||||
lock_result_any_failed_with_report(self, self.repo_lock.release(), report_type='WARNING')
|
||||
del self.repo_lock
|
||||
|
||||
# TODO: it would be nice to include this message in the banner.
|
||||
def handle_error(ex):
|
||||
self.report({'ERROR'}, str(ex))
|
||||
|
||||
# Refresh installed packages for repositories that were operated on.
|
||||
repo_cache_store = repo_cache_store_ensure()
|
||||
for directory in self._repo_directories:
|
||||
@@ -2263,12 +2289,9 @@ class EXTENSIONS_OT_package_install_marked(Operator, _ExtCmdMixIn):
|
||||
extensions_enabled=extensions_enabled,
|
||||
compat_calc=True,
|
||||
stats_calc=True,
|
||||
error_fn=handle_error,
|
||||
)
|
||||
|
||||
# TODO: it would be nice to include this message in the banner.
|
||||
def handle_error(ex):
|
||||
self.report({'ERROR'}, str(ex))
|
||||
|
||||
for directory, pkg_id_sequence in self._repo_map_packages_addon_only:
|
||||
|
||||
pkg_manifest_local = repo_cache_store.refresh_local_from_directory(
|
||||
@@ -2297,6 +2320,7 @@ class EXTENSIONS_OT_package_install_marked(Operator, _ExtCmdMixIn):
|
||||
extensions_enabled=extensions_enabled_test,
|
||||
compat_calc=False,
|
||||
stats_calc=False,
|
||||
error_fn=handle_error,
|
||||
)
|
||||
|
||||
_preferences_ui_redraw()
|
||||
@@ -2400,6 +2424,10 @@ class EXTENSIONS_OT_package_uninstall_marked(Operator, _ExtCmdMixIn):
|
||||
lock_result_any_failed_with_report(self, self.repo_lock.release(), report_type='WARNING')
|
||||
del self.repo_lock
|
||||
|
||||
# TODO: it would be nice to include this message in the banner.
|
||||
def handle_error(ex):
|
||||
self.report({'ERROR'}, str(ex))
|
||||
|
||||
for directory, pkg_id_sequence in self._pkg_id_sequence_from_directory.items():
|
||||
_extensions_repo_temp_files_make_stale(repo_directory=directory)
|
||||
_extensions_repo_uninstall_stale_package_fallback(
|
||||
@@ -2420,6 +2448,7 @@ class EXTENSIONS_OT_package_uninstall_marked(Operator, _ExtCmdMixIn):
|
||||
extensions_enabled=_extensions_enabled(),
|
||||
compat_calc=True,
|
||||
stats_calc=True,
|
||||
error_fn=handle_error,
|
||||
)
|
||||
|
||||
_preferences_theme_state_restore(self._theme_restore)
|
||||
@@ -2622,6 +2651,10 @@ class EXTENSIONS_OT_package_install_files(Operator, _ExtCmdMixIn):
|
||||
lock_result_any_failed_with_report(self, self.repo_lock.release(), report_type='WARNING')
|
||||
del self.repo_lock
|
||||
|
||||
# TODO: it would be nice to include this message in the banner.
|
||||
def handle_error(ex):
|
||||
self.report({'ERROR'}, str(ex))
|
||||
|
||||
pkg_manifest_local = repo_cache_store.refresh_local_from_directory(
|
||||
directory=self.repo_directory,
|
||||
error_fn=self.error_fn_from_exception,
|
||||
@@ -2642,13 +2675,9 @@ class EXTENSIONS_OT_package_install_files(Operator, _ExtCmdMixIn):
|
||||
extensions_enabled=extensions_enabled,
|
||||
compat_calc=True,
|
||||
stats_calc=True,
|
||||
error_fn=handle_error,
|
||||
)
|
||||
|
||||
# TODO: it would be nice to include this message in the banner.
|
||||
|
||||
def handle_error(ex):
|
||||
self.report({'ERROR'}, str(ex))
|
||||
|
||||
_preferences_ensure_enabled_all(
|
||||
addon_restore=self._addon_restore,
|
||||
handle_error=handle_error,
|
||||
@@ -2678,6 +2707,7 @@ class EXTENSIONS_OT_package_install_files(Operator, _ExtCmdMixIn):
|
||||
extensions_enabled=extensions_enabled_test,
|
||||
compat_calc=False,
|
||||
stats_calc=False,
|
||||
error_fn=handle_error,
|
||||
)
|
||||
|
||||
_extensions_repo_temp_files_make_stale(self.repo_directory)
|
||||
@@ -2994,6 +3024,10 @@ class EXTENSIONS_OT_package_install(Operator, _ExtCmdMixIn):
|
||||
|
||||
def exec_command_finish(self, canceled):
|
||||
|
||||
# TODO: it would be nice to include this message in the banner.
|
||||
def handle_error(ex):
|
||||
self.report({'ERROR'}, str(ex))
|
||||
|
||||
# Unlock repositories.
|
||||
lock_result_any_failed_with_report(self, self.repo_lock.release(), report_type='WARNING')
|
||||
del self.repo_lock
|
||||
@@ -3021,12 +3055,9 @@ class EXTENSIONS_OT_package_install(Operator, _ExtCmdMixIn):
|
||||
extensions_enabled=extensions_enabled,
|
||||
compat_calc=True,
|
||||
stats_calc=True,
|
||||
error_fn=handle_error,
|
||||
)
|
||||
|
||||
# TODO: it would be nice to include this message in the banner.
|
||||
def handle_error(ex):
|
||||
self.report({'ERROR'}, str(ex))
|
||||
|
||||
_preferences_ensure_enabled_all(
|
||||
addon_restore=self._addon_restore,
|
||||
handle_error=handle_error,
|
||||
@@ -3056,6 +3087,7 @@ class EXTENSIONS_OT_package_install(Operator, _ExtCmdMixIn):
|
||||
extensions_enabled=extensions_enabled_test,
|
||||
compat_calc=False,
|
||||
stats_calc=False,
|
||||
error_fn=handle_error,
|
||||
)
|
||||
|
||||
_extensions_repo_temp_files_make_stale(self.repo_directory)
|
||||
@@ -3459,6 +3491,10 @@ class EXTENSIONS_OT_package_uninstall(Operator, _ExtCmdMixIn):
|
||||
|
||||
def exec_command_finish(self, canceled):
|
||||
|
||||
# TODO: it would be nice to include this message in the banner.
|
||||
def handle_error(ex):
|
||||
self.report({'ERROR'}, str(ex))
|
||||
|
||||
_extensions_repo_temp_files_make_stale(repo_directory=self.repo_directory)
|
||||
_extensions_repo_uninstall_stale_package_fallback(
|
||||
repo_directory=self.repo_directory,
|
||||
@@ -3494,6 +3530,7 @@ class EXTENSIONS_OT_package_uninstall(Operator, _ExtCmdMixIn):
|
||||
extensions_enabled=_extensions_enabled(),
|
||||
compat_calc=True,
|
||||
stats_calc=True,
|
||||
error_fn=handle_error,
|
||||
)
|
||||
|
||||
_preferences_theme_state_restore(self._theme_restore)
|
||||
|
||||
@@ -13,6 +13,7 @@ __all__ = (
|
||||
"apply_action",
|
||||
)
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
@@ -21,6 +22,7 @@ import zipfile
|
||||
|
||||
from collections.abc import (
|
||||
Callable,
|
||||
Iterator,
|
||||
)
|
||||
|
||||
WheelSource = tuple[
|
||||
@@ -179,6 +181,9 @@ def _zipfile_extractall_safe(
|
||||
zip_fh: zipfile.ZipFile,
|
||||
path: str,
|
||||
path_restrict: str,
|
||||
*,
|
||||
error_fn: Callable[[Exception], None],
|
||||
remove_error_fn: Callable[[str, Exception], None],
|
||||
) -> None:
|
||||
"""
|
||||
A version of ``ZipFile.extractall`` that wont write to paths outside ``path_restrict``.
|
||||
@@ -203,20 +208,69 @@ def _zipfile_extractall_safe(
|
||||
path_restrict_with_slash = path_restrict + sep
|
||||
assert len(path) >= len(path_restrict_with_slash)
|
||||
if not path.startswith(path_restrict_with_slash):
|
||||
raise Exception("Expected the restricted directory to start with ")
|
||||
# This is an internal error if it ever happens.
|
||||
raise Exception("Expected the restricted directory to start with \"{:s}\"".format(path_restrict_with_slash))
|
||||
|
||||
has_error = False
|
||||
member_index = 0
|
||||
|
||||
# Use an iterator to avoid duplicating the checks (for the cleanup pass).
|
||||
def zip_iter_filtered(*, verbose: bool) -> Iterator[tuple[zipfile.ZipInfo, str, str]]:
|
||||
for member in zip_fh.infolist():
|
||||
filename_orig = member.filename
|
||||
filename_next = path_prefix + filename_orig
|
||||
|
||||
# This isn't likely to happen so accept a noisy print here.
|
||||
# If this ends up happening more often, it could be suppressed.
|
||||
# (although this hints at bigger problems because we might be excluding necessary files).
|
||||
if os.path.normpath(filename_next).startswith(".." + sep):
|
||||
if verbose:
|
||||
print("Skipping path:", filename_next, "that escapes:", path_restrict)
|
||||
continue
|
||||
yield member, filename_orig, filename_next
|
||||
|
||||
for member, filename_orig, filename_next in zip_iter_filtered(verbose=True):
|
||||
# Increment before extracting, so a potential cleanup will a file that failed to extract.
|
||||
member_index += 1
|
||||
|
||||
member.filename = filename_next
|
||||
|
||||
# Extraction can fail for many reasons, see: #132924.
|
||||
try:
|
||||
zip_fh.extract(member, path_restrict)
|
||||
except Exception as ex:
|
||||
error_fn(ex)
|
||||
|
||||
filepath_native = path_restrict + sep + filename_next.replace("/", sep)
|
||||
print("Failed to extract path:", filepath_native, "error", str(ex))
|
||||
remove_error_fn(filepath_native, ex)
|
||||
has_error = True
|
||||
|
||||
for member in zip_fh.infolist():
|
||||
filename_orig = member.filename
|
||||
member.filename = path_prefix + filename_orig
|
||||
# This isn't likely to happen so accept a noisy print here.
|
||||
# If this ends up happening more often, it could be suppressed.
|
||||
# (although this hints at bigger problems because we might be excluding necessary files).
|
||||
if os.path.normpath(member.filename).startswith(".." + sep):
|
||||
print("Skipping path:", member.filename, "that escapes:", path_restrict)
|
||||
continue
|
||||
zip_fh.extract(member, path_restrict)
|
||||
member.filename = filename_orig
|
||||
|
||||
if has_error:
|
||||
break
|
||||
|
||||
# If the zip-file failed to extract, remove all files that were extracted.
|
||||
# This is done so failure to extract a file never results in a partially-working
|
||||
# state which can cause confusing situations for users.
|
||||
if has_error:
|
||||
# NOTE: this currently leaves empty directories which is not ideal.
|
||||
# It's possible to calculate directories created by this extraction but more involved.
|
||||
member_cleanup_len = member_index + 1
|
||||
member_index = 0
|
||||
|
||||
for member, filename_orig, filename_next in zip_iter_filtered(verbose=False):
|
||||
member_index += 1
|
||||
if member_index >= member_cleanup_len:
|
||||
break
|
||||
|
||||
filepath_native = path_restrict + sep + filename_next.replace("/", sep)
|
||||
try:
|
||||
os.unlink(filepath_native)
|
||||
except Exception as ex:
|
||||
remove_error_fn(filepath_native, ex)
|
||||
|
||||
|
||||
WHEEL_VERSION_RE = re.compile(r"(\d+)?(?:\.(\d+))?(?:\.(\d+))")
|
||||
|
||||
@@ -311,6 +365,7 @@ def apply_action(
|
||||
local_dir: str,
|
||||
local_dir_site_packages: str,
|
||||
wheel_list: list[WheelSource],
|
||||
error_fn: Callable[[Exception], None],
|
||||
remove_error_fn: Callable[[str, Exception], None],
|
||||
debug: bool,
|
||||
) -> None:
|
||||
@@ -419,5 +474,20 @@ def apply_action(
|
||||
filepath = wheels_dir_info_to_filepath_map[dir_info]
|
||||
# `ZipFile.extractall` is needed because some wheels contain paths that point to parent directories.
|
||||
# Handle this *safely* by allowing extracting to parent directories but limit this to the `local_dir`.
|
||||
with zipfile.ZipFile(filepath, mode="r") as zip_fh:
|
||||
_zipfile_extractall_safe(zip_fh, local_dir_site_packages, local_dir)
|
||||
|
||||
try:
|
||||
# pylint: disable-next=consider-using-with
|
||||
zip_fh_context = zipfile.ZipFile(filepath, mode="r")
|
||||
except Exception as ex:
|
||||
print("Error ({:s}) opening zip-file: {:s}".format(str(ex), filepath))
|
||||
error_fn(ex)
|
||||
continue
|
||||
|
||||
with contextlib.closing(zip_fh_context) as zip_fh:
|
||||
_zipfile_extractall_safe(
|
||||
zip_fh,
|
||||
local_dir_site_packages,
|
||||
local_dir,
|
||||
error_fn=error_fn,
|
||||
remove_error_fn=remove_error_fn,
|
||||
)
|
||||
|
||||
@@ -13,6 +13,7 @@ __all__ = (
|
||||
"reset_all",
|
||||
"module_bl_info",
|
||||
"extensions_refresh",
|
||||
"stale_pending_remove_paths",
|
||||
"stale_pending_stage_paths",
|
||||
)
|
||||
|
||||
@@ -347,7 +348,10 @@ def enable(module_name, *, default_set=False, persistent=False, refresh_handled=
|
||||
|
||||
if (is_extension := module_name.startswith(_ext_base_pkg_idname_with_dot)):
|
||||
if not refresh_handled:
|
||||
extensions_refresh(addon_modules_pending=[module_name])
|
||||
extensions_refresh(
|
||||
addon_modules_pending=[module_name],
|
||||
handle_error=handle_error,
|
||||
)
|
||||
|
||||
# Ensure the extensions are compatible.
|
||||
if _extensions_incompatible:
|
||||
@@ -389,7 +393,7 @@ def enable(module_name, *, default_set=False, persistent=False, refresh_handled=
|
||||
print("Exception in module unregister():", (mod_file or module_name))
|
||||
handle_error(ex)
|
||||
if is_extension and not refresh_handled:
|
||||
extensions_refresh()
|
||||
extensions_refresh(handle_error=handle_error)
|
||||
return None
|
||||
|
||||
mod.__addon_enabled__ = False
|
||||
@@ -405,7 +409,7 @@ def enable(module_name, *, default_set=False, persistent=False, refresh_handled=
|
||||
del sys.modules[module_name]
|
||||
|
||||
if is_extension and not refresh_handled:
|
||||
extensions_refresh()
|
||||
extensions_refresh(handle_error=handle_error)
|
||||
return None
|
||||
mod.__addon_enabled__ = False
|
||||
|
||||
@@ -477,7 +481,7 @@ def enable(module_name, *, default_set=False, persistent=False, refresh_handled=
|
||||
if default_set:
|
||||
_addon_remove(module_name)
|
||||
if is_extension and not refresh_handled:
|
||||
extensions_refresh()
|
||||
extensions_refresh(handle_error=handle_error)
|
||||
return None
|
||||
|
||||
if is_extension:
|
||||
@@ -516,7 +520,7 @@ def enable(module_name, *, default_set=False, persistent=False, refresh_handled=
|
||||
if default_set:
|
||||
_addon_remove(module_name)
|
||||
if is_extension and not refresh_handled:
|
||||
extensions_refresh()
|
||||
extensions_refresh(handle_error=handle_error)
|
||||
return None
|
||||
finally:
|
||||
_bl_owner_id_set(owner_id_prev)
|
||||
@@ -578,7 +582,7 @@ def disable(module_name, *, default_set=False, refresh_handled=False, handle_err
|
||||
_addon_remove(module_name)
|
||||
|
||||
if not refresh_handled:
|
||||
extensions_refresh()
|
||||
extensions_refresh(handle_error=handle_error)
|
||||
|
||||
if _bpy.app.debug_python:
|
||||
print("\taddon_utils.disable", module_name)
|
||||
@@ -1144,12 +1148,13 @@ def _initialize_extensions_compat_ensure_up_to_date(extensions_directory, extens
|
||||
return updated, wheel_list
|
||||
|
||||
|
||||
def _initialize_extensions_compat_ensure_up_to_date_wheels(extensions_directory, wheel_list, debug):
|
||||
def _initialize_extensions_compat_ensure_up_to_date_wheels(extensions_directory, wheel_list, debug, error_fn):
|
||||
import os
|
||||
_extension_sync_wheels(
|
||||
local_dir=os.path.join(extensions_directory, ".local"),
|
||||
wheel_list=wheel_list,
|
||||
debug=debug,
|
||||
error_fn=error_fn,
|
||||
)
|
||||
|
||||
|
||||
@@ -1159,6 +1164,7 @@ def _initialize_extensions_compat_data(
|
||||
ensure_wheels, # `bool`
|
||||
addon_modules_pending, # `Optional[Sequence[str]]`
|
||||
use_startup_fastpath, # `bool`
|
||||
error_fn, # `Callable[[Exception], None] | None`
|
||||
):
|
||||
# WARNING: this function must *never* raise an exception because it would interfere with low level initialization.
|
||||
# As the function deals with file IO, use what are typically over zealous exception checks so as to rule out
|
||||
@@ -1204,7 +1210,7 @@ def _initialize_extensions_compat_data(
|
||||
extensions_enabled,
|
||||
print_debug,
|
||||
)
|
||||
except Exception as ex:
|
||||
except Exception:
|
||||
print("Extension: unexpected error detecting cache, this is is a bug!")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
@@ -1212,9 +1218,18 @@ def _initialize_extensions_compat_data(
|
||||
|
||||
if ensure_wheels:
|
||||
if updated:
|
||||
if error_fn is None:
|
||||
def error_fn(ex):
|
||||
print("Error:", str(ex))
|
||||
|
||||
try:
|
||||
_initialize_extensions_compat_ensure_up_to_date_wheels(extensions_directory, wheel_list, debug)
|
||||
except Exception as ex:
|
||||
_initialize_extensions_compat_ensure_up_to_date_wheels(
|
||||
extensions_directory,
|
||||
wheel_list,
|
||||
debug,
|
||||
error_fn=error_fn,
|
||||
)
|
||||
except Exception:
|
||||
print("Extension: unexpected error updating wheels, this is is a bug!")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
@@ -1336,6 +1351,7 @@ def _extension_sync_wheels(
|
||||
local_dir, # `str`
|
||||
wheel_list, # `List[WheelSource]`
|
||||
debug, # `bool`
|
||||
error_fn, # `Callable[[Exception], None]`
|
||||
): # `-> None`
|
||||
import os
|
||||
import sys
|
||||
@@ -1357,6 +1373,7 @@ def _extension_sync_wheels(
|
||||
local_dir=local_dir,
|
||||
local_dir_site_packages=local_dir_site_packages,
|
||||
wheel_list=wheel_list,
|
||||
error_fn=error_fn,
|
||||
remove_error_fn=remove_error_fn,
|
||||
debug=debug,
|
||||
)
|
||||
@@ -1804,6 +1821,8 @@ def _initialize_extensions_repos_once():
|
||||
ensure_wheels=True,
|
||||
addon_modules_pending=None,
|
||||
use_startup_fastpath=True,
|
||||
# Runs on startup, fall back to printing.
|
||||
error_fn=None,
|
||||
)
|
||||
|
||||
# Setup repositories for the first time.
|
||||
@@ -1819,7 +1838,23 @@ def _initialize_extensions_repos_once():
|
||||
# -----------------------------------------------------------------------------
|
||||
# Extension Public API
|
||||
|
||||
def extensions_refresh(ensure_wheels=True, addon_modules_pending=None):
|
||||
def extensions_refresh(
|
||||
ensure_wheels=True,
|
||||
addon_modules_pending=None,
|
||||
handle_error=None,
|
||||
):
|
||||
"""
|
||||
Ensure data relating to extensions is up to date.
|
||||
This should be called after extensions on the file-system have changed.
|
||||
|
||||
:arg ensure_wheels: When true, refresh installed wheels with wheels used by extensions.
|
||||
:type ensure_wheels: bool
|
||||
:arg addon_modules_pending: Refresh these add-ons by listing their package names, as if they are enabled.
|
||||
This is needed so wheels can be setup before the add-on is enabled.
|
||||
:type addon_modules_pending: Sequence[str] | None
|
||||
:arg handle_error: Called in the case of an error, taking an exception argument.
|
||||
:type handle_error: Callable[[Exception], None] | None
|
||||
"""
|
||||
|
||||
# Ensure any changes to extensions refresh `_extensions_incompatible`.
|
||||
_initialize_extensions_compat_data(
|
||||
@@ -1827,6 +1862,7 @@ def extensions_refresh(ensure_wheels=True, addon_modules_pending=None):
|
||||
ensure_wheels=ensure_wheels,
|
||||
addon_modules_pending=addon_modules_pending,
|
||||
use_startup_fastpath=False,
|
||||
error_fn=handle_error,
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user