Sitemap
Data Science Collective

Advice, insights, and ideas from the Medium data science community

Guarantee a Locked & Reproducible Environment with Every Python Run

4 min readFeb 26, 2025

--

Zoom image will be displayed
Image by Author

For many, daily use of Python involves executing code in an environment of well-defined dependencies. If collaborating with others, dependencies can change without notice; even if dependencies do not change, it is easy to mistakenly install a package in the wrong environment. When local dependencies are misaligned, bad things can happen: behaviors may change, outputs might differ, or known malware might linger.

For compiled languages, alignment of dependencies in distributed binaries is required for creating reproducible builds. When running Python in local environments, is it possible to enforce something similar?

Leveraging Python’s support for such initialization intervention, the Fetter command-line tool can configure an environment’s python to either warn or exit before running with misaligned dependencies. For example, the following command will configure fetter to always validate the environment of the locally active python3 with “requirements.lock”:

$ pip install fetter
$ fetter -e python3 site-install --bound requirements.lock

Implemented in efficient multi-threaded Rust, performance overhead is insignificant. While project-specific Python “front-end” commands like uv run or poetry run offer related functionality, fetter is run on every execution of python itself, and can be used with any type of Python project.

Validating Environments

Before automating validation, a few examples of fetter usage are provided. To validate an environment, two things are required: (1) the Python executable for that environment and (2) a manifest of dependencies.

Environments are specified with a path to a Python executable, given with the--exe or -e parameter. If a virtual environment is active, specifying -e python3 is sufficient.

Dependencies are specified with a file path given to the --bound parameter. Most Python projects define direct (explicitly imported) dependencies in a “requirements.txt” or “pyproject.toml” file. As direct dependencies generally require many of their own “transitive” dependencies, a direct dependency listing is insufficient to fully define an environment. For this reason, tools such as pip-compile, pipenv, poetry and uv offer solutions for maintaining lock files: complete definitions of both direct and transitive dependencies.

Given values for -e and --bound, the fetter validate command can be used to validate an environment. To demonstrate, a fresh virtual environment can be created with Python’s built-in venv:

$ python3 -m venv ~/.env-test
$ source ~/.env-test/bin/activate
{.env-test} $

Given a “requirements.txt” file with the entries shown below, pip install -r can be used to install all packages:

{.env-test} $ cat requirements.txt
numpy==2.2.3
requests==2.32.2
fetter==1.7.0
{.env-test} $ pip install -r requirements.txt

While only three direct dependencies are defined, seven packages are installed: requests adds four transitive dependencies. Providing the virtual environment’s Python with -e python3 and the dependencies with--bound requirements.txt, fetter validate reports these installed but not defined dependencies as “Unrequired”:

{.env-test} % fetter -e python3 validate --bound requirements.txt
Package Dependency Explain Sites
certifi-2025.1.31 Unrequired ~/.env-test/lib/python3.13/site-packages
charset_normalizer-3.4.1 Unrequired ~/.env-test/lib/python3.13/site-packages
idna-3.10 Unrequired ~/.env-test/lib/python3.13/site-packages
urllib3-2.3.0 Unrequired ~/.env-test/lib/python3.13/site-packages

While unrequired packages can be accepted with the --superset flag, this disables enforcement of a truly locked environment. For example, using --superset, validation passes with no failure.

{.env-test} % fetter -e python3 validate --bound requirements.txt --superset

To comprehensively validate an environment, a lock file is required. The --bound argument will accept lock files created by pip-tools, pipenv, poetry, uv, and even the proposed PEP 751 format. A simple alternative is to use pip freeze:

{.env-test} % pip freeze > requirements.lock

Validating against “requirements.lock”, the --superset argument is no longer necessary. Now all dependencies, both direct and transitive, are bound within a fixed definition.

{.env-test} % fetter -e python3 validate --bound requirements.lock

Automating Validation

While environment validation with fetter can be done as needed or with every commit (via pre-commit), a truly reproducible, locked environment is only maintainable if validation is done before every Python execution. The fetter site-install command installs a special “.pth” file in the target environment’s “site-packages” directory that, at the start of Python site initialization, runs a pre-configured fetter validate command. By default, if validation fails, a warning is printed. As shown below, the same arguments provided to validate are provided to site-install:

{.env-test} $ fetter -e python3 site-install --bound requirements.lock

Now, on every execution of Python, the environment is validated. For example, after installing an unrequired package, running python3 displays a validation failure:

{.env-test} $ pip3 install typing-extensions
{.env-test} $ python3
Failed: fetter 1.7.0: validate --bound requirements.lock
Package Dependency Explain Sites
typing_extensions-4.12.2 Unrequired ~/.env-test/lib/python3.13/site-packages
Python 3.13.1 (main, Dec 3 2024, 17:59:52) [Clang 16.0.0 (clang-1600.0.26.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>

For even stronger control, if environment validation fails, fetter can exit the process with a supplied exit code:

{.env-test} $ fetter -e python3 site-install --bound requirements.lock exit --code 3
{.env-test} $ python3
Failed: fetter 1.7.0: validate --bound requirements.lock
Package Dependency Explain Sites
typing_extensions-4.12.2 Unrequired ~/.env-test/lib/python3.13/site-packages
{.env-test} $

To uninstall automatic environment validation, use fetter site-uninstall:

{.env-test} $ fetter -e python3 site-uninstall

Continuous Environment Locking

Unlike compiled languages, where dependencies are locked at build time and unchangeable at run time, local Python environments are “live”: Python will attempt to run regardless of if dependencies have drifted from defined expectations. With Fetter, dependency alignment can be ensured before every execution.

--

--

Data Science Collective
Data Science Collective

Published in Data Science Collective

Advice, insights, and ideas from the Medium data science community

Christopher Ariza
Christopher Ariza

Written by Christopher Ariza

Chief Technology Officer at Research Affiliates, a global leader in investment strategies and research.

Responses (2)