|
2 | 2 |
|
3 | 3 | # radicli: Radically lightweight command-line interfaces |
4 | 4 |
|
5 | | -`radicli` is a small, zero-dependency Python package for creating command line interfaces, built on top of Python's [`argparse`](https://docs.python.org/3/library/argparse.html) module. It introduces minimal overhead, preserves your original Python functions and uses type hints to parse values provided on the CLI. It supports all common types out-of-the-box, including complex ones like `List[str]`, `Literal` and `Enum`, and allows registering custom types with custom converters, as well as custom CLI-only error handling. |
| 5 | +`radicli` is a small, zero-dependency Python package for creating command line interfaces, built on top of Python's [`argparse`](https://docs.python.org/3/library/argparse.html) module. It introduces minimal overhead, preserves your original Python functions and uses **type hints** to parse values provided on the CLI. It supports all common types out-of-the-box, including complex ones like `List[str]`, `Literal` and `Enum`, and allows registering **custom types** with custom converters, as well as custom CLI-only **error handling** and exporting a **static representation** for faster `--help` and errors. |
6 | 6 |
|
7 | 7 | > **Important note:** This package aims to be a simple option based on the requirements of our libraries. If you're looking for a more full-featured CLI toolkit, check out [`typer`](https://typer.tiangolo.com), [`click`](https://click.palletsprojects.com) or [`plac`](https://plac.readthedocs.io/en/latest/). |
8 | 8 |
|
@@ -284,6 +284,33 @@ def handle_custom_error(error: CustomError) -> int: |
284 | 284 | return 1 |
285 | 285 | ``` |
286 | 286 |
|
| 287 | +### Using static data for faster help and errors |
| 288 | + |
| 289 | +CLIs often require various other Python packages that need to be imported – for example, you might need to import `pytorch` and `tensorflow`, or load other resources in the global scope. This all adds to the CLI's load time, so even showing the `--help` message may take several seconds to run. That's all unnecessary and makes for a frustrating developer experience. |
| 290 | + |
| 291 | +`radicli` lets you generate a static representation of your CLI as a JSON file, including everything needed to output help messages and to check that the command exists and the correct and required arguments are provided. If the static CLI doesn't perform a system exit via printing the help message or raising an error, you can import and run the "live" CLI to continue. This lets you **defer the import until it's really needed**, i.e. to convert the arguments to the expected types and executing the command function. |
| 292 | + |
| 293 | +```python |
| 294 | +cli.to_static("./static.json") |
| 295 | +``` |
| 296 | + |
| 297 | +```python |
| 298 | +from radicli import StaticRadicli |
| 299 | + |
| 300 | +static = StaticRadicli.load("./static.json") |
| 301 | + |
| 302 | +if __name__ == "__main__": |
| 303 | + static.run() |
| 304 | + |
| 305 | + # This only runs if the static CLI doesn't error or print help |
| 306 | + from .cli import cli |
| 307 | + cli.run() |
| 308 | +``` |
| 309 | + |
| 310 | +If the CLI is part of a Python package, you can generate the static JSON file during your build process and ship the pre-generated JSON file with your package. |
| 311 | + |
| 312 | +`StaticRadicli` also provides a `disable` argument to disable static parsing during development (or if a certain environment variable is set). Setting `debug=True` will print an additional start and optional end marker (if the static CLI didn't exit before) to indicate that the static CLI ran. |
| 313 | + |
287 | 314 | ## 🎛 API |
288 | 315 |
|
289 | 316 | ### <kbd>dataclass</kbd> `Arg` |
@@ -452,6 +479,90 @@ if __name__ == "__main__": |
452 | 479 | | -------- | --------------------- | ----------------------------------------------------------------------------------------- | |
453 | 480 | | `args` | `Optional[List[str]]` | Optional command to pass in. Will be read from `sys.argv` if not set (standard use case). | |
454 | 481 |
|
| 482 | +#### <kbd>method</kbd> `Radicli.to_static` |
| 483 | + |
| 484 | +Export a static JSON representation of the CLI for `StaticRadicli`. |
| 485 | + |
| 486 | +```python |
| 487 | +cli.to_static("./static.json") |
| 488 | +``` |
| 489 | + |
| 490 | +| Argument | Type | Description | |
| 491 | +| ----------- | ------------------ | ------------------------------- | |
| 492 | +| `file_path` | `Union[str, Path]` | The path to the JSON file. | |
| 493 | +| **RETURNS** | `Path` | The path the data was saved to. | |
| 494 | + |
| 495 | +#### <kbd>method</kbd> `Radicli.to_static_json` |
| 496 | + |
| 497 | +Generate a static representation of the CLI for `StaticRadicli` as a JSON-serializable dict. |
| 498 | + |
| 499 | +```python |
| 500 | +data = cli.to_static_json() |
| 501 | +``` |
| 502 | + |
| 503 | +| Argument | Type | Description | |
| 504 | +| ----------- | ---------------- | ---------------- | |
| 505 | +| **RETURNS** | `Dict[str, Any]` | The static data. | |
| 506 | + |
| 507 | +### <kbd>class</kbd> `StaticRadicli` |
| 508 | + |
| 509 | +Subclass of `Radicli` and static version of the CLI that can be loaded from a static representation of the CLI, generated with `Radicli.to_static`. The static CLI can run before importing and running the live CLI and will take care of showing help messages and doing basic argument checks, e.g. to ensure all arguments are correct and present. This can make your CLI help significantly faster by deferring the import of the live CLI until it's really needed, i.e. to convert the values and execute the function. |
| 510 | + |
| 511 | +```python |
| 512 | +static = StaticRadicli.load("./static.json") |
| 513 | + |
| 514 | +if __name__ == "__main__": |
| 515 | + static.run() |
| 516 | + # This only runs if the static CLI doesn't error or print help |
| 517 | + from .cli import cli |
| 518 | + cli.run() |
| 519 | +``` |
| 520 | + |
| 521 | +#### <kbd>classmethod</kbd> `StaticRadicli.load` |
| 522 | + |
| 523 | +Load the static CLI from a JSON file generated with `Radicli.to_static`. |
| 524 | + |
| 525 | +```python |
| 526 | +static = StaticRadicli.load("./static.json") |
| 527 | +``` |
| 528 | + |
| 529 | +| Argument | Type | Description | |
| 530 | +| ----------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
| 531 | +| `file_path` | `Union[str, Path]` | The JSON file to load. | |
| 532 | +| `disable` | `bool` | Whether to disable static parsing. Can be useful during development. Defaults to `False`. | |
| 533 | +| `debug` | `bool` | Enable debugging mode and print an additional start and optional end marker (if the static CLI didn't exit before) to indicate that the static CLI ran. Defaults to `False`. | |
| 534 | + |
| 535 | +#### <kbd>method</kbd> `StaticRadicli.__init__` |
| 536 | + |
| 537 | +Initialize the static CLI with the JSON-serializable static representation. |
| 538 | + |
| 539 | +```python |
| 540 | +data = cli.to_static_json() |
| 541 | +static = StaticRadicli(data) |
| 542 | +``` |
| 543 | + |
| 544 | +| Argument | Type | Description | |
| 545 | +| --------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
| 546 | +| `data` | `Dict[str, Any]` | The static data. | |
| 547 | +| `disable` | `bool` | Whether to disable static parsing. Can be useful during development. Defaults to `False`. | |
| 548 | +| `debug` | `bool` | Enable debugging mode and print an additional start and optional end marker (if the static CLI didn't exit before) to indicate that the static CLI ran. Defaults to `False`. | |
| 549 | + |
| 550 | +#### <kbd>method</kbd> `StaticRadicli.run` |
| 551 | + |
| 552 | +Run the static CLI. Typically called before running the live CLI and will perform a system exit if a help message was printed (`0`) or if argument names were missing or incorrect (`1`). This means you can defer loading the live CLI until it's really needed, , i.e. to convert the values and execute the function. |
| 553 | + |
| 554 | +```python |
| 555 | +if __name__ == "__main__": |
| 556 | + static.run() |
| 557 | + |
| 558 | + from .cli import cli |
| 559 | + cli.run() |
| 560 | +``` |
| 561 | + |
| 562 | +| Argument | Type | Description | |
| 563 | +| -------- | --------------------- | ----------------------------------------------------------------------------------------- | |
| 564 | +| `args` | `Optional[List[str]]` | Optional command to pass in. Will be read from `sys.argv` if not set (standard use case). | |
| 565 | + |
455 | 566 | ### Custom types and converters |
456 | 567 |
|
457 | 568 | The package includes several custom types implemented as `TypeVar`s with pre-defined converter functions. If these custom types are used in the decorated function, the values received from the CLI will be converted and validated accordingly. |
|
0 commit comments