Roundup Tracker

Attachment 'interceptor.py'

Download

   1 import gzip, hashlib
   2 from roundup import hyperdb
   3 from StringIO import StringIO
   4 
   5 
   6 def interceptor_factory(classname, parent, baseclass) :
   7     '''
   8     Creates a subclass of parent which is also a subclass of the specified base
   9     class.  The base class is saved as class variable on the derived
  10     subclass.
  11     '''
  12     attrs = {'super' : baseclass }
  13     return type(classname, (parent, baseclass, object), attrs)
  14  
  15 
  16 
  17 class ClassInterceptor : 
  18     '''
  19     This class provides hooks to the create, get and get methods of a 
  20     superclass which is defined at runtime. This allows children of
  21     this class to override the set and get methods with their own 
  22     functionality without re-implementing what has already been 
  23     written. It is designed to be used with the interceptor_factory() 
  24     method in this class.
  25     '''
  26     def super_get(self, *p, **kw):
  27         '''
  28         Convenience method to call the get() method of the superclass. 
  29         '''
  30         return self.super.get(self, *p, **kw)
  31     def super_set(self, *p, **kw):
  32         '''
  33         Convenience method to call the set() method of the superclass.
  34         '''
  35         return self.super.set(self, *p, **kw)
  36     def super_create(self, *p, **kw):
  37         '''
  38         Convenience method to call the create() method of the superclass.
  39         '''
  40         return self.super.create(self, *p, **kw)
  41 
  42 
  43 class GzipFileClass (ClassInterceptor) : 
  44     '''
  45     This is designed to be a mixin with the FileClass implementation of the 
  46     backend. It provides transparent compression and decompression of file objects.
  47     Note that only the contents of the file are affected (and not the file name
  48     on disk, as FileClass does not have control over file naming.
  49     '''
  50     
  51     def _compress_content(self, propvalues):
  52         '''
  53         The compression code is factored out into its own method becuase it
  54         is needed for create() and set() 
  55         '''
  56         if 'content' in propvalues : 
  57             content = propvalues['content']
  58             compressed_fobj = StringIO()
  59             compressor = gzip.GzipFile(fileobj=compressed_fobj,mode="wb")
  60             compressor.write(content)
  61             compressor.close()
  62             propvalues['content'] = compressed_fobj.getvalue()
  63             
  64     def create(self, **propvalues):
  65         '''
  66         This method intercepts the "content" property if present. All other
  67         properties remain unaffected. The content property is compressed
  68         with gzip compression, then handed off to the superclass.
  69         '''
  70         self._compress_content(propvalues)
  71         return self.super_create(**propvalues)
  72 
  73     def set(self, itemid, **propvalues):  
  74         '''
  75         This method intercepts the "content" property if present. All other 
  76         properties remain unaffected. The content property is compressed with
  77         gzip compression, then handed off to the superclass for storage.
  78         '''  
  79         self._compress_content(propvalues)
  80         return self.super_set(itemid, **propvalues)
  81         
  82     def get(self, nodeid, propname, *p, **kw):
  83         '''
  84         This method intercepts the content property if present. No other 
  85         properties are affected. The content property is uncompressed before it
  86         is returned.
  87         If the file is not a gzipped file, the content is just directly
  88         returned.
  89         '''
  90         data = self.super_get(nodeid, propname, *p, **kw)
  91         if propname == 'content' : 
  92             compressed_content = data
  93             compressed_fobj = StringIO(compressed_content)
  94             decompressor = gzip.GzipFile(fileobj=compressed_fobj,mode='rb')
  95             try : 
  96                 data = decompressor.read()
  97             except IOError : 
  98                 pass
  99             decompressor.close()
 100             
 101         return data
 102     
 103 class Md5FileClass (ClassInterceptor):
 104     '''
 105     This class creates another property of FileClass (md5hash) which is 
 106     automatically maintained. This new property is the hash of the content
 107     property, and is updated whenever the content property is set. The user
 108     is never allowed to set the md5hash property directly.
 109     
 110     The md5sum property is visible to the user, so there is no need to 
 111     override the get() method.
 112     '''
 113     def __init__(self, db, classname, **properties):
 114         '''
 115         Add (or overwrite) the md5sum property to ensure that it 
 116         exists.
 117         '''
 118         properties['md5hash'] = hyperdb.String(indexme='yes')
 119         self.super.__init__(self,db,classname,**properties)
 120         
 121     def _hash_content(self, propvalues):
 122         # do not let user set md5hash
 123         if 'md5hash' in propvalues : 
 124             del propvalues['md5hash']
 125         if 'content' in propvalues : 
 126             sh = hashlib.md5()
 127             sh.update(propvalues['content'])
 128             propvalues['md5hash'] = sh.hexdigest()
 129             
 130     def create(self, **propvalues):
 131         self._hash_content(propvalues)
 132         return self.super_create(**propvalues)
 133     
 134     def set(self, itemid, **propvalues):  
 135         self._hash_content(propvalues)
 136         return self.super_set(itemid, **propvalues)
 137     
 138 class UniqueFileClass (Md5FileClass):
 139     '''
 140     This class intercepts requests to create new objects and refuses to create 
 141     duplicates. If the "new" file has the same content as an existing file, 
 142     the existing ID is returned. Otherwise, the file is created as specified. 
 143     Note that the only property used in the determination of "same or different" 
 144     is the content property. If the content is the same, but something else is 
 145     different, the new values in the other properties are ignored when the
 146     existing ID is returned.
 147     
 148     Note that this only affects the creation of new files. After a file is 
 149     created, you can set it's content property to the same value as some other
 150     file.
 151     '''
 152     def create(self, **propvalues):
 153         '''Intercepts file creation requests and creates only those files 
 154         which do not already exist in the database. If the file already 
 155         exists, the existing ID is returned.
 156         
 157         Note that due to the use of old-style classes in Roundup, I had to 
 158         hard-code the deferred create() method to the Md5FileClass class.
 159         '''
 160         if 'content' in propvalues : 
 161             sh = hashlib.md5()
 162             sh.update(propvalues['content'])
 163             hash = sh.hexdigest()
 164             myclass = self.db.getclass(self.classname)
 165             existing_id = myclass.filter(None, {'md5hash':hash})
 166             if existing_id and len(existing_id) > 0 : 
 167                 return existing_id[0]
 168         return Md5FileClass.create(self,**propvalues)
 169 #SHA: 615b532ce82cb1a3f8893a1153cbda3aeb82f68d

Attached Files

To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.
  • [get | view] (2018-06-01 23:50:17, 6.8 KB) [[attachment:interceptor.py]]
 All files | Selected Files: delete move to page copy to page

You are not allowed to attach a file to this page.