Skip to content

Commit

Permalink
fix(python): lazy proxy module does not require registration in `sys.…
Browse files Browse the repository at this point in the history
…globals` (#5390)
  • Loading branch information
alexander-beedie committed Nov 1, 2022
1 parent 9dabbe5 commit 0349059
Show file tree
Hide file tree
Showing 2 changed files with 14 additions and 13 deletions.
21 changes: 11 additions & 10 deletions py-polars/polars/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,40 +22,41 @@
_HYPOTHESIS_AVAILABLE = True


def _proxy_module(module_name: str, register: bool = True) -> ModuleType:
def _proxy_module(module_name: str) -> ModuleType:
"""
Create a module that raises a helpful/explanatory exception on attribute access.
Notes
-----
We do NOT register this module with `sys.modules` so as not to cause confusion
in the global environment. This way we have a valid lazy/proxy module for our
own use, but it is scoped _exclusively_ for use within polars.
Parameters
----------
module_name : str
the name of the new/proxy module.
register : bool
indicate if the module should be registered with ``sys.modules``.
"""
# module-level getattr for the proxy
def __getattr__(*args: Any, **kwargs: Any) -> None:
attr = args[0]
# handle some introspection issues on private module attrs
# allow some very minimal introspection on private module
# attrs to avoid unnecessary error-handling elsewhere
if re.match(r"^__\w+__$", attr):
return None

# all other attribute access raises exception
# all other attribute access raises an exception
pfx = _mod_pfx.get(module_name, "")
raise ModuleNotFoundError(
f"{pfx}{attr} requires '{module_name}' module to be installed"
) from None

# create module that raises an exception on attribute access.
# create the module (do NOT register with sys.globals)
proxy_module = module_from_spec(ModuleSpec(module_name, None))
for name, obj in (("__getattr__", __getattr__),):
setattr(proxy_module, name, obj)

# add proxy into sys.modules under the target module's name
if register:
sys.modules[module_name] = proxy_module
return proxy_module


Expand Down
6 changes: 3 additions & 3 deletions py-polars/tests/unit/test_interop.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,15 +317,15 @@ def test_from_optional_not_available() -> None:
# proxy module is created dynamically if the required module is not available
# (see the polars.dependencies source code for additional detail/comments)

np = _proxy_module("numpy", register=False)
np = _proxy_module("numpy")
with pytest.raises(ImportError, match=r"np\.array requires 'numpy'"):
pl.from_numpy(np.array([[1, 2], [3, 4]]), columns=["a", "b"])

pa = _proxy_module("pyarrow", register=False)
pa = _proxy_module("pyarrow")
with pytest.raises(ImportError, match=r"pa\.table requires 'pyarrow'"):
pl.from_arrow(pa.table({"a": [1, 2, 3], "b": [4, 5, 6]}))

pd = _proxy_module("pandas", register=False)
pd = _proxy_module("pandas")
with pytest.raises(ImportError, match=r"pd\.Series requires 'pandas'"):
pl.from_pandas(pd.Series([1, 2, 3]))

Expand Down

0 comments on commit 0349059

Please sign in to comment.