77import sys
88import time
99from sys import version_info
10- from typing import Iterable , List , Optional
10+ from typing import Any , Iterable , List , Optional
1111
1212import requests
1313from packaging import version
1414from requests import Response
1515
1616from linodecli .helpers import API_CA_PATH
1717
18- from .baked .operation import ExplicitNullValue , OpenAPIOperation
18+ from .baked .operation import (
19+ ExplicitEmptyListValue ,
20+ ExplicitNullValue ,
21+ OpenAPIOperation ,
22+ )
1923from .helpers import handle_url_overrides
2024
2125
@@ -199,6 +203,47 @@ def _build_request_url(ctx, operation, parsed_args) -> str:
199203 return result
200204
201205
206+ def _traverse_request_body (o : Any ) -> Any :
207+ """
208+ This function traverses is intended to be called immediately before
209+ request body serialization and contains special handling for dropping
210+ keys with null values and translating ExplicitNullValue instances into
211+ serializable null values.
212+ """
213+ if isinstance (o , dict ):
214+ result = {}
215+ for k , v in o .items ():
216+ # Implicit null values should be dropped from the request
217+ if v is None :
218+ continue
219+
220+ # Values that are expected to be serialized as empty lists
221+ # and explicit None values are converted here.
222+ # See: operation.py
223+ # NOTE: These aren't handled at the top-level of this function
224+ # because we don't want them filtered out in the step below.
225+ if isinstance (v , ExplicitEmptyListValue ):
226+ result [k ] = []
227+ continue
228+
229+ if isinstance (v , ExplicitNullValue ):
230+ result [k ] = None
231+ continue
232+
233+ value = _traverse_request_body (v )
234+
235+ # We should exclude implicit empty lists
236+ if not (isinstance (value , (dict , list )) and len (value ) < 1 ):
237+ result [k ] = value
238+
239+ return result
240+
241+ if isinstance (o , list ):
242+ return [_traverse_request_body (v ) for v in o ]
243+
244+ return o
245+
246+
202247def _build_request_body (ctx , operation , parsed_args ) -> Optional [str ]:
203248 if operation .method == "get" :
204249 # Get operations don't have a body
@@ -208,32 +253,18 @@ def _build_request_body(ctx, operation, parsed_args) -> Optional[str]:
208253 if ctx .defaults :
209254 parsed_args = ctx .config .update (parsed_args , operation .allowed_defaults )
210255
211- to_json = {}
212-
213- for k , v in vars (parsed_args ).items ():
214- # Skip null values
215- if v is None :
216- continue
217-
218- # Explicitly include ExplicitNullValues
219- if isinstance (v , ExplicitNullValue ):
220- to_json [k ] = None
221- continue
222-
223- to_json [k ] = v
224-
225256 expanded_json = {}
226257
227258 # expand paths
228- for k , v in to_json .items ():
259+ for k , v in vars ( parsed_args ) .items ():
229260 cur = expanded_json
230261 for part in k .split ("." )[:- 1 ]:
231262 if part not in cur :
232263 cur [part ] = {}
233264 cur = cur [part ]
234265 cur [k .split ("." )[- 1 ]] = v
235266
236- return json .dumps (expanded_json )
267+ return json .dumps (_traverse_request_body ( expanded_json ) )
237268
238269
239270def _print_request_debug_info (method , url , headers , body ):
0 commit comments