Claude Code for argparse: Command-Line Argument Parsing in Python — Claude Skills 360 Blog
Blog / AI / Claude Code for argparse: Command-Line Argument Parsing in Python
AI

Claude Code for argparse: Command-Line Argument Parsing in Python

Published: July 4, 2028
Read time: 5 min read
By: Claude Skills 360

argparse is Python’s stdlib CLI argument parser. import argparse. Basic: parser = argparse.ArgumentParser(description="My tool"); parser.add_argument("input", help="Input file"); args = parser.parse_args(). Optional: parser.add_argument("--verbose","-v", action="store_true"). Type: parser.add_argument("--port", type=int, default=8080). Choices: parser.add_argument("--env", choices=["dev","prod"]). nargs: "?" zero-or-one, "*" zero-or-more, "+" one-or-more, N exactly N. append: parser.add_argument("--tag", action="append") — repeated flags. count: parser.add_argument("-v", action="count", default=0) — -vvv → 3. required: parser.add_argument("--token", required=True). metavar: parser.add_argument("--out", metavar="FILE"). dest: parser.add_argument("--dry-run", dest="dry_run"). Subparsers: subs = parser.add_subparsers(dest="cmd"); push = subs.add_parser("push"); push.add_argument("remote"). Mutually exclusive: g = parser.add_mutually_exclusive_group(); g.add_argument("--json"); g.add_argument("--yaml"). Argument group: grp = parser.add_argument_group("Database"). Formatter: formatter_class=argparse.ArgumentDefaultsHelpFormatter. FileType: parser.add_argument("--config", type=argparse.FileType("r")). parse_known_args: args, rest = parser.parse_known_args(). set_defaults: parser.set_defaults(func=handler). Namespace: args.input; args.verbose; vars(args). parser.error("msg") — exit with usage. parser.add_argument("--config", default=argparse.SUPPRESS). Claude Code generates git-style CLIs, daemon launchers, ETL runner scripts, and tool wrappers.

CLAUDE.md for argparse

## argparse Stack
- Stdlib: import argparse, sys
- Parser: parser = argparse.ArgumentParser(description="...", formatter_class=argparse.ArgumentDefaultsHelpFormatter)
- Sub-commands: subs = parser.add_subparsers(dest="command", required=True); cmd = subs.add_parser("name")
- Dispatch: parser.set_defaults(func=handler) on each sub-parser; args.func(args)
- Types: type=int|float|Path|argparse.FileType("r") | custom validator function
- Groups: parser.add_mutually_exclusive_group(required=True) | parser.add_argument_group("section")

argparse CLI Pipeline

# app/cli.py — ArgumentParser, subparsers, dispatch, validators, config overlay
from __future__ import annotations

import argparse
import json
import os
import sys
from pathlib import Path
from typing import Any, Callable, Sequence


# ─────────────────────────────────────────────────────────────────────────────
# 1. Argument type validators
# ─────────────────────────────────────────────────────────────────────────────

def positive_int(value: str) -> int:
    """
    Argument type: parse as int, reject if <= 0.

    Example:
        parser.add_argument("--workers", type=positive_int, default=4)
    """
    n = int(value)
    if n <= 0:
        raise argparse.ArgumentTypeError(f"must be a positive integer, got {value!r}")
    return n


def port_number(value: str) -> int:
    """
    Argument type: validate as TCP port (1–65535).

    Example:
        parser.add_argument("--port", type=port_number, default=8080)
    """
    n = int(value)
    if not (1 <= n <= 65535):
        raise argparse.ArgumentTypeError(f"port must be 1–65535, got {n}")
    return n


def existing_path(value: str) -> Path:
    """
    Argument type: path that must already exist.

    Example:
        parser.add_argument("config", type=existing_path)
    """
    p = Path(value)
    if not p.exists():
        raise argparse.ArgumentTypeError(f"path does not exist: {value!r}")
    return p


def writable_dir(value: str) -> Path:
    """
    Argument type: directory; created if it does not exist.

    Example:
        parser.add_argument("--output-dir", type=writable_dir, default="./output")
    """
    p = Path(value)
    p.mkdir(parents=True, exist_ok=True)
    return p


def key_value_pair(value: str) -> tuple[str, str]:
    """
    Argument type for KEY=VALUE pairs.

    Example:
        parser.add_argument("--env", type=key_value_pair, action="append")
        # --env FOO=bar --env BAZ=qux  →  [("FOO","bar"),("BAZ","qux")]
    """
    if "=" not in value:
        raise argparse.ArgumentTypeError(f"expected KEY=VALUE, got {value!r}")
    k, _, v = value.partition("=")
    return k.strip(), v.strip()


# ─────────────────────────────────────────────────────────────────────────────
# 2. Base parser factory
# ─────────────────────────────────────────────────────────────────────────────

