Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PyROOT] Drop support for from ROOT import * #14588

Merged
merged 2 commits into from
Feb 5, 2024
Merged
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
1 change: 1 addition & 0 deletions README/ReleaseNotes/v632/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ The following people have contributed to this new version:
- Some redundant **RooDataSet** constructors are deprecated and will be removed in ROOT 6.34.
Please use the RooDataSet constructors that take RooFit command arguments instead
- ROOT does not longer support Python 2. The minimum required Python version to build ROOT is 3.8.
- Support for wildcard imports like `from ROOT import *` is dropped from PyROOT

## Core Libraries

Expand Down
27 changes: 26 additions & 1 deletion bindings/pyroot/pythonizations/python/ROOT/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,26 @@

_is_ipython = hasattr(builtins, "__IPYTHON__")


class _PoisonedDunderAll:
"""
Dummy class used to trigger an ImportError on wildcard imports if the
`__all__` attribute of a module is an instance of this class.
"""

def __getitem__(self, _):
import textwrap

message = """
Wildcard import e.g. `from module import *` is bad practice, so it is disallowed in ROOT. Please import explicitly.
"""
raise ImportError(textwrap.dedent(message))


# Prevent `from ROOT import *` by setting the __all__ attribute to something
# that will raise an ImportError on item retrieval.
__all__ = _PoisonedDunderAll()

# Configure ROOT facade module
import sys
from ._facade import ROOTFacade
Expand Down Expand Up @@ -127,7 +147,12 @@ def is_package(self, fullname: str) -> bool:
return _lookup_root_module(fullname) is not None

def create_module(self, spec: ModuleSpec):
return _lookup_root_module(spec.name)
out = _lookup_root_module(spec.name)
# Prevent wildcard import for the submodule by setting the __all__
# attribute to something that will raise an ImportError on item
# retrieval.
out.__all__ = _PoisonedDunderAll()
return out

def exec_module(self, module):
pass
Expand Down
26 changes: 2 additions & 24 deletions bindings/pyroot/pythonizations/python/ROOT/_facade.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,8 @@ def __init__(self, module, is_ipython):
types.ModuleType.__init__(self, module.__name__)

self.module = module
# Importing all will be customised later
self.module.__all__ = []

self.__all__ = module.__all__
self.__name__ = module.__name__
self.__file__ = module.__file__
self.__cached__ = module.__cached__
Expand Down Expand Up @@ -145,22 +144,6 @@ def AddressOf(self, obj):
# Create a buffer (LowLevelView) from address
return cppyy.ll.cast[out_type](addr)

def _handle_import_all(self):
# Called if "from ROOT import *" is executed in the app.
# Customises lookup in Python's main module to also
# check in C++'s global namespace

# Get caller module (jump over the facade frames)
num_frame = 2
frame = sys._getframe(num_frame).f_globals["__name__"]
while frame == "ROOT._facade":
num_frame += 1
frame = sys._getframe(num_frame).f_globals["__name__"]
caller = sys.modules[frame]

# Install the hook
cppyy_backend._set_cpp_lazy_lookup(caller.__dict__)

def _fallback_getattr(self, name):
# Try:
# - in the global namespace
Expand All @@ -170,13 +153,8 @@ def _fallback_getattr(self, name):
# The first two attempts allow to lookup
# e.g. ROOT.ROOT.Math as ROOT.Math

if name == "__all__":
self._handle_import_all()
# Make the attributes of the facade be injected in the
# caller module
raise AttributeError()
# Note that hasattr caches the lookup for getattr
elif hasattr(gbl_namespace, name):
if hasattr(gbl_namespace, name):
return getattr(gbl_namespace, name)
elif hasattr(gbl_namespace.ROOT, name):
return getattr(gbl_namespace.ROOT, name)
Expand Down
Loading