Installation

There are two things necessary for a good Python package installation. First, the setup.cfg and/or setup.py files which control the installation of the package itself. This is needed, if you want to create your package and use it like a pip installed package. The other thing is the environment.yml which is used to set up the environment for the installation. For this small project, the environment.yml is using a sledgehammer to crack a nut. We will still want to keep it here for completeness.

Actual installation

Having all these corner stones in place, we simply need to install it. This is simply done by the following line of code.

# Install your package
pip install -e .

The -e simply let’s you modify the package without having to reinstall it all the time. So, testing of the package can be done with every change of the source files even though there is no re-installation. Note that this is only necessary for packages that you install from the source code and plan on modifying!

setup.cfg

In newer installations, the setup.cfg replaces parts of the setup.py or, in many cases, even the whole file. Here, we define all the options that we want setuptools.setup() to use when executing:

pip install -e .

First, let’s have a look at the file in our project:

 1[metadata]
 2# replace with your username:
 3name = htmapp
 4version = 0.0.4
 5author = Lucas Sawade, Peter Makus
 6author_email = lsawade@princeton.edu
 7description = How to make a Python package.
 8long_description = file: README.md
 9long_description_content_type = text/markdown
10url = https://github.com/lsawade/how_to_make_a_python_package
11project_urls =
12    Documentation = https://how-to-make-a-python-package.readthedocs.io/en/latest/
13    TravisCI = https://travis-ci.com/github/lsawade/how_to_make_a_python_package
14classifiers =
15    Programming Language :: Python :: 3
16    License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
17    Operating System :: OS Independent
18keywords = Fun, Education, Learning, Programming
19
20[options]
21package_dir =
22    = src
23packages = find:
24python_requires = >=3.6
25install_requires = numpy
26tests_require = pytest
27zip_safe = False
28
29[options.extras_require]
30docs = 
31    sphinx 
32    sphinx-rtd-theme
33tests = pytest; py
34
35[options.entry_points]
36console_scripts =
37    sample-bin = matpy.bins.sample_bin:main
38
39[options.packages.find]
40where = src

Most of the defined options are quite self-explanatory.

[options.packages.find]
where = src

defines where the package is located. Typically, we have a src folder that contains the whole package. This line changes the import structure so that we can later run import matpy instead of import src.matpy. For a list of all available parameters and their correct syntax check out the setuptools documentation.

Scripts argument

The scripts argument lets you create executables that are installed upon running the installation. For example, in the directory bins (binaries, which are not technically binaries in this case, but used in the same way as traditional binary files), there is the executable sample-bin:

 1#!/usr/bin/env python
 2
 3from matpy import MatrixMultiplication
 4import numpy as np
 5import logging
 6
 7# Use this to set the logger to different levels.
 8logger = logging.getLogger("matpy")
 9
10
11def main():
12    # Set up tests arrays
13    a = np.array([[1, 2],
14                  [3, 4]])
15    b = np.array([[2, 3, 5],
16                  [4, 5, 6]])
17
18    # Use class and function call
19    M = MatrixMultiplication(a, b, method='matmul')
20    c = M()
21
22    print("C:")
23    print(c)
24
25if __name__ == "__main__":
26    main()

It is normal Python code, but after installation

pip install -e .

you can simply run

sample-bin

from anywhere on your computer, which will run the python command sample_bin(). Do make sure that this filenames do not conflict with your traditional command line tools such as ls, cd, etc. Otherwise you won’t be able to use your created executables.

setup.py

In some cases, you might still want to include a setup.py which used to be the old standard way. One of those cases is if you would like to be able to use the -e (editable) pip install command. Then, you would just include a minimal setup.py containing the following:

from setuptools import setup

# configuration is done in setup.cfg
setup()

A second use-case of the setup.py is illustrated in our sample file:

 1""" :noindex:
 2Setup.py file that governs the installatino process of
 3`how_to_make_a_python_package` it is used by
 4`conda install -f environment.yml` which will install the package in an
 5environment specified in that file.
 6
 7"""
 8from setuptools import setup
 9from setuptools.command.test import test as testcommand
10
11# This installs the pytest command. Meaning that you can simply type pytest
12# anywhere and "pytest" will look for all available tests in the current
13# directory and subdirectories recursively (not yet supported in the setup.cfg)
14
15
16class PyTest(testcommand):
17    user_options = [('pytest-args=', 'a', "Arguments to pass to py.tests")]
18
19    def initialize_options(self):
20        testcommand.initialize_options(self)
21        self.pytest_args = []
22
23    def run_tests(self):
24        import pytest
25        import sys
26        errno = pytest.main(self.pytest_args)
27        sys.exit(errno)
28
29
30setup(cmdclass={'tests': PyTest})

Here, we add the cmdclass keyword, which is not supported by the setup.cfg (at least, at the time of writing). After package installation, when you in the repository, a simple

pytest

will check all subdirectories for tests and run them subsequently. It is amazing for debugging your code. A third case is for more advanced dynamic installs, in which we wish to compile our module differenly depending upon which machine it is installed on (e.g., different OS or architecture).

environment.yml

This one is mainly to set up an environment and install dependencies there, so that you main Python installation stays untouched and cannot be broken. The general structure looks as follows:

 1dependencies:
 2- python=3.8
 3- numpy
 4- pip
 5- pip:
 6  - recommonmark
 7  - readthedocs-sphinx-ext
 8  - sphinx
 9  - sphinx-rtd-theme
10  - pytest
11  - -e .
12- mock
13- pillow
14- sphinx
15- sphinx_rtd_theme
16name: htmapp

name specifies the name of the environment to be created, and pip defines extra packages that possibly aren’t available from the conda package manager. And additional setting that is often used, but we are skipping here are channels, which are online locations from where to grab the dependencies when they are installed with conda (a typical example is conda-forge). The file is pretty straightforward, so I’m gonna stop talking about it now.

To create an environment from the environment.yml file simply execute the following:

conda env create -f environment.yml