Skip to content

CLI

The mypackage console script. See the CLI guide for usage, flags, and exit codes.

cli

Command-line interface for mypackage.

Installed as the mypackage console script via the [project.scripts] entry point in pyproject.toml:

mypackage summarize data.txt
mypackage smooth data.txt --method median --window 5
echo "1 2 3 4 5" | mypackage summarize - --json

The whole CLI is built on argparse from the standard library, so the package still declares zero runtime dependencies. main returns an exit status rather than calling sys.exit, which makes it directly testable.

main

main(argv: Sequence[str] | None = None, *, out: TextIO | None = None, err: TextIO | None = None, stdin: TextIO | None = None) -> int

Run the command-line interface.

Parameters:

Name Type Description Default
argv Sequence[str] | None

Argument list, excluding the program name. Defaults to sys.argv[1:].

None
out TextIO | None

Stream for normal output, including --help and --version. Defaults to sys.stdout.

None
err TextIO | None

Stream for error messages, including argparse usage errors. Defaults to sys.stderr.

None
stdin TextIO | None

Stream used when the input path is "-". Defaults to sys.stdin.

None

Returns:

Type Description
int

0 on success, 1 on a package-level or I/O error, and 2 on

int

a usage error (propagated from argparse).

Examples:

>>> import io
>>> buffer = io.StringIO()
>>> main(
...     ["summarize", "-", "--json"],
...     out=buffer,
...     stdin=io.StringIO("1 2 3 4"),
... )
0
>>> json.loads(buffer.getvalue())["mean"]
2.5
Source code in src/mypackage/cli.py
def main(
    argv: Sequence[str] | None = None,
    *,
    out: TextIO | None = None,
    err: TextIO | None = None,
    stdin: TextIO | None = None,
) -> int:
    """Run the command-line interface.

    Args:
        argv: Argument list, excluding the program name. Defaults to
            `sys.argv[1:]`.
        out: Stream for normal output, including ``--help`` and
            ``--version``. Defaults to `sys.stdout`.
        err: Stream for error messages, including argparse usage errors.
            Defaults to `sys.stderr`.
        stdin: Stream used when the input path is ``"-"``. Defaults to
            `sys.stdin`.

    Returns:
        ``0`` on success, ``1`` on a package-level or I/O error, and ``2`` on
        a usage error (propagated from `argparse`).

    Examples:
        >>> import io
        >>> buffer = io.StringIO()
        >>> main(
        ...     ["summarize", "-", "--json"],
        ...     out=buffer,
        ...     stdin=io.StringIO("1 2 3 4"),
        ... )
        0
        >>> json.loads(buffer.getvalue())["mean"]
        2.5
    """
    out = out if out is not None else sys.stdout
    err = err if err is not None else sys.stderr

    parser = build_parser()
    # argparse prints --help, --version, and usage errors straight to
    # sys.stdout / sys.stderr, so redirect them to honour the injected streams.
    try:
        with contextlib.redirect_stdout(out), contextlib.redirect_stderr(err):
            args = parser.parse_args(argv)
    except SystemExit as exc:  # --help / --version / usage error
        return int(exc.code or EXIT_OK)

    handlers = {"summarize": _run_summarize, "smooth": _run_smooth}
    try:
        return handlers[args.command](args, out, stdin)
    except MypackageError as exc:
        err.write(f"error: {exc}\n")
        return EXIT_ERROR
    except OSError as exc:
        err.write(f"error: could not read {args.path!r}: {exc}\n")
        return EXIT_ERROR

build_parser

build_parser() -> ArgumentParser

Build the top-level argument parser.

Exposed separately from main so documentation tooling and tests can introspect the CLI without running it.

Returns:

Type Description
ArgumentParser

A fully configured argparse.ArgumentParser.

Examples:

>>> parser = build_parser()
>>> args = parser.parse_args(["summarize", "data.txt"])
>>> args.command, args.path, args.json
('summarize', 'data.txt', False)
Source code in src/mypackage/cli.py
def build_parser() -> argparse.ArgumentParser:
    """Build the top-level argument parser.

    Exposed separately from [`main`][mypackage.cli.main] so documentation
    tooling and tests can introspect the CLI without running it.

    Returns:
        A fully configured `argparse.ArgumentParser`.

    Examples:
        >>> parser = build_parser()
        >>> args = parser.parse_args(["summarize", "data.txt"])
        >>> args.command, args.path, args.json
        ('summarize', 'data.txt', False)
    """
    parser = argparse.ArgumentParser(
        prog="mypackage",
        description="Descriptive statistics and smoothing for numeric series.",
    )
    parser.add_argument(
        "--version",
        action="version",
        version=f"mypackage {__version__}",
    )
    subparsers = parser.add_subparsers(dest="command", required=True)

    summarize_parser = subparsers.add_parser(
        "summarize",
        help="Print descriptive statistics for a series.",
    )
    summarize_parser.add_argument(
        "path",
        nargs="?",
        default="-",
        help="File of numbers, or '-' for standard input (default: '-').",
    )
    summarize_parser.add_argument(
        "--ddof",
        type=int,
        default=1,
        help="Delta degrees of freedom for the variance (default: 1).",
    )
    summarize_parser.add_argument(
        "--json",
        action="store_true",
        help="Emit JSON instead of an aligned table.",
    )

    smooth_parser = subparsers.add_parser(
        "smooth",
        help="Smooth a series and print the result.",
    )
    smooth_parser.add_argument(
        "path",
        nargs="?",
        default="-",
        help="File of numbers, or '-' for standard input (default: '-').",
    )
    smooth_parser.add_argument(
        "--method",
        choices=("mean", "median", "ewma"),
        default="mean",
        help="Smoothing filter to apply (default: mean).",
    )
    smooth_parser.add_argument(
        "--window",
        type=int,
        default=3,
        help="Window length for the 'mean' and 'median' methods (default: 3).",
    )
    smooth_parser.add_argument(
        "--alpha",
        type=float,
        default=0.3,
        help="Smoothing factor for the 'ewma' method (default: 0.3).",
    )
    smooth_parser.add_argument(
        "--padding",
        choices=tuple(member.value for member in Padding),
        default=Padding.EDGE.value,
        help="Boundary strategy for windowed methods (default: edge).",
    )
    smooth_parser.add_argument(
        "--precision",
        type=_non_negative_int,
        default=6,
        help="Number of decimals in the printed output (default: 6).",
    )

    return parser