Logo Search packages:      
Sourcecode: mayavi2 version File versions  Download package

plugin_definition_loader.py

00001 """ The plugin definition loader. """


# Standard library imports.
import imp, logging, os, sys
from inspect import isclass
from os.path import normpath
from zipimport import zipimporter

# Enthought library imports.
from enthought.io.api import File
from enthought.traits.api import Event, HasTraits, Instance

# Local imports.
from importer import Importer
from plugin_definition import PluginDefinition
from util import get_module_name, get_zip_path, is_zip_path


# Setup a logger for this module.
logger=logging.getLogger(__name__)


00024 class PluginDefinitionLoader(HasTraits):
    """ The plugin definition loader.

    The intent of the plugin definition loader is to import plugin definition
    modules *without* importing any of the plugin implementation modules.

    This is achieved by *temporarily* hooking the Python import mechanism while
    plugin definition modules are being loaded.

    """

    #### 'PluginDefinitionLoader' interface ###################################

    # The application that the loader is part of.
    application = Instance('enthought.envisage.core.application.Application')

    #### Events ####

    # Fired when a plugin definition has been loaded.
    plugin_definition_loaded = Event(Instance(PluginDefinition))

    ###########################################################################
    # 'object' interface.
    ###########################################################################

00049     def __init__(self, **traits):
        """ Creates a new plugin definition loader. """

        # Super class constructor.
        super(PluginDefinitionLoader, self).__init__(**traits)

        # Mappings from filename to module name and back. These allow our
        # import hook to detect when a module is trying to import from a module
        # that is really a plugin definition module.
        self._filename_to_module_name_map = {}
        self._module_name_to_filename_map = {}

        return

    ###########################################################################
    # 'PluginDefinitionLoader' interface.
    ###########################################################################

00067     def load(self, filenames):
        """ Loads the specified plugin definition files.

        Parameters
        ----------
        filenames : a list of the absolute path names
            Names of the plugin definition files to be loaded.

        Returns a list containing the IDs of the plugin definitions that were
        loaded.

        """

        logger.debug('loading plugin definitions %s', str(filenames))

        # Populate the mappings from filename to module name and back. These
        # allow our import hook to detect when a module is trying to import
        # from a module that is really a plugin definition module.
        for filename in filenames + self.application.include:
            self._add_to_import_maps(filename)

        # Import all of the plugin definition files.
        #
        # Warning: The Python import mechanism is hooked in this method!
        modules = self._import_files(filenames)

        # Get all of the plugin definitions found in the imported modules
        # (there is normally one plugin definition per module, but we don't
        # enforce that).
        definitions = []
        for module in modules:
            definitions.extend(self._get_plugin_definitions(module))

        # Register the plugin definitions found in each module.
        registry = self.application.plugin_definition_registry

        ids = []
        for definition in definitions:
            # This is where extension points and extensions are added to
            # the extension registry.
            registry.add_definition(definition)
            ids.append(definition.id)

            # Event notification.
            self.plugin_definition_loaded = definition

        return ids

    ###########################################################################
    # Private interface.
    ###########################################################################

00119     def _add_to_import_maps(self, filename):
        """ Adds a filename to the import maps. """

        # Get the fully qualified module name for the plugin definition.
        module_name = get_module_name(filename)

        # Set up a mapping from module name to filename and back.
        self._module_name_to_filename_map[module_name] = filename
        self._filename_to_module_name_map[normpath(filename)] = module_name

        return

00131     def _import_files(self, filenames):
        """ Imports the specified plugin definition files. """

        # We hook the Python import mechanism while we are actually doing the
        # importing.
        self._hook_import()
        try:
            # Import the files.
            modules = []
            for filename in filenames:
                logger.info('importing %s', filename)
                module = self._import_file(filename)
                if module is not None and module not in modules:
                    logger.info('imported %s', filename)
                    modules.append(module)

        finally:
            # Unhook the Python import mechanism.
            self._unhook_import()

        return modules

