Claude Code for plistlib: Python Property List Files — Claude Skills 360 Blog
Blog / AI / Claude Code for plistlib: Python Property List Files
AI

Claude Code for plistlib: Python Property List Files

Published: September 27, 2028
Read time: 5 min read
By: Claude Skills 360

Python’s plistlib module reads and writes Apple property list (.plist) files in XML and binary formats. import plistlib. load: with open("Info.plist", "rb") as f: data = plistlib.load(f) — auto-detects XML or binary. loads: plistlib.loads(bytes_data) — parse from bytes. dump: with open("out.plist", "wb") as f: plistlib.dump(obj, f, fmt=plistlib.FMT_XML). dumps: plistlib.dumps(obj, fmt=plistlib.FMT_BINARY) → bytes. FMT_XML: produces <?xml version="1.0"...><plist version="1.0">...</plist> — human-readable. FMT_BINARY: produces compact bplist00 binary — used by macOS Preferences subsystem, iOS bundles. Type mapping: dict<dict>, list/tuple<array>, str<string>, int<integer>, float<real>, bool<true/false>, bytes/bytearray<data>, datetime.datetime<date>, plistlib.UID(n) → UID (NSKeyedArchiver). sort_keys: dumps(obj, sort_keys=True) — stable XML output. skipkeys=False (default, raises on non-string keys). InvalidFileException raised for corrupt files. Claude Code generates macOS app config readers, Info.plist bundlers, iOS preferences file parsers, and NSKeyedArchiver UID utilities.

CLAUDE.md for plistlib

## plistlib Stack
- Stdlib:  import plistlib
- Read:    with open("Info.plist", "rb") as f: data = plistlib.load(f)
- Parse:   data = plistlib.loads(raw_bytes)
- Write:   plistlib.dump(obj, f, fmt=plistlib.FMT_XML)
- Bytes:   xml_bytes  = plistlib.dumps(obj, fmt=plistlib.FMT_XML)
           bin_bytes  = plistlib.dumps(obj, fmt=plistlib.FMT_BINARY)
- UID:     plistlib.UID(42)  # for NSKeyedArchiver plists

plistlib Property List Pipeline

# app/plistutil.py — read, write, convert, merge, validate, diff
from __future__ import annotations

import datetime
import plistlib
from dataclasses import dataclass
from pathlib import Path
from typing import Any


# ─────────────────────────────────────────────────────────────────────────────
# 1. Read helpers
# ─────────────────────────────────────────────────────────────────────────────

def read_plist(path: str | Path) -> Any:
    """
    Read a plist file (XML or binary, auto-detected).

    Example:
        data = read_plist("Info.plist")
        print(data["CFBundleIdentifier"])
    """
    with Path(path).open("rb") as f:
        return plistlib.load(f)


def parse_plist(data: bytes) -> Any:
    """
    Parse plist bytes (XML or binary, auto-detected).

    Example:
        obj = parse_plist(response.content)
    """
    return plistlib.loads(data)


def read_plist_safe(path: str | Path, default: Any = None) -> Any:
    """
    Read a plist file, returning default on error.

    Example:
        prefs = read_plist_safe("~/.config/app.plist", {})
    """
    try:
        return read_plist(path)
    except (plistlib.InvalidFileException, OSError, ValueError):
        return default


# ─────────────────────────────────────────────────────────────────────────────
# 2. Write helpers
# ─────────────────────────────────────────────────────────────────────────────

def write_plist(
    obj: Any,
    path: str | Path,
    fmt: int = plistlib.FMT_XML,
    sort_keys: bool = True,
) -> None:
    """
    Write a Python object to a plist file.

    fmt: plistlib.FMT_XML (default) or plistlib.FMT_BINARY
    sort_keys: produce stable XML output (default True).

    Example:
        write_plist({"CFBundleIdentifier": "com.example.app"}, "Info.plist")
        write_plist({"token": "abc"}, "prefs.plist", fmt=plistlib.FMT_BINARY)
    """
    with Path(path).open("wb") as f:
        plistlib.dump(obj, f, fmt=fmt, sort_keys=sort_keys)


def to_xml_bytes(obj: Any, sort_keys: bool = True) -> bytes:
    """
    Serialize to XML plist bytes.

    Example:
        xml = to_xml_bytes({"key": "value"})
        print(xml.decode())
    """
    return plistlib.dumps(obj, fmt=plistlib.FMT_XML, sort_keys=sort_keys)


def to_binary_bytes(obj: Any) -> bytes:
    """
    Serialize to binary bplist00 bytes.

    Example:
        blob = to_binary_bytes({"token": "secret", "ts": 1234567890})
    """
    return plistlib.dumps(obj, fmt=plistlib.FMT_BINARY)


def convert_plist(
    src: str | Path,
    dst: str | Path,
    fmt: int = plistlib.FMT_BINARY,
) -> None:
    """
    Convert a plist file between XML and binary formats.

    Example:
        convert_plist("prefs.xml.plist", "prefs.binary.plist", plistlib.FMT_BINARY)
        convert_plist("archive.bplist",  "archive.xml.plist",  plistlib.FMT_XML)
    """
    obj = read_plist(src)
    write_plist(obj, dst, fmt=fmt)


