Skip to content
Python Basics: Precompile Source Trees with compileall

Python Basics: Precompile Source Trees with compileall

October 27, 2026

A packaging check can catch syntax errors before a program is started. compileall compiles Python source into bytecode caches without importing the module. This fixture verifies both that a cache appears and that module-level code was not executed.

Prepare a scratch directory

Tested on Linux with CPython 3.12.14. You need the named runtime, Bash, a text editor, and the standard mktemp, rm, and rmdir utilities. Run Python without -O so the assertions remain enabled. Any generated fixture files live inside an automatically cleaned temporary directory.

Keep using the same terminal. Stop if setup fails, before saving the source:

start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1

Run the example

Save this as demonstrate-compileall.py:

import compileall
import contextlib
import io
from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as directory:
    root = Path(directory)
    good = root / "good.py"
    good.write_text('raise RuntimeError("must not execute")\n', encoding="utf-8")
    assert compileall.compile_file(str(good), quiet=2, force=True)
    caches = list((root / "__pycache__").glob("good.*.pyc"))
    assert len(caches) == 1
    print("Valid source: compiled without execution")
    print("Bytecode cache count:", len(caches))

    bad = root / "broken.py"
    bad.write_text("def broken(:\n", encoding="utf-8")
    report = io.StringIO()
    with contextlib.redirect_stdout(report):
        success = compileall.compile_dir(str(root), quiet=1, force=True)
    assert success is False
    assert "SyntaxError" in report.getvalue()
    assert not list((root / "__pycache__").glob("broken.*.pyc"))
    print("Directory with invalid syntax:", success)
    print("Diagnostic includes SyntaxError:", "SyntaxError" in report.getvalue())

Run it:

python3 -B demonstrate-compileall.py

Observed output:

Valid source: compiled without execution
Bytecode cache count: 1
Directory with invalid syntax: False
Diagnostic includes SyntaxError: True

Read the result

The valid file would raise immediately if imported, but compilation only translates its source. The cache filename includes an interpreter tag, so the program checks the cache count instead of hard-coding a version-specific filename. The second step adds invalid syntax and verifies that the directory result is false. Capturing the diagnostic keeps temporary paths out of the displayed output while still asserting the error type.

Apply it carefully

Successful compilation does not prove imports will resolve, types are correct, or execution will succeed. Keep runtime tests as a separate gate. quiet=1 suppresses routine progress but retains errors; quiet=2 suppresses both. When used from Bash, python3 -m compileall -q path/to/package provides a command exit status suitable for CI. Caches are interpreter-specific build products, not portable executables or protected source code.

The official reference describes the API and its options. The checks above cover the specific fixture, not every possible input or environment.

Clean up

Remove the source and then the empty practice directory:

rm -- demonstrate-compileall.py
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dir

If the directory is not empty, inspect the remaining files rather than deleting unknown contents.

Acknowledgements

This post was created with assistance from AI.

Last updated on