Skip to content

Commit 556685d

Browse files
Merge pull request #732 from linode/dev
v5.56.3
2 parents c5cecf9 + fafe73e commit 556685d

File tree

8 files changed

+94
-65
lines changed

8 files changed

+94
-65
lines changed

Dockerfile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM python:3.11-slim AS builder
1+
FROM python:3.13-slim AS builder
22

33
ARG linode_cli_version
44

@@ -15,11 +15,11 @@ RUN make requirements
1515

1616
RUN LINODE_CLI_VERSION=$linode_cli_version GITHUB_TOKEN=$github_token make build
1717

18-
FROM python:3.11-slim
18+
FROM python:3.13-slim
1919

2020
COPY --from=builder /src/dist /dist
2121

22-
RUN pip3 install /dist/*.whl boto3
22+
RUN pip3 install --no-cache-dir /dist/*.whl boto3
2323

2424
RUN useradd -ms /bin/bash cli
2525
USER cli:cli

linodecli/baked/request.py

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

5+
from typing import List, Optional
6+
7+
from openapi3.paths import MediaType
58
from openapi3.schemas import Schema
69

710
from linodecli.baked.parsing import simplify_description
11+
from linodecli.baked.response import OpenAPIResponse
812
from linodecli.baked.util import _aggregate_schema_properties
913

1014

@@ -13,16 +17,16 @@ class OpenAPIRequestArg:
1317
A single argument to a request as defined by a Schema in the OpenAPI spec
1418
"""
1519

16-
def __init__(
20+
def __init__( # pylint: disable=too-many-arguments
1721
self,
18-
name,
19-
schema,
20-
required,
21-
prefix=None,
22-
is_parent=False,
23-
parent=None,
24-
depth=0,
25-
): # pylint: disable=too-many-arguments
22+
name: str,
23+
schema: Schema,
24+
required: bool,
25+
prefix: Optional[str] = None,
26+
is_parent: bool = False,
27+
parent: Optional[str] = None,
28+
depth: int = 0,
29+
) -> None:
2630
"""
2731
Parses a single Schema node into a argument the CLI can use when making
2832
requests.
@@ -120,9 +124,14 @@ def __init__(
120124
)
121125

122126

123-
def _parse_request_model(schema, prefix=None, parent=None, depth=0):
127+
def _parse_request_model(
128+
schema: Schema,
129+
prefix: Optional[str] = None,
130+
parent: Optional[str] = None,
131+
depth: int = 0,
132+
) -> List[OpenAPIRequestArg]:
124133
"""
125-
Parses a schema into a list of OpenAPIRequest objects
134+
Parses an OpenAPI schema into a list of OpenAPIRequest objects
126135
:param schema: The schema to parse as a request model
127136
:type schema: openapi3.Schema
128137
:param prefix: The prefix to add to all keys in this schema, as a json path
@@ -143,6 +152,7 @@ def _parse_request_model(schema, prefix=None, parent=None, depth=0):
143152
return args
144153

145154
for k, v in properties.items():
155+
# Handle nested objects which aren't read-only and have properties
146156
if (
147157
v.type == "object"
148158
and not v.readOnly
@@ -159,6 +169,8 @@ def _parse_request_model(schema, prefix=None, parent=None, depth=0):
159169
# parent arguments.
160170
depth=depth,
161171
)
172+
173+
# Handle arrays of objects that not marked as JSON
162174
elif (
163175
v.type == "array"
164176
and v.items
@@ -209,7 +221,7 @@ class OpenAPIRequest:
209221
on the MediaType object of a requestBody portion of an OpenAPI Operation
210222
"""
211223

212-
def __init__(self, request):
224+
def __init__(self, request: MediaType) -> None:
213225
"""
214226
:param request: The request's MediaType object in the OpenAPI spec,
215227
corresponding to the application/json data the endpoint
@@ -256,7 +268,7 @@ class OpenAPIFilteringRequest:
256268
endpoints where filters are accepted.
257269
"""
258270

259-
def __init__(self, response_model):
271+
def __init__(self, response_model: OpenAPIResponse) -> None:
260272
"""
261273
:param response_model: The parsed response model whose properties may be
262274
filterable.

linodecli/baked/response.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
Converting the processed OpenAPI Responses into something the CLI can work with
33
"""
44

5+
from typing import Optional
6+
57
from openapi3.paths import MediaType
8+
from openapi3.schemas import Schema
69

710
from linodecli.baked.util import _aggregate_schema_properties
811

@@ -30,7 +33,13 @@ class OpenAPIResponseAttr:
3033
from it.
3134
"""
3235

33-
def __init__(self, name, schema, prefix=None, nested_list_depth=0):
36+
def __init__(
37+
self,
38+
name: str,
39+
schema: Schema,
40+
prefix: Optional[str] = None,
41+
nested_list_depth: int = 0,
42+
) -> None:
3443
"""
3544
:param name: The key that held this schema in the properties list, representing
3645
its name in a response.
@@ -84,10 +93,13 @@ def __init__(self, name, schema, prefix=None, nested_list_depth=0):
8493
self.item_type = schema.items.type
8594

8695
@property
87-
def path(self):
96+
def path(self) -> str:
8897
"""
8998
This is a helper for filterable fields to return the json path to this
9099
element in a response.
100+
101+
:returns: The json path to the element in a response.
102+
:rtype: str
91103
"""
92104
return self.name
93105

@@ -129,6 +141,7 @@ def render_value(self, model, colorize=True):
129141
value = str(value)
130142
color = self.color_map.get(value) or self.color_map["default_"]
131143
value = f"[{color}]{value}[/]"
144+
# Convert None value to an empty string for better display
132145
if value is None:
133146
# Prints the word None if you don't change it
134147
value = ""
@@ -194,12 +207,14 @@ def _parse_response_model(schema, prefix=None, nested_list_depth=0):
194207
elif v.type == "object":
195208
attrs += _parse_response_model(v, prefix=pref)
196209
elif v.type == "array" and v.items.type == "object":
210+
# Parse arrays for objects recursively and increase the nesting depth
197211
attrs += _parse_response_model(
198212
v.items,
199213
prefix=pref,
200214
nested_list_depth=nested_list_depth + 1,
201215
)
202216
else:
217+
# Handle any other simple types
203218
attrs.append(
204219
OpenAPIResponseAttr(
205220
k, v, prefix=prefix, nested_list_depth=nested_list_depth
@@ -215,7 +230,7 @@ class OpenAPIResponse:
215230
responses section of an OpenAPI Operation
216231
"""
217232

218-
def __init__(self, response: MediaType):
233+
def __init__(self, response: MediaType) -> None:
219234
"""
220235
:param response: The response's MediaType object in the OpenAPI spec,
221236
corresponding to the application/json response type
@@ -287,15 +302,22 @@ def _fix_nested_list(self, json):
287302

288303
nested_lists = [c.strip() for c in self.nested_list.split(",")]
289304
result = []
305+
290306
for nested_list in nested_lists:
291307
path_parts = nested_list.split(".")
308+
292309
if not isinstance(json, list):
293310
json = [json]
311+
294312
for cur in json:
313+
# Get the nested list using the path
295314
nlist_path = cur
296315
for p in path_parts:
297316
nlist_path = nlist_path.get(p)
298317
nlist = nlist_path
318+
319+
# For each item in the nested list,
320+
# combine the parent properties with the nested item
299321
for item in nlist:
300322
cobj = {k: v for k, v in cur.items() if k != path_parts[0]}
301323
cobj["_split"] = path_parts[-1]

linodecli/configuration/config.py

Lines changed: 16 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,7 @@ def default_username(self) -> str:
8282
:returns: The `default-user` username or an empty string.
8383
:rtype: str
8484
"""
85-
if self.config.has_option("DEFAULT", "default-user"):
86-
return self.config.get("DEFAULT", "default-user")
87-
88-
return ""
85+
return self.config.get("DEFAULT", "default-user", fallback="")
8986

9087
def set_user(self, username: str):
9188
"""
@@ -153,15 +150,11 @@ def get_token(self) -> str:
153150
:rtype: str
154151
"""
155152
if self.used_env_token:
156-
return os.environ.get(ENV_TOKEN_NAME, None)
153+
return os.getenv(ENV_TOKEN_NAME, None)
157154

158-
if self.config.has_option(
159-
self.username or self.default_username(), "token"
160-
):
161-
return self.config.get(
162-
self.username or self.default_username(), "token"
163-
)
164-
return ""
155+
return self.config.get(
156+
self.username or self.default_username(), "token", fallback=""
157+
)
165158

166159
def get_value(self, key: str) -> Optional[Any]:
167160
"""
@@ -180,12 +173,9 @@ def get_value(self, key: str) -> Optional[Any]:
180173
current user.
181174
:rtype: any
182175
"""
183-
username = self.username or self.default_username()
184-
185-
if not self.config.has_option(username, key):
186-
return None
187-
188-
return self.config.get(username, key)
176+
return self.config.get(
177+
self.username or self.default_username(), key, fallback=None
178+
)
189179

190180
def get_bool(self, key: str) -> bool:
191181
"""
@@ -204,12 +194,10 @@ def get_bool(self, key: str) -> bool:
204194
current user.
205195
:rtype: any
206196
"""
207-
username = self.username or self.default_username()
208-
209-
if not self.config.has_option(username, key):
210-
return False
211197

212-
return self.config.getboolean(username, key)
198+
return self.config.getboolean(
199+
self.username or self.default_username(), key, fallback=False
200+
)
213201

214202
# plugin methods - these are intended for plugins to utilize to store their
215203
# own persistent config information
@@ -255,13 +243,10 @@ def plugin_get_value(self, key: str) -> Optional[Any]:
255243
"No running plugin to retrieve configuration for!"
256244
)
257245

258-
username = self.username or self.default_username()
246+
username = self.username or self.default_username() or "DEFAULT"
259247
full_key = f"plugin-{self.running_plugin}-{key}"
260248

261-
if not self.config.has_option(username, full_key):
262-
return None
263-
264-
return self.config.get(username, full_key)
249+
return self.config.get(username, full_key, fallback=None)
265250

266251
# TODO: this is more of an argparsing function than it is a config function
267252
# might be better to move this to argparsing during refactor and just have
@@ -308,11 +293,8 @@ def update(
308293
# these don't get included in the updated namespace
309294
if key.startswith("plugin-"):
310295
continue
311-
value = None
312-
if self.config.has_option(username, key):
313-
value = self.config.get(username, key)
314-
else:
315-
value = ns_dict[key]
296+
297+
value = self.config.get(username, key, fallback=ns_dict.get(key))
316298

317299
if not value:
318300
continue
@@ -553,7 +535,7 @@ def _handle_no_default_user(self): # pylint: disable=too-many-branches
553535

554536
if len(users) == 0:
555537
# config is new or _really_ old
556-
token = self.config.get("DEFAULT", "token")
538+
token = self.config.get("DEFAULT", "token", fallback=None)
557539

558540
if token is not None:
559541
# there's a token in the config - configure that user

linodecli/configuration/helpers.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,4 @@ def _config_get_with_default(
299299
:returns: The value pulled from the config or the default value.
300300
:rtype: Any
301301
"""
302-
return (
303-
config.get(user, field) if config.has_option(user, field) else default
304-
)
302+
return config.get(user, field, fallback=default)

wiki/Output.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
By default, the CLI displays on some pre-selected fields for a given type of
66
response. If you want to see everything, just ask::
77
```bash
8-
linode-cli linodes list --all
8+
linode-cli linodes list --all-columns
99
```
1010

11-
Using `--all` will cause the CLI to display all returned columns of output.
12-
Note that this will probably be hard to read on normal-sized screens for most
13-
actions.
11+
Using `--all-columns` will cause the CLI to display all returned columns of
12+
output. Note that this will probably be hard to read on normal-sized screens
13+
for most actions.
1414

1515
If you want even finer control over your output, you can request specific columns
1616
be displayed::
@@ -48,11 +48,12 @@ linode-cli linodes list --no-headers --text
4848

4949
To get JSON output from the CLI, simple request it::
5050
```bash
51-
linode-cli linodes list --json --all
51+
linode-cli linodes list --json --all-columns
5252
```
5353

54-
While the `--all` is optional, you probably want to see all output fields in
55-
your JSON output. If you want your JSON pretty-printed, we can do that too::
54+
While the `--all-columns` is optional, you probably want to see all output
55+
fields in your JSON output. If you want your JSON pretty-printed, we can do
56+
that too::
5657
```bash
57-
linode-cli linodes list --json --pretty --all
58+
linode-cli linodes list --json --pretty --all-columns
5859
```

wiki/Uninstallation.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Uninstallation
2+
3+
## PyPi
4+
5+
```bash
6+
pip3 uninstall linode-cli
7+
```
8+
9+
If you would like to remove the config file (easy to re-create) you must do so manually.
10+
11+
```bash
12+
rm $HOME/.config/linode-cli
13+
```

wiki/_Sidebar.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
- [Installation](./Installation)
2+
- [Uninstallation](./Uninstallation)
23
- [Configuration](./Configuration)
34
- [Usage](./Usage)
45
- [Output](./Output)
@@ -7,4 +8,4 @@
78
- [Overview](./Development%20-%20Overview)
89
- [Skeleton](./Development%20-%20Skeleton)
910
- [Setup](./Development%20-%20Setup)
10-
- [Testing](./Development%20-%20Testing)
11+
- [Testing](./Development%20-%20Testing)

0 commit comments

Comments
 (0)