Claude Code for zipapp: Python Executable Zip Application Builder — Claude Skills 360 Blog
Blog / AI / Claude Code for zipapp: Python Executable Zip Application Builder
AI

Claude Code for zipapp: Python Executable Zip Application Builder

Published: December 15, 2028
Read time: 5 min read
By: Claude Skills 360

Python’s zipapp module creates executable .pyz archives — zip files with a #!/usr/bin/env python3 shebang that can be run directly as python app.pyz or ./app.pyz. import zipapp. Create: zipapp.create_archive(source, target=None, interpreter="/usr/bin/env python3", main=None, filter=None, compressed=False)source is a directory with a __main__.py or an existing zip; target defaults to source with .pyz extension; interpreter is the shebang (None = no shebang); main overrides the entry point as "pkg.module:function" (auto-generates __main__.py); filter(path) → bool selects which files to include; compressed=True uses ZIP_DEFLATED. Read shebang: zipapp.get_interpreter(archive)str | None. The resulting .pyz file is a valid zip: zipfile.ZipFile("app.pyz") works normally. On POSIX, set execute bit (chmod +x) and run as ./app.pyz; on Windows, associate .pyz with python. Claude Code generates single-file CLI tools, deployment bundles, Lambda layers, and self-extracting archives.

CLAUDE.md for zipapp

## zipapp Stack
- Stdlib: import zipapp, zipfile, shutil
- Create: zipapp.create_archive("src/", target="dist/app.pyz",
-                               interpreter="/usr/bin/env python3")
- Entry:  zipapp.create_archive("src/", main="myapp.cli:main")
-         # auto-generates __main__.py calling myapp.cli:main()
- Compress: zipapp.create_archive("src/", compressed=True)
- Read:   zipapp.get_interpreter("app.pyz")
- Run:    python app.pyz  OR  ./app.pyz  (after chmod +x)
- Note:   stdlib-only; for third-party deps use shiv or pex

zipapp Executable Archive Pipeline

# app/zipapputil.py — builder, inspector, bundler, deployer
from __future__ import annotations

import os
import shutil
import stat
import zipapp
import zipfile
from dataclasses import dataclass, field
from pathlib import Path


# ─────────────────────────────────────────────────────────────────────────────
# 1. Archive inspection
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class PyzInfo:
    """
    Metadata about an existing .pyz archive.

    Example:
        info = inspect_pyz("dist/app.pyz")
        print(info)
    """
    path:       Path
    interpreter: "str | None"
    size_bytes:  int
    files:       list[str]
    has_main:    bool
    compressed:  bool

    def __str__(self) -> str:
        interp = self.interpreter or "(no shebang)"
        return (
            f"PyzInfo({self.path.name}  {self.size_bytes} bytes  "
            f"interp={interp!r}  files={len(self.files)}  "
            f"has_main={self.has_main}  compressed={self.compressed})"
        )


def inspect_pyz(path: "str | Path") -> PyzInfo:
    """
    Return metadata for a .pyz archive.

    Example:
        info = inspect_pyz("dist/app.pyz")
        print(info)
        for f in info.files:
            print(f"  {f}")
    """
    p = Path(path)
    interpreter = zipapp.get_interpreter(str(p))

    with zipfile.ZipFile(p) as zf:
        names = zf.namelist()
        infos = zf.infolist()
        compressed = any(
            zi.compress_type == zipfile.ZIP_DEFLATED for zi in infos
        )

    return PyzInfo(
        path=p,
        interpreter=interpreter,
        size_bytes=p.stat().st_size,
        files=names,
        has_main="__main__.py" in names,
        compressed=compressed,
    )


def read_pyz_file(archive: "str | Path", inner_path: str) -> bytes:
    """
    Read a file from inside a .pyz archive.

    Example:
        src = read_pyz_file("dist/app.pyz", "__main__.py")
        print(src.decode())
    """
    with zipfile.ZipFile(str(archive)) as zf:
        return zf.read(inner_path)


# ─────────────────────────────────────────────────────────────────────────────
# 2. Builder helpers
# ─────────────────────────────────────────────────────────────────────────────

