Claude Code for runpy: Python Module Execution as Scripts — Claude Skills 360 Blog
Blog / AI / Claude Code for runpy: Python Module Execution as Scripts
AI

Claude Code for runpy: Python Module Execution as Scripts

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

Python’s runpy module runs a named module or a path as a top-level __main__ script — the same mechanism used by python -m mod. import runpy. Run by name: runpy.run_module(mod_name, run_name="__main__", alter_sys=False, init_globals=None)dict — locates mod_name on sys.path, executes it, returns the resulting globals dict. Run by path: runpy.run_path(path_name, run_name="__main__", init_globals=None)dict — accepts a .py file, a directory with __main__.py, or a zip archive with __main__.py; does NOT permanently alter sys.path. Parameters: run_name — overrides __name__ inside the module (default "__main__"); alter_sys — if True, temporarily sets sys.argv[0] to the module’s file and adjusts sys.modules; init_globals — dict of extra names injected into the namespace before execution. Return value: the final __dict__ of the module (all names defined or imported during execution). Both functions isolate execution: the module runs in its own namespace, not the caller’s. Claude Code generates in-process script runners, isolated namespace executors, plugin sandboxes, and test harnesses.

CLAUDE.md for runpy

## runpy Stack
- Stdlib: import runpy
- By name:  ns = runpy.run_module("mypackage.script", run_name="__main__")
- By path:  ns = runpy.run_path("scripts/deploy.py")
- Inject:   ns = runpy.run_path("tool.py", init_globals={"CONFIG": cfg})
- alter_sys: runpy.run_module("mod", alter_sys=True)  # updates sys.argv[0]
- Note:    returns the module __dict__; raises SystemExit if script calls sys.exit()

runpy Script Execution Pipeline

# app/runpyutil.py — in-process runner, namespace capture, plugin sandbox, harness
from __future__ import annotations

import runpy
import sys
import traceback
from contextlib import contextmanager
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any


# ─────────────────────────────────────────────────────────────────────────────
# 1. Simple run helpers
# ─────────────────────────────────────────────────────────────────────────────

def run_module(
    mod_name: str,
    init_globals: "dict[str, Any] | None" = None,
    run_name: str = "__main__",
    alter_sys: bool = False,
) -> "dict[str, Any]":
    """
    Run a module by name, returning its namespace dict.
    Propagates SystemExit; wraps other exceptions in RuntimeError.

    Example:
        ns = run_module("http.server")  # starts server; intercepted by SystemExit
    """
    return runpy.run_module(
        mod_name,
        init_globals=init_globals,
        run_name=run_name,
        alter_sys=alter_sys,
    )


def run_path(
    path: "str | Path",
    init_globals: "dict[str, Any] | None" = None,
    run_name: str = "__main__",
) -> "dict[str, Any]":
    """
    Run a .py file (or directory/__main__.py or zip/__main__.py) by path.
    Returns the resulting namespace dict.

    Example:
        ns = run_path("scripts/compute.py", init_globals={"N": 100})
        print(ns.get("result"))
    """
    return runpy.run_path(str(path), init_globals=init_globals, run_name=run_name)


# ─────────────────────────────────────────────────────────────────────────────
# 2. Captured run — catch exceptions and SystemExit
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class RunResult:
    """
    Result of a captured script/module execution.

    Example:
        result = safe_run_path("scripts/build.py")
        if result.ok:
            print(result.namespace.get("OUTPUT"))
        else:
            print(f"Error: {result.error}")
    """
    namespace:  "dict[str, Any]"
    ok:         bool
    exit_code:  "int | None"    # set when SystemExit was raised
    error:      str             # formatted traceback / exception message
    exc:        "BaseException | None"

    def __str__(self) -> str:
        if self.ok:
            return f"RunResult(ok, vars={list(self.namespace.keys())[:5]})"
        if self.exit_code is not None:
            return f"RunResult(exit={self.exit_code})"
        return f"RunResult(error={self.error[:80]!r})"


def safe_run_path(
    path: "str | Path",
    init_globals: "dict[str, Any] | None" = None,
    run_name: str = "__main__",
) -> RunResult:
    """
    Run a script by path, capturing exceptions and SystemExit.

    Example:
        result = safe_run_path("scripts/report.py", init_globals={"DATE": "2026-01"})
        if not result.ok:
            print(result.error)
    """
    try:
        ns = runpy.run_path(str(path), init_globals=init_globals, run_name=run_name)
        return RunResult(namespace=ns, ok=True, exit_code=None, error="", exc=None)
    except SystemExit as e:
        code = e.code if isinstance(e.code, int) else (0 if e.code is None else 1)
        return RunResult(namespace={}, ok=(code == 0), exit_code=code,
                         error=str(e), exc=e)
    except Exception as e:
        tb = traceback.format_exc()
        return RunResult(namespace={}, ok=False, exit_code=None, error=tb, exc=e)


