Skip to content

Commit

Permalink
add sessioning interfaces, configuration API, and a sample implementa…
Browse files Browse the repository at this point in the history
…tion that uses cookies
  • Loading branch information
mcdonc committed Oct 29, 2010
1 parent a62cc22 commit 9682095
Show file tree
Hide file tree
Showing 9 changed files with 654 additions and 4 deletions.
7 changes: 7 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ Features (delta from BFG 1.3.X)
a Pylons-style "view handler" (such a thing used to be called a
"controller" in Pylons 1.0).

- New argument to configurator: ``session_factory``.

- New method on configurator: ``set_session_factory``

- Using ``request.session`` now returns a (dictionary-like) session
object if a session factory has been configured.

Documentation (delta from BFG 1.3)
-----------------------------------

Expand Down
4 changes: 3 additions & 1 deletion docs/api/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

.. automodule:: pyramid.configuration

.. autoclass:: Configurator(registry=None, package=None, settings=None, root_factory=None, authentication_policy=None, authorization_policy=None, renderers=DEFAULT_RENDERERS, debug_logger=None, locale_negotiator=None, request_factory=None, renderer_globals_factory=None, default_permission=None)
.. autoclass:: Configurator(registry=None, package=None, settings=None, root_factory=None, authentication_policy=None, authorization_policy=None, renderers=DEFAULT_RENDERERS, debug_logger=None, locale_negotiator=None, request_factory=None, renderer_globals_factory=None, default_permission=None, session_factory=None)

.. attribute:: registry

Expand Down Expand Up @@ -64,6 +64,8 @@

.. automethod:: set_default_permission

.. automethod:: set_session_factory

.. automethod:: set_request_factory

.. automethod:: set_renderer_globals_factory
Expand Down
31 changes: 28 additions & 3 deletions pyramid/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
from pyramid.interfaces import IViewClassifier
from pyramid.interfaces import IExceptionResponse
from pyramid.interfaces import IException
from pyramid.interfaces import ISessionFactory

from pyramid import chameleon_text
from pyramid import chameleon_zpt
Expand Down Expand Up @@ -187,7 +188,17 @@ class or a :term:`dotted Python name` to same. The debug logger
always be executable by entirely anonymous users (any
authorization policy in effect is ignored). See also
:ref:`setting_a_default_permission`.
"""
If ``session_factory`` is passed, it should be an object which
implements the :term:`session factory` interface. If a nondefault
value is passed, the ``session_factory`` will be used to create a
session object when ``request.session`` is accessed. Note that
the same outcome can be achieved by calling
:ref:`pyramid.configration.Configurator.set_session_factory`. By
default, this argument is ``None``, indicating that no session
factory will be configured (and thus accessing ``request.session``
will throw an error) unless ``set_session_factory`` is called later
during configuration. """

manager = manager # for testing injection
venusian = venusian # for testing injection
Expand All @@ -205,7 +216,9 @@ def __init__(self,
locale_negotiator=None,
request_factory=None,
renderer_globals_factory=None,
default_permission=None):
default_permission=None,
session_factory=None,
):
if package is None:
package = caller_package()
name_resolver = DottedNameResolver(package)
Expand All @@ -227,6 +240,7 @@ def __init__(self,
request_factory=request_factory,
renderer_globals_factory=renderer_globals_factory,
default_permission=default_permission,
session_factory=session_factory,
)

def _set_settings(self, mapping):
Expand Down Expand Up @@ -362,7 +376,8 @@ def setup_registry(self, settings=None, root_factory=None,
renderers=DEFAULT_RENDERERS, debug_logger=None,
locale_negotiator=None, request_factory=None,
renderer_globals_factory=None,
default_permission=None):
default_permission=None,
session_factory=None):
""" When you pass a non-``None`` ``registry`` argument to the
:term:`Configurator` constructor, no initial 'setup' is
performed against the registry. This is because the registry
Expand Down Expand Up @@ -408,6 +423,8 @@ def setup_registry(self, settings=None, root_factory=None,
self.set_renderer_globals_factory(renderer_globals_factory)
if default_permission:
self.set_default_permission(default_permission)
if session_factory is not None:
self.set_session_factory(session_factory)

# getSiteManager is a unit testing dep injection
def hook_zca(self, getSiteManager=None):
Expand Down Expand Up @@ -1800,6 +1817,14 @@ def set_default_permission(self, permission):
"""
self.registry.registerUtility(permission, IDefaultPermission)

