# This program is free software; you can redistribute it and/or modify # it under the terms of the (LGPL) GNU Lesser General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library Lesser General Public License for more details at # ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Contains basic caching classes. """ import os import suds from tempfile import gettempdir as tmp from suds.transport import * from suds.sax.parser import Parser from suds.sax.element import Element from datetime import datetime as dt from datetime import timedelta from cStringIO import StringIO from logging import getLogger try: import cPickle as pickle except: import pickle log = getLogger(__name__) class Cache: """ An object object cache. """ def get(self, id): """ Get a object from the cache by ID. @param id: The object ID. @type id: str @return: The object, else None @rtype: any """ raise Exception('not-implemented') def getf(self, id): """ Get a object from the cache by ID. @param id: The object ID. @type id: str @return: The object, else None @rtype: any """ raise Exception('not-implemented') def put(self, id, object): """ Put a object into the cache. @param id: The object ID. @type id: str @param object: The object to add. @type object: any """ raise Exception('not-implemented') def putf(self, id, fp): """ Write a fp into the cache. @param id: The object ID. @type id: str @param fp: File pointer. @type fp: file-like object. """ raise Exception('not-implemented') def purge(self, id): """ Purge a object from the cache by id. @param id: A object ID. @type id: str """ raise Exception('not-implemented') def clear(self): """ Clear all objects from the cache. """ raise Exception('not-implemented') class NoCache(Cache): """ The passthru object cache. """ def get(self, id): return None def getf(self, id): return None def put(self, id, object): pass def putf(self, id, fp): pass class FileCache(Cache): """ A file-based URL cache. @cvar fnprefix: The file name prefix. @type fnsuffix: str @ivar duration: The cached file duration which defines how long the file will be cached. @type duration: (unit, value) @ivar location: The directory for the cached files. @type location: str """ fnprefix = 'suds' units = ('months', 'weeks', 'days', 'hours', 'minutes', 'seconds') def __init__(self, location=None, **duration): """ @param location: The directory for the cached files. @type location: str @param duration: The cached file duration which defines how long the file will be cached. A duration=0 means forever. The duration may be: (months|weeks|days|hours|minutes|seconds). @type duration: {unit:value} """ if location is None: location = os.path.join(tmp(), 'suds') self.location = location self.duration = (None, 0) self.setduration(**duration) self.checkversion() def fnsuffix(self): """ Get the file name suffix @return: The suffix @rtype: str """ return 'gcf' def setduration(self, **duration): """ Set the caching duration which defines how long the file will be cached. @param duration: The cached file duration which defines how long the file will be cached. A duration=0 means forever. The duration may be: (months|weeks|days|hours|minutes|seconds). @type duration: {unit:value} """ if len(duration) == 1: arg = duration.items()[0] if not arg[0] in self.units: raise Exception('must be: %s' % str(self.units)) self.duration = arg return self def setlocation(self, location): """ Set the location (directory) for the cached files. @param location: The directory for the cached files. @type location: str """ self.location = location def mktmp(self): """ Make the I{location} directory if it doesn't already exits. """ try: if not os.path.isdir(self.location): os.makedirs(self.location) except: log.debug(self.location, exc_info=1) return self def put(self, id, bfr): try: fn = self.__fn(id) f = self.open(fn, 'w') f.write(bfr) f.close() return bfr except: log.debug(id, exc_info=1) return bfr def putf(self, id, fp): try: fn = self.__fn(id) f = self.open(fn, 'w') f.write(fp.read()) fp.close() f.close() return open(fn) except: log.debug(id, exc_info=1) return fp def get(self, id): try: f = self.getf(id) bfr = f.read() f.close() return bfr except: pass def getf(self, id): try: fn = self.__fn(id) self.validate(fn) return self.open(fn) except: pass def validate(self, fn): """ Validate that the file has not expired based on the I{duration}. @param fn: The file name. @type fn: str """ if self.duration[1] < 1: return created = dt.fromtimestamp(os.path.getctime(fn)) d = { self.duration[0]:self.duration[1] } expired = created+timedelta(**d) if expired < dt.now(): log.debug('%s expired, deleted', fn) os.remove(fn) def clear(self): for fn in os.listdir(self.location): if os.path.isdir(fn): continue if fn.startswith(self.fnprefix): log.debug('deleted: %s', fn) os.remove(os.path.join(self.location, fn)) def purge(self, id): fn = self.__fn(id) try: os.remove(fn) except: pass def open(self, fn, *args): """ Open the cache file making sure the directory is created. """ self.mktmp() return open(fn, *args) def checkversion(self): path = os.path.join(self.location, 'version') try: f = self.open(path) version = f.read() f.close() if version != suds.__version__: raise Exception() except: self.clear() f = self.open(path, 'w') f.write(suds.__version__) f.close() def __fn(self, id): name = id suffix = self.fnsuffix() fn = '%s-%s.%s' % (self.fnprefix, name, suffix) return os.path.join(self.location, fn) class DocumentCache(FileCache): """ Provides xml document caching. """ def fnsuffix(self): return 'xml' def get(self, id): try: fp = FileCache.getf(self, id) if fp is None: return None p = Parser() return p.parse(fp) except: FileCache.purge(self, id) def put(self, id, object): if isinstance(object, Element): FileCache.put(self, id, str(object)) return object class ObjectCache(FileCache): """ Provides pickled object caching. @cvar protocol: The pickling protocol. @type protocol: int """ protocol = 2 def fnsuffix(self): return 'px' def get(self, id): try: fp = FileCache.getf(self, id) if fp is None: return None else: return pickle.load(fp) except: FileCache.purge(self, id) def put(self, id, object): bfr = pickle.dumps(object, self.protocol) FileCache.put(self, id, bfr) return object