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:
imSp4rky
2026-09-05 22:13:25 -06:00
parent 2f28aa8e64
commit e845e9ba28
2 changed files with 50 additions and 1 deletions
@@ -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) == ""
+20 -1
View File
@@ -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: