I am creating a commands system in Python. I have module vkcommands
that has a class that processes commands from chat (this is a chat-bot), and inside it, I also have class VKCommand
with attributes like name
, usage
, min_rank
, etc. Then I have module vkcmds
with submodules that implement these commands:
...
vkcommands.py
vkcmds
|- __init__.py # empty
|- add_group.py
|- another_cmd.py
|- ...
Implementations of commands (e.g. add_group
) look like this:
import ranks
import vkcommands
from vkcommands import VKCommand
class AddGroup(VKCommand):
def __init__(self, kristy):
VKCommand.__init__(self, kristy,
label='create',
# ... (other attributes)
min_rank=ranks.Rank.USER)
def execute(self, chat, peer, sender, args=None, attachments=None):
# implementation (called from vkcommands.py)
When a user sends a message in the chat, the command manager analyzes it and looks through the registered commands
list to see if this is an ordinary message or a bot command. Currently I register all commands in the commands
list manually like this:
class VKCommandsManager:
def __init__(self, kristy):
from vkcmds import (
add_group,
next_class
)
self.kristy = kristy
self.commands = (
add_group.AddGroup(kristy),
next_class.NextClass(kristy)
)
Now I would like all commands to be registered automatically using reflections instead. In Java, I'd iterate over all classes in my commands package, reflectively getConstructor
of each, call it to retrieve the VKCommand
object, and add it to the commands list.
How can I do so in Python? Again, what I need is to:
- iterate over all submodules in module (folder)
vkcmds/
; - for each submodule, check if there is some class
X
that extendsVKCommand
inside; - if (2) is
true
, then call the constructor of that class with one argument (it is guaranteed that the constructor for all commands only has one argument of a known type (my bot's main class)); - add the object (
? extends VKCommand
) constructed in (3) to thecommands
list that I can iterate over later.
With this file structure:
And the following inside the
commands
directory files:base.py
baz.py
foo_bar.py
We can retrieve the class instances and names directly using the following code:
main.py
Explanation
The solution is twofold. It first locates all submodules that have a class which inherit from
VKCommand
and are located in the folder 'commands`. This leads to the following output containing the module and the class that have to be imported and instantiated respectively:The second part of the code imports the correct module and class name at run time. The variable
class_instance
contains the class name and a reference to the class which can be used to instantiate it. The final output will be:Important notes:
The code only works when importing modules that are 1 dictionary deeper. If you want to use it recursively, you will have to locate the relative path difference and update the
pyclbr.readmodule
and__import__
with the correct (full) relative import path.Only the modules that contain a class which inherit from
VKCommand
get loaded. All other modules are not imported, and have to be imported manually.