1. brianfrantz
  2. beaker

Commits

Ben Bangert  committed 92864b0

* Updated session to only save to the storage *once* no under any/all
conditions rather than every time save() is called.
* Added session.revert() function that reverts the session to the state at
the beginning of the request.

  • Participants
  • Parent commits 89eec8f
  • Branches trunk

Comments (0)

Files changed (5)

File CHANGELOG

View file
 Beaker Changelog
 ================
 
+1.2 (**tip**)
+* Updated session to only save to the storage *once* no under any/all
+  conditions rather than every time save() is called.
+* Added session.revert() function that reverts the session to the state at
+  the beginning of the request.
+* Updated session to store entire session data in a single namespace key,
+  this lets memcached work properly, and makes for more efficient use of the
+  storage system for sessions.
+
 1.1.3 (12/29/2008)
 * Fix the 1.1.2 old cache/session upgrader to handle the has_current_value
   method.

File beaker/middleware.py

View file
         environ['beaker.get_session'] = self._get_session
         
         def session_start_response(status, headers, exc_info = None):
-            if session.__dict__['_sess'] is not None:
+            if session.dirty() or (session.accessed() and self.options.get('auto')):
+                session.persist()
                 if session.__dict__['_headers']['set_cookie']:
                     cookie = session.__dict__['_headers']['cookie_out']
                     if cookie:

File beaker/session.py

View file
         self.request['cookie_out'] = self.cookie[self.key].output(header='')
         self.request['set_cookie'] = True
 
-
     def delete(self):
         """Deletes the session from the persistent storage, and sends
         an expired cookie out"""
         if self.use_cookies:
             self._delete_cookie()
-        self._remove_session_dict()
+        self.clear()
 
     def invalidate(self):
         """Invalidates this session, creates a new session id, returns
         to the is_new state"""
-        self._remove_session_dict()
+        self.clear()
         self.was_invalidated = True
         self._create_id()
         self.load()
         now = time.time()
         try:
             session_data = self.namespace['session']
-            self.accessed = session_data['_accessed_time']
             session_data['_accessed_time'] = now
         except KeyError:
             session_data = {
                 '_accessed_time':now
             }
             self.is_new = True
-            self.accessed = now
+        self.accessed = session_data['_accessed_time']
         return session_data
-
-    def _remove_session_dict(self):
-        if hasattr(self, 'namespace'):
-            self.namespace.acquire_write_lock()
-            try:
-                del self.namespace['session']
-            finally:
-                self.namespace.release_write_lock()
     
     def _persist_session_dict(self, session_dict):
-        self.namespace['session'] = session_dict
+        if not session_dict:
+            del self.namespace['session']
+        else:
+            self.namespace['session'] = session_dict
         
     def load(self):
         "Loads the data from this session from persistent storage"
         now = time.time()
         self.request['set_cookie'] = True
         
-        self.namespace.acquire_write_lock()
+        self.namespace.acquire_read_lock()
         try:
             self.clear()
+            session_data = self._session_dict_from_namespace()
             
-            session_data = self._session_dict_from_namespace()
-                
             if self.timeout is not None and now - self.accessed > self.timeout:
                 self.invalidate()
             else:
                 self.update(session_data)
-                    
-            self._persist_session_dict(session_data)
+            self.accessed_dict = session_data.copy()
         finally:
-            self.namespace.release_write_lock()
+            self.namespace.release_read_lock()
     
-    def save(self):
-        "Saves the data for this session to persistent storage"
-
+    def save(self, accessed_only=False):
+        """Saves the data for this session to persistent storage
+        
+        If accessed_only is True, then only the original data loaded
+        at the beginning of the request will be saved, with the updated
+        last accessed time.
+        
+        """
         if not hasattr(self, 'namespace'):
             self.namespace = self.namespace_class(
                                     self.id, 
 
         self.namespace.acquire_write_lock()
         try:
-            session_data = self._session_dict_from_namespace()
-            session_data.update(self)
-            
-            for key in list(session_data):
-                if key not in self and key not in ('_accessed_time', '_creation_time'):
-                    del session_data[key]
-            
-            self._persist_session_dict(session_data)        
+            if accessed_only:
+                data = dict(self.accessed_dict.items())
+            else:
+                data = dict(self.items())
+            self._persist_session_dict(data)
         finally:
             self.namespace.release_write_lock()
         if self.is_new:
             self.request['set_cookie'] = True
     
+    def revert(self):
+        """Revert the session to its original state from its first
+        access in the request"""
+        self.clear()
+        self.update(self.accessed_dict)
+    
     # TODO: I think both these methods should be removed.  They're from
     # the original mod_python code i was ripping off but they really
     # have no use here.
             if self.timeout is not None and time.time() - \
                self['_accessed_time'] > self.timeout:
                 self.clear()
+            self.accessed_dict = self.copy()
             self._create_cookie()
     
     def created(self):
             ).hexdigest()
         ).hexdigest()
     
-    def save(self):
+    def save(self, accessed_only=False):
         """Saves the data for this session to persistent storage"""
+        if accessed_only:
+            self.clear()
+            self.update(self.accessed_dict)
         self._create_cookie()
     
     def expire(self):
             self['_id'] = self._make_id()
         self['_accessed_time'] = time.time()
         
-        
         if self.cookie_expires is not True:
             if self.cookie_expires is False:
                 expires = datetime.fromtimestamp( 0x7FFFFFFF )
         params = self.__dict__['_params']
         session = Session({}, use_cookies=False, id=id, **params)
         if session.is_new:
-            session.namespace.remove()
             return None
         return session
+    
+    def save(self):
+        self.__dict__['_dirty'] = True
+    
+    def persist(self):
+        """Persist the session to the storage
+        
+        If its set to autosave, then the entire session will be saved
+        regardless of if save() has been called. Otherwise, just the
+        accessed time will be updated if save() was not called, or
+        the session will be saved if save() was called.
+        
+        """
+        if self.__dict__['_params'].get('auto'):
+            self._session.save()
+        else:
+            if self.__dict__['_dirty']:
+                self._session().save()
+            else:
+                self._session().save(accessed_only=True)
+    
+    def dirty(self):
+        return self.__dict__.get('_dirty', False)
+    
+    def accessed(self):
+        return self.__dict__['_sess'] is not None

File beaker/util.py

View file
         ('secure', (bool, types.NoneType), "Session secure must be a boolean."),
         ('timeout', (int, types.NoneType), "Session timeout must be an "
          "integer."),
+        ('auto', (bool, types.NoneType), "Session is created if accessed."),
     ]
     return verify_rules(params, rules)
 

File setup.py

View file
 
 from setuptools import setup, find_packages
 
-version = '1.1.3'
+version = '1.2'
 
 pycryptopp = 'pycryptopp>=0.5.12'
 tests_require = ['nose', 'webtest']