Implementing a basic plugin architecture shouldn’t be a complicated task. The solution described here is working but you still have to import every plugin (inheriting from the base class). This is my solution:

<!– –>

1
2
3
4
5
6
7
$ tree
.
├── main.py
└── plugins
    ├── __init__.py
    ├── plugin_a.py
    ├── plugin_b.py

<!– –>

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
$ cat plugins/__init__.py

import os
import traceback
from importlib import util


class Base:
    """Basic resource class. Concrete resources will inherit from this one
    """
    plugins = []

    # For every class that inherits from the current,
    # the class name will be added to plugins
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.plugins.append(cls)


# Small utility to automatically load modules
def load_module(path):
    name = os.path.split(path)[-1]
    spec = util.spec_from_file_location(name, path)
    module = util.module_from_spec(spec)
    spec.loader.exec_module(module)
    return module


# Get current path
path = os.path.abspath(__file__)
dirpath = os.path.dirname(path)

for fname in os.listdir(dirpath):
    # Load only "real modules"
    if not fname.startswith('.') and \
       not fname.startswith('__') and fname.endswith('.py'):
        try:
            load_module(os.path.join(dirpath, fname))
        except Exception:
            traceback.print_exc()

<!– –>

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ cat plugins/plugin_a.py
import plugins


class PluginA(plugins.Base):

    def __init__(self):
        pass

    def start(self):
        print("Plugin A")

And now the main.py:

1
2
3
4
5
6
7
$ cat main.py
from plugins import Base

if __name__ == '__main__':
    for p in Base.plugins:
        inst = p()
        inst.start()