Make use of the output framework consistent across all client SDK's CLI commands
- Dominant language
- Python
- Stars
- 670
- Forks
- 183
- Avg merge
- 14h 15m
- Merged PRs (30d)
- 335
Description
We should use the following patterns for the Client SDK's CLI commands, so that we can use `--output=json` option to parse and verify the command execution results programmatically for [our integration test suite](https://github.com/lablup/backend.ai/tree/main/src/ai/backend/test) as well as user-written automation scripts.
The output framework (https://github.com/lablup/backend.ai/tree/main/src/ai/backend/client/output) allows using different output implementations with a unified API to render the API responses.
We should **avoid**:
- Directly using `print()`
- Directly using `ai.backend.client.cli.pretty.{print_error,print_fail,...`}\* It is okay to use `print_done()` to indicate the completion of long-running tasks, but such long tasks will be transitioned to explicit background tasks in the future.
- Directly using `logging` like `log.exception("An error has ocurred.")`\* You may still use `logging` in the functional API layer (`ai.backend.client.func`), but you should prefer raising up exceptions explicitly instead of verbose logging here.
We shoud use:
- `ctx.output.print_error()`, `ctx.output.print_fail()`, ...
- `ctx.output.print_item()`, `ctx.output.print_paginated_list()`, ...
Currently there are many CLI modules that DOES NOT follow the output framework:
- [ ] `ai.backend.client.cli.image`
- [ ] `ai.backend.client.cli.server_log`
- [ ] `ai.backend.client.cli.dotfile`
- [ ] `ai.backend.client.cli.vfolder`
- [ ] `ai.backend.client.cli.session.lifecycle`
- [ ] `ai.backend.client.cli.admin.etcd`
- [ ] `ai.backend.client.cli.admin.license`
- [ ] `ai.backend.client.cli.admin.manager`
- [ ] `ai.backend.client.cli.admin.resource`
- [ ] We also need to update/adapt the existing integration test suite as well.
There are some cases that do not fit very well with the output framework (e.g., requiring a special terminal manipulation), so let's exclude them:
- `ai.backend.client.cli.app`
- `ai.backend.client.cli.logs`
- `ai.backend.client.cli.proxy`
- `ai.backend.client.cli.session.app`
- `ai.backend.client.cli.session.execute`
- `ai.backend.client.cli.session.ssh`
Well-written examples:
- https://github.com/lablup/backend.ai/blob/main/src/ai/backend/client/cli/admin/keypair.py
- https://github.com/lablup/backend.ai/blob/main/src/ai/backend/client/cli/service.py
## The output framework patterns
These are the common patterns to use, but you may adapt it as long as the proper output framework APIs.
For paginated list queries:
```python
@parent_cmdgroup.command()
@pass_ctx_obj
@click.option("--filter", "filter_", default=None, help="Set the query filter expression.")
@click.option("--order", default=None, help="Set the query ordering expression.")
@click.option("--offset", default=0, help="The index of the current page start for pagination.")
@click.option("--limit", type=int, default=None, help="The page size for pagination.")
def list(ctx: CLIContext, filter_, order, offset, limit):
with Session() as api_session:
try:
fetch_func = lambda pg_offset, pg_size: api_session.TargetObjectType.paginated_list(
page_offset=pg_offset,
page_size=pg_size,
filter=filter_,
order=order,
)
ctx.output.print_paginated_list(
fetch_func,
initial_page_offset=offset,
page_size=limit,
)
except Exception as e:
ctx.output.print_error(e)
sys.exit(ExitCode.FAILURE)
```
For item queries:
```python
@parent_cmdgroup.command()
@pass_ctx_obj
@click.argument(...)
def info(ctx: CLIContext, service_name_or_id: str):
with Session() as api_session:
try:
# get something with api_session
result = ...
ctx.output.print_item(
result,
_default_detail_fields,
)
print()
except Exception as e:
ctx.output.print_error(e)
sys.exit(ExitCode.FAILURE)
```
For mutations:
```python
@parent_cmdgroup.command()
@pass_ctx_obj
@click.argument(...)
@click.option(...)
def mutate_something(
ctx: CLIContext,
...,
) -> None:
with Session() as api_session:
try:
# mutate something with api_session
result = ...
except Exception as e:
ctx.output.print_mutation_error(
e,
item_name="object-type",
action_name="action",
)
sys.exit(ExitCode.FAILURE)
if not data["ok"]:
ctx.output.print_mutation_error(
msg=data["msg"],
item_name="object-type",
action_name="action",
)
sys.exit(ExitCode.FAILURE)
ctx.output.print_mutation_result(
data,
item_name="object-type",
extra_info={...},
)
```
JIRA Issue: BA-139
Contributor guide
Assessment
This issue has not been assessed yet.