Welcome, guest | Sign In | My Account | Store | Cart

This demo shows how you can create and manage your own custom plugins for extending functionality in your Python projects. There are no safety wrappers in this demo for restricting plugins aside from that fact that plugins are run as an extention of a management class which is run in its own instance only receiving data passed to it by the RumModules method, that said security should ideally be applied to the ModuleAPI class, by restricting __builtins__ on eval calls and/or validating plugin content and parameters which can be done by extending the Prepaser Class. For a great recipe/demo of restricting eval call see http://code.activestate.com/recipes/496746.

That aside it was alot of fun to write and use. Enjoy

PS: you will need http://code.activestate.com/recipes/577144/ to import Xceptions

Python, 472 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
import sys, thread
from array import array
from Xceptions import XceptionHandler


"""
PluginManager

Author: AJ Mayorga
Date:   5/5/2010

** Here Module and Plugin are used interchangably to refer to custom
   code provided during runtime of a project to provide additional 
   functionality. Not the import of a proper Python module **


A Demo And Framework For Creating Plugins/Modules For Python Projects. 
Providing For:

    - Modules contain there own pragma section for config vars

    - Pragma sections are read and validated by Preparser before evaluating/executing
          - Checks for necessary config vars
          - Checks module dependencies

    - Modules can be run inline or inside of their own thread

    - Provides for module garbage collection
 

"""

#Some Constants For Use with Xceptions
EXC_RETURN = -1 # Report Exception and return ie continue on
EXC_RAW    = -2 # Report on and return a traceback dump as dict() then continue
EXC_RAISE  = -3 # Report on & raise the exception do not continue 


########################################################
# Thanks to Daniel Brodie for the how to on Extending Classes
# http://code.activestate.com/recipes/412717

def get_obj(name): return eval(name)

class ExtendInplace(type):
    def __new__(self, name, bases, dict):
        prevclass = get_obj(name)
      
        del dict['__module__']
        del dict['__metaclass__']

       
        for k,v in dict.iteritems():
            setattr(prevclass, k, v)
        return prevclass

########################################################

"""
Exception Class For Our Preparser
we'll use this when we want to raise on a
logic failure

"""
class ModuleParserException(Exception):
       def __init__(self, value):
           self.parameter = value
       def __str__(self):
           return repr(self.parameter)


"""
Reading in the Pragma Section from the Module get config parameters
Ensure Module has needed parameters and check module dependencies


"""
class Preparser(XceptionHandler):
    def __init__(self):
        self.name        = str(self.__class__).split(".")[1]
        XceptionHandler.__init__(self, DEBUG=False)
        
        #Modules Are Required To Have These Config Parameters
        self.params = ['_NAME_','_AUTHOR_','_DATE_','_DEPENDS_','_ON_EXC_','_RUN_']
    
    
    def Parser_Xception(self, *args):
        return self.ProcessReturn(self.name, args)
    
    
    def DependencyCheck(self, Modules):
        try:
            depends     =  list()
            ModuleNames =  [Module['_NAME_'] for Module in Modules]
          
            for Module in Modules:
                count = 0
                for d in Module['_DEPENDS_']:
                    if not d in ModuleNames:
                        msg  = "MODULE: "+Module['_NAME_']+" Failed Dependency Check"
                        msg += " Dependency "+d+" Could Not Be Found"
                        raise ModuleParserException(msg)
        except:
            self.Parser_Xception(EXC_RAISE)
        
        
    def LoadModules(self, Modules):
        ModulesOut = list()
        try:
            for Module in Modules:
                Module = self.ProcessModule(Module)
                ModulesOut.append(Module)
                
            self.DependencyCheck(ModulesOut)
            
            for Module in ModulesOut:
                yield Module
            
        except:
            self.Parser_Xception(EXC_RAISE)
        
        
    def ProcessModule(self, Module):
        try:
            ModuleOut   = dict()
            StartTag    = "PRAGMA_START"
            EndTag      = "PRAGMA_END"
            
            pragma_section = Module[Module.find(StartTag)+len(StartTag):Module.rfind(EndTag)-1].strip()
            args = pragma_section.split('\n')
            for x in args:
                key, value = x.split('=')
               
                key   = key.replace(" ", "")
                key   = key.replace("self.", "")
                value = value.replace(" ", "")
                
                ModuleOut[key] = eval(value)
            
            ModuleOut['CODE'] = Module
            moduleName        = ModuleOut.get('_NAME_', "Unknown")
            for param in self.params:
                if not ModuleOut.has_key(param):
                    raise ModuleParserException("MODULE: "+moduleName+" PRAGMA SECTION IS MISSING: "+param)
                    
            return ModuleOut
           
        except:
            self.Parser_Xception(EXC_RAISE)
          

