Packaging

Your project might already be publicly available in a GitLab/GitHub/etc. repository. But how do you actually install it? How do you make it available as a system-wide boxes command, or let your code be used by something else?

This is where packaging comes in. By creating a package, you can make your work easy to distribute, install and use.

The world of Python packaging is full of different opinions. Apply caution where necessary.

This chapter was written by Chris Warrick, whose opinions may not agree with others' world views.

A little reorganization

In Lesson 11 of Part 2, we've had three Python files: boxes.py, fonts.py, and hyphen.py. We can't install all three to our users' systems, since that could lead to conflicts and other unwelcome situations. In fact, boxes is not the best name either, as it's too generic. Fortunately, nobody had uploaded a boxes package to PyPI before this chapter was written.

That said, we need to make a few changes:

$ mkdir boxes
$ mv boxes.py boxes/__init__.py
$ mv fonts.py hyphen.py boxes/

Then, we'll create a new __main__.py file and reorganize code slightly. We'll move the UI code to that new file. First, change the first few lines like so:

 1"""Boxes, a SVG text layout engine"""
 2
 3from collections import deque
 4
 5from boxes.fonts import adjust_widths_by_letter
 6from boxes.hyphen import insert_soft_hyphens
 7
 8import svgwrite
 9
10__version__ = "0.1.4"

With that, we'll use the new names of our helper files (which are now members of our package), remove the docopt import and set __version__ (which is a good thing to have). We will then remove the if __name__ == "__main__": block at the end of boxes/__init__.py. This will now be a function that lives in __main__.py, like so:

 1"""
 2Usage:
 3    boxes <input> <output> [--page-size=<WxH>] [--separation=<sep>]
 4    boxes --version
 5"""
 6
 7from docopt import docopt
 8import boxes
 9
10
11def main():
12    """The main routine of Boxes."""
13    arguments = docopt(__doc__, version=f"Boxes {boxes.version}")
14
15    if arguments["--page-size"]:
16        p_size = [int(x) for x in arguments["--page-size"].split("x")]
17    else:
18        p_size = (30, 50)
19
20    if arguments["--separation"]:
21        separation = float(arguments["--separation"])
22    else:
23        separation = 0.05
24
25    boxes.convert(
26        input=arguments["<input>"],
27        output=arguments["<output>"],
28        page_size=p_size,
29        separation=separation,
30    )
31
32
33if __name__ == "__main__":
34    main()

We did three things here:

  1. We should ship all our code in one package (directory) so we don't make a large mess.
  2. We moved boxes.py to a boxes/__init__.py file. The contents of the __init__.py file will be what's imported when you run that.
  3. We moved user-facing code to a __main__.py file. This lets us achieve separation between library code (that others can reuse and call from their code) and UI code (that requires an interactive terminal). That filename will become important soon-ish, as will the no-argument function.

We could also consider moving our code from __init__.py to yet another submodule, but let's leave it as-is.

Writing a setup.py file

To let users install our software, as well as describe what it is, we need to write a setup.py file. We're going to write a pretty basic version of it.

 1#!/usr/bin/env python
 2# -*- encoding: utf-8 -*-
 3import io
 4from setuptools import setup, find_packages
 5
 6
 7setup(
 8    name="boxes",
 9    version="0.14",
10    description="A SVG text layout engine",
11    keywords="boxes,book,svg,typesetting",
12    author="Roberto Alsina",
13    author_email="ralsina@netmanagers.com.ar",
14    url="https://ralsina.gitlab.io/boxes-book/",
15    license="MIT",
16    long_description=io.open(
17        "./README.rst", "r", encoding="utf-8"
18    ).read(),
19    platforms="any",
20    zip_safe=False,
21    # http://pypi.python.org/pypi?%3Aaction=list_classifiers
22    classifiers=[
23        "Development Status :: 3 - Alpha",
24        "Programming Language :: Python",
25        "Topic :: Text Processing",
26        "Programming Language :: Python :: 3",
27        "Programming Language :: Python :: 3.6",
28    ],
29    packages=find_packages(exclude=("tests",)),
30    include_package_data=True,
31    install_requires=[
32        "svgwrite", "harfbuzz", "freetype2", "pyphen", "docopt"
33    ],
34    entry_points={"console_scripts": ["boxes = boxes.__main__:main"]},
35)

Nothing special. We've automated finding packages, and added an exception for our tests folder (we don't install that, and it isn't a part of our boxes package). We're using entry_points to install our package. Had our package been Windows-compatible (it isn't, due to harfbuzz/freetype2), this would make the lives of Windows users easier by producing .exe runner files.

The description will come from a README.rst file which we've written while nobody was looking. A README file is the first thing your users will see — this is where you should describe in detail what your project is and what it does.

Choosing a license

Uploading to PyPI

Creating new releases and automating the process

results matching ""

    No results matching ""