22This module is responsible for handling HTTP requests to the Linode API.
33"""
44
5+ import itertools
56import json
67import sys
78from sys import version_info
8- from typing import Optional
9+ from typing import Iterable , List , Optional
910
1011import requests
1112from packaging import version
1213from requests import Response
1314
1415from linodecli .helpers import API_CA_PATH
1516
17+ from .baked .operation import ExplicitNullValue , OpenAPIOperation
18+ from .helpers import handle_url_overrides
19+
20+
21+ def get_all_pages (ctx , operation : OpenAPIOperation , args : List [str ]):
22+ """
23+ Receive all pages of a resource from multiple
24+ API responses then merge into one page.
25+
26+ :param ctx: The main CLI object
27+ """
28+
29+ ctx .page_size = 500
30+ ctx .page = 1
31+ result = do_request (ctx , operation , args ).json ()
32+
33+ total_pages = result .get ("pages" )
34+
35+ if total_pages and total_pages > 1 :
36+ pages_needed = range (2 , total_pages + 1 )
37+
38+ result = _merge_results_data (
39+ itertools .chain (
40+ (result ,),
41+ _generate_all_pages_results (ctx , operation , args , pages_needed ),
42+ )
43+ )
44+ return result
45+
1646
1747def do_request (
1848 ctx ,
@@ -26,7 +56,10 @@ def do_request(
2656 """
2757 Makes a request to an operation's URL and returns the resulting JSON, or
2858 prints and error if a non-200 comes back
59+
60+ :param ctx: The main CLI object
2961 """
62+ # TODO: Revisit using pre-built calls from OpenAPI
3063 method = getattr (requests , operation .method )
3164 headers = {
3265 "Authorization" : f"Bearer { ctx .config .get_token ()} " ,
@@ -67,6 +100,39 @@ def do_request(
67100 return result
68101
69102
103+ def _merge_results_data (results : Iterable [dict ]):
104+ """Merge multiple json response into one"""
105+
106+ iterator = iter (results )
107+ merged_result = next (iterator , None )
108+ if not merged_result :
109+ return None
110+
111+ if "pages" in merged_result :
112+ merged_result ["pages" ] = 1
113+ if "page" in merged_result :
114+ merged_result ["page" ] = 1
115+ if "data" in merged_result :
116+ merged_result ["data" ] += list (
117+ itertools .chain .from_iterable (r ["data" ] for r in iterator )
118+ )
119+ return merged_result
120+
121+
122+ def _generate_all_pages_results (
123+ ctx ,
124+ operation : OpenAPIOperation ,
125+ args : List [str ],
126+ pages_needed : Iterable [int ],
127+ ):
128+ """
129+ :param ctx: The main CLI object
130+ """
131+ for p in pages_needed :
132+ ctx .page = p
133+ yield do_request (ctx , operation , args ).json ()
134+
135+
70136def _build_filter_header (
71137 operation , parsed_args , filter_header = None
72138) -> Optional [str ]:
@@ -84,6 +150,10 @@ def _build_filter_header(
84150 if p .name in parsed_args_dict :
85151 del parsed_args_dict [p .name ]
86152
153+ # check for order-by and order
154+ order_by = parsed_args_dict .pop ("order_by" )
155+ order = parsed_args_dict .pop ("order" ) or "asc"
156+
87157 # The "+and" list to be used in the filter header
88158 filter_list = []
89159
@@ -95,14 +165,27 @@ def _build_filter_header(
95165 new_filters = [{k : j } for j in v ] if isinstance (v , list ) else [{k : v }]
96166 filter_list .extend (new_filters )
97167
168+ result = {}
98169 if len (filter_list ) > 0 :
99- return json .dumps ({"+and" : filter_list })
100-
101- return None
170+ if len (filter_list ) == 1 :
171+ result = filter_list [0 ]
172+ else :
173+ result ["+and" ] = filter_list
174+ if order_by is not None :
175+ result ["+order_by" ] = order_by
176+ result ["+order" ] = order
177+ return json .dumps (result ) if len (result ) > 0 else None
102178
103179
104180def _build_request_url (ctx , operation , parsed_args ) -> str :
105- result = operation .url .format (** vars (parsed_args ))
181+ target_server = handle_url_overrides (
182+ operation .url_base ,
183+ host = ctx .config .get_value ("api_host" ),
184+ version = ctx .config .get_value ("api_version" ),
185+ scheme = ctx .config .get_value ("api_scheme" ),
186+ )
187+
188+ result = f"{ target_server } { operation .url_path } " .format (** vars (parsed_args ))
106189
107190 if operation .method == "get" :
108191 result += f"?page={ ctx .page } &page_size={ ctx .page_size } "
@@ -121,7 +204,19 @@ def _build_request_body(ctx, operation, parsed_args) -> Optional[str]:
121204 parsed_args , operation .allowed_defaults , operation .action
122205 )
123206
124- to_json = {k : v for k , v in vars (parsed_args ).items () if v is not None }
207+ to_json = {}
208+
209+ for k , v in vars (parsed_args ).items ():
210+ # Skip null values
211+ if v is None :
212+ continue
213+
214+ # Explicitly include ExplicitNullValues
215+ if isinstance (v , ExplicitNullValue ):
216+ to_json [k ] = None
217+ continue
218+
219+ to_json [k ] = v
125220
126221 expanded_json = {}
127222
0 commit comments