Skip to content

Commit

Permalink
Fixed #383 and moved behavior_of to its own module. (#384)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
jpivarski and pre-commit-ci[bot] authored Jun 30, 2021
1 parent 04f3b10 commit 54541a6
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 94 deletions.
1 change: 1 addition & 0 deletions docs-sphinx/prepare_docstrings.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"uproot",
"uproot.reading",
"uproot.behaviors",
"uproot.behavior",
"uproot.model",
"uproot.streamers",
"uproot.cache",
Expand Down
95 changes: 1 addition & 94 deletions src/uproot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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), "<dynamic>", "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
105 changes: 105 additions & 0 deletions src/uproot/behavior.py
Original file line number Diff line number Diff line change
@@ -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), "<dynamic>", "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_",
]
30 changes: 30 additions & 0 deletions tests/test_0384-move-behavior_of-and-fix-383.py
Original file line number Diff line number Diff line change
@@ -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]
)

0 comments on commit 54541a6

Please sign in to comment.