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

application.py

00001 """ The entry point for an Envisage application. """


# Standard library imports.
import inspect, os, sys, time, user
from os.path import exists, join
import logging
from logging.handlers import RotatingFileHandler

# fixme: The ordering of these imports is critical. We don't use traits UI in
# this module, but it must be imported *before* any 'HasTraits' class whose
# instances might want to have 'edit_traits' called on them.
from enthought.traits.api import Any, Bool, Event, HasTraits, Instance, List
from enthought.traits.api import Property, Str, VetoableEvent

# fixme: Just importing the package is enought (see above).
import enthought.traits.ui

# Enthought library imports.
from enthought.etsconfig.api import ETSConfig
from enthought.io.api import File
from enthought.logger.api import add_log_queue_handler, create_log_file_handler
from enthought.logger.api import CONFIG_FILE_EXISTS

# Local imports.
from extension_registry import ExtensionRegistry
from import_manager import ImportManager
from plugin import Plugin
from plugin_activator import PluginActivator
from plugin_definition import PluginDefinition
from plugin_definition_loader import PluginDefinitionLoader
from plugin_definition_registry import PluginDefinitionRegistry
from service_registry import ServiceRegistry
from vetoable_application_event import VetoableApplicationEvent

# A logger instance.
logger = logging.getLogger(__name__)


00040 class Application(HasTraits):
    """ The entry point for an Envisage application. """

    # A sub-directory of the application's state location, where we create a
    # specific state location for each plugin (on demand).
    PLUGIN_STATE_DIR = 'plugins'

    #### 'Application' interface ##############################################

    # The remaining command-line arguments after those required by Envisage
    # itself have been removed.
    argv = List(Str)

    # The extension registry holds all extension points and extensions (which
    # are normally defined in plugin definitions).
    extension_registry = Instance(ExtensionRegistry, allow_none=False)

    # If the application requires a GUI, then this is the Pyface GUI that
    # represents it. The type is declared as **Any** so that we don't have to
    # import anything from Pyface if no GUI is required.
    #
    # fixme: This should probably not be created here; but rather the
    # application writer should create the GUI and then set this trait (and
    # indeed, manage the starting of the event loop, etc.). In that case, the
    # only reason to keep it as a trait on the application is to make it easy
    # for parts of the application to get a reference to the GUI so they can
    # get system metrics, etc. Of course we could then remove the 'requires_gui'
    # and the 'splash_screen' traits.
    gui = Any

    # The application's *globally* unique identifier.
    id = Str

    # The import manager handles importing symbols by name.
    import_manager = Instance(ImportManager, (), allow_none=False)

    # Plug-in definitions that other plugin definitions wish to import from,
    # but that otherwise are not part of the application (that is, their
    # extensions are not added to the extension registry, and they are never
    # started).
    #
    # Each item in this list is the absolute path to a plugin definition file.
    include = List(Str)

    # The name of the application's install location (i.e., the directory that
    # contains *this* module).
    install_location = Property(Str)

    # The plugin activator is responsible for starting and stopping plugin
    # implementations.
    plugin_activator = Instance(PluginActivator, allow_none=False)

    # The plugin definitions used in the application.
    #
    # Each item in this list is the absolute path to a plugin definition file.
    plugin_definitions = List(Str)

    # The plugin definition loader is responsible for the loading of plugin
    # definitions. When definitions are loaded, they are automatically
    # registered with the plugin definition registry.
    plugin_definition_loader = Instance(
        PluginDefinitionLoader, allow_none=False
    )

    # The plugin definition registry is the repository for all loaded plugin
    # definitions.
    plugin_definition_registry = Instance(
        PluginDefinitionRegistry, allow_none=False
    )

    # Does this application require a GUI?
    #
    # DEPRECATED: This will be removed in future releases; it is up to the
    # application writer to create the GUI and start its event loop, etc.
    requires_gui = Bool(True)

    # The service registry.
    #
    # fixme: Currently, the service registry implementation is very simple;
    # it should probably be expanded as a part of any OSGi-like effort.
    service_registry = Instance(ServiceRegistry, (), allow_none=False)

    # If the application requires a GUI, then this is the (optional) splash
    # screen that is displayed while the application starts up. The
    # type is **Any** so that nothing is imported from Pyface if a GUI is not
    # required.
    #
    # DEPRECATED: This will be removed in future releases; it is up to the
    # application writer to create the GUI, show the splash screen, and start
    # the event loop.
    splash_screen = Any

    # The application's state location.
    #
    # The state location is a directory on the local filesystem in which the
    # application stores things such as configuration information, preferences,
    # downloaded plugins, etc.
    #
    # Plug-ins *should not* access this directory directly. Instead they should
    # use the state location provided for them by the application. In a plugin,
    # use ``self.state_location`` to get a directory that the plugin is free to
    # read and write to.
    state_location = Property(Str)

    #### Events ####

    # fixme: The set of events fired by the application should be expanded as
    # part of any OSGi-like effort.

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

    # Fired when a plugin is about to be started.
    plugin_starting = Event(Instance(Plugin))

    # Fired when a plugin has been started.
    plugin_started = Event(Instance(Plugin))

    # Fired when a plugin is about to be stopped.
    plugin_stopping = Event(Instance(Plugin))

    # Fired when a plugin has been stopped.
    plugin_started = Event(Instance(Plugin))

    # Fired when the application is starting. This is the first thing that
    # happens when the **start()** method is called.
    starting = Event(Instance('Application'))

    # Fired when all plugins have been started, but before the GUI event loop
    # is started, if there is a GUI.
    started = Event(Instance('Application'))

    # Fired when the application is stopping. This is the first thing that
    # happens when the **stop()** method is called.
    stopping = VetoableEvent(Instance(VetoableApplicationEvent))

    # Fired when all plugins have been stopped.
    stopped = Event(Instance('Application'))

    #### Private interface ####################################################

    # Shadow trait for the 'install_location' property.
    _install_location = Any

    # Shadow trait for the 'state_location' property.
    _state_location = Any


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