def base_parser(
    description: str = "",
    epilog: str = "",
    **kwargs,
) -> argparse.ArgumentParser:
    """
    Create a parser with sensible defaults: auto-helps, default display.

    Example:
        parser = base_parser("ETL runner", epilog="Use 'cmd --help' for more.")
        parser.add_argument("--dry-run", action="store_true")
    """
    return argparse.ArgumentParser(
        description=description,
        epilog=epilog,
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        **kwargs,
    )


def add_common_args(parser: argparse.ArgumentParser) -> None:
    """
    Add standard --verbose/-v and --log-level flags to any parser.

    Example:
        add_common_args(parser)
        add_common_args(sub_parser)
    """
    group = parser.add_argument_group("logging")
    group.add_argument(
        "--verbose", "-v",
        action="count",
        default=0,
        help="verbosity level (-v INFO, -vv DEBUG)",
    )
    group.add_argument(
        "--log-level",
        choices=["DEBUG","INFO","WARNING","ERROR","CRITICAL"],
        default=None,
        help="set log level explicitly (overrides --verbose)",
    )


def add_config_args(parser: argparse.ArgumentParser) -> None:
    """
    Add --config FILE and --set KEY=VALUE arguments.

    Example:
        add_config_args(parser)
        # --config myapp.json --set debug=true --set port=9000
    """
    parser.add_argument(
        "--config",
        type=existing_path,
        metavar="FILE",
        help="JSON config file to load",
    )
    parser.add_argument(
        "--set",
        type=key_value_pair,
        action="append",
        metavar="KEY=VALUE",
        dest="overrides",
        default=[],
        help="override config key (repeatable)",
    )


# ─────────────────────────────────────────────────────────────────────────────
# 3. Subcommand dispatcher
# ─────────────────────────────────────────────────────────────────────────────

class CLI:
    """
    Subcommand-based CLI with automatic dispatch.

    Example:
        cli = CLI("myapp", description="My application")

        @cli.command("serve", help="Start the HTTP server")
        def serve_cmd(args):
            parser.add_argument("--port", type=port_number, default=8080)
            ...

        cli.run()
    """

    def __init__(self, prog: str, description: str = "") -> None:
        self.parser  = base_parser(description, prog=prog)
        add_common_args(self.parser)
        add_config_args(self.parser)
        self._subs   = self.parser.add_subparsers(dest="command", metavar="COMMAND")
        self._subs.required = True
        self._handlers: dict[str, Callable] = {}

    def command(
        self,
        name: str,
        help: str = "",
        setup: Callable[[argparse.ArgumentParser], None] | None = None,
    ):
        """
        Decorator to register a subcommand.

        Example:
            @cli.command("ingest", help="Ingest data from source")
            def ingest(args):
                run_ingest(path=args.path, limit=args.limit)
        """
        def decorator(fn: Callable) -> Callable:
            sub = self._subs.add_parser(
                name,
                help=help,
                formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            )
            if setup:
                setup(sub)
            sub.set_defaults(_handler=fn)
            self._handlers[name] = fn
            return fn
        return decorator

    def run(self, argv: Sequence[str] | None = None) -> int:
        args = self.parser.parse_args(argv)
        _configure_logging(args)
        config = _load_config(args)
        args.config_data = config
        handler = getattr(args, "_handler", None)
        if handler is None:
            self.parser.print_help()
            return 1
        try:
            result = handler(args)
            return 0 if result is None else int(result)
        except KeyboardInterrupt:
            return 130
        except Exception as exc:
            self.parser.error(str(exc))
            return 1


def _configure_logging(args: argparse.Namespace) -> None:
    import logging
    level = getattr(args, "log_level", None) or None
    verbosity = getattr(args, "verbose", 0)
    if level:
        logging.basicConfig(level=level)
    elif verbosity >= 2:
        logging.basicConfig(level=logging.DEBUG)
    elif verbosity >= 1:
        logging.basicConfig(level=logging.INFO)
    else:
        logging.basicConfig(level=logging.WARNING)


def _load_config(args: argparse.Namespace) -> dict:
    config: dict = {}
    cfg_path = getattr(args, "config", None)
    if cfg_path and Path(cfg_path).exists():
        config = json.loads(Path(cfg_path).read_text())
    for key, value in getattr(args, "overrides", []):
        config[key] = value
    return config


# ─────────────────────────────────────────────────────────────────────────────
# 4. Demo CLI — git-style with subcommands
# ─────────────────────────────────────────────────────────────────────────────

