Claude Code for netrc: Python .netrc Credential Parser — Claude Skills 360 Blog
Blog / AI / Claude Code for netrc: Python .netrc Credential Parser
AI

Claude Code for netrc: Python .netrc Credential Parser

Published: November 11, 2028
Read time: 5 min read
By: Claude Skills 360

Python’s netrc module parses the standard Unix ~/.netrc file, which stores per-host login credentials for FTP, HTTP basic auth, and other protocols. import netrc. Parse file: n = netrc.netrc() — reads ~/.netrc; n = netrc.netrc(path) — reads a custom path. Lookup: login, account, password = n.authenticators(hostname) — returns (login, account, password) or None if not found; falls through to the default entry if no machine-specific entry matches. Attributes: n.hosts → dict of {host: (login, account, password)}; n.macros → dict of {macro_name: [lines]}; n.files — file path. Exceptions: netrc.NetrcParseError — raised for syntax errors (bad permissions, unknown tokens). File format: machine host login user password secret — must have 0600 permissions (enforced on Unix). default entry: default login anonymous password [email protected] — catches all unmatched hosts. Security: the file must be readable only by the owner (-rw-------); ~/.netrc is read automatically by ftp, curl, wget, and other tools. Claude Code generates authenticated FTP clients, credential loaders, CI secret readers, and multi-host authentication managers.

CLAUDE.md for netrc

## netrc Stack
- Stdlib: import netrc
- Parse:  n = netrc.netrc()                          # ~/.netrc
-         n = netrc.netrc("/path/to/.netrc")
- Lookup: auth = n.authenticators("ftp.example.com")
-         if auth: login, account, password = auth
- All:    n.hosts   # {host: (login, account, password)}
-         n.macros  # {name: [lines]}
- Note:   File must be chmod 0600; raises NetrcParseError on syntax errors

netrc Credential Manager Pipeline

# app/netrcutil.py — parse, lookup, write, validate, FTP/HTTP auth bridge
from __future__ import annotations

import os
import stat
import tempfile
import textwrap
from dataclasses import dataclass, field
from pathlib import Path

import netrc as _netrc


# ─────────────────────────────────────────────────────────────────────────────
# 1. Core lookup helpers
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class Credential:
    host:     str
    login:    str
    password: str
    account:  str | None = None

    def __str__(self) -> str:
        acct = f"  account={self.account!r}" if self.account else ""
        return f"Credential(host={self.host!r}, login={self.login!r}{acct})"

    def mask_password(self) -> str:
        if not self.password:
            return "(empty)"
        return self.password[:2] + "***" + self.password[-1:]


def load_netrc(path: str | Path | None = None) -> _netrc.netrc | None:
    """
    Load a netrc file. Returns a netrc object or None if not found / unreadable.

    Example:
        n = load_netrc()
        if n: print(n.hosts)
    """
    try:
        if path is None:
            return _netrc.netrc()
        return _netrc.netrc(str(path))
    except (FileNotFoundError, PermissionError):
        return None
    except _netrc.NetrcParseError:
        raise


def lookup(
    hostname: str,
    path: str | Path | None = None,
) -> Credential | None:
    """
    Look up credentials for hostname from ~/.netrc (or custom path).
    Returns None if no match found.

    Example:
        cred = lookup("ftp.example.com")
        if cred:
            print(cred.login, cred.mask_password())
    """
    n = load_netrc(path)
    if n is None:
        return None
    auth = n.authenticators(hostname)
    if auth is None:
        return None
    login, account, password = auth
    return Credential(
        host=hostname,
        login=login or "",
        password=password or "",
        account=account,
    )


def list_credentials(path: str | Path | None = None) -> list[Credential]:
    """
    Return all credentials stored in the netrc file.

    Example:
        for cred in list_credentials():
            print(cred.host, cred.login)
    """
    n = load_netrc(path)
    if n is None:
        return []
    results = []
    for host, (login, account, password) in n.hosts.items():
        results.append(Credential(
            host=host,
            login=login or "",
            password=password or "",
            account=account,
        ))
    return results


# ─────────────────────────────────────────────────────────────────────────────
# 2. Netrc file writer
# ─────────────────────────────────────────────────────────────────────────────

