In Part 1, we installed and configured Errbot, a Python-based chat bot. In Part 2, we added and configured plugins for Errbot. Now it’s time to create our own plugin for Errbot.

This guide is intended for beginners who are comfortable with the command line and have some programming experience, but may be new to Python (like me) or continuous integration (CI).

Errbot documentation

Errbot User Guide has a plugin development section. It has examples of things you might want your plugin to do. I found it helpful to read a bit of the documentation, implement a feature, and then return later when I was implementing another part of the project.

I based this part of the series around two topics in the plugin development section:

The goal is to write a simple plugin from scratch, test it first using Errbot’s interactive text mode, then the Python test tool py.test, and finally connect the project to a continuous integration service, Travis CI.

Development environment

How do we get started? If you don’t have a development environment yet, no worries. While working on this guide, I built a simple, shareable development environment for Errbot plugins, which:

  • uses Vagrant and VirtualBox to set up a virtual machine runnning Ubuntu 14.04
  • installs pip, err (and other dependencies mentioned in Part I) on the virtual machine
  • installs coverage and pep8 (used for testing)
  • sets up a minimal configuration for Err
  • creates err-data/ and err-plugins/ directories

To use this development environment, install Vagrant and VirtualBox first. You will also need Git to be installed on your computer.

I recommend installing the vagrant-vbguest plugin, to keep VirtualBox Guest Additions up to date. On the command line, run:

vagrant plugin install vagrant-vbguest

Now it’s time to download the errbot-dev project. You can either clone it with git:

git clone https://github.com/alimac/errbot-dev.git

Or download the .zip file.

Inside the project, there are two files:

  • Vagrantfile - contains the virtual server configuration and shell commands that will install Err
  • config.py - a minimal configuration of Err

Once downloaded (and unpacked), use the terminal to go to the errbot-dev directory and start the virtual server:

cd errbot-dev/
vagrant up

The first time you run vagrant up, it will take a while. Especially if Vagrant has to download the (Ubuntu 14.04) base box.

Once the virtual server is ready, use vagrant ssh to connect to it. Then, go to the /vagrant directory. You should be able to start Errbot with the -T flag:

cd /vagrant
errbot -T