def build_demo_parser() -> argparse.ArgumentParser:
    """
    Build an example CLI with serve, process, and export subcommands.

    Example:
        parser = build_demo_parser()
        args = parser.parse_args(["serve", "--port", "9000"])
        # args.command == "serve", args.port == 9000
    """
    parser = base_parser(
        "data-tool",
        description="Example data pipeline CLI",
        epilog="Use 'data-tool COMMAND --help' for subcommand help.",
    )
    add_common_args(parser)
    add_config_args(parser)

    subs = parser.add_subparsers(dest="command", metavar="COMMAND")
    subs.required = True

    # ── serve ──────────────────────────────────────────────────────────────
    serve = subs.add_parser(
        "serve",
        help="Start the API server",
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
    )
    serve.add_argument("--host",    default="0.0.0.0", help="Bind address")
    serve.add_argument("--port",    type=port_number, default=8080, help="TCP port")
    serve.add_argument("--workers", type=positive_int, default=2, help="Worker count")
    serve.add_argument("--reload",  action="store_true", help="Auto-reload on change")

    # ── process ────────────────────────────────────────────────────────────
    process = subs.add_parser(
        "process",
        help="Process an input file",
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
    )
    process.add_argument("input",     type=existing_path, help="Input file path")
    process.add_argument("--output",  type=writable_dir, default="./output")
    process.add_argument("--dry-run", action="store_true", dest="dry_run")
    process.add_argument("--limit",   type=positive_int, metavar="N", help="Max rows")
    process.add_argument(
        "--format",
        choices=["csv","parquet","json"],
        default="csv",
        help="Output format",
    )
    # Mutually exclusive: --all vs --recent
    mode = process.add_mutually_exclusive_group()
    mode.add_argument("--all",    action="store_true",  help="Process all records")
    mode.add_argument("--recent", type=positive_int, metavar="DAYS",
                      help="Process records from last N days")

    # ── export ─────────────────────────────────────────────────────────────
    export = subs.add_parser(
        "export",
        help="Export data to destination",
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
    )
    export.add_argument("destination", help="Export destination URL or path")
    export.add_argument("--tag",   action="append", default=[], help="Filter tags (repeatable)")
    export.add_argument("--since", metavar="DATE",  help="ISO date lower bound")
    export.add_argument("--until", metavar="DATE",  help="ISO date upper bound")
    export.add_argument(
        "--compress",
        choices=["none","gzip","zstd"],
        default="none",
    )

    return parser


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

if __name__ == "__main__":
    parser = build_demo_parser()

    print("=== argparse demo ===")

    # 1. serve subcommand
    print("\n--- serve args ---")
    args = parser.parse_args(["serve", "--port", "9000", "--workers", "4", "--reload"])
    print(f"  command={args.command}  port={args.port}  workers={args.workers}  reload={args.reload}")

    # 2. process subcommand
    import tempfile
    with tempfile.NamedTemporaryFile(suffix=".csv", delete=False) as f:
        f.write(b"a,b\n1,2\n")
        tmp = f.name

    print("\n--- process args ---")
    args2 = parser.parse_args([
        "process", tmp,
        "--format", "parquet",
        "--limit", "1000",
        "--dry-run",
        "--recent", "7",
    ])
    print(f"  command={args2.command}  format={args2.format}")
    print(f"  limit={args2.limit}  dry_run={args2.dry_run}  recent={args2.recent}")
    Path(tmp).unlink(missing_ok=True)

    # 3. export subcommand
    print("\n--- export args ---")
    args3 = parser.parse_args([
        "export", "s3://my-bucket/data",
        "--tag", "daily",
        "--tag", "prod",
        "--compress", "gzip",
    ])
    print(f"  destination={args3.destination}  tags={args3.tag}  compress={args3.compress}")

    # 4. Namespace → dict
    print("\n--- vars(args) ---")
    d = vars(args)
    print(f"  {d}")

    # 5. parse_known_args
    print("\n--- parse_known_args ---")
    known, extra = parser.parse_known_args(["serve", "--port", "8080", "--unknown-flag"])
    print(f"  known.port={known.port}  extra={extra}")

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

For the click alternative — click (PyPI) builds CLIs using decorators: @click.command, @click.option, @click.argument, with automatic ANSI color support, prompting, password masking, file handling, and plugin systems via click.Group; Python’s stdlib argparse requires more setup boilerplate but has zero dependencies, native Python integration, and is always available — use click when building user-facing tools that need interactive prompting, rich help formatting, or plugin architectures, argparse for library CLIs, simple internal tooling, or anywhere you must avoid external dependencies. For the typer alternative — typer (PyPI) wraps click to generate CLI argument definitions from Python type annotations and docstrings, reducing @option boilerplate to just function signatures; argparse uses explicit add_argument() calls but integrates cleanly with test suites via parser.parse_args([...]) without subprocess overhead — use typer when you want typed function signatures to drive your CLI with minimal code, argparse when you need fine-grained control over help formatting, subcommand dispatch, or want the stdlib-only solution. The Claude Skills 360 bundle includes argparse skill sets covering positive_int()/port_number()/existing_path()/writable_dir()/key_value_pair() type validators, base_parser()/add_common_args()/add_config_args() parser factories, CLI class with subcommand dispatch, build_demo_parser() with serve/process/export subcommands, mutually exclusive groups, and parse_known_args() partial parsing. Start with the free tier to try CLI building and argparse command 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