lablup / lablup/backend.ai

Make use of the output framework consistent across all client SDK's CLI commands

Open
#1,925 1 comment 0 reactions 0 assignees View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.