mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-12 12:05:07 +00:00
fix(services): name the build a compiled service is missing
A service shipped as .so/.pyd failed with a bare ModuleNotFoundError. Report the build this interpreter needs, with its version and platform.
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from importlib.machinery import EXTENSION_SUFFIXES
|
||||
from pathlib import Path
|
||||
|
||||
from unshackle.core.services import compiled_import_hint
|
||||
|
||||
|
||||
def _service(tmp_path: Path, *files: str) -> Path:
|
||||
(tmp_path / "EXAMPLE").mkdir()
|
||||
init = tmp_path / "EXAMPLE" / "__init__.py"
|
||||
init.write_text("from .example import EXAMPLE\n")
|
||||
for name in files:
|
||||
(init.parent / name).touch()
|
||||
return init
|
||||
|
||||
|
||||
def test_hint_names_the_file_this_python_needs(tmp_path: Path) -> None:
|
||||
init = _service(tmp_path, "example.cp999-win_amd64.pyd", "example.cpython-999-x86_64-linux-gnu.so")
|
||||
assert f"needs example{EXTENSION_SUFFIXES[0]}" in compiled_import_hint("EXAMPLE.example", init)
|
||||
|
||||
|
||||
def test_no_hint_when_this_python_can_load_it(tmp_path: Path) -> None:
|
||||
init = _service(tmp_path, f"example{EXTENSION_SUFFIXES[0]}")
|
||||
assert compiled_import_hint("EXAMPLE.example", init) == ""
|
||||
|
||||
|
||||
def test_no_hint_for_a_pure_python_service(tmp_path: Path) -> None:
|
||||
init = _service(tmp_path)
|
||||
assert compiled_import_hint("EXAMPLE.example", init) == ""
|
||||
@@ -2,10 +2,12 @@ from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import logging
|
||||
import platform
|
||||
import re
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from importlib.machinery import EXTENSION_SUFFIXES
|
||||
from pathlib import Path
|
||||
from typing import Any, Iterable
|
||||
|
||||
@@ -67,6 +69,22 @@ def discover_services() -> tuple[list[Path], list[str]]:
|
||||
SERVICES, SHADOWED = discover_services()
|
||||
|
||||
|
||||
def compiled_import_hint(missing: str, path: Path) -> str:
|
||||
"""Say why a compiled service submodule did not import.
|
||||
|
||||
Python loads a compiled service only from a file named for the exact interpreter
|
||||
and platform that runs it, so name the build the service does not ship.
|
||||
"""
|
||||
stem = missing.rsplit(".", 1)[-1]
|
||||
present = [f.name for f in path.parent.glob(f"{stem}.*") if f.suffix in (".so", ".pyd")]
|
||||
if not present or any(f"{stem}{suffix}" in present for suffix in EXTENSION_SUFFIXES):
|
||||
return ""
|
||||
return (
|
||||
f" - this service ships no build for Python {platform.python_version()}"
|
||||
f" on {platform.system()}, which needs {stem}{EXTENSION_SUFFIXES[0]}"
|
||||
)
|
||||
|
||||
|
||||
def load_service(path: Path) -> object:
|
||||
"""Load one Service module, returning its tag-named class.
|
||||
|
||||
@@ -77,7 +95,8 @@ def load_service(path: Path) -> object:
|
||||
try:
|
||||
module = import_module_by_path(path)
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"{tag}: failed to import - {type(e).__name__}: {e} ({path})") from e
|
||||
hint = compiled_import_hint(e.name, path) if isinstance(e, ModuleNotFoundError) and e.name else ""
|
||||
raise RuntimeError(f"{tag}: failed to import - {type(e).__name__}: {e}{hint} ({path})") from e
|
||||
try:
|
||||
return getattr(module, tag)
|
||||
except AttributeError as e:
|
||||
|
||||
Reference in New Issue
Block a user