The -T flag means that Errbot will not try to connect to a backend. Instead, it will give you an interactive prompt where you can type bot commands, (as if you were connected to an XMPP backend and in a chat. Try !status, !help or another command. If you type:

!whoami

You will find out that you are gbin@localhost. gbin is Guillaume Binet, the maintainer of Errbot.

To exit the Errbot shell, use Ctrl + c.

I find it useful to have two simultaneous sessions, one in which I run errbot -T, and another in which I use vim to make changes to a plugin’s code. You can also use a local code editor, on your desktop.

Our first plugin

Let’s create a basic plugin for Errbot. A plugin will consist of at least three files:

  • pluginname.py - the plugin itself
  • pluginname.plug - a metadata file
  • test_pluginname.py - tests for the plugin
It’s good idea to get into the habit of adding tests early in the project. Having tests reduces the chance that you will introduce bugs later, and it helps make sure that your plugin can be reliably built over and over again.

To start, we will build a simple plugin that replies whenever someone in the chat says “hello”.

In /vagrant/errbot-plugins/ create a HelloBot directory where we will store the plugin files.

hello.plug

The first file - hello.plug - will contain basic information about the plugin. The Name refers to the class name we will use. The module refers to the .py filename.

Our plugin will work with Python 2 or above, and we will include a short description of the plugin in the Documentation section.

[Core]
Name = HelloBot
Module = hello

[Python]
Version = 2+

[Documentation]
Description = Plugin for Errbot that responds to hello’s.

hello.py

Our .py file is where the plugin’s logic will be. This example is similar to the example given in section 3. Hello, World of the Errbot plugin development guide. Take a look at it first. Then continue reading below.

from errbot import BotPlugin, botcmd


class HelloBot(BotPlugin):
    """'Hello!' plugin for Errbot"""

    @botcmd
    def hello(self, msg, args):
        """Say hello to someone"""
        return "Hello, " + format(msg.frm)
  1. The first line lists the packages we are importing that will be used by the plugin.
  2. Then, we define our class, HelloBot, along with a documentation comment wrapped in three double quotes.
  3. def is how we start our function. This function takes three parameters: self (our plugin), msg (the message that the bot is responding to), and args (anything that follows the !hello command).
  4. Above it, is @botcmd, a decorator. It means that hello is a command that we can give to the bot.
  5. Inside the function, we have another documentation comment. This one will be shown when you call for help with !help HelloBot.
  6. The last line is the bot’s response. msg.frm is the identifier of the user who types the !hello command in the chat.

Whew! That’s a lot to take in. If you want to test it out, run:

errbot -T -c /vagrant/config.py

and once Errbot loads, say !hello and see what happens.

test_hello.py

In test_hello.py we will add a test to make sure that if someone says !hello, the bot does respond with what we expect.

Remember how we used !whoami to find out that in the interactive text mode our username is gbin@localhost? We can use this in our tests:

import os
import hello
from errbot.backends.test import testbot


class TestHelloBot(object):
    extra_plugin_dir = '.'

    def test_hello(self, testbot):
        testbot.push_message('!hello')
        assert 'Hello, gbin@localhost' in testbot.pop_message()
  1. As in hello.py we import a few packages:
  2. We define a class in which we will define the test functions. Each function is considered to be an independent test.
  3. Within our test class, we have a test_hello function that uses the testbot to pass a command - !hello - and checks that the response matches with the expected reply, Hello, gbin@localhost.

Test with py.test

It’s time to run our test. We will use py.test, a Python testing tool. In /vagrant/err-plugins/HelloBot/ directory, run the following command:

py.test -sv

The -v flag means that we will see verbose output of each test (rather than a . for successful test and an F for each failed test). The -s flag will conveniently display the errors at the end, so that we don’t have to scroll through all of the output capture to find them.

If all goes well, you will see something like:

===================== test session starts =============================
platform linux2 -- Python 2.7.6, pytest-2.8.2, py-1.4.30, pluggy-0.3.1
rootdir: /vagrant/err-plugins/HelloBot, inifile:
plugins: pep8-1.0.6, xdist-1.13.1
collected 1 items

test_hello.py::TestHelloBot::test_hello PASSED

=================== 1 passed in 0.79 seconds ==========================

If any of the tests do not pass, you might see:

=========================== FAILURES ==================================
_____________________TestHelloBot.test_hello __________________________

self = <test_hello.TestHelloBot object at 0x7fe22149af90>
testbot = <errbot.backends.test.TestBot object at 0x7fe220ae01d0>

    def test_hello(self, testbot):
        testbot.push_message('!hello')
>       assert 'Hello, alimac@localhost' in testbot.pop_message()
E       assert 'Hello, alimac@localhost' in 'Hello, gbin@localhost'
E        +  where 'Hello, gbin@localhost' = <bound method TestBot.pop_message of <errbot.backends.test.TestBot object at 0x7fe220ae01d0>>()
E        +    where <bound method TestBot.pop_message of <errbot.backends.test.TestBot object at 0x7fe220ae01d0>> = <errbot.backends.test.TestBot object at 0x7fe220ae01d0>.pop_message

test_hello.py:11: AssertionError
==================== 1 failed in 0.88 seconds =========================

What happened? In the example above, the test failed because the expected response (Hello, alimac@localhost) did not match the response the Errbot gave (Hello, gbin@localhost).

Code style

PEP 8 is style guide for Python code, and pep8 is a tool to check your Python code against some of the style conventions in PEP 8.

It will check that your line lengths are at most 79 characters, that you use the right kind of indentation, etc. The goal here is consistency.

To perform PEP 8 checks, run py.test with the --pep8 flag. Here is an example of what you might see in the output:

_________________________ PEP8-check ________________________________
/vagrant/err-plugins/HelloBot/hello.py:3:1: E302 expected 2 blank lines, found 1
class HelloBot(BotPlugin):
^
/vagrant/err-plugins/HelloBot/hello.py:4:80: E501 line too long (89 > 79 characters)
    """Example 'Hello!' plugin for Errbot that replies to hello's to chat participants"""
                                                                               ^

================= 1 failed, 2 passed in 0.90 seconds ================

As a newcomer to Python, I found these consistency checks very helpful.

Test coverage

How do we know what tests to write? coverage is a tool that measures code coverage during test execution. We can use it to pinpoint code that is not covered by tests.

To use coverage, we have to modify the command we use to run tests:

coverage run --source hello -m py.test --pep8

To check the results, run coverage report. You might see the following output:

Name       Stmts   Miss  Cover
------------------------------
hello.py       4      0   100%

When coverage is less than 100%, we would like to see exactly where we are missing tests. Run the coverage html command. It will create a directory, htmlcov, containing web pages showing where the tests are missing.

Navigate to the errbot-dev/err-plugins/HelloBot/htmlcov directory on your computer (instead of the virtual machine) and view the contents in a browser.

You should see a page like this:

HTML output of `coverage` command HTML output of coverage.

You can click on each filename to see detailed results. For example, if we remove the test_hello function, we would see which statements need a test highlighed in red:

Coverage results highlight statements that
  need to be tested Coverage results highlight statements that need to be tested.

Great! We now have style guide checks, tests, and a measurement of test coverage. What’s next?

Continous integration

Continuous Integration (CI) is a development practice that requires developers to integrate code into a shared repository several times a day. Each check-in is then verified by an automated build, allowing teams to detect problems early.

In final step of this guide we are going to set up an automated build of our project.

By testing our plugin in a disposable environment, we will make sure that there are no unexpected dependencies and that we can reliably build and test our plugin using different Python versions.

We are going to need three things:

  1. a public git repository of our project hosted on GitHub
  2. a free Travis CI account, set up to build our project
  3. a configuration file for Travis CI in the repository

Git repository

Let’s intialize our git repository by running the git init command in the HelloBot/ directory on our computer (instead of the virtual machine).

git status should show us all of the untracked files:

  .cache/
  .coverage
  __pycache__/
  hello.plug
  hello.py
  hello.pyc
  htmlcov/
  test_hello.py

Some of these files are directories are artifacts, or byproducts of testing and Python byte code compilation. We can ask git to ignore them by creating a .gitignore file with the following contents:

.cache/
.coverage
__pycache__/
*.pyc
htmlcov/

Now we can add the files we care about to the repository and make our first commit:

git add .gitignore hello.plug hello.py test_hello.py
git commit -m "Initial commit"

Next, create a GitHub repository and add it as a remote to your local repository. Then, push the project to the remote repository (substitute the URL for your own):

git remote add origin git@github.com:alimac/err-hello.git
git push -u origin master

Travis account

Open a Travis CI account with your GitHub credentials (steps 1 and 2).

Make sure that the repositories have synced, and flip the switch on our err-hello repository to a green checkmark:

View of repositories on Travis CI View of repositories on Travis CI

The next step is to add a .travis.yml configuration file to the repo.

Travis configuration file

In your local repo, create a file named .travis.yml and add the following contents:

language: python
python:
  - 2.7
  - 3.3
  - 3.4
  - 3.5
install:
  - pip install -q err pytest pytest-pep8 --use-wheel
  - pip install -q coverage --use-wheel
script:
  - coverage run --source hello -m py.test --pep8
notifications:
  email: false

The configuration file is where we specify the language of our project, which versions of Python we want to test, and how we run the test. We also include any packages that need to be installed.

Travis builds are triggered whenever you push to the remote repository on GitHub. Add .travis.yml to your local repository, commit it, and push to start the Travis build.

You should see it in action (refresh travis-ci.org page if you don’t see the build):

View of repositories on Travis CI Build jobs will be marked as green if they run successfully

Click on a build job to view its log:

View of repositories on Travis CI Travis CI build job log

Now every time we push code to the GitHub repository it will automatically start four build jobs (one for each version of Python).

What’s next?

Our project is missing a README.md file, but I will leave this part up to you.

Consider adding a build status badge to the README file to show that your build is passing.

You can also integrate your project with Coveralls, an online service that will show test coverage results. Like Travis CI, you can add a test coverage badge to the README indicate how well do your tests cover your codebase.

And that’s all for now. In later parts of this series, I will tackle more advanced plugin functionality by taking a look at the err-factoid and err-request-tracker plugins I built while learning about Errbot.

Was this guide useful? Did you notice any mistakes? Tell me.