def build_pyz(
    source: "str | Path",
    target: "str | Path | None" = None,
    main: "str | None" = None,
    interpreter: str = "/usr/bin/env python3",
    compressed: bool = True,
    executable: bool = True,
    filter_fn=None,
) -> Path:
    """
    Build a .pyz from source directory. Returns the output path.

    Args:
        source:     Directory containing __main__.py (or use main= to generate one)
        target:     Output .pyz path (default: source.pyz)
        main:       Entry point like "myapp.cli:main" (generates __main__.py)
        interpreter: Shebang line (None = no shebang)
        compressed: Deflate the archive contents
        executable: chmod +x the output on POSIX
        filter_fn:  (path: Path) -> bool; return False to exclude

    Example:
        path = build_pyz("src/myapp", target="dist/myapp.pyz",
                         main="myapp.cli:main", compressed=True)
    """
    src = Path(source)
    dest = Path(target) if target else src.parent / (src.name + ".pyz")
    dest.parent.mkdir(parents=True, exist_ok=True)

    py_filter = None
    if filter_fn is not None:
        def py_filter(p: Path) -> bool:
            return filter_fn(p)

    zipapp.create_archive(
        str(src),
        target=str(dest),
        interpreter=interpreter,
        main=main,
        filter=py_filter,
        compressed=compressed,
    )

    if executable and os.name == "posix":
        current = dest.stat().st_mode
        dest.chmod(current | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)

    return dest


def exclude_tests_and_cache(path: Path) -> bool:
    """
    Filter function for build_pyz: exclude test files and __pycache__.

    Example:
        pyz = build_pyz("src/app", filter_fn=exclude_tests_and_cache)
    """
    parts = path.parts
    excluded_dirs = {"__pycache__", "tests", "test", ".git"}
    if any(p in excluded_dirs for p in parts):
        return False
    if path.name.startswith("test_") or path.name.endswith("_test.py"):
        return False
    return True


# ─────────────────────────────────────────────────────────────────────────────
# 3. Staged bundle builder
# ─────────────────────────────────────────────────────────────────────────────

class PyzBundler:
    """
    Build a .pyz by explicitly collecting files from multiple sources.

    Example:
        bundler = PyzBundler()
        bundler.add_directory("src/myapp", prefix="myapp")
        bundler.add_file("data/schema.json", arcname="myapp/schema.json")
        bundler.set_main("myapp.server:main")
        pyz = bundler.build("dist/server.pyz")
    """

    def __init__(self) -> None:
        self._files: list[tuple[Path, str]] = []   # (real_path, arcname)
        self._main: "str | None" = None
        self._interpreter: str = "/usr/bin/env python3"
        self._compressed: bool = True

    def add_directory(
        self,
        directory: "str | Path",
        prefix: str = "",
        filter_fn=None,
    ) -> "PyzBundler":
        """Add all .py files from directory under the given prefix."""
        root = Path(directory)
        for py in sorted(root.rglob("*.py")):
            rel = py.relative_to(root)
            arcname = str(Path(prefix) / rel) if prefix else str(rel)
            if filter_fn is None or filter_fn(py):
                self._files.append((py, arcname))
        return self

    def add_file(
        self,
        path: "str | Path",
        arcname: "str | None" = None,
    ) -> "PyzBundler":
        """Add a single file at the given arcname."""
        p = Path(path)
        self._files.append((p, arcname or p.name))
        return self

    def set_main(self, main: str) -> "PyzBundler":
        """Set entry point as 'pkg.module:function'."""
        self._main = main
        return self

    def set_interpreter(self, interpreter: str) -> "PyzBundler":
        self._interpreter = interpreter
        return self

    def build(self, target: "str | Path", tmp_dir: "str | Path | None" = None) -> Path:
        """
        Assemble and write the .pyz file.
        Returns the path to the created archive.
        """
        import tempfile
        ctx = tempfile.TemporaryDirectory() if tmp_dir is None else None
        work = Path(tmp_dir) if tmp_dir else Path(ctx.name)  # type: ignore[union-attr]
        try:
            stage = work / "_stage"
            stage.mkdir(parents=True)

            for real_path, arcname in self._files:
                dest = stage / arcname
                dest.parent.mkdir(parents=True, exist_ok=True)
                shutil.copy2(str(real_path), str(dest))

            if self._main:
                main_py = stage / "__main__.py"
                pkg, _, func = self._main.rpartition(":")
                main_py.write_text(
                    f"import sys\nimport {pkg}\n"
                    f"sys.exit({pkg}.{func}())\n"
                )

            dest_pyz = Path(target)
            zipapp.create_archive(
                str(stage),
                target=str(dest_pyz),
                interpreter=self._interpreter,
                compressed=self._compressed,
            )
            return dest_pyz
        finally:
            if ctx is not None:
                ctx.cleanup()


# ─────────────────────────────────────────────────────────────────────────────
# 4. Repack with different settings
# ─────────────────────────────────────────────────────────────────────────────

