pyclig
A Python Click wrapper with Command Line Interface Guidelines (CLIG) paradigms.
click owns parsing, help, shell completion & the test runner. pyclig adds the conventions on
top: STDOUT for output & STDERR for messages, exit codes, error rendering, logging, output
primitives & standard flags. Options, arguments & types are plain click.
Usage
import click
import pyclig
from dkais import exceptions
@pyclig.group(
"dkais",
short_help="DMA AIS data manager",
examples=["dkais list --start 2026-10"],
errors=(exceptions.Error,),
epilog="For more information see https://github.com/andyrids/dkais.",
)
def cli() -> None:
"""Download DMA AIS archives and convert them to Parquet."""
@cli.command("list")
@pyclig.options.json_output()
@click.option("--start", metavar="DATE")
@pyclig.pass_context
def list_(ctx: pyclig.Context, start: str | None, json_output: bool) -> int:
"""List bucket archives & local state."""
ctx.out.print(f"from {start}")
return pyclig.ExitCode.OK
if __name__ == "__main__":
cli()
Point the entry point at the group: [project.scripts] dkais = "dkais.__main__:cli". Always
name the group - the name is the program name, also under python -m, and sets the shell
completion variable (_DKAIS_COMPLETE).
What every group gets
| Feature | Behaviour |
|---|---|
-h, --help |
Usage, description, options, commands, examples & epilog |
Bare prog |
Concise help on STDOUT, exit 0 - --help without options & epilog |
-v, --version |
prog version from the distribution metadata, else 0.0.0+unknown |
-d, --debug |
On every command, before or after its name; DEBUG logging as soon as parsed |
| Subcommands | @cli.command() & @cli.group() create pyclig.Command & pyclig.Group |
| Arguments | Reach commands as typed - globs & variables are not expanded on Windows either |
| Streams | UTF-8 STDOUT & STDERR, so ✓ or → never fail on a cp1252 pipe |
ctx.obj is left to the application.
Errors & exit codes
Commands return an exit code (or None for 0). Anything else is mapped by the runner, with messages on STDERR:
| Outcome | STDERR | Exit |
|---|---|---|
return code |
- | code |
click.UsageError, ctx.fail() |
error:, the failing command's usage, a --help hint |
2 |
Other click.ClickException |
error: message |
its code |
| Ctrl-C, in a command or at a prompt | warning: interrupted |
130 |
Declined click.confirm(abort=True) |
error: aborted |
1 |
A type in errors= |
error: message, the traceback first with -d |
1 |
Broken STDOUT pipe (prog | head -1) |
- | 1 |
| Any other exception | trimmed traceback, error:, bug report hint |
70 |
pyclig.ExitCode names the codes: OK, FAILURE, USAGE, INTERNAL & INTERRUPT. The bug
report hint names the distribution's Issues project URL. Messages are never parsed as Rich
markup: user input such as [/b] or :warning: prints as typed.
A command may catch Ctrl-C or its own errors to print a tailored message first - a returned code
passes through untouched. Wrap third-party failures (e.g. network errors) in an errors= type, so
users see a message instead of a crash.
Output
ctx.out (STDOUT) & ctx.err (STDERR) are Rich consoles created for each run, with markup,
emoji codes & highlighting off. Pass ctx.err to any Progress or Status. Each console
follows its own stream: FORCE_COLOR, TTY_COMPATIBLE & NO_COLOR are honoured, and piped
output on Windows keeps its colour when forced.
| Primitive | On a terminal | Otherwise |
|---|---|---|
notice(console, level=..., message=...) |
Bold red / yellow level:, wrapped |
One line |
info(console, message=...) |
Dim | Plain |
hanging(console, head="✓", message=...) |
Wrapped under the message | Same |
Arguments after the console are keyword-only. Control characters, including terminal escapes,
are dropped from messages. Print machine-readable output - JSON, tab-separated rows - with
click.echo, which neither wraps nor expands tabs. Use rich.text.Text with a style for styled
output, and TextColumn(markup=False) in a Rich Progress.
Standard flags
pyclig.options holds the flags with standard names, each taking a help= override:
| Factory | Flag | Parameter |
|---|---|---|
json_output() |
--json |
json_output |
select_all() |
-a, --all |
select_all |
force() |
-f, --force |
force |
Logging
Log through the package logger - logging.getLogger(__name__) in any module of the package that
defines the group. Libraries add only a NullHandler, in the package __init__.py:
logging.getLogger(__name__).addHandler(logging.NullHandler())
During a run, warnings & errors print as warning: / error: notices on ctx.err. With -d, a
RichHandler adds DEBUG records, times ([2026-10-31T18:44:45+0000]) & call sites. The
logger's handlers, level & propagation are restored when the run ends.
Testing
from pyclig.testing import invoke
def test_list_rows_when_piped() -> None:
"""Assert `list` prints tab-separated rows when STDOUT is a pipe."""
result = invoke(cli, ["list"], out_tty=False, err_tty=True)
assert result.exit_code == 0
assert "\t" in result.stdout
invoke returns click's Result, with stdout & stderr apart. out_tty, err_tty, color
& width set up each console; env sets (or, with None, unsets) variables. Colour & terminal
variables from the developer's shell are cleared. Never patch Rich private attributes.
Parser-less entry points
pyclig.run() gives a pre-commit hook shim, which leaves sys.argv to the tool it wraps, the
same exit codes, error rendering & logging:
def ruff_format() -> NoReturn:
pyclig.run(
lambda: core_ruff_format(sys.argv[1:]),
errors=(exceptions.Error,),
)
Migrating from argparse
- Each subcommand becomes a
@cli.command(); shared options become one decorator stackingclick.options. Commands are listed in the order they are added. - click shows a command's docstring as its help: put
\fafter the help paragraph, so theArgs:&Returns:sections stay out of--help. Passhelp=for a description that is not an imperative docstring line. - Custom argument types become
click.ParamType[T]subclasses callingself.fail(). parser.error()becomesraise click.UsageError(...)(orctx.fail()).- A
type=function checking a minimum becomesclick.IntRange(min=1). - An environment variable default becomes
envvar=, withshow_envvar=Trueso a missing value names it. - Module-level consoles become
ctx.out/ctx.err, passed to helpers as arguments. - argparse accepted unambiguous option prefixes (
--stfor--start); click does not.
Metadata
Release files for pyclig 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| pyclig-0.1.0.tar.gz | 21.9 kB | Without provenance | Details for pyclig-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pyclig-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for pyclig-0.1.0-py3-none-any.whl |
Total release size: 47.6 kB
Release files / pyclig-0.1.0.tar.gz
| Download URL | pyclig-0.1.0.tar.gz |
|---|---|
| Size | 21.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
151bfa24e75ed7e35b7407765f62554976eb089e06946c30f99464ba155f85e0
|
|
BLAKE2b-256 checksum How to use checksums |
31184503466fcb5e3e05e864818a333ada562f28ee7cc181a6d9f164bbf8e233
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / pyclig-0.1.0-py3-none-any.whl
| Download URL | pyclig-0.1.0-py3-none-any.whl |
|---|---|
| Size | 25.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
30b55a5b701161636b46696286b7563ad171d1569cd6f23488d0d4345e859b16
|
|
BLAKE2b-256 checksum How to use checksums |
a08c19320b0e7bbdfecb350e708a6b6c86e1b7f9ba04ba4e4477f73af914159b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for pyclig 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.