Skip to content
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
48 changes: 22 additions & 26 deletions bindings/pyroot/pythonizations/python/ROOT/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,21 @@
# For the list of contributors see $ROOTSYS/README/CREDITS. #
################################################################################

import importlib
import atexit
import builtins
import os
import sys
import types
from importlib.abc import Loader, MetaPathFinder
from importlib.machinery import ModuleSpec
from typing import Optional, Union

import cppyy
import cppyy.types

from . import _asan # noqa: F401 # imported for side effects for setup specific to AddressSanitizer environments
from ._facade import ROOTFacade
from ._pythonization import _register_pythonizations

# Prevent cppyy's check for extra header directory
os.environ["CPPYY_API_PATH"] = "none"
Expand All @@ -23,32 +35,23 @@
# the path of the ROOT library directory (only needed on Windows). For example,
# if the ROOT Python module is in $ROOTSYS/bin/ROOT/__init__.py, the libraries
# are usually in $ROOTSYS/bin.
if 'win32' in sys.platform:
root_module_path = os.path.dirname(__file__) # expected to be ${CMAKE_INSTALL_PYTHONDIR}/ROOT
root_install_pythondir = os.path.dirname(root_module_path) # expected to be ${CMAKE_INSTALL_PYTHONDIR}
if "win32" in sys.platform:
root_module_path = os.path.dirname(__file__) # expected to be ${CMAKE_INSTALL_PYTHONDIR}/ROOT
root_install_pythondir = os.path.dirname(root_module_path) # expected to be ${CMAKE_INSTALL_PYTHONDIR}
os.add_dll_directory(root_install_pythondir)

# Do setup specific to AddressSanitizer environments
from . import _asan

import cppyy
import cppyy.types

# Build cache of commonly used python strings (the cache is python intern, so
# all strings are shared python-wide, not just in PyROOT).
# See: https://docs.python.org/3.2/library/sys.html?highlight=sys.intern#sys.intern
_cached_strings = []
for s in ["Branch", "FitFCN", "ROOT", "SetBranchAddress", "SetFCN", "_TClass__DynamicCast", "__class__"]:
_cached_strings.append(sys.intern(s))

# Trigger the addition of the pythonizations
from ._pythonization import _register_pythonizations

# Trigger the addition of the pythonizations
_register_pythonizations()

# Check if we are in the IPython shell
import builtins

_is_ipython = hasattr(builtins, "__IPYTHON__")


Expand All @@ -72,9 +75,6 @@ def __getitem__(self, _):
__all__ = _PoisonedDunderAll()

# Configure ROOT facade module
import sys
from ._facade import ROOTFacade

_root_facade = ROOTFacade(sys.modules[__name__], _is_ipython)
sys.modules[__name__] = _root_facade

Expand All @@ -84,9 +84,6 @@ def __getitem__(self, _):
# * https://docs.python.org/3/library/importlib.html#module-importlib.abc
#
# * https://python.plainenglish.io/metapathfinders-or-how-to-change-python-import-behavior-a1cf3b5a13ec
from importlib.abc import Loader, MetaPathFinder
from importlib.machinery import ModuleSpec
from importlib.util import spec_from_loader


def _can_be_module(obj) -> bool:
Expand All @@ -108,10 +105,6 @@ def _can_be_module(obj) -> bool:
return False


from typing import Optional, Union
import types


def _lookup_root_module(fullname: str) -> Optional[Union[types.ModuleType, cppyy.types.Scope]]:
"""
Recursively looks up attributes of the ROOT facade, using a full module
Expand Down Expand Up @@ -160,6 +153,8 @@ class _RootNamespaceFinder(MetaPathFinder):
"""

def find_spec(self, fullname: str, path, target=None) -> ModuleSpec:
from importlib.util import spec_from_loader

if not fullname.startswith("ROOT."):
# This finder only finds ROOT.*
return None
Expand All @@ -178,11 +173,11 @@ def find_spec(self, fullname: str, path, target=None) -> ModuleSpec:

