Skip to content

Commit f1c8095

Browse files
author
Hanif Virani
committed
Add framework
1 parent 7fe686b commit f1c8095

File tree

5 files changed

+165
-0
lines changed

5 files changed

+165
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ __pycache__/
55
# C extensions
66
*.so
77

8+
# Pycharm
9+
.idea/
10+
811
# Distribution / packaging
912
.Python
1013
env/

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# yantra
2+
3+
yantra is a simple plugin manager/plugin discovery framework.
4+
5+
6+
## Usage
7+
8+
Let's assume you have a class called BaseReportPlugin which all report plugins derive from.
9+
10+
```python
11+
from yantra import PluginManager, PluginType
12+
from reports import BaseReportPlugin
13+
14+
# Define the type
15+
report_type = PluginType(name='report',
16+
base_class=BaseReportPlugin',
17+
path='/plugins/reports/')
18+
19+
# Instantiate the manager
20+
plugin_manager = PluginManager([report_type])
21+
22+
# Fetch plugins
23+
plugin_manager.get_plugins(report_type)
24+
```
25+
26+
You can register multiple plugin types. Alternate way to register plugin types is as follows:
27+
28+
```python
29+
plugin_manager = PluginManager()
30+
plugin_manager.register_plugin_type(foo_type)
31+
```

setup.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from setuptools import setup
2+
3+
setup(
4+
name='yantra',
5+
version='0.1',
6+
description='A plugin framework',
7+
url='',
8+
author='Hanif Virani',
9+
license='MIT',
10+
packages=['yantra'],
11+
install_requires=[
12+
],
13+
)

yantra/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from manager import PluginManager, PluginType

yantra/manager.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import imp
2+
import inspect
3+
import os
4+
5+
6+
class PluginType(object):
7+
"""
8+
A plugin type.
9+
"""
10+
11+
def __init__(self, name, base_class, path):
12+
self.name = name
13+
self.base_class = base_class
14+
self.path = path
15+
16+
17+
class PluginContainer(object):
18+
"""
19+
A plugin container.
20+
21+
Holds the plugins for a specific plugin type and has methods to discover
22+
them.
23+
"""
24+
25+
def __init__(self, plugin_type):
26+
self._plugins = []
27+
self.plugin_type = plugin_type
28+
29+
def register_plugin(self, plugin_cls):
30+
"""Register instance of the plugin class"""
31+
self._plugins.append(plugin_cls())
32+
33+
def _get_modules(self):
34+
"""Discover all modules in the path"""
35+
plugin_modules = []
36+
37+
# walk the plugins folder
38+
for root, subdir, files in os.walk(self.plugin_type.path):
39+
40+
# check all sub directories
41+
for directory in subdir:
42+
43+
directory_path = os.path.join(root, directory)
44+
directory_list = os.listdir(directory_path)
45+
46+
for filename in directory_list:
47+
modname, ext = os.path.splitext(filename)
48+
49+
if ext == ".py":
50+
filename, path, desc = imp.find_module(modname, [directory_path])
51+
plugin_modules.append((modname, filename, path, desc,))
52+
53+
return plugin_modules
54+
55+
def get_plugins(self):
56+
"""Discover all plugins of the set plugin type in the path"""
57+
modules = self._get_modules()
58+
59+
# load plugins again only if a plugin was added or removed
60+
if len(modules) == len(self._plugins):
61+
return self._plugins
62+
63+
self._plugins = []
64+
65+
for plugin_module in modules:
66+
modname, filename, path, desc = plugin_module
67+
68+
if filename:
69+
# load the module and look for classes
70+
module = imp.load_module(modname, filename, path, desc)
71+
classes_in_module = inspect.getmembers(module, inspect.isclass)
72+
73+
for cls in classes_in_module:
74+
cls = cls[1]
75+
76+
# ignore if it's the base plugin class
77+
if cls.__name__ == self.plugin_type.base_class.__name__:
78+
continue
79+
80+
# make sure that the class is a subclass of the plugin's
81+
# base class
82+
if self.plugin_type.base_class in cls.__bases__:
83+
self.register_plugin(cls)
84+
85+
return self._plugins
86+
87+
88+
class PluginManager(object):
89+
"""
90+
Manager class to interact with plugins.
91+
"""
92+
93+
def __init__(self, plugin_types=None):
94+
self._containers = {}
95+
plugin_types = plugin_types or []
96+
97+
for plugin_type in plugin_types:
98+
self.register_plugin_type(plugin_type)
99+
100+
def register_plugin_type(self, plugin_type):
101+
assert isinstance(plugin_type, PluginType)
102+
103+
if plugin_type.name in self._containers:
104+
raise AssertionError("Plugin type with that name already exists")
105+
106+
self._containers[plugin_type.name] = PluginContainer(plugin_type)
107+
108+
def get_plugins(self, plugin_type):
109+
try:
110+
container = self._containers[plugin_type.name]
111+
except KeyError:
112+
raise AssertionError("No plugins found for type: {0}: ".format(plugin_type))
113+
114+
return container.get_plugins()
115+
116+
def has_plugins(self, plugin_type):
117+
return bool(self.get_plugins(plugin_type))

0 commit comments

Comments
 (0)