00192     def __init__(self, **traits):
        """ Creates a new application.

        There is exactly *one* Application object per application.

        """

        try:
            # Base class constructor.
            super(Application, self).__init__(**traits)

            # Initialize logging. We have do this *after* calling the base
            # class constructor because we need the application's 'id' trait
            # to be set to find the location of the log file!
            if not CONFIG_FILE_EXISTS:
                self._initialize_logging()

            # Register the singleton application instance.
            #
            # fixme: Singletons suck. We *do* however want scripting users
            # within Envisage to be able to get access to the application
            # easily. Currently they can use:
            #
            # 'from enthought.envisage import application'
            #
            # Which is OK, but sadly we have code that uses that too which
            # obviously makes testing, etc. a bit trickier, since during test
            # runs we want to create multiple applications.
            from enthought.envisage import _set_application
            _set_application(self)

        except:
            logger.exception('error in application constructor')

        return

    ###########################################################################
    # 'Application' interface.
    ###########################################################################

    #### Initializers #########################################################

00234     def _argv_default(self):
        """ Initializes the **argv** trait. """

        return sys.argv

00239     def _extension_registry_default(self):
        """ Initializes the **extension_registry** trait. """

        return ExtensionRegistry(application=self)

00244     def _plugin_activator_default(self):
        """ Initializes the **plugin_activator** trait. """

        plugin_activator = PluginActivator(application=self)

        # For convenience (so that people don't have to reach in and get the
        # activator) the application fires the following activator events.
        plugin_activator.on_trait_change(
            self._on_plugin_starting, 'plugin_starting'
        )

        plugin_activator.on_trait_change(
            self._on_plugin_started, 'plugin_started'
        )

        return plugin_activator

00261     def _plugin_definition_loader_default(self):
        """ Initializes the **plugin_definition_loader** trait. """

        plugin_definition_loader = PluginDefinitionLoader(application=self)

        # For convenience (so that people don't have to reach in and get the
        # definition loader) the application fires the following definition
        # loader events.
        plugin_definition_loader.on_trait_change(
                self._on_plugin_definition_loaded, 'plugin_definition_loaded'
        )

        return plugin_definition_loader