def safe_run_module(
    mod_name: str,
    init_globals: "dict[str, Any] | None" = None,
    run_name: str = "__main__",
) -> RunResult:
    """
    Run a module by name, capturing exceptions and SystemExit.

    Example:
        result = safe_run_module("myapp.cli", init_globals={"DRYRUN": True})
    """
    try:
        ns = runpy.run_module(mod_name, init_globals=init_globals, run_name=run_name)
        return RunResult(namespace=ns, ok=True, exit_code=None, error="", exc=None)
    except SystemExit as e:
        code = e.code if isinstance(e.code, int) else (0 if e.code is None else 1)
        return RunResult(namespace={}, ok=(code == 0), exit_code=code,
                         error=str(e), exc=e)
    except Exception as e:
        tb = traceback.format_exc()
        return RunResult(namespace={}, ok=False, exit_code=None, error=tb, exc=e)


# ─────────────────────────────────────────────────────────────────────────────
# 3. Namespace extractor
# ─────────────────────────────────────────────────────────────────────────────

def extract_globals(
    path: "str | Path",
    names: "list[str]",
    init_globals: "dict[str, Any] | None" = None,
) -> "dict[str, Any]":
    """
    Run a script and extract only the named variables from its namespace.
    Returns {name: value} for names that exist; absent names are omitted.

    Example:
        vals = extract_globals("config.py", ["DB_URL", "DEBUG", "SECRET_KEY"])
        print(vals)
    """
    result = safe_run_path(path, init_globals=init_globals)
    if not result.ok and result.exit_code != 0:
        raise RuntimeError(f"Script failed: {result.error}")
    return {k: result.namespace[k] for k in names if k in result.namespace}


# ─────────────────────────────────────────────────────────────────────────────
# 4. Plugin sandbox
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class PluginResult:
    """
    Result of loading and running a plugin script.

    Plugins can expose a dict named EXPORTS or any top-level callable.
    """
    path:     Path
    ok:       bool
    exports:  "dict[str, Any]"
    error:    str

    def get(self, name: str, default: Any = None) -> Any:
        return self.exports.get(name, default)


def load_plugin(
    plugin_path: "str | Path",
    api: "dict[str, Any] | None" = None,
) -> PluginResult:
    """
    Load a plugin script via runpy.run_path, injecting api as init_globals.
    Plugins signal their exports via an EXPORTS dict in their namespace.

    Example:
        result = load_plugin("plugins/report.py", api={"db": db_conn})
        if result.ok:
            fn = result.exports.get("generate")
            if callable(fn):
                fn()
    """
    init = dict(api or {})
    result = safe_run_path(plugin_path, init_globals=init)
    if not result.ok:
        return PluginResult(
            path=Path(plugin_path), ok=False, exports={}, error=result.error
        )
    exports = result.namespace.get("EXPORTS", {})
    if not isinstance(exports, dict):
        exports = {}
    # Also expose any top-level callables defined by the plugin
    for k, v in result.namespace.items():
        if callable(v) and not k.startswith("_") and k not in exports:
            exports[k] = v
    return PluginResult(path=Path(plugin_path), ok=True, exports=exports, error="")


def load_plugins_from_dir(
    directory: "str | Path",
    api: "dict[str, Any] | None" = None,
    pattern: str = "plugin_*.py",
) -> list[PluginResult]:
    """
    Load all plugin scripts matching pattern from directory.

    Example:
        plugins = load_plugins_from_dir("extensions/", api={"registry": reg})
        for p in plugins:
            if p.ok:
                print(f"  {p.path.name}: exports={list(p.exports.keys())}")
    """
    results = []
    for py_file in sorted(Path(directory).glob(pattern)):
        results.append(load_plugin(py_file, api=api))
    return results


# ─────────────────────────────────────────────────────────────────────────────
# 5. Test harness helper
# ─────────────────────────────────────────────────────────────────────────────

@contextmanager
def patched_argv(argv: list[str]):
    """
    Context manager that temporarily replaces sys.argv.
    Useful when running scripts that parse argv.

    Example:
        with patched_argv(["script.py", "--verbose", "--output", "out.txt"]):
            ns = runpy.run_path("script.py")
    """
    original = sys.argv[:]
    sys.argv[:] = argv
    try:
        yield
    finally:
        sys.argv[:] = original


def run_as_main(
    path: "str | Path",
    argv: "list[str] | None" = None,
    init_globals: "dict[str, Any] | None" = None,
) -> RunResult:
    """
    Run a script as __main__ with optional argv substitution.

    Example:
        result = run_as_main("tools/gen.py", argv=["gen.py", "--count", "10"])
        print(result.namespace.get("OUTPUT"))
    """
    effective_argv = argv or [str(path)]
    with patched_argv(effective_argv):
        return safe_run_path(path, init_globals=init_globals, run_name="__main__")


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