"""
Plugin/Module Manager Class, All modules will run as an extention of this class

"""        
class ModuleAPI(XceptionHandler, Preparser):
    def __init__(self):
        self.name        = str(self.__class__).split(".")[1]
        XceptionHandler.__init__(self, DEBUG=False)
        Preparser.__init__(self)
        
        """
        These vars the the only intended vars meant to survive module runs
        other objects created by the module will be cleaned up after run

        """
        self.ModuleARGS             = None
        self.ModuleName             = ""
        self.ModuleDataStor         = None
        self.ModuleReturn           = None
        self.ModuleXceptionHandler  = XceptionHandler(DEBUG=True)
        self.ModuleOnException      = ""
        
        self.OrigObjects            = None
        
     
    def ModuleAPI_Xception(self, *args):
        return self.ProcessReturn(self.name, args)
    
    
    def ModuleXception(self, *args):
        return self.ModuleXceptionHandler.ProcessReturn(self.ModuleName, args)

       
    """
    Really this is a place holder, just printing, but can be called from within the module
    to relay messages to DB,Server,Parent Process etc.

    """    
    def Callback(self, Message):
        print Message

        
    """
    Here we check to see what objects have been created during our module run
    and delete them garbage collection of sorts.
    This helps with object naming issues between modules, conserving resources etc.

    """        
    def CleanUp(self):
        cleanup = list()
                   
        for k, v in self.__dict__.iteritems():
            if  not self.OrigObjects.has_key(k):
                cleanup.append(k)
                        
        for old in cleanup:
            del self.__dict__[old]
            
            
    """
    Where we bring it all together

    """
    def RunModules(self, Modules, Args):
        ret = True
        self.OrigObjects   = self.__dict__.copy()
        
        for Module in self.LoadModules(Modules):
            try:        
                self.ModuleName        = Module['_NAME_']
                self.ModuleOnException = Module['_ON_EXC_']
                self.ModuleARGS        = Args
                
                print "STARTING NEW MODULE RUN:  ",self.ModuleName,"\n"
                
                eval(compile(Module['CODE'], sys.argv[0], "exec"))
                
                if   Module['_RUN_'] == 'INLINE':
                     self.Run()
                    
                elif Module['_RUN_'] == 'THREAD':
                     self.lock=thread.allocate_lock()
                     self.lock.acquire()
                     thread.start_new_thread ( self.Run, () )
                     self.lock.acquire()
                    
                self.Callback(self.ModuleName+" Output:"+self.ModuleDataStor.tostring()+"\n")
                
            except:
                self.Callback(self.ModuleAPI_Xception(self.ModuleOnException))
                ret = False
                break
            finally: 
                self.ModuleARGS = None
                self.CleanUp()
              
                
                print "ENDING MODULE RUN:  ",self.ModuleName,"\n\n"
                 
        return ret
    




