Skip to content

Commit c11cbe4

Browse files
committed
Improving help info for call command (detailed information about endpoint and body).
1 parent 46548f4 commit c11cbe4

File tree

4 files changed

+97
-37
lines changed

4 files changed

+97
-37
lines changed

README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,23 @@ The client can also be extended with plugins that can streamline common request
144144
recodex info swagger
145145
```
146146

147-
### Examples
147+
## Examples
148148

149149
The following examples can be used as snippets to quickly perform common tasks (just replace parameters as needed).
150150

151+
### Group management
152+
153+
**Add a student to a group:**
154+
```bash
155+
recodex call groups.add_student <group-id> <user-id>
156+
```
157+
158+
**Remove a student from a group:**
159+
```bash
160+
recodex call groups.remove_student <group-id> <user-id>
161+
```
162+
163+
**Add a member (admin) to a group (this does not work for students):**
164+
```bash
165+
recodex call groups.add_member <group-id> <user-id> --body {\"type\":\"admin\"}
166+
```

src/recodex_cli/call_command/help_printer.py

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,7 @@ class HelpPrinter:
1515
body_panel: Panel | None = None
1616
console: Console | None = None
1717

18-
def __get_param_panel(self, params: list[dict], title) -> Panel:
19-
rows_tokenized = []
20-
for param in params:
21-
rows_tokenized.append(cmd_utils.get_param_info_text_tokens(param))
22-
23-
rows = ["" for i in range(len(rows_tokenized))]
24-
25-
self.__add_text_token(rows, rows_tokenized, "name", "bright_cyan")
26-
self.__add_text_token(rows, rows_tokenized, "type", "yellow")
27-
self.__add_text_token(rows, rows_tokenized, "opt", "magenta")
28-
self.__add_text_token(rows, rows_tokenized, "desc")
29-
30-
text = "\n".join(rows)
31-
return self.__create_panel(text, title)
32-
33-
def __add_text_token(
18+
def _add_text_token(
3419
self,
3520
texts: list[str],
3621
token_dicts: list[dict[str, str]],
@@ -53,7 +38,7 @@ def __add_text_token(
5338
token = f"[{color}]{token}[/{color}]"
5439
texts[i] += token
5540

56-
def __create_panel(self, text: str, title: str) -> Panel:
41+
def _create_panel(self, text: str, title: str) -> Panel:
5742
# escape opening brackets
5843

5944
return Panel(
@@ -63,38 +48,74 @@ def __create_panel(self, text: str, title: str) -> Panel:
6348
title_align="left",
6449
)
6550

66-
def __prepare(self, ctx: click.Context, endpoint: str, verbose: bool):
51+
def _prepare_param_table(self, rows_tokenized: list[dict]) -> str:
52+
rows = ["" for i in range(len(rows_tokenized))]
53+
self._add_text_token(rows, rows_tokenized, "name", "bright_cyan")
54+
self._add_text_token(rows, rows_tokenized, "type", "yellow")
55+
self._add_text_token(rows, rows_tokenized, "opt", "magenta")
56+
self._add_text_token(rows, rows_tokenized, "desc")
57+
return "\n".join(rows)
58+
59+
def _get_param_panel(self, params: list[dict], title: str) -> Panel:
60+
rows_tokenized = []
61+
for param in params:
62+
rows_tokenized.append(cmd_utils.get_param_info_text_tokens(param))
63+
64+
return self._create_panel(self._prepare_param_table(rows_tokenized), title)
65+
66+
def _get_body_panel(self, body_schema: dict | None, body_types: list[str], title: str) -> Panel:
67+
if not body_types:
68+
body_types = ["[unknown]"]
69+
70+
types_text = "[bright_black]A body is required of the following type(s):[/bright_black] " + ", ".join(
71+
body_types)
72+
if body_schema is None:
73+
return self._create_panel(types_text, title)
74+
75+
types_text += "\n[bright_black]Body schema:[/bright_black]\n"
76+
rows_tokenized = cmd_utils.get_body_params_info_text_tokens(body_schema)
77+
return self._create_panel(types_text + self._prepare_param_table(rows_tokenized), title)
78+
79+
def _prepare(self, ctx: click.Context, endpoint: str, verbose: bool):
6780
self.ctx = ctx
6881
self.formatter = ctx.make_formatter()
6982

7083
self.print_detail = endpoint != ""
7184
if self.print_detail:
7285
def __init():
73-
presenter, action = cmd_utils.execute_with_verbosity(
86+
self.presenter, self.action = cmd_utils.execute_with_verbosity(
7487
lambda: cmd_utils.parse_endpoint_or_throw(endpoint),
7588
verbose
7689
)
7790
endpoint_resolver = EndpointResolver()
7891
self.console = Console()
79-
path_params = endpoint_resolver.get_path_params(presenter, action)
80-
query_params = endpoint_resolver.get_query_params(presenter, action)
92+
path_params = endpoint_resolver.get_path_params(self.presenter, self.action)
93+
query_params = endpoint_resolver.get_query_params(self.presenter, self.action)
94+
body_schema = endpoint_resolver.get_request_body_schema(self.presenter, self.action)
95+
body_types = endpoint_resolver.get_request_body_types(self.presenter, self.action)
8196

97+
self.description = endpoint_resolver.get_endpoint_description(self.presenter, self.action)
8298
self.path_panel, self.query_panel, self.body_panel = None, None, None
8399
if len(path_params) > 0:
84-
self.path_panel = self.__get_param_panel(path_params, "Path Parameters Detail")
100+
self.path_panel = self._get_param_panel(path_params, "Path Parameters Detail")
85101
if len(query_params) > 0:
86-
self.query_panel = self.__get_param_panel(query_params, "Query Parameters Detail")
87-
if endpoint_resolver.endpoint_has_body(presenter, action):
88-
self.body_panel = self.__create_panel("The endpoint expects a JSON body.", "Body Detail")
102+
self.query_panel = self._get_param_panel(query_params, "Query Parameters Detail")
103+
if endpoint_resolver.endpoint_has_body(self.presenter, self.action):
104+
self.body_panel = self._get_body_panel(body_schema, body_types, "Body Detail")
89105
cmd_utils.execute_with_verbosity(__init, verbose)
90106

91107
def print(self, ctx: click.Context, endpoint: str, verbose: bool):
92-
self.__prepare(ctx, endpoint, verbose)
108+
self._prepare(ctx, endpoint, verbose)
93109
self.ctx.command.format_help(self.ctx, self.formatter)
94110

95111
if self.print_detail:
96112
if self.console is None:
97113
raise Exception("The Console was not instantiated properly.")
114+
self.console.print("\n[bold]Endpoint Detail[/bold]:")
115+
self.console.print(f"Presenter: [yellow]{self.presenter}[/yellow]")
116+
self.console.print(f"Action: [yellow]{self.action}[/yellow]")
117+
self.console.print(f"Description: [bright_black]{self.description}[/bright_black]\n")
118+
98119
if self.path_panel:
99120
self.console.print(self.path_panel)
100121
if self.query_panel:

src/recodex_cli/console.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323

2424
@app.command()
25-
def call(
25+
def call( # noqa: C901
2626
endpoint: Annotated[
2727
str, typer.Argument(help="Endpoint identifier in <presenter.action> format", is_eager=True)
2828
] = "",

src/recodex_cli/utils/cmd_utils.py

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -117,19 +117,42 @@ def get_param_info_text_tokens(param: dict) -> dict:
117117
"""
118118

119119
# get info
120-
name = param["name"]
121-
desc = param["description"]
122-
type = param["schema"]["type"]
123-
if param["schema"]["nullable"]:
120+
schema = param.get("schema", {})
121+
type = schema.get("type", "?")
122+
if schema.get("nullable", False):
124123
type += "|null"
125-
required = param["required"]
126124

127125
tokens = {
128-
"name": name,
126+
"name": param.get("name", ""),
129127
"type": f"[{type}]",
128+
"opt": "" if param.get("required", False) else "[OPT]",
129+
"desc": param.get("description", "")
130130
}
131-
if not required:
132-
tokens["opt"] = "[OPT]"
133-
tokens["desc"] = desc
134131

135132
return tokens
133+
134+
135+
def get_body_params_info_text_tokens(schema: dict) -> list[dict]:
136+
"""Parses a body schema and yields a description of the body parameters.
137+
138+
Returns:
139+
list[dict]: List of top-level parameters, each represented as a dictionary containing
140+
the name, type, desc, opt keys describing the body parameter.
141+
"""
142+
143+
params = []
144+
required = schema.get("required", [])
145+
146+
for name, prop in schema.get("properties", {}).items():
147+
type = prop.get("type", "?")
148+
if prop.get("nullable", False):
149+
type += "|null"
150+
tokens = {
151+
"name": name,
152+
"type": f"[{type}]",
153+
"desc": prop.get("description", ""),
154+
"opt": "" if name in required else "[OPT]"
155+
}
156+
params.append(tokens)
157+
158+
return params

0 commit comments

Comments
 (0)