Python’s pip can install different dependencies based on the environment the code is running in.
Let’s say you have a project that needs requests for HTTP calls and psutil for system process information. However, psutil has platform-specific installation steps and might not be needed on every system. You can tell pip to install psutil only on Windows.
# setup.py
from setuptools import setup, find_packages
setup(
name="my_conditional_app",
version="0.1.0",
packages=find_packages(),
install_requires=[
"requests",
"psutil; sys_platform == 'win32'", # Install psutil only on Windows
],
)
When you install this package:
pip install .
On a Windows machine, pip will install both requests and psutil. On a Linux or macOS machine, it will only install requests. This is controlled by environment markers.
Environment markers are expressions in Python that evaluate to True or False at install time, determining whether a dependency should be included. They are typically used in install_requires within setup.py or pyproject.toml, or directly in requirements.txt files.
The most common markers relate to the operating system (sys_platform), Python version (python_version), and interpreter implementation (implementation_name).
Here are some examples of environment markers in action:
-
Platform-specific:
"some_package; sys_platform == 'linux'": Installssome_packageonly on Linux."another_package; sys_platform == 'darwin'": Installsanother_packageonly on macOS."windows_only_package; sys_platform == 'win32'": Installswindows_only_packageonly on Windows."unix_package; sys_platform != 'win32'": Installsunix_packageon any non-Windows system.
-
Python Version Specific:
"new_feature_lib; python_version >= '3.8'": Installsnew_feature_libif Python is 3.8 or newer."legacy_support; python_version < '3.7'": Installslegacy_supportif Python is older than 3.7."specific_version; python_version == '3.9.5'": Installsspecific_versiononly for Python 3.9.5."version_range; python_version in ('3.7', '3.8', '3.9')": Installsversion_rangefor Python versions 3.7, 3.8, or 3.9.
-
Interpreter Implementation Specific:
"cpython_optimized; implementation_name == 'cpython'": Installscpython_optimizedif using the standard CPython interpreter."pypy_compat; implementation_name == 'pypy'": Installspypy_compatif using the PyPy interpreter.
You can also combine markers using and and or:
# setup.py
setup(
name="complex_app",
version="0.1.0",
packages=find_packages(),
install_requires=[
"requests",
# Install a specific version of 'db_driver' only on Linux for Python 3.7
"db_driver==1.2.3; sys_platform == 'linux' and python_version == '3.7'",
# Install a different driver on Windows for Python 3.8+
"win_db_driver; sys_platform == 'win32' and python_version >= '3.8'",
],
)
These markers are parsed and evaluated by pip (or other build tools like poetry or flit) during the installation process. The values for these markers are derived from the environment where pip is run, typically from sys.platform and sys.version_info.
When pip encounters a dependency with a marker, it evaluates the marker expression. If the expression evaluates to True, the dependency is considered for installation. If it evaluates to False, the dependency is skipped entirely for that environment. This is crucial for managing dependencies that are either incompatible with certain platforms or only offer benefits on specific ones, helping to keep your installed packages lean and relevant.
A subtle but important point is that the marker expressions are evaluated before the dependency is installed. This means you cannot use environment markers to conditionally install a package based on whether another package is already installed. The decision to install is made purely based on the environment’s characteristics, not its current package state. The marker expression is essentially a Python expression that’s evaluated within a specific context provided by pip. This context includes predefined variables like sys_platform, python_version, platform_machine, platform_python_implementation, etc., which are populated with values from the current system.
The next step in dependency management is understanding how to define custom markers for more complex scenarios.