00275     def _plugin_definition_registry_default(self):
        """ Initializes the **plugin_definition_registry** trait. """

        return PluginDefinitionRegistry(application=self)

    #### Properties ###########################################################

00282     def _get_install_location(self):
        """ Returns the name of the application's install location. """

        if self._install_location is None:
            filename = inspect.getfile(Application)
            self._install_location = os.path.dirname(filename)

        return self._install_location

00291     def _get_state_location(self):
        """ Returns the name of the application's state location. """

        if self._state_location is None:
            self._state_location = self._initialize_state_location()

        return self._state_location

    #### Methods ##############################################################

00301     def start(self):
        """ Starts the application. """

        logger.debug('---------- application starting ----------')

        # Event notification.
        self.starting = self

        # fixme: This is a hack to allow for backwards compatability with the
        # deprecated 'requires_gui' flag. When that flag is removed this can
        # obviously go too!
        old_style_gui = self.gui is None and self.requires_gui

        # If the application requires a GUI then create one!
        if old_style_gui:
            # We do the import here so that there is no need to have a GUI
            # toolkit on the Python path unless a GUI is required.
            from enthought.pyface.api import GUI

            redirect = False
            file = None
            if sys.platform == 'win32' and \
                    sys.executable.endswith('pythonw.exe'):
                redirect = True
                file = "NULL"

            # This creates the GUI but does NOT start its event loop.
            self.gui = GUI(splash_screen=self.splash_screen, redirect=redirect, filename=file)

        # Load all of the application's plugin definitions.
        self.load_plugin_definitions(self.plugin_definitions)

        # Start all loaded plugins marked as 'autostart' along with any plugins
        # that they require.
        self.plugin_activator.start_all()

        logger.debug('---------- application started ----------')

        # Event notification.
        self.started = self

        # If the application requires a GUI then start its event loop.
        if old_style_gui:
            # THIS CALL DOES NOT RETURN UNTIL THE GUI IS CLOSED.
            self.gui.start_event_loop()

        return

00349     def stop(self):
        """ Stops the application. """
        logger.debug('---------- application stopping ----------')

        # Event notification.
        self.stopping = event = VetoableApplicationEvent(application=self)
        if event.veto:
            return False
        else:
            # Stop all plugins.
            self.plugin_activator.stop_all()

            # Event notification.
            self.stopped = self

            logger.debug('---------- application stopped ----------')
            return True

        return

00369     def get_plugin_state_location(self, plugin):
        """ Returns the name of a plugin's state location.

        A plugin's state location is a directory on the local filesystem that
        the plugin can read and write to at will. By default, the plugin's
        preferences are stored in here.

        """

        # The pathname of the plugin's state location.
        state_location = join(
            self.state_location, self.PLUGIN_STATE_DIR, plugin.id
        )

        # Make sure that the location exists (and is a directory etc).
        try:
            os.makedirs(state_location)

        # We get an 'OSError' if the directory already exists.
        #
        # fixme: We should check the exception a little more closely to make
        # sure that is is an 'already exists' error!
        except OSError:
            pass

        return state_location

00396     def get_preferences(self, id):
        """ Return preferences in a particular plugin given its ID

            Parameters:
            -----------
            id: Str

            Returns:
            -------
            preferences: Preferences

        """

        # Check using the registry if the plugin definition is already loaded
        definition = self.plugin_definition_registry.get_definition(id)
        if definition is None:
            raise ValueError('No such plugin definition %s' % id)

        # Check with plugin-activator if the plugin already exists
        plugin = self.plugin_activator.get_plugin(id)
        if plugin is None:
            raise ValueError('No such plugin definition %s' % id)

        return plugin.preferences