# ─────────────────────────────────────────────────────────────────────────────
# 3. Merge and update helpers
# ─────────────────────────────────────────────────────────────────────────────

def merge_plists(*sources: str | Path, output: str | Path,
                 fmt: int = plistlib.FMT_XML) -> dict:
    """
    Deep-merge multiple plist files (left-wins on key collision).
    Returns the merged dict and optionally writes it to output.

    Example:
        merged = merge_plists("base.plist", "override.plist", output="merged.plist")
    """
    result: dict = {}
    for src in sources:
        data = read_plist(src)
        if isinstance(data, dict):
            result.update(data)
    write_plist(result, output, fmt=fmt)
    return result


def plist_set(path: str | Path, key: str, value: Any,
              fmt: int | None = None) -> None:
    """
    Set or update a single key in a plist file (creates if missing).
    Preserves existing format unless fmt is specified.

    Example:
        plist_set("prefs.plist", "DarkMode", True)
        plist_set("config.plist", "MaxRetries", 5)
    """
    p = Path(path)
    if p.exists():
        data = read_plist(p)
        if not isinstance(data, dict):
            data = {}
    else:
        data = {}
    data[key] = value
    # Detect format from existing file if not specified
    if fmt is None:
        fmt = _detect_fmt(p)
    write_plist(data, p, fmt=fmt)


def _detect_fmt(path: Path) -> int:
    """Detect whether an existing plist is binary or XML."""
    try:
        with path.open("rb") as f:
            magic = f.read(8)
        if magic.startswith(b"bplist"):
            return plistlib.FMT_BINARY
    except OSError:
        pass
    return plistlib.FMT_XML


def plist_get(path: str | Path, key: str, default: Any = None) -> Any:
    """
    Read a single key from a plist file.

    Example:
        debug_mode = plist_get("config.plist", "Debug", False)
    """
    data = read_plist_safe(path, {})
    if isinstance(data, dict):
        return data.get(key, default)
    return default


# ─────────────────────────────────────────────────────────────────────────────
# 4. Validation and diff
# ─────────────────────────────────────────────────────────────────────────────

_PLIST_VALID_TYPES = (
    dict, list, tuple, str, int, float, bool,
    bytes, bytearray, datetime.datetime, plistlib.UID,
)


def validate_plist_types(obj: Any, path: str = "") -> list[str]:
    """
    Walk obj and return error strings for any value whose type is not
    supported by plistlib. Returns [] if the object is fully valid.

    Example:
        errors = validate_plist_types({"key": {1: "bad_int_key"}})
        for e in errors:
            print(e)
    """
    errors: list[str] = []
    _validate_recursive(obj, path, errors)
    return errors


def _validate_recursive(obj: Any, path: str, errors: list[str]) -> None:
    if isinstance(obj, dict):
        for k, v in obj.items():
            if not isinstance(k, str):
                errors.append(f"{path}: dict key {k!r} is not a str")
            _validate_recursive(v, f"{path}.{k}" if path else k, errors)
    elif isinstance(obj, (list, tuple)):
        for i, v in enumerate(obj):
            _validate_recursive(v, f"{path}[{i}]", errors)
    elif not isinstance(obj, _PLIST_VALID_TYPES):
        errors.append(f"{path}: unsupported type {type(obj).__name__!r} ({obj!r})")


@dataclass
class PlistDiffEntry:
    key_path: str
    left:     Any
    right:    Any

    def __str__(self) -> str:
        return f"  {self.key_path}: {self.left!r}{self.right!r}"


def diff_plists(a: Any, b: Any, path: str = "") -> list[PlistDiffEntry]:
    """
    Recursively diff two plist objects (typically dicts).
    Returns a list of differing entries.

    Example:
        diffs = diff_plists(read_plist("old.plist"), read_plist("new.plist"))
        for d in diffs:
            print(d)
    """
    diffs: list[PlistDiffEntry] = []
    _diff_recursive(a, b, path, diffs)
    return diffs


def _diff_recursive(a: Any, b: Any, path: str, out: list[PlistDiffEntry]) -> None:
    if type(a) != type(b):
        out.append(PlistDiffEntry(path, a, b))
        return
    if isinstance(a, dict):
        all_keys = set(a) | set(b)
        for k in sorted(all_keys):
            child = f"{path}.{k}" if path else k
            if k not in a:
                out.append(PlistDiffEntry(child, None, b[k]))
            elif k not in b:
                out.append(PlistDiffEntry(child, a[k], None))
            else:
                _diff_recursive(a[k], b[k], child, out)
    elif isinstance(a, (list, tuple)):
        for i, (av, bv) in enumerate(zip(a, b)):
            _diff_recursive(av, bv, f"{path}[{i}]", out)
        if len(a) != len(b):
            out.append(PlistDiffEntry(f"{path}[len]", len(a), len(b)))
    elif a != b:
        out.append(PlistDiffEntry(path, a, b))


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

