server: add support for python dependency recovery
It is possible that older versions of Moonraker's update_manager will fail in its attempt to update python packages. This can lead to missing modules when the new version of Moonraker is loaded. When a `ModuleNotFound` error is received during a call to "load_component" Moonraker will attempt to install its missing dependencies. Signed-off-by: Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
parent
01ad427d75
commit
94b1896e28
|
@ -23,7 +23,14 @@ from . import confighelper
|
|||
from .eventloop import EventLoop
|
||||
from .app import MoonrakerApp
|
||||
from .klippy_connection import KlippyConnection
|
||||
from .utils import ServerError, Sentinel, get_software_info, json_wrapper
|
||||
from .utils import (
|
||||
ServerError,
|
||||
Sentinel,
|
||||
get_software_info,
|
||||
json_wrapper,
|
||||
pip_utils,
|
||||
source_info
|
||||
)
|
||||
from .loghelper import LogManager
|
||||
from .common import RequestType
|
||||
from .websockets import WebsocketManager
|
||||
|
@ -81,6 +88,7 @@ class Server:
|
|||
self.ssl_port: int = config.getint('ssl_port', 7130)
|
||||
self.exit_reason: str = ""
|
||||
self.server_running: bool = False
|
||||
self.pip_recovery_attempted: bool = False
|
||||
|
||||
# Configure Debug Logging
|
||||
config.getboolean('enable_debug_logging', False, deprecate=True)
|
||||
|
@ -276,7 +284,11 @@ class Server:
|
|||
config = config.getsection(component_name, fallback)
|
||||
load_func = getattr(module, "load_component")
|
||||
component = load_func(config)
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
ucomps: List[str] = self.app_args.get("unofficial_components", [])
|
||||
if isinstance(e, ModuleNotFoundError) and component_name not in ucomps:
|
||||
if self.try_pip_recovery(e.name or "unknown"):
|
||||
return self.load_component(config, component_name, default)
|
||||
msg = f"Unable to load component: ({component_name})"
|
||||
logging.exception(msg)
|
||||
if component_name not in self.failed_components:
|
||||
|
@ -288,6 +300,36 @@ class Server:
|
|||
logging.info(f"Component ({component_name}) loaded")
|
||||
return component
|
||||
|
||||
def try_pip_recovery(self, missing_module: str) -> bool:
|
||||
if self.pip_recovery_attempted:
|
||||
return False
|
||||
self.pip_recovery_attempted = True
|
||||
src_dir = source_info.source_path()
|
||||
req_file = src_dir.joinpath("scripts/moonraker-requirements.txt")
|
||||
if not req_file.is_file():
|
||||
return False
|
||||
pip_cmd = f"{sys.executable} -m pip"
|
||||
pip_exec = pip_utils.PipExecutor(pip_cmd, logging.info)
|
||||
logging.info(f"Module '{missing_module}' not found. Attempting Pip Update...")
|
||||
logging.info("Checking Pip Version...")
|
||||
try:
|
||||
pipver = pip_exec.get_pip_version()
|
||||
if pip_utils.check_pip_needs_update(pipver):
|
||||
cur_ver = pipver.pip_version_string
|
||||
new_ver = ".".join([str(part) for part in pip_utils.MIN_PIP_VERSION])
|
||||
logging.info(f"Updating Pip from {cur_ver} to {new_ver}...")
|
||||
pip_exec.update_pip()
|
||||
except Exception:
|
||||
logging.exception("Pip version check failed")
|
||||
return False
|
||||
logging.info("Installing Moonraker python dependencies...")
|
||||
try:
|
||||
pip_exec.install_packages(req_file, {"SKIP_CYTHON": "Y"})
|
||||
except Exception:
|
||||
logging.exception("Failed to install python packages")
|
||||
return False
|
||||
return True
|
||||
|
||||
def lookup_component(
|
||||
self, component_name: str, default: _T = Sentinel.MISSING
|
||||
) -> Union[_T, Any]:
|
||||
|
|
Loading…
Reference in New Issue