00153     def _import_file(self, filename):
        """ Imports a single plugin definition file.

        Returns '''None''' if the file does not exist or cannot be imported.
        If successful, it returns the module.

        """

        # Get the fully qualified module name.
        module_name = self._filename_to_module_name_map[normpath(filename)]

        # Is the file a simple plugin definition file (i.e. a Python file)?
        f = File(filename)
        if f.is_file:
            # Import the module (if the module has already been imported then
            # this will just return it).
            try:
                # Remember that this is our hooked version of import!
                __import__(module_name)
                module = sys.modules[module_name]

            except:
                module = None
                logger.exception('error loading plugin definition %s',filename)

        # Is the file a zip file?
        elif is_zip_path(filename):
            # Import the module (if the module has already been imported then
            # this will just return it).
            try:
                module = self._import_from_zipfile(module_name, filename)

            except:
                module = None
                logger.exception('error loading plugin definition %s',filename)

        else:
            module = None
            logger.error('plugin definition %s does not exist', filename)

        return module

    ###########################################################################
    # Private interface.
    ###########################################################################

00199     def _hook_import(self):
        """ Hooks the import mechanism. """

        self._importer = Importer(self._module_name_to_filename_map)
        self._importer.install()

        logger.debug('hooked import mechanism')

        return

00209     def _unhook_import(self):
        """ Unhooks the import mechanism. """

        self._importer.uninstall()

        logger.debug('unhooked import mechanism')

        return

00218     def _import_from_zipfile(self, module_name, filename):
        """ Imports a single module from the specified zip file.

        This is a very direct import; ie., it does not touch the package that
        module is in. This is done so that we can reach in and import plugin
        definition modules without causing the import of any of the plugins'
        implementation files.

        """

        module = sys.modules.get(module_name, None)
        if module is None:
            logger.info("importing zip plugin definition %s" % module_name)

            # Get the path to the zip file.
            #
            # fixme: So what is filename? An example would help here.
            filepath = get_zip_path(filename)

            # Create an importer for the zip file.
            importer = zipimporter(filepath)

            # Create a new and empty module.
            module = imp.new_module(module_name)
            module.__file__ = filename

            # Add it to the Python modules collection.
            sys.modules[module_name] = module

            # Load the code from the file within the zip.
            code = importer.get_code(module_name)

            # Execute the plugin definition in the module's namespace.
            #
            # fixme: Is it ok to eval here? Will it cope with statements?
            eval(code, module.__dict__)

            # Add the zip file to the python path so we can  actually load
            # things from it
            sys.path.append(filepath)

        return module

00261     def _get_plugin_definitions(self, module):
        """ Returns all of the plugin definitions found in a module. """

        # The old, un-improved way to declare plugin definitions was to
        # create *instances* of the 'PluginDefinition' class.
        if hasattr(module, '__plugin_definitions__'):
            definitions = self._get_instance_plugin_definitions(module)

        # The new, improved way to declare plugin definitions is to create
        # *classes* that inherit from the 'PluginDefinition' class.
        else:
            definitions = self._get_class_plugin_definitions(module)

        return definitions

00276     def _get_instance_plugin_definitions(self, module):
        """ Returns all plugin definitions defined as instances. """

        return module.__plugin_definitions__.values()

00281     def _get_class_plugin_definitions(self, module):
        """ Returns all plugin definitions defined as classes. """

        # fixme: For some reason some people are seeing plugin definition
        # modules reporting that their file is a '.pyc' file instead of a '.py'
        # file. Normally we never create '.pyc' files for plugin definition
        # modules, so, somebody, somewhere is importing the plugin definition
        # file *before* the application starts up (i.e. using the standard
        # Python import mechanism).
        filename = normpath(module.__file__)
        if filename.endswith('.pyc'):
            filename = filename[:-1]

        # Get the corresponding module name.
        module_name = self._filename_to_module_name_map[filename]

        # Look through the module dictionary looking for subclasses of
        # 'PluginDefinition' that are *defined in this module* (as opposed to
        # classes that have been imported).
        #
        # fixme: Surely there is a better way to do this?
        definitions = []
        for key, value in module.__dict__.items():
            if isclass(value) and value.__module__ == module_name:
                if issubclass(value, PluginDefinition):
                    # Create a plugin definition *instance*.
                    definition = value()

                    # For plugin definitions created as instances the location
                    # is set during construction (in the slightly dodgy, stack
                    # walking part). For plugin definitions created as classes
                    # this is a bit easier, but we obviously have to do it
                    # outside of the constructor since until we deprecate the
                    # old way, since we can't stop the funny business in the
                    # constructor.
                    definition.location = os.path.dirname(filename)
                    definitions.append(definition)

        return definitions

#### EOF ######################################################################

Generated by  Doxygen 1.6.0   Back to index