def repack_pyz(
    source_pyz: "str | Path",
    target: "str | Path",
    interpreter: "str | None" = None,
    compressed: bool = True,
) -> Path:
    """
    Repack an existing .pyz with a new interpreter or compression setting.
    Useful for cross-platform redistribution (strip shebang for Windows).

    Example:
        repack_pyz("dist/app.pyz", "dist/app-win.pyz", interpreter=None)
    """
    import tempfile
    src = Path(source_pyz)
    dest = Path(target)

    with tempfile.TemporaryDirectory() as td:
        stage = Path(td) / "stage"
        stage.mkdir()
        with zipfile.ZipFile(src) as zf:
            zf.extractall(stage)

        effective_interp = (
            interpreter
            if interpreter is not None
            else zipapp.get_interpreter(str(src))
        )

        zipapp.create_archive(
            str(stage),
            target=str(dest),
            interpreter=effective_interp,
            compressed=compressed,
        )

    return dest


# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    import subprocess
    import tempfile

    print("=== zipapp demo ===")

    with tempfile.TemporaryDirectory() as td:
        td_path = Path(td)

        # ── create a minimal app directory ────────────────────────────────────
        app_dir = td_path / "myapp"
        app_dir.mkdir()
        (app_dir / "__main__.py").write_text(
            "import sys\n"
            "from myapp import greet\n"
            "name = sys.argv[1] if len(sys.argv) > 1 else 'World'\n"
            "print(greet(name))\n"
        )
        pkg = app_dir / "myapp"
        pkg.mkdir()
        (pkg / "__init__.py").write_text(
            "def greet(name: str) -> str:\n"
            "    return f'Hello from pyz, {name}!'\n"
        )

        # ── build_pyz ─────────────────────────────────────────────────────────
        print("\n--- build_pyz ---")
        pyz = build_pyz(app_dir, target=td_path / "myapp.pyz", compressed=True)
        print(f"  built: {pyz.name}  size={pyz.stat().st_size} bytes")

        # ── inspect_pyz ───────────────────────────────────────────────────────
        print("\n--- inspect_pyz ---")
        info = inspect_pyz(pyz)
        print(f"  {info}")
        for f in info.files:
            print(f"    {f}")

        # ── read_pyz_file ─────────────────────────────────────────────────────
        print("\n--- read_pyz_file ---")
        main_src = read_pyz_file(pyz, "__main__.py")
        print(f"  __main__.py:\n    {main_src.decode().strip()[:80]!r}")

        # ── run the pyz ───────────────────────────────────────────────────────
        print("\n--- run pyz ---")
        proc = subprocess.run(
            [sys.executable, str(pyz), "Claude"],
            capture_output=True, text=True
        )
        print(f"  stdout: {proc.stdout.strip()!r}")

        # ── PyzBundler ────────────────────────────────────────────────────────
        print("\n--- PyzBundler ---")
        bundler = PyzBundler()
        bundler.add_directory(pkg, prefix="myapp")
        bundler.set_main("myapp:greet")
        bundler.set_interpreter("/usr/bin/env python3")
        bundler2 = td_path / "bundled.pyz"
        bundler.build(bundler2)
        info2 = inspect_pyz(bundler2)
        print(f"  {info2}")

        # ── repack_pyz (strip shebang) ────────────────────────────────────────
        print("\n--- repack_pyz ---")
        repacked = repack_pyz(pyz, td_path / "myapp-nointerp.pyz", interpreter=None)
        info3 = inspect_pyz(repacked)
        print(f"  interpreter: {info3.interpreter!r}")
        print(f"  size: {info3.size_bytes} bytes")

    print("\n=== done ===")

For the shiv / pex (PyPI) alternative — shiv and pex embed a virtualenv with third-party packages inside a zip so the bundle is fully self-contained — use shiv or pex when your application depends on packages not in the stdlib (e.g., requests, fastapi); use zipapp for stdlib-only tools where a single-file distribution is needed without the overhead of bundling a virtualenv. For the PyInstaller / Nuitka alternative — PyInstaller and Nuitka compile Python to a native executable with the interpreter embedded — use these when you need a true binary with no Python runtime requirement; use zipapp when the deployment target has Python installed and you want a simple, transparent, inspect-able zip that zipfile.ZipFile can open. The Claude Skills 360 bundle includes zipapp skill sets covering PyzInfo + inspect_pyz() archive inspector, read_pyz_file() content reader, build_pyz() with executable/compressed/filter_fn options, exclude_tests_and_cache() standard filter, PyzBundler with add_directory()/add_file()/set_main()/build() staged assembly, and repack_pyz() for interpreter/compression reconfiguration. Start with the free tier to try executable archive patterns and zipapp pipeline code generation.

