Python’s zipimport module lets you import packages directly from .zip files listed on sys.path. import zipimport. Create: zi = zipimport.zipimporter(path) — path is either a zip file path ("app.zip") or a path inside a zip ("app.zip/subdir"). Attributes: zi.archive — the zip file path; zi.prefix — the path within the archive. Find (modern): zi.find_spec(fullname, target=None) → ModuleSpec | None. Find (legacy): zi.find_module(fullname, path=None) → self | None. Load (legacy): zi.load_module(fullname) → module (deprecated, use importlib machinery). Data: zi.get_data(pathname) → bytes — reads any file from the zip by archive-relative path. Source: zi.get_source(fullname) → str | None — decompressed .py source for the module or None if only .pyc present. Code: zi.get_code(fullname) → code | None — compiled code object. Filename: zi.get_filename(fullname) → archive-rooted path. Is-package: zi.is_package(fullname) → bool. Use: add a zip file to sys.path and Python’s import machinery picks it up automatically via zipimport.zipimporter; inspect with zipimport.zipimporter(path) directly when you need data access without importing. Claude Code generates single-file app bundles, resource readers, zip-based plugin loaders, and distribution validators.
CLAUDE.md for zipimport
## zipimport Stack
- Stdlib: import zipimport, sys, zipfile, importlib
- Install: sys.path.insert(0, "bundle.zip") # auto-activates zipimporter
- Inspect: zi = zipimport.zipimporter("bundle.zip")
- Find: spec = zi.find_spec("mymodule") # ModuleSpec or None
- Data: data = zi.get_data("bundle.zip/assets/config.json")
- Source: src = zi.get_source("mymodule") # .py source string or None
- Note: zip entries on sys.path are auto-handled by Python's import system
zipimport Zip Import Pipeline
# app/zipimportutil.py — bundle builder, reader, loader, validator
from __future__ import annotations
import importlib
import importlib.util
import io
import sys
import types
import zipfile
import zipimport
from dataclasses import dataclass, field
from pathlib import Path
# ─────────────────────────────────────────────────────────────────────────────
# 1. Inspection helpers
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class ZipModuleInfo:
"""
Metadata about one module found inside a zip archive.
Example:
for info in list_zip_modules("bundle.zip"):
print(info)
"""
name: str
is_package: bool
has_source: bool
filename: str
def __str__(self) -> str:
flags = []
if self.is_package:
flags.append("pkg")
if self.has_source:
flags.append("src")
flag_str = f" [{', '.join(flags)}]" if flags else ""
return f"{self.name:30s} {self.filename}{flag_str}"
def list_zip_modules(zip_path: "str | Path") -> list[ZipModuleInfo]:
"""
Return metadata for all importable modules inside a zip archive.
Example:
for info in list_zip_modules("bundle.zip"):
print(info)
"""
zi = zipimport.zipimporter(str(zip_path))
results = []
with zipfile.ZipFile(str(zip_path)) as zf:
names_in_zip = set(zf.namelist())
# Collect candidate module names from .py and .pyc files
seen: set[str] = set()
with zipfile.ZipFile(str(zip_path)) as zf:
for entry in zf.namelist():
# Strip prefix inside the zip
p = entry.replace("\\", "/")
if p.endswith(".py") or p.endswith(".pyc"):
# Convert path to dotted module name
if p.endswith("/__init__.py") or p.endswith("/__init__.pyc"):
mod = p.rsplit("/", 1)[0].replace("/", ".")
elif "/" in p:
mod = p.rsplit("/", 1)[1]
mod = mod[:-3] if mod.endswith(".py") else mod[:-4]
parent = p.rsplit("/", 1)[0].replace("/", ".")
mod = f"{parent}.{mod}" if parent else mod
else:
mod = p[:-3] if p.endswith(".py") else p[:-4]
if not mod or mod in seen:
continue
seen.add(mod)
try:
spec = zi.find_spec(mod)
if spec is None:
continue
src = zi.get_source(mod)
fname = zi.get_filename(mod)
results.append(ZipModuleInfo(
name=mod,
is_package=zi.is_package(mod),
has_source=src is not None,
filename=fname,
))
except (ImportError, AttributeError):
continue
return sorted(results, key=lambda x: x.name)
def zip_has_module(zip_path: "str | Path", module_name: str) -> bool:
"""
Return True if module_name is importable from zip_path.
Example:
if zip_has_module("bundle.zip", "myapp.config"):
print("config module found")
"""
zi = zipimport.zipimporter(str(zip_path))
return zi.find_spec(module_name) is not None
def read_zip_data(zip_path: "str | Path", inner_path: str) -> bytes:
"""
Read raw bytes from a file inside a zip using zipimporter.get_data.
inner_path is relative to the zip root (e.g. "assets/config.json").
Example:
cfg = read_zip_data("bundle.zip", "config/settings.toml")
"""
zi = zipimport.zipimporter(str(zip_path))
# get_data expects the full archive-rooted path
full_path = str(Path(zip_path) / inner_path)
return zi.get_data(full_path)
def get_zip_source(zip_path: "str | Path", module_name: str) -> "str | None":
"""
Return the source code of module_name inside zip_path, or None.
Example:
src = get_zip_source("bundle.zip", "myapp.utils")
if src:
print(src[:100])
"""
zi = zipimport.zipimporter(str(zip_path))
return zi.get_source(module_name)
# ─────────────────────────────────────────────────────────────────────────────
# 2. Zip bundle builder
# ─────────────────────────────────────────────────────────────────────────────
def build_zip_bundle(
source_root: "str | Path",
output: "str | Path",
include_pyc: bool = False,
exclude_patterns: "list[str] | None" = None,
) -> Path:
"""
Package all .py files under source_root into a zip suitable for sys.path.
Example:
build_zip_bundle("src/myapp", "dist/myapp.zip")
sys.path.insert(0, "dist/myapp.zip")
import myapp
"""
import fnmatch
root = Path(source_root)
dest = Path(output)
exclude = exclude_patterns or []
with zipfile.ZipFile(dest, "w", compression=zipfile.ZIP_DEFLATED) as zf:
for py_file in sorted(root.rglob("*.py")):
rel = py_file.relative_to(root)
skip = any(fnmatch.fnmatch(str(rel), pat) for pat in exclude)
if skip:
continue
zf.write(py_file, arcname=str(rel))
if include_pyc:
for pyc_file in sorted(root.rglob("**/__pycache__/*.pyc")):
rel = pyc_file.relative_to(root)
zf.write(pyc_file, arcname=str(rel))
return dest
# ─────────────────────────────────────────────────────────────────────────────
# 3. Dynamic zip importer context
# ─────────────────────────────────────────────────────────────────────────────
class ZipImportContext:
"""
Context manager that temporarily adds a zip archive to sys.path,
then removes it on exit.
Example:
with ZipImportContext("plugins/extra.zip"):
import extra_plugin
extra_plugin.run()
# extra.zip removed from sys.path after the block
"""
def __init__(self, zip_path: "str | Path") -> None:
self._path = str(Path(zip_path).resolve())
self._added: list[str] = []
def __enter__(self) -> "ZipImportContext":
if self._path not in sys.path:
sys.path.insert(0, self._path)
self._added.append(self._path)
return self
def __exit__(self, *_: object) -> None:
for p in self._added:
if p in sys.path:
sys.path.remove(p)
self._added.clear()
# ─────────────────────────────────────────────────────────────────────────────
# 4. Zip bundle validator
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class BundleValidation:
"""
Validate that a zip bundle contains the expected modules and data files.
Example:
v = BundleValidation("dist/app.zip")
v.require_module("myapp")
v.require_module("myapp.config")
v.require_data("assets/schema.json")
v.run()
if v.errors:
for e in v.errors:
print(f" FAIL: {e}")
"""
zip_path: Path
required_modules: list[str] = field(default_factory=list)
required_data: list[str] = field(default_factory=list)
errors: list[str] = field(default_factory=list)
def __post_init__(self) -> None:
self.zip_path = Path(self.zip_path)
def require_module(self, name: str) -> "BundleValidation":
self.required_modules.append(name)
return self
def require_data(self, inner_path: str) -> "BundleValidation":
self.required_data.append(inner_path)
return self
def run(self) -> "BundleValidation":
self.errors.clear()
zi = zipimport.zipimporter(str(self.zip_path))
for mod in self.required_modules:
try:
spec = zi.find_spec(mod)
if spec is None:
self.errors.append(f"module not found: {mod}")
except Exception as e:
self.errors.append(f"module error ({mod}): {e}")
with zipfile.ZipFile(self.zip_path) as zf:
names = set(zf.namelist())
for data_path in self.required_data:
normalized = data_path.replace("\\", "/")
if normalized not in names:
self.errors.append(f"data file not found: {data_path}")
return self
@property
def ok(self) -> bool:
return len(self.errors) == 0
# ─────────────────────────────────────────────────────────────────────────────
# 5. zipimport path hook inspection
# ─────────────────────────────────────────────────────────────────────────────
def active_zip_importers() -> list[zipimport.zipimporter]:
"""
Return all zipimporter instances currently active in sys.path_importer_cache.
Example:
for zi in active_zip_importers():
print(f" archive: {zi.archive}")
"""
return [
imp for imp in sys.path_importer_cache.values()
if isinstance(imp, zipimport.zipimporter)
]
def zip_module_origin(module_name: str) -> "str | None":
"""
If module_name was imported from a zip, return the zip archive path.
Returns None if it was not loaded via zipimport.
Example:
with ZipImportContext("bundle.zip"):
import mymodule
print(zip_module_origin("mymodule")) # "bundle.zip" or None
"""
mod = sys.modules.get(module_name)
if mod is None:
return None
loader = getattr(mod, "__loader__", None)
if isinstance(loader, zipimport.zipimporter):
return loader.archive
return None
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
import os
import tempfile
print("=== zipimport demo ===")
with tempfile.TemporaryDirectory() as td:
td_path = Path(td)
# ── build a small bundle ───────────────────────────────────────────────
pkg = td_path / "greetpkg"
pkg.mkdir()
(pkg / "__init__.py").write_text(
'def hello(name): return f"Hello, {name}!"\n'
)
(pkg / "utils.py").write_text(
'def shout(s): return s.upper() + "!"\n'
)
(td_path / "standalone.py").write_text(
'ANSWER = 42\n'
)
# Add a data file
assets = td_path / "assets"
assets.mkdir()
(assets / "info.txt").write_text("bundle version 1.0")
zip_path = td_path / "bundle.zip"
with zipfile.ZipFile(zip_path, "w") as zf:
zf.write(pkg / "__init__.py", "greetpkg/__init__.py")
zf.write(pkg / "utils.py", "greetpkg/utils.py")
zf.write(td_path / "standalone.py", "standalone.py")
zf.write(assets / "info.txt", "assets/info.txt")
# ── list_zip_modules ──────────────────────────────────────────────────
print("\n--- list_zip_modules ---")
for info in list_zip_modules(zip_path):
print(f" {info}")
# ── zip_has_module ────────────────────────────────────────────────────
print("\n--- zip_has_module ---")
for mod in ["greetpkg", "greetpkg.utils", "standalone", "missing"]:
print(f" {mod}: {zip_has_module(zip_path, mod)}")
# ── get_zip_source ────────────────────────────────────────────────────
print("\n--- get_zip_source ---")
src = get_zip_source(zip_path, "standalone")
print(f" standalone.py source: {src!r}")
# ── read_zip_data ─────────────────────────────────────────────────────
print("\n--- read_zip_data ---")
data = read_zip_data(zip_path, "assets/info.txt")
print(f" assets/info.txt: {data.decode()!r}")
# ── ZipImportContext + import ──────────────────────────────────────────
print("\n--- ZipImportContext ---")
with ZipImportContext(zip_path):
import standalone # type: ignore[import]
print(f" standalone.ANSWER = {standalone.ANSWER}")
origin = zip_module_origin("standalone")
print(f" loaded from zip: {origin!r}")
# clean up sys.modules
sys.modules.pop("standalone", None)
# ── BundleValidation ──────────────────────────────────────────────────
print("\n--- BundleValidation ---")
v = (
BundleValidation(zip_path)
.require_module("greetpkg")
.require_module("greetpkg.utils")
.require_module("standalone")
.require_module("nonexistent")
.require_data("assets/info.txt")
.require_data("readme.txt")
.run()
)
print(f" ok: {v.ok} errors: {v.errors}")
# ── active_zip_importers ──────────────────────────────────────────────
print("\n--- active_zip_importers ---")
with ZipImportContext(zip_path):
importers = active_zip_importers()
print(f" found {len(importers)} active zip importer(s)")
for zi in importers:
print(f" archive={zi.archive} prefix={zi.prefix!r}")
print("\n=== done ===")
For the sys.path / zipfile alternative — you can open a zip with zipfile.ZipFile and read file contents directly, bypassing the import system entirely — use zipfile when you need to access data files or non-Python content inside a zip; use zipimport when you want to import Python modules from the zip as first-class objects and need find_spec/get_source/get_code for tooling or plugin systems. For the importlib.util.spec_from_file_location alternative — spec_from_file_location(name, path) creates a module spec from an explicit filesystem path, supporting .so/.pyd extension modules that raw zip loading cannot handle — use importlib.util when you need to load extension modules or control the full loader lifecycle (e.g., injecting into sys.modules under a custom name); use zipimport for pure-Python single-file distribution bundles. The Claude Skills 360 bundle includes zipimport skill sets covering ZipModuleInfo + list_zip_modules() archive introspection, zip_has_module()/read_zip_data()/get_zip_source() data access helpers, build_zip_bundle() source packager, ZipImportContext temporary sys.path injector, BundleValidation with require_module()/require_data()/run() pre-deploy validator, and active_zip_importers()/zip_module_origin() runtime inspection. Start with the free tier to try zip import patterns and zipimport pipeline code generation.