From ba5cc907f6570a675937eaed60f058aaedf2093e Mon Sep 17 00:00:00 2001 From: Ye Chen Date: Mon, 10 Feb 2025 14:37:15 -0500 Subject: [PATCH] multiple improvements --- linodecli/baked/request.py | 38 ++++++++++++++++++++++++------------- linodecli/baked/response.py | 28 ++++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/linodecli/baked/request.py b/linodecli/baked/request.py index 4023d743..e429895a 100644 --- a/linodecli/baked/request.py +++ b/linodecli/baked/request.py @@ -2,9 +2,13 @@ Request details for a CLI Operation """ +from typing import List, Optional + +from openapi3.paths import MediaType from openapi3.schemas import Schema from linodecli.baked.parsing import simplify_description +from linodecli.baked.response import OpenAPIResponse from linodecli.baked.util import _aggregate_schema_properties @@ -13,16 +17,16 @@ class OpenAPIRequestArg: A single argument to a request as defined by a Schema in the OpenAPI spec """ - def __init__( + def __init__( # pylint: disable=too-many-arguments self, - name, - schema, - required, - prefix=None, - is_parent=False, - parent=None, - depth=0, - ): # pylint: disable=too-many-arguments + name: str, + schema: Schema, + required: bool, + prefix: Optional[str] = None, + is_parent: bool = False, + parent: Optional[str] = None, + depth: int = 0, + ) -> None: """ Parses a single Schema node into a argument the CLI can use when making requests. @@ -120,9 +124,14 @@ def __init__( ) -def _parse_request_model(schema, prefix=None, parent=None, depth=0): +def _parse_request_model( + schema: Schema, + prefix: Optional[str] = None, + parent: Optional[str] = None, + depth: int = 0, +) -> List[OpenAPIRequestArg]: """ - Parses a schema into a list of OpenAPIRequest objects + Parses an OpenAPI schema into a list of OpenAPIRequest objects :param schema: The schema to parse as a request model :type schema: openapi3.Schema :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): return args for k, v in properties.items(): + # Handle nested objects which aren't read-only and have properties if ( v.type == "object" and not v.readOnly @@ -159,6 +169,8 @@ def _parse_request_model(schema, prefix=None, parent=None, depth=0): # parent arguments. depth=depth, ) + + # Handle arrays of objects that not marked as JSON elif ( v.type == "array" and v.items @@ -209,7 +221,7 @@ class OpenAPIRequest: on the MediaType object of a requestBody portion of an OpenAPI Operation """ - def __init__(self, request): + def __init__(self, request: MediaType) -> None: """ :param request: The request's MediaType object in the OpenAPI spec, corresponding to the application/json data the endpoint @@ -256,7 +268,7 @@ class OpenAPIFilteringRequest: endpoints where filters are accepted. """ - def __init__(self, response_model): + def __init__(self, response_model: OpenAPIResponse) -> None: """ :param response_model: The parsed response model whose properties may be filterable. diff --git a/linodecli/baked/response.py b/linodecli/baked/response.py index 9d08a7a4..9e64d0be 100644 --- a/linodecli/baked/response.py +++ b/linodecli/baked/response.py @@ -2,7 +2,10 @@ Converting the processed OpenAPI Responses into something the CLI can work with """ +from typing import Optional + from openapi3.paths import MediaType +from openapi3.schemas import Schema from linodecli.baked.util import _aggregate_schema_properties @@ -30,7 +33,13 @@ class OpenAPIResponseAttr: from it. """ - def __init__(self, name, schema, prefix=None, nested_list_depth=0): + def __init__( + self, + name: str, + schema: Schema, + prefix: Optional[str] = None, + nested_list_depth: int = 0, + ) -> None: """ :param name: The key that held this schema in the properties list, representing its name in a response. @@ -84,10 +93,13 @@ def __init__(self, name, schema, prefix=None, nested_list_depth=0): self.item_type = schema.items.type @property - def path(self): + def path(self) -> str: """ This is a helper for filterable fields to return the json path to this element in a response. + + :returns: The json path to the element in a response. + :rtype: str """ return self.name @@ -129,6 +141,7 @@ def render_value(self, model, colorize=True): value = str(value) color = self.color_map.get(value) or self.color_map["default_"] value = f"[{color}]{value}[/]" + # Convert None value to an empty string for better display if value is None: # Prints the word None if you don't change it value = "" @@ -194,12 +207,14 @@ def _parse_response_model(schema, prefix=None, nested_list_depth=0): elif v.type == "object": attrs += _parse_response_model(v, prefix=pref) elif v.type == "array" and v.items.type == "object": + # Parse arrays for objects recursively and increase the nesting depth attrs += _parse_response_model( v.items, prefix=pref, nested_list_depth=nested_list_depth + 1, ) else: + # Handle any other simple types attrs.append( OpenAPIResponseAttr( k, v, prefix=prefix, nested_list_depth=nested_list_depth @@ -215,7 +230,7 @@ class OpenAPIResponse: responses section of an OpenAPI Operation """ - def __init__(self, response: MediaType): + def __init__(self, response: MediaType) -> None: """ :param response: The response's MediaType object in the OpenAPI spec, corresponding to the application/json response type @@ -287,15 +302,22 @@ def _fix_nested_list(self, json): nested_lists = [c.strip() for c in self.nested_list.split(",")] result = [] + for nested_list in nested_lists: path_parts = nested_list.split(".") + if not isinstance(json, list): json = [json] + for cur in json: + # Get the nested list using the path nlist_path = cur for p in path_parts: nlist_path = nlist_path.get(p) nlist = nlist_path + + # For each item in the nested list, + # combine the parent properties with the nested item for item in nlist: cobj = {k: v for k, v in cur.items() if k != path_parts[0]} cobj["_split"] = path_parts[-1]