Repository: rootpy/rootpy

Issue:
Error message "AttributeError: 'TDirectory' object has no attribute 'func'" from example program model_simple.py
Hi,

I have installed rootpy 0.7.1. via "git clone". I have written some simple rootpy programs, and they seem to work fine. I ran into some problems when I wanted to use my own classes while reading from a tree, so I tried the example program on the rootpy webpage named "model_simple.py", and it also gives me an error:

[akin@localhost rootpy]$ python model_simple.py 
# 
# A simple tree model

This example demonstrates how to define a simple tree model.

Traceback (most recent call last):
  File "model_simple.py", line 26, in <module>
    tree = Tree("test", model=Event)
  File "<string>", line 2, in **init**
  File "/usr/lib/python2.7/site-packages/rootpy-dev-py2.7.egg/rootpy/decorators.py", line 34, in method_file_check
    curr_dir = ROOT.gDirectory.func()
AttributeError: 'TDirectory' object has no attribute 'func'
[akin@localhost rootpy]$ 

Is this a bug or am I doing something wrong? I am running Fedora 15 (Lovelock) 2.6.40.6-0.fc15.x86_64 GNU/Linux and Python 2.7.1.

Best,

Akin



Discussion:
Odd. What version of ROOT? Does this work?

```
python -c "import ROOT; print ROOT.gDirectory.func()"
```

I am using Root Version  5.28/00h. The output of the command above is:

[akin@localhost rootpy]$ python -c "import ROOT; print ROOT.gDirectory.func()"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
AttributeError: 'TDirectory' object has no attribute 'func'
[akin@localhost rootpy]$ 

This is almost certainly something that changed since 5.28. @ndawe, thoughts on how to proceed? Do we really want to support as old as 5.28?

I can upgrade to a newer version and let you know whether it works, no problem. I am still recovering from the schock that 5.28 is considered so old. To my defense, I am a theoretician ...

Hehe. That's probably unfair of me to call it old. It's probably not such a big fix we need to make on our side to make it work. But if we forever try to work around deficiencies in "the old ROOT" then rootpy would get very complicated and we would have no time left over.

I'm pretty certain that this will work with newer versions of ROOT. I just tried with an old version and reproduced your problem.

We should be able to quickly fix this in rootpy. In post-5.28 ROOT we have:

``` python
>>> import ROOT
>>> ROOT.gDirectory
<ROOT._ExpandMacroFunction object at 0x7f8976ecfb90>
>>> ROOT.gDirectory.func()
<ROOT.TROOT object ("PyROOT") at 0x7f897a465640>
>>>
```

but in 5.28 we have:

``` python
>>> import ROOT
>>> ROOT.gDirectory
<ROOT.TDirectory* object ("PyROOT") at 0x2b6d8452d5f0>
>>> 
>>> ROOT.gDirectory.func()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'TDirectory' object has no attribute 'func'
>>> 
```

So the logic seems to be: we use `func()` to access the underlying directory through the `_ExpandMacroFunction` after 5.28 but for 5.28 we just use `ROOT.gDirectory`

I will post a PR that fixes this shortly. Maybe we should consider including a 5.28 build in Travis-CI since we do advertise that rootpy should work there.

To be specific ROOT introduced this `_ExpandMacroFunction` for `gDirectory` in 5.32.00. 


Resolving patch:
diff --git a/rootpy/__init__.py b/rootpy/__init__.py
--- a/rootpy/__init__.py
+++ b/rootpy/__init__.py
@@ -194,3 +194,11 @@ def create(cls_name, *args, **kwargs):
         return None
     obj = cls(*args, **kwargs)
     return asrootpy(obj)
+
+
+def gDirectory():
+    # handle versions of ROOT older than 5.32.00
+    if ROOT_VERSION < 53200:
+        return ROOT.gDirectory
+    else:
+        return ROOT.gDirectory.func()
diff --git a/rootpy/context.py b/rootpy/context.py
--- a/rootpy/context.py
+++ b/rootpy/context.py
@@ -11,6 +11,8 @@
 LOCK = threading.RLock()
 
 import ROOT
