One of the approaches I started exploring some time ago for distributing required Python packages to support particular courses (modules) was to create a dummy Python package containing just a list of required packages and push it to PyPi. Installing the single dummy package then installs all the actually required packages. (The dummy package could also include environmental checks, perhaps even some simple module documentation etc. But for now, I’ve been keeping it simple.
The setup I’m using bundles the requirements into a separate file, rather than expicitly listing them. The generic package source tree looks something like this:
.
├── LICENSE
├── MANIFEST.in
├── README.md
├── requirements.txt
├── ou-MODULECODE-py
│ ├── __init__.py
├── setup.py
The MANIFEST.in
file is required and ensures that the requirements.py
file is bundled into the package that is uploaded to PyPi. If the MANIFEST.in
file is omitted, the package install will work from Github (because the requirements.txt
will form part of the cloned download, but not from PyPi (becuase the requirements file won’t be uploaded to PyPi). (A safer route to install requirements would be to list them explicitly within the setup.py
file. And perhaps not trap for the missing requirements file… Or at least, raise a warning if an anctipated requirements.txt
file is not available.)
The __init__.py
file can be empty, but I’ve started exploring simple test scripts within it:
def about():
"""Provide a simple description of the package."""
msg = f"""
# ===== ou_tm351_py, version: {__version__} =====
The `ou_tm351_py` package is an "empty" package that installs Python package requirements
for the Open University module "Data management and analysis (TM351)" [http://www.open.ac.uk/courses/modules/tm351].
You can test that key required packages are installed by running the command: ou_tm351_py.test_install()
"""
print(msg)
def test_install(key_packages=None):
"""Test the install of key packages."""
import importlib
if key_packages is None:
key_packages = [
"pandas",
"schemadisplay_magic"
]
for p in key_packages:
try:
importlib.import_module(p.strip())
print(f"{p} loaded correctly")
except:
print(f"{p} appears to be missing")
A more elaborate test file could attempt to check that all the requirements are installed. We could also bundle simple command line commands, or even a simple webserver serving module help files / documentation.
The setup.py
file is largely boilerplate:
from setuptools import setup
from os import path
def get_requirements(fn='requirements.txt'):
"""Get requirements."""
if path.exists(fn):
with open(fn, 'r') as f:
requirements = [r.split()[0].strip() for r in f.read().splitlines() if r and not r.startswith('#')]
else:
requirements = []
return requirements
requirements = get_requirements(nogit=False)
print(f'Requirements: {requirements}')
# Any extra requirements will need bundling via MANIFEST.in
extras = {
'production': get_requirements('requirements_production.txt'),
'AL': get_requirements('requirements_AL.txt')
}
setup(
# Meta
author='Tony Hirst',
author_email='tony.hirst@open.ac.uk',
description='Python package installation for OU module MODULECODE',
name='ou-MODULECODE-py',
license='MIT',
url='https://github.com/innovationOUtside/innovationOUtside/ou-MODULECODE-py',
version='0.0.1',
packages=['ou_MODUECODE_py'],
# Dependencies
install_requires=requirements,
#setup_requires=[],
extras_require=extras,
# Packaging
#entry_points="",
include_package_data=True,
zip_safe=False,
# Classifiers
classifiers=[
'Development Status :: 3 - Alpha',
'Environment :: Web Environment',
'Intended Audience :: Education',
'License :: Free For Educational Use',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Topic :: Education',
'Topic :: Scientific/Engineering :: Visualization'
],
)
Installing the package is then a command along the lines of pip install ou-MODULECODE-py
.