diff --git a/src/cunumpy/_fake_cupy_impl.py b/src/cunumpy/_fake_cupy_impl.py index c25fb5b..b937320 100644 --- a/src/cunumpy/_fake_cupy_impl.py +++ b/src/cunumpy/_fake_cupy_impl.py @@ -532,11 +532,21 @@ def __getattr__(self, name): random = types.ModuleType("cupy.random") random.default_rng = lambda seed=None: _RNGProxy(_np.random.default_rng(_unwrap(seed))) -random.__getattr__ = lambda attr: _wrap_callable( - getattr(_np.random, attr), - strict=False, - name=f"cupy.random.{attr}", -) + + +def _random_getattr(attr): + # dunder names (``__file__``, ``__path__``, ...) are not NumPy functions: + # answering them with a wrapper breaks module introspection (astropy's import) + if attr.startswith("__"): + raise AttributeError(attr) + return _wrap_callable( + getattr(_np.random, attr), + strict=False, + name=f"cupy.random.{attr}", + ) + + +random.__getattr__ = _random_getattr for _m in (cuda, cuda.device, cuda.runtime, linalg, fft, random): _sys.modules[_m.__name__] = _m diff --git a/tests/unit/test_fake_cupy_random.py b/tests/unit/test_fake_cupy_random.py new file mode 100644 index 0000000..d93308e --- /dev/null +++ b/tests/unit/test_fake_cupy_random.py @@ -0,0 +1,29 @@ +"""The fake cupy.random: NumPy's random functions behind the CuPy rules.""" + +from pathlib import Path + +from cunumpy.kernel_testing import run_in_fake_cupy_subprocess + +SRC = str(Path(__file__).resolve().parents[2] / "src") + + +def test_dunder_names_are_not_wrapped(): + """A module attribute such as ``__file__`` is not a function. + + Wrapping it broke module introspection: importing astropy after cunumpy + failed with "'function' object has no attribute 'endswith'". + """ + code = """ +import inspect +import sys +import cunumpy as xp +xp.set_backend("cupy") +random = sys.modules["cupy.random"] +assert not callable(getattr(random, "__file__", None)) +assert not hasattr(random, "__path__") +assert inspect.getmodule(random) is random +assert callable(random.standard_normal) +print("ok") +""" + out = run_in_fake_cupy_subprocess(code, env={"PYTHONPATH": SRC}).stdout + assert "ok" in out