00421     def lookup_application_object(self, uol):
        """ Resolve a UOL (Universal Object Locator) to produce an actual object.

        For 'lookup' the UOL can be:

        * ``'service://a_service_identifier'``
        * ``'name://a/path/through/the/naming/service'``
        * ``'factory://package.module.callable'``
        * ``'import://package.module.symbol'``
        * ``'file://the/pathname/of/a/file/containing/a/UOL'``
        * ``'http://a/URL/pointing/to/a/text/document/containing/a/UOL'``

        fixme: This mechanism needs to be extensible with new UOL protocols.

        """

        # Service Id.
        if uol.lower()[:10] == 'service://':
            # Lookup the service.
            obj = self.get_service(uol[10:])

        # Naming service name.
        elif uol.lower()[:7] == 'name://':
            # Get a reference to the naming service.
            naming = self.get_service('enthought.envisage.core.INamingService')

            # Lookup the name.
            try:
                obj = naming.lookup(uol[7:])

            except:
                obj = None

        # Factory.
        elif uol.lower()[:10] == 'factory://':
            factory = self.import_symbol(uol[10:])
            obj = factory()

        # Import.
        elif uol.lower()[:9] == 'import://':
            obj = self.import_symbol(uol[9:])

        # Filename.
        elif uol.lower()[:7] == 'file://':
            try:
                f = file(uol[7:])
                uol = f.read().strip()
                f.close()

                # Recursively resolve the UOL.
                obj = self._resolve_uol(uol)

            # We get an IOError if the file doesn't exist or is not readable!
            except IOError:
                obj = None

        # URL.
        elif uol.lower()[:7] == 'http://':
            # Do the import here 'cos I'm not sure how much this will actually
            # be used.
            import urllib
            try:
                f = urllib.urlopen(uol)
                uol = f.read().strip()
                f.close()

                # Recursively resolve the UOL.
                obj = self._resolve_uol(uol)

            except:
                obj = None

        else:
            logger.error(
                "Unable to handle UOL [%s] in Application [%s]", uol, self
            )
            obj = None

        # If the object has an 'application' trait then set it.
        #
        # fixme: We probably need stricter type checking on the trait to make
        # sure it is actually an Envisage application trait!
        if hasattr(obj, 'application'):
            obj.application = self

        return obj

00508     def publish_application_object(self, uol, obj, properties=None):
        """ Publish an object via a UOL (Universal Object Locator).

        For 'publishing' the UOL can be:

        * ``'service://a_service_identifier'``
        * ``'name://a/path/through/the/naming/service'``

        fixme: This mechanism needs to be extensible with new UOL protocols.

        """

        # Service Id.
        if uol.lower()[:10] == 'service://':
            self.register_service(uol[10:], obj, properties)

        # Naming service name.
        elif uol.lower()[:7] == 'name://':
            # Get a reference to the naming service.
            naming = self.get_service('enthought.envisage.core.INamingService')

            # Bind the object to the name.
            naming.rebind(uol[7:], obj)

        return

    # Convenience methods for common operations on the plugin definition
    # loader.
00536     def load_plugin_definition(self, filename):
        """ Loads a plugin definition. """

        self.plugin_definition_loader.load([filename])

        return

00543     def load_plugin_definitions(self, filenames):
        """ Loads a list of plugin definitions. """

        self.plugin_definition_loader.load(filenames)

        return

    # Convenience methods for common operations on the plugin activator.
00551     def start_plugin(self, id):
        """ Starts the plugin that has the specified ID.

        Notes
        -----
        * The plugin definition MUST have already been loaded.
        * All plugins required by the plugin will also be started.
        * If the plugin has already been started, then this does nothing.

        """

        self.plugin_activator.start_plugin(id)

        return

00566     def stop_plugin(self, id):
        """ Stops the plugin that has the specified ID. """

        self.plugin_activator.stop_plugin(id)

        return

    # Convenience methods for common operations on the import manager.
00574     def import_symbol(self, symbol_path):
        """ Imports the symbol defined by *symbol_path*.

        Parameters
        ----------
        symbol_path : a string in the form 'foo.bar.baz'
            The module path to a symbol to import

        The *symbol_path* value is turned into an import statement
        ``'from foo.bar import baz'``
        (i.e., the last component of the name is the symbol name; the rest
        is the module path to load it from).


        """

        return self.import_manager.import_symbol(symbol_path)