ip = get_ipython()
if hasattr(ip, "kernel"):
import JupyROOT
import JupyROOT # noqa: F401 # imported the side effect of setting up JupyROOT

# from . import JsMVA

# Register cleanup
import atexit


def cleanup():
Expand All @@ -192,4 +187,5 @@ def cleanup():
facade.__dict__["app"].keep_polling = False
facade.__dict__["app"].process_root_events.join()


atexit.register(cleanup)
10 changes: 6 additions & 4 deletions bindings/pyroot/pythonizations/python/ROOT/_facade.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@
import cppyy
import cppyy.ll

from ._application import PyROOTApplication
from ._numbadeclare import _NumbaDeclareDecorator
from ._pythonization import pythonization


class PyROOTConfiguration(object):
"""Class for configuring PyROOT"""
Expand Down Expand Up @@ -57,6 +53,8 @@ class ROOTFacade(types.ModuleType):
"""Facade class for ROOT module"""

def __init__(self, module, is_ipython):
from ._pythonization import pythonization

types.ModuleType.__init__(self, module.__name__)

self.module = module
Expand Down Expand Up @@ -181,6 +179,8 @@ def _register_converters_and_executors(self):
CPyCppyyRegisterExecutorAlias(name, target)

def _finalSetup(self):
from ._application import PyROOTApplication

# Prevent this method from being re-entered through the gROOT wrapper
self.__dict__["gROOT"] = cppyy.gbl.ROOT.GetROOT()

Expand Down Expand Up @@ -443,6 +443,8 @@ def TMVA(self):
# Create and overload Numba namespace
@property
def Numba(self):
from ._numbadeclare import _NumbaDeclareDecorator

cppyy.cppdef("namespace Numba {}")
ns = self._fallback_getattr("Numba")
ns.Declare = staticmethod(_NumbaDeclareDecorator)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@

import cppyy

from ._generic import pythonize_generic

# \cond INTERNALS
gbl_namespace = cppyy.gbl
# \endcond
Expand Down Expand Up @@ -70,6 +68,7 @@ def pythonization(class_name, ns="::", is_prefix=False):

def passes_filter(class_name):
return any(class_name.startswith(prefix) for prefix in target)

else:

def passes_filter(class_name):
Expand Down Expand Up @@ -114,6 +113,7 @@ def cppyy_pythonizor(klass, name):
name (string): name of the class that is the current candidate
to be pythonized.
"""
from ._generic import pythonize_generic

fqn = klass.__cpp_name__

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
# For the list of contributors see $ROOTSYS/README/CREDITS. #
################################################################################

from ROOT.libROOTPythonizations import AddPrettyPrintingPyz

def _add_getitem_checked(klass):
# Parameters:
Expand All @@ -27,25 +26,28 @@ def getitem_checked(o, i):
if i >= 0 and i < len(o):
return o._getitem__unchecked(i)
else:
raise IndexError('index out of range')
raise IndexError("index out of range")

klass._getitem__unchecked = klass.__getitem__
klass.__getitem__ = getitem_checked


# Generic pythonizor for pretty printing that is applied to (almost) all classes
def pythonize_generic(klass, name):
from ROOT.libROOTPythonizations import AddPrettyPrintingPyz

# Parameters:
# klass: class to be pythonized
# name: string containing the name of the class

# Add pretty printing via setting the __str__ special function

# Exclude classes which have the method __str__ already defined in C++
m = getattr(klass, '__str__', None)
has_cpp_str = True if m is not None and type(m).__name__ == 'CPPOverload' else False
m = getattr(klass, "__str__", None)
has_cpp_str = True if m is not None and type(m).__name__ == "CPPOverload" else False

# Exclude std::string with its own pythonization from cppyy
exclude = [ 'std::string' ]
exclude = ["std::string"]

if name not in exclude and not has_cpp_str:
AddPrettyPrintingPyz(klass)
Loading