My Unofficial “How to Extend distutils” FAQ

Whenever I have to set up a Python project, I’m always amazed at how little documentation there is on extending distutils to add new commands or otherwise deal with un-anticipated (and apparently, some anticipated) circumstances. In the absence of an easily found HOWTO or guide, I’ve had to go source-diving… and when I do that, it’s only good manners that I share what I found.

The following questions are things I found myself frequently wondering, but feel free to ask me other questions. I’ll add answers for the “likely to be frequent” ones as time permits.

How do I create a new command for distutils?
As I find myself often forgetting this, I’ve posted a template with explanatory comments as a GitHub Gist.
How do I add my custom command to multiple projects without including it and setting cmdclass in each one?
As far as I know, using only distutils, you can’t. However, setuptools provides a mechanism.
How do I make a custom command fail gracefully if non-optional dependencies aren’t present?
  • For dependencies internal to Python (modules, specific module versions, etc.), you can define your command inside a try/except or if block. my template includes an example.
  • For external commands, I recommend defining your command inside an if block and using a which function to test for them. (Using an external “which” command is ugly and fragile)
How do I access the attributes I passed to setup() from my custom command?
This was a surprisingly involved thing to figure out. setup() passes its arguments to an instance of distutils.dist.Distribution which then parcels them out to various places. 

The simple explanation is that, if it’s set in setup() and setup.py doesn’t complain about it being unrecognized, your command can access it as an attribute of either self.distribution or self.distribution.metadata. (With the possible exception of it being a command-specific option. I forget how those work)

However, the metadata also offers a comprehensive collection of getter methods which return strings like “UNKNOWN” in place of None, so you’ll probably want to ask PyDoc for details:

pydoc distutils.dist.DistributionMetadata

A practical example of the most common use I’ve had for this is retrieving the list of packages to be installed via self.distribution.packages so a tool like Epydoc or Pylint can be run on them.

What’s the recommended way to output messages?
I’m not sure, but from distutils import log seems to be what’s used internally.
Is there a clean, sane way to bundle resources (eg. custom toolbar icons) with my project
Not really. [1] [2] [3] The approaches linked aren’t pretty, but they’re all I’ve been able to find.
Where do I find new commands?
Given that Python has PyPI, you’d think there would at least be a repository of commands for setuptools. However, aside from the commands that come bundled with their associated programs (nosetests, flakes, build_sphinx, etc.), I haven’t found anything. The wiki mentioned in the next question has a very sparse cookbook though.
There’s really no community documentation for this stuff?
There is, but it only covers some of the questions listed here. One of these days when I’m not feeling so unmotivated, I’ll merge this FAQ into theirs. 

Until then, these sites may help to cover some of the deficiencies:

Isn’t anybody working to fix this mess?
Yes, but it’s not going to happen overnight.

CC BY-SA 4.0 My Unofficial “How to Extend distutils” FAQ by Stephan Sokolow is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

This entry was posted in Geek Stuff. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

By submitting a comment here you grant this site a perpetual license to reproduce your words and name/web site in attribution under the same terms as the associated post.

All comments are moderated. If your comment is generic enough to apply to any post, it will be assumed to be spam. Borderline comments will have their URL field erased before being approved.