Skip to content

Commit

Permalink
Tighten the definition of a method on Python 3.
Browse files Browse the repository at this point in the history
Fixes #6
  • Loading branch information
jamadden committed May 24, 2017
1 parent b7bb345 commit 9d5cb8b
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 10 deletions.
23 changes: 13 additions & 10 deletions src/zope/app/apidoc/codemodule/class_.py
Expand Up @@ -16,7 +16,9 @@

__docformat__ = 'restructuredtext'

from inspect import ismethod, ismethoddescriptor
from inspect import isfunction
from inspect import ismethod
from inspect import ismethoddescriptor

from zope.interface import implementer, implementedBy
from zope.security.checker import getCheckerForInstancesOf
Expand Down Expand Up @@ -72,16 +74,17 @@ def _iterAllAttributes(self):

if str is bytes:
# Python 2
def _ismethod(self, obj):
return ismethod(obj)
_ismethod = staticmethod(ismethod)
else:
# On Python 3, there is no unbound method.
# we treat everything that's callable as a method.
# The corner case is where we bind a C
# function to an attribute; it's not *technically* a method,
# but it acts just like a @staticmethod, so it works out
# the same
_ismethod = callable
# On Python 3, there is no unbound method. But we can't treat
# things that are simply callable as methods. Things like the
# security proxy are callable, but when `permission =
# CheckerPublic` (where zope.security.checker.CheckerPublic is
# proxied) is a class attribute, that's *not* a method.
# Checking if its actually a function gets us much more accurate
# results. (We could also check its qualname to see if it "belongs"
# to this class, but this seems to do the trick)
_ismethod = staticmethod(isfunction)

def getAttributes(self):
"""See IClassDocumentation."""
Expand Down
21 changes: 21 additions & 0 deletions src/zope/app/apidoc/codemodule/tests.py
Expand Up @@ -135,6 +135,27 @@ def test_copy_with_root(self):
clone = zcml.withParentAndName(self, 'name')
self.assertEqual(clone.rootElement.__parent__, clone)

class TestClass(unittest.TestCase):

def test_permission_is_not_method(self):
# We don't incorrectly assume that callable objects,
# like security proxies, are methods
from zope.app.apidoc.codemodule.class_ import Class
from zope.security.checker import CheckerPublic

self.assertTrue(callable(CheckerPublic))

class Parent(object):
def getPath(self):
return ''

class MyClass(object):
permission = CheckerPublic

klass = Class(Parent(), MyClass.__name__, MyClass)

self.assertEqual([], klass.getMethods())

def test_suite():
checker = standard_checker()

Expand Down

0 comments on commit 9d5cb8b

Please sign in to comment.