Changeset 199

Show
Ignore:
Timestamp:
Tue Mar 7 16:39:05 2006
Author:
drew
Message:

- added Curried class
- updated unit tests

Files:

Legend:

Unmodified
Added
Removed
Modified
  • trunk/tests/doctests/utils/python.py

    r159 r199  
    1   from xix.utils.python import ModuleWrapper  
      1 ##from xix.utils.python import ModuleWrapper  
      2 #  
    2 3  
    3   class ModuleWrapperTest:  
    4       def importAll(self):  
    5           '''Example Usage:  
      4 class DummyTest:  
      5     """  
      6     >>> print 1  
      7     1  
      8     """  
    6 9  
    7           >>> "uppercase" not in locals()  
    8           True  
    9           >>> wrapper = ModuleWrapper("string")  
    10           >>> wrapper.importAll()  
    11           >>> print uppercase  
    12           ABCDEFGHIJKLMNOPQRSTUVWXYZ  
    13           '''  
    14       def importModule(self):  
    15           '''Example Usage:  
    16    
    17           >>> "sre" not in locals()  
    18           True  
    19           >>> wrapper = ModuleWrapper("sre")  
    20           >>> wrapper.importModule()  
    21           >>> type(sre)  
    22           <type 'module'>  
    23           '''  
    24       def importNames(self, *names):  
    25           '''Example Usage:  
    26    
    27           >>> "uppercase" not in locals() and "upper" not in locals()  
    28           True  
    29           >>> wrapper = ModuleWrapper("string")  
    30           >>> wrapper.importNames("uppercase", "upper")  
    31           >>> print uppercase  
    32           ABCDEFGHIJKLMNOPQRSTUVWXYZ  
    33           >>> print upper("lower")  
    34           LOWER  
    35           '''  
      10 #class ModuleWrapperTest:  
      11 #    def importAll(self):  
      12 #        '''Example Usage:  
      13 #  
      14 #        >>> "uppercase" not in locals()  
      15 #        True  
      16 #        >>> wrapper = ModuleWrapper("string")  
      17 #        >>> wrapper.importAll()  
      18 #        >>> print uppercase  
      19 #        ABCDEFGHIJKLMNOPQRSTUVWXYZ  
      20 #        '''  
      21 #    def importModule(self):  
      22 #        '''Example Usage:  
      23 #  
      24 #        >>> "sre" not in locals()  
      25 #        True  
      26 #        >>> wrapper = ModuleWrapper("sre")  
      27 #        >>> wrapper.importModule()  
      28 #        >>> type(sre)  
      29 #        <type 'module'>  
      30 #        '''  
      31 #    def importNames(self, *names):  
      32 #        '''Example Usage:  
      33 #  
      34 #        >>> "uppercase" not in locals() and "upper" not in locals()  
      35 #        True  
      36 #        >>> wrapper = ModuleWrapper("string")  
      37 #        >>> wrapper.importNames("uppercase", "upper")  
      38 #        >>> print uppercase  
      39 #        ABCDEFGHIJKLMNOPQRSTUVWXYZ  
      40 #        >>> print upper("lower")  
      41 #        LOWER  
      42 #        '''  
  • trunk/tests/doctests/utils/config.py

    r159 r199  
    7 7  
    8 8 from xix.utils.config import Config, ConfigLoader, ConfigFactory, configFactory  
    9   from xix.utils.config import ModuleWrapper  
      9 #from xix.utils.config import ModuleWrapper  
    9 9  
    10 10 # pydoc unit tests  
     
    14 14     def __init__(self, data):  
    15 15         self.data = data  
      16         self.lines = [(line + '\n') for line in data.split('\n')]  
    16 17     def read(self):  
    17 18         return self.data  
      19     def readline(self):  
      20         if self.lines:  
      21             next = self.lines.pop(0)  
      22             return next  
    18 23  
    19 24 class ConfigLoaderTest:  
     
    99 104         Traceback (most recent call last):  
    100 105         ...  
    101           ConfigLoaderException: Internal exception while trying to convert not good dude -- Cannot resolve type : not good dude.  
    102           """  
    103       def example_someTypesNotSupported(self):  
    104           """  
    105           Not all types are supported:  
    106            
    107           >>> fd = File("foo.bar=type")  
    108           >>> loader = ConfigLoader()  
    109           >>> loader.load(fd)  
    110           Traceback (most recent call last):  
    111           ...  
    112           ConfigLoaderException: Unsupported type: <type 'type'>.  
    113           """  
    114       def example_moduleWrapper(self):  
    115           """  
    116           We can also get a module wrapper ...  
    117    
    118           >>> fd = File("foo.bar=xix.utils.string")  
    119           >>> loader = ConfigLoader()  
    120           >>> cfg = loader.load(fd)  
    121           >>> from xix.utils.interfaces import IModuleWrapper  
    122           >>> IModuleWrapper.providedBy(cfg.foo.bar)  
    123           True  
      106         ConfigLoaderException: Exeception occurred evaluating: not good dude  
    124 107         """  
      108 #    def example_someTypesNotSupported(self):  
      109 #        """  
      110 #        Not all types are supported:  
      111 #         
      112 #        >>> fd = File("foo.bar=type")  
      113 #        >>> loader = ConfigLoader()  
      114 #        >>> loader.load(fd)  
      115 #        Traceback (most recent call last):  
      116 #        ...  
      117 #        ConfigLoaderException: Unsupported type: <type 'type'>.  
      118 #        """  
      119 #    def example_moduleWrapper(self):  
      120 #        """  
      121 #        We can also get a module wrapper ...  
      122 #  
      123 #        >>> fd = File("foo.bar=xix.utils.string")  
      124 #        >>> loader = ConfigLoader()  
      125 #        >>> cfg = loader.load(fd)  
      126 #        >>> from xix.utils.interfaces import IModuleWrapper  
      127 #        >>> IModuleWrapper.providedBy(cfg.foo.bar)  
      128 #        True  
      129 #        """  
    125 130  
    126 131  
  • trunk/utiltests.py

    r193 r199  
    1 1 import xix.utils.config  
    2 2 import xix.utils.console  
      3 import xix.utils.decor  
    3 4 import xix.utils.string  
    4 5 import xix.utils.rules  
     
    14 15     xix.utils.config,  
    15 16     xix.utils.console,  
      17     xix.utils.decor,  
    16 18     xix.utils.string,  
    17 19     xix.utils.rules,  
  • trunk/xix/utils/decor.py

    r177 r199  
    5 5 '''  
    6 6  
    7   from xix.utils.python import allexcept  
      7 from xix.utils.python import Curried  
    7 7 import time, inspect  
    8 8  
      9 import sys  
      10  
    9 11 __revision__ = '$Id$'  
    10 12  
     
    14 16 __copyright__ = 'Copyright (C) 2005, Drew Smathers'  
    15 17  
    16   def timedfunction(function):  
    17       '''This decorator time's calls to function and prints  
    18       to standard out.  
    19       '''  
    20       def wrapper(*args, **kwargs):  
    21           before = time.time()  
    22           result = function(*args, **kwargs)  
    23           after = time.time()  
    24           print '%s took %f s to complete' % \  
    25               (function.__name__, after - before)  
    26           return result  
    27       # We want the wrapper (which will replace the wrappee)  
    28       # to be transparent as much as possible.  
    29       wrapper.__dict__.update(function.__dict__)  
    30       wrapper.__doc__ = function.__doc__  
    31       wrapper.__name__ = function.__name__  
      18  
      19 def _wrapup(wrapper, func):  
      20     wrapper.__dict__.update(func.__dict__)  
      21     wrapper.__doc__ = func.__doc__  
      22     wrapper.__name__ = func.__name__  
    32 23     return wrapper  
    33 24  
    34   ##########################################################################  
    35   #       Curryable functions, methods  
    36   ##########################################################################  
    37        
    38   class CannotCurryException(Exception):  
    39       '''  
    40       Raise when trying to build a curryable function with mixed argument  
    41       dimensions (keyword and positional)  
    42       '''  
    43       pass  
    44   # we can build a decorator that makes a function curryable  
    45        
    46   def curryable(function):  
    47       '''My magnus opus of decorators ;)  
    48       This decorator makes the wrapped function curryable - with  
    49       positional or keyword arguments only - no mixing folks.  
    50       '''  
    51       class _CurriedFunction:         
    52           def __init__(self, function, pargs=None, kwargs=None, nargs=None, posonly=False):  
    53               self._function = function  
    54               self._nargs = nargs  
    55               if posonly or pargs:  
    56                   self._curried = pargs or []  
    57                   self._posonly = True  
    58               else:  
    59                   self._curried = kwargs or {}  
    60                   self._posonly = False  
    61               if self._nargs is None:  
    62                   self._nargs = len(inspect.getargspec(self._function)[0]) - \  
    63                       (0,1)[inspect.ismethod(self._function)]  
    64                    
    65           def __call__(self, *pargs, **kwargs):  
    66               if self._posonly and kwargs:  
    67                   raise TypeError, "This curryable instance accepts only positional arguments"  
    68               if not self._posonly and pargs:  
    69                   raise TypeError, "This curryable instance accepts only keyword arguments"  
    70               if (self._posonly):  
    71                   curried = self._curried + list(pargs)  
    72                   if len(curried) == self._nargs:  
    73                       return self._function(*tuple(curried))  
    74                   elif len(curried) > self._nargs:  
    75                       raise TypeError, ("curryable accepts at most %d positional arguments (%d given)" %  
    76                           (self._nargs - len(self._curried), len(pargs)))  
    77                   else:  
    78                       new = _CurriedFunction(self._function, pargs=curried, nargs=self._nargs)  
    79                       return new  
    80               else:  
    81                   curried = dict(self._curried)  
    82                   curried.update(kwargs)  
    83                   if len(curried) == self._nargs:  
    84                       return self._function(**dict(curried))  
    85                   elif len(curried) > self._nargs:  
    86                       raise TypeError, ("curryable accepts at most %d keyword arguments (%d given)" %  
    87                           (self._nargs - len(self._curried), len(pargs)))  
    88                   else:  
    89                       new = _CurriedFunction(self._function, kwargs=curried, nargs=self._nargs)  
    90                       return new  
    91                    
      25    
      26 def curryable(func):  
      27     """curryable wrapper - syntax suger for CurriedCallable.  
      28  
      29     Example Usage:  
      30  
      31     >>> @curryable  
      32     ... def something(a, b, c, d=23, e=32):  
      33     ...    return a, b, c, d, e  
      34     ...  
      35     >>> curried = something()  
      36     >>> curried = something(d=56)  
      37     >>> print curried(1,2,3)  
      38     (1, 2, 3, 56, 32)  
      39     """  
    92 40     def wrapper(*args, **kwargs):  
    93           if args and kwargs:  
    94               raise CannotCurryException, "Curryable functions can contain only positional " +\  
    95                   "or keyword arguments (not both)"  
    96           if args: curried = _CurriedFunction(function, posonly=True)  
    97           else: curried = _CurriedFunction(function, posonly=False)  
      41         curried = Curried(func)  
    98 42         return curried(*args, **kwargs)  
    99       wrapper.__dict__.update(function.__dict__)  
    100       wrapper.__doc__ = function.__doc__  
    101       wrapper.__name__ = function.__name__  
    102       return wrapper  
      43     return _wrapup(wrapper, func)  
    103 44  
    104   #__all__ = allexcept('time', 'inspect', 'allexcept')  
    105 45  
  • trunk/xix/utils/console.py

    r177 r199  
    105 105             return message     
    106 106         code_string = ';'.join([str(code) for code in codes])  
    107           return '\033[%sm%s\033[%sm' % (code_string, message, RESET)  
      107         return '\033[%sm%s\033[%sm' % (code_string, message, RESET)  
      108  
      109 class FormatFactory:  
      110     """Example Usage:  
      111  
      112     >>> factory = FormatFactory()  
      113     >>> factory['a'] = Format()  
      114     >>> factory['b'] = Format()  
      115     >>> factory['a'].format_on = False  
      116     >>> print int(factory['a'].format_on), int(factory['b'].format_on)  
      117     0 1  
      118     """  
      119  
      120     def __init__(self):  
      121         self._reg = {}  
      122  
      123     def __getitem__(self, name):  
      124         return self._reg[name]  
      125  
      126     def __setitem__(self, name, fmt):  
      127         if self._reg.has_key(name):  
      128             raise ValueError, 'Format named %s already registered' % name  
      129         self._reg[name] = fmt  
    108 130  
    109 131 format = Format()  
      132 formatFactory = FormatFactory()  
    110 133      
    111 134 __all__ = setAll([], locals(), 'setAll', 'k', 'v')  
  • trunk/xix/utils/config.py

    r193 r199  
    38 38  
    39 39 1. Cascading configurations  
    40   2. Object loader casting  
    41   3. Textual substitution in configuration:  
    42       ex.  
    43       foo.world="World"  
    44       foo.greeting="Hello " + $foo.world  
    45    
    46       foo.lazy=1 + $foo.eval  
    47       foo.eval=0  
    48 40                
    49 41 '''  
    50 42  
    51 43 from xix.utils.interfaces import IConfig, IConfigLoader, IConfigFactory  
    52   from xix.utils.interfaces import IModuleWrapper  
      44 #from xix.utils.interfaces import IModuleWrapper  
    52 44 from xix.utils.comp.interface import providedBy, implements, classProvides  
    53 45 from xix.utils.python import allexcept  
    54   from xix.utils.python import ModuleWrapper  
      46 #from xix.utils.python import ModuleWrapper  
    54 46 from xix.utils.python import getCallersGlobals  
    55 47 import warnings  
     
    62 54 __version__ = '$Revision$'[11:-2]  
    63 55  
      56 sectionHeader = re.compile('^(\[+)([^\[\]]*)(\]+)\s*$').search  
      57 keyValue = re.compile('^([^=]+)=(.+)$').search  
      58  
    64 59 class ConfigException(Exception):  
    65 60     '''Raised when error directly tied to configuration occurs.  
     
    145 140     pass  
    146 141      
    147        
      142    
      143 ############ Builtin plugins to ConfigLoader##  
    148 144 def _unixpath(path):  
    149 145     return path.replace('/', os.path.sep)  
    150        
      146  
      147 def _replace(name):  
      148     _loader.append((_chain, name))  
      149 ###############################################  
      150  
      151 class _StringFile:  
      152  
      153     def __init__(self, data):  
      154         self.data = data  
      155         self.lines = [(line + '\n') for line in data.split('\n')]  
      156  
      157     def read(self):  
      158         return self.data  
      159  
      160     def readline(self):  
      161         if self.lines:  
      162             return self.lines.pop(0)  
      163         return ''  
      164  
      165 BADLINE = 'Malformed line found in configuration: %s'  
    151 166      
    152 167 class ConfigLoader:  
     
    157 172  
    158 173     plugins = {  
      174         '_chain' : None, # Do not reference me directly  
      175         '_loader' : None, # Do not reference me directly  
    159 176         'path' : _unixpath,  
      177         '_' : _replace,  
    160 178     }  
    161 179      
    162       def load(self, fd):  
      180     def load(self, data):  
    162 180         '''  
    163 181         Example usage:  
    164 182          
    165           >>> class File:  
    166           ...     def read(self):  
    167           ...         return """  
      183         >>> data = """  
    168 184         ... foo.bar=123  
    169 185         ... foo.baz='hello world'  
    170 186         ... foo.foo.nested.pi=3.14  
    171 187         ... """  
    172           ...  
    173           >>> fd = File()  
    174 188         >>> loader = ConfigLoader()  
    175           >>> cfg = loader.load(fd)  
      189         >>> cfg = loader.load(data)  
    175 189         >>> print cfg.foo.bar, cfg.foo.baz, cfg.foo.foo.nested.pi  
    176 190         123 hello world 3.14  
    177 191         '''  
      192         if hasattr(data, 'read'):  
      193             fd = data  
      194         else:  
      195             fd = _StringFile(data)  
    178 196         cfg = Config()  
    179           for line in (fd.read().split('\n')):  
    180               try:  
    181                   if line[0] == "#": continue  
    182                   if line.split() == []: continue  
    183               except: continue  
      197         self.replacement = []  
      198         self.plugins['_loader'] = self  
      199         chainPrefixStack = []  
      200         line = fd.readline()  
      201         prev = ''  
      202         while line:  
      203             line = line[:-1]  
    184 204             try:  
    185                   cfg_chain = line.split("=")[0]  
    186                   cfg_val   = line.split("=")[1]  
    187                   cfg_chain = cfg_chain.split(".")  
    188                   assert len(cfg_chain) > 0  
      205                 if line[0] == "#":  
      206                     line = fd.readline()  
      207                     continue  
      208                 if line.split() == []:  
      209                     line = fd.readline()  
      210                     continue  
    189 211             except:  
    190                   raise ConfigLoaderException("Malformed line found in configuration: %s" % line)  
    191               try:  
    192                   cast = self._caster(cfg_val)  
    193                   cfg_val = cast(cfg_val)  
    194               except ConfigLoaderException, cle:  
    195                   raise ConfigLoaderException(str(cle))  
    196               except Exception, e:  
    197                   raise ConfigLoaderException("Exeception occurred in loading: " + str(e))  
      212                 line = fd.readline()  
      213                 continue  
      214             if line[-1] == '\\': # continue scanning next line  
      215                 prev = prev + line[:-1]  
      216                 line = fd.readline()  
      217                 continue  
      218             else: # finish scanning mult lines, or single line  
      219                 line = prev + line  
      220                 prev = ''  
      221             if keyValue(line):  
      222                 cfg_chain, cfg_val = self._handleAssignment(line, keyValue(line), _flat(chainPrefixStack))  
      223             elif sectionHeader(line):  
      224                 cfg_chain, cfg_val = self._handleSection(line, sectionHeader(line), chainPrefixStack)  
      225             else:  
      226                 raise ConfigLoaderException, BADLINE % line  
    198 227             for node in cfg_chain:  
    199 228                 if not _isvalidname(node):  
     
    203 232                             "Config node %s does not map to valid Python name" % node);  
    204 233             self._updateConfig(cfg, cfg_chain, cfg_val)  
      234             line = fd.readline()  
    205 235         return cfg  
    206            
      236  
      237     def _handleAssignment(self, line, reSearchResults, chainPrefix=[]):  
      238         lhs = reSearchResults.groups()[0].strip()  
      239         cfg_chain = chainPrefix + lhs.split('.')  
      240         cfg_val = reSearchResults.groups()[1].strip()  
      241         self._chain = cfg_chain  
      242         if len(cfg_chain) == 0:  
      243             raise ConfigLoaderException, BADLINE % line  
      244         try:  
      245             cfg_val = cast(self, cfg_val)  
      246         except ConfigLoaderException, cle:  
      247             raise ConfigLoaderException(str(cle))  
      248         except Exception, e:  
      249             raise ConfigLoaderException("Exeception occurred evaluating: " + cfg_val)  
      250         return cfg_chain, cfg_val  
      251  
      252     def _handleSection(self, line, reSearchResults, stack):  
      253         head, section, tail = reSearchResults.groups()  
      254         if len(head) != len(tail):  
      255             raise ConfigLoaderException, BADLINE % line  
      256         if len(head) > len(stack) + 1:  
      257             raise ConfigLoaderException, 'Subsection does not have parent: %s' % line  
      258         while len(stack) > len(head) - 1:  
      259             stack.pop()     
      260         cfg_chain = section.split('.')  
      261         stack.append(cfg_chain)  
      262         return cfg_chain, None  
    207 263      
    208       def _updateConfig(self, cfg, cfg_chain, cfg_value):  
      264     def _updateConfig(self, cfg, cfg_chain, cfg_value=None):  
    208 264         chain = list(cfg_chain)  
    209 265         name = '.'.join(cfg_chain)  
     
    219 275             if bound:  
    220 276                 if chain and IConfig.providedBy(attr):  
    221                       cfg = getattr(cfg, node)  
    222                   else:  
      277                     cfg = attr  
      278                 elif not cfg_value is None:  
      279                     # TODO - decide if we really want raise exception or allowing  
      280                     # redefinition to make overlays easier.  
    223 281                     raise ConfigLoaderException("Duplicate entry %s" % name)  
    224 282             else:  
    225                  if chain:  
      283                if chain or cfg_value is None: # TODO unit test for None case  
    225 283                     child = Config()                     
    226 284                     setattr(cfg, node, child)  
     
    229 287                else:  
    230 288                     setattr(cfg, node, cfg_value)  
    231    
    232 289      
    233       def _caster(self, s):  
    234           namespace = {}  
    235           try:  
    236               try:  
    237                   # TODO - This is extremely unsafe - need an rexec  
    238                   exec("tipe = type(%s)" % s, namespace, namespace)  
    239                   tipe = namespace["tipe"]  
    240                   if tipe == int or tipe == long:  
    241                       return _IntegerCaster()  
    242                   if tipe == float:  
    243                       return _FloatCaster()  
    244                   if tipe == str:  
    245                       return _StringCaster()  
    246                   if tipe == bool:  
    247                       return _BooleanCaster()  
    248                   if tipe == list:  
    249                       return _ListCaster()  
    250                   if tipe == tuple:  
    251                       return _TupleCaster()  
    252                   if tipe == dict:  
    253                       return _DictionaryCaster()  
    254               except:  
    255                   if _modulename(s): # This will go away in future versions.  
    256                       return _ModuleWrapperCaster()  
    257                   if _plugin(s):  
    258                       return _PluginCaster()  
    259                   raise ConfigLoaderException("Cannot resolve type : %s." % s)  
    260           except Exception, e:  
    261               raise ConfigLoaderException(\  
    262                       'Internal exception while trying to convert %s -- %s' % (s, e))  
    263           raise ConfigLoaderException('Unsupported type: %s.' % type(tipe))  
    264    
    265 290 class ConfigFactory:  
    266 291     '''Factory for managing multiple named configurations.  
     
    297 322         if config: self.loaded[name] = config  
    298 323  
    299    
    300 324 configFactory = ConfigFactory()  
    301 325  
    302    
    303    
    304 326 ###############################################################################  
    305 327 ## -- Private utility functions  
     
    315 337     return True  
    316 338  
    317   def _modulename(name):  
    318       modname_pattern = re.compile(r'^([a-zA-Z]\w*(\.[a-zA-Z]\w*)?)+$')  
    319       return modname_pattern.match(name) is not None  
      339 #def _modulename(name):  
      340 #    modname_pattern = re.compile(r'^([a-zA-Z]\w*(\.[a-zA-Z]\w*)?)+$')  
      341 #    return modname_pattern.match(name) is not None  
    320 342  
    321 343 def _plugin(strng):  
     
    323 345     return plugin_patt.match(strng) is not None  
    324 346  
    325   ## -- end Private utility functions  
    326   ###############################################################################  
      347 def _flat(seq):  
      348     flat = []  
      349     for inner in seq:  
      350         flat = flat + inner  
      351     return flat  
      352  
      353 def cast(loader, val):  
      354     ns = dict(loader.plugins)  
      355     ns['__val__'] = None  
      356     exec('__val__=%s' % val, ns, ns)  
      357     return ns['__val__']  
    327 358  
      359 ## -- end Private utility functions  
    328 360 ###############################################################################  
    329   # -------- INTERNAL ----------  Loaders for various types ---------------------  
    330   ###############################################################################  
    331    
    332    
    333   class _Caster:  
    334       def _exec(self, val):  
    335           ns = {}  
    336           exec("val=%s" % val, ns, ns)  
    337           return ns["val"]  
    338       def __call__(self, val):  
    339           return  self._exec(val)  
    340        
    341   class _IntegerCaster(_Caster):  
    342       def __call__(self, val):  
    343           return int(val)  
    344    
    345   class _FloatCaster(_Caster):  
    346       def __call__(self, val):  
    347           return float(val)  
    348    
    349   class _StringCaster(_Caster):  
    350       def __call__(self, val):  
    351           return str(val)[1:-1] # get rid of quotes  
    352    
    353   class _BooleanCaster(_Caster):  
    354       def __call__(self, val):  
    355           return "True" == val  
    356            
    357   class _ListCaster(_Caster):  
    358       pass  
    359        
    360   class _TupleCaster(_Caster):  
    361       pass  
    362    
    363   class _DictionaryCaster(_Caster):  
    364       pass  
    365    
    366   class _ModuleWrapperCaster(_Caster):  
    367       def __call__(self, val):  
    368           return ModuleWrapper(val)  
    369    
    370   class _PluginCaster(_Caster):  
    371       def __call__(self, val):  
    372           ns = dict(ConfigLoader.plugins)  
    373           ns['__val__'] = None  
    374           exec('__val__=%s' % val, ns, ns)  
    375           return ns['__val__']  
    376 361  
    377   #__all__ = allexcept("IConfig", "IConfigLoader", "IConfigFactory",  
    378   #        "providedBy", "implements", "allexcept", "ModuleWrapper")  
  • trunk/xix/utils/python.py

    r159 r199  
    19 19 from interfaces import IModuleWrapper  
    20 20 from comp.interface import implements  
      21 import inspect  
    21 22 import warnings  
      23 import sys &nb