Python’s importlib.util module exposes the building blocks of the import system for dynamically loading modules and inspecting import machinery. import importlib.util. Load any file: spec = importlib.util.spec_from_file_location("name", "/path/to/file.py") → ModuleSpec; mod = importlib.util.module_from_spec(spec) → new module object; spec.loader.exec_module(mod) — execute the module code. Find without importing: importlib.util.find_spec("json") → ModuleSpec | None. Cache paths: importlib.util.cache_from_source("/src/foo.py") → .pyc path; importlib.util.source_from_cache("/src/__pycache__/foo.cpython-312.pyc") → source path. Lazy loading: loader = importlib.util.LazyLoader(spec.loader); spec.loader = loader — defers exec_module until first attribute access. Source hash: importlib.util.source_hash(source_bytes) → 8-byte hash for hash-based pyc files. Claude Code generates plugin loaders, dynamic module executors, sandboxed import contexts, and path-based module discovery tools.
CLAUDE.md for importlib.util
## importlib.util Stack
- Stdlib: import importlib.util
- Load file: spec = importlib.util.spec_from_file_location("name", path)
- mod = importlib.util.module_from_spec(spec)
- spec.loader.exec_module(mod)
- Find: importlib.util.find_spec("json") # ModuleSpec or None
- Cache: importlib.util.cache_from_source("foo.py") # → .pyc path
- Lazy: importlib.util.LazyLoader(spec.loader)
- Note: register mod in sys.modules BEFORE exec_module to handle circular imports
importlib.util Dynamic Import Pipeline
# app/importutil.py — loader, finder, plugin, cache, lazy, discovery
from __future__ import annotations
import importlib
import importlib.util
import sys
from dataclasses import dataclass, field
from pathlib import Path
from types import ModuleType
from typing import Any
# ─────────────────────────────────────────────────────────────────────────────
# 1. Dynamic file loading
# ─────────────────────────────────────────────────────────────────────────────
def load_module_from_file(
name: str,
path: "str | Path",
register: bool = True,
) -> ModuleType:
"""
Load a Python source file as a module.
If register=True (default) the module is added to sys.modules under name,
which is required for modules that import from themselves (circular imports).
Example:
plugin = load_module_from_file("myplugin", "/opt/plugins/myplugin.py")
plugin.run()
"""
path = Path(path)
spec = importlib.util.spec_from_file_location(name, path)
if spec is None or spec.loader is None:
raise ImportError(f"Cannot create spec for {path}")
mod = importlib.util.module_from_spec(spec)
if register:
sys.modules[name] = mod
spec.loader.exec_module(mod) # type: ignore[union-attr]
return mod
def load_module_from_file_lazy(
name: str,
path: "str | Path",
) -> ModuleType:
"""
Load a module lazily — code executes on first attribute access.
Useful for optional heavyweight modules.
Example:
heavy = load_module_from_file_lazy("heavy", "/opt/heavy.py")
# No code executed yet
heavy.process() # executes now
"""
path = Path(path)
spec = importlib.util.spec_from_file_location(name, path)
if spec is None or spec.loader is None:
raise ImportError(f"Cannot create spec for {path}")
spec.loader = importlib.util.LazyLoader(spec.loader) # type: ignore[assignment]
mod = importlib.util.module_from_spec(spec)
sys.modules[name] = mod
spec.loader.exec_module(mod) # type: ignore[union-attr]
return mod
def reload_module(mod: ModuleType) -> ModuleType:
"""
Reload a previously imported module in place.
Example:
import mymodule
# edit mymodule.py on disk
reload_module(mymodule)
"""
return importlib.reload(mod)
# ─────────────────────────────────────────────────────────────────────────────
# 2. Module lookup / introspection
# ─────────────────────────────────────────────────────────────────────────────
def module_exists(name: str) -> bool:
"""
Return True if the named module can be found without importing it.
Example:
module_exists("json") # True
module_exists("nonexistent") # False
"""
return importlib.util.find_spec(name) is not None
def module_path(name: str) -> "Path | None":
"""
Return the filesystem path of the named module without importing it.
Returns None if the module is not found or is a built-in.
Example:
print(module_path("email")) # PosixPath('/usr/lib/.../email/__init__.py')
print(module_path("sys")) # None (built-in)
print(module_path("nonexistent")) # None
"""
spec = importlib.util.find_spec(name)
if spec is None or spec.origin is None:
return None
return Path(spec.origin)
def is_package(name: str) -> bool:
"""
Return True if the named module is a package (has subpackages).
Example:
is_package("email") # True
is_package("json") # True
is_package("sys") # False
"""
spec = importlib.util.find_spec(name)
if spec is None:
return False
return spec.submodule_search_locations is not None
@dataclass
class ModuleInfo:
"""Summary info about an importable module."""
name: str
found: bool
path: "Path | None"
is_pkg: bool
origin: "str | None"
is_builtin: bool
def __str__(self) -> str:
if not self.found:
return f"ModuleInfo({self.name!r} NOT FOUND)"
kind = "package" if self.is_pkg else ("built-in" if self.is_builtin else "module")
return f"ModuleInfo({self.name!r} {kind} path={self.path})"
def inspect_module(name: str) -> ModuleInfo:
"""
Return ModuleInfo for a named module without importing it.
Example:
print(inspect_module("json"))
print(inspect_module("sys"))
print(inspect_module("nonexistent"))
"""
spec = importlib.util.find_spec(name)
if spec is None:
return ModuleInfo(name=name, found=False, path=None, is_pkg=False, origin=None, is_builtin=False)
origin = spec.origin
is_builtin = origin == "built-in" or (spec.loader is not None and
type(spec.loader).__name__ == "BuiltinImporter")
path = Path(spec.origin) if origin and origin != "built-in" else None
is_pkg = spec.submodule_search_locations is not None
return ModuleInfo(name=name, found=True, path=path, is_pkg=is_pkg, origin=origin, is_builtin=is_builtin)
# ─────────────────────────────────────────────────────────────────────────────
# 3. Plugin loader
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class Plugin:
"""A loaded plugin module with metadata."""
name: str
path: Path
module: ModuleType
ok: bool
error: str = ""
def get(self, attr: str, default: Any = None) -> Any:
return getattr(self.module, attr, default)
def has(self, attr: str) -> bool:
return hasattr(self.module, attr)
def load_plugins_from_dir(
directory: "str | Path",
pattern: str = "*.py",
name_prefix: str = "plugin",
api_attrs: "list[str] | None" = None,
) -> list[Plugin]:
"""
Load all Python files matching pattern from directory as plugins.
If api_attrs is given, only files that define all listed attributes are kept.
Example:
plugins = load_plugins_from_dir("/opt/plugins", api_attrs=["run", "NAME"])
for p in plugins:
print(p.name, p.get("NAME"), p.module.run())
"""
plugins: list[Plugin] = []
for path in sorted(Path(directory).glob(pattern)):
if path.name.startswith("_"):
continue
name = f"{name_prefix}.{path.stem}"
try:
mod = load_module_from_file(name, path)
if api_attrs and not all(hasattr(mod, a) for a in api_attrs):
continue
plugins.append(Plugin(name=name, path=path, module=mod, ok=True))
except Exception as e:
plugins.append(Plugin(
name=name, path=path, module=ModuleType(name),
ok=False, error=str(e),
))
return plugins
# ─────────────────────────────────────────────────────────────────────────────
# 4. Cache path utilities
# ─────────────────────────────────────────────────────────────────────────────
def pyc_path(source: "str | Path") -> Path:
"""
Return the expected .pyc cache path for a source file.
Example:
print(pyc_path("myapp/utils.py"))
# myapp/__pycache__/utils.cpython-312.pyc
"""
return Path(importlib.util.cache_from_source(str(source)))
def source_from_pyc(pyc: "str | Path") -> Path:
"""
Return the source path that corresponds to a .pyc cache file.
Example:
print(source_from_pyc("myapp/__pycache__/utils.cpython-312.pyc"))
# myapp/utils.py
"""
return Path(importlib.util.source_from_cache(str(pyc)))
def pyc_is_stale(source: "str | Path") -> bool:
"""
Return True if the source file is newer than its cached .pyc, or if no .pyc exists.
Example:
if pyc_is_stale("myapp/utils.py"):
print("cache is outdated")
"""
src = Path(source)
try:
cache = pyc_path(src)
if not cache.exists():
return True
return src.stat().st_mtime > cache.stat().st_mtime
except Exception:
return True
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
import tempfile
print("=== importlib.util demo ===")
# ── inspect_module ────────────────────────────────────────────────────────
print("\n--- inspect_module ---")
for name in ["json", "sys", "email", "nonexistent_xyz", "os.path"]:
print(f" {inspect_module(name)}")
# ── module_exists / module_path / is_package ──────────────────────────────
print("\n--- module_exists / module_path ---")
for name in ["json", "pathlib", "sys", "nonexistent_xyz"]:
print(f" {name:15s} exists={module_exists(name)} is_pkg={is_package(name)} path={module_path(name)}")
# ── load_module_from_file ─────────────────────────────────────────────────
print("\n--- load_module_from_file ---")
with tempfile.NamedTemporaryFile(suffix=".py", mode="w", delete=False) as f:
f.write('NAME = "demo_module"\n\ndef greet(name):\n return f"Hello, {name}!"\n')
tmp_path = f.name
try:
demo = load_module_from_file("_demo_dynamic", tmp_path)
print(f" NAME: {demo.NAME!r}")
print(f" greet(): {demo.greet('World')!r}")
finally:
import os
os.unlink(tmp_path)
sys.modules.pop("_demo_dynamic", None)
# ── load_plugins_from_dir ─────────────────────────────────────────────────
print("\n--- load_plugins_from_dir ---")
with tempfile.TemporaryDirectory() as tmpdir:
(Path(tmpdir) / "alpha.py").write_text('NAME="alpha"\ndef run(): return "alpha-result"')
(Path(tmpdir) / "beta.py").write_text('NAME="beta"\ndef run(): return "beta-result"')
(Path(tmpdir) / "gamma.py").write_text('# no NAME or run\nfoo = 1')
plugins = load_plugins_from_dir(tmpdir, api_attrs=["NAME", "run"])
for p in plugins:
print(f" {p.name:25s} ok={p.ok} NAME={p.get('NAME')!r} run()={p.module.run()!r}")
# ── pyc_path / source_from_pyc ────────────────────────────────────────────
print("\n--- pyc_path / source_from_pyc ---")
src = Path(__file__)
cache = pyc_path(src)
print(f" source: {src.name}")
print(f" cache: {cache.name}")
print(f" stale: {pyc_is_stale(src)}")
print("\n=== done ===")
For the importlib.import_module() alternative — importlib.import_module("json") is the standard way to import a module by string name when you have the module name but not a file path — use importlib.import_module() for name-based imports (plugins registered in config files); use importlib.util.spec_from_file_location() when you have a file path on disk that is not on sys.path. For the __import__() built-in alternative — __import__("json") is the low-level hook that import statements compile to; it returns the top-level package, not the submodule — always prefer importlib.import_module() over __import__() for programmatic imports; they both ultimately call the same import machinery but importlib.import_module() has cleaner semantics and handles dotted names correctly. The Claude Skills 360 bundle includes importlib.util skill sets covering load_module_from_file()/load_module_from_file_lazy()/reload_module() file loaders, module_exists()/module_path()/is_package()/ModuleInfo/inspect_module() introspection helpers, Plugin dataclass + load_plugins_from_dir() plugin system, and pyc_path()/source_from_pyc()/pyc_is_stale() cache utilities. Start with the free tier to try dynamic import patterns and importlib.util pipeline code generation.