summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Petrello <lists@ryanpetrello.com>2013-04-30 18:27:49 -0400
committerRyan Petrello <lists@ryanpetrello.com>2013-04-30 21:17:49 -0400
commitdf2df2ace56c80cf8b0d89e048d7721951d59ca1 (patch)
treec2681d353919114cc45905642d1d5e3383700c8f
parentccf3d8895aebbb111f79829660de0d09c66d14de (diff)
downloadpecan-df2df2ace56c80cf8b0d89e048d7721951d59ca1.tar.gz
Fix a Py3 compatability bug in `pecan.jsonify`.
-rw-r--r--pecan/compat/__init__.py11
-rw-r--r--pecan/jsonify.py3
-rw-r--r--pecan/secure.py100
-rw-r--r--pecan/tests/test_secure.py39
4 files changed, 89 insertions, 64 deletions
diff --git a/pecan/compat/__init__.py b/pecan/compat/__init__.py
index 3869c12..6efe111 100644
--- a/pecan/compat/__init__.py
+++ b/pecan/compat/__init__.py
@@ -1,15 +1,15 @@
import sys
+import inspect
-# True if we are running on Python 3.
-PY3 = sys.version_info[0] == 3
+import six
-if PY3: # pragma: no cover
+if six.PY3: # pragma: no cover
text_type = str
else:
text_type = unicode
-if PY3: # pragma: no cover
+if six.PY3: # pragma: no cover
def native_(s, encoding='latin-1', errors='strict'):
""" If ``s`` is an instance of ``text_type``, return
``s``, otherwise return ``str(s, encoding, errors)``"""
@@ -31,3 +31,6 @@ return ``str(s, encoding, errors)``
Python 2: If ``s`` is an instance of ``text_type``, return
``s.encode(encoding, errors)``, otherwise return ``str(s)``
"""
+
+def is_bound_method(ob):
+ return inspect.ismethod(ob) and six.get_method_self(ob) is not None
diff --git a/pecan/jsonify.py b/pecan/jsonify.py
index 5bd4c0f..2d89c53 100644
--- a/pecan/jsonify.py
+++ b/pecan/jsonify.py
@@ -16,6 +16,7 @@ except ImportError: # pragma no cover
from webob.multidict import MultiDict
webob_dicts = (MultiDict,)
+import six
from simplegeneric import generic
try:
@@ -77,7 +78,7 @@ class GenericJSON(JSONEncoder):
returns webob_dicts.mixed() dictionary, which is guaranteed
to be JSON-friendly.
'''
- if hasattr(obj, '__json__') and callable(obj.__json__):
+ if hasattr(obj, '__json__') and six.callable(obj.__json__):
return obj.__json__()
elif isinstance(obj, (date, datetime)):
return str(obj)
diff --git a/pecan/secure.py b/pecan/secure.py
index f0a3975..e44aacb 100644
--- a/pecan/secure.py
+++ b/pecan/secure.py
@@ -1,9 +1,14 @@
from functools import wraps
-from inspect import getmembers, ismethod, isfunction
+from inspect import getmembers, isfunction
from webob import exc
import six
+if six.PY3:
+ from .compat import is_bound_method as ismethod
+else:
+ from inspect import ismethod
+
from .decorators import expose
from .util import _cfg, iscontroller
@@ -21,6 +26,9 @@ class _SecureState(object):
def __nonzero__(self):
return self.boolean_value
+ def __bool__(self):
+ return self.__nonzero__()
+
Any = _SecureState('Any', False)
Protected = _SecureState('Protected', True)
@@ -68,7 +76,7 @@ class _SecuredAttribute(object):
def __set_parent(self, parent):
if ismethod(parent):
- self._parent = parent.im_self
+ self._parent = six.get_method_self(parent)
else:
self._parent = parent
parent = property(__get_parent, __set_parent)
@@ -122,7 +130,7 @@ def secure(func_or_obj, check_permissions_for_obj=None):
return _SecuredAttribute(func_or_obj, check_permissions_for_obj)
-class SecureController(object):
+class SecureControllerMeta(type):
"""
Used to apply security to a controller.
Implementations of SecureController should extend the
@@ -130,48 +138,57 @@ class SecureController(object):
value (depending on whether or not the user has permissions
to the controller).
"""
- class __metaclass__(type):
- def __init__(cls, name, bases, dict_):
- cls._pecan = dict(
- secured=Protected,
- check_permissions=cls.check_permissions,
- unlocked=[]
- )
-
- for name, value in getmembers(cls)[:]:
- if ismethod(value):
- if iscontroller(value) and value._pecan.get(
- 'secured'
- ) is None:
- # Wrap the function so that the security context is
- # local to this class definition. This works around
- # the fact that unbound method attributes are shared
- # across classes with the same bases.
- wrapped = _make_wrapper(value)
- wrapped._pecan['secured'] = Protected
- wrapped._pecan['check_permissions'] = \
- cls.check_permissions
- setattr(cls, name, wrapped)
- elif hasattr(value, '__class__'):
- if name.startswith('__') and name.endswith('__'):
- continue
- if isinstance(value, _UnlockedAttribute):
- # mark it as unlocked and remove wrapper
- cls._pecan['unlocked'].append(value.obj)
- setattr(cls, name, value.obj)
- elif isinstance(value, _SecuredAttribute):
- # The user has specified a different check_permissions
- # than the class level version. As far as the class
- # is concerned, this method is unlocked because
- # it is using a check_permissions function embedded in
- # the _SecuredAttribute wrapper
- cls._pecan['unlocked'].append(value)
+ def __init__(cls, name, bases, dict_):
+ cls._pecan = dict(
+ secured=Protected,
+ check_permissions=cls.check_permissions,
+ unlocked=[]
+ )
+
+ for name, value in getmembers(cls)[:]:
+ if (isfunction if six.PY3 else ismethod)(value):
+ if iscontroller(value) and value._pecan.get(
+ 'secured'
+ ) is None:
+ # Wrap the function so that the security context is
+ # local to this class definition. This works around
+ # the fact that unbound method attributes are shared
+ # across classes with the same bases.
+ wrapped = _make_wrapper(value)
+ wrapped._pecan['secured'] = Protected
+ wrapped._pecan['check_permissions'] = \
+ cls.check_permissions
+ setattr(cls, name, wrapped)
+ elif hasattr(value, '__class__'):
+ if name.startswith('__') and name.endswith('__'):
+ continue
+ if isinstance(value, _UnlockedAttribute):
+ # mark it as unlocked and remove wrapper
+ cls._pecan['unlocked'].append(value.obj)
+ setattr(cls, name, value.obj)
+ elif isinstance(value, _SecuredAttribute):
+ # The user has specified a different check_permissions
+ # than the class level version. As far as the class
+ # is concerned, this method is unlocked because
+ # it is using a check_permissions function embedded in
+ # the _SecuredAttribute wrapper
+ cls._pecan['unlocked'].append(value)
+
+
+class SecureControllerBase(object):
@classmethod
def check_permissions(cls):
return False
+SecureController = SecureControllerMeta(
+ 'SecureController',
+ (SecureControllerBase,),
+ {}
+)
+
+
def _make_wrapper(f):
"""return a wrapped function with a copy of the _pecan context"""
@wraps(f)
@@ -188,7 +205,10 @@ def handle_security(controller):
check_permissions = controller._pecan['check_permissions']
if isinstance(check_permissions, six.string_types):
- check_permissions = getattr(controller.im_self, check_permissions)
+ check_permissions = getattr(
+ six.get_method_self(controller),
+ check_permissions
+ )
if not check_permissions():
raise exc.HTTPUnauthorized
diff --git a/pecan/tests/test_secure.py b/pecan/tests/test_secure.py
index 1f48037..53a63ca 100644
--- a/pecan/tests/test_secure.py
+++ b/pecan/tests/test_secure.py
@@ -5,6 +5,7 @@ if sys.version_info < (2, 7):
else:
import unittest # noqa
+from six import b as b_
from webtest import TestApp
from pecan import expose, make_app
@@ -59,11 +60,11 @@ class TestSecure(PecanTestCase):
))
response = app.get('/')
assert response.status_int == 200
- assert response.body == 'Hello, World!'
+ assert response.body == b_('Hello, World!')
response = app.get('/unlocked')
assert response.status_int == 200
- assert response.body == 'Sure thing'
+ assert response.body == b_('Sure thing')
response = app.get('/locked', expect_errors=True)
assert response.status_int == 401
@@ -73,7 +74,7 @@ class TestSecure(PecanTestCase):
response = app.get('/secret/allowed')
assert response.status_int == 200
- assert response.body == 'Allowed!'
+ assert response.body == b_('Allowed!')
def test_unlocked_attribute(self):
class AuthorizedSubController(object):
@@ -121,11 +122,11 @@ class TestSecure(PecanTestCase):
))
response = app.get('/')
assert response.status_int == 200
- assert response.body == 'Hello, World!'
+ assert response.body == b_('Hello, World!')
response = app.get('/unlocked')
assert response.status_int == 200
- assert response.body == 'Sure thing'
+ assert response.body == b_('Sure thing')
response = app.get('/locked', expect_errors=True)
assert response.status_int == 401
@@ -135,15 +136,15 @@ class TestSecure(PecanTestCase):
response = app.get('/secret/allowed')
assert response.status_int == 200
- assert response.body == 'Allowed!'
+ assert response.body == b_('Allowed!')
response = app.get('/secret/authorized/')
assert response.status_int == 200
- assert response.body == 'Index'
+ assert response.body == b_('Index')
response = app.get('/secret/authorized/allowed')
assert response.status_int == 200
- assert response.body == 'Allowed!'
+ assert response.body == b_('Allowed!')
def test_secure_attribute(self):
authorized = False
@@ -163,7 +164,7 @@ class TestSecure(PecanTestCase):
app = TestApp(make_app(RootController()))
response = app.get('/')
assert response.status_int == 200
- assert response.body == 'Hello from root!'
+ assert response.body == b_('Hello from root!')
response = app.get('/sub/', expect_errors=True)
assert response.status_int == 401
@@ -171,7 +172,7 @@ class TestSecure(PecanTestCase):
authorized = True
response = app.get('/sub/')
assert response.status_int == 200
- assert response.body == 'Hello from sub!'
+ assert response.body == b_('Hello from sub!')
def test_state_attribute(self):
from pecan.secure import Any, Protected
@@ -288,7 +289,7 @@ class TestObjectPathSecurity(PecanTestCase):
def test_sub_of_both_not_secret(self):
response = self.app.get('/notsecret/hi/')
assert response.status_int == 200
- assert response.body == 'Index hi'
+ assert response.body == b_('Index hi')
def test_protected_lookup(self):
response = self.app.get('/secret/hi/', expect_errors=True)
@@ -297,7 +298,7 @@ class TestObjectPathSecurity(PecanTestCase):
self.secret_cls.authorized = True
response = self.app.get('/secret/hi/')
assert response.status_int == 200
- assert response.body == 'Index hi'
+ assert response.body == b_('Index hi')
assert 'secretcontroller' in self.permissions_checked
def test_secured_notfound_lookup(self):
@@ -324,7 +325,7 @@ class TestObjectPathSecurity(PecanTestCase):
self.deepsecret_cls.authorized = True
response = self.app.get('/secret/hi/deepsecret/')
assert response.status_int == 200
- assert response.body == 'Deep Secret'
+ assert response.body == b_('Deep Secret')
assert 'secretcontroller' in self.permissions_checked
assert 'deepsecret' in self.permissions_checked
@@ -333,14 +334,14 @@ class TestObjectPathSecurity(PecanTestCase):
self.deepsecret_cls.authorized = True
response = self.app.get('/secret/1/deepsecret/2/deepsecret/')
assert response.status_int == 200
- assert response.body == 'Deep Secret'
+ assert response.body == b_('Deep Secret')
assert 'secretcontroller' in self.permissions_checked
assert 'deepsecret' in self.permissions_checked
def test_unlocked_lookup(self):
response = self.app.get('/notsecret/1/deepsecret/2/')
assert response.status_int == 200
- assert response.body == 'Index 2'
+ assert response.body == b_('Index 2')
assert 'deepsecret' not in self.permissions_checked
response = self.app.get(
@@ -368,7 +369,7 @@ class TestObjectPathSecurity(PecanTestCase):
self.secret_cls.independent_authorization = True
response = self.app.get('/secret/independent')
assert response.status_int == 200
- assert response.body == 'Independent Security'
+ assert response.body == b_('Independent Security')
assert len(self.permissions_checked) == 1
assert 'independent' in self.permissions_checked
@@ -383,7 +384,7 @@ class TestObjectPathSecurity(PecanTestCase):
self.secret_cls.independent_authorization = True
response = self.app.get('/secret/wrapped/')
assert response.status_int == 200
- assert response.body == 'Index wrapped'
+ assert response.body == b_('Index wrapped')
assert len(self.permissions_checked) == 1
assert 'independent' in self.permissions_checked
@@ -392,7 +393,7 @@ class TestObjectPathSecurity(PecanTestCase):
self.secret_cls.independent_authorization = True
response = self.app.get('/secret/lookup_wrapped/')
assert response.status_int == 200
- assert response.body == 'Index wrapped'
+ assert response.body == b_('Index wrapped')
assert len(self.permissions_checked) == 2
assert 'independent' in self.permissions_checked
assert 'secretcontroller' in self.permissions_checked
@@ -400,7 +401,7 @@ class TestObjectPathSecurity(PecanTestCase):
def test_unlocked_attribute_in_insecure(self):
response = self.app.get('/notsecret/unlocked/')
assert response.status_int == 200
- assert response.body == 'Index unlocked'
+ assert response.body == b_('Index unlocked')
class SecureControllerSharedPermissionsRegression(PecanTestCase):