Sopel

The Python IRC Bot

Sopel tutorial, Part 2: The config file, and the configure & setup functions

NOTE: This guide is for 6.0. Most of this will not work in lower versions, due to significant changes in the config module. If you are still using a version named “Willie”, we strongly encourage you to upgrade, as such old versions are no longer supported.

Sopel has a config file, written in an INI-like format. It can provide configuration options not just for the bot itself, but for plugins as well. Here, we’re going to cover how you can leverage that configuration, as well as how to make your plugin configurable in the wizard like we showed in the installation guide.

Config Basics

The config file is made up of a number of sections, each of which has a number of keys or attributes. If you open up your config file, you’ll see something like this.

[core]
nick = Sopel
host = irc.dftba.net
use_ssl = False
port = 6667
owner = Embolalia
channels =
    "#YourPants"
    "#tech"

[admin]
hold_ground = False

[bugzilla]
domains =
    bugzilla.redhat.com
    bugzilla.gnome.org

Here, core, admin, and bugzilla are sections. As you may have guessed, core contains attributes relevant to the bot’s core functionality - it’s nick, the host to connect to, etc., and bugzilla and admin contain attributes relevant to two of the bot’s plugins.

In the context of your callable, the config file is available as an attribute of the bot parameter. Each section is an attribute of the config file, and each key in a section is an attribute of its respective section. For example, if we wanted to get the owner of the bot, we would use bot.config.core.owner.

Configuring your plugin

There are two ways you can use the config. The first is super easy; if you have eggs=parrot set in the spam section, and you do bot.config.spam.eggs, you’ll get 'parrot'. If you don’t, you’ll get None. Simple as that.

Only, you’ll want to know if that value is valid. You could check that every time you use it, or you could use StaticSections to define what you need your config values to look like ahead of time. If you’ve used an ORM like SQLAlchemy this will look very familiar, but if you haven’t it’s easy to learn.

A config section is defined by a class, subclassing StaticSection and with any number of ValidatedAttributes (or other subclasses of BaseValidated; see the docs for more detail) on it. So if you want to have a key velocity in the swallow section always be a number, you can do this:

from sopel.config.types import StaticSection, ValidatedAttribute
class SwallowSection(StaticSection):
    velocity = ValidatedAttribute('velocity', int)

def setup(bot):
    bot.config.define_section('swallow', SwallowSection)

def configure(config):
    config.define_section('swallow', SwallowSection, validate=False)
    config.swallow.configure_setting('velocity',
                                     'What is the speed of an unladen swallow?')

Now, when you use bot.config.swallow.velocity, you will always get an integer. If you hadn’t defined it, you would’ve gotten a string that you’d have to make an integer yourself, and handle if it wasn’t something you could make an integer.

There are a few things that need explaining here, though. First, ValidatedAttribute takes a number of arguments. The first is the only one that’s required; it’s the name of the config attribute being described. The second one is the function used to parse what’s in the config file. Here, it’s int to make it an integer, but other things work like float, bool, and json.loads. You can also pass an argument like default=3 so that, if it’s not set, it will just give you that default value.

setup is a special function that is run when the bot is loading the plugin. You can do anything you need to here (and we’ll touch on a few other uses later in the tutorial), but all we do here is set up our config definition. The define_section function gets our section name and our definition class, and sets it up. Since this happens before the plugin starts getting messages from IRC, you’ll be able to do bot.config.swallow.velocity and get your value back in all your triggered functions. It also means that you can’t send messages to IRC; bot.say is defined here, but it won’t do anything. If setup raises any sort of exception, the plugin will be disabled. Since define_section raises an exception by default when there’s an invalid config setting, this is all you have to do to make sure your config is valid.

The next function is configure. This is a special function which will be run when you’re going through the setup wizard. You don’t have to have it in order for your config definition to work, but it’s a good idea if you’re going to be sharing your plugin, so that others will have an easier time setting it up.

Again, we define the config section, but we don’t want it to validate what’s already in the config, in case someone edited the file manually and put in a bad value. The next thing we do is configure the setting. If there’s a bad value already in the config, or the user tries to put in a bad value, we’ll cast them into the Gorge of Eternal Peril give them an error and ask them again.

Want to learn about URLs and remembering things? Continue to part 3!