A preventable Python packaging peeve

Tuesday 9 February 2010This is close to 15 years old. Be careful.

Python packaging is a common theme on which to complain, and rightly so. It’s no one’s first love, so it tends not to get the devoted attention of say, Numpy. And it’s a hard problem to solve well. So we have a mish-mash of tools that each do about 75% of the job.

But there’s one small aspect of Python packaging that could easily be solved well if people just attended to it: Not enough Python projects clearly state what versions of Python they run on.

For example, suppose you are in the market for a mock object library for your tests. There’s no shortage. Less than a minute at PyPI produces mock, MiniMock, mocktest, Mocky, pmock, mocker, mockito, and ludibrio. Some of those PyPI pages have extensive documentation. Not a single one explicitly mentions the versions of Python supported. And I don’t mean 2.x vs. 3.x. I want to know if it will run on 2.4 or not. Ludibrio and Mocky offer a slight clue in that they are available for download as an egg, for 2.5 and 2.4 respectively. pMock mentions >= 2.3 support on the home page linked from the PyPI page.

On top of all the other well-known difficulties people have with Python packaging, at the very least, we should be able to manage this: clearly state what versions of Python you support. This is a simple three-step process:

  1. Decide what versions you want to support.
  2. Test your code on those versions.
  3. Add a sentence like this to your PyPI documentation: “SpockMocker runs on Python 2.5 and 2.6”.

The Python community will thank you.

Comments

[gravatar]
You can declare formal Python version dependencies. But those dependencies are hard, which sucks -- you'll get errors if you try to use it on the wrong Python version. Maybe, for instance, you don't know if Python 2.6 works; you don't want to keep people from trying, you just don't want to lie and say you've tested it either (and maybe the package is old and 2.6 wasn't out at the time).

It would be helpful to indicate what platforms are tested, or intended to run. In a lot of cases there's really not much intention though. The author can say what they use and test, but that's the sum of it. (Of course, some libraries explicitly don't support some versions of Python, usually 2.3 or 2.4, and that could actually be put into dependency information and made quite explicit).
[gravatar]
I have a couple of pet peeves as well. I just recently authored my first package so it's possible I missed something obvious during the process. Anyway, here we go:

* Replace .pypirc password field with something more secure (ssh?). Storing my PyPI in plain ASCII just doesn't feel right.
* Add long_description checker to PyPI side. Currently it's possible to handle this manually by using http://pypi.python.org/pypi/collective.checkdocs/0.1.1 but I would not mind if PyPI would reject my package if it's long_description was not valid reST syntax.
* Add "documentation" field. It might be nice if it pointed directly to http://packages.python.org/ if the packager has uploaded some documentation there though I would be more than happy with a field that's just set explicitly.

Just a comment about the version issue... Is there some simple way to set up a testing environment so that the package may be run against given range of versions (ie. 2.2-2.6, 3.0-3.1)? This also brings forth the interesting question how to handle any version specific hacks. I rather would not pollute single codebase with the hacks. Perhaps there should be a branch for each or something similar.
[gravatar]
@Ian, yes these dependencies can be succinctly expressed in metadata, but I'd also like to see them in human readable form somehow. If it can be auto-extracted from the metadata for display on the PyPI page, great. When I'm shopping around for a package, a big concern is whether its Python version support is a superset of mine. I don't want to have to try installing the package to find out if I'm covered.
[gravatar]
Some classifiers are meant for that.
setup(
    name='mypackage',
    packages=['mypackage'],
    version='1.0',
    description='Does something usefull',
    long_description=open('README.rst', 'r').read(),
    classifiers = [
        # Get more strings from http://www.python.org/pypi?%3Aaction=list_classifiers
        "Development Status :: 5 - Production/Stable",
        "Programming Language :: Python :: 2.6",
        "Programming Language :: Python :: 2.7",
    ]
   )
e.g. for Pinax, on Pypi, look under category:

http://pypi.python.org/pypi/Pinax/
[gravatar]
@Damien, @Ned : Notice that in Python 2.7 with PEP 345, you will be able to define which Python versions are required. See : ttp://www.python.org/dev/peps/pep-0345/#requires-python. This will be better than the classifier because it will be directly parseable.


@Juho: you can omit the password now, distutils will ask it for you at the prompt. I am also working on integrating Keyring.

2.7 contains now a "check" command you can use to check for reSt compliancy.
[gravatar]
@Damien: it's interesting that the Pinax page you point to seems to imply that it only works on Python 2.4, since it's the only version listed.

@Tarek, these all look like great improvements, but I'd be happy if people just put words on the page too...
[gravatar]
@Ned. Sure ! just mentioning this because it implies that tools like Pip will be able to warn you when your Python is not in the list of required Python when they are given by the project. So you would get the "SpockMocker runs on Python 2.5 and 2.6" sentence when you do "pip install SpockMocker" under Python 2.7
[gravatar]
Thanks for pointing this out. I've just run the mock tests with Python 2.4 and they all pass except the test of using 'patch' as a context manager. Those tests are kept in a separate module because the with statement is invalid syntax in Python 2.4.

I've raised an issue on the mock issue tracker to add the version classifications to PyPI next time I do a release. I'd better check my other packages as well. :-(
[gravatar]
Cool. Most, if not all, of my pet peeves will be gone after 2.7 is released. :)

I also realised that Bicking's excellent virtualenv should provide a nice basis for testing against different versions of Python. Pretty much all you need to do is to set up a script to generate virtualenvs against wanted versions of Python and have it run the tests against each version at each env. I don't know it has direct API for something like this but it should be doable anyhow. I wouldn't be surprised if there was an existing solution for this.

Considering the codebase issue I mentioned about earlier it might be most straightforward to have one branch for 2.x and 3.x each. I suppose that's how most of people handle it. Optimally you should be able to focus on maintaining on 2.x version while porting the changes using the 2to3 utility (no idea how well it works in practice) though I guess it might take some manual work as well.
[gravatar]
Inspired by a Stack Overflow question, last year I wrote a tool called pyqver that reads Python source code to (try to) automatically determine the minimum version of Python required to run. It works reasonably well, but with a dynamic language like Python there will always be ways to subvert it.

I'll need to update it for Python 3.x+ sometime soon.

Add a comment:

Ignore this:
Leave this empty:
Name is required. Either email or web are required. Email won't be displayed and I won't spam you. Your web site won't be indexed by search engines.
Don't put anything here:
Leave this empty:
Comment text is Markdown.