saving state in Python/CGI

Peter van Kampen news at datatailors.com
Fri Sep 20 23:00:09 CEST 2002


In article <mailman.1032544767.23458.python-list at python.org>, Frank Gibbons 
wrote:
> Hi,
> 
> I've been using Python (and Jython) for about 5 months now. So far, so 
> great. I'm building a CGI app that has about 5 different stages, and I need 
> to maintain state between them. Specifically, it's a scientific tool that 
> requires the user to upload a (possibly quite large) datafile, processes 
> it, does some computation, then hands it back (formatted) with results.
> 
> I need access to the data on each page, yet don't want to pass it as a 
> hidden field, because of its potential size. In Perl, I could just use the 
> save() method from CGI.pm to save the form on the server side, pass a 
> session key in a hidden field, then retrieve the data using the key. For 
> the life of me, I can't find an equivalent way in Python. cgiFormStorage 
> looks like a dictionary, but it's immutable, so you can't retrieve data 
> from disk and add it to the form. cgi.py appears to have no analog to 
> CGI.pm's save() method, at least not that I've been able to find. I've 
> tried pickling the form, but that doesn't work either (it saves something 
> to disk alright, but not the large data retrieved from a multi-part form 
> file upload).
> 
> Perhaps I've missed something. Maybe that's just not how you do things in 
> Python. Can anyone help?
> 
> Thanks,
> 
> -Frank Gibbons
> 

Hi Frank,

Perhaps you can use the code below. It's my first little python
project so I'm mainly posting this for some feedback. It's a simpler
version (at least to my mind) of something I found in the vaults.

It creates a dict to store sessionvars in and pickles it to the hd. You
have to setup a dir where the webserver account has write (and read)
access. It defaults to .session/ in the current dir. 

It uses cookies to pass the sessionid that's why you have be careful to
write the headers that the class generates. It uses apache's
mod_unique_id if it is enabled (and you're using apache ;-) I haven't
tested yet but it should work in any cgi-environment) which provides
nicer cookie-id's than tempfiles which is used as a fallback but I
haven't checked if tempfilenames are safe sessionids. 

You can use it like this.

#First page

import CookieSession

s = CookieSession.CookieSession()

s["whatever"] = "you want (if it can be pickled)"

s.save()

print "Content-type: text/html"
print s.headers()
print
print YourHTML

#Another page

import CookieSession

s = CookieSession.CookieSession()

restore_from_session = s["whatever"]

As I said. Comments are very welcome.

PterK


#! /usr/bin/env python

# CookieSession.py
#
# Based on stuff i found at http://wingide.com/opensource/httpsession 
# 	which in turn I located in the Vaults of Parnassus : 
#			http://py.vaults.ca/parnassus/

import os
import time
import stat
import glob
import Cookie
import cPickle
import tempfile 

#python2.2 version
#class CookieSession(dict):

