1

I am building a custom speech recognition and for that I defined a Command interface. The idea is that the speech recognition returns a String and I got a list of all supported commands. Then I would be going through the list and find the best matching one and execute it.

In order to keep the code maintainable I want to create a plugin system, where you define a class derived from ICommand in a separate file. The folder structure would be the following:

project/
├── ICommand
├── commands/
│   ├── command1.py (inherits ICommand)
│   ├── command2.py (inherits ICommand)  

I am facing multiple problems with that:

  1. How can I import ICommand for inheritance in the command1.py file?

  2. How do I create one instance of every class contained in a .py file in the commands folder?

  3. Is there any way to ensure that the plugin file only contains a definition for a derived ICommand class?

Curunir
  • 1,186
  • 2
  • 13
  • 30

1 Answers1

1
  • How can I import ICommand for inheritance in the command1.py file?

Your project would have to be inside a proper package. If you are not doing that already, you should. Once Python understands that "command1" is inside a package, inside it you can do:

from ..ICommand import ICommand

(ICommand must be a .py file defining your ICommand, of course).

  • How do I create one instance of every class contained in a .py file in the commands folder?

For Python, unless you explicitly import a file, a .py file is as good as any data file sitting on the directory. The language allows you to, but does not perform any "auto-discovery" of whatever files you will have under your plug-ins.

You can do that yourself, our you can use a framework that does this kind of thing - zope.configuration is one, but you have to create xml-like files with the folders it should scan.

Doing it yourself can be easy, it is just a matter of listing the desired folders, and call __import__ for any Python files in there. TThe object returned by the call to __import__ is the module itself, you can then just check its contents for subclasses (or otherwise implementations of) ICommand and instantiate then.

module = __import__(...)
plugins = {}
for name, obj in module.__dict__.items():
    if isinstance(obj, ICommand):
         plugins[name] = obj()   # This instatiate the class

I have two answers with explicit examples of how to do that:

Load module to invoke its decorators

Python: import every module from a folder?

  • Is there any way to ensure that the plugin file only contains a definition for a derived ICommand class?

Yes.

But you should not put that restriction there, as you state it. The snippet above for getting the ICommand subclasses inspect all of a modues top level contents, and you could easily raise an exception on any variable, function or class there that is not an ICommand subclass. That however is not a rational restriction: it is usual in Python to need small "utility" functions that are not class related, or other variables that will hold constant values - it is not usefull to constrain those to be class members for your plug-in. Likewise, if you are worried about the plugins containing arbitrary code that should not be there: you can't prevent that at all. It is not feasible to securely "sandbox" a Python file in a Python process. Very good people tried for years to have a "Python sandbox", only to be frustrated. (If that is a requirement, use O.S. level process resource restrictions, and run your plug-ins i.e. only import their files, in a process with those restrictions)

Otherwise, you may be meaning that you want "a single subclass of ICommand per file", with no other restrictions: then just insert a check in the above loop to instantiate the plug-in - if it finds a second class in the same file, raise an exception.

jsbueno
  • 99,910
  • 10
  • 151
  • 209