Distributing Python Packages Part I: Creating a Python Package
I write lots of Python scripts that I think would be useful to other
people, but I've put off learning how to submit to the Python Package Index,
PyPI, so that my packages can be installed using pip install
.
Now that I've finally done it, I see why I put it off for so long. Unlike programming in Python, packaging is a huge, poorly documented hassle, and it took me days to get a working.package. Maybe some of the hints here will help other struggling Pythonistas.
Create a setup.py
The setup.py file is the file that describes the files in your project and other installation information. If you've never created a setup.py before, Submitting a Python package with GitHub and PyPI has a decent example, and you can find lots more good examples with a web search for "setup.py", so I'll skip the basics and just mention some of the parts that weren't straightforward.
Distutils vs. Setuptools
However, there's one confusing point that no one seems to mention.
setup.py
examples all rely on a predefined function
called setup
, but some examples start with
from distutils.core import setupwhile others start with
from setuptools import setup
In other words, there are two different versions of setup
!
What's the difference? I still have no idea. The setuptools
version seems to be a bit more advanced, and I found that using
distutils.core
, sometimes I'd get weird errors when
trying to follow suggestions I found on the web. So I ended up using
the setuptools
version.
But I didn't initially have setuptools installed (it's not part of the standard Python distribution), so I installed it from the Debian package:
apt-get install python-setuptools python-wheel
The python-wheel
package isn't strictly needed, but I
found I got assorted warnings warnings from pip install
later in the process ("Cannot build wheel") unless I installed it, so
I recommend you install it from the start.
Including scripts
setup.py has a scripts option to include scripts that are part of your package:
scripts=['script1', 'script2'],
But when I tried to use it, I had all sorts of problems, starting with
scripts not actually being included in the source distribution. There
isn't much support for using scripts
-- it turns out
you're actually supposed to use something called
console_scripts
, which is more elaborate.
First, you can't have a separate script file, or even a __main__
inside an existing class file. You must have a function, typically
called main(), so you'll typically have this:
def main(): # do your script stuff if __name__ == "__main__": main()
Then add something like this to your setup.py
:
entry_points={ 'console_scripts': [ script1=yourpackage.filename:main', script2=yourpackage.filename2:main' ] },
There's a secret undocumented alternative that a few people use for scripts with graphical user interfaces: use 'gui_scripts' rather than 'console_scripts'. It seems to work when I try it, but the fact that it's not documented and none of the Python experts even seem to know about it scared me off, and I stuck with 'console_scripts'.
Including data files
One of my packages, pytopo, has a couple of files it needs to install,
like an icon image. setup.py
has a provision for that:
data_files=[('/usr/share/pixmaps', ["resources/appname.png"]), ('/usr/share/applications', ["resources/appname.desktop"]), ('/usr/share/appname', ["resources/pin.png"]), ],
Great -- except it doesn't work. None of the files actually gets added to the source distribution.
One solution people mention to a "files not getting added" problem is
to create an explicit MANIFEST file listing all files that need to be
in the distribution. Normally, setup generates the MANIFEST automatically,
but apparently it isn't smart enough to notice data_files
and include those in its generated MANIFEST.
I tried creating a MANIFEST listing all the .py files plus the various resources -- but it didn't make any difference. My MANIFEST was ignored.
The solution turned out to be creating a MANIFEST.in file, which is used to generate a MANIFEST. It's easier than creating the MANIFEST itself: you don't have to list every file, just patterns that describe them:
include setup.py include packagename/*.py include resources/*If you have any scripts that don't use the extension .py, don't forget to include them as well. This may have been why
scripts=
didn't work for me earlier, but by the time
I found out about MANIFEST.in I had already switched to using
console_scripts
.
Testing setup.py
Once you have a setup.py, use it to generate a source distribution with:
python setup.py sdist(You can also use bdist to generate a binary distribution, but you'll probably only need that if you're compiling C as part of your package. Source dists are apparently enough for pure Python packages.)
Your package will end up in dist/packagename-version.tar.gz
so you can use tar tf dist/packagename-version.tar.gz
to verify what files are in it. Work on your setup.py until you
don't get any errors or warnings and the list of files looks right.
Congratulations -- you've made a Python package! I'll post a followup article in a day or two about more ways of testing, and how to submit your working package to PyPI.
Update: Part II is up: Distributing Python Packages Part II: Submitting to PyPI.
[ 12:54 Dec 11, 2016 More programming | permalink to this entry | ]