import imp
import inspect
import os


class PluginType(object):
    """
    A plugin type.
    """

    def __init__(self, name, base_class, path):
        self.name = name
        self.base_class = base_class
        self.path = path


class PluginContainer(object):
    """
    A plugin container.

    Holds the plugins for a specific plugin type and has methods to discover
    them.
    """

    def __init__(self, plugin_type):
        self._plugins = []
        self.plugin_type = plugin_type

    def register_plugin(self, plugin_cls):
        """Register instance of the plugin class"""
        self._plugins.append(plugin_cls())

    def _get_modules(self):
        """Discover all modules in the path"""
        plugin_modules = []

        # walk the plugins folder
        for root, subdir, files in os.walk(self.plugin_type.path):

            # check all sub directories
            for directory in subdir:

                directory_path = os.path.join(root, directory)
                directory_list = os.listdir(directory_path)

                for filename in directory_list:
                    modname, ext = os.path.splitext(filename)

                    if ext == ".py":
                        filename, path, desc = imp.find_module(modname, [directory_path])
                        plugin_modules.append((modname, filename, path, desc,))

        return plugin_modules

    def get_plugins(self):
        """Discover all plugins of the set plugin type in the path"""
        modules = self._get_modules()

        # load plugins again only if a plugin was added or removed
        if len(modules) == len(self._plugins):
            return self._plugins

        self._plugins = []

        for plugin_module in modules:
            modname, filename, path, desc = plugin_module

            if filename:
                # load the module and look for classes
                module = imp.load_module(modname, filename, path, desc)
                classes_in_module = inspect.getmembers(module, inspect.isclass)

                for cls in classes_in_module:
                    cls = cls[1]

                    # ignore if it's the base plugin class
                    if cls.__name__ == self.plugin_type.base_class.__name__:
                        continue

                    # make sure that the class is a subclass of the plugin's
                    # base class
                    if self.plugin_type.base_class in cls.__bases__:
                        self.register_plugin(cls)

        return self._plugins


class PluginManager(object):
    """
    Manager class to interact with plugins.
    """

    def __init__(self, plugin_types=None):
        self._containers = {}
        plugin_types = plugin_types or []

        for plugin_type in plugin_types:
            self.register_plugin_type(plugin_type)

    def register_plugin_type(self, plugin_type):
        assert isinstance(plugin_type, PluginType)

        if plugin_type.name in self._containers:
            raise AssertionError("Plugin type with that name already exists")

        self._containers[plugin_type.name] = PluginContainer(plugin_type)

    def get_plugins(self, plugin_type):
        try:
            container = self._containers[plugin_type.name]
        except KeyError:
            raise AssertionError("No plugins found for type: {0}: ".format(plugin_type))

        return container.get_plugins()

    def has_plugins(self, plugin_type):
        return bool(self.get_plugins(plugin_type))

@@ -471,33 +471,301 @@
 elf.
-plugin_type = plugin_type
+_errors = %7B%7D%0A        self.plugin_type = plugin_type%0A%0A    @property%0A    def errors(self):%0A        # clear errors from the last time we called get_plugins%0A        self._errors = %7B%7D%0A%0A        # attempt to load the plugins and populate errors%0A        self.get_plugins()%0A%0A        return self._errors
 %0A%0A  
@@ -1482,23 +1482,17 @@
        f
-ilename
+p
 , path, 
@@ -1590,39 +1590,33 @@
 pend((modname, f
-ilename
+p
 , path, desc,))%0A
@@ -2011,39 +2011,33 @@
       modname, f
-ilename
+p
 , path, desc = p
@@ -2066,19 +2066,11 @@
     
-if filename
+try
 :%0A  
@@ -2173,23 +2173,17 @@
 dname, f
-ilename
+p
 , path, 
@@ -2188,24 +2188,199 @@
 , desc)%0A    
+        except Exception as e:%0A                error_msg = e.__class__.__name__ + ': ' + e.message%0A                self._errors%5Bfp.name%5D = error_msg%0A                continue%0A%0A
             
@@ -2452,28 +2452,24 @@
             
-    
 for cls in c
@@ -2502,20 +2502,16 @@
         
-  
-  
 cls = cl
@@ -2516,20 +2516,16 @@
 cls%5B1%5D%0A%0A
-    
         
@@ -2587,20 +2587,16 @@
         
-  
-  
 if cls._
@@ -2656,36 +2656,32 @@
                 
-    
 continue%0A%0A      
@@ -2690,20 +2690,16 @@
         
- 
-   
 # make s
@@ -2763,20 +2763,16 @@
         
-    
 # base c
@@ -2792,20 +2792,16 @@
         
- 
-   
 if self.
@@ -2837,28 +2837,24 @@
 .__bases__:%0A
-    
             
@@ -3210,32 +3210,352 @@
 e(plugin_type)%0A%0A
+    def _get_container(self, plugin_type):%0A        try:%0A            return self._containers%5Bplugin_type.name%5D%0A        except KeyError:%0A            raise AssertionError(%22No plugins found for type: %7B0%7D: %22.format(plugin_type))%0A%0A    def get_errors(self, plugin_type):%0A        return self._get_container(plugin_type).errors%0A%0A
     def register
@@ -3894,211 +3894,47 @@
     
-try:%0A            container = self._containers%5Bplugin_type.name%5D%0A        except KeyError:%0A            raise AssertionError(%22No plugins found for type: %7B0%7D: %22.format(plugin_type))%0A%0A        return container
+return self._get_container(plugin_type)
 .get

Make plugins fail gracefully