thisishardtoread

code, thoughts, and learnings

github.com/arbylee

Packaging and distributing a Python project with setuptools

21 Dec 2013

Scenario: You want to package your Python project for others to retrieve via pip install your_project

There are a few packaging tool options out there, but I’m going to focus on using setuptools. If you want a brief look into the history of the available tools, this stackoverflow post provides a nice summary.

To start, let’s take a look at an example app.

import os
from jinja2 import Template


class HelloWorld(object):
    def hello(self):
        current_dir = os.path.dirname(os.path.realpath(__file__))
        contents_file = open(os.path.join(current_dir, 'contents/hello'))

        template = Template(contents_file.read())
        print template.render()

hw = HelloWorld()
hw.hello() # 'heyoooo world'

We have a simple class with a hello method that just prints the contents of a static file. We also have a superfluous use of jinja2 here to introduce an external dependency

Now let’s make sure we’ve got setuptools installed:

pip install setuptools

Next, we need to tell setuptools what to do by creating a setup.py file. There are a few straightforward, required params: name, version, author, author_email, url.

But there are some other parameters that we care about too.

from setuptools import setup, find_packages

setup(name='setuptools_app',
      version='0.0.1',
      author="yourname",
      author_email="your@email.com",
      url="http://example.com",
      packages=find_packages(),
      package_data={'setuptools_app': ['contents/hello']},
      scripts=['bin/say_hello'],
      description=("an example app packaged with setuptools"),
      long_description=open('README.rst').read(),
      install_requires=['jinja2'],
      classifiers=[
          "Programming Language :: Python",
          "License :: OSI Approved :: MIT License",
          "Development Status :: 3 - Alpha"
          ]
      )

We use find_packages from setuptools with the packages option to recursively find any python packages (folders with __init__.py) to include in the distribution.

package_data is used to explicitly declare any other files that should be part of our package. In our case, HelloWorld will need access to the data file contents/hello to run.

scripts can be used to have setuptools automatically setup our bin/say_hello script to be available on the command line after installing our package.

long_description will be used by PyPi to display on your package’s homepage. If you use reStructuredText, PyPi will convert the contents to html for us.

install_requires lets us declare our project dependencies, so that whenever the package is installed, any unfulfilled dependencies are automatically downloaded and installed.

classifiers help others classify and identify your package. Take a look at the list of identifiers on PyPi to see which ones fit for your project.

There are other options available, but we’ve set up what we need for now.

With our setup.py complete, all that’s left is to register our package, build the source distribution, and upload it. Thankfully, this can all be done with one command:

python setup.py register sdist upload

After the upload is complete, you should be able to both find your package on PyPi and run pip install your_project.

Happy packaging!

Note: I did not upload the example app to PyPi to avoid adding clutter. If you want a package with a similar setup.py to look at, the example app was based off of pythonwarrior (github / PyPi).