How would I separate my Python File to multiple plugins?
Asked Answered
T

1

2

So first thing I want to say: I have been looking into modules and such, I just don't quiet know how I would rewrite it to fit this in.

Project: What I have is a Skype Bot using the Skype4Py module. I have about 11 commands I noticed the one script is getting a little large.

I'm trying to think about how to link one main.py file to a Plugin Folder, which contains each and every bot function in it's own respectable Python file. It sounds simple an all, except when it comes to how the function is called.

Here is just a basic look at my Skype bot, missing some of the larger functions.

import Skype4Py, random

class SkypeBot():

    def __init__(self):
        self.skype = Skype4Py.Skype()

        if self.skype.Client.IsRunning == False:
            self.skype.Client.Start()

        self.skype.Attach()

        self.results = ['Yes', 'No', 'Maybe', 'Never']

    def main(self):
        print '       Skype Bot currently running on user: %s' % self.skype.CurrentUserHandle

        print "\n\nCommands Called:\n"

        while True:
            self.skype.OnMessageStatus = self.RunFunction

    def RunFunction(self, Message, Status):
        if Status == 'SENT' or Status == 'RECEIVED':
            cmd = Message.Body.split(' ')[0]

            if cmd in self.functions.keys():
                self.context = Message
                self.caller = self.context.FromHandle
                self.functions[cmd](self)

    def ping(self):
        print "    %s : Ping" % self.caller
        self.context.Chat.SendMessage('Pong')

    def say(self):
        try:
            response = self.context.Body.split(' ', 1)

            if response[1] == "-info":
                print "    %s : say -info" % self.caller
                self.context.Chat.SendMessage("Resends the message entered. \n"
                                              "Usage: !say Hello. \n"
                                              "Example: Bot: Hello.")

            else:
                say = response[1]
                print "    %s : Say [%s]" % (self.caller, say)
                self.context.Chat.SendMessage(say)

        except:
            self.context.Chat.SendMessage("Please use -info to properly use the !say command")

    def eightball(self):
        try:
            question = self.context.Body.split(' ', 1)

            if question[1] == "-info":
                print "    %s : 8Ball -info" % self.caller
                self.context.Chat.SendMessage("Responds with an answer.\n"
                                              "Usage: !8ball 'Do I have swag?'\n"
                                              "Example: !8Ball Response: 'Yes'")

            else:
                random.shuffle(self.results)
                answer = self.results[3]
                print "    %s : 8Ball [%s]" % (self.caller, question[1])
                self.context.Chat.SendMessage("!8Ball Response: %s" % answer)

        except:
            self.context.Chat.SendMessage("Please use -info to properly use the !8ball command")

    #FUNCTIONS LIST
    #********************

    functions = {
    "!ping": ping,
    "!say": say,
    "!8ball": eightball,
    }


if __name__ == "__main__":
    snayer = SkypeBot()
    snayer.main()

So basically, what I am wondering, how can I change

self.skype.OnMessageStatus = self.RunFunction

so that it'll run functions from another file?

Tiller answered 28/10, 2014 at 2:7 Comment(3)
Try writing the name of the file followed by the class(if there is one) and the module. import Skype4Py then call the module a_name() in Skype4Py by writing Skype4Py.a_name(). Let me know if this worked or you have any questions.Tableware
The only question I have is that I don't quiet understand how I am supposed to listen for the specific command to call, since as of currently I'm calling it from the same file, but if I were to mix it up, I wouldn't know how to listen for the calling of the command.Tiller
It should be the same, except you prefix the module name. It's the same as using the random module, except you made the module. As for the module itself, you can write it the same way you would if you had it in the same file.Tableware
A
2

For a program of this size it's not really necessary to put your command functions into separate files, but I guess it is good organization. And good practice for when you write a program that has thousands of lines of code. :)

One way to do this is to create a basic SkypeBot class without any command methods and then import the command methods from your plugins directory and add them to the class. It's easy enough to add new attributes to an existing class, and it doesn't matter if the new attributes are properties or methods, the syntax to add them is identical. (With a tiny bit more work it's even possible to add new attributes to an instance, so you can have multiple instances, each with their own individual set of commands. But I guess that's not necessary here, since a program that uses the SkypeBot class will normally only create a single instance).

So we can break your question into two parts:

  1. How to add methods to an existing class.
  2. How to import those methods from other source files.

As I said, 1) is easy. 2) is quite easy as well, but I've never done it before, so I had to do a little bit of research and testing, and I can't promise that what I've done is best practice, but it works. :)

I don't know much about Skype, and I don't have that Skype4Py module, and as you said, the code above is not the complete program, so I've written some fairly simple code to illustrate the process of adding plugin methods from separate files to an existing class.

The name of the main program is "plugin_demo.py". To keep things neat, it lives in its own directory, "plugintest/", which you should create somewhere in your Python path (eg where you normally keep your Python programs). This path must be specified in your PYTHONPATH environment variable.

"plugintest/" has the following structure:

plugintest/
    __init__.py
    plugin_demo.py
    plugins/
        __init__.py
        add.py
        multiply.py

The __init__.py files are used by Python's import machinery to let it know that a directory contains a Python package, see 6.4. Packages in the Python docs for further details.

Here are the contents of those files. Firstly, the files that go into "plugintest/" itself:

__init__.py

__all__ = ['plugin_demo', 'plugins']
from plugintest import *

plugin_demo.py

#! /usr/bin/env python

#A simple class that will get methods added later from plugins directory
class Test(object):
    def __init__(self, data):
        self.data = data

def add_plugins(cls):
    import plugins

    print "Adding plugin methods to %s class" % cls.__name__
    for name in plugins.__all__:
        print name
        plug = getattr(plugins, name)
        print plug
        method = getattr(plug, name)
        print method
        setattr(cls, name, method)
        print
    print "Done\n"

add_plugins(Test)

def main():
    #Now test it!
    t = Test([1, 2, 3]); print t.data

    t.multiply(10); print t.data
    t.add(5); print t.data

if __name__ == '__main__':  
    main()

And now the contents of the "plugintest/plugins/" directory:

__init__.py

__all__ = ['add', 'multiply']
from plugintest.plugins import *

add.py

#A method for the Test class of plugin_demo.py
def add(self, m):
    self.data = [m + i for i in self.data]

multiply.py

#A method for the Test class of plugin_demo.py
def multiply(self, m):
    self.data = [m * i for i in self.data]

If you cd to the directory containing the "plugintest/" folder, you should be able to run it with

python plugintest/plugin_demo.py

and if you cd to "plugintest/" itself

python plugin_demo.py

Also, in the interpreter (or another Python program), you should be able to do

import plugintest

and then run the main() function of "plugin_demo.py" with

plugintest.plugin_demo.main()

The other usual variations of from ... import ... etc should also work as expected.

The function in "plugin_demo.py" that performs the magic of adding the imported methods to the Test class is add_plugins(). When it runs it prints out each method name, its module, and its function. This could be handy during development, but you'd probably comment out some of those print statements once the program's working properly.

I hope this helps, and if you have any questions please don't hesitate to ask.

Afc answered 29/10, 2014 at 5:46 Comment(1)
this was very helpful, you just clarified the issue for me. I salute you.Seville

© 2022 - 2024 — McMap. All rights reserved.