if __name__ == "__main__":
    import tempfile

    print("=== plistlib demo ===")

    info_plist_obj = {
        "CFBundleIdentifier":   "com.example.myapp",
        "CFBundleVersion":      "1.0.0",
        "CFBundleDisplayName":  "My App",
        "LSMinimumSystemVersion": "13.0",
        "NSHighResolutionCapable": True,
        "BuildDate":            datetime.datetime(2028, 9, 27, 12, 0, 0),
        "Frameworks":           ["Foundation", "AppKit"],
        "Metadata":             {"Author": "Claude", "License": "MIT"},
    }

    with tempfile.TemporaryDirectory() as tmpdir:
        tmp = Path(tmpdir)

        # ── write XML and binary ───────────────────────────────────────────────
        print("\n--- write XML and binary plists ---")
        xml_path = tmp / "Info.plist"
        bin_path = tmp / "Info.binary.plist"
        write_plist(info_plist_obj, xml_path, fmt=plistlib.FMT_XML)
        write_plist(info_plist_obj, bin_path, fmt=plistlib.FMT_BINARY)
        print(f"  XML:    {xml_path.stat().st_size:,d} bytes")
        print(f"  Binary: {bin_path.stat().st_size:,d} bytes")

        # ── read back ──────────────────────────────────────────────────────────
        print("\n--- read_plist ---")
        loaded = read_plist(xml_path)
        print(f"  CFBundleIdentifier: {loaded['CFBundleIdentifier']!r}")
        print(f"  BuildDate: {loaded['BuildDate']}")
        print(f"  Frameworks: {loaded['Frameworks']}")

        # ── to_xml_bytes (inspect first 120 chars) ─────────────────────────────
        print("\n--- to_xml_bytes snippet ---")
        xml_bytes = to_xml_bytes({"key": "value", "count": 42})
        print(f"  {xml_bytes[:120].decode()!r}")

        # ── plist_get / plist_set ──────────────────────────────────────────────
        print("\n--- plist_get / plist_set ---")
        print(f"  CFBundleVersion: {plist_get(xml_path, 'CFBundleVersion')!r}")
        plist_set(xml_path, "Debug", True)
        print(f"  after set Debug: {plist_get(xml_path, 'Debug')!r}")

        # ── convert format ─────────────────────────────────────────────────────
        print("\n--- convert_plist ---")
        converted = tmp / "converted.plist"
        convert_plist(xml_path, converted, plistlib.FMT_BINARY)
        data = read_plist(converted)
        print(f"  converted has CFBundleIdentifier: {data['CFBundleIdentifier']!r}")

        # ── validate ───────────────────────────────────────────────────────────
        print("\n--- validate_plist_types ---")
        good_errors = validate_plist_types(info_plist_obj)
        bad_obj = {"ok": "yes", "bad_key": {1: "int key"}, "set_val": {1, 2}}
        bad_errors = validate_plist_types(bad_obj)
        print(f"  valid object errors: {good_errors}")
        print(f"  invalid object errors:")
        for e in bad_errors:
            print(f"    {e}")

        # ── diff ───────────────────────────────────────────────────────────────
        print("\n--- diff_plists ---")
        v1 = {"CFBundleVersion": "1.0.0", "Debug": False, "MaxRetries": 3}
        v2 = {"CFBundleVersion": "1.1.0", "Debug": False, "MaxRetries": 5, "NewKey": "hello"}
        diffs = diff_plists(v1, v2)
        for d in diffs:
            print(d)

        # ── UID type ───────────────────────────────────────────────────────────
        print("\n--- UID ---")
        uid_obj = {"$uid": plistlib.UID(42)}
        uid_bytes = to_binary_bytes(uid_obj)
        back = parse_plist(uid_bytes)
        print(f"  UID round-trip: {back['$uid']} (type={type(back['$uid']).__name__})")

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

For the xml.etree.ElementTree alternative — xml.etree.ElementTree (stdlib) can parse plist XML directly as generic XML and lets you walk the tree manually, giving full control over handling malformed or extended plist dialects — use ElementTree when you need to process non-standard plist XML that plistlib rejects, or when you’re building a plist parser for a target format that extends Apple’s schema; use plistlib for standard Apple plist files because it handles both XML and binary formats, maps plist types to Python types automatically, and is much more concise. For the biplist / plistlib (C extension) alternative — biplist (PyPI) is a third-party pure-Python binary plist reader/writer that handles edge cases in binary plists (UID arrays, offset table sizes >4 bytes) that older versions of plistlib could not; since Python 3.4 plistlib handles all standard binary plist features natively — use plistlib for all standard macOS/iOS plists; fall back to biplist only if you encounter binary plists from unusual sources that fail to parse with plistlib.InvalidFileException. The Claude Skills 360 bundle includes plistlib skill sets covering read_plist()/parse_plist()/read_plist_safe() readers, write_plist()/to_xml_bytes()/to_binary_bytes()/convert_plist() writers, merge_plists()/plist_set()/plist_get() dict update helpers, validate_plist_types() type checker, and PlistDiffEntry dataclass with diff_plists() recursive differ. Start with the free tier to try property list patterns and plistlib 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