Refactored request and session properties management
authorThierry Florac <thierry.florac@onf.fr>
Wed, 12 Jul 2017 13:56:41 +0200 (2017-07-12)
changeset 100 119b9c2f3022
parent 99 2d076b2b485a
child 101 8b2786685e8b
Refactored request and session properties management
src/pyams_utils/property.py
src/pyams_utils/request.py
src/pyams_utils/session.py
src/pyams_utils/zodb.py
--- a/src/pyams_utils/property.py	Wed Jul 12 13:55:47 2017 +0200
+++ b/src/pyams_utils/property.py	Wed Jul 12 13:56:41 2017 +0200
@@ -18,8 +18,6 @@
 # import interfaces
 
 # import packages
-from pyams_utils.request import check_request, get_request_data, set_request_data
-from pyams_utils.session import get_session_data, set_session_data
 from zope.schema.fieldproperty import FieldProperty
 
 
@@ -75,73 +73,6 @@
         return result
 
 
-_marker = object()
-
-
-def request_property(key, prefix=None):
-    """Define a method decorator used to store result into current request's annotations
-
-    If not request is currently running, a new one is created.
-    `key` is a required argument; if None, the key will be the method's object
-
-    :param str key: session's value key; if *None*, the key will be the method's object; if *key* is a callable
-        object, it will be called to get the actual session key
-    :param prefix: str; prefix to use for session key; if *None*, the prefix will be the property name
-    """
-
-    def request_decorator(func):
-
-        def wrapper(obj, key, *args, **kwargs):
-            request = check_request()
-            if callable(key):
-                key = key(obj)
-            if not key:
-                key = '{1}::{0!r}'.format(obj, prefix or func.__name__)
-            data = get_request_data(request, key, _marker)
-            if data is _marker:
-                data = func
-                if callable(data):
-                    data = data(obj, *args, **kwargs)
-                set_request_data(request, key, data)
-            return data
-
-        return lambda x: wrapper(x, key=key)
-
-    return request_decorator
-
-
-def session_property(app, key=None, prefix=None):
-    """Define a method decorator used to store result into request's session
-
-    If no request is currently running, a new one is created.
-
-    :param str app: application identifier used to prefix session keys
-    :param str key: session's value key; if *None*, the key will be the method's object; if *key* is a callable
-        object, il will be called to get the actual session key
-    :param prefix: str; prefix to use for session key; if *None*, the prefix will be the property name
-    """
-
-    def session_decorator(func):
-
-        def wrapper(obj, app, key, *args, **kwargs):
-            request = check_request()
-            if callable(key):
-                key = key(obj)
-            if not key:
-                key = '{1}::{0!r}'.format(obj, prefix or func.__name__)
-            data = get_session_data(request, app, key, _marker)
-            if data is _marker:
-                data = func
-                if callable(data):
-                    data = data(obj, *args, **kwargs)
-                set_session_data(request, app, key, data)
-            return data
-
-        return lambda x: wrapper(x, app=app, key=key)
-
-    return session_decorator
-
-
 class classproperty:
     """Same decorator as property(), but passes obj.__class__ instead of obj to fget/fset/fdel.
     
--- a/src/pyams_utils/request.py	Wed Jul 12 13:55:47 2017 +0200
+++ b/src/pyams_utils/request.py	Wed Jul 12 13:56:41 2017 +0200
@@ -16,15 +16,82 @@
 # import standard library
 
 # import interfaces
-from pyams_utils.interfaces import MissingRequestError
+from pyams_utils.interfaces import MissingRequestError, ICacheKeyValue
+from pyramid.interfaces import IAuthenticationPolicy, IAuthorizationPolicy
 from zope.annotation.interfaces import IAttributeAnnotatable, IAnnotations
 
 # import packages
 from pyramid.request import Request
+from pyramid.security import Allowed
 from pyramid.threadlocal import get_current_request, get_current_registry
 from zope.interface import alsoProvides
 
 
+_marker = object()
+
+
+def request_property(key=None, prefix=None):
+    """Define a method decorator used to store result into current request's annotations
+
+    If no request is currently running, a new one is created.
+    `key` is a required argument; if None, the key will be the method's object
+
+    :param str key: annotations value key; if *None*, the key will be the method's object; if *key* is a callable
+        object, it will be called to get the actual session key
+    :param prefix: str; prefix to use for session key; if *None*, the prefix will be the property name
+    """
+
+    def request_decorator(func):
+
+        def wrapper(obj, key, *args, **kwargs):
+            request = check_request()
+            if callable(key):
+                key = key(obj, *args, **kwargs)
+            if not key:
+                key = '{0}::{1}'.format(prefix or func.__name__, ICacheKeyValue(obj))
+                if args:
+                    key += '::' + '::'.join((ICacheKeyValue(arg) for arg in args))
+                if kwargs:
+                    key += '::' + '::'.join((ICacheKeyValue(arg) for arg in kwargs.items()))
+            data = get_request_data(request, key, _marker)
+            if data is _marker:
+                data = func
+                if callable(data):
+                    data = data(obj, *args, **kwargs)
+                set_request_data(request, key, data)
+            return data
+
+        return lambda x, *args, **kwargs: wrapper(x, key, *args, **kwargs)
+
+    return request_decorator
+
+
+class PyAMSRequest(Request):
+    """Custom request factory
+
+    Used to add 'context' argument to 'effective_principals' method call
+    to be able to get 'roles' principals
+    """
+
+    @request_property(key=None)
+    def has_permission(self, permission, context=None):
+        if context is None:
+            context = self.context
+        try:
+            reg = self.registry
+        except AttributeError:
+            reg = get_current_registry()
+        authn_policy = reg.queryUtility(IAuthenticationPolicy)
+        if authn_policy is None:
+            return Allowed('No authentication policy in use.')
+        authz_policy = reg.queryUtility(IAuthorizationPolicy)
+        if authz_policy is None:
+            raise ValueError('Authentication policy registered without '
+                             'authorization policy')  # should never happen
+        principals = authn_policy.effective_principals(self, context)
+        return authz_policy.permits(context, principals, permission)
+
+
 def get_request(raise_exception=True):
     """Get current request
 
