# HG changeset patch # User Christophe de Vienne # Date 1422399605 -3600 # Node ID a322a02ca301955f5478ee7a96beb6967892b5a6 # Parent d92a608c1a16fb3dd23f01fd2801459ea58ac3d0 [core] Protect session data from unwanted loading. Use specialised Session and Connection types that forward their 'data' and 'session_data' attributes to the pyramid request.session attribute. This forwarding is done with properties, instead of copying a reference, which allow to access request.session (and the session factory) if and only if Session.data or Connection.session_data is accessed. In some cases, most notably the static resources requests, it can mean no access the session during the request handling, which saves a request to the session persistence layer. Closes #4891437 diff -r d92a608c1a16 -r a322a02ca301 pyramid_cubicweb/core.py --- a/pyramid_cubicweb/core.py Mon Jan 26 18:06:58 2015 +0100 +++ b/pyramid_cubicweb/core.py Wed Jan 28 00:00:05 2015 +0100 @@ -9,7 +9,7 @@ import cubicweb import cubicweb.web -from cubicweb.server.session import Session +from cubicweb.server import session as cwsession from pyramid import httpexceptions @@ -20,6 +20,56 @@ log = logging.getLogger(__name__) +class Connection(cwsession.Connection): + """ A specialised Connection that access the session data through a + property. + + This behavior makes sure the actual session data is not loaded until + actually accessed. + """ + def __init__(self, session, *args, **kw): + super(Connection, self).__init__(session, *args, **kw) + self._session = session + + def _get_session_data(self): + return self._session.data + + def _set_session_data(self, data): + pass + + _session_data = property(_get_session_data, _set_session_data) + + +class Session(cwsession.Session): + """ A Session that access the session data through a property. + + Along with :class:`Connection`, it avoid any load of the pyramid session + data until it is actually accessed. + """ + def __init__(self, pyramid_request, user, repo): + super(Session, self).__init__(user, repo) + self._pyramid_request = pyramid_request + + def get_data(self): + if not getattr(self, '_protect_data_access', False): + self._data_accessed = True + return self._pyramid_request.session + + def set_data(self, data): + if getattr(self, '_data_accessed', False): + self._pyramid_request.session.clear() + self._pyramid_request.session.update(data) + + data = property(get_data, set_data) + + def new_cnx(self): + self._protect_data_access = True + try: + return Connection(self) + finally: + self._protect_data_access = False + + @contextmanager def cw_to_pyramid(request): """ Context manager to wrap a call to the cubicweb API. @@ -175,12 +225,12 @@ return cnx -def repo_connect(repo, eid): +def repo_connect(request, repo, eid): """A lightweight version of :meth:`cubicweb.server.repository.Repository.connect` that does not keep track of opened sessions, removing the need of closing them""" user = tools.cached_build_user(repo, eid) - session = Session(user, repo, None) + session = Session(request, user, repo) tools.cnx_attach_entity(session, user) # Calling the hooks should be done only once, disabling it completely for # now @@ -203,14 +253,10 @@ if not request.authenticated_userid: session = repo_connect( - repo, eid=request.registry['cubicweb.anonymous_eid']) + request, repo, eid=request.registry['cubicweb.anonymous_eid']) else: session = request._cw_cached_session - # XXX Ideally we store the cw session data in the pyramid session. - # BUT some data in the cw session data dictionnary makes pyramid fail. - session.data = request.session - return session @@ -249,7 +295,7 @@ repo = request.registry['cubicweb.repository'] try: - session = repo_connect(repo, eid=login) + session = repo_connect(request, repo, eid=login) request._cw_cached_session = session except: log.exception("Failed")