Maturin Import Hook
The maturin-import-hook library provides a Python import hook for projects built with `maturin`, allowing Python code to dynamically load and use Rust modules without needing an `editable` install or pre-building. It simplifies development workflows for mixed Python/Rust projects by building the Rust extension on demand. The current version is 0.3.0, and it generally follows a bug-fix driven release cadence with support for new Python versions.
Common errors
-
ImportError: No module named 'your_rust_module'
cause The `maturin_import_hook.install()` function was not called, or the `pyproject.toml` and Rust project structure are not correctly set up or discoverable by the hook.fixEnsure `maturin_import_hook.install()` is called early in your application's lifecycle, and verify that a valid `pyproject.toml` file is in the current working directory or a parent relative to where the script is run. -
ModuleNotFoundError: No module named 'maturin'
cause The `maturin` CLI tool, which `maturin-import-hook` relies on for building and discovery, is not installed in the active Python environment.fixInstall `maturin` using `pip install maturin` in the same Python environment. -
(Rust compiler errors, e.g., 'error[E0425]: cannot find function `some_rust_function` in crate `some_crate`' during import)
cause The underlying Rust project has compilation issues (e.g., syntax errors, missing dependencies in `Cargo.toml`, incorrect PyO3 bindings). These errors occur when the hook attempts to build the Rust extension.fixReview the detailed error messages from `cargo build` (which `maturin` wraps) to diagnose and fix issues in your `Cargo.toml` or Rust source code. You can often reproduce these by running `maturin build` manually in your Rust project directory to get clearer output.
Warnings
- gotcha The `maturin` CLI tool must be installed in the active Python environment for the import hook to function. It is a runtime dependency used by the hook to locate and potentially build Rust extensions.
- gotcha The import hook relies on finding a `pyproject.toml` file in the current working directory or a parent directory to identify the Rust project root. If the file is not discoverable, the hook will fail to find or build your Rust module.
- gotcha The first time a Rust module is imported via the hook (or if the built artifact is missing/outdated), it will trigger a full `maturin build` process. This can introduce significant latency to the initial import, especially for large Rust projects.
- gotcha The import hook modifies `sys.meta_path`. If your application uses other import hooks or heavily manipulates `sys.meta_path`, there's a potential for conflicts or unexpected behavior.
Install
-
pip install maturin-import-hook -
pip install maturin
Imports
- install
import maturin_import_hook; maturin_import_hook.install()
Quickstart
import maturin_import_hook
import sys
import os
import shutil
# This hook allows Python to find and load Rust modules
# defined in a maturin project within the current directory
# or a parent directory.
maturin_import_hook.install()
# --- Setup a dummy Rust project for demonstration ---
# In a real project, this setup would already exist.
project_name = "my_rust_module"
project_dir = "temp_rust_project"
module_path = os.path.join(project_dir, "src")
# Clean up any previous run's artifacts
if os.path.exists(project_dir):
shutil.rmtree(project_dir)
os.makedirs(module_path, exist_ok=True)
# Create pyproject.toml
with open(os.path.join(project_dir, "pyproject.toml"), "w") as f:
f.write(f"""
[project]
name = "{project_name}"
version = "0.1.0"
[tool.maturin]
name = "{project_name}"
bindings = "pyo3"
""")
# Create Cargo.toml (maturin can infer some parts, but explicit is clearer for quickstart)
with open(os.path.join(project_dir, "Cargo.toml"), "w") as f:
f.write(f"""
[package]
name = "{project_name}"
version = "0.1.0"
edition = "2021"
[lib]
name = "{project_name}"
crate-type = ["cdylib"]
[dependencies]
pyo3 = {{ version = "0.20", features = ["extension-module"] }}
""")
# Create src/lib.rs with a simple PyO3 function
with open(os.path.join(module_path, "lib.rs"), "w") as f:
f.write("""
use pyo3::prelude::*;
#[pyfunction]
fn greet() -> PyResult<String> {
Ok("Hello from Rust!".to_string())
}
#[pymodule]
fn my_rust_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(greet, m)?)?;
Ok(())
}
""")
# Change to the project directory so the hook can find pyproject.toml
original_cwd = os.getcwd()
os.chdir(project_dir)
print(f"Attempting to import '{project_name}' from: {os.getcwd()}")
# Now, import the Rust module as if it were a Python module
# The hook will find pyproject.toml and potentially build the module.
try:
import my_rust_module
message = my_rust_module.greet()
print(f"✅ Successfully imported and called Rust module: {message}")
except ImportError as e:
print(f"❌ Failed to import Rust module: {e}", file=sys.stderr)
print("Ensure 'maturin' is installed (pip install maturin) and your Rust project is valid.", file=sys.stderr)
except Exception as e:
print(f"❌ An unexpected error occurred: {e}", file=sys.stderr)
finally:
# Clean up dummy project and restore original cwd
os.chdir(original_cwd)
if os.path.exists(project_dir):
print(f"Cleaning up temporary project directory: {project_dir}")
shutil.rmtree(project_dir)