def set_session_factory(self, session_factory):
"""
Configure the application with a :term:`session factory`. If
this method is called, the ``session_factory`` argument must
be a session factory callable.
"""
self.registry.registerUtility(session_factory, ISessionFactory)

def add_translation_dirs(self, *specs):
""" Add one or more :term:`translation directory` paths to the
current configuration state. The ``specs`` argument is a
Expand Down
117 changes: 117 additions & 0 deletions pyramid/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,5 +375,122 @@ class IDefaultPermission(Interface):
""" A string object representing the default permission to be used
for all view configurations which do not explicitly declare their
own."""

class ISessionFactory(Interface):
""" An interface representing a factory which accepts a request object and
returns an ISession object """
def __call__(request):
""" Return an ISession object """

class ISession(Interface):
""" An interface representing a session (a web session object,
usually accessed via ``request.session``.
Keys and values of a session must be pickleable.
"""

# attributes

created = Attribute('Integer representing Epoch time when created.')
modified = Attribute(
'Integer representing Epoch time of last modification. If the '
'session has not yet been modified (it is new), this time will '
'be the created time.')
new = Attribute('Boolean attribute. If ``True``, the session is new.')

# special methods

def invalidate():
""" Invalidate the session. The action caused by
``invalidate`` is implementation-dependent, but it should have
the effect of completely dissociating any data stored in the
session with the current request. It might set response
values (such as one which clears a cookie), or it might not."""

def changed():
""" Mark the session as changed. A user of a session should
call this method after he or she mutates a mutable object that
is *a value of the session* (it should not be required after
mutating the session itself). For example, if the user has
stored a dictionary in the session under the key ``foo``, and
he or she does ``session['foo'] = {}``, ``changed()`` needn't
be called. However, if subsequently he or she does
``session['foo']['a'] = 1``, ``changed()`` must be called for
the sessioning machinery to notice the mutation of the
internal dictionary."""

# mapping methods

def __getitem__(key):
"""Get a value for a key
A KeyError is raised if there is no value for the key.
"""

def get(key, default=None):
"""Get a value for a key
The default is returned if there is no value for the key.
"""

def __delitem__(key):
"""Delete a value from the mapping using the key.
A KeyError is raised if there is no value for the key.
"""

def __setitem__(key, value):
"""Set a new item in the mapping."""

def keys():
"""Return the keys of the mapping object.
"""

def values():
"""Return the values of the mapping object.
"""

def items():
"""Return the items of the mapping object.
"""

def iterkeys():
"iterate over keys; equivalent to __iter__"

def itervalues():
"iterate over values"

def iteritems():
"iterate over items"

def clear():
"delete all items"

def update(d):
" Update D from E: for k in E.keys(): D[k] = E[k]"

def setdefault(key, default=None):
"D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D"

def pop(k, *args):
"""remove specified key and return the corresponding value
*args may contain a single default value, or may not be supplied.
If key is not found, default is returned if given, otherwise
KeyError is raised"""

def popitem():
"""remove and return some (key, value) pair as a
2-tuple; but raise KeyError if mapping is empty"""

def __len__():
"""Return the number of items in the session.
"""

def __iter__():
"""Return an iterator for the keys of the mapping object.
"""

def __contains__(key):
"""Return true if a key exists in the mapping."""

NO_PERMISSION_REQUIRED = '__no_permission_required__'
17 changes: 17 additions & 0 deletions pyramid/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
from webob import Request as WebobRequest

from pyramid.interfaces import IRequest
from pyramid.interfaces import ISessionFactory

from pyramid.exceptions import ConfigurationError
from pyramid.decorator import reify

class Request(WebobRequest):
"""
Expand Down Expand Up @@ -137,6 +141,19 @@ def _process_finished_callbacks(self):
callback = callbacks.pop(0)
callback(self)

@reify
def session(self):
""" Obtain the :term:`session` object associated with this
request. If a :term:`session factory` has not been registered
during application configuration, a
:class:`pyramid.exceptions.ConfigurationError` will be raised"""
factory = self.registry.queryUtility(ISessionFactory)
if factory is None:
raise ConfigurationError(
'No session factory registered '
'(use ``config.add_session_factory``)')
return factory(self)

# override default WebOb "environ['adhoc_attr']" mutation behavior
__getattr__ = object.__getattribute__
__setattr__ = object.__setattr__
Expand Down
Loading

0 comments on commit 9682095

Please sign in to comment.