Repository: Pylons/pyramid

Issue:
Sphinx doesn't like the reify decorator
> Failed to get a method signature for kinto.core.resource.Resource.timestamp: <pyramid.decorator.reify object at 0x7f175ae45640> is not a callable object

If I understand correctly this is because the reify decorator doesn't pass along the function signature.



Discussion:
The problem here is that a reify'd property isn't callable—`@reify` has the same API as `@property`.  So Sphinx is looking for a callable object (not sure why, I don't know what your docs look like), but gets something that looks like a property.
Yes but it is missing some things that property is doing (__doc__, __annotation__, etc). If figured out a fix in #3656.

Resolving patch:
diff --git a/src/pyramid/decorator.py b/src/pyramid/decorator.py
--- a/src/pyramid/decorator.py
+++ b/src/pyramid/decorator.py
@@ -1,6 +1,3 @@
-from functools import update_wrapper
-
-
 class reify:
     """Use as a class method decorator.  It operates almost exactly like the
     Python ``@property`` decorator, but it puts the result of the method it
@@ -35,7 +32,7 @@ class reify:
 
     def __init__(self, wrapped):
         self.wrapped = wrapped
-        update_wrapper(self, wrapped)
+        self.__doc__ = wrapped.__doc__
 
     def __get__(self, inst, objtype=None):
         if inst is None:


Test patch:
diff --git a/tests/test_config/test_views.py b/tests/test_config/test_views.py
--- a/tests/test_config/test_views.py
+++ b/tests/test_config/test_views.py
@@ -4289,21 +4289,6 @@ def view():  # pragma: no cover
         self.assertEqual(result, 'function tests.test_config.test_views.view')
 
 
-class Test_viewdefaults(unittest.TestCase):
-    def _makeOne(self, wrapped):
-        from pyramid.decorator import reify
-
-        return reify(wrapped)
-
-    def test_dunder_attrs_copied(self):
-        from pyramid.config.views import viewdefaults
-
-        decorator = self._makeOne(viewdefaults)
-        self.assertEqual(decorator.__doc__, viewdefaults.__doc__)
-        self.assertEqual(decorator.__name__, viewdefaults.__name__)
-        self.assertEqual(decorator.__module__, viewdefaults.__module__)
-
-
 class DummyRegistry:
     utility = None
 
diff --git a/tests/test_decorator.py b/tests/test_decorator.py
--- a/tests/test_decorator.py
+++ b/tests/test_decorator.py
@@ -1,3 +1,4 @@
+import inspect
 import unittest
 
 
@@ -25,6 +26,28 @@ def wrapped(inst):
         result = decorator.__get__(None)
         self.assertEqual(result, decorator)
 
+    def test_copy_docstring(self):
+        def wrapped(inst):
+            """Test doc"""
+            return 'a'  # pragma: no cover
+
+        decorator = self._makeOne(wrapped)
+        assert decorator.__doc__ == 'Test doc'
+
+    def test_not_function(self):
+        """
+        Because reify'd methods act as attributes, it's important that they
+        aren't recognized as a function.  Otherwise tools like Sphinx may
+        misbehave, like in https://github.com/Pylons/pyramid/issues/3655
+
+        """
+
+        def wrapped(inst):
+            return 'a'  # pragma: no cover
+
+        decorator = self._makeOne(wrapped)
+        assert not inspect.isfunction(decorator)
+
 
 class Dummy:
     pass