--- a/src/pyams_utils/session.py	Wed Jul 12 13:55:47 2017 +0200
+++ b/src/pyams_utils/session.py	Wed Jul 12 13:56:41 2017 +0200
@@ -18,6 +18,42 @@
 # import interfaces
 
 # import packages
+from pyams_utils.request import check_request
+
+
+_marker = object()
+
+
+def session_property(app, key=None, prefix=None):
+    """Define a method decorator used to store result into request's session
+
+    If no request is currently running, a new one is created.
+
+    :param str app: application identifier used to prefix session keys
+    :param str key: session's value key; if *None*, the key will be the method's object; if *key* is a callable
+        object, il will be called to get the actual session key
+    :param prefix: str; prefix to use for session key; if *None*, the prefix will be the property name
+    """
+
+    def session_decorator(func):
+
+        def wrapper(obj, app, key, *args, **kwargs):
+            request = check_request()
+            if callable(key):
+                key = key(obj, *args, **kwargs)
+            if not key:
+                key = '{1}::{0!r}'.format(obj, prefix or func.__name__)
+            data = get_session_data(request, app, key, _marker)
+            if data is _marker:
+                data = func
+                if callable(data):
+                    data = data(obj, *args, **kwargs)
+                set_session_data(request, app, key, data)
+            return data
+
+        return lambda x, *args, **kwargs: wrapper(x, app, key, *args, **kwargs)
+
+    return session_decorator
 
 
 def get_session_data(request, app, key, default=None):
--- a/src/pyams_utils/zodb.py	Wed Jul 12 13:55:47 2017 +0200
+++ b/src/pyams_utils/zodb.py	Wed Jul 12 13:56:41 2017 +0200
@@ -17,6 +17,7 @@
 
 # import interfaces
 from persistent.interfaces import IPersistent
+from pyams_utils.interfaces import ICacheKeyValue
 from pyams_utils.interfaces.site import IOptionalUtility
 from pyams_utils.interfaces.zeo import IZEOConnection
 from transaction.interfaces import ITransactionManager
@@ -71,6 +72,11 @@
         # recent spelling.
 
 
+@adapter_config(context=object, provides=ICacheKeyValue)
+def persistent_key_adapter(obj):
+    return '{0!r}'.format(obj)
+
+
 @implementer(IZEOConnection)
 class ZEOConnection(object):
     """ZEO connection object