setup.py - Package and install CodeChat

Builds and installs CodeChat.

Packaging notes

Packaging on Python is a mess, IMHO. It takes an easy job and makes it hard.

A quick summary: distutils can’t install dependencies from PyPI, so use setuptools. A source distribution is a good idea becaues it can run on a bare Python installation with no other installs required, but there’s no standard format (.zip?, .tar.gz?, etc.). An .egg is nice, but requires setuptools/pip/ez_setup installed. The .whl (Python wheel) is the latest and greatest format that superceeds eggs, but with similar problems (requires wheel to be installed).

Reading to get up to speed:

  • Python Packaging User Guide - the most up-to-date reference I’ve found so far. Tells which tools to actually use.
  • distutils - The built-in installer. Tells what to do, but not what actually happens. It doesn’t have the ability to install dependencies from PyPI, which I need.
  • setuptools - A distutils replacement which can install dependencies, so I use it.

To package

Create a source distribution, a built distribution, then upload both to CodeChat at PyPI:

python setup.py sdist bdist_wheel upload

To upload docs, which are placed here (make sure to run Sphinx first, so the docs will be current):

python setup.py upload_docs --upload-dir=_build\html

For development:

python setup.py develop

Yajo helped package this for Linux. Thanks so much. See also python-codechat.spec - openSUSE Build Service packaging file. Unfortunately, the Linux packaging is untested.

Packaging script

Otherwise known as the evils of setup.py.

For users who install this from source but don’t have setuptools installed, auto-install it. When packaging for Linux, downloads are blocked so we must specify a very old already-installed version. Leave this as a patch so that we normally use a more modern version.

import ez_setup
ez_setup.use_setuptools()

PyPA copied code

From PyPA’s sample setup.py, read long_description from a file. This code was last updated on 26-May-2015 based on this commit.

Always prefer setuptools over distutils

from setuptools import setup, find_packages

To use a consistent encoding

from codecs import open
from os import path

Imports for version parse code.

import sys
import os
import re
import io

here = path.abspath(path.dirname(__file__))
 

Get the long description from the relevant file.

with open(path.join(here, 'README.rst'), encoding='utf-8') as f:
    long_description = f.read()

The inclusion of a raw tag causes PyPI to not render the reST. Ouch. Remove it before uploading.

    long_description = re.sub('\.\. raw.*<\/iframe>', '', long_description, flags=re.DOTALL)
 

This code was copied from version parse code. See version in the call to setup below.

def read(*names, **kwargs):
    with io.open(
        os.path.join(os.path.dirname(__file__), *names),
        encoding=kwargs.get("encoding", "utf8")
    ) as fp:
        return fp.read()

def find_version(*file_paths):
    version_file = read(*file_paths)
    version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
                              version_file, re.M)
    if version_match:
        return version_match.group(1)
    raise RuntimeError("Unable to find version string.")
 

My code

We support Python 3.3 and higher.

assert sys.version_info >= (3, 3)

setup(

This must comply with PEP 0426‘s name requirements.

    name='CodeChat',
 

Projects should comply with the version scheme specified in PEP440. I use this so that my Sphinx docs will have the same version number. There are a lot of alternatives in Single-sourcing the Project Version. While I like something simple, such as import CodeChat then version=CodeChat.__version__ here, this means any dependeninces of __init__.py will be requred to run setup, a bad thing. So, instead I read the file in setup.py and parse the version with a regex (see version parse code).

    version=find_version("CodeChat", "__init__.py"),

    description="The CodeChat system for software documentation",
    long_description=long_description,
 

The project’s main homepage.

    url='https://pythonhosted.org/CodeChat/README.html',
 

Obscure my e-mail address to help defeat spam-bots.

    author="Bryan A. Jones",
    author_email="bjones AT ece.msstate.edu",

    license='GPLv3+',
 

These are taken from the full list.

    classifiers=[
        'Development Status :: 5 - Production/Stable',
        'Intended Audience :: Developers',
        'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
        'Operating System :: OS Independent',
        'Natural Language :: English',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.3',
        'Programming Language :: Python :: 3.4',
        'Programming Language :: Python :: 3.5',
        'Topic :: Software Development :: Documentation',
        'Topic :: Text Processing :: Markup',
    ],

    keywords='literate programming',

    packages=['CodeChat'],
 

List run-time dependencies here. These will be installed by pip when your project is installed. For an analysis of “install_requires” vs pip’s requirements files see: https://packaging.python.org/en/latest/requirements.html

    install_requires=(

Enum was introduced in Python 3.4. Use a backport of it if needed.

      (['enum34'] if sys.version_info.minor == 3 else [])

Note: I don’t include Sphinx in this list: while CodeToRest.py can be executed from the command line if the packages below are installed, CodeToRestSphinx.py can only be executed by Sphinx.

      + ['docutils>=0.12',
         'pygments>=2.1']),
 

List additional groups of dependencies here (e.g. development dependencies). You can install these using the following syntax, for example:

$ pip install -e .[test]
    extras_require={
        'test': ['pytest'],
    },
 

To package data files, I’m using include_package_data=True then putting the files in MANIFEST.in. See including data files.

    include_package_data=True,
)