Index: django/contrib/sessions/backends/appengine.py
===================================================================
--- django/contrib/sessions/backends/appengine.py	(Revision 0)
+++ django/contrib/sessions/backends/appengine.py	(Revision 0)
@@ -0,0 +1,140 @@
+r"""
+>>> from django.conf import settings
+>>> from django.contrib.sessions.backends.appengine import SessionStore as AppEngineSession
+>>> from django.contrib.sessions.backends.base import SessionBase
+
+>>> appengine_session = AppEngineSession()
+>>> appengine_session.modified
+False
+>>> appengine_session['cat'] = "dog"
+>>> appengine_session.modified
+True
+>>> appengine_session.pop('cat')
+'dog'
+>>> appengine_session.pop('some key', 'does not exist')
+'does not exist'
+>>> appengine_session.save()
+>>> appengine_session.exists(appengine_session.session_key)
+True
+>>> appengine_session.delete(appengine_session.session_key)
+>>> appengine_session.exists(appengine_session.session_key)
+False
+>>> s = SessionBase()
+>>> s._session['some key'] = 'exists' # Pre-populate the session with some data
+>>> s.accessed = False   # Reset to pretend this wasn't accessed previously
+
+>>> s.accessed, s.modified
+(False, False)
+
+>>> s.pop('non existant key', 'does not exist')
+'does not exist'
+>>> s.accessed, s.modified
+(True, False)
+
+>>> s.setdefault('foo', 'bar')
+'bar'
+>>> s.setdefault('foo', 'baz')
+'bar'
+
+>>> s.accessed = False  # Reset the accessed flag
+
+>>> s.pop('some key')
+'exists'
+>>> s.accessed, s.modified
+(True, True)
+
+>>> s.pop('some key', 'does not exist')
+'does not exist'
+"""
+import md5
+import sys
+import base64
+import datetime
+import cPickle as pickle
+
+from django.conf import settings
+from django.contrib.sessions.backends.base import SessionBase
+from django.core.exceptions import SuspiciousOperation
+
+# add the weird appengine location (here: MacOSX & Windows) to the pythonpath
+# if script is started from the command shell, not needed if you are running
+# the your app with dev_appserver.py
+if __name__ == '__main__':
+    sys.path.insert(0, '/usr/local/google_appengine/', )
+    
+    # needed for using datastore on the commandline
+    from google.appengine.api import apiproxy_stub_map
+    from google.appengine.api import datastore_file_stub
+    apiproxy_stub_map.apiproxy = apiproxy_stub_map.APIProxyStubMap() 
+    apiproxy_stub_map.apiproxy.RegisterStub('datastore_v3',
+        datastore_file_stub.DatastoreFileStub('whatever',
+            '/dev/null', '/dev/null'))
+
+try:
+    from google.appengine.ext import db
+except ImportError:
+    raise ImportError("The app engine datastore module could not be found. Please add it to your PYTHONPATH (e.g. '/usr/local/google_appengine/' on MacOS)")
+
+class Session(db.Model):
+    session_key = db.StringProperty(required=True)
+    session_data = db.TextProperty(required=True)
+    expire_date = db.DateTimeProperty(required=True)
+
+    def get_decoded(self):
+        encoded_data = base64.decodestring(self.session_data)
+        pickled, tamper_check = encoded_data[:-32], encoded_data[-32:]
+        if md5.new(pickled + settings.SECRET_KEY).hexdigest() != tamper_check:
+            raise SuspiciousOperation, "User tampered with session cookie."
+        try:
+            return pickle.loads(pickled)
+        # Unpickling can cause a variety of exceptions. If something happens,
+        # just return an empty dictionary (an empty session).
+        except:
+            return {}
+
+class SessionStore(SessionBase):
+    """
+    Implements appengine session store
+    """
+    def __init__(self, session_key=None):
+        super(SessionStore, self).__init__(session_key)
+
+    def load(self):
+        try:
+            s = Session.all().filter(
+                'session_key =', self.session_key).filter(
+                    'expire_date >=', datetime.datetime.now()).get()
+            if not s:
+                return self.decode(result.session_data)
+            raise SuspiciousOperation
+        except SuspiciousOperation:
+            # Create a new session_key for extra security.
+            self.session_key = self._get_new_session_key()
+            self._session_cache = {}
+
+            # Save immediately to minimize collision
+            self.save()
+            # Ensure the user is notified via a new cookie.
+            self.modified = True
+            return {}
+
+    def exists(self, session_key):
+        if Session.all().filter('session_key =', session_key).get():
+            return True
+        return False
+
+    def save(self):
+        Session(session_key = self.session_key,
+                session_data = self.encode(self._session),
+                expire_date = datetime.datetime.now() + \
+                    datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE)
+        ).put()
+
+    def delete(self, session_key):
+        s = Session.all().filter('session_key =', session_key).get()
+        if s:
+            s.delete()
+
+if __name__ == '__main__':
+    import doctest
+    doctest.testmod()
Index: docs/sessions.txt
===================================================================
--- docs/sessions.txt	(Revision 7412)
+++ docs/sessions.txt	(Arbeitskopie)
@@ -70,6 +70,17 @@
     to use file or database sessions directly instead of sending everything
     through the file or database cache backends.
 
+Using appengine-based sessions
+------------------------------
+
+To use appengine-based sessions, set the ``SESSION_ENGINE`` setting to
+``"django.contrib.sessions.backends.appengine"``.
+
+You might also want to add the path to the Google App Engine backend to your
+``PYTHONPATH``, e.g. ``/usr/local/google_appengine/`` on Mac OS X, if you want
+to run the code on your computer. This is not needed if you run it with the
+``dev_appserver.py`` or on Google's infrastructure.
+
 Using sessions in views
 =======================
 