if __name__ == "__main__":
    import tempfile

    print("=== runpy demo ===")

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

        # ── write test scripts ─────────────────────────────────────────────────
        compute = td_path / "compute.py"
        compute.write_text(
            "import math\n"
            "N = N if 'N' in dir() else 10\n"  # uses injected N if present
            "result = sum(math.sqrt(i) for i in range(1, N + 1))\n"
        )

        config = td_path / "config.py"
        config.write_text(
            "DB_URL = 'sqlite:///app.db'\n"
            "DEBUG = True\n"
            "SECRET_KEY = 'dev-secret'\n"
        )

        failing = td_path / "failing.py"
        failing.write_text(
            "import sys\n"
            "raise RuntimeError('intentional failure')\n"
        )

        exiting = td_path / "exiting.py"
        exiting.write_text(
            "import sys\n"
            "sys.exit(2)\n"
        )

        plugin_a = td_path / "plugin_alpha.py"
        plugin_a.write_text(
            "def transform(data):\n    return [x * 2 for x in data]\n"
            "EXPORTS = {'transform': transform, 'version': '1.0'}\n"
        )
        plugin_b = td_path / "plugin_beta.py"
        plugin_b.write_text(
            "def transform(data):\n    return sorted(data, reverse=True)\n"
            "EXPORTS = {'transform': transform, 'version': '2.0'}\n"
        )

        # ── run_path with inject ───────────────────────────────────────────────
        print("\n--- run_path with init_globals ---")
        ns = run_path(compute, init_globals={"N": 5})
        print(f"  result (N=5): {ns['result']:.4f}")
        ns2 = run_path(compute)
        print(f"  result (default N=10): {ns2['result']:.4f}")

        # ── extract_globals ───────────────────────────────────────────────────
        print("\n--- extract_globals ---")
        cfg = extract_globals(config, ["DB_URL", "DEBUG", "SECRET_KEY", "MISSING"])
        for k, v in cfg.items():
            print(f"  {k} = {v!r}")

        # ── safe_run_path (exception) ─────────────────────────────────────────
        print("\n--- safe_run_path (exception) ---")
        r = safe_run_path(failing)
        print(f"  ok={r.ok}  exit_code={r.exit_code}")
        print(f"  error snippet: {r.error.splitlines()[-1]!r}")

        # ── safe_run_path (SystemExit) ────────────────────────────────────────
        print("\n--- safe_run_path (SystemExit) ---")
        r2 = safe_run_path(exiting)
        print(f"  ok={r2.ok}  exit_code={r2.exit_code}")

        # ── load_plugins_from_dir ─────────────────────────────────────────────
        print("\n--- load_plugins_from_dir ---")
        plugins = load_plugins_from_dir(td_path)
        for p in plugins:
            if p.ok:
                fn = p.exports.get("transform")
                out = fn([3, 1, 4, 1, 5]) if callable(fn) else None
                print(f"  {p.path.name}: version={p.exports.get('version')}  "
                      f"transform([3,1,4,1,5])={out}")

        # ── run_as_main with argv ─────────────────────────────────────────────
        print("\n--- run_as_main with custom argv ---")
        argv_script = td_path / "printargs.py"
        argv_script.write_text(
            "import sys\n"
            "args = sys.argv[1:]\n"
        )
        r3 = run_as_main(argv_script, argv=["printargs.py", "--foo", "bar"])
        print(f"  args captured: {r3.namespace.get('args')}")

        # ── run_module (stdlib) ───────────────────────────────────────────────
        print("\n--- run_module (json.tool help check) ---")
        r4 = safe_run_module("json.tool", init_globals={})
        # json.tool exits with error if no input; just check it ran
        print(f"  json.tool: ok={r4.ok}  exit_code={r4.exit_code}")

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

For the exec() built-in alternative — exec(compile(source, filename, "exec"), namespace) runs compiled source in a provided dict — use exec when you already have source text or a code object and want direct namespace control without involving the import machinery; use runpy.run_path when you want Python’s standard import-path rules (including __file__, __spec__, __loader__ setup) and zip/package support. For the subprocess.run(["python", "script.py"]) alternative — subprocess.run executes a script in a completely isolated child process with its own interpreter state — use subprocess when you need strict isolation, separate stdout/stderr capture, or cross-interpreter compatibility; use runpy when you want in-process execution and access to the script’s namespace dict without spawning a process. The Claude Skills 360 bundle includes runpy skill sets covering run_module()/run_path() thin wrappers, RunResult with ok/exit_code/error/namespace fields and safe_run_path()/safe_run_module() exception-catching runners, extract_globals() selective namespace reader, PluginResult + load_plugin()/load_plugins_from_dir() plugin sandbox, patched_argv() context manager, and run_as_main() with argv injection. Start with the free tier to try in-process script execution patterns and runpy 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