Python’s importlib module provides the implementation of Python’s import system and utilities for dynamic imports, module reloading, and package resource access. import importlib. import_module: mod = importlib.import_module("json") — same as import json; importlib.import_module(".sub", package="pkg") for relative. reload: importlib.reload(mod) — re-execute the module’s code (useful for hot-reload in dev). find_spec: spec = importlib.util.find_spec("numpy") → ModuleSpec or None for not-installed. spec_from_file_location: spec = importlib.util.spec_from_file_location("mymod", "/path/to/file.py"). module_from_spec: mod = importlib.util.module_from_spec(spec); spec.loader.exec_module(mod) — load arbitrary .py file. resources.files: ref = importlib.resources.files("mypackage") / "data/config.json" → Traversable. ref.read_text(), ref.read_bytes(). as_file: with importlib.resources.as_file(ref) as path: — guaranteed real Path. metadata.version: importlib.metadata.version("requests") → "2.31.0". entry_points: importlib.metadata.entry_points(group="console_scripts") → list. requires: importlib.metadata.requires("flask") → list of requirement strings. importlib.metadata.packages_distributions() → dict. Claude Code generates plugin loaders, hot-reload utilities, embedded resource readers, and installed-package inspectors.
CLAUDE.md for importlib
## importlib Stack
- Stdlib: import importlib, importlib.util, importlib.resources, importlib.metadata
- Import: mod = importlib.import_module("json")
- Find: spec = importlib.util.find_spec("numpy") # None if not installed
- File: spec = importlib.util.spec_from_file_location("name", path)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
- Resource: ref = importlib.resources.files("pkg") / "data/file.txt"
text = ref.read_text(encoding="utf-8")
- Meta: importlib.metadata.version("requests")
importlib Dynamic Import Pipeline
# app/importutil.py — import, find, load from file, resources, metadata, plugin
from __future__ import annotations
import importlib
import importlib.metadata
import importlib.resources
import importlib.util
import sys
from dataclasses import dataclass
from pathlib import Path
from types import ModuleType
from typing import Any
# ─────────────────────────────────────────────────────────────────────────────
# 1. Dynamic import helpers
# ─────────────────────────────────────────────────────────────────────────────
def import_module(name: str, package: str | None = None) -> ModuleType:
"""
Import a module by name string.
Example:
json = import_module("json")
sub = import_module(".utils", package="myapp")
"""
return importlib.import_module(name, package=package)
def try_import(name: str, default: Any = None) -> ModuleType | Any:
"""
Import a module, returning default on ImportError.
Example:
numpy = try_import("numpy")
if numpy is None:
print("numpy not installed")
"""
try:
return importlib.import_module(name)
except ImportError:
return default
def is_importable(name: str) -> bool:
"""
Return True if the module can be imported (installed and on sys.path).
Example:
if is_importable("ujson"):
import ujson as json
else:
import json
"""
return importlib.util.find_spec(name) is not None
def import_attr(dotted_path: str) -> Any:
"""
Import a module and return a named attribute, given "module.attr" path.
Example:
loads = import_attr("json.loads")
Path = import_attr("pathlib.Path")
"""
module_path, _, attr = dotted_path.rpartition(".")
if not module_path:
raise ValueError(f"No module in dotted path: {dotted_path!r}")
mod = importlib.import_module(module_path)
return getattr(mod, attr)
def reload_module(module: ModuleType) -> ModuleType:
"""
Reload an already-imported module to pick up source changes.
Example:
import myconfig
# ... edit myconfig.py ...
myconfig = reload_module(myconfig)
"""
return importlib.reload(module)
# ─────────────────────────────────────────────────────────────────────────────
# 2. Load module from arbitrary file path
# ─────────────────────────────────────────────────────────────────────────────
def load_from_file(
path: str | Path,
module_name: str | None = None,
add_to_sys_modules: bool = False,
) -> ModuleType:
"""
Load a Python file as a module without it being on sys.path.
module_name: defaults to the file stem.
add_to_sys_modules: register in sys.modules under module_name.
Example:
plugin = load_from_file("/plugins/my_plugin.py")
plugin.run()
"""
p = Path(path)
name = module_name or p.stem
spec = importlib.util.spec_from_file_location(name, str(p))
if spec is None or spec.loader is None:
raise ImportError(f"Cannot create spec for {path!r}")
mod = importlib.util.module_from_spec(spec)
if add_to_sys_modules:
sys.modules[name] = mod
spec.loader.exec_module(mod) # type: ignore[attr-defined]
return mod
def load_plugins_from_dir(
directory: str | Path,
pattern: str = "*.py",
exclude: list[str] | None = None,
) -> dict[str, ModuleType]:
"""
Load all .py files in a directory as plugins.
Returns {stem: module}.
Example:
plugins = load_plugins_from_dir("plugins/")
for name, mod in plugins.items():
if hasattr(mod, "run"):
mod.run()
"""
excluded = set(exclude or []) | {"__init__"}
result: dict[str, ModuleType] = {}
for p in sorted(Path(directory).glob(pattern)):
if p.stem not in excluded:
try:
result[p.stem] = load_from_file(p)
except Exception as e:
result[p.stem + "_ERROR"] = e # type: ignore[assignment]
return result
# ─────────────────────────────────────────────────────────────────────────────
# 3. Package resource access
# ─────────────────────────────────────────────────────────────────────────────
def read_package_resource(package: str, resource: str, encoding: str = "utf-8") -> str:
"""
Read a text resource bundled inside a Python package (works in wheels/zips).
Example:
# mypackage/data/config.json exists
content = read_package_resource("mypackage", "data/config.json")
"""
ref = importlib.resources.files(package).joinpath(resource)
return ref.read_text(encoding=encoding)
def read_package_resource_bytes(package: str, resource: str) -> bytes:
"""Read a binary resource from a package."""
ref = importlib.resources.files(package).joinpath(resource)
return ref.read_bytes()
def list_package_resources(package: str) -> list[str]:
"""
List all resource files under a package (non-recursive).
Example:
files = list_package_resources("mypackage")
"""
result: list[str] = []
pkg_ref = importlib.resources.files(package)
try:
for item in pkg_ref.iterdir():
result.append(item.name)
except (NotADirectoryError, AttributeError):
pass
return sorted(result)
# ─────────────────────────────────────────────────────────────────────────────
# 4. Package metadata
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class PackageInfo:
name: str
version: str
summary: str
requires_python: str
homepage: str
requires: list[str]
entry_points: dict[str, list[str]] # {group: [name=value, ...]}
def __str__(self) -> str:
return (f"{self.name}=={self.version} "
f"py>={self.requires_python or '?'} "
f"{self.summary[:60]}")
def package_info(name: str) -> PackageInfo:
"""
Return metadata for an installed package.
Example:
info = package_info("requests")
print(info)
"""
meta = importlib.metadata.metadata(name)
eps: dict[str, list[str]] = {}
for ep in importlib.metadata.entry_points().get(name, []):
eps.setdefault(ep.group, []).append(f"{ep.name}={ep.value}")
all_eps = importlib.metadata.entry_points()
ep_dict: dict[str, list[str]] = {}
if hasattr(all_eps, "select"):
pass # 3.12+ API
else:
for group, entries in all_eps.items():
pkg_distros = importlib.metadata.packages_distributions()
for ep in entries:
dist = ep.value.split(".")[0]
if name.lower().replace("-", "_") in [
(d or "").lower().replace("-", "_")
for d in pkg_distros.get(dist, [])
]:
ep_dict.setdefault(group, []).append(f"{ep.name}={ep.value}")
try:
reqs = importlib.metadata.requires(name) or []
except Exception:
reqs = []
return PackageInfo(
name=meta.get("Name", name),
version=meta.get("Version", "?"),
summary=meta.get("Summary", ""),
requires_python=meta.get("Requires-Python", ""),
homepage=meta.get("Home-page", ""),
requires=reqs,
entry_points=ep_dict,
)
def installed_version(package: str) -> str | None:
"""
Return the installed version string for a package, or None.
Example:
ver = installed_version("requests") # e.g. "2.31.0"
"""
try:
return importlib.metadata.version(package)
except importlib.metadata.PackageNotFoundError:
return None
def check_versions(requirements: dict[str, str]) -> dict[str, tuple[str | None, bool]]:
"""
Check installed versions against minimum requirements.
Returns {package: (installed_version, meets_requirement)}.
Example:
check_versions({"requests": "2.28", "flask": "2.0"})
"""
from packaging.version import Version # type: ignore[import]
result: dict[str, tuple[str | None, bool]] = {}
for pkg, min_ver in requirements.items():
installed = installed_version(pkg)
try:
ok = installed is not None and Version(installed) >= Version(min_ver)
except Exception:
ok = False
result[pkg] = (installed, ok)
return result
# ─────────────────────────────────────────────────────────────────────────────
# 5. Plugin registry pattern
# ─────────────────────────────────────────────────────────────────────────────
class PluginRegistry:
"""
Simple plugin registry: register and discover callables by entry_points group
or by loading all .py files from a plugins directory.
Example:
reg = PluginRegistry("myapp.plugins")
reg.discover_entry_points() # load installed plugins
reg.register("custom", my_fn) # manual registration
for name, fn in reg.items():
fn()
"""
def __init__(self, group: str = "") -> None:
self._group = group
self._plugins: dict[str, Any] = {}
def register(self, name: str, obj: Any) -> None:
self._plugins[name] = obj
def discover_entry_points(self) -> list[str]:
"""Load plugins from the configured entry_points group."""
loaded: list[str] = []
try:
eps = importlib.metadata.entry_points(group=self._group)
except TypeError:
eps = importlib.metadata.entry_points().get(self._group, [])
for ep in eps:
try:
self._plugins[ep.name] = ep.load()
loaded.append(ep.name)
except Exception:
pass
return loaded
def discover_directory(self, path: str | Path) -> list[str]:
"""Load plugins from a directory of .py files."""
mods = load_plugins_from_dir(path)
loaded: list[str] = []
for name, mod in mods.items():
if isinstance(mod, ModuleType) and hasattr(mod, "plugin"):
self._plugins[name] = mod.plugin
loaded.append(name)
return loaded
def get(self, name: str) -> Any | None:
return self._plugins.get(name)
def items(self):
return self._plugins.items()
def names(self) -> list[str]:
return sorted(self._plugins)
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
import tempfile
print("=== importlib demo ===")
# ── dynamic import ─────────────────────────────────────────────────────────
print("\n--- import_module / try_import ---")
json_mod = import_module("json")
print(f" json.dumps([1,2,3]) = {json_mod.dumps([1, 2, 3])}")
numpy = try_import("numpy")
print(f" numpy importable: {numpy is not None}")
print(f" is_importable('json'): {is_importable('json')}")
print(f" is_importable('__nonexistent__'): {is_importable('__nonexistent__')}")
# ── import_attr ────────────────────────────────────────────────────────────
print("\n--- import_attr ---")
loads = import_attr("json.loads")
print(f" json.loads = {loads!r}")
print(f" json.loads('[1,2]') = {loads('[1,2]')}")
# ── load_from_file ─────────────────────────────────────────────────────────
print("\n--- load_from_file ---")
with tempfile.TemporaryDirectory() as tmpdir:
plugin_path = Path(tmpdir) / "hello_plugin.py"
plugin_path.write_text(
"plugin_name = 'hello'\n"
"def run(n=1): return 'hello ' * n\n"
)
plugin = load_from_file(plugin_path)
print(f" plugin.plugin_name = {plugin.plugin_name!r}")
print(f" plugin.run(3) = {plugin.run(3)!r}")
# load_plugins_from_dir
(Path(tmpdir) / "plugin_a.py").write_text("plugin = lambda: 'A'")
(Path(tmpdir) / "plugin_b.py").write_text("plugin = lambda: 'B'")
plugins = load_plugins_from_dir(tmpdir, exclude=["hello_plugin"])
for name_, mod in plugins.items():
print(f" plugins/{name_}: {type(mod)}")
# ── metadata ───────────────────────────────────────────────────────────────
print("\n--- installed_version ---")
for pkg in ["pip", "setuptools", "requests", "__nonexistent__"]:
ver = installed_version(pkg)
print(f" {pkg:20s}: {ver!r}")
print("\n=== done ===")
For the pkgutil alternative — pkgutil (stdlib) provides pkgutil.iter_modules() for discovering sub-packages and pkgutil.get_data() for reading package resources using the legacy pre-3.9 resource API — use pkgutil.iter_modules() when you need to enumerate all submodules of a package for auto-discovery; use importlib.resources.files() instead of pkgutil.get_data() for resource access, because files() returns a Traversable that works correctly in wheels and zip-imported packages where the resource is not a real filesystem file. For the sys.modules / importlib.machinery alternative — sys.modules is the module cache that importlib.import_module consults before loading; you can inject mock modules by assigning sys.modules["name"] = mock_obj, which is useful in tests; importlib.machinery.SourceFileLoader and importlib.machinery.FileFinder are the lower-level loader classes that handle .py file discovery and compilation — use these when building custom import hooks or meta-path finders; use importlib.util.spec_from_file_location for the cleaner high-level API when loading .py files dynamically. The Claude Skills 360 bundle includes importlib skill sets covering import_module()/try_import()/is_importable()/import_attr()/reload_module() dynamic import helpers, load_from_file()/load_plugins_from_dir() file-based loaders, read_package_resource()/read_package_resource_bytes()/list_package_resources() bundled resource readers, PackageInfo dataclass with package_info()/installed_version()/check_versions() metadata tools, and PluginRegistry entry-points plugin loader. Start with the free tier to try dynamic import patterns and importlib pipeline code generation.