From 54541a64fed9fc0b405fa2d53c6aef2685a5d134 Mon Sep 17 00:00:00 2001 From: Jim Pivarski Date: Wed, 30 Jun 2021 11:33:26 -0500 Subject: [PATCH] Fixed #383 and moved behavior_of to its own module. (#384) * Fixed #383 and moved behavior_of to its own module. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * And documentation for the new module. * Fix precommits. * Added a test. (It would trigger the bug.) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- docs-sphinx/prepare_docstrings.py | 1 + src/uproot/__init__.py | 95 +--------------- src/uproot/behavior.py | 105 ++++++++++++++++++ .../test_0384-move-behavior_of-and-fix-383.py | 30 +++++ 4 files changed, 137 insertions(+), 94 deletions(-) create mode 100644 src/uproot/behavior.py create mode 100644 tests/test_0384-move-behavior_of-and-fix-383.py diff --git a/docs-sphinx/prepare_docstrings.py b/docs-sphinx/prepare_docstrings.py index 924099506..b039ae350 100644 --- a/docs-sphinx/prepare_docstrings.py +++ b/docs-sphinx/prepare_docstrings.py @@ -13,6 +13,7 @@ "uproot", "uproot.reading", "uproot.behaviors", + "uproot.behavior", "uproot.model", "uproot.streamers", "uproot.cache", diff --git a/src/uproot/__init__.py b/src/uproot/__init__.py index b9c273e95..51c3c1914 100644 --- a/src/uproot/__init__.py +++ b/src/uproot/__init__.py @@ -175,99 +175,6 @@ from uproot.behaviors.TBranch import concatenate from uproot.behaviors.TBranch import lazy -import pkgutil -import uproot.behaviors - - -def behavior_of(classname): - """ - Finds and loads the behavior class for C++ (decoded) classname or returns - None if there isn't one. - - Behaviors do not have a required base class, and they may be used with - Awkward Array's ``ak.behavior``. - - The search strategy for finding behavior classes is: - - 1. Translate the ROOT class name from C++ to Python with - :doc:`uproot.model.classname_encode`. For example, - ``"ROOT::RThing"`` becomes ``"Model_ROOT_3a3a_RThing"``. - 2. Look for a submodule of ``uproot.behaviors`` without - the ``"Model_"`` prefix. For example, ``"ROOT_3a3a_RThing"``. - 3. Look for a class in that submodule with the fully encoded - name. For example, ``"Model_ROOT_3a3a_RThing"``. - - See :doc:`uproot.behaviors` for details. - """ - name = classname_encode(classname) - assert name.startswith("Model_") - name = name[6:] - - specialization = None - for param in behavior_of._specializations: - if name.endswith(param): - specialization = param - name = name[: -len(param)] - break - - if name not in globals(): - if name in behavior_of._module_names: - exec( - compile( - "import uproot.behaviors.{0}".format(name), "", "exec" - ), - globals(), - ) - module = eval("uproot.behaviors.{0}".format(name)) - behavior_cls = getattr(module, name, None) - if behavior_cls is not None: - globals()[name] = behavior_cls - - if specialization is None: - return globals().get(name) - else: - return globals().get(name)(specialization) - - -behavior_of._module_names = [ - module_name - for loader, module_name, is_pkg in pkgutil.walk_packages(uproot.behaviors.__path__) -] - -behavior_of._specializations = [ - "_3c_bool_3e_", - "_3c_char_3e_", - "_3c_unsigned_20_char_3e_", - "_3c_short_3e_", - "_3c_unsigned_20_short_3e_", - "_3c_int_3e_", - "_3c_unsigned_20_int_3e_", - "_3c_long_3e_", - "_3c_unsigned_20_long_3e_", - "_3c_long_20_long_3e_", - "_3c_unsigned_20_long_20_long_3e_", - "_3c_size_5f_t_3e_", - "_3c_ssize_5f_t_3e_", - "_3c_float_3e_", - "_3c_double_3e_", - "_3c_long_20_double_3e_", - "_3c_Bool_5f_t_3e_", - "_3c_Char_5f_t_3e_", - "_3c_UChar_5f_t_3e_", - "_3c_Short_5f_t_3e_", - "_3c_UShort_5f_t_3e_", - "_3c_Int_5f_t_3e_", - "_3c_UInt_5f_t_3e_", - "_3c_Long_5f_t_3e_", - "_3c_ULong_5f_t_3e_", - "_3c_Long64_5f_t_3e_", - "_3c_ULong64_5f_t_3e_", - "_3c_Size_5f_t_3e_", - "_3c_Float_5f_t_3e_", - "_3c_Double_5f_t_3e_", - "_3c_LongDouble_5f_t_3e_", -] - -del pkgutil +from uproot.behavior import behavior_of from uproot._util import no_filter diff --git a/src/uproot/behavior.py b/src/uproot/behavior.py new file mode 100644 index 000000000..9a1fbd5ac --- /dev/null +++ b/src/uproot/behavior.py @@ -0,0 +1,105 @@ +# BSD 3-Clause License; see https://github.com/scikit-hep/uproot4/blob/main/LICENSE + +""" +This module defines utilities for adding behaviors to C++ objects in Python. +Behaviors are defined by specially named classes in specially named modules +that get auto-detected by :doc:`uproot.behavior.behavior_of`. +""" + +from __future__ import absolute_import + +import pkgutil + +import uproot.behaviors + + +def behavior_of(classname): + """ + Finds and loads the behavior class for C++ (decoded) classname or returns + None if there isn't one. + + Behaviors do not have a required base class, and they may be used with + Awkward Array's ``ak.behavior``. + + The search strategy for finding behavior classes is: + + 1. Translate the ROOT class name from C++ to Python with + :doc:`uproot.model.classname_encode`. For example, + ``"ROOT::RThing"`` becomes ``"Model_ROOT_3a3a_RThing"``. + 2. Look for a submodule of ``uproot.behaviors`` without + the ``"Model_"`` prefix. For example, ``"ROOT_3a3a_RThing"``. + 3. Look for a class in that submodule with the fully encoded + name. For example, ``"Model_ROOT_3a3a_RThing"``. + + See :doc:`uproot.behaviors` for details. + """ + name = uproot.model.classname_encode(classname) + assert name.startswith("Model_") + name = name[6:] + + specialization = None + for param in behavior_of._specializations: + if name.endswith(param): + specialization = param + name = name[: -len(param)] + break + + if name not in globals(): + if name in behavior_of._module_names: + exec( + compile( + "import uproot.behaviors.{0}".format(name), "", "exec" + ), + globals(), + ) + module = eval("uproot.behaviors.{0}".format(name)) + behavior_cls = getattr(module, name, None) + if behavior_cls is not None: + globals()[name] = behavior_cls + + cls = globals().get(name) + + if cls is None or specialization is None: + return cls + else: + return cls(specialization) + + +behavior_of._module_names = [ + module_name + for loader, module_name, is_pkg in pkgutil.walk_packages(uproot.behaviors.__path__) +] + +behavior_of._specializations = [ + "_3c_bool_3e_", + "_3c_char_3e_", + "_3c_unsigned_20_char_3e_", + "_3c_short_3e_", + "_3c_unsigned_20_short_3e_", + "_3c_int_3e_", + "_3c_unsigned_20_int_3e_", + "_3c_long_3e_", + "_3c_unsigned_20_long_3e_", + "_3c_long_20_long_3e_", + "_3c_unsigned_20_long_20_long_3e_", + "_3c_size_5f_t_3e_", + "_3c_ssize_5f_t_3e_", + "_3c_float_3e_", + "_3c_double_3e_", + "_3c_long_20_double_3e_", + "_3c_Bool_5f_t_3e_", + "_3c_Char_5f_t_3e_", + "_3c_UChar_5f_t_3e_", + "_3c_Short_5f_t_3e_", + "_3c_UShort_5f_t_3e_", + "_3c_Int_5f_t_3e_", + "_3c_UInt_5f_t_3e_", + "_3c_Long_5f_t_3e_", + "_3c_ULong_5f_t_3e_", + "_3c_Long64_5f_t_3e_", + "_3c_ULong64_5f_t_3e_", + "_3c_Size_5f_t_3e_", + "_3c_Float_5f_t_3e_", + "_3c_Double_5f_t_3e_", + "_3c_LongDouble_5f_t_3e_", +] diff --git a/tests/test_0384-move-behavior_of-and-fix-383.py b/tests/test_0384-move-behavior_of-and-fix-383.py new file mode 100644 index 000000000..861bd538d --- /dev/null +++ b/tests/test_0384-move-behavior_of-and-fix-383.py @@ -0,0 +1,30 @@ +# BSD 3-Clause License; see https://github.com/scikit-hep/uproot4/blob/main/LICENSE + +import os + +import numpy as np +import pytest + +import uproot +import uproot.writing + +ROOT = pytest.importorskip("ROOT") + + +def test_recreate(tmp_path): + filename = os.path.join(tmp_path, "whatever.root") + + f1 = ROOT.TFile(filename, "recreate") + mat = ROOT.TMatrixD(3, 3) + mat[0, 1] = 4 + mat[1, 0] = 8 + mat[2, 2] = 3 + mat.Write("mat") + f1.Close() + + with uproot.open(filename) as f2: + assert f2["mat"].member("fNrows") == 3 + assert f2["mat"].member("fNcols") == 3 + assert np.array_equal( + f2["mat"].member("fElements"), [0, 4, 0, 8, 0, 0, 0, 0, 3] + )