Repository: ipython/ipython

Issue:
_repr_latex_ in boost.python object does not get used
When one uses boost.python to export a `_repr_latex_` member of a C++ object to Python, IPython fails to use it for its notebook display. The culprit is `_valid_formatter` in https://github.com/ipython/ipython/blob/master/IPython/core/formatters.py . When it gets to `inspect.getcallargs(f)` on line 62, the inspect module throws an exception `TypeError: <Boost.Python.function object at 0x2481410> is not a Python function` because it does not know how to handle the boost member function.

This makes it impossible to write simple LaTeX formatters using boost.python (I know there are still other ways to get boost.python objects formatted using LaTeX but the `_repr_latex_` route is by far the easiest).



Discussion:
What do you get from `type(obj._repr_latex_).mro()`? On other compiled classes, I get `builtin_function_or_method`, which is caught by the previous statement.


Resolving patch:
diff --git a/IPython/core/formatters.py b/IPython/core/formatters.py
--- a/IPython/core/formatters.py
+++ b/IPython/core/formatters.py
@@ -39,38 +39,21 @@
 #-----------------------------------------------------------------------------
 
 
-def _valid_formatter(f):
-    """Return whether an object is a valid formatter
-    
-    Cases checked:
+def _safe_get_formatter_method(obj, name):
+    """Safely get a formatter method
     
-    - bound methods             OK
-    - unbound methods           NO
-    - callable with zero args   OK
+    - Classes cannot have formatter methods, only instance
+    - protect against proxy objects that claim to have everything
     """
-    if f is None:
-        return False
-    elif isinstance(f, type(str.find)):
-        # unbound methods on compiled classes have type method_descriptor
-        return False
-    elif isinstance(f, types.BuiltinFunctionType):
-        # bound methods on compiled classes have type builtin_function
-        return True
-    elif callable(f):
-        # anything that works with zero args should be okay
-        try:
-            inspect.getcallargs(f)
-        except Exception:
-            return False
-        else:
-            return True
-    return False
-
-def _safe_get_formatter_method(obj, name):
-    """Safely get a formatter method"""
+    if inspect.isclass(obj):
+        # repr methods only make sense on instances, not classes
+        return None
     method = pretty._safe_getattr(obj, name, None)
-    # formatter methods must be bound
-    if _valid_formatter(method):
+    if callable(method):
+        # obj claims to have repr method...
+        if callable(pretty._safe_getattr(obj, '_ipython_canary_method_should_not_exist_', None)):
+            # ...but don't trust proxy objects that claim to have everything
+            return None
         return method
 
 


Test patch:
diff --git a/IPython/core/tests/test_formatters.py b/IPython/core/tests/test_formatters.py
--- a/IPython/core/tests/test_formatters.py
+++ b/IPython/core/tests/test_formatters.py
@@ -314,7 +314,6 @@ def test_print_method_bound():
     class MyHTML(object):
         def _repr_html_(self):
             return "hello"
-
     with capture_output() as captured:
         result = f(MyHTML)
     nt.assert_is(result, None)
@@ -325,6 +324,44 @@ def _repr_html_(self):
     nt.assert_equal(result, "hello")
     nt.assert_equal(captured.stderr, "")
 
+def test_print_method_weird():
+
+    class TextMagicHat(object):
+        def __getattr__(self, key):
+            return key
+
+    f = HTMLFormatter()
+    
+    text_hat = TextMagicHat()
+    nt.assert_equal(text_hat._repr_html_, '_repr_html_')
+    with capture_output() as captured:
+        result = f(text_hat)
+    
+    nt.assert_is(result, None)
+    nt.assert_not_in("FormatterWarning", captured.stderr)
+
+    class CallableMagicHat(object):
+        def __getattr__(self, key):
+            return lambda : key
+    
+    call_hat = CallableMagicHat()
+    with capture_output() as captured:
+        result = f(call_hat)
+    
+    nt.assert_equal(result, None)
+
+    class BadReprArgs(object):
+        def _repr_html_(self, extra, args):
+            return "html"
+    
+    bad = BadReprArgs()
+    with capture_output() as captured:
+        result = f(bad)
+    
+    nt.assert_is(result, None)
+    nt.assert_not_in("FormatterWarning", captured.stderr)
+
+
 def test_format_config():
     """config objects don't pretend to support fancy reprs with lazy attrs"""
     f = HTMLFormatter()