Pip Requirements Standardization
Pip Requirements Standardization
Proposer
Mark Beierl (Canonical)
Type
Feature
Target MDG/TF
common, devops, IM, LCM, MON, N2VC, NBI, osmclient, PLA, POL, RO
Description
The history of OSM saw us installing the software as Debian (.deb) packages onto an Ubuntu based operating system. Over time, this had changed to being docker image based, with python packages still being provided as .deb installable components.
This presents us with a new problem: the mixing and matching of Python dependencies across pip, the python package installer, and apt, the Ubuntu OS level package installer. Python modules can be shipped using either of the two package installers, but they are at odds with each other as pip will not alter or update anything that was installed by apt, as it is considered to be a system level package manager and should be the authoritative manager of system packages. Having said that, not all versions of Python modules that are in pip are available in .deb format. Also, the apt repositories choose their own cadence for when versions are released, and might not carry versions that we require.
This change proposes moving away from using .deb+apt to manage dependency versions, and to rely solely on Python being able to express its own dependencies based on the Python Package Index (PyPI). The scope of this change involves all modules that produce Python packages, and can be broken down into the following areas.
- Dependency expression (requirements.txt)
- Debian package expression (apt requires)
- Common format for building Python (tox.ini)
- Installation of packages (Dockerfiles)
Sample Chain of Modules
This example uses the POL
module as it is a fairly simple chain to follow:
-
POL
requiresosm_common
-
osm_common
does not require anything from OSM, but has some upstream dependencies
Starting with common
:
Define standard tox.ini
All projects will follow the same pattern:
- have the following environments:
flake8
,cover
,safety
,dist
- have different requirements files for final product and test modules
-
build
will produce the .deb package, which must include the project's requirements.txt file
Expression of Requirements
No requirements may be expressed in setup.py
. Anything that is in that file
will be used when creating the .deb
package and force the installation of the
.deb
version of the dependency. To avoid this, we must use requirements files
external to setup.py
until we move away from .deb
packaging entirely.
requirements.in
: this file is used to express the upstream dependencies from PyPi for
the module. It is permissible to express the modules in the following ways:
- Name only:
aiokafka
This means we are looking to use the latest version at this point in time - Range:
aiokafka<=0.6.0
This one means we are looking for an older version, and it cannot be more recent than 0.6.0
Version ranges in requirements.in is how we control the dependency matrix when a version in upstream is known to cause problems. This should be avoided by spending the time to adjust our code to work with the particular version, but is acceptable as a measure to proceed until the time is available to resolve the issue.
requirements-test.in
: this file is used to express additional modules that are used
when unit testing the software only. For example, mocking libraries are often needed
for a unit test, but are not part of the production code.
requirements-dist.in
: this is used to install packages that are required while
producing the final distributable package. For example, to create a .deb, we
need the stdeb
module. For uploading to PyPI, we would need twine
.
All .in
files must be compiled into their corresponding .txt
equivalents, using
pip-compile
. This takes the expressed modules, with any possible version constraints
and produces a final list of all the required modules at a specific version. For
example, requirements.in
containing aiokafka
would produce a requirements.txt
as
follows:
aiokafka==0.5.2
kafka-python==1.4.6 # via aiokafka
Note that while only aiokafka
was mentioned, the pip-compile
tool looked further
upstream and found that kafka-python
was also required, found the best version,
and noted why it was included in the requirements.txt
Promotion of Requirements to Downstream
Moving on to POL
:
Now that the .deb
package has been created without any dependencies, we face the
next problem: how does the downstream software (in our example POL
) know what
osm_common
requires in order to function? To support this, we will include common's
requirements.txt file in its .deb
package.
The Dockerfile (in devops, stage 3) for POL
would then look something like this:
FROM ubuntu 18.04
RUN apt --yes update
RUN apt --yes install python3 python3-pip
...
RUN apt --yes install python3-osm-common${COMMON_VERSION} \
python3-osm-policy-module${POL_VERSION}
RUN pip3 install -r /usr/lib/python3/dist-packages/osm_common/requirements.txt \
-r /usr/lib/python3/dist-packages/osm_pol/requirements.txt
The packages that used to be automatically installed by apt now must be explicitly
installed via the pip3
install command. This is the desired outcome: we now have
once place to express requirements, which is in the source code of the module itself,
and complete control over the final list of dependencies that gets installed.