plugins

This page contains tutorials about the plugins package.

PluginInfo

For every plugin a plugins.info.PluginInfo instance can be retrieved, even if the plugin isn’t loaded. The plugins.info.PluginInfo instance is created based on a file called info.ini that has to reside in the plugin’s main directory. This file is completely optional. So, if it doesn’t exist, the plugins.info.PluginInfo instance will still be created, but it won’t contain much more information beside the name of the plugin.

Here is an example info.ini file containing the most basic options:

# A verbose name of the plugin.
# If this option is not defined, the plugin's name will be used, all
# underscores are replaced with spaces and the first letter of every word is
# being capitalized.
verbose_name = "Paintball"

# Name of the Author.
# If this option is not defined, the plugin info will contain 'None'.
author = "Ayuto"

# A description of what the plugin does.
# If this option is not defined, the plugin info will contain 'None'.
description = "Adds paintball effects to the game."

# Version of the plugin.
# If this option is not defined, the plugin info will contain 'unversioned'.
version = "1.3"

# An link to the 'Plugin Releases' forum or the plugin's SPPM link (hopefully
# coming some day).
# If this option is not defined, the plugin info will contain 'None'.
url = "http://forums.sourcepython.com/viewtopic.php?f=7&t=502"

You can also note which permissions are defined by your plugin. This helps server owners to configure their authorization settings properly. See the example below:

# Permissions defined by the plugin.
# If this option is not defined, the plugin info will contain an empty list.
[permissions]
    admin.kick = "Ability to kick a player."
    admin.ban = "Ability to ban a player."

As soon as a plugin is being loaded, a public console variable is created that contains the following information:

  • Name: <plugin name>_version
  • Value: <version of the plugin>
  • Description: <verbose name> version.

If you don’t want a public console variable, you can simply use the following option in your info file to disable that feature:

public_convar = False

If you wish to use different values to create the public console variable, you can use the following in your info file:

[public_convar]
    # All of these options are optional.
    name = "my_plugin_version"
    value = "My custom value."
    description = "My custom description."

Sometimes you might also want to define some custom options for the plugin info. Adding those is quite easy. You just need to define them:

my_custom_option = "something"
my_custom_option2 = "something else"

Since those are custom options, they are not displayed when the list of loaded plugins is printed (e.g. via sp plugin list). If you want to change that behaviour, you can define the display_in_listing option:

display_in_listing = "my_custom_option", "my_custom_option2"

Retrieving a PluginInfo instance

Note

If you retrieve a plugins.info.PluginInfo instance of a plugin that isn’t loaded, the plugins.info.PluginInfo instance is recreated everytime you retrieve it. Only loaded plugins will cache the instance.

The following example will show how to retrieve a plugins.info.PluginInfo instance for a specific plugin.

from plugins.manager import plugin_manager

# Retrieve the plugin info of the paintball plugin. This is case sensitive!
info = plugin_manager.get_plugin_info('paintball')

# Print the plugin's description
print(info.description)

You are not only restricted to the plugin’s name, but you can also use the plugin’s import path. See the example below:

from plugins.manager import plugin_manager

info = plugin_manager.get_plugin_info('paintball.paintball')

Obviously, this doesn’t make much sense as the first example is shorter and both result in the same. But this feature has been added, so plugin’s can pass their own __name__ variable, which contains their import path. Thus, you can use the following snippet to retrieve the plugin info of your own plugin, without directly specifying the plugin’s name.

from plugins.manager import plugin_manager

info = plugin_manager.get_plugin_info(__name__)

You can also use this snippet outside of your plugin’s main file (e.g. in other sub-modules or sub-packages).

Adding sub-plugins

Adding sub-plugins to your plugin is done a very few steps. All you actually need is a new instance of the plugins.manager.PluginManager class. This instance allows you to load plugins from a specific directory.

Imagine your plugin resides in ../addons/source-python/plugins/my_plugin and within that directory you have created a new directory called plugins, which should contain all sub-plugins of my_plugin. Then the plugin manager could be created using the following code:

from plugins.manager import PluginManager

my_plugin_manager = PluginManager('my_plugin.plugins.')

That’s all you need! Now you can load sub-plugins using my_plugin_manager from your sub-plugins directory with the following code:

# Load the plugin 'my_sub_plugin' from
# ../addons/source-python/plugins/my_plugin/plugins
my_plugin_manager.load('my_sub_plugin')

However, this doesn’t print any messages like Source.Python does when you load a plugin via sp plugin load. If you would like to have those messages as well, without implementing them on your own, you can simply create an instance of plugins.command.SubCommandManager.

from plugins.command import SubCommandManager

my_sub_command_manager = SubCommandManager(
    # Tell the sub command manager to use this plugin manager to load plugins
    my_plugin_manager,

    # If you create sub-commands, they will use 'my_plugin' as the base
    # command like Source.Python uses 'sp'
    'my_plugin'
)

Now, you can load your sub-plugin using the following code:

my_sub_command_manager.load_plugin('my_sub_plugin')

So far, so good. But what if you want to load your plugins via a server command? Well, just add it using the following code:

@my_sub_command_manager.server_sub_command(['plugin', 'load'])
def plugin_load(command_info, plugin):
    my_sub_command_manager.load_plugin(plugin)

@my_sub_command_manager.server_sub_command(['plugin', 'unload'])
def plugin_unload(command_info, plugin):
    my_sub_command_manager.unload_plugin(plugin)

Now you can also load your sub-plugins using my_plugin plugin load and unload them using my_plugin plugin unload.

There is only one last thing left to do. If your main plugin is being unloaded, you should also unload all of your sub-plugins. It doesn’t cause any problems with Source.Python if you don’t do that, because Source.Python also unloads all core.AutoUnload and core.WeakAutoUnload instances of your sub-plugins. But if you don’t do that the unload functions of your sub-plugins are never getting called. To avoid this issue use the following code:

def unload():
    for plugin in my_plugin_manager.loaded_plugins:
        plugin.unload()

Here is the full example code to implement sub-plugins:

from plugins.manager import PluginManager
from plugins.command import SubCommandManager

my_plugin_manager = PluginManager('my_plugin.plugins.')
my_sub_command_manager = SubCommandManager(
    # Tell the sub command manager to use this plugin manager to load plugins
    my_plugin_manager,

    # If you create sub-commands, they will use 'my_plugin' as the base
    # command like Source.Python uses 'sp'
    'my_plugin'
)

@my_sub_command_manager.server_sub_command(['plugin', 'load'])
def plugin_load(command_info, plugin):
    my_sub_command_manager.load_plugin(plugin)

@my_sub_command_manager.server_sub_command(['plugin', 'unload'])
def plugin_unload(command_info, plugin):
    my_sub_command_manager.unload_plugin(plugin)

def unload():
    for plugin in my_plugin_manager.loaded_plugins:
        plugin.unload()