Skip to content

Commit 5f07222

Browse files
Remove grouping logic
1 parent 3903593 commit 5f07222

File tree

5 files changed

+125
-105
lines changed

5 files changed

+125
-105
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ test-unit:
6464
@mkdir -p /tmp/linode/.config
6565
@orig_xdg_config_home=$${XDG_CONFIG_HOME:-}; \
6666
export LINODE_CLI_TEST_MODE=1 XDG_CONFIG_HOME=/tmp/linode/.config; \
67-
pytest -v tests/unit; \
67+
pytest -vv tests/unit; \
6868
exit_code=$$?; \
6969
export XDG_CONFIG_HOME=$$orig_xdg_config_home; \
7070
exit $$exit_code

linodecli/baked/request.py

Lines changed: 10 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
Request details for a CLI Operation
33
"""
44

5-
from typing import List, Optional
5+
from typing import Dict, List, Optional, Tuple
66

77
from openapi3.paths import MediaType
88
from openapi3.schemas import Schema
9+
from typing_extensions import Self
910

1011
from linodecli.baked.parsing import simplify_description
1112
from linodecli.baked.response import OpenAPIResponse
@@ -26,7 +27,7 @@ def __init__( # pylint: disable=too-many-arguments
2627
is_parent: bool = False,
2728
parent: Optional[str] = None,
2829
depth: int = 0,
29-
option_path: Optional[List[str]] = None,
30+
option_variants: Optional[Dict[Tuple, Self]] = None,
3031
) -> None:
3132
"""
3233
Parses a single Schema node into a argument the CLI can use when making
@@ -46,6 +47,8 @@ def __init__( # pylint: disable=too-many-arguments
4647
:type parent: Optional[str]
4748
:param depth: The depth of this argument, or how many parent arguments this argument has.
4849
:type depth: int
50+
:param option_variants: A mapping of options, defined using oneOf in the to spec,
51+
to a variant of this argument.
4952
"""
5053
#: The name of this argument, mostly used for display and docs
5154
self.name = name
@@ -124,15 +127,18 @@ def __init__( # pylint: disable=too-many-arguments
124127
"instead of object's properties! This is a programming error."
125128
)
126129

127-
self.option_path = option_path
130+
#: A mapping between option keys and argument variants.
131+
#: This is necessary because argparse requires a merged list of options
132+
#: but we still want to preserve these arguments for use in help pages.
133+
self.option_variants = option_variants or {}
128134

129135

130136
def _parse_request_model(
131137
schema: Schema,
132138
prefix: Optional[str] = None,
133139
parent: Optional[str] = None,
134140
depth: int = 0,
135-
option_path: List[str] = None,
141+
option_path: Optional[Tuple[str, ...]] = None,
136142
) -> List[OpenAPIRequestArg]:
137143
"""
138144
Parses an OpenAPI schema into a list of OpenAPIRequest objects
@@ -149,29 +155,8 @@ def _parse_request_model(
149155
:rtype: list[OpenAPIRequestArg]
150156
"""
151157

152-
if option_path is None:
153-
option_path = []
154-
155158
args = []
156159

157-
if isinstance(schema, dict):
158-
schema = Schema(schema["path"], schema, schema._root)
159-
160-
for i, entry in enumerate(schema.oneOf or []):
161-
if isinstance(entry, dict):
162-
entry = Schema(schema.path + ["oneOf", i + 1], entry, schema._root)
163-
164-
args += _parse_request_model(
165-
entry,
166-
prefix=prefix,
167-
parent=parent,
168-
depth=depth,
169-
option_path=option_path + [entry.title],
170-
)
171-
172-
if schema.oneOf is not None:
173-
return args
174-
175160
properties, required = _aggregate_schema_properties(schema)
176161

177162
if properties is None:
@@ -218,7 +203,6 @@ def _parse_request_model(
218203
is_parent=True,
219204
parent=parent,
220205
depth=depth,
221-
option_path=option_path,
222206
)
223207
)
224208

@@ -238,7 +222,6 @@ def _parse_request_model(
238222
prefix=prefix,
239223
parent=parent,
240224
depth=depth,
241-
option_path=option_path,
242225
)
243226
)
244227

linodecli/baked/util.py

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"""
44

55
from collections import defaultdict
6-
from typing import Any, Dict, Set, Tuple
6+
from typing import Any, Dict, List, Set, Tuple
77

88
from openapi3.schemas import Schema
99

@@ -23,30 +23,31 @@ def _aggregate_schema_properties(
2323
properties = {}
2424
required = defaultdict(lambda: 0)
2525

26-
def _handle_schema(_schema: Schema):
27-
if _schema.properties is None:
28-
return
29-
26+
def __inner(path: List[str], entry: Schema):
3027
nonlocal schema_count
31-
schema_count += 1
3228

33-
properties.update(dict(_schema.properties))
29+
if isinstance(entry, dict):
30+
# TODO: Figure out why this happens (openapi3 package bug?)
31+
entry = Schema(path, entry, schema._root)
3432

35-
# Aggregate required keys and their number of usages.
36-
if _schema.required is not None:
37-
for key in _schema.required:
33+
if entry.properties is not None:
34+
schema_count += 1
35+
properties.update(entry.properties)
36+
37+
for key in entry.required or []:
3838
required[key] += 1
3939

40-
_handle_schema(schema)
40+
return
4141

42-
for field in ["oneOf", "anyOf", "allOf"]:
43-
for i, entry in enumerate(getattr(schema, field) or []):
44-
if isinstance(entry, dict):
45-
entry = Schema(
46-
schema.path + [field, i + 1], entry, schema._root
47-
)
42+
# If there aren't any properties, this could be a
43+
# composite schema
44+
for composition_field in ["oneOf", "allOf", "anyOf"]:
45+
for i, nested_entry in enumerate(
46+
getattr(entry, composition_field) or []
47+
):
48+
__inner(schema.path + [composition_field, str(i)], nested_entry)
4849

49-
_handle_schema(entry)
50+
__inner(schema.path, schema)
5051

5152
return (
5253
properties,

linodecli/help_pages.py

Lines changed: 25 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import sys
77
import textwrap
88
from collections import defaultdict
9-
from typing import Dict, List, Optional, Tuple
9+
from typing import Dict, List, Optional
1010

1111
from rich import box
1212
from rich import print as rprint
@@ -263,43 +263,33 @@ def _help_action_print_body_args(
263263
"""
264264
console.print(f"[bold]Arguments{f' ({title})' if title else ''}:[/]")
265265

266-
for option in _help_group_options(args):
267-
if option[0] is not None:
268-
# If this is an option with an explicit title,
269-
# display it here
270-
console.print(
271-
Padding.indent(f"[bold underline]{option[0]}[/]:", 4),
272-
)
273-
274-
for group in _help_group_arguments(option[1]):
275-
for arg in group:
276-
metadata = []
266+
for group in _help_group_arguments(args):
267+
for arg in group:
268+
metadata = []
277269

278-
if op.method in {"post", "put"} and arg.required:
279-
metadata.append("required")
270+
if op.method in {"post", "put"} and arg.required:
271+
metadata.append("required")
280272

281-
if arg.format == "json":
282-
metadata.append("JSON")
273+
if arg.format == "json":
274+
metadata.append("JSON")
283275

284-
if arg.nullable:
285-
metadata.append("nullable")
276+
if arg.nullable:
277+
metadata.append("nullable")
286278

287-
if arg.is_parent:
288-
metadata.append("conflicts with children")
279+
if arg.is_parent:
280+
metadata.append("conflicts with children")
289281

290-
prefix = (
291-
f" ({', '.join(metadata)})" if len(metadata) > 0 else ""
292-
)
282+
prefix = f" ({', '.join(metadata)})" if len(metadata) > 0 else ""
293283

294-
arg_text = Text.from_markup(
295-
f"[bold green]--{arg.path}[/][bold]{prefix}:[/] {arg.description_rich}"
296-
)
284+
arg_text = Text.from_markup(
285+
f"[bold green]--{arg.path}[/][bold]{prefix}:[/] {arg.description_rich}"
286+
)
297287

298-
console.print(
299-
Padding.indent(arg_text, (arg.depth * 2) + 2),
300-
)
288+
console.print(
289+
Padding.indent(arg_text, (arg.depth * 2) + 2),
290+
)
301291

302-
console.print()
292+
console.print()
303293

304294

305295
def _help_group_arguments(
@@ -318,10 +308,12 @@ def _help_group_arguments(
318308
if arg.read_only:
319309
continue
320310

321-
group_key = arg.parent
311+
group_key = arg.path.split(".")[-1]
312+
if arg.parent:
313+
group_key = arg.parent
322314

323-
if arg.path in arg_parents:
324-
group_key = arg.path
315+
if arg.path in arg_parents:
316+
group_key = arg.path
325317

326318
groups_tmp[group_key].append(arg)
327319

@@ -362,15 +354,3 @@ def _help_group_arguments(
362354
result += groups
363355

364356
return result
365-
366-
367-
def _help_group_options(
368-
args: List[OpenAPIRequestArg],
369-
) -> List[Tuple[Optional[str], List[OpenAPIRequestArg]]]:
370-
groups = defaultdict(list)
371-
372-
for arg in args:
373-
group_key = " - ".join(arg.option_path) if arg.option_path else None
374-
groups[group_key].append(arg)
375-
376-
return sorted(groups.items(), key=lambda v: v[0] or "")

tests/unit/test_help_pages.py

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,54 +14,110 @@ def test_group_arguments(self, capsys):
1414
# NOTE: We use SimpleNamespace here so we can do deep comparisons using ==
1515
args = [
1616
SimpleNamespace(
17-
read_only=False, required=False, depth=0, path="foobaz"
17+
read_only=False,
18+
required=False,
19+
depth=0,
20+
path="foobaz",
21+
parent=None,
1822
),
1923
SimpleNamespace(
20-
read_only=False, required=False, depth=0, path="foobar"
24+
read_only=False,
25+
required=False,
26+
depth=0,
27+
path="foobar",
28+
parent=None,
2129
),
2230
SimpleNamespace(
23-
read_only=False, required=True, depth=0, path="barfoo"
31+
read_only=False,
32+
required=True,
33+
depth=0,
34+
path="barfoo",
35+
parent=None,
2436
),
2537
SimpleNamespace(
26-
read_only=False, required=False, depth=0, path="foo"
38+
read_only=False,
39+
required=False,
40+
depth=0,
41+
path="foo",
42+
parent=None,
2743
),
2844
SimpleNamespace(
29-
read_only=False, required=False, depth=1, path="foo.bar"
45+
read_only=False,
46+
required=False,
47+
depth=1,
48+
path="foo.bar",
49+
parent="foo",
3050
),
3151
SimpleNamespace(
32-
read_only=False, required=False, depth=1, path="foo.foo"
52+
read_only=False,
53+
required=False,
54+
depth=1,
55+
path="foo.foo",
56+
parent="foo",
3357
),
3458
SimpleNamespace(
35-
read_only=False, required=True, depth=1, path="foo.baz"
59+
read_only=False,
60+
required=True,
61+
depth=1,
62+
path="foo.baz",
63+
parent="foo",
3664
),
3765
]
3866

3967
expected = [
4068
[
4169
SimpleNamespace(
42-
read_only=False, required=True, path="barfoo", depth=0
70+
read_only=False,
71+
required=True,
72+
path="barfoo",
73+
depth=0,
74+
parent=None,
4375
),
4476
],
4577
[
4678
SimpleNamespace(
47-
read_only=False, required=False, path="foobar", depth=0
79+
read_only=False,
80+
required=False,
81+
path="foobar",
82+
depth=0,
83+
parent=None,
4884
),
4985
SimpleNamespace(
50-
read_only=False, required=False, path="foobaz", depth=0
86+
read_only=False,
87+
required=False,
88+
path="foobaz",
89+
depth=0,
90+
parent=None,
5191
),
5292
],
5393
[
5494
SimpleNamespace(
55-
read_only=False, required=False, path="foo", depth=0
95+
read_only=False,
96+
required=False,
97+
path="foo",
98+
depth=0,
99+
parent=None,
56100
),
57101
SimpleNamespace(
58-
read_only=False, required=True, path="foo.baz", depth=1
102+
read_only=False,
103+
required=True,
104+
path="foo.baz",
105+
depth=1,
106+
parent="foo",
59107
),
60108
SimpleNamespace(
61-
read_only=False, required=False, path="foo.bar", depth=1
109+
read_only=False,
110+
required=False,
111+
path="foo.bar",
112+
depth=1,
113+
parent="foo",
62114
),
63115
SimpleNamespace(
64-
read_only=False, required=False, path="foo.foo", depth=1
116+
read_only=False,
117+
required=False,
118+
path="foo.foo",
119+
depth=1,
120+
parent="foo",
65121
),
66122
],
67123
]

0 commit comments

Comments
 (0)