Setting up and testing the package

This recitation was written by Patrick Almhjell.


You may have noticed that we did not add any tests with our code. We should definitely have tests for our code.

The easiest way to do this, however, first requires setting up our package to be available to the Python interpreter. So we’ll need a setup.py file.

Creating a simple setup.py file

As you have seen, there are many, many ways to organize your package. Creating a setup.py file is no different. However, here is a pretty basic and effective file:

from setuptools import setup, find_packages


with open("README.md", "r") as f:
    long_description = f.read()


with open("tinypkg/__init__.py", "r") as f:
    init = f.readlines()

for line in init:
    if '__author__' in line:
        __author__ = line.split("'")[-2]
    if '__email__' in line:
        __email__ = line.split("'")[-2]
    if '__version__' in line:
        __version__ = line.split("'")[-2]


setup(
    name='tinypkg',
    version=__version__,
    author=__author__,
    author_email=__email__,
    description='A tiny, simple package for Recitation 3 of bebi103a (2019).',
    long_description=long_description,
    long_description_content_type='ext/markdown',
    packages=find_packages(),
    classifiers=(
        "Programming Language :: Python :: 3",
        "Operating System :: OS Independent",
    ),
)

One thing you’ll find to be different from Lesson 2 is that I parse some of the important info directly from the __init__.py file, allowing it to stay in a single place (the __init__.py file) and only changed there.

Once you have this in your package, run pip install -e . if you’re in the root directory.

Now, we can import it:

[1]:
import tinypkg

It works!

Note that the -e flag here means --editable. This form of installation creates a symbolic link to your package, meaning that it’s more dynamic than a normal pip install of a package from PyPi. As you make changes to your package, they are automatically added and there’s no need to reinstall the package.

For most of your own packages, this is ideal. As you make changes in your local directory, you can test them. Once they are in good shape, you can push them to GitHub and others can pull and use those changes (assuming they have also installed with the --editable version).

We’ll get more into this in the next section.

Testing: with modules and pytest

Now, back to testing. The first test: let’s see if it can make the same type of chart as before.

[2]:
import numpy as np
import pandas as pd

# Make a fake Dataset
np.random.seed(8675309)

# Michaelis-Menten equation, because I'm a biochemist
def noisy_mm(concs, kcat, KM=10):
    noise = np.random.normal(1, 0.1, len(concs))
    return kcat*(concs / (concs+KM))*noise

# Make some concentrations in logspace
concs = np.logspace(1e-10, 2, 8)

# Set up the fake MM data
df = pd.concat([pd.DataFrame({
    'Sample' : 'Enzyme '+str(i+1),
    'Substrate Concentration' : np.concatenate([concs for _ in range(8)]),
    'Rate' : np.concatenate([noisy_mm(concs, kcat=1+i**2) for _ in range(8)]),
    'Substrate' : 'Indole',
}) for i in range(3)])

# Plot it!
tinypkg.plot_timecourse(df,
                        'Substrate Concentration',
                        'Rate',
                        condition='Sample',
                        legend='top_left')
[2]:

Looks good!

Now that we know it’s working and accessible to the Python interpreter, we can make some testing modules.

In a new directory called test/ I’ve added the module test_general_utils.py with the following import:

import numpy as np
import pandas as pd

import tinypkg.general_utils as utils

Followed by the relevant testing functions, calling utils.function() for each function I want to test. Feel free to check them out in the repo.

Finally, we can use pytest to verify that everything is working as expected. (Ignore the warnings.)

[3]:
!pytest -v ~/git/tinypkg/
============================= test session starts ==============================
platform darwin -- Python 3.7.4, pytest-5.2.1, py-1.8.0, pluggy-0.13.0 -- /Users/bois/anaconda3/bin/python
cachedir: .pytest_cache
rootdir: /Users/bois/Dropbox/git/external/tinypkg
plugins: arraydiff-0.3, remotedata-0.3.2, doctestplus-0.4.0, openfiles-0.4.0
collected 5 items                                                              

../../../../../../external/tinypkg/tests/test_general_utils.py::test_col_exists PASSED [ 20%]
../../../../../../external/tinypkg/tests/test_general_utils.py::test_col_does_not_exist PASSED [ 40%]
../../../../../../external/tinypkg/tests/test_general_utils.py::test_no_reps PASSED [ 60%]
../../../../../../external/tinypkg/tests/test_general_utils.py::test_reps PASSED [ 80%]
../../../../../../external/tinypkg/tests/test_general_utils.py::test_replicate_mean PASSED [100%]

=============================== warnings summary ===============================
/Users/bois/anaconda3/lib/python3.7/site-packages/holoviews/core/data/grid.py:5
  /Users/bois/anaconda3/lib/python3.7/site-packages/holoviews/core/data/grid.py:5: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
    from collections import OrderedDict, defaultdict, Iterable

-- Docs: https://docs.pytest.org/en/latest/warnings.html
======================== 5 passed, 1 warnings in 1.20s =========================

Computing environment

[4]:
%load_ext watermark

%watermark -v -p jupyterlab,numpy,pandas,holoviews,bokeh,pytest
CPython 3.7.4
IPython 7.8.0

jupyterlab 1.1.4
numpy 1.17.2
pandas 0.24.2
holoviews 1.12.6
bokeh 1.3.4
pytest 5.2.1