class CookieSession:
	"""
	TODO: Write docstring
	
	"""

	#-----------------------------------------------------------------------
	def __init__(self, timeout = 20, \
			store = ".sessions"):
		""" Init the class.
			1. Set timeout
			2. Set where to store the pickles 
			3. Clear up old sessions
			4. Find out if a session is active
			5. Yes? Load it and 'touch' it
			6. No? Create a new one, set some attributes and save it to disk.

		"""
		
		self.__timeout = timeout * 60
		self.__session_store = store
		self.__cookies = "Cache-control: no-cache\r\n"
		self.__on_expire_is_set = 0
		
		# expire old sessions
		self.__ClearOldSessions()

		# Get the current session or create a new one
		try:
			self.__sessionid = Cookie.SimpleCookie(os.environ["HTTP_COOKIE"]) \
									["sessionid"].value
			#touch session (from an effbot-posting)
			now = time.time()
			os.utime(self.__SessionFilename(), (now, now))
			#load session
			self.__SessionDict = self.__LoadSession()
		except:
			# Create a new session
			self.__sessionid = self.__CreateSessionid()
			self.__CreateCookie()
			self.__SessionDict = {}
			self["sessionid"] = self.__sessionid
			self.save()

	#-----------------------------------------------------------------------
	def expire(self):
		"Forces the current Session to expire"
		self.__on_expire()

	#-----------------------------------------------------------------------
	def headers(self):
		"Creates all headers for the calling page"
		return self.__cookies

	#-----------------------------------------------------------------------
	def save(self):
		"Saves the sessionDict to a pickled file"
		cPickle.dump(self.__SessionDict, self.__OpenSessionFile("w"))

	#-----------------------------------------------------------------------
	def __setattr__(self, name, value):
		"Used to check whether on_expire is set. Maybe a method is better?)"
		self.__dict__[name] = value
		if name == "on_expire":
			self.__on_expire_is_set = 1
		
	#-----------------------------------------------------------------------
	def __on_expire(self, sessionid = None):
		""" Calls a function that can be assigned to the attribute on_expire
			similar to ASP's On_Session_End. 
			
			It passes the session about to expire.
		
			def OnExpire(expired_session):
				if not expired_session["TransactionComplete"]:
					#Remove cruft from database
					pass
				
			s = CookieSession()
			s.on_expire = OnExpire
			
			...
			
			s.expire()

		"""

		if sessionid is None: sessionid = self.__sessionid
		try:
			# Test if it is set to prevent unnecessary loading of pickles 
			if self.__on_expire_is_set:
				self.on_expire(self.__LoadSession(sessionid))
		finally:
			os.remove(self.__SessionFilename(sessionid))

	#-----------------------------------------------------------------------
	def __CreateSessionid(self):
		"Create a sessionid from the apache unique_id module or a tempfile."
		try:
			id = os.environ["UNIQUE_ID"]
		except:
			id = os.path.basename(tempfile.mktemp())
		return id

	#-----------------------------------------------------------------------
	def __CreateCookie(self):
		"Create a cookie to store the sessionid on the clientside"
		C = Cookie.SimpleCookie()
		C["sessionid"] = self.__sessionid
		C["sessionid"]["path"] = "/"
		self.__cookies += C.output()

	#-----------------------------------------------------------------------
	def __SessionFilename(self, sessionid = None):
		if sessionid is None: sessionid = self.__sessionid
		return self.__session_store + "/.session" + str(sessionid)

	#-----------------------------------------------------------------------
	def __OpenSessionFile(self, mode, sessionid = None):
		"Open up the on-disk session persistence file"
		if sessionid is None: sessionid = self.__sessionid
		f = open(self.__SessionFilename(sessionid), mode)
		return f

	#-----------------------------------------------------------------------
	def __LoadSession(self, sessionid = None):
		"Loads a session from a pickled file"
		if sessionid is None: sessionid = self.__sessionid
		return cPickle.load(self.__OpenSessionFile("r", sessionid))
	
	#-----------------------------------------------------------------------
	def __ClearOldSessions(self):
		"Remove any expired session files"
		files = glob.glob(os.path.join(self.__session_store, '.session*'))
		for file in files:
			filetime = os.stat(file)[stat.ST_MTIME]
			currtime = time.time()
			if filetime + self.__timeout < currtime:
				# len(".session") = 8
				sessionid = file[len(self.__session_store)+9:]
				self.__on_expire(sessionid)

	#-----------------------------------------------------------------------
	# In 2.2 this works : 
	# class CookieSession(dict):
	#
	# Python map emulation : This (below) can be dropped for python2.2
	#-----------------------------------------------------------------------
	def __len__(self):
		return len(self.__SessionDict)

	#-----------------------------------------------------------------------
	def __getitem__(self, key):
		retval = self.__SessionDict.get(key)
		if retval == None:
		  retval = ""
		return retval

	#-----------------------------------------------------------------------
	def __setitem__(self, key, value):
		self.__SessionDict[key] = value

	#-----------------------------------------------------------------------
	def __delitem__(self, key):
		del self.__SessionDict[key]

	#-----------------------------------------------------------------------
	def keys(self):
		return self.__SessionDict.keys()

	#-----------------------------------------------------------------------
	def values(self):
		return self.__SessionDict.values()

	#-----------------------------------------------------------------------
	def items(self):
		return self.__SessionDict.items()

	#-----------------------------------------------------------------------
	def has_key(self, key):
		return self.__SessionDict.has_key(key)

	#-----------------------------------------------------------------------
	def get(self, key, f=None):
		return self.__SessionDict.get(key, f)

	#-----------------------------------------------------------------------
	def clear():
		return self.__SessionDict.clear()

	#-----------------------------------------------------------------------
	def copy():
		return self.__SessionDict.copy()

	#-----------------------------------------------------------------------
	def update(b):
		return self.__SessionDict.update(b)




More information about the Python-list mailing list