├── .gitignore ├── LICENSE ├── README.md ├── main_application.py ├── plugin_collection.py └── plugins ├── double ├── double_negative.py └── double_positive.py └── identity.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | **/__pycache__/ 3 | *.py[cod] 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Guido Diepen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple Plugin implementation in Python 2 | 3 | This is a sample program that demonstrates how you can easily implement a plugin structure in Python 4 | -------------------------------------------------------------------------------- /main_application.py: -------------------------------------------------------------------------------- 1 | """Main applicatoin that demonstrates the functionality of 2 | the dynamic plugins and the PluginCollection class 3 | """ 4 | 5 | from plugin_collection import PluginCollection 6 | 7 | def main(): 8 | """main function that runs the application 9 | """ 10 | my_plugins = PluginCollection('plugins') 11 | my_plugins.apply_all_plugins_on_value(5) 12 | 13 | if __name__ == '__main__': 14 | main() 15 | -------------------------------------------------------------------------------- /plugin_collection.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import os 3 | import pkgutil 4 | 5 | 6 | class Plugin(object): 7 | """Base class that each plugin must inherit from. within this class 8 | you must define the methods that all of your plugins must implement 9 | """ 10 | 11 | def __init__(self): 12 | self.description = 'UNKNOWN' 13 | 14 | def perform_operation(self, argument): 15 | """The method that we expect all plugins to implement. This is the 16 | method that our framework will call 17 | """ 18 | raise NotImplementedError 19 | 20 | 21 | 22 | 23 | class PluginCollection(object): 24 | """Upon creation, this class will read the plugins package for modules 25 | that contain a class definition that is inheriting from the Plugin class 26 | """ 27 | 28 | def __init__(self, plugin_package): 29 | """Constructor that initiates the reading of all available plugins 30 | when an instance of the PluginCollection object is created 31 | """ 32 | self.plugin_package = plugin_package 33 | self.reload_plugins() 34 | 35 | 36 | def reload_plugins(self): 37 | """Reset the list of all plugins and initiate the walk over the main 38 | provided plugin package to load all available plugins 39 | """ 40 | self.plugins = [] 41 | self.seen_paths = [] 42 | print() 43 | print(f'Looking for plugins under package {self.plugin_package}') 44 | self.walk_package(self.plugin_package) 45 | 46 | 47 | def apply_all_plugins_on_value(self, argument): 48 | """Apply all of the plugins on the argument supplied to this function 49 | """ 50 | print() 51 | print(f'Applying all plugins on value {argument}:') 52 | for plugin in self.plugins: 53 | print(f' Applying {plugin.description} on value {argument} yields value {plugin.perform_operation(argument)}') 54 | 55 | def walk_package(self, package): 56 | """Recursively walk the supplied package to retrieve all plugins 57 | """ 58 | imported_package = __import__(package, fromlist=['blah']) 59 | 60 | for _, pluginname, ispkg in pkgutil.iter_modules(imported_package.__path__, imported_package.__name__ + '.'): 61 | if not ispkg: 62 | plugin_module = __import__(pluginname, fromlist=['blah']) 63 | clsmembers = inspect.getmembers(plugin_module, inspect.isclass) 64 | for (_, c) in clsmembers: 65 | # Only add classes that are a sub class of Plugin, but NOT Plugin itself 66 | if issubclass(c, Plugin) & (c is not Plugin): 67 | print(f' Found plugin class: {c.__module__}.{c.__name__}') 68 | self.plugins.append(c()) 69 | 70 | 71 | # Now that we have looked at all the modules in the current package, start looking 72 | # recursively for additional modules in sub packages 73 | all_current_paths = [] 74 | if isinstance(imported_package.__path__, str): 75 | all_current_paths.append(imported_package.__path__) 76 | else: 77 | all_current_paths.extend([x for x in imported_package.__path__]) 78 | 79 | for pkg_path in all_current_paths: 80 | if pkg_path not in self.seen_paths: 81 | self.seen_paths.append(pkg_path) 82 | 83 | # Get all sub directory of the current package path directory 84 | child_pkgs = [p for p in os.listdir(pkg_path) if os.path.isdir(os.path.join(pkg_path, p))] 85 | 86 | # For each sub directory, apply the walk_package method recursively 87 | for child_pkg in child_pkgs: 88 | self.walk_package(package + '.' + child_pkg) 89 | -------------------------------------------------------------------------------- /plugins/double/double_negative.py: -------------------------------------------------------------------------------- 1 | import plugin_collection 2 | 3 | class DoubleNegative(plugin_collection.Plugin): 4 | """This plugin will just multiply the argument with the value -2 5 | """ 6 | def __init__(self): 7 | super().__init__() 8 | self.description = 'Negative double function' 9 | 10 | def perform_operation(self, argument): 11 | """The actual implementation of this plugin is to multiple the 12 | value of the supplied argument by -2 13 | """ 14 | return argument*-2 15 | -------------------------------------------------------------------------------- /plugins/double/double_positive.py: -------------------------------------------------------------------------------- 1 | from plugin_collection import Plugin 2 | 3 | class DoublePositive(Plugin): 4 | """This plugin will just multiply the argument with the value 2 5 | """ 6 | def __init__(self): 7 | super().__init__() 8 | self.description = 'Double function' 9 | 10 | def perform_operation(self, argument): 11 | """The actual implementation of this plugin is to multiple the 12 | value of the supplied argument by 2 13 | """ 14 | return argument*2 15 | -------------------------------------------------------------------------------- /plugins/identity.py: -------------------------------------------------------------------------------- 1 | import plugin_collection 2 | 3 | class Identity(plugin_collection.Plugin): 4 | """This plugin is just the identity function: it returns the argument 5 | """ 6 | def __init__(self): 7 | super().__init__() 8 | self.description = 'Identity function' 9 | 10 | def perform_operation(self, argument): 11 | """The actual implementation of the identity plugin is to just return the 12 | argument 13 | """ 14 | return argument 15 | --------------------------------------------------------------------------------