Python subprocess on Windows, Mac and Linux

Paths to executables for Python subprocess on Windows and other operating systems should be handled robustly to avoid unexpected errors on end user systems that may not occur on the developer’s laptop or CI system. For best robustness, programs making important use of non-Python executables should use Python ≥ 3.7.

NOTE: relative paths (names with slashes and/or “..") are not allowed–discussion. That means “build-on-run” or “build-at-setup/install” executables must live at the same directory level as the resource specified.

Boilerplate examples

This example uses a black-box executable “amender.bin” that we assume has been already built and exists in the package directory.

#!/usr/bin/env python3
from pathlib import Path
import os
import subprocess
import importlib.resources

def my_func():

    name = "amender.bin"
    if os.name == "nt":
        name += ".exe"
    if not importlib.resources.is_resource(__package__, name:
        raise ImportError(f"{name} not found, please see README")

	with importlib.resources.path(__package__, name) as exe:
        cmd = [str(exe), myparam1, myparam2]
        subprocess.check_call(cmd)

Older Python < 3.7

Older Python versions < 3.7 have downsides for this application including:

Notes

Generally using __file__ is not robust for packages. Consider importlib.resources or using conftest.py to generate test files.