def write_netrc(
    credentials: list[Credential],
    path: str | Path | None = None,
    default: Credential | None = None,
) -> Path:
    """
    Write credentials to a netrc file with correct permissions (0600).
    If path is None, writes to ~/.netrc.
    Returns the path written.

    Example:
        write_netrc([
            Credential("ftp.example.com", "alice", "s3cr3t"),
            Credential("api.example.com", "token", "abc123"),
        ])
    """
    dest = Path(path) if path else Path.home() / ".netrc"
    lines = []
    for cred in credentials:
        lines.append(f"machine {cred.host}")
        lines.append(f"  login {cred.login}")
        lines.append(f"  password {cred.password}")
        if cred.account:
            lines.append(f"  account {cred.account}")
        lines.append("")
    if default:
        lines.append("default")
        lines.append(f"  login {default.login}")
        lines.append(f"  password {default.password}")
        if default.account:
            lines.append(f"  account {default.account}")
        lines.append("")

    dest.write_text("\n".join(lines) + "\n", encoding="utf-8")
    # Set restrictive permissions (owner read/write only)
    dest.chmod(0o600)
    return dest


def add_credential(
    host: str,
    login: str,
    password: str,
    account: str | None = None,
    path: str | Path | None = None,
) -> None:
    """
    Add or update a credential in the netrc file.
    Creates the file if it does not exist.

    Example:
        add_credential("ftp.example.com", "alice", "s3cr3t")
    """
    existing = list_credentials(path)
    # Remove any existing entry for this host
    updated = [c for c in existing if c.host != host]
    updated.append(Credential(host=host, login=login,
                              password=password, account=account))
    write_netrc(updated, path=path)


def remove_credential(host: str, path: str | Path | None = None) -> bool:
    """
    Remove the credential for a host from the netrc file.
    Returns True if a credential was removed, False if not found.

    Example:
        removed = remove_credential("ftp.example.com")
    """
    existing = list_credentials(path)
    updated = [c for c in existing if c.host != host]
    if len(updated) == len(existing):
        return False
    write_netrc(updated, path=path)
    return True


# ─────────────────────────────────────────────────────────────────────────────
# 3. Validation
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class NetrcValidation:
    path:   str
    ok:     bool
    errors: list[str] = field(default_factory=list)
    warnings: list[str] = field(default_factory=list)
    host_count: int = 0

    def __str__(self) -> str:
        status = "OK" if self.ok else "FAIL"
        return (f"[{status}] {self.path}  "
                f"({self.host_count} hosts, "
                f"{len(self.errors)} errors, "
                f"{len(self.warnings)} warnings)")


def validate_netrc(path: str | Path | None = None) -> NetrcValidation:
    """
    Validate a netrc file: parse errors, permission check, empty passwords.

    Example:
        result = validate_netrc()
        print(result)
        for err in result.errors:
            print(f"  ERROR: {err}")
    """
    dest = Path(path) if path else Path.home() / ".netrc"
    v = NetrcValidation(path=str(dest), ok=True)

    if not dest.exists():
        v.errors.append(f"File not found: {dest}")
        v.ok = False
        return v

    # Permission check (Unix only)
    try:
        mode = dest.stat().st_mode
        if mode & stat.S_IRWXG or mode & stat.S_IRWXO:
            v.warnings.append(
                f"Permissions too open: {oct(mode & 0o777)}; recommend 0600"
            )
            if os.name == "posix":
                v.errors.append("File is world/group readable — security risk")
                v.ok = False
    except OSError:
        pass

    # Parse
    try:
        n = _netrc.netrc(str(dest))
        v.host_count = len(n.hosts)
        for host, (login, account, password) in n.hosts.items():
            if not login:
                v.warnings.append(f"Host {host!r} has no login")
            if not password:
                v.warnings.append(f"Host {host!r} has no password")
    except _netrc.NetrcParseError as e:
        v.errors.append(f"Parse error: {e}")
        v.ok = False

    return v


# ─────────────────────────────────────────────────────────────────────────────
# 4. Integration bridges
# ─────────────────────────────────────────────────────────────────────────────

def ftp_login_from_netrc(
    ftp,
    host: str,
    path: str | Path | None = None,
) -> bool:
    """
    Log in to an existing ftplib.FTP connection using netrc credentials.
    Returns True on success, False if no credentials found.

    Example:
        import ftplib
        ftp = ftplib.FTP(host)
        if not ftp_login_from_netrc(ftp, host):
            ftp.login()   # anonymous fallback
    """
    cred = lookup(host, path=path)
    if cred is None:
        return False
    ftp.login(user=cred.login, passwd=cred.password,
              acct=cred.account or "")
    return True