+from . import gDirectory
+
 
 @contextmanager
 def preserve_current_style():
@@ -38,7 +40,7 @@ def preserve_current_canvas():
     finally:
         if old:
             old.cd()
-            
+
         else:
             # Put things back how they were before.
             if ROOT.gPad.func():
@@ -53,7 +55,7 @@ def preserve_current_directory():
     Context manager which ensures that the current directory remains the current
     directory when the context is left.
     """
-    old = ROOT.gDirectory.func()
+    old = gDirectory()
     try:
         yield
     finally:
@@ -88,11 +90,11 @@ def invisible_canvas():
             return g.GetXaxis()
     """
     with preserve_current_canvas():
-    
+
         with preserve_batch_state():
             ROOT.gROOT.SetBatch()
             c = ROOT.TCanvas()
-            
+
         try:
             c.cd()
             yield c
@@ -105,12 +107,12 @@ def thread_specific_tmprootdir():
     """
     Context manager which makes a thread specific gDirectory to avoid interfering
     with the current file.
-    
+
     Use cases:
-        
+
         A TTree Draw function which doesn't want to interfere with whatever
         gDirectory happens to be.
-        
+
         Multi-threading where there are two threads creating objects with the
         same name which must reside in a directory. (again, this happens with
         TTree draw)
@@ -123,7 +125,7 @@ def thread_specific_tmprootdir():
             assert d, "Unexpected failure, can't cd to tmpdir."
         d.cd()
         yield d
-        
+
 @contextmanager
 def set_directory(robject):
     """
@@ -131,11 +133,11 @@ def set_directory(robject):
     """
     old_dir = robject.GetDirectory()
     try:
-        robject.SetDirectory(ROOT.gDirectory.func())
+        robject.SetDirectory(gDirectory())
         yield
     finally:
         robject.SetDirectory(old_dir)
-            
+
 @contextmanager
 def preserve_set_th1_add_directory(state=True):
     """
@@ -148,7 +150,7 @@ def preserve_set_th1_add_directory(state=True):
             yield
         finally:
             ROOT.TH1.AddDirectory(status)
-            
+
 @contextmanager
 def do_nothing():
     yield
diff --git a/rootpy/decorators.py b/rootpy/decorators.py
--- a/rootpy/decorators.py
+++ b/rootpy/decorators.py
@@ -4,6 +4,7 @@
 import inspect
 from .context import preserve_current_directory
 from .extern import decorator
+from . import gDirectory
 
 
 CONVERT_SNAKE_CASE = os.getenv('NO_ROOTPY_SNAKE_CASE', False) == False
@@ -31,7 +32,7 @@ def method_file_check(f, self, *args, **kwargs):
     # This requires special treatment since in Python 3 unbound methods are
     # just functions: http://stackoverflow.com/a/3589335/1002176 but to get
     # consistent access to the class in both 2.x and 3.x, we need self.
-    curr_dir = ROOT.gDirectory.func()
+    curr_dir = gDirectory()
     if isinstance(curr_dir, ROOT.TROOT):
         raise RuntimeError(
             "You must first create a File before calling %s.%s" % (


Test patch:
diff --git a/rootpy/tests/test_decorators.py b/rootpy/tests/test_decorators.py
--- a/rootpy/tests/test_decorators.py
+++ b/rootpy/tests/test_decorators.py
@@ -4,6 +4,7 @@
 from rootpy.decorators import (method_file_check, method_file_cd,
                                snake_case_methods)
 from rootpy.io import TemporaryFile
+import rootpy
 from nose.tools import assert_true, raises
 
 
@@ -33,12 +34,12 @@ class Foo(ROOT.TH1D):
 
     @method_file_check
     def something(self, foo):
-        self.file = ROOT.gDirectory.func()
+        self.file = rootpy.gDirectory()
         return foo
 
     @method_file_cd
     def write(self):
-        assert_true(self.GetDirectory() == ROOT.gDirectory.func())
+        assert_true(self.GetDirectory() == rootpy.gDirectory())
 
 
 def test_method_file_check_good():