Skip to content

Commit

Permalink
feat!: rename subcommands
Browse files Browse the repository at this point in the history
Do not use plurals for the subcommands.
  • Loading branch information
alvarolopez committed Sep 10, 2024
1 parent e594146 commit 29d969c
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 7 deletions.
4 changes: 2 additions & 2 deletions src/ai4_cli/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
the DOTENV_FILE environment variable.
"""
)
app.add_typer(modules.app, name="modules")
app.add_typer(tools.app, name="tools")
app.add_typer(modules.app, name="module")
app.add_typer(tools.app, name="tool")


DOTENV_FILE = os.getenv("AI4_DOTENV_FILE", ".env.ai4")
Expand Down
151 changes: 151 additions & 0 deletions src/ai4_cli/cli/tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
"""Handle CLI commands for tools."""

import enum
from typing_extensions import Annotated
from typing import List, Optional

import typer

from ai4_cli.client import client
from ai4_cli import exceptions
from ai4_cli import utils

app = typer.Typer(help="List and get details of the defined tools.")


class ToolColumns(str, enum.Enum):
"""Columns to show in the list command."""

ID = "ID"
NAME = "Tool name"
SUMMARY = "Summary"
KEYWORDS = "Keywords"


@app.command(name="list")
def list(
ctx: typer.Context,
long: Annotated[
bool,
typer.Option(
"--long",
"-l",
help="Show more details.",
),
] = False,
sort: Annotated[
ToolColumns,
typer.Option(
"--sort",
help="Sort the tools by the given field.",
),
] = ToolColumns.ID,
tags: Annotated[
Optional[List[str]],
typer.Option(
"--tags",
help="Filter tools by tags. The given tags must all be present on a "
"module to be included in the results. Boolean expression is "
"t1 AND t2.",
),
] = None,
not_tags: Annotated[
Optional[List[str]],
typer.Option(
"--not-tags",
help="Filter tools by tags. Only the tools that do not have any of the "
"given tags will be included in the results. Boolean expression is "
"NOT (t1 AND t2).",
),
] = None,
tags_any: Annotated[
Optional[List[str]],
typer.Option(
"--tags-any",
help="Filter tools by tags. If any of the given tags is present on a "
"module it will be included in the results. Boolean expression is "
"t1 OR t2.",
),
] = None,
not_tags_any: Annotated[
Optional[List[str]],
typer.Option(
"--not-tags-any",
help="Filter tools by tags. Only the tools that do not have at least "
"any of the given tags will be included in the results. "
"Boolean expression is "
"NOT (t1 OR t2).",
),
] = None,
):
"""List all tools."""
endpoint = ctx.obj.endpoint
version = ctx.obj.api_version
debug = ctx.obj.debug

cli = client.AI4Client(endpoint, version, http_debug=debug)
filters = {
"tags": tags,
"not_tags": not_tags,
"tags_any": tags_any,
"not_tags_any": not_tags_any,
}
_, content = cli.tools.list(filters=filters)

if long:
rows = [
[
k.get("name"),
k.get("title"),
k.get("summary"),
", ".join(k.get("keywords")),
]
for k in content
]

columns = [
ToolColumns.ID,
ToolColumns.NAME,
ToolColumns.SUMMARY,
ToolColumns.KEYWORDS,
]
else:
rows = [[k.get("name"), k.get("title"), k.get("summary")] for k in content]
columns = [
ToolColumns.ID,
ToolColumns.NAME,
ToolColumns.SUMMARY,
]

try:
idx = columns.index(sort)
except ValueError:
e = exceptions.InvalidUsageError(f"Invalid column to sort by: {sort}")
utils.format_rich_error(e)
raise typer.Exit()

sorted_rows = sorted(rows, key=lambda x: x[idx])
utils.format_list(
columns=columns,
items=sorted_rows,
)


@app.command(name="show")
def show(
ctx: typer.Context,
module_id: str = typer.Argument(..., help="The ID of the module to show."),
):
"""Show details of a module."""
endpoint = ctx.obj.endpoint
version = ctx.obj.api_version
debug = ctx.obj.debug

cli = client.AI4Client(endpoint, version, http_debug=debug)
try:
_, content = cli.tools.show(module_id)
except exceptions.BaseHTTPError as e:
utils.format_rich_error(e)
raise typer.Exit()

utils.format_dict(content, exclude=["tosca", "continuous_integration"])
25 changes: 25 additions & 0 deletions src/ai4_cli/client/tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""Tools (catalog) HTTP client."""


class _Tools(object):
"""Tools HTTP client."""

def __init__(self, client):
"""Create a new instance.
:param client: The AI4Client instance.
"""
self.client = client

def list(self, filters=None):
"""List all tools."""
params = {}
for key, value in filters.items():
if value is None:
continue
params[key] = value
return self.client.request("catalog/tools/detail", "GET", params=params)

def show(self, tool_id):
"""Show details of a tool."""
return self.client.request(f"catalog/tools/{tool_id}/metadata", "GET")
26 changes: 21 additions & 5 deletions src/ai4_cli/tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,33 @@ def test_version():
assert ai4_cli.extract_version() in result.output


def test_modules_command():
def test_module_command():
"""Test that the modules command is available."""
result = typer.testing.CliRunner().invoke(cli.app, ["modules", "--help"])
result = typer.testing.CliRunner().invoke(cli.app, ["module", "--help"])
assert result.exit_code == 0
assert "List and get details of the defined modules and tools." in result.output
assert "List and get details of the defined modules." in result.output


def test_modules_list_and_wrong_api_version():
def test_module_list_and_wrong_api_version():
"""Test that the modules list command fails with an invalid API version."""
result = typer.testing.CliRunner().invoke(
cli.app, ["--api-version", "v2", "modules", "list"]
cli.app, ["--api-version", "v2", "module", "list"]
)
assert result.exit_code == 2
assert "Invalid value for '--api-version'" in result.output


def test_tool_command():
"""Test that the tools command is available."""
result = typer.testing.CliRunner().invoke(cli.app, ["tool", "--help"])
assert result.exit_code == 0
assert "List and get details of the defined tools." in result.output


def test_tool_list_and_wrong_api_version():
"""Test that the tools list command fails with an invalid API version."""
result = typer.testing.CliRunner().invoke(
cli.app, ["--api-version", "v2", "tool", "list"]
)
assert result.exit_code == 2
assert "Invalid value for '--api-version'" in result.output

0 comments on commit 29d969c

Please sign in to comment.