Keep Reading

AI

Claude Code for email.contentmanager: Python Email Content Accessors

Read and write EmailMessage body content with Python's email.contentmanager module and Claude Code — email contentmanager ContentManager for the class that maps content types to get and set handler functions allowing EmailMessage to support get_content and set_content with type-specific behaviour, email contentmanager raw_data_manager for the ContentManager instance that handles raw bytes and str payloads without any conversion, email contentmanager content_manager for the standard ContentManager instance used by email.policy.default that intelligently handles text plain text html multipart and binary content types, email contentmanager get_content_text for the handler that returns the decoded text payload of a text-star message part as a str, email contentmanager get_content_binary for the handler that returns the raw decoded bytes payload of a non-text message part, email contentmanager get_data_manager for the get-handler lookup used by EmailMessage get_content to find the right reader function for the content type, email contentmanager set_content text for the handler that creates and sets a text part correctly choosing charset and transfer encoding, email contentmanager set_content bytes for the handler that creates and sets a binary part with base64 encoding and optional filename Content-Disposition, email contentmanager EmailMessage get_content for the method that reads the message body using the registered content manager handlers, email contentmanager EmailMessage set_content for the method that sets the message body and MIME headers in one call, email contentmanager EmailMessage make_alternative make_mixed make_related for the methods that convert a simple message into a multipart container, email contentmanager EmailMessage add_attachment for the method that attaches a file or bytes to a multipart message, and email contentmanager integration with email.message and email.policy and email.mime and io for building high-level email readers attachment extractors text body accessors HTML readers and policy-aware MIME construction pipelines.

5 min read Feb 12, 2029
AI

Claude Code for email.charset: Python Email Charset Encoding

Control header and body encoding for international email with Python's email.charset module and Claude Code — email charset Charset for the class that wraps a character set name with the encoding rules for header encoding and body encoding describing how to encode text for that charset in email messages, email charset Charset header_encoding for the attribute specifying whether headers using this charset should use QP quoted-printable encoding BASE64 encoding or no encoding, email charset Charset body_encoding for the attribute specifying the Content-Transfer-Encoding to use for message bodies in this charset such as QP or BASE64, email charset Charset output_codec for the attribute giving the Python codec name used to encode the string to bytes for the wire format, email charset Charset input_codec for the attribute giving the Python codec name used to decode incoming bytes to str, email charset Charset get_output_charset for returning the output charset name, email charset Charset header_encode for encoding a header string using the charset's header_encoding method, email charset Charset body_encode for encoding body content using the charset's body_encoding, email charset Charset convert for converting a string from the input_codec to the output_codec, email charset add_charset for registering a new charset with custom encoding rules in the global charset registry, email charset add_alias for adding an alias name that maps to an existing registered charset, email charset add_codec for registering a codec name mapping for use by the charset machinery, and email charset integration with email.message and email.mime and email.policy and email.encoders for building international email senders non-ASCII header encoders Content-Transfer-Encoding selectors charset-aware message constructors and MIME encoding pipelines.

5 min read Feb 11, 2029
AI

Claude Code for email.utils: Python Email Address and Header Utilities

Parse and format RFC 2822 email addresses and dates with Python's email.utils module and Claude Code — email utils parseaddr for splitting a display-name plus angle-bracket address string into a realname and email address tuple, email utils formataddr for combining a realname and address string into a properly quoted RFC 2822 address with angle brackets, email utils getaddresses for parsing a list of raw address header strings each potentially containing multiple comma-separated addresses into a list of realname address tuples, email utils parsedate for parsing an RFC 2822 date string into a nine-tuple compatible with time.mktime, email utils parsedate_tz for parsing an RFC 2822 date string into a ten-tuple that includes the UTC offset timezone in seconds, email utils parsedate_to_datetime for parsing an RFC 2822 date string into an aware datetime object with timezone, email utils formatdate for formatting a POSIX timestamp or the current time as an RFC 2822 date string with optional usegmt and localtime flags, email utils format_datetime for formatting a datetime object as an RFC 2822 date string, email utils make_msgid for generating a globally unique Message-ID string with optional idstring and domain components, email utils decode_rfc2231 for decoding an RFC 2231 encoded parameter value into a tuple of charset language and value, email utils encode_rfc2231 for encoding a string as an RFC 2231 encoded parameter value, email utils collapse_rfc2231_value for collapsing a decoded RFC 2231 tuple to a Unicode string, and email utils integration with email.message and email.headerregistry and datetime and time for building address parsers date formatters message-id generators header extractors and RFC-compliant email construction utilities.

5 min read Feb 10, 2029

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free