create_executable.py
Overview
`create_executable.py` is a utility script designed to automate the creation of a standalone executable that embeds a pytest test runner. It leverages **PyInstaller** to package a Python script (`runtests_script.py`) into a single executable file. This executable, when run, will execute tests using pytest.
The script dynamically determines the necessary hidden imports for pytest to work correctly in the frozen executable and passes these to PyInstaller to ensure all required modules are included. This approach ensures that the resulting executable can run tests without missing dependencies, which is a common issue when freezing Python applications that rely on dynamic imports.
Detailed Explanation
Script Behavior
This file is intended to be run as a standalone script (`python create_executable.py`). It does not define any classes or functions but executes logic within the `if __name__ == "__main__":` block.
Main Process
if __name__ == "__main__":
import subprocess
import pytest
hidden = []
for x in pytest.freeze_includes():
hidden.extend(["--hidden-import", x])
hidden.extend(["--hidden-import", "distutils"])
args = ["pyinstaller", "--noconfirm", *hidden, "runtests_script.py"]
subprocess.check_call(" ".join(args), shell=True)
Imports:
subprocess: Used to spawn a new process to run the PyInstaller command.pytest: Imported to accesspytest.freeze_includes()which provides a list of modules pytest requires to be explicitly included when freezing.
Building
hiddenimports list:pytest.freeze_includes()returns a list of module names that pytest uses dynamically and thus need to be explicitly included when freezing.For each module name returned, the script adds
--hidden-import <module>to thehiddenlist.Additionally, the
"distutils"module is manually added as a hidden import, which is often required to avoid runtime import errors in frozen environments.
Forming PyInstaller command:
The command list begins with
"pyinstaller".--noconfirmflag is added to avoid interactive prompts during the build.The accumulated hidden imports are expanded into the command.
Finally, the target script
"runtests_script.py"is specified as the entry point for the executable.
Running PyInstaller:
The command list is joined into a single shell command string.
subprocess.check_call()executes this command synchronously, raising an error if PyInstaller fails.
Parameters and Return Values
This script does not define functions or classes and therefore does not have parameters or return values. Instead, its behavior is driven entirely by the build process it triggers.
Usage Example
To create the pytest runner executable, run this script from the command line:
python create_executable.py
This command will invoke PyInstaller with the appropriate hidden imports to build a standalone executable from `runtests_script.py`.
Important Implementation Details
Dynamic Hidden Imports Handling:
PyInstaller requires explicit declaration of modules that are imported dynamically at runtime. Pytest, in particular, uses dynamic imports for plugins and internal components. The methodpytest.freeze_includes()provides the list of such modules, enabling this script to automatically include them without manual maintenance.Manual Inclusion of
distutils:
The script explicitly addsdistutilsas a hidden import since it is a standard library module often needed by PyInstaller but sometimes omitted from the frozen package, leading to runtime errors.Use of
subprocesswithshell=True:
The script constructs a shell command string and executes it withshell=True. This simplifies command construction but requires care to avoid shell injection if parameters are dynamic (which they are not in this controlled scenario).
Interaction with Other Parts of the System
runtests_script.py:
This script is the target for PyInstaller. It presumably contains the pytest invocation to run tests. The executable created by this process will embed pytest and run the test suite defined inruntests_script.py.Pytest:
The script uses pytest internally to determine the modules that must be included in the packaged executable.PyInstaller:
The script automates PyInstaller command-line usage to create the standalone executable.
Visual Diagram
flowchart TD
A[Start: Run create_executable.py] --> B[Import subprocess and pytest]
B --> C[Call pytest.freeze_includes()]
C --> D[Build hidden import arguments list]
D --> E[Add --hidden-import distutils]
E --> F[Form PyInstaller command]
F --> G[Execute PyInstaller command via subprocess]
G --> H{Success?}
H -- Yes --> I[Executable created successfully]
H -- No --> J[Raise error and stop]
Summary
`create_executable.py` is a straightforward but crucial build utility that automates the packaging of a pytest-based test runner executable using PyInstaller. It dynamically resolves hidden imports necessary for pytest to function in frozen form, reducing manual configuration errors. This enables developers to distribute or run tests in environments where Python and pytest may not be installed, improving portability and ease of test execution.