Skip to content

zbowling/beartype-pyinstaller-repro

Repository files navigation

Beartype PyInstaller Reproduction & Fix

This project demonstrates and fixes a module import error when using fastmcp (which depends on py-key-value-aio that uses beartype) with PyInstaller.

Issue

When packaging a FastMCP server with PyInstaller, the bundled executable fails at runtime with ModuleNotFoundError for modules imported after beartype is initialized.

Root Cause: When py-key-value-aio imports, it calls beartype_this_package() which installs beartype's import hook via add_beartype_pathhook(). This hook:

  1. Prepends a custom FileFinder to sys.path_hooks
  2. Clears sys.path_importer_cache
  3. Uses SourceFileLoader which expects .py source files

In PyInstaller's frozen environment, there are no source files - only compiled bytecode in a PYZ archive. When beartype's hook takes precedence over PyInstaller's PyiFrozenFinder, all subsequent imports fail.

✅ The Fix

A PyInstaller runtime hook patches beartype's add_beartype_pathhook to skip installation in frozen environments. See hooks/pyi_rth_beartype.py.

The key insight: we must patch two locations because clawpkgmain.py does from ... import add_beartype_pathhook, creating a local binding that survives module-level patches.

Project Structure

beartype_pyinstaller_repro/
├── src/
│   └── beartype_pyinstaller_repro/
│       ├── __init__.py
│       └── server.py          # FastMCP server with Fibonacci tool
├── tests/
│   └── test_integration.py    # Integration tests
├── server.py                  # Entry point for PyInstaller
├── server.spec               # PyInstaller configuration
├── pyproject.toml            # Project configuration
└── Makefile                  # Build commands

Setup

Prerequisites

  • Python 3.12+
  • uv package manager

Installation

# Install dependencies
uv sync

Building

Build the PyInstaller executable:

make build

Or manually:

uv run pyinstaller server.spec

The executable will be created at dist/server (or dist/server.exe on Windows).

Running Tests

Direct Import Test (Baseline)

This test verifies that the server works correctly when run directly:

uv run pytest tests/test_integration.py::test_direct_import -v

This should pass, demonstrating that the code works fine outside of PyInstaller.

PyInstaller Executable Test

This test runs the bundled executable and checks for import errors:

# First build the executable
make build

# Then run the integration test
uv run pytest tests/test_integration.py::test_pyinstaller_executable_imports -v

This test is expected to fail (or detect the import error), demonstrating the bug.

Run All Tests

uv run pytest tests/ -v

Reproducing the Issue

  1. Install dependencies:

    uv sync
  2. Build the executable:

    make build
  3. Run the executable directly:

    ./dist/server

    You should see a ModuleNotFoundError or ImportError. The error may mention diskcache or other modules, but the root cause is beartype initialization breaking subsequent imports in the PyInstaller bundle.

  4. Compare with direct execution:

    uv run python server.py

    This should work fine, demonstrating that the issue is specific to the PyInstaller bundle.

CI/CD

GitHub Actions CI is configured to:

  • Run ruff for code quality checks
  • Run pytest tests
  • Build the PyInstaller executable
  • Run integration tests

See .github/workflows/ci.yml for details.

Dependencies

  • fastmcp - FastMCP framework for building MCP servers
  • pyinstaller - Python application bundler
  • pytest - Testing framework
  • ruff - Fast Python linter

The issue stems from fastmcp's dependency on py-key-value-aio, which initializes beartype when imported. In PyInstaller bundles, after beartype is initialized, the Python import system breaks and all subsequent imports fail with ModuleNotFoundError, even for modules that are actually included in the bundle.

Expected Behavior (with fix)

  • ✅ Direct execution: Works correctly
  • ✅ Direct import test: Passes
  • ✅ PyInstaller bundle: Works with runtime hook fix

Using the Fix in Your Project

To use this fix in your own PyInstaller project:

  1. Copy the runtime hook (hooks/pyi_rth_beartype.py) to your project

  2. Add it to your spec file:

    runtime_hooks=['path/to/pyi_rth_beartype.py'],
  3. Or via command line:

    pyinstaller --runtime-hook=path/to/pyi_rth_beartype.py your_app.py

Related Issues

Technical Details

See docs/INVESTIGATION.md for a detailed analysis of the root cause and various fix approaches considered.

About

Reproduction case for beartype import errors with PyInstaller and fastmcp

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published