Changeset 1084


Ignore:
Timestamp:
02/02/09 16:49:49 (5 years ago)
Author:
wehart
Message:

A major rework of how the pyutilib.plugin package coordinates with
plugins. The framework now supports environment namespaces, which can be
used to key configuration options.

Further, the Python 'logging' package is now used to support logging of
the pyutili.plugin package (and associated packages).

All of this functionality may not work as advertised, but I'm committing this
now because all of the existing tests pass. Next, I'll work on additional tests
that exercise this new functionality.

Location:
trunk/pyutilib
Files:
3 added
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/pyutilib/__init__.py

    r1079 r1084  
    3333import plugin 
    3434import options 
     35from application import * 
  • trunk/pyutilib/misc/misc.py

    r1048 r1084  
    322322   return None 
    323323 
     324 
     325 
     326class MonoState(object): 
     327    """A class where every instance shares state.""" 
     328    _state = {} 
     329    def __new__(cls, *p, **k): 
     330        self = object.__new__(cls, *p, **k) 
     331        self.__dict__ = cls._state 
     332        return self 
     333 
     334class Singleton(object): 
     335    """A class where the __new__() method returns the same instance.""" 
     336    def __new__(cls, *p, **k): 
     337        if not '_the_instance' in cls.__dict__: 
     338            cls._the_instance = object.__new__(cls) 
     339        return cls._the_instance 
     340 
  • trunk/pyutilib/options/__init__.py

    r1081 r1084  
    2020""" 
    2121 
     22# 
     23# Core pyutilib.options components 
     24# 
    2225from options import * 
    2326from configuration import * 
     27from logging_config import * 
     28from env_config import * 
     29# 
     30# Optional Plugins 
     31# 
    2432import plugin_ConfigParser 
  • trunk/pyutilib/options/configuration.py

    r1081 r1084  
    6767        self.option_plugins = ExtensionPoint(IOption, env) 
    6868        self.option_data_plugin = ExtensionPoint(IOptionDataProvider, env) 
    69         self.pathoption_plugins = ExtensionPoint(IPathOption, env) 
     69        self.pathoption_plugins = ExtensionPoint(IFileOption, env) 
     70        self.postconfig_actions = ExtensionPoint(IPostConfigurationAction, env) 
    7071        self.clear() 
    7172 
     
    101102            raise ConfigurationError, "Cannot load without a filename" 
    102103        for option in self.pathoption_plugins: 
    103             option.set_path(os.path.dirname(filename)) 
     104            option.set_dir(os.path.dirname(filename)) 
    104105        # 
    105106        # By default, we simply take the first parser 
     
    134135                if not flag: 
    135136                    raise ConfigurationError, "Problem loading file %r. Option %r in section %r is not recognized!" % (self.filename, option,sec) 
     137        # 
     138        # Finalize the configuration process 
     139        # 
     140        for plugin in self.postconfig_actions: 
     141            plugin.finalize() 
    136142             
    137143         
  • trunk/pyutilib/options/options.py

    r1081 r1084  
    2626 
    2727import re 
     28import os 
    2829import os.path 
    2930from pyutilib.plugin import * 
     
    5253 
    5354 
    54 class IPathOption(Interface): 
    55     """An interface that supports the initialization of the path for  
    56     options that specify paths.  This is needed to correctly initialize 
    57     relative path options.""" 
    58  
    59     def set_path(self,path): 
    60         """Sets the path of the configuration data file.""" 
     55class IPostConfigurationAction(Interface): 
     56    """Define actions that are applied after configuration data has been 
     57    loaded.""" 
     58 
     59    def finalize(self): 
     60        """Perform post-configuration operations that use the option data.""" 
     61 
     62 
     63class IFileOption(Interface): 
     64    """An interface that supports the initialization of the directory for  
     65    options that specify files.  This is needed to correctly initialize 
     66    relative paths for files.""" 
     67 
     68    def set_dir(self,path): 
     69        """Sets the directory of the configuration data file.""" 
    6170 
    6271 
     
    187196    """ 
    188197 
    189     def __init__(self, name, **kwds): 
     198    def __init__(self, name=None, **kwds): 
    190199        """Create the extension point. 
    191200 
     
    203212        self.__doc__ = "" 
    204213        for (k,v) in kwds.items(): 
    205             if k == "section": 
    206                 self.section = v 
    207             elif k == "section_re": 
    208                 self.section_re = v 
    209                 self.section_p = re.compile(v) 
    210             elif k == "default": 
    211                 self.default = v 
    212             elif k == "doc": 
    213                 self.__doc__ = v 
    214             else: 
    215                 raise OptionError, "Unknown keyword: "+k 
     214            self._parse_option(k,v) 
    216215        if name is None: 
    217216            raise OptionError, "The Option class requires a name" 
    218217        self.set_value(self.default) 
     218 
     219    def _parse_option(self, k, v): 
     220        """Parse option""" 
     221        if k == "section": 
     222                self.section = v 
     223        elif k == "section_re": 
     224                self.section_re = v 
     225                self.section_p = re.compile(v) 
     226        elif k == "default": 
     227                self.default = v 
     228        elif k == "name": 
     229                self.name = v 
     230        elif k == "doc": 
     231                self.__doc__ = v 
     232        else: 
     233                raise OptionError, "Unknown keyword: "+k 
    219234 
    220235    def __get__(self, instance, owner): 
     
    342357 
    343358 
    344 class PathOption(Option,Plugin): 
     359class FileOption(Option,Plugin): 
    345360    """A class that converts option data into a path.  Relative paths are 
    346361    converted using the path for the configuration file.""" 
    347362 
    348     implements(IPathOption) 
     363    implements(IFileOption) 
     364    implements(IPostConfigurationAction) 
    349365 
    350366    def __init__(self, name, **kwds): 
    351         """Constructor.  By default, the current working directory is the 
    352         path used in this data.""" 
     367        """ 
     368        Constructor.  By default, the current working directory is the 
     369        path used in this data. 
     370        """ 
     371        self.dir = None 
    353372        Option.__init__(self,name,**kwds) 
    354         self.path=os.getcwd() 
     373 
     374    def _parse_option(self, k, v): 
     375        """Parse options that are specific to the FileOption class""" 
     376        if k == "directory": 
     377            self.dir = v 
     378        else: 
     379            Option._parse_option(self, k, v) 
    355380 
    356381    def convert(self, value, default): 
     
    360385            return os.path.normcase(os.path.realpath(default)) 
    361386        if not os.path.isabs(val): 
    362             val = os.path.join(self.path, val) 
     387            val = os.path.join(self.dir, val) 
    363388            return os.path.normcase(os.path.realpath(val)) 
    364389        else: 
    365390            return os.path.normcase(os.path.realpath(val)) 
    366391 
    367     def set_path(self,path): 
     392    def set_dir(self,path): 
    368393        """Sets the path of the configuration data file.""" 
    369         self.path = path 
    370  
     394        self.dir = path 
     395 
     396    def finalize(self): 
     397        if self.dir is None: 
     398            raise OptionError, "FileOption must have a directory specified." 
     399         
     400 
  • trunk/pyutilib/options/plugin_ConfigParser.py

    r1081 r1084  
    2222 
    2323 
    24 class ConfigParser_Plugin(SingletonPlugin): 
     24class Configuration_ConfigParser(SingletonPlugin): 
    2525    """A configuration parser that uses the ConfigParser package.""" 
    2626 
  • trunk/pyutilib/plugin/core.py

    r1083 r1084  
    3939""" 
    4040 
    41 import types 
    4241import re 
     42import logging 
    4343 
    4444__all__ = ['Plugin', 'SingletonPlugin', 'Plugins',  
     
    4646           'PluginError', 'PluginsEnvironment', 'IPluginLoader', 
    4747           'IPluginLoadPath'] 
     48 
     49 
     50# 
     51# Define the default logging behavior for a given namespace, which is to 
     52# ignore the log messages. 
     53# 
     54def logger_factory(namespace): 
     55    log = logging.getLogger(namespace) 
     56    class NullHandler(logging.Handler): 
     57        def emit(self, record): 
     58            pass 
     59    log.addHandler(NullHandler()) 
     60    return log 
    4861 
    4962 
     
    139152 
    140153* name - A string that identifies this environment.  By default a unique integer id is used to define the name "env.#" 
     154# namespace - A name the defines the relationship of this environment to other environments 
    141155* registry - A map from interfaces to registered services that match each interface 
    142156* services - The set of all services (Plugin instances) that have been registered in this environment 
    143157* singleton_services - Singleton services, which can only be registered once in each environment 
    144158* enabled - A cache that denotes whether a service has been enabled. 
     159 
     160The namespace of Environment instances is dynamically generated by extending the namespace of the current environment.  However, the environment namespace can be explicitly declared in the constructor. 
    145161""" 
    146162class PluginsEnvironment(object): 
    147163 
    148     def __init__(self, name=None): 
     164    def __init__(self, name=None, namespace=None): 
    149165        if name is None: 
    150             self.name = "env."+str(Plugins.next_id()) 
     166            self.name = "env"+str(Plugins.next_id()) 
    151167        else: 
    152168            self.name = name 
     169        if namespace is None: 
     170            tmp = Plugins.env().namespace 
     171            if tmp != "": 
     172                tmp += "." 
     173            self.namespace = tmp+self.name 
     174        else: 
     175            self.namespace = namespace 
     176        if self.namespace in Plugins.env_registry: 
     177            raise PluginError, "The Environment namespace %r already exists!" % self.namespace 
     178        Plugins.env_registry[self.namespace] = self 
    153179        self.enabled = {} 
    154180        self.singleton_services={} 
     
    157183        self.loaders = ExtensionPoint(IPluginLoader, self) 
    158184        self.loader_paths = ExtensionPoint(IPluginLoadPath, self) 
     185        self.log = logger_factory(namespace) 
     186 
     187    def __del__(self): 
     188        del Plugins.env_registry[self.namespace] 
    159189 
    160190    def __contains__(self, cls): 
     
    242272        raise PluginError, "The Plugins class should not be created." 
    243273 
     274    """The registry of environments, by namespace""" 
     275    env_registry = {} 
     276 
    244277    """The stack of environments that is being used.""" 
    245278    env_stack = [] 
     
    254287        new default environment 
    255288        """ 
     289        Plugins.env_registry = {} 
    256290        Plugins.env_stack=[] 
    257291        Plugins.id_counter=0 
    258         Plugins.push_env( PluginsEnvironment("default") ) 
     292        Plugins.push_env( PluginsEnvironment(name="<default>",namespace="") ) 
    259293 
    260294    @staticmethod 
     
    330364        s = "" 
    331365        s += "--------------------------------------------------------------\n" 
    332         s += " Plugins Environment Summary\n" 
     366        s += " Registered Environments\n" 
     367        s += "--------------------------------------------------------------\n" 
     368        keys = Plugins.env_registry.keys() 
     369        keys.sort() 
     370        for key in keys: 
     371            s += " "+Plugins.env_registry[key].name+" ("+key+")\n" 
     372        s += "\n" 
     373        s += "--------------------------------------------------------------\n" 
     374        s += " Environment Stack\n" 
    333375        s += "--------------------------------------------------------------\n" 
    334376        for env in Plugins.env_stack: 
     
    462504 
    463505class SingletonPlugin(Plugin): 
    464     """The base class for singleton plugins.  The PluginMeta class instantiates 
    465     a SingletonPlugin class when it is declared.  Note that only one instance of a  
    466     SingletonPlugin class is created in any environment.   
     506    """The base class for singleton plugins.  The PluginMeta class  
     507    instantiates a SingletonPlugin class when it is declared.  Note that  
     508    only one instance of a SingletonPlugin class is created in  
     509    any environment.   
    467510    """ 
    468  
    469  
    470511 
    471512 
Note: See TracChangeset for help on using the changeset viewer.