Module1 = """

class ModuleAPI:

    __metaclass__   = ExtendInplace
    
    def Init(self):
        try:
            __name__        = self.ModuleName
          
            #PRAGMA_START

            self._NAME_           = 'VModule_Test1'
            self._AUTHOR_         = 'AJ Mayorga'
            self._DATE_           = 'May 1, 2010'
            self._DESCRIPTION_    = 'Test Plugin'
            self._DEPENDS_        = ()
            self._ON_EXC_         =  EXC_RETURN
            self._RUN_            = 'INLINE'

            #PRAGMA_END
            
            if self.ModuleDataStor:
                self.DataBuffer   = self.ModuleDataStor
            else:
                self.DataBuffer   = self.ModuleARGS['DataBuffer']
                
            self.Return           = False

        except:
            self.ModuleXception(self._ON_EXC_)
   
   
    def Run(self):
        try:
            self.Init()
            self.Callback(self.ModuleName+" Input: "+self.DataBuffer.tostring())
            self.ModData()
            self.ModuleDataStor  = self.DataBuffer
            self.Return = True
            
        except:
            self.ModuleDataStor = self.ModuleARGS['DataBuffer']
            self.ModuleXception(self._ON_EXC_)
            
        finally:
            self.ModuleReturn = self.Return
            if self._RUN_ == 'THREAD':
                print self.ModuleName,"Exiting From Thread: ",thread.get_ident()
                lock.release()
         
           
    def ModData(self):
        try:
            import string
            temp = self.DataBuffer.tostring()
            temp = string.replace(temp, "SCARY", "HAPPY")
            self.DataBuffer = array('B', temp)
            
        except:
            return self.ModuleXception(self._ON_EXC_)
    
"""


       
Module2 = """

class ModuleAPI:
    
    __metaclass__   = ExtendInplace
    
    def Init(self):
        try:
            __name__        = self.ModuleName
                   
            #PRAGMA_START

            self._NAME_           = 'VModule_Test2'
            self._AUTHOR_         = 'AJ Mayorga'
            self._DATE_           = 'May 1, 2010'
            self._DESCRIPTION_    = 'Test Plugin'
            self._DEPENDS_        = ('VModule_Test1', 'VModule_Test3')
            self._ON_EXC_         =  EXC_RETURN
            self._RUN_            = 'THREAD'

            #PRAGMA_END
             
            if self.ModuleDataStor:
                self.DataBuffer   = self.ModuleDataStor
            else:
                self.DataBuffer   = self.ModuleARGS['DataBuffer'] 

            self.Return           = False
            
        except:
            self.ModuleXception(self._ON_EXC_)
   
   
    def Run(self):
        try:
            self.Init()
            self.Callback(self.ModuleName+" Input: "+self.DataBuffer.tostring())
            self.ModData()
            self.ModuleDataStor = self.DataBuffer
            self.Return = True
            
        except:
            self.ModuleDataStor = self.ModuleARGS['DataBuffer']
            self.ModuleXception(self._ON_EXC_)
            
        finally:
            self.ModuleReturn = self.Return
            if self._RUN_ == 'THREAD':
                print self.ModuleName,"Exiting From Thread: ",thread.get_ident()
                self.lock.release()
       
           
    def ModData(self):
        try:
            import string
            temp = self.DataBuffer.tostring()
            temp = string.replace(temp, "RECTAL", "LITTLE")
            self.DataBuffer = array('B', temp)
        except:
            return self.ModuleXception(self._ON_EXC_)

"""


       
Module3 = """

class ModuleAPI:
    
    __metaclass__   = ExtendInplace
    
    def Init(self):
        try:
            __name__        = self.ModuleName
                   
            #PRAGMA_START

            self._NAME_           = 'VModule_Test3'
            self._AUTHOR_         = 'AJ Mayorga'
            self._DATE_           = 'May 1, 2010'
            self._DESCRIPTION_    = 'Test Plugin'
            self._DEPENDS_        = ('VModule_Test1', 'VModule_Test2')
            self._ON_EXC_         =  EXC_RETURN
            self._RUN_            = 'INLINE'

            #PRAGMA_END
           
            if self.ModuleDataStor:
                self.DataBuffer   = self.ModuleDataStor
            else:
                self.DataBuffer   =  self.ModuleARGS['DataBuffer'] 

            self.Return           = False
            
        except:
            self.ModuleXception(EXC_RETURN)
   
   
    def Run(self):
        try:
            self.Init()
            self.Callback(self.ModuleName+" Input: "+self.DataBuffer.tostring())
            self.ModData()
            self.ModuleDataStor = self.DataBuffer
            self.Return = True
            
        except:
            self.ModuleDataStor = self.ModuleARGS['DataBuffer']
            self.ModuleXception(self._ON_EXC_)
            
        finally:
            self.ModuleReturn = self.Return
            if self._RUN_ == 'THREAD':
                print self.ModuleName," Exiting From Thread: ",thread.get_ident()
                lock.release()
          
    def ModData(self):
        try:
            import string
            temp = self.DataBuffer.tostring()
            temp = string.replace(temp, "THERMOMETERS", "TREES")
            self.DataBuffer = array('B', temp)
        except:
            return self.ModuleXception(EXC_RAISE)
    
"""

    
    
if __name__ == '__main__':       
               
    Modules                = [Module1, Module2, Module3]
    ARGS                   = dict()
    ARGS['DataBuffer']     = array('B', "SCARY RECTAL THERMOMETERS")
    
    
    ModAPI = ModuleAPI()
    
    #Call One
    print "Running One Module"
    ModAPI.RunModules([Module1], ARGS)
    print "Single Module Output: ", ModAPI.ModuleDataStor.tostring()
    
    print "\n\n"
    
    #Call Them All
    print "Running All Modules"
    ModAPI.RunModules(Modules, ARGS)
    
    print "Module Group Output:  ",ModAPI.ModuleDataStor.tostring()