All.dealing with missing dependencies.how to guide
Dealing with missing dependencies in Docker images#
- Sometimes our scripts need to use dependencies that are not available in our primary Docker image
- If the package is necessary for the entire project and not just for a specific script, we can request to have it installed in the Docker image and release a new version of the image
-
However, sometimes the packages are only needed for a specific script or part of a project. Thus, we don't want to install them in the Docker image as it would bloat the image
-
Due to missing dependencies, the build process will fail when tests are discovered or when the module is imported and used in another module
- The following are some common workarounds to deal with the missing dependencies without breaking the build
Quick hacks#
-
In test files,
- We can use
pytest.importorskipto skip the tests if the package is not availablepython pytest.importorskip("somepackage")
- We can use
-
In non-test files,
- We do not need to do anything besides avoiding importing the module
- If the file is discovered by
pytestand the image doesn't have the package installed, it will be skipped with thepytest.importorskipin the test file
-
Why not installing the package on the fly within the script?
- While it is possible, it is NOT a good practice
- This approach is NOT great because every time somebody imports that module
(even pytest during test discovery), the package gets installed
- Bad
python subprocess.call(["sudo", "/venv/bin/pip", "install", "somepackage"])
- Bad
- An exception is in Jupyter Notebooks, where it's acceptable to install packages on the fly for prototyping, experimenting, or running analyses
- However, we should "comment out" those lines afterwards, since Jupyter Notebooks are often converted to Python scripts (through jupytext), and we don't want these installation commands running automatically
- Example (in a Jupyter Notebook cell)
!sudo sudo /venv/bin/pip install --quiet somepackage)"
-
If we want to implement different variations of the code depending on whether the package is available or not, we can use the following approaches
python _HAS_MOTO = henv.has_module("moto") if _HAS_MOTO: import moto # Implementation that uses moto. else: # Implementation that doesn't use moto.
Delay the evaluation#
-
Sometimes it's sufficient to delay when a module is imported to prevent errors during script loading or testing
-
Move the
importstatement inside the function or class definition so that the module is only required when that part of the code is executedpython def my_function(): import somepackagepython class MyClass: import somepackage ...
Type annotations#
-
If the missing dependency is only needed for type annotations, we can postpone the evaluation of the annotations to avoid errors during module loading or pytest discovery
-
One option is to use string literals for type hints
python def my_function(param: "SomeClass") -> "SomeClass": ... -
Another option is to use the
__future__module to enable postponed evaluation of type annotationspython from __future__ import annotations
Long term solution#
- While quick hacks are useful temporary workarounds, they are not a long term solution
Add the dependency to the Docker image#
- Obviously, if the package is needed for the entire project, we should add it to the Docker image and release a new version of the image
Create "dockerized" executable#
- We can create a "dockerized" executable that can be run in a Docker container with all the dependencies pre-installed
- A "dockerized" executable is easier to set up compared to a runnable directory
- It is suitable for utility scripts that have a single and well defined purpose and do not have a large number of dependencies
- The image is built on the fly
- Some examples of dockerized executables:
dockerized_prettierdockerized_mermaiddockerized_extract_notebook_images
Create "runnable" directory#
- We can create a "runnable" directory that contains all the code and a
devopsdirectory so it can build its own container with all the dependencies needed to run and be tested - A "runnable" dir is more complex to set up than the previous approach
- It is good for larger projects or parts of a project with many interacting components that have many dependencies
- The image can be built and stored in the container registry
- Some examples of runnable directories:
infraoptimizersports_analyticstutorial_langchain