Python: Getting Started with setuptools

References

* PEAK Dev Center


Overview

setuptools is a collection of enhancements to the Python distutils that allow you to more easily build and distribute Python packages, especially ones that have dependencies on other packages.

Now just to be clear, setuptools has a lot of features... however, so far I've really only needed two of them. Setting up console scripts, and enabling plugins to tie into an application. I expect that as I use setuptools more and more, there will be much more that I will want to add here.

You might be reading this and wonder, "Why not just follow the code examples from PEAK?". Well.. that is simple, as with many of my articles, I always try to keep the beginner in mind. And far too often, sifting through the full documentation with 100 different ways to do something gets well confusing. I'm hoping that breaking it down a bit further with some complete examples might help someone move towards adopting setuptools into their daily tool kit. That said, this article is an overview of a the basic usage of setuptools.



Before We Begin - YAHW (Yet Another Hello World)

The following code sets up a basic python command line application that will be used to add setuptools to. Create the files to follow along... or don't. The first file we look at is the __init__.py within the helloworld module. We will deal with this later when we talk about plugins, however for now just create an empty file.

Note: Before starting, you should be working out of a current directory called 'helloworld'. Therefore, from within that directly, there is another 'helloworld' directory also... the second level directory is the helloworld library that gets installed.

 $ pwd
 
/Users/you/helloworld



./helloworld/__init__.py:

# leave file empty



./helloworld/core.py:

#!/usr/bin/env python
 
class HelloWorld(object):
    def __init__(self):
        pass
 
    def say_hi(self, name=None):
        if name:
            print 'Hello World! My name is %s.' % name
        else:
            print 'Hello World!'
 
def main():
    h = HelloWorld()
    h.say_hi('johnny')
 
if __name__ == '__main__':
    main()

Note: core.py is an arbitrary name for the file, that is just a personal preference as a starting point (though is common practice by many).



So, with this simple application I quickly say hi to everyone by running it directly (via __main__):

$ python helloworld/core.py 
Hello World! My name is johnny.



Now, that's all and good... but most likely if this is a command line utility/application, it will be expected to be accessible via a common location like '/usr/bin/helloworld'. Now, technically I could simply install helloworld/core.py to /usr/bin/helloworld but that isn't what we are going for here, is it? (is it???)


AttachmentSize
helloworld-1.tar.gz384 bytes

Python - Automatic Script Creation with setuptools

Overview

This article is a continuation of Getting Started with setuptools.


entry_points are a dictionary mapping entry point group names to strings or lists of strings defining the entry points. Entry points are used to support dynamic discovery of services or plugins provided by a project. See Dynamic Discovery of Services and Plugins for details and examples of the format of this argument. In addition, this keyword is used to support Automatic Script Creation.

The following is an example setup.py that automatically creates our '/usr/bin/helloworld' (or similar) console script:

./setup.py:

#!/usr/bin/env python
 
from setuptools import setup, find_packages
import sys, os
 
version = '0.1'
 
setup(name='helloworld',
    version=version,
    description="My Hello World Application",
    long_description="""My even longer description about Hello World.""",
    classifiers=[],
    keywords='',
    author='Your Name',
    author_email='you@example.com',
    url='',
    license='',
    packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
    include_package_data=True,
    zip_safe=False,
    install_requires=[
        # -*- Extra requirements: -*-
        ],
    entry_points="""
    [console_scripts]
    helloworld = helloworld.core:main
    """
    )



After creating the setup.py, I can now install it or because we are still working on it, we can use the setuptools develop mode feature:

$ sudo python setup.py develop
 
running develop
running egg_info
writing helloworld.egg-info/PKG-INFO
writing top-level names to helloworld.egg-info/top_level.txt
writing dependency_links to helloworld.egg-info/dependency_links.txt
writing entry points to helloworld.egg-info/entry_points.txt
reading manifest file 'helloworld.egg-info/SOURCES.txt'
writing manifest file 'helloworld.egg-info/SOURCES.txt'
running build_ext
Creating /opt/local/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages/helloworld.egg-link (link to .)
helloworld 0.1 is already the active version in easy-install.pth
Installing helloworld script to /opt/local/Library/Frameworks/Python.framework/Versions/2.6/bin
 
Installed /Users/you/helloworld
Processing dependencies for helloworld==0.1
Finished processing dependencies for helloworld==0.1

Note: Using 'develop' rather than 'install' allows you to deploy your project in "development mode", such that it's available on sys.path, yet can still be edited directly from its source checkout.



We should now be able to call the command line utility 'helloworld' right away:

$ helloworld
 
Hello World! My name is johnny.



Depending on the system you are working on (OSX in this example) depends on where the console script gets created to. On most Linux distributions this should end up being /usr/bin or /usr/local/bin. In my case it is in a deep, convoluted path that Apple chose. If we take a look at the script it creates you'll see how it is calling our application:

#!/usr/bin/env python
# EASY-INSTALL-ENTRY-SCRIPT: 'helloworld==0.1','console_scripts','helloworld'
__requires__ = 'helloworld==0.1'
import sys
from pkg_resources import load_entry_point
 
sys.exit(
   load_entry_point('helloworld==0.1', 'console_scripts', 'helloworld')()
)


And so... because we registered our helloworld.core.main() function as an entry point for the helloworld console script, when you run it the entry point is identified via pkg_resources.load_entry_point() and therefore main() is called.

Solid?


AttachmentSize
helloworld-2.tar.gz780 bytes