Creating an Installable Plugin

The process for sharing plugins with others is well-defined. Even if you never put your own plugin up on PyPI, by walking through the process, you’ll have an easier time reading the code from open source plugins and be better equipped to judge if they will help you or not.

It would be overkill to fully cover Python packaging and distribution in this book, as the topic is well documented elsewhere.[13][14] However, it’s a small task to go from the local config plugin we created in the previous section to something pip-installable.

First, we need to create a new directory to put our plugin code. It does not matter what you call it, but since we are making a plugin for the “nice” flag, let’s call it pytest-nice. We will have two files in this new directory: pytest_nice.py and setup.py. (The tests directory will be discussed in Testing Plugins.)

 pytest-nice
 ├── LICENCE
 ├── README.rst
 ├── pytest_nice.py
 ├── setup.py
 └── tests
  ├── conftest.py
  └── test_nice.py

In pytest_nice.py, we’ll put the exact contents of our conftest.py that were related to this feature (and take it out of the tasks_proj/tests/conftest.py):

ch5/pytest-nice/pytest_nice.py
 """Code for pytest-nice plugin."""
 
 import​ pytest
 
 
 def​ pytest_addoption(parser):
 """Turn nice features on with --nice option."""
  group = parser.getgroup(​'nice'​)
  group.addoption(​"--nice"​, action=​"store_true"​,
  help=​"nice: turn FAILED into OPPORTUNITY for improvement"​)
 
 
 def​ pytest_report_header():
 """Thank tester for running tests."""
 if​ pytest.config.getoption(​'nice'​):
 return​ ​"Thanks for running the tests."
 
 
 def​ pytest_report_teststatus(report):
 """Turn failures into opportunities."""
 if​ report.when == ​'call'​:
 if​ report.failed ​and​ pytest.config.getoption(​'nice'​):
 return​ (report.outcome, ​'O'​, ​'OPPORTUNITY for improvement'​)

In setup.py, we need a very minimal call to setup():

ch5/pytest-nice/setup.py
 """Setup for pytest-nice plugin."""
 
 from​ setuptools ​import​ setup
 
 setup(
  name=​'pytest-nice'​,
  version=​'0.1.0'​,
  description=​'A pytest plugin to turn FAILURE into OPPORTUNITY'​,
  url=​'https://wherever/you/have/info/on/this/package'​,
  author=​'Your Name'​,
  author_email=​'your_email@somewhere.com'​,
  license=​'proprietary'​,
  py_modules=[​'pytest_nice'​],
  install_requires=[​'pytest'​],
  entry_points={​'pytest11'​: [​'nice = pytest_nice'​, ], },
 )

You’ll want more information in your setup if you’re going to distribute to a wide audience or online. However, for a small team or just for yourself, this will suffice.

You can include many more parameters to setup(); we only have the required fields. The version field is the version of this plugin. And it’s up to you when you bump the version. The url field is required. You can leave it out, but you get a warning if you do. The author and author_email fields can be replaced with maintainer and maintainer_email, but one of those pairs needs to be there. The license field is a short text field. It can be one of the many open source licenses, your name or company, or whatever is appropriate for you. The py_modules entry lists pytest_nice as our one and only module for this plugin. Although it’s a list and you could include more than one module, if I had more than one, I’d use packages instead and put all the modules inside a directory.

So far, all of the parameters to setup() are standard and used for all Python installers. The piece that is different for pytest plugins is the entry_points parameter. We have listed entry_points={’pytest11’: [’nice = pytest_nice’, ], },. The entry_points feature is standard for setuptools, but pytest11 is a special identifier that pytest looks for. With this line, we are telling pytest that nice is the name of our plugin, and pytest_nice is the name of the module where our plugin lives. If we had used a package, our entry here would be:

 entry_points={​'pytest11'​: [​'name_of_plugin = myproject.pluginmodule'​,], },

I haven’t talked about the README.rst file yet. Some form of README is a requirement by setuptools. If you leave it out, you’ll get this:

 ...
 warning: sdist: standard file not found: should have one of README,
  README.rst, README.txt
 ...

Keeping a README around as a standard way to include some information about a project is a good idea anyway. Here’s what I’ve put in the file for pytest-nice:

ch5/pytest-nice/README.rst
 pytest-nice : A pytest plugin
 =============================
 
 Makes pytest output just a bit nicer during failures.
 
 Features
 --------
 
 - Includes user name of person running tests in pytest output.
 - Adds ``--nice`` option that:
 
  - turns ``F`` to ``O``
  - with ``-v``, turns ``FAILURE`` to ``OPPORTUNITY for improvement``
 
 Installation
 ------------
 
 Given that our pytest plugins are being saved in .tar.gz form in the
 shared directory PATH, then install like this:
 
 ::
 
  $ pip install PATH/pytest-nice-0.1.0.tar.gz
  $ pip install --no-index --find-links PATH pytest-nice
 
 Usage
 -----
 
 ::
 
  $ pytest --nice

There are lots of opinions about what should be in a README. This is a rather minimal version, but it works.