def get_basic_auth_header(
    host: str,
    path: str | Path | None = None,
) -> dict[str, str] | None:
    """
    Return an HTTP Authorization header dict for a host from netrc.
    Returns None if no credentials found.

    Example:
        headers = get_basic_auth_header("api.example.com") or {}
        response = urllib.request.urlopen(req)
    """
    import base64
    cred = lookup(host, path=path)
    if cred is None:
        return None
    token = base64.b64encode(f"{cred.login}:{cred.password}".encode()).decode()
    return {"Authorization": f"Basic {token}"}


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

if __name__ == "__main__":
    import io
    print("=== netrc demo ===")

    with tempfile.TemporaryDirectory() as tmp:
        netrc_path = Path(tmp) / ".netrc"

        # ── write_netrc ────────────────────────────────────────────────────────
        print("\n--- write_netrc ---")
        creds = [
            Credential("ftp.example.com", "alice", "wonderland"),
            Credential("api.example.com", "mytoken", "abc123xyz"),
            Credential("files.corp.com",  "alice",  "hunter2",
                       account="uploads"),
        ]
        p = write_netrc(creds, path=netrc_path)
        print(f"  wrote to {p}")
        print(f"  permissions: {oct(p.stat().st_mode & 0o777)}")
        print(f"  content:\n{p.read_text()}")

        # ── lookup ─────────────────────────────────────────────────────────────
        print("--- lookup ---")
        for host in ["ftp.example.com", "api.example.com", "unknown.com"]:
            c = lookup(host, path=netrc_path)
            print(f"  {host}: {c}")

        # ── list_credentials ───────────────────────────────────────────────────
        print("\n--- list_credentials ---")
        for c in list_credentials(path=netrc_path):
            print(f"  {c.host:<24s}  {c.login:<12s}  {c.mask_password()}")

        # ── add / remove ───────────────────────────────────────────────────────
        print("\n--- add_credential + remove_credential ---")
        add_credential("new.example.com", "bob", "pass99", path=netrc_path)
        print(f"  after add: {[c.host for c in list_credentials(path=netrc_path)]}")
        removed = remove_credential("ftp.example.com", path=netrc_path)
        print(f"  removed ftp.example.com: {removed}")
        print(f"  after remove: {[c.host for c in list_credentials(path=netrc_path)]}")

        # ── validate_netrc ─────────────────────────────────────────────────────
        print("\n--- validate_netrc ---")
        result = validate_netrc(path=netrc_path)
        print(f"  {result}")
        for w in result.warnings:
            print(f"  WARN: {w}")

        # ── get_basic_auth_header ──────────────────────────────────────────────
        print("\n--- get_basic_auth_header ---")
        import base64
        header = get_basic_auth_header("api.example.com", path=netrc_path)
        if header:
            token = header["Authorization"].split(" ", 1)[1]
            decoded = base64.b64decode(token).decode()
            print(f"  Authorization: Basic {token[:12]}...")
            print(f"  decoded: {decoded}")

    # ── load ~/.netrc if present ───────────────────────────────────────────────
    print("\n--- load ~/.netrc (if present) ---")
    try:
        home_creds = list_credentials()
        if home_creds:
            print(f"  {len(home_creds)} credentials found")
            for c in home_creds:
                print(f"  {c.host}: {c.login}")
        else:
            print("  ~/.netrc not found or empty")
    except _netrc.NetrcParseError as e:
        print(f"  parse error: {e}")

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

For the keyring (PyPI) alternative — keyring.get_password(service, username) and keyring.set_password(service, username, password) store credentials in the OS native secret store (macOS Keychain, Windows Credential Locker, secret-service on Linux) — use keyring for desktop applications or interactive tools where the user has an OS session and a secret store; use netrc for server automation scripts, CI systems, and command-line tools that follow Unix conventions and need a simple file-based credential store compatible with ftp, curl, wget, and git credential. For the dotenv / environment variable alternative — os.environ["API_KEY"] and python-dotenv (PyPI) load secrets from .env files — use environment variables for container-based deployments and 12-factor app configuration where secrets are injected at runtime; use netrc when you need per-host credential routing (authenticators(hostname) automatically selects the right entry) and compatibility with Unix tooling that reads ~/.netrc natively. The Claude Skills 360 bundle includes netrc skill sets covering Credential with lookup()/list_credentials()/load_netrc(), write_netrc()/add_credential()/remove_credential() file management, NetrcValidation with validate_netrc() security checker, and ftp_login_from_netrc()/get_basic_auth_header() protocol bridges. Start with the free tier to try credential management patterns and netrc 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