2014-09-05 15:11:11 +00:00
# coding=utf-8
2015-02-27 19:28:24 +00:00
"""
In this module resides the core data structures and logic of the plugin system . It is implemented in an OctoPrint - agnostic
way and could be extracted into a separate Python module in the future .
. . autoclass : : PluginManager
2015-03-20 10:33:54 +00:00
: members :
2015-02-27 19:28:24 +00:00
. . autoclass : : PluginInfo
2015-03-20 10:33:54 +00:00
: members :
2015-02-27 19:28:24 +00:00
. . autoclass : : Plugin
2015-03-20 10:33:54 +00:00
: members :
2015-02-27 19:28:24 +00:00
"""
2014-09-05 15:11:11 +00:00
from __future__ import absolute_import
__author__ = " Gina Häußge <osd@foosel.net> "
__license__ = ' GNU Affero General Public License http://www.gnu.org/licenses/agpl.html '
__copyright__ = " Copyright (C) 2014 The OctoPrint Project - Released under terms of the AGPLv3 License "
import os
import imp
2015-09-04 09:25:27 +00:00
from collections import defaultdict , namedtuple , OrderedDict
2014-09-05 15:11:11 +00:00
import logging
2015-04-16 16:37:03 +00:00
import pkg_resources
import pkginfo
2014-09-05 15:11:11 +00:00
2015-06-03 08:44:27 +00:00
EntryPointOrigin = namedtuple ( " EntryPointOrigin " , " type, entry_point, module_name, package_name, package_version " )
FolderOrigin = namedtuple ( " FolderOrigin " , " type, folder " )
2014-09-05 15:11:11 +00:00
class PluginInfo ( object ) :
2015-02-27 19:28:24 +00:00
"""
The : class : ` PluginInfo ` class wraps all available information about a registered plugin .
This includes its meta data ( like name , description , version , etc ) as well as the actual plugin extensions like
implementations , hooks and helpers .
It works on Python module objects and extracts the relevant data from those via accessing the
2015-03-31 18:08:32 +00:00
: ref : ` control properties < sec - plugin - concepts - controlproperties > ` .
2015-03-20 10:33:54 +00:00
Arguments :
key ( str ) : Identifier of the plugin
location ( str ) : Installation folder of the plugin
instance ( module ) : Plugin module instance
name ( str ) : Human readable name of the plugin
version ( str ) : Version of the plugin
description ( str ) : Description of the plugin
author ( str ) : Author of the plugin
url ( str ) : URL of the website of the plugin
license ( str ) : License of the plugin
2015-02-27 19:28:24 +00:00
"""
2014-09-05 15:11:11 +00:00
attr_name = ' __plugin_name__ '
2015-03-20 10:33:54 +00:00
""" Module attribute from which to retrieve the plugin ' s human readable name. """
2014-09-05 15:11:11 +00:00
attr_description = ' __plugin_description__ '
2015-03-20 10:33:54 +00:00
""" Module attribute from which to retrieve the plugin ' s description. """
2014-09-05 15:11:11 +00:00
attr_version = ' __plugin_version__ '
2015-03-20 10:33:54 +00:00
""" Module attribute from which to retrieve the plugin ' s version. """
2014-09-05 15:11:11 +00:00
2015-01-26 11:51:44 +00:00
attr_author = ' __plugin_author__ '
2015-03-20 10:33:54 +00:00
""" Module attribute from which to retrieve the plugin ' s author. """
2015-01-26 11:51:44 +00:00
attr_url = ' __plugin_url__ '
2015-03-20 10:33:54 +00:00
""" Module attribute from which to retrieve the plugin ' s website URL. """
2015-01-26 11:51:44 +00:00
2015-01-26 21:21:58 +00:00
attr_license = ' __plugin_license__ '
2015-03-20 10:33:54 +00:00
""" Module attribute from which to retrieve the plugin ' s license. """
2015-01-26 21:21:58 +00:00
2014-09-05 15:11:11 +00:00
attr_hooks = ' __plugin_hooks__ '
2015-03-20 10:33:54 +00:00
""" Module attribute from which to retrieve the plugin ' s provided hooks. """
2014-09-05 15:11:11 +00:00
2015-03-30 14:50:06 +00:00
attr_implementation = ' __plugin_implementation__ '
""" Module attribute from which to retrieve the plugin ' s provided mixin implementation. """
2014-09-05 15:11:11 +00:00
attr_implementations = ' __plugin_implementations__ '
2015-03-30 14:50:06 +00:00
"""
Module attribute from which to retrieve the plugin ' s provided implementations.
This deprecated attribute will only be used if a plugin does not yet offer : attr : ` attr_implementation ` . Only the
first entry will be evaluated .
. . deprecated : : 1.2 .0 - dev - 694
Use : attr : ` attr_implementation ` instead .
"""
2014-09-05 15:11:11 +00:00
2014-09-10 20:28:03 +00:00
attr_helpers = ' __plugin_helpers__ '
2015-03-20 10:33:54 +00:00
""" Module attribute from which to retrieve the plugin ' s provided helpers. """
2014-09-10 20:28:03 +00:00
2014-09-05 15:11:11 +00:00
attr_check = ' __plugin_check__ '
2015-03-20 10:33:54 +00:00
""" Module attribute which to call to determine if the plugin can be loaded. """
2014-09-05 15:11:11 +00:00
2014-09-12 09:21:39 +00:00
attr_init = ' __plugin_init__ '
2015-04-14 15:55:46 +00:00
"""
Module attribute which to call when loading the plugin .
This deprecated attribute will only be used if a plugin does not yet offer : attr : ` attr_load ` .
. . deprecated : : 1.2 .0 - dev - 720
Use : attr : ` attr_load ` instead .
"""
2014-09-12 09:21:39 +00:00
2015-04-01 08:55:13 +00:00
attr_load = ' __plugin_load__ '
2015-04-14 15:55:46 +00:00
""" Module attribute which to call when loading the plugin. """
2015-04-01 08:55:13 +00:00
attr_unload = ' __plugin_unload__ '
2015-04-14 15:55:46 +00:00
""" Module attribute which to call when unloading the plugin. """
2015-04-01 08:55:13 +00:00
attr_enable = ' __plugin_enable__ '
2015-04-14 15:55:46 +00:00
""" Module attribute which to call when enabling the plugin. """
2015-04-01 08:55:13 +00:00
attr_disable = ' __plugin_disable__ '
2015-04-14 15:55:46 +00:00
""" Module attribute which to call when disabling the plugin. """
2015-04-01 08:55:13 +00:00
2015-01-26 21:21:58 +00:00
def __init__ ( self , key , location , instance , name = None , version = None , description = None , author = None , url = None , license = None ) :
2014-09-05 15:11:11 +00:00
self . key = key
self . location = location
self . instance = instance
2015-01-26 11:51:44 +00:00
self . origin = None
2015-01-26 21:21:58 +00:00
self . enabled = True
2015-01-26 16:27:34 +00:00
self . bundled = False
2015-04-02 21:02:42 +00:00
self . loaded = False
2014-09-05 15:11:11 +00:00
2015-01-26 16:27:34 +00:00
self . _name = name
2014-09-10 13:44:53 +00:00
self . _version = version
2015-01-26 16:27:34 +00:00
self . _description = description
self . _author = author
self . _url = url
2015-01-26 21:21:58 +00:00
self . _license = license
2015-01-15 10:06:59 +00:00
2015-06-09 16:50:55 +00:00
def validate ( self , phase , additional_validators = None ) :
if phase == " before_load " :
# if the plugin still uses __plugin_init__, log a deprecation warning and move it to __plugin_load__
if hasattr ( self . instance , self . __class__ . attr_init ) :
if not hasattr ( self . instance , self . __class__ . attr_load ) :
# deprecation warning
import warnings
warnings . warn ( " {name} uses deprecated control property __plugin_init__, use __plugin_load__ instead " . format ( name = self . key ) , DeprecationWarning )
# move it
init = getattr ( self . instance , self . __class__ . attr_init )
setattr ( self . instance , self . __class__ . attr_load , init )
# delete __plugin_init__
delattr ( self . instance , self . __class__ . attr_init )
elif phase == " after_load " :
# if the plugin still uses __plugin_implementations__, log a deprecation warning and put the first
# item into __plugin_implementation__
if hasattr ( self . instance , self . __class__ . attr_implementations ) :
if not hasattr ( self . instance , self . __class__ . attr_implementation ) :
# deprecation warning
import warnings
warnings . warn ( " {name} uses deprecated control property __plugin_implementations__, use __plugin_implementation__ instead - only the first implementation of {name} will be recognized " . format ( name = self . key ) , DeprecationWarning )
# put first item into __plugin_implementation__
implementations = getattr ( self . instance , self . __class__ . attr_implementations )
if len ( implementations ) > 0 :
setattr ( self . instance , self . __class__ . attr_implementation , implementations [ 0 ] )
# delete __plugin_implementations__
delattr ( self . instance , self . __class__ . attr_implementations )
if additional_validators is not None :
for validator in additional_validators :
validator ( phase , self )
2015-04-14 15:55:46 +00:00
2014-09-05 15:11:11 +00:00
def __str__ ( self ) :
2015-03-25 17:56:11 +00:00
if self . version :
return " {name} ( {version} ) " . format ( name = self . name , version = self . version )
else :
return self . name
2015-04-14 15:55:46 +00:00
def long_str ( self , show_bundled = False , bundled_strs = ( " [B] " , " " ) ,
2015-03-25 17:56:11 +00:00
show_location = False , location_str = " - {location} " ,
2015-04-14 15:55:46 +00:00
show_enabled = False , enabled_strs = ( " * " , " " ) ) :
"""
Long string representation of the plugin ' s information. Will return a string of the format ``<enabled><str(self)><bundled><location>``.
` ` enabled ` ` , ` ` bundled ` ` and ` ` location ` ` will only be displayed if the corresponding flags are set to ` ` True ` ` .
The will be filled from ` ` enabled_str ` ` , ` ` bundled_str ` ` and ` ` location_str ` ` as follows :
` ` enabled_str ` `
a 2 - tuple , the first entry being the string to insert when the plugin is enabled , the second
entry the string to insert when it is not .
` ` bundled_str ` `
a 2 - tuple , the first entry being the string to insert when the plugin is bundled , the second
entry the string to insert when it is not .
` ` location_str ` `
a format string ( to be parsed with ` ` str . format ` ` ) , the ` ` { location } ` ` placeholder will be
replaced with the plugin ' s installation folder on disk.
Arguments :
show_enabled ( boolean ) : whether to show the ` ` enabled ` ` part
enabled_strs ( tuple ) : the 2 - tuple containing the two possible strings to use for displaying the enabled state
show_bundled ( boolean ) : whether to show the ` ` bundled ` ` part
bundled_strs ( tuple ) : the 2 - tuple containing the two possible strings to use for displaying the bundled state
show_location ( boolean ) : whether to show the ` ` location ` ` part
location_str ( str ) : the format string to use for displaying the plugin ' s installation location
Returns :
str : The long string representation of the plugin as described above
"""
2015-03-25 17:56:11 +00:00
if show_enabled :
2015-04-14 15:55:46 +00:00
ret = enabled_strs [ 0 ] if self . enabled else enabled_strs [ 1 ]
2015-03-25 17:56:11 +00:00
else :
ret = " "
ret + = str ( self )
if show_bundled :
2015-04-14 15:55:46 +00:00
ret + = bundled_strs [ 0 ] if self . bundled else bundled_strs [ 1 ]
2015-03-25 17:56:11 +00:00
if show_location and self . location :
ret + = location_str . format ( location = self . location )
return ret
2014-09-05 15:11:11 +00:00
def get_hook ( self , hook ) :
2015-03-20 10:33:54 +00:00
"""
Arguments :
hook ( str ) : Hook to return .
Returns :
callable or None : Handler for the requested ` ` hook ` ` or None if no handler is registered .
"""
2014-09-05 15:11:11 +00:00
if not hook in self . hooks :
return None
return self . hooks [ hook ]
2015-03-30 14:50:06 +00:00
def get_implementation ( self , * types ) :
2015-03-20 10:33:54 +00:00
"""
Arguments :
types ( list ) : List of : class : ` Plugin ` sub classes all returned implementations need to implement .
Returns :
2015-03-30 14:50:06 +00:00
object : The plugin ' s implementation if it matches all of the requested ``types``, None otherwise.
2015-03-20 10:33:54 +00:00
"""
2015-03-30 14:50:06 +00:00
if not self . implementation :
return None
for t in types :
if not isinstance ( self . implementation , t ) :
return None
return self . implementation
2014-09-05 15:11:11 +00:00
@property
def name ( self ) :
2015-03-20 10:33:54 +00:00
"""
Human readable name of the plugin . Will be taken from name attribute of the plugin module if available ,
otherwise from the ` ` name ` ` supplied during construction with a fallback to ` ` key ` ` .
Returns :
str : Name of the plugin , fallback is the plugin ' s identifier.
"""
2015-01-30 17:04:15 +00:00
return self . _get_instance_attribute ( self . __class__ . attr_name , defaults = ( self . _name , self . key ) )
2014-09-05 15:11:11 +00:00
@property
def description ( self ) :
2015-03-20 10:33:54 +00:00
"""
Description of the plugin . Will be taken from the description attribute of the plugin module as defined in
: attr : ` attr_description ` if available , otherwise from the ` ` description ` ` supplied during construction .
May be None .
Returns :
str or None : Description of the plugin .
"""
2015-01-26 16:27:34 +00:00
return self . _get_instance_attribute ( self . __class__ . attr_description , default = self . _description )
2014-09-05 15:11:11 +00:00
@property
def version ( self ) :
2015-03-20 10:33:54 +00:00
"""
Version of the plugin . Will be taken from the version attribute of the plugin module as defined in
: attr : ` attr_version ` if available , otherwise from the ` ` version ` ` supplied during construction . May be None .
Returns :
str or None : Version of the plugin .
"""
2015-01-26 16:27:34 +00:00
return self . _version if self . _version is not None else self . _get_instance_attribute ( self . __class__ . attr_version , default = self . _version )
2014-09-05 15:11:11 +00:00
2015-01-26 11:51:44 +00:00
@property
def author ( self ) :
2015-03-20 10:33:54 +00:00
"""
Author of the plugin . Will be taken from the author attribute of the plugin module as defined in
: attr : ` attr_author ` if available , otherwise from the ` ` author ` ` supplied during construction . May be None .
Returns :
str or None : Author of the plugin .
"""
2015-01-26 16:27:34 +00:00
return self . _get_instance_attribute ( self . __class__ . attr_author , default = self . _author )
2015-01-26 11:51:44 +00:00
@property
def url ( self ) :
2015-03-20 10:33:54 +00:00
"""
Website URL for the plugin . Will be taken from the url attribute of the plugin module as defined in
: attr : ` attr_url ` if available , otherwise from the ` ` url ` ` supplied during construction . May be None .
Returns :
str or None : Website URL for the plugin .
"""
2015-01-26 16:27:34 +00:00
return self . _get_instance_attribute ( self . __class__ . attr_url , default = self . _url )
2015-01-26 11:51:44 +00:00
2015-01-26 21:21:58 +00:00
@property
def license ( self ) :
2015-03-20 10:33:54 +00:00
"""
License of the plugin . Will be taken from the license attribute of the plugin module as defined in
: attr : ` attr_license ` if available , otherwise from the ` ` license ` ` supplied during construction . May be None .
Returns :
str or None : License of the plugin .
"""
2015-01-26 21:21:58 +00:00
return self . _get_instance_attribute ( self . __class__ . attr_license , default = self . _license )
2014-09-05 15:11:11 +00:00
@property
def hooks ( self ) :
2015-03-20 10:33:54 +00:00
"""
Hooks provided by the plugin . Will be taken from the hooks attribute of the plugin module as defiend in
: attr : ` attr_hooks ` if available , otherwise an empty dictionary is returned .
Returns :
dict : Hooks provided by the plugin .
"""
2014-09-05 15:11:11 +00:00
return self . _get_instance_attribute ( self . __class__ . attr_hooks , default = { } )
@property
2015-03-30 14:50:06 +00:00
def implementation ( self ) :
2015-03-20 10:33:54 +00:00
"""
2015-04-01 09:51:08 +00:00
Implementation provided by the plugin . Will be taken from the implementation attribute of the plugin module
as defined in : attr : ` attr_implementation ` if available , otherwise None is returned .
2015-03-20 10:33:54 +00:00
Returns :
2015-04-01 09:51:08 +00:00
object : Implementation provided by the plugin .
2015-03-20 10:33:54 +00:00
"""
2015-03-30 14:50:06 +00:00
return self . _get_instance_attribute ( self . __class__ . attr_implementation , default = None )
2014-09-05 15:11:11 +00:00
2014-09-10 20:28:03 +00:00
@property
def helpers ( self ) :
2015-03-20 10:33:54 +00:00
"""
Helpers provided by the plugin . Will be taken from the helpers attribute of the plugin module as defined in
: attr : ` attr_helpers ` if available , otherwise an empty list is returned .
Returns :
dict : Helpers provided by the plugin .
"""
2014-09-10 20:28:03 +00:00
return self . _get_instance_attribute ( self . __class__ . attr_helpers , default = { } )
2014-09-05 15:11:11 +00:00
@property
def check ( self ) :
2015-03-20 10:33:54 +00:00
"""
Method for pre - load check of plugin . Will be taken from the check attribute of the plugin module as defined in
: attr : ` attr_check ` if available , otherwise a lambda always returning True is returned .
Returns :
callable : Check method for the plugin module which should return True if the plugin can be loaded , False
otherwise .
"""
2014-09-05 15:11:11 +00:00
return self . _get_instance_attribute ( self . __class__ . attr_check , default = lambda : True )
2014-09-12 09:21:39 +00:00
@property
2015-04-01 08:55:13 +00:00
def load ( self ) :
2015-03-20 10:33:54 +00:00
"""
2015-04-01 08:55:13 +00:00
Method for loading the plugin module . Will be taken from the load attribute of the plugin module as defined
in : attr : ` attr_load ` if available , otherwise a no - operation lambda will be returned .
2015-03-20 10:33:54 +00:00
Returns :
2015-04-14 15:55:46 +00:00
callable : Load method for the plugin module .
2015-03-20 10:33:54 +00:00
"""
2015-04-14 15:55:46 +00:00
return self . _get_instance_attribute ( self . __class__ . attr_load , default = lambda : True )
2015-04-01 08:55:13 +00:00
@property
def unload ( self ) :
2015-04-14 15:55:46 +00:00
"""
Method for unloading the plugin module . Will be taken from the unload attribute of the plugin module as defined
in : attr : ` attr_unload ` if available , otherwise a no - operation lambda will be returned .
2015-04-01 08:55:13 +00:00
2015-04-14 15:55:46 +00:00
Returns :
callable : Unload method for the plugin module .
"""
return self . _get_instance_attribute ( self . __class__ . attr_unload , default = lambda : True )
2015-04-01 08:55:13 +00:00
@property
def enable ( self ) :
2015-04-14 15:55:46 +00:00
"""
Method for enabling the plugin module . Will be taken from the enable attribute of the plugin module as defined
in : attr : ` attr_enable ` if available , otherwise a no - operation lambda will be returned .
Returns :
callable : Enable method for the plugin module .
"""
2015-04-01 08:55:13 +00:00
return self . _get_instance_attribute ( self . __class__ . attr_enable , default = lambda : True )
@property
def disable ( self ) :
2015-04-14 15:55:46 +00:00
"""
Method for disabling the plugin module . Will be taken from the disable attribute of the plugin module as defined
in : attr : ` attr_disable ` if available , otherwise a no - operation lambda will be returned .
Returns :
callable : Disable method for the plugin module .
"""
2015-04-01 08:55:13 +00:00
return self . _get_instance_attribute ( self . __class__ . attr_disable , default = lambda : True )
2014-09-12 09:21:39 +00:00
2015-01-30 17:04:15 +00:00
def _get_instance_attribute ( self , attr , default = None , defaults = None ) :
2014-09-05 15:11:11 +00:00
if not hasattr ( self . instance , attr ) :
2015-01-30 17:04:15 +00:00
if defaults is not None :
for value in defaults :
2015-01-30 12:12:25 +00:00
if value is not None :
return value
2015-01-30 17:04:15 +00:00
return default
2014-09-05 15:11:11 +00:00
return getattr ( self . instance , attr )
class PluginManager ( object ) :
2015-02-27 19:28:24 +00:00
"""
The : class : ` PluginManager ` is the central component for finding , loading and accessing plugins provided to the
system .
It is able to discover plugins both through possible file system locations as well as customizable entry points .
"""
2014-09-05 15:11:11 +00:00
2015-06-19 09:09:08 +00:00
def __init__ ( self , plugin_folders , plugin_types , plugin_entry_points , logging_prefix = None ,
plugin_disabled_list = None , plugin_restart_needing_hooks = None , plugin_obsolete_hooks = None ,
plugin_validators = None ) :
2014-09-05 15:11:11 +00:00
self . logger = logging . getLogger ( __name__ )
2015-01-30 12:12:25 +00:00
if logging_prefix is None :
logging_prefix = " "
2014-09-05 15:11:11 +00:00
if plugin_disabled_list is None :
plugin_disabled_list = [ ]
self . plugin_folders = plugin_folders
self . plugin_types = plugin_types
2014-09-10 13:44:53 +00:00
self . plugin_entry_points = plugin_entry_points
2014-09-05 15:11:11 +00:00
self . plugin_disabled_list = plugin_disabled_list
2015-05-11 13:47:40 +00:00
self . plugin_restart_needing_hooks = plugin_restart_needing_hooks
2015-05-28 10:56:42 +00:00
self . plugin_obsolete_hooks = plugin_obsolete_hooks
2015-06-09 16:50:55 +00:00
self . plugin_validators = plugin_validators
2015-01-30 12:12:25 +00:00
self . logging_prefix = logging_prefix
2014-09-05 15:11:11 +00:00
2015-04-14 15:55:46 +00:00
self . enabled_plugins = dict ( )
self . disabled_plugins = dict ( )
2015-03-30 14:50:06 +00:00
self . plugin_implementations = dict ( )
2015-01-15 10:06:59 +00:00
self . plugin_implementations_by_type = defaultdict ( list )
2014-09-05 15:11:11 +00:00
2015-09-04 09:25:27 +00:00
self . _plugin_hooks = defaultdict ( list )
2015-04-01 08:55:13 +00:00
self . implementation_injects = dict ( )
self . implementation_inject_factories = [ ]
2015-06-19 09:09:08 +00:00
self . implementation_pre_inits = [ ]
self . implementation_post_inits = [ ]
2015-04-01 08:55:13 +00:00
self . on_plugin_loaded = lambda * args , * * kwargs : None
self . on_plugin_unloaded = lambda * args , * * kwargs : None
self . on_plugin_enabled = lambda * args , * * kwargs : None
self . on_plugin_disabled = lambda * args , * * kwargs : None
self . on_plugin_implementations_initialized = lambda * args , * * kwargs : None
2014-11-25 08:08:33 +00:00
self . registered_clients = [ ]
2015-05-29 14:25:18 +00:00
self . marked_plugins = defaultdict ( list )
2015-04-02 21:02:42 +00:00
self . reload_plugins ( startup = True , initialize_implementations = False )
2014-09-05 15:11:11 +00:00
2015-04-02 21:02:42 +00:00
@property
2015-04-14 15:55:46 +00:00
def plugins ( self ) :
plugins = dict ( self . enabled_plugins )
2015-04-02 21:02:42 +00:00
plugins . update ( self . disabled_plugins )
return plugins
2015-09-04 09:25:27 +00:00
@property
def plugin_hooks ( self ) :
return { key : map ( lambda v : ( v [ 1 ] , v [ 2 ] ) , value ) for key , value in self . _plugin_hooks . items ( ) }
2015-05-29 14:25:18 +00:00
def find_plugins ( self , existing = None , ignore_uninstalled = True ) :
2015-04-02 21:02:42 +00:00
if existing is None :
2015-06-09 11:54:21 +00:00
existing = dict ( self . plugins )
2015-04-02 21:02:42 +00:00
result = dict ( )
2014-09-10 13:44:53 +00:00
if self . plugin_folders :
2015-05-29 14:25:18 +00:00
result . update ( self . _find_plugins_from_folders ( self . plugin_folders , existing , ignored_uninstalled = ignore_uninstalled ) )
2014-09-10 13:44:53 +00:00
if self . plugin_entry_points :
2015-06-09 11:54:21 +00:00
existing . update ( result )
2015-05-29 14:25:18 +00:00
result . update ( self . _find_plugins_from_entry_points ( self . plugin_entry_points , existing , ignore_uninstalled = ignore_uninstalled ) )
2015-04-02 21:02:42 +00:00
return result
2015-05-29 14:25:18 +00:00
def _find_plugins_from_folders ( self , folders , existing , ignored_uninstalled = True ) :
2015-04-02 21:02:42 +00:00
result = dict ( )
2014-09-05 15:11:11 +00:00
2014-09-10 13:44:53 +00:00
for folder in folders :
2015-01-26 21:21:58 +00:00
readonly = False
if isinstance ( folder , ( list , tuple ) ) :
if len ( folder ) == 2 :
folder , readonly = folder
else :
continue
2014-09-05 15:11:11 +00:00
if not os . path . exists ( folder ) :
self . logger . warn ( " Plugin folder {folder} could not be found, skipping it " . format ( folder = folder ) )
continue
entries = os . listdir ( folder )
for entry in entries :
path = os . path . join ( folder , entry )
if os . path . isdir ( path ) and os . path . isfile ( os . path . join ( path , " __init__.py " ) ) :
2014-09-10 13:44:53 +00:00
key = entry
2014-09-05 15:11:11 +00:00
elif os . path . isfile ( path ) and entry . endswith ( " .py " ) :
2014-09-10 13:44:53 +00:00
key = entry [ : - 3 ] # strip off the .py extension
2014-09-05 15:11:11 +00:00
else :
continue
2015-05-29 14:25:18 +00:00
if key in existing or key in result or ( ignored_uninstalled and key in self . marked_plugins [ " uninstalled " ] ) :
2014-09-05 15:11:11 +00:00
# plugin is already defined, ignore it
continue
2015-04-01 08:55:13 +00:00
plugin = self . _import_plugin_from_module ( key , folder = folder )
2014-09-10 13:44:53 +00:00
if plugin :
2015-06-03 08:44:27 +00:00
plugin . origin = FolderOrigin ( " folder " , folder )
2015-01-26 21:21:58 +00:00
if readonly :
plugin . bundled = True
2015-04-02 21:02:42 +00:00
plugin . enabled = False
result [ key ] = plugin
2014-09-10 13:44:53 +00:00
2015-04-02 21:02:42 +00:00
return result
2015-05-29 14:25:18 +00:00
def _find_plugins_from_entry_points ( self , groups , existing , ignore_uninstalled = True ) :
2015-04-02 21:02:42 +00:00
result = dict ( )
2014-09-10 13:44:53 +00:00
2015-04-02 21:02:42 +00:00
# let's make sure we have a current working set
working_set = pkg_resources . WorkingSet ( )
2014-09-10 13:44:53 +00:00
if not isinstance ( groups , ( list , tuple ) ) :
groups = [ groups ]
for group in groups :
2015-04-02 21:02:42 +00:00
for entry_point in working_set . iter_entry_points ( group = group , name = None ) :
2014-09-10 13:44:53 +00:00
key = entry_point . name
module_name = entry_point . module_name
version = entry_point . dist . version
2015-05-29 14:25:18 +00:00
if key in existing or key in result or ( ignore_uninstalled and key in self . marked_plugins [ " uninstalled " ] ) :
# plugin is already defined or marked as uninstalled, ignore it
2014-09-10 13:44:53 +00:00
continue
2015-01-26 16:27:34 +00:00
kwargs = dict ( module_name = module_name , version = version )
2015-05-29 14:25:18 +00:00
package_name = None
2015-01-26 16:27:34 +00:00
try :
2015-04-16 16:37:03 +00:00
module_pkginfo = InstalledEntryPoint ( entry_point )
2015-01-26 16:27:34 +00:00
except :
self . logger . exception ( " Something went wrong while retrieving package info data for module %s " % module_name )
else :
kwargs . update ( dict (
name = module_pkginfo . name ,
summary = module_pkginfo . summary ,
author = module_pkginfo . author ,
2015-01-26 21:21:58 +00:00
url = module_pkginfo . home_page ,
license = module_pkginfo . license
2015-01-26 16:27:34 +00:00
) )
2015-04-27 16:43:00 +00:00
package_name = module_pkginfo . name
2015-01-26 16:27:34 +00:00
2015-04-01 08:55:13 +00:00
plugin = self . _import_plugin_from_module ( key , * * kwargs )
2014-09-10 13:44:53 +00:00
if plugin :
2015-06-03 08:44:27 +00:00
plugin . origin = EntryPointOrigin ( " entry_point " , group , module_name , package_name , version )
2015-04-02 21:02:42 +00:00
plugin . enabled = False
result [ key ] = plugin
2014-09-05 15:11:11 +00:00
2015-04-02 21:02:42 +00:00
return result
2014-09-05 15:11:11 +00:00
2015-04-01 08:55:13 +00:00
def _import_plugin_from_module ( self , key , folder = None , module_name = None , name = None , version = None , summary = None , author = None , url = None , license = None ) :
2014-09-10 13:44:53 +00:00
# TODO error handling
2015-01-26 21:21:58 +00:00
try :
if folder :
module = imp . find_module ( key , [ folder ] )
elif module_name :
module = imp . find_module ( module_name )
else :
return None
except :
self . logger . warn ( " Could not locate plugin {key} " )
2014-09-10 13:44:53 +00:00
return None
2015-04-01 08:55:13 +00:00
plugin = self . _import_plugin ( key , * module , name = name , version = version , summary = summary , author = author , url = url , license = license )
2015-04-02 21:02:42 +00:00
if plugin is None :
return None
if plugin . check ( ) :
return plugin
else :
self . logger . warn ( " Plugin \" {plugin} \" did not pass check " . format ( plugin = str ( plugin ) ) )
return None
2014-09-10 13:44:53 +00:00
2015-01-26 21:21:58 +00:00
2015-04-01 08:55:13 +00:00
def _import_plugin ( self , key , f , filename , description , name = None , version = None , summary = None , author = None , url = None , license = None ) :
2014-09-11 10:17:45 +00:00
try :
instance = imp . load_module ( key , f , filename , description )
2015-01-26 21:21:58 +00:00
return PluginInfo ( key , filename , instance , name = name , version = version , description = summary , author = author , url = url , license = license )
2014-09-11 10:17:45 +00:00
except :
2015-04-02 21:02:42 +00:00
self . logger . exception ( " Error loading plugin {key} " . format ( key = key ) )
2014-09-11 10:17:45 +00:00
return None
2014-09-05 15:11:11 +00:00
2014-09-10 20:28:03 +00:00
def _is_plugin_disabled ( self , key ) :
return key in self . plugin_disabled_list or key . endswith ( ' disabled ' )
2014-09-05 15:11:11 +00:00
2015-05-29 14:25:18 +00:00
def reload_plugins ( self , startup = False , initialize_implementations = True , force_reload = None ) :
2015-04-01 09:51:08 +00:00
self . logger . info ( " Loading plugins from {folders} and installed plugin packages... " . format (
folders = " , " . join ( map ( lambda x : x [ 0 ] if isinstance ( x , tuple ) else str ( x ) , self . plugin_folders ) )
) )
2015-01-30 17:06:52 +00:00
2015-05-29 14:25:18 +00:00
if force_reload is None :
force_reload = [ ]
plugins = self . find_plugins ( existing = dict ( ( k , v ) for k , v in self . plugins . items ( ) if not k in force_reload ) )
2015-04-02 21:02:42 +00:00
self . disabled_plugins . update ( plugins )
2015-02-05 18:20:51 +00:00
2015-04-01 08:55:13 +00:00
for name , plugin in plugins . items ( ) :
2015-04-02 21:02:42 +00:00
try :
self . load_plugin ( name , plugin , startup = startup , initialize_implementation = initialize_implementations )
if not self . _is_plugin_disabled ( name ) :
self . enable_plugin ( name , plugin = plugin , initialize_implementation = initialize_implementations , startup = startup )
2015-05-28 10:56:42 +00:00
except PluginNeedsRestart :
2015-04-02 21:02:42 +00:00
pass
2015-05-28 10:56:42 +00:00
except PluginLifecycleException as e :
self . logger . info ( str ( e ) )
2014-09-05 15:11:11 +00:00
2015-04-14 15:55:46 +00:00
if len ( self . enabled_plugins ) < = 0 :
2015-03-25 17:56:11 +00:00
self . logger . info ( " No plugins found " )
else :
self . logger . info ( " Found {count} plugin(s) providing {implementations} mixin implementations, {hooks} hook handlers " . format (
2015-04-14 15:55:46 +00:00
count = len ( self . enabled_plugins ) + len ( self . disabled_plugins ) ,
2015-03-30 14:50:06 +00:00
implementations = len ( self . plugin_implementations ) ,
2015-03-25 17:56:11 +00:00
hooks = sum ( map ( lambda x : len ( x ) , self . plugin_hooks . values ( ) ) )
) )
2014-11-19 10:33:20 +00:00
2015-05-29 14:25:18 +00:00
def mark_plugin ( self , name , uninstalled = None ) :
if not name in self . plugins :
self . logger . warn ( " Trying to mark an unknown plugin {name} " . format ( * * locals ( ) ) )
if uninstalled is not None :
if uninstalled and not name in self . marked_plugins [ " uninstalled " ] :
self . marked_plugins [ " uninstalled " ] . append ( name )
elif not uninstalled and name in self . marked_plugins [ " uninstalled " ] :
self . marked_plugins [ " uninstalled " ] . remove ( name )
2015-04-02 21:02:42 +00:00
def load_plugin ( self , name , plugin = None , startup = False , initialize_implementation = True ) :
2015-04-14 15:55:46 +00:00
if not name in self . plugins :
2015-04-02 21:02:42 +00:00
self . logger . warn ( " Trying to load an unknown plugin {name} " . format ( * * locals ( ) ) )
return
if plugin is None :
2015-04-14 15:55:46 +00:00
plugin = self . plugins [ name ]
2015-04-01 08:55:13 +00:00
try :
2015-06-09 16:50:55 +00:00
plugin . validate ( " before_load " , additional_validators = self . plugin_validators )
2015-04-01 08:55:13 +00:00
plugin . load ( )
2015-06-09 16:50:55 +00:00
plugin . validate ( " after_load " , additional_validators = self . plugin_validators )
2015-04-01 08:55:13 +00:00
self . on_plugin_loaded ( name , plugin )
2015-04-02 21:02:42 +00:00
plugin . loaded = True
2015-04-01 08:55:13 +00:00
self . logger . debug ( " Loaded plugin {name} : {plugin} " . format ( * * locals ( ) ) )
2015-04-02 21:02:42 +00:00
except PluginLifecycleException as e :
raise e
except :
self . logger . exception ( " There was an error loading plugin %s " % name )
2015-04-01 08:55:13 +00:00
def unload_plugin ( self , name ) :
2015-04-14 15:55:46 +00:00
if not name in self . plugins :
2015-04-02 21:02:42 +00:00
self . logger . warn ( " Trying to unload unknown plugin {name} " . format ( * * locals ( ) ) )
return
2015-04-14 15:55:46 +00:00
plugin = self . plugins [ name ]
2015-04-01 08:55:13 +00:00
try :
2015-04-02 21:02:42 +00:00
if plugin . enabled :
self . disable_plugin ( name , plugin = plugin )
2015-04-01 08:55:13 +00:00
plugin . unload ( )
2015-04-02 21:02:42 +00:00
self . on_plugin_unloaded ( name , plugin )
2015-04-14 15:55:46 +00:00
if name in self . enabled_plugins :
del self . enabled_plugins [ name ]
2015-04-02 21:02:42 +00:00
2015-04-01 08:55:13 +00:00
if name in self . disabled_plugins :
del self . disabled_plugins [ name ]
2015-04-02 21:02:42 +00:00
plugin . loaded = False
self . logger . debug ( " Unloaded plugin {name} : {plugin} " . format ( * * locals ( ) ) )
except PluginLifecycleException as e :
raise e
2015-04-01 08:55:13 +00:00
except :
self . logger . exception ( " There was an error unloading plugin {name} " . format ( * * locals ( ) ) )
2015-04-02 21:02:42 +00:00
# make sure the plugin is NOT in the list of enabled plugins but in the list of disabled plugins
2015-04-14 15:55:46 +00:00
if name in self . enabled_plugins :
del self . enabled_plugins [ name ]
2015-04-02 21:02:42 +00:00
if not name in self . disabled_plugins :
self . disabled_plugins [ name ] = plugin
def enable_plugin ( self , name , plugin = None , initialize_implementation = True , startup = False ) :
2015-04-01 08:55:13 +00:00
if not name in self . disabled_plugins :
self . logger . warn ( " Tried to enable plugin {name} , however it is not disabled " . format ( * * locals ( ) ) )
return
2015-04-02 21:02:42 +00:00
if plugin is None :
plugin = self . disabled_plugins [ name ]
2015-05-11 13:47:40 +00:00
if not startup and self . is_restart_needing_plugin ( plugin ) :
2015-04-02 21:02:42 +00:00
raise PluginNeedsRestart ( name )
2015-04-01 08:55:13 +00:00
2015-05-28 10:56:42 +00:00
if self . has_obsolete_hooks ( plugin ) :
raise PluginCantEnable ( name , " Dependency on obsolete hooks detected, full functionality cannot be guaranteed " )
2015-04-01 08:55:13 +00:00
try :
plugin . enable ( )
self . _activate_plugin ( name , plugin )
2015-04-02 21:02:42 +00:00
except PluginLifecycleException as e :
raise e
2015-04-01 08:55:13 +00:00
except :
self . logger . exception ( " There was an error while enabling plugin {name} " . format ( * * locals ( ) ) )
2015-04-02 21:02:42 +00:00
return False
2015-04-01 08:55:13 +00:00
else :
2015-04-02 21:02:42 +00:00
if name in self . disabled_plugins :
del self . disabled_plugins [ name ]
2015-04-14 15:55:46 +00:00
self . enabled_plugins [ name ] = plugin
2015-04-02 21:02:42 +00:00
plugin . enabled = True
if plugin . implementation :
if initialize_implementation :
if not self . initialize_implementation_of_plugin ( name , plugin ) :
return False
plugin . implementation . on_plugin_enabled ( )
2015-04-01 08:55:13 +00:00
self . on_plugin_enabled ( name , plugin )
2015-04-02 21:02:42 +00:00
2015-04-01 08:55:13 +00:00
self . logger . debug ( " Enabled plugin {name} : {plugin} " . format ( * * locals ( ) ) )
2015-04-02 21:02:42 +00:00
return True
def disable_plugin ( self , name , plugin = None ) :
2015-04-14 15:55:46 +00:00
if not name in self . enabled_plugins :
2015-04-01 08:55:13 +00:00
self . logger . warn ( " Tried to disable plugin {name} , however it is not enabled " . format ( * * locals ( ) ) )
return
2015-04-02 21:02:42 +00:00
if plugin is None :
2015-04-14 15:55:46 +00:00
plugin = self . enabled_plugins [ name ]
2015-04-02 21:02:42 +00:00
2015-05-11 13:47:40 +00:00
if self . is_restart_needing_plugin ( plugin ) :
2015-04-02 21:02:42 +00:00
raise PluginNeedsRestart ( name )
2015-04-01 08:55:13 +00:00
try :
plugin . disable ( )
self . _deactivate_plugin ( name , plugin )
2015-04-02 21:02:42 +00:00
except PluginLifecycleException as e :
raise e
2015-04-01 08:55:13 +00:00
except :
self . logger . exception ( " There was an error while disabling plugin {name} " . format ( * * locals ( ) ) )
2015-04-02 21:02:42 +00:00
return False
2015-04-01 08:55:13 +00:00
else :
2015-04-14 15:55:46 +00:00
if name in self . enabled_plugins :
del self . enabled_plugins [ name ]
2015-04-02 21:02:42 +00:00
self . disabled_plugins [ name ] = plugin
plugin . enabled = False
if plugin . implementation :
plugin . implementation . on_plugin_disabled ( )
2015-04-01 08:55:13 +00:00
self . on_plugin_disabled ( name , plugin )
2015-04-02 21:02:42 +00:00
2015-04-01 08:55:13 +00:00
self . logger . debug ( " Disabled plugin {name} : {plugin} " . format ( * * locals ( ) ) )
2015-04-02 21:02:42 +00:00
return True
2015-04-01 08:55:13 +00:00
2015-04-02 21:02:42 +00:00
def _activate_plugin ( self , name , plugin ) :
2015-05-11 13:47:40 +00:00
plugin . hotchangeable = self . is_restart_needing_plugin ( plugin )
2015-04-01 08:55:13 +00:00
# evaluate registered hooks
2015-09-04 14:08:57 +00:00
for hook , definition in plugin . hooks . items ( ) :
try :
callback , order = self . _get_callback_and_order ( definition )
except ValueError as e :
self . logger . warn ( " There is something wrong with the hook definition {} for plugin {} : {} " . format ( definition , name , str ( e ) ) )
continue
2015-09-04 09:25:27 +00:00
self . _plugin_hooks [ hook ] . append ( ( order , name , callback ) )
self . _sort_hooks ( hook )
2015-04-01 08:55:13 +00:00
2015-04-01 09:51:08 +00:00
# evaluate registered implementation
if plugin . implementation :
for plugin_type in self . plugin_types :
if isinstance ( plugin . implementation , plugin_type ) :
self . plugin_implementations_by_type [ plugin_type ] . append ( ( name , plugin . implementation ) )
2015-04-01 08:55:13 +00:00
2015-04-01 09:51:08 +00:00
self . plugin_implementations [ name ] = plugin . implementation
2015-04-01 08:55:13 +00:00
def _deactivate_plugin ( self , name , plugin ) :
2015-09-04 14:08:57 +00:00
for hook , definition in plugin . hooks . items ( ) :
try :
callback , order = self . _get_callback_and_order ( definition )
except ValueError as e :
self . logger . warn ( " There is something wrong with the hook definition {} for plugin {} : {} " . format ( definition , name , str ( e ) ) )
continue
2015-09-04 09:25:27 +00:00
2015-04-02 21:02:42 +00:00
try :
2015-09-04 09:25:27 +00:00
self . _plugin_hooks [ hook ] . remove ( ( order , name , callback ) )
self . _sort_hooks ( hook )
2015-04-02 21:02:42 +00:00
except ValueError :
# that's ok, the plugin was just not registered for the hook
pass
2015-04-01 08:55:13 +00:00
2015-04-01 09:51:08 +00:00
if plugin . implementation is not None :
2015-04-02 21:02:42 +00:00
if name in self . plugin_implementations :
del self . plugin_implementations [ name ]
2015-04-01 09:51:08 +00:00
for plugin_type in self . plugin_types :
try :
self . plugin_implementations_by_type [ plugin_type ] . remove ( ( name , plugin . implementation ) )
except ValueError :
# that's ok, the plugin was just not registered for the type
pass
2015-04-01 08:55:13 +00:00
2015-05-11 13:47:40 +00:00
def is_restart_needing_plugin ( self , plugin ) :
return self . has_restart_needing_implementation ( plugin ) or self . has_restart_needing_hooks ( plugin )
def has_restart_needing_implementation ( self , plugin ) :
if not plugin . implementation :
return False
return isinstance ( plugin . implementation , RestartNeedingPlugin )
def has_restart_needing_hooks ( self , plugin ) :
if not plugin . hooks :
return False
hooks = plugin . hooks . keys ( )
for hook in hooks :
if self . is_restart_needing_hook ( hook ) :
return True
return False
2015-05-28 10:56:42 +00:00
def has_obsolete_hooks ( self , plugin ) :
if not plugin . hooks :
return False
hooks = plugin . hooks . keys ( )
for hook in hooks :
if self . is_obsolete_hook ( hook ) :
return True
return False
2015-05-11 13:47:40 +00:00
def is_restart_needing_hook ( self , hook ) :
if self . plugin_restart_needing_hooks is None :
return False
for h in self . plugin_restart_needing_hooks :
if hook . startswith ( h ) :
return True
return False
2015-05-28 10:56:42 +00:00
def is_obsolete_hook ( self , hook ) :
if self . plugin_obsolete_hooks is None :
return False
return hook in self . plugin_obsolete_hooks
2015-06-19 09:09:08 +00:00
def initialize_implementations ( self , additional_injects = None , additional_inject_factories = None , additional_pre_inits = None , additional_post_inits = None ) :
2015-04-14 15:55:46 +00:00
for name , plugin in self . enabled_plugins . items ( ) :
2015-04-01 09:51:08 +00:00
self . initialize_implementation_of_plugin ( name , plugin ,
additional_injects = additional_injects ,
2015-06-19 09:09:08 +00:00
additional_inject_factories = additional_inject_factories ,
additional_pre_inits = additional_pre_inits ,
additional_post_inits = additional_post_inits )
2015-04-01 08:55:13 +00:00
self . logger . info ( " Initialized {count} plugin(s) " . format ( count = len ( self . plugin_implementations ) ) )
2015-06-19 09:09:08 +00:00
def initialize_implementation_of_plugin ( self , name , plugin , additional_injects = None , additional_inject_factories = None , additional_pre_inits = None , additional_post_inits = None ) :
2015-04-01 09:51:08 +00:00
if plugin . implementation is None :
2015-04-01 08:55:13 +00:00
return
2015-04-02 21:02:42 +00:00
return self . initialize_implementation ( name , plugin , plugin . implementation ,
2015-04-01 09:51:08 +00:00
additional_injects = additional_injects ,
2015-06-19 09:09:08 +00:00
additional_inject_factories = additional_inject_factories ,
additional_pre_inits = additional_pre_inits ,
additional_post_inits = additional_post_inits )
2015-04-01 08:55:13 +00:00
2015-06-19 09:09:08 +00:00
def initialize_implementation ( self , name , plugin , implementation , additional_injects = None , additional_inject_factories = None , additional_pre_inits = None , additional_post_inits = None ) :
2015-01-15 10:06:59 +00:00
if additional_injects is None :
additional_injects = dict ( )
2015-01-30 12:12:25 +00:00
if additional_inject_factories is None :
additional_inject_factories = [ ]
2015-06-19 09:09:08 +00:00
if additional_pre_inits is None :
additional_pre_inits = [ ]
if additional_post_inits is None :
additional_post_inits = [ ]
2015-01-15 10:06:59 +00:00
2015-04-01 08:55:13 +00:00
injects = self . implementation_injects
injects . update ( additional_injects )
2015-01-30 12:12:25 +00:00
2015-04-01 08:55:13 +00:00
inject_factories = self . implementation_inject_factories
inject_factories + = additional_inject_factories
2015-01-30 12:12:25 +00:00
2015-06-19 09:09:08 +00:00
pre_inits = self . implementation_pre_inits
pre_inits + = additional_pre_inits
post_inits = self . implementation_post_inits
post_inits + = additional_post_inits
2015-04-01 08:55:13 +00:00
try :
kwargs = dict ( injects )
kwargs . update ( dict (
identifier = name ,
plugin_name = plugin . name ,
plugin_version = plugin . version ,
basefolder = os . path . realpath ( plugin . location ) ,
logger = logging . getLogger ( self . logging_prefix + name ) ,
) )
# inject the additional_injects
for arg , value in kwargs . items ( ) :
setattr ( implementation , " _ " + arg , value )
# inject any injects produced in the additional_inject_factories
for factory in inject_factories :
try :
return_value = factory ( name , implementation )
2015-01-15 10:06:59 +00:00
except :
2015-04-01 08:55:13 +00:00
self . logger . exception ( " Exception while executing injection factory %r " % factory )
else :
if return_value is not None :
if isinstance ( return_value , dict ) :
for arg , value in return_value . items ( ) :
setattr ( implementation , " _ " + arg , value )
2015-06-19 09:09:08 +00:00
# execute any additional pre init methods
for pre_init in pre_inits :
pre_init ( name , implementation )
2015-04-02 21:02:42 +00:00
implementation . initialize ( )
2015-06-19 09:09:08 +00:00
# execute any additional post init methods
for post_init in post_inits :
post_init ( name , implementation )
2015-04-02 21:02:42 +00:00
except Exception as e :
2015-04-01 08:55:13 +00:00
self . _deactivate_plugin ( name , plugin )
2015-04-02 21:02:42 +00:00
plugin . enabled = False
if isinstance ( e , PluginLifecycleException ) :
raise e
else :
self . logger . exception ( " Exception while initializing plugin {name} , disabling it " . format ( * * locals ( ) ) )
return False
2015-04-01 08:55:13 +00:00
else :
self . on_plugin_implementations_initialized ( name , plugin )
2015-01-15 10:06:59 +00:00
2015-04-01 09:51:08 +00:00
self . logger . debug ( " Initialized plugin mixin implementation for plugin {name} " . format ( * * locals ( ) ) )
2015-04-02 21:02:42 +00:00
return True
2015-01-15 10:06:59 +00:00
2015-04-01 08:55:13 +00:00
2015-03-25 17:56:11 +00:00
def log_all_plugins ( self , show_bundled = True , bundled_str = ( " (bundled) " , " " ) , show_location = True , location_str = " = {location} " , show_enabled = True , enabled_str = ( " " , " ! " ) ) :
2015-04-14 15:55:46 +00:00
all_plugins = self . enabled_plugins . values ( ) + self . disabled_plugins . values ( )
2014-09-05 15:11:11 +00:00
2015-03-25 17:56:11 +00:00
if len ( all_plugins ) < = 0 :
self . logger . info ( " No plugins available " )
else :
self . logger . info ( " {count} plugin(s) registered with the system: \n {plugins} " . format ( count = len ( all_plugins ) , plugins = " \n " . join (
sorted (
map ( lambda x : " | " + x . long_str ( show_bundled = show_bundled ,
2015-04-14 15:55:46 +00:00
bundled_strs = bundled_str ,
2015-03-25 17:56:11 +00:00
show_location = show_location ,
location_str = location_str ,
show_enabled = show_enabled ,
2015-04-14 15:55:46 +00:00
enabled_strs = enabled_str ) ,
self . enabled_plugins . values ( ) )
2015-03-25 17:56:11 +00:00
)
) ) )
2015-01-26 21:21:58 +00:00
2015-03-30 14:50:06 +00:00
def get_plugin ( self , identifier , require_enabled = True ) :
"""
Retrieves the module of the plugin identified by ` ` identifier ` ` . If the plugin is not registered or disabled and
` ` required_enabled ` ` is True ( the default ) None will be returned .
Arguments :
identifier ( str ) : The identifier of the plugin to retrieve .
require_enabled ( boolean ) : Whether to only return the plugin if is enabled ( True , default ) or also if it ' s
disabled .
Returns :
module : The requested plugin module or None
"""
plugin_info = self . get_plugin_info ( identifier , require_enabled = require_enabled )
if plugin_info is not None :
return plugin_info . instance
return None
def get_plugin_info ( self , identifier , require_enabled = True ) :
"""
Retrieves the : class : ` PluginInfo ` instance identified by ` ` identifier ` ` . If the plugin is not registered or
disabled and ` ` required_enabled ` ` is True ( the default ) None will be returned .
Arguments :
identifier ( str ) : The identifier of the plugin to retrieve .
require_enabled ( boolean ) : Whether to only return the plugin if is enabled ( True , default ) or also if it ' s
disabled .
Returns :
~ . PluginInfo : The requested : class : ` PluginInfo ` or None
"""
2015-04-14 15:55:46 +00:00
if identifier in self . enabled_plugins :
return self . enabled_plugins [ identifier ]
2015-03-30 14:50:06 +00:00
elif not require_enabled and identifier in self . disabled_plugins :
return self . disabled_plugins [ identifier ]
return None
2014-09-05 15:11:11 +00:00
def get_hooks ( self , hook ) :
2015-03-30 14:50:06 +00:00
"""
Retrieves all registered handlers for the specified hook .
Arguments :
hook ( str ) : The hook for which to retrieve the handlers .
Returns :
dict : A dict containing all registered handlers mapped by their plugin ' s identifier.
"""
2014-09-05 15:11:11 +00:00
if not hook in self . plugin_hooks :
2014-11-26 15:53:35 +00:00
return dict ( )
2015-09-04 09:25:27 +00:00
result = OrderedDict ( )
for h in self . plugin_hooks [ hook ] :
result [ h [ 0 ] ] = h [ 1 ]
return result
2014-09-05 15:11:11 +00:00
2015-09-04 13:01:35 +00:00
def get_implementations ( self , * types , * * kwargs ) :
2015-03-30 14:50:06 +00:00
"""
Get all mixin implementations that implement * all * of the provided ` ` types ` ` .
Arguments :
types ( one or more type ) : The types a mixin implementation needs to implement in order to be returned .
Returns :
list : A list of all found implementations
"""
2015-09-04 13:01:35 +00:00
sorting_context = kwargs . get ( " sorting_context " , None )
2014-09-05 15:11:11 +00:00
result = None
for t in types :
2015-01-15 10:06:59 +00:00
implementations = self . plugin_implementations_by_type [ t ]
2014-09-05 15:11:11 +00:00
if result is None :
result = set ( implementations )
else :
result = result . intersection ( implementations )
if result is None :
2015-09-04 13:01:35 +00:00
return [ ]
2015-09-04 13:38:15 +00:00
def sort_func ( impl ) :
sorting_value = None
if sorting_context is not None and isinstance ( impl [ 1 ] , SorteablePlugin ) :
try :
sorting_value = impl [ 1 ] . get_sorting_key ( sorting_context )
except :
self . logger . exception ( " Error while trying to retrieve sorting order for plugin {} " . format ( impl [ 0 ] ) )
2015-03-30 14:50:06 +00:00
2015-09-04 14:08:57 +00:00
try :
int ( sorting_value )
except ValueError :
self . logger . warn ( " The order value returned by {} for sorting context {} is not a valid integer, ignoring it " . format ( impl [ 0 ] , sorting_context ) )
sorting_value = None
2015-09-04 13:38:15 +00:00
return sorting_value is None , sorting_value , impl [ 0 ]
2015-09-04 13:01:35 +00:00
2015-09-04 13:38:15 +00:00
return [ impl [ 1 ] for impl in sorted ( result , key = sort_func ) ]
2015-09-04 13:01:35 +00:00
2015-09-04 13:54:48 +00:00
def get_filtered_implementations ( self , f , * types , * * kwargs ) :
2015-03-30 14:50:06 +00:00
"""
Get all mixin implementation that implementat * all * of the provided ` ` types ` ` and match the provided filter ` f ` .
Arguments :
f ( callable ) : A filter function returning True for implementations to return and False for those to exclude .
types ( one or more type ) : The types a mixin implementation needs to implement in order to be returned .
Returns :
list : A list of all found and matching implementations .
"""
assert callable ( f )
2015-09-04 13:54:48 +00:00
implementations = self . get_implementations ( * types , sorting_context = kwargs . get ( " sorting_context " , None ) )
2015-03-30 14:50:06 +00:00
return filter ( f , implementations )
2014-09-05 15:11:11 +00:00
2014-09-12 09:21:39 +00:00
def get_helpers ( self , name , * helpers ) :
2015-03-30 14:50:06 +00:00
"""
Retrieves the named ` ` helpers ` ` for the plugin with identifier ` ` name ` ` .
If the plugin is not available , returns None . Otherwise returns a : class : ` dict ` with the requested plugin
helper names mapped to the method - if a helper could not be resolved , it will be missing from the dict .
Arguments :
name ( str ) : Identifier of the plugin for which to look up the ` ` helpers ` ` .
helpers ( one or more str ) : Identifiers of the helpers of plugin ` ` name ` ` to return .
Returns :
dict : A dictionary of all resolved helpers , mapped by their identifiers , or None if the plugin was not
registered with the system .
"""
2015-04-14 15:55:46 +00:00
if not name in self . enabled_plugins :
2014-09-10 20:28:03 +00:00
return None
2015-04-14 15:55:46 +00:00
plugin = self . enabled_plugins [ name ]
2014-09-10 20:28:03 +00:00
all_helpers = plugin . helpers
2014-09-12 09:21:39 +00:00
if len ( helpers ) :
2014-09-10 20:28:03 +00:00
return dict ( ( k , v ) for ( k , v ) in all_helpers . items ( ) if k in helpers )
else :
return all_helpers
2015-03-30 14:50:06 +00:00
def register_message_receiver ( self , client ) :
"""
Registers a ` ` client ` ` for receiving plugin messages . The ` ` client ` ` needs to be a callable accepting two
input arguments , ` ` plugin ` ` ( the sending plugin ' s identifier) and ``data`` (the message itself).
"""
2014-11-25 08:08:33 +00:00
if client is None :
return
self . registered_clients . append ( client )
2015-03-30 14:50:06 +00:00
def unregister_message_receiver ( self , client ) :
"""
Unregisters a ` ` client ` ` for receiving plugin messages .
"""
2014-11-25 08:08:33 +00:00
self . registered_clients . remove ( client )
def send_plugin_message ( self , plugin , data ) :
2015-03-30 14:50:06 +00:00
"""
Sends ` ` data ` ` in the name of ` ` plugin ` ` to all currently registered message receivers by invoking them
with the two arguments .
Arguments :
plugin ( str ) : The sending plugin ' s identifier.
data ( object ) : The message .
"""
2014-11-25 08:08:33 +00:00
for client in self . registered_clients :
2015-03-30 14:50:06 +00:00
try : client ( plugin , data )
2014-11-25 08:08:33 +00:00
except : self . logger . exception ( " Exception while sending plugin data to client " )
2015-09-04 09:25:27 +00:00
def _sort_hooks ( self , hook ) :
self . _plugin_hooks [ hook ] = sorted ( self . _plugin_hooks [ hook ] ,
key = lambda x : ( x [ 0 ] is None , x [ 0 ] , x [ 1 ] , x [ 2 ] ) )
2015-09-04 14:08:57 +00:00
def _get_callback_and_order ( self , hook ) :
if callable ( hook ) :
return hook , None
elif isinstance ( hook , tuple ) and len ( hook ) == 2 :
callback , order = hook
# test that callback is a callable
if not callable ( callback ) :
raise ValueError ( " Hook callback is not a callable " )
# test that number is an int
try :
int ( order )
except ValueError :
raise ValueError ( " Hook order is not a number " )
return callback , order
else :
raise ValueError ( " Invalid hook definition, neither a callable nor a 2-tuple (callback, order): {!r} " . format ( hook ) )
2014-09-05 15:11:11 +00:00
2015-04-16 16:37:03 +00:00
class InstalledEntryPoint ( pkginfo . Installed ) :
def __init__ ( self , entry_point , metadata_version = None ) :
self . entry_point = entry_point
package = entry_point . module_name
pkginfo . Installed . __init__ ( self , package , metadata_version = metadata_version )
def read ( self ) :
import sys
import glob
import warnings
opj = os . path . join
if self . package is not None :
package = self . package . __package__
if package is None :
package = self . package . __name__
project = pkg_resources . to_filename ( pkg_resources . safe_name ( self . entry_point . dist . project_name ) )
package_pattern = ' %s *.egg-info ' % package
project_pattern = ' %s *.egg-info ' % project
file = getattr ( self . package , ' __file__ ' , None )
if file is not None :
candidates = [ ]
def _add_candidate ( where ) :
candidates . extend ( glob . glob ( where ) )
for entry in sys . path :
if file . startswith ( entry ) :
_add_candidate ( opj ( entry , ' EGG-INFO ' ) ) # egg?
for pattern in ( package_pattern , project_pattern ) : # dist-installed?
_add_candidate ( opj ( entry , pattern ) )
dir , name = os . path . split ( self . package . __file__ )
for pattern in ( package_pattern , project_pattern ) :
_add_candidate ( opj ( dir , pattern ) )
_add_candidate ( opj ( dir , ' .. ' , pattern ) )
for candidate in candidates :
if os . path . isdir ( candidate ) :
path = opj ( candidate , ' PKG-INFO ' )
else :
path = candidate
if os . path . exists ( path ) :
with open ( path ) as f :
return f . read ( )
warnings . warn ( ' No PKG-INFO found for package: %s ' % self . package_name )
2014-09-05 15:11:11 +00:00
class Plugin ( object ) :
2015-02-27 19:28:24 +00:00
"""
The parent class of all plugin implementations .
. . attribute : : _identifier
The identifier of the plugin . Injected by the plugin core system upon initialization of the implementation .
. . attribute : : _plugin_name
The name of the plugin . Injected by the plugin core system upon initialization of the implementation .
. . attribute : : _plugin_version
The version of the plugin . Injected by the plugin core system upon initialization of the implementation .
. . attribute : : _basefolder
The base folder of the plugin . Injected by the plugin core system upon initialization of the implementation .
. . attribute : : _logger
The logger instance to use , with the logging name set to the : attr : ` PluginManager . logging_prefix ` of the
: class : ` PluginManager ` concatenated with : attr : ` _identifier ` . Injected by the plugin core system upon
initialization of the implementation .
"""
2015-01-15 10:06:59 +00:00
def initialize ( self ) :
2015-02-27 19:28:24 +00:00
"""
2015-03-20 10:33:54 +00:00
Called by the plugin core after performing all injections . Override this to initialize your implementation .
2015-02-27 19:28:24 +00:00
"""
2015-01-15 10:06:59 +00:00
pass
2015-04-02 21:02:42 +00:00
def on_plugin_enabled ( self ) :
pass
def on_plugin_disabled ( self ) :
pass
class RestartNeedingPlugin ( Plugin ) :
pass
2015-09-04 13:01:35 +00:00
class SorteablePlugin ( Plugin ) :
def get_sorting_key ( self , context = None ) :
return None
2015-06-28 00:19:28 +00:00
class PluginNeedsRestart ( Exception ) :
2015-04-02 21:02:42 +00:00
def __init__ ( self , name ) :
2015-06-28 00:19:28 +00:00
Exception . __init__ ( self )
2015-04-02 21:02:42 +00:00
self . name = name
self . message = " Plugin {name} cannot be enabled or disabled after system startup " . format ( * * locals ( ) )
2015-06-28 00:19:28 +00:00
class PluginLifecycleException ( Exception ) :
2015-04-02 21:02:42 +00:00
def __init__ ( self , name , reason , message ) :
2015-06-28 00:19:28 +00:00
Exception . __init__ ( self )
2015-04-02 21:02:42 +00:00
self . name = name
self . reason = reason
self . message = message . format ( * * locals ( ) )
2015-05-28 10:56:42 +00:00
def __str__ ( self ) :
return self . message
2015-04-02 21:02:42 +00:00
class PluginCantInitialize ( PluginLifecycleException ) :
def __init__ ( self , name , reason ) :
2015-06-19 13:25:37 +00:00
PluginLifecycleException . __init__ ( self , name , reason , " Plugin {name} cannot be initialized: {reason} " )
2015-04-02 21:02:42 +00:00
class PluginCantEnable ( PluginLifecycleException ) :
def __init__ ( self , name , reason ) :
2015-05-28 10:56:42 +00:00
PluginLifecycleException . __init__ ( self , name , reason , " Plugin {name} cannot be enabled: {reason} " )
2015-04-02 21:02:42 +00:00
class PluginCantDisable ( PluginLifecycleException ) :
def __init__ ( self , name , reason ) :
2015-06-19 13:25:37 +00:00
PluginLifecycleException . __init__ ( self , name , reason , " Plugin {name} cannot be disabled: {reason} " )