From concept to announcement
A practical guide to taking a Python package from an initial idea through project setup, testing, PyPI distribution, and announcement.
Tackling Python open source projects
Overview
PyPI, GitHub, Google Code, and similar sites contain packages with very different layouts, test runners, and naming conventions. That can be confusing to new package authors. This is a record of my own process for taking a Python package from scratch through announcement.
The post assumes GitHub, though another distributed version-control system can be substituted. It also assumes you use version control. Nobody skips that these days, right?
So you have an idea on how to solve a problem
First, see whether someone already solved it. Search PyPI, GitHub, Google Code, Bitbucket, and elsewhere. If an existing project solves the problem and is not disastrous, use it. If it is almost what you need, fork it and contribute. Do not rebuild the wheel.
If you still need a new project, start with a prototype for a small scope or a high-level architecture diagram for a larger project. Good design is at least as important as good code; otherwise you will spend time refactoring or create hard-to-maintain spaghetti code.
PEP 8: Read it, learn it, love it
There is not much more to say: read it.
Name your package
Use a name indicative of the package’s purpose. Package names should be lowercase with underscores; parent directories should use hyphens. A starting layout might be:
my-package/
my_package/
Virtual environments
Develop each project in a virtual environment to keep dependencies separate from global modules. It makes dependencies visible through pip freeze and permits incompatible package versions across projects.
I strongly suggested virtualenvwrapper over bare virtualenv at the time. I also kept a global_requirements.txt outside projects for universal development dependencies such as Sphinx.
The skeleton
Git initialization
git init
Skeletal files
Each Python project should have:
README.rstorREADME.mdfor a synopsis and documentation linksLICENSEsetup.pyfor packaging and installation metadatarequirements.txt, produced frompip freeze, for dependency installation- a
Makefilefor repetitive build, test, example, and documentation tasks MANIFEST.infor distribution contentsCHANGES.txtfor release notes- tests in
tests.pyor a dedicated directory - a
docs/directory generated withsphinx-quickstart
For a reusable package, add an example application as well. It documents intended usage and can provide functional tests. Once the skeleton exists, make the traditional first commit:
Initial commit
From there, add, commit, and push as atomically as possible.
TDD/BDD
Depending on the problem, I try to use TDD or BDD: tests come before library code. It is not for everyone, but I have found it invaluable.
My historical workflow used Vim for code and a second terminal for tests and ipdb. I generally used nose and coverage:
# -s prevents nose from capturing output.
# -x stops at the first error.
# --pdb breaks into pdb.
nosetests . -s -x --pdb
nosetests -s --pdb --with-coverage --cover-package=my_package
Consult each tool’s documentation for its available configuration.
Writing the modules
Conform to PEP 8 where possible. Use established naming conventions and spaces rather than tabs. I followed the 79-character limit, wrote doctests where appropriate, and used the Python data model when it fit.
I also kept PEP 20, SOLID principles, and a balance between object-oriented and functional techniques in mind. The right balance comes through experience and iteration.
Distribution via PyPI
Historical note: this section describes the pre-Python-3 packaging ecosystem.
distutils
The original standard packaging tool.
setuptools
A project created to address perceived gaps in distutils; it became a common packaging choice for Python 2.
distribute
A setuptools fork created by developers who felt its development was too slow.
Authoring setup.py
A Python 2 setup.py generally contains project attributes, classifiers, and descriptors. A simplified example based on sanction:
from setuptools import setup
setup(
name='sanction',
keywords='python,oauth2',
version='0.2',
description='A simple, lightweight OAuth2 client',
author='Demian Brecht',
url='https://github.com/demianbrecht/sanction',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Programming Language :: Python',
'Topic :: Internet :: WWW/HTTP',
],
packages=['sanction'],
)
After creating a PyPI account, the historical source-distribution upload command was:
python setup.py sdist upload
For closed-source or in-development work, a requirements.txt describing dependencies and versions may be sufficient.
Announcements
“If you build it, they will come” does not work especially well in open source. GitHub and similar services are saturated with content, so market the package. My targets were python-list@python.org, Google+ and LinkedIn communities, and Twitter. Another option was to find recent, unanswered Stack Overflow questions that the package genuinely addressed.
Conclusion
That is my historical brain dump for authoring Python packages from concept to announcement. I hope it provides some clarity among the many package structures found in the wild.
Comments