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'": Installs some_package only on Linux.
    • "another_package; sys_platform == 'darwin'": Installs another_package only on macOS.
    • "windows_only_package; sys_platform == 'win32'": Installs windows_only_package only on Windows.
    • "unix_package; sys_platform != 'win32'": Installs unix_package on any non-Windows system.
  • Python Version Specific:

    • "new_feature_lib; python_version >= '3.8'": Installs new_feature_lib if Python is 3.8 or newer.
    • "legacy_support; python_version < '3.7'": Installs legacy_support if Python is older than 3.7.
    • "specific_version; python_version == '3.9.5'": Installs specific_version only for Python 3.9.5.
    • "version_range; python_version in ('3.7', '3.8', '3.9')": Installs version_range for Python versions 3.7, 3.8, or 3.9.
  • Interpreter Implementation Specific:

    • "cpython_optimized; implementation_name == 'cpython'": Installs cpython_optimized if using the standard CPython interpreter.
    • "pypy_compat; implementation_name == 'pypy'": Installs pypy_compat if 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.

Want structured learning?

Take the full Pip course →