00592     def get_class(self, class_path):
        """ Returns the class defined by *class_path*.

        Returns **None** if the class has not yet been loaded.

        """

        return self.import_manager.get_class(class_path)

    # Convenience methods for common operations on the extension registry.
00602     def get_extensions(self, extension_point_id, plugin_id=None):
        """ Returns a list of all contributions made to an extension point. """

        extensions = self.extension_registry.get_extensions(
            extension_point_id, plugin_id
        )

        return extensions

00611     def load_extensions(self, extension_point_id, plugin_id=None):
        """ Returns a list of all contributions made to an extension point.

        The difference between this method and **get_extensions()** is that
        this method makes sure that the plugin that contributed each extension
        has been started.

        Parameters
        ----------
        extension_point_id : a class that is derived from **ExtensionPoint**
            The extension point whose extensions are retrieved.
        plugin_id : a plugin ID
            If specified, only this plugin's extensions to the extension point
            are returned

        """

        extensions = self.extension_registry.load_extensions(
            extension_point_id, plugin_id
        )

        return extensions

    # Convenience methods for common operations on the service registry.
00635     def get_service_ids(self):
        """ Returns a list of the currrently registered service IDs.

        fixme: This method name needs to be modified when we switch to using
        real interfaces for identifying services.

        """

        return self.service_registry.services.keys()

00645     def get_service(self, interface, query=None):
        """ Returns a service that implements the specified interface.

        Raises a **SystemError** if no such service is found.

        """

        service = self.service_registry.get_service(interface, query)
        if service is None:
            raise SystemError("Service %s not found" % str(interface))

        return service

00658     def get_services(self, interface, query=None):
        """ Returns all services that match the specified query.

        If no services match the query, then an empty list is returned.

        """

        return self.service_registry.get_services(interface, query)

00667     def register_service(self, interface, obj, properties=None):
        """ Registers a service that implements the specified interface.

        Returns a service ID (a unique ID for the service within the
        application).

        """

        service_id = self.service_registry.register_service(
            interface, obj, properties
        )

        return service_id

00681     def unregister_service(self, service_id):
        """ Unregisters a service. """

        self.service_registry.unregister_service(service_id)

        return

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

00692     def _initialize_logging(self):
        """ Initializes the logging system.

        Every application gets its own log file.

        """

        root = logging.getLogger()

        log_file_path = join(self.state_location, 'ets.log')
        # First check if there is already a rotating file handler for
        # 'ets.log' registered.
        for handler in root.handlers:
            if isinstance(handler, RotatingFileHandler):
                if handler.stream.name == log_file_path:
                    break
        else:
            handler = create_log_file_handler(log_file_path)
            root.addHandler(handler)
            # fixme: Add the queueing handler to the root logger.
            add_log_queue_handler(root)

        return

00716     def _initialize_state_location(self):
        """ Initializes the application's state location.
        """
        # Note: Don't use the logger in here, since we use this value to
        # initialize the location of the log file!

        path = join(ETSConfig.application_data, self.id)

        state_location = File(path)
        if not state_location.exists:
            state_location.create_folders()

        return path

    #### Trait change handlers ################################################

    #### Static ####

00734     def _plugin_activator_changed(self, old, new):
        """ Static trait change handler. """

        old.on_trait_change(
            self._on_plugin_starting, 'plugin_starting', remove=True
        )

        old.on_trait_change(
            self._on_plugin_started, 'plugin_started', remove=True
        )

        new.on_trait_change(self._on_plugin_starting, 'plugin_starting')
        new.on_trait_change(self._on_plugin_started, 'plugin_started')

        return

    #### Dynamic ####

00752     def _on_plugin_definition_loaded(self, plugin):
        """ Dynamic trait change handler. """

        self.plugin_definition_loaded = plugin

        return

00759     def _on_plugin_starting(self, plugin):
        """ Dynamic trait change handler. """

        self.plugin_starting = plugin

        return

00766     def _on_plugin_started(self, plugin):
        """ Dynamic trait change handler. """

        self.plugin_started = plugin

        return

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

Generated by  Doxygen 1.6.0   Back to index