indexpost archiveatom feed syndication feed icon



I've finally made a foray into Python packaging; publishing my own static site generator. What follows are my notes on the process and some implications on the development of what used to static.

pip install quiescent

The first point to address is: "What is quiescent?", and the answer is simply, a rename of my static site generator that I've covered previously. The functionality is largely unchanged, but the interface is better. Where previously I took the lazy route and invoked the site generator through an if __name__ == '__main__' block, the new package has an honest to goodness command line interface that does the same work.

The real big news though, with my finally getting the project up on PyPI and configured correctly is that the following just works now:

pip install quiescent
cd idle-cycles/
quiescent       # just like that, blog is regenerated

Naming things is hard

Why the rename? Well, it turns out every package on PyPI has to be unique, and perhaps unsurprisingly, the name static just isn't that unique. I fully admit the name quiescent isn't very good, but I sort of hate wasting cycles on this kind of thing.

Packaging Intricacies

Much of the work involved was documented (pretty well) in this guide, with a few minor changes or differences. In order to make my own test configuration work with I first stripped out another if __name__ == '__main__' block that guarded test execution. With the following configuration in, tests may be invoked with the (I guess recommended?) interface:

      description="A static weblog generator",
          'Jinja2 >= 2.8',
          'MarkupSafe >= 0.23',
          'mistune >= 0.7.3',
      'console_scripts': ['quiescent=quiescent.command_line:main']

I am slowly coming around to the standard configuration of Python projects, which involves moving my tests into their own directory, so that imports don't clutter the package namespace. Similarly, while it seems odd to do so, I've nested a quiescent directory inside of the top-level directory of the same name.

One bit that I was very happy to learn and finally get right was the console scripts directive, which specifies command-line programs executable within the package. It is eminently nicer to finally have a single command-line program named sensibly and properly configured.

One small hiccup in the process of uploading the package to PyPI was in an opaque error after attempting to upload. It seems the only way to provide a username with which to use when uploading is through a .pypirc file in the home directory; not really a problem, but a bit underdocumented. The fix for me was to include the following in the file:

index-servers = pypi

username: nprescott

Further Work

I am not certain I have the python_requires directive correct yet. The idea is to limit the potential installations to only those that are specified (in my case, only Python 3.6 due to the use of f-strings). With limited testing, it seems I am still able to install into a Python 2 environment, I'll need to dig deeper and see if my installation of pip is at least version 9. In an attempt to test the above, I ended up bumping the version number (to 0.2), because of difficulty testing the final packagea prior to uploading. It seems the thing to do is use twine, which is designed to avoid this exact problem.

Finally, in between rewriting the README, patching in the new "bootstrap" function, and repackaging the tests; I realized -- some of the architecture in the current version is less than stellar. Running coverage reveals large portions of the code are not yet tested, specifically those that serve to write file output or create and move directories. I've been mulling over how to better design these necessary I/O dependencies and am considering a more "functional core, imperative shell" model, which I have yet to try.

The point of which seems to be, actually publishing something where people might see it in a state beyond "it's code you found on Github, what more do you want" sort of forces better decision making. A problem for another day.