Pip Requirements Standardization
[osm/Features.git] / Release10 / pip-requirements-standardization.md
1 # Pip Requirements Standardization #
2
3 ## Proposer ##
4 Mark Beierl (Canonical)
5
6 ## Type ##
7 **Feature**
8
9 ## Target MDG/TF ##
10 common, devops, IM, LCM, MON, N2VC, NBI, osmclient, PLA, POL, RO
11
12 ## Description ##
13 The history of OSM saw us installing the software as Debian (.deb) packages onto an
14 Ubuntu based operating system.  Over time, this had changed to being docker image
15 based, with python packages still being provided as .deb installable components.
16
17 This presents us with a new problem: the mixing and matching of Python dependencies
18 across pip, the python package installer, and apt, the Ubuntu OS level package
19 installer.  Python modules can be shipped using either of the two package installers,
20 but they are at odds with each other as pip will not alter or update anything that was
21 installed by apt, as it is considered to be a system level package manager and should
22 be the authoritative manager of system packages.  Having said that, not all versions
23 of Python modules that are in pip are available in .deb format.  Also, the apt
24 repositories choose their own cadence for when versions are released, and might
25 not carry versions that we require.
26
27 This change proposes moving away from using .deb+apt to manage dependency versions,
28 and to rely solely on Python being able to express its own dependencies based on the
29 Python Package Index (PyPI).  The scope of this change involves all modules that
30 produce Python packages, and can be broken down into the following areas.
31
32 * Dependency expression (requirements.txt)
33 * Debian package expression (apt requires)
34 * Common format for building Python (tox.ini)
35 * Installation of packages (Dockerfiles)
36
37 ### Sample Chain of Modules
38
39 This example uses the `POL` module as it is a fairly simple chain to follow:
40
41 * `POL` requires `osm_common`
42 * `osm_common` does not require anything from OSM, but has some upstream dependencies
43
44 Starting with `common`:
45
46 ### Define standard tox.ini
47
48 All projects will follow the same pattern:
49
50 * have the following environments: `flake8`, `cover`, `safety`, `dist`
51 * have different requirements files for final product and test modules
52 * `build` will produce the .deb package, which must include the project's
53   requirements.txt file
54
55 ### Expression of Requirements
56
57 No requirements may be expressed in `setup.py`.  Anything that is in that file
58 will be used when creating the `.deb` package and force the installation of the
59 `.deb` version of the dependency.  To avoid this, we must use requirements files
60 external to `setup.py` until we move away from `.deb` packaging entirely.
61
62 `requirements.in`: this file is used to express the upstream dependencies from PyPi for
63 the module.  It is permissible to express the modules in the following ways:
64
65 * Name only: `aiokafka`  This means we are looking to use the latest version at this
66   point in time
67 * Range: `aiokafka<=0.6.0`  This one means we are looking for an older version, and it
68   cannot be more recent than 0.6.0
69
70 Version ranges in requirements.in is how we control the dependency matrix when a
71 version in upstream is known to cause problems.  This should be avoided by spending
72 the time to adjust our code to work with the particular version, but is acceptable
73 as a measure to proceed until the time is available to resolve the issue.
74
75 `requirements-test.in`: this file is used to express additional modules that are used
76 when unit testing the software only.  For example, mocking libraries are often needed
77 for a unit test, but are not part of the production code.
78
79 `requirements-dist.in`: this is used to install packages that are required while
80 producing the final distributable package.  For example, to create a .deb, we
81 need the `stdeb` module.  For uploading to PyPI, we would need `twine`.
82
83 All `.in` files must be compiled into their corresponding `.txt` equivalents, using
84 `pip-compile`.  This takes the expressed modules, with any possible version constraints
85 and produces a final list of all the required modules at a specific version.  For
86 example, `requirements.in` containing `aiokafka` would produce a `requirements.txt` as
87 follows:
88
89 ```
90 aiokafka==0.5.2
91 kafka-python==1.4.6 # via aiokafka
92 ```
93
94 Note that while only `aiokafka` was mentioned, the `pip-compile` tool looked further
95 upstream and found that `kafka-python` was also required, found the best version,
96 and noted why it was included in the `requirements.txt`
97
98 ### Promotion of Requirements to Downstream
99
100 Moving on to `POL`:
101
102 Now that the `.deb` package has been created without any dependencies, we face the
103 next problem: how does the downstream software (in our example `POL`) know what
104 `osm_common` requires in order to function?  To support this, we will include common's
105 requirements.txt file in its `.deb` package.
106
107 The Dockerfile (in devops, stage 3) for `POL` would then look something like this:
108
109 ```
110 FROM ubuntu 18.04
111
112 RUN apt --yes update
113 RUN apt --yes install python3 python3-pip
114
115 ...
116 RUN apt --yes install python3-osm-common${COMMON_VERSION} \
117                       python3-osm-policy-module${POL_VERSION}
118
119 RUN pip3 install -r /usr/lib/python3/dist-packages/osm_common/requirements.txt \
120                  -r /usr/lib/python3/dist-packages/osm_pol/requirements.txt
121 ```
122
123 The packages that used to be automatically installed by apt now must be explicitly
124 installed via the `pip3` install command.  This is the desired outcome: we now have
125 once place to express requirements, which is in the source code of the module itself,
126 and complete control over the final list of dependencies that gets installed.