Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions cuda_pathfinder/cuda/pathfinder/_optional_cuda_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@
from cuda.pathfinder._dynamic_libs.load_dl_common import DynamicLibNotFoundError


def _target_is_unavailable(missing_modname: str | None, fully_qualified_modname: str) -> bool:
"""Report whether a ModuleNotFoundError means "the target is not installed".

``ModuleNotFoundError.name`` is the module that could not be found, which
for ``import a.b.c`` is the *outermost* missing name: if package ``a.b`` is
not installed at all, ``name`` is ``"a.b"``, not ``"a.b.c"``. A missing
ancestor makes the target just as unavailable as a missing leaf, so both
count. Anything else is a broken dependency of the target and must not be
swallowed.
"""
if missing_modname is None:
return False
return fully_qualified_modname == missing_modname or fully_qualified_modname.startswith(missing_modname + ".")


def _optional_cuda_import(
fully_qualified_modname: str,
*,
Expand All @@ -19,7 +34,8 @@ def _optional_cuda_import(

Returns:
The imported module if available and the optional probe succeeds,
otherwise ``None`` when the requested module is unavailable.
otherwise ``None`` when the requested module — or a package containing
it — is not installed.

Raises:
ModuleNotFoundError: If the import fails because a dependency of the
Expand All @@ -30,7 +46,7 @@ def _optional_cuda_import(
try:
module = importlib.import_module(fully_qualified_modname)
except ModuleNotFoundError as err:
if err.name != fully_qualified_modname:
if not _target_is_unavailable(err.name, fully_qualified_modname):
raise
return None

Expand Down
36 changes: 36 additions & 0 deletions cuda_pathfinder/tests/test_optional_cuda_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,42 @@ def fake_import_module(name):
assert result is None


@pytest.mark.agent_authored(model="claude-opus-5")
def test__optional_cuda_import_returns_none_when_parent_package_missing(monkeypatch):
def fake_import_module(_name):
# What CPython reports for `import cuda.bindings.nvjitlink` when the
# cuda.bindings package itself is not installed: the outermost missing
# name, not the fully qualified one that was requested.
err = ModuleNotFoundError("No module named 'cuda.bindings'")
err.name = "cuda.bindings"
raise err

monkeypatch.setattr(optional_import_mod.importlib, "import_module", fake_import_module)

assert _optional_cuda_import("cuda.bindings.nvjitlink") is None


@pytest.mark.agent_authored(model="claude-opus-5")
def test__optional_cuda_import_returns_none_for_a_really_uninstalled_package():
"""Same as above, but through the real import machinery instead of a stub."""
assert _optional_cuda_import("cuda_pathfinder_no_such_package.sub.mod") is None


@pytest.mark.agent_authored(model="claude-opus-5")
def test__optional_cuda_import_reraises_for_a_string_prefix_that_is_not_an_ancestor(monkeypatch):
"""``cuda.bindings`` is a string prefix of ``cuda.bindings_extra``, not a parent of it."""

def fake_import_module(_name):
err = ModuleNotFoundError("No module named 'cuda.bindings'")
err.name = "cuda.bindings"
raise err

monkeypatch.setattr(optional_import_mod.importlib, "import_module", fake_import_module)

with pytest.raises(ModuleNotFoundError, match="cuda.bindings"):
_optional_cuda_import("cuda.bindings_extra.mod")


def test__optional_cuda_import_reraises_nested_module_not_found(monkeypatch):
def fake_import_module(_name):
err = ModuleNotFoundError("No module named 'not_a_real_dependency'")
Expand Down
Loading