pulumi/pulumi · error · ValueError

module object for {original_name!r} substituted in sys.modul

Error message

module object for {original_name!r} substituted in sys.modules during a lazy load

What it means

Pulumi's `_LazyLoader` (used by `lazy_import`) defers module execution until first attribute access, then verifies the freshly executed module is still the object registered in `sys.modules`. This ValueError fires when, during that lazy load, a different module object was substituted under the same name in `sys.modules` — meaning some hook or custom loader replaced the module, which would leave references inconsistent.

Source

Thrown at sdk/python/lib/pulumi/_utils.py:323

        original_name = self.__spec__.name
        # Figure out exactly what attributes were mutated between the creation
        # of the module and now.
        attrs_then = self.__spec__.loader_state["__dict__"]
        attrs_now = self.__dict__
        attrs_updated = {}
        for key, value in attrs_now.items():
            # Code that set the attribute may have kept a reference to the
            # assigned object, making identity more important than equality.
            if key not in attrs_then:
                attrs_updated[key] = value
            elif id(attrs_now[key]) != id(attrs_then[key]):
                attrs_updated[key] = value
        self.__spec__.loader.exec_module(self)
        # If exec_module() was used directly there is no guarantee the module
        # object was put into sys.modules.
        if original_name in sys.modules:
            if id(self) != id(sys.modules[original_name]):
                raise ValueError(
                    f"module object for {original_name!r} "
                    "substituted in sys.modules during a lazy "
                    "load"
                )
        # Update after loading since that's what would happen in an eager
        # loading situation.
        self.__dict__.update(attrs_updated)
        return getattr(self, attr)

    def __delattr__(self, attr):
        """Trigger the load and then perform the deletion."""
        # To trigger the load and raise an exception if the attribute
        # doesn't exist.
        self.__getattribute__(attr)
        delattr(self, attr)


class _LazyLoader(importlib.abc.Loader):

View on GitHub (pinned to 793f7b2e16)

Solutions

  1. Remove any code that assigns a different object to `sys.modules[<name>]` during import.
  2. Access the module normally (`import module`) instead of through `lazy_import` when mocking is involved.
  3. In tests, mock attributes on the real module after import rather than replacing the module object in sys.modules.
  4. Ensure no custom meta_path finder/loader re-registers the module during exec.

Example fix

// before
# in module under lazy import
import sys
sys.modules[__name__] = MyShim()

// after
# in module under lazy import
# no sys.modules reassignment; expose shim as an attribute
shim = MyShim()
Defensive patterns

Strategy: try-catch

Validate before calling

# before lazy load, ensure nothing will replace the module
assert original_name not in sys.modules or id(sys.modules[original_name]) == id(expected_module)

Try / catch

try:
    mod = lazy_import("mymodule")
    mod.attr
except ValueError as e:
    if "substituted in sys.modules" in str(e):
        import importlib
        mod = importlib.import_module("mymodule")
    else:
        raise

Prevention

When it happens

Trigger: During a lazy load, `exec_module()` (or module code itself) installs a new module object into `sys.modules[original_name]` — e.g. the module re-imports itself, a test double replaces it, or a custom importer hooks `sys.modules` writes.

Common situations: Test suites injecting mocks into sys.modules while lazy imports are pending; modules that call `sys.modules[__name__] = something` at import time; circular imports combined with lazy_import; custom meta-path importers fighting the lazy loader.

Related errors


AI-assisted analysis of pulumi/pulumi@793f7b2e16 (2026-08-31). Data as JSON: /api/errors/527cfd04e938e1e0. Report an issue: GitHub.