Skip to content

Commit 084c2de

Browse files
GWealecopybara-github
authored andcommitted
fix: Make sure request bodies without explicit names are named 'body'
The `Parameter` class now provides default Python names based on the parameter location when the original name is empty. This prevents parameters from having an empty string as their Python name, especially for request bodies defined without a top-level name. Close #2213 Co-authored-by: George Weale <gweale@google.com> PiperOrigin-RevId: 834850255
1 parent bf8b85d commit 084c2de

File tree

4 files changed

+76
-7
lines changed

4 files changed

+76
-7
lines changed

src/google/adk/tools/openapi_tool/common/common.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,9 @@ class ApiParameter(BaseModel):
6464
required: bool = False
6565

6666
def model_post_init(self, _: Any):
67-
self.py_name = (
68-
self.py_name
69-
if self.py_name
70-
else rename_python_keywords(_to_snake_case(self.original_name))
71-
)
67+
if not self.py_name:
68+
inferred_name = rename_python_keywords(_to_snake_case(self.original_name))
69+
self.py_name = inferred_name or self._default_py_name()
7270
if isinstance(self.param_schema, str):
7371
self.param_schema = Schema.model_validate_json(self.param_schema)
7472

@@ -77,6 +75,16 @@ def model_post_init(self, _: Any):
7775
self.type_hint = TypeHintHelper.get_type_hint(self.param_schema)
7876
return self
7977

78+
def _default_py_name(self) -> str:
79+
location_defaults = {
80+
'body': 'body',
81+
'query': 'query_param',
82+
'path': 'path_param',
83+
'header': 'header_param',
84+
'cookie': 'cookie_param',
85+
}
86+
return location_defaults.get(self.param_location or '', 'value')
87+
8088
@model_serializer
8189
def _serialize(self):
8290
return {

src/google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,19 @@ def _process_request_body(self):
139139
)
140140
)
141141
else:
142+
# Prefer explicit body name to avoid empty keys when schema lacks type
143+
# information (e.g., oneOf/anyOf/allOf) while retaining legacy behavior
144+
# for simple scalar types.
145+
if schema and (schema.oneOf or schema.anyOf or schema.allOf):
146+
param_name = 'body'
147+
elif not schema or not schema.type:
148+
param_name = 'body'
149+
else:
150+
param_name = ''
151+
142152
self._params.append(
143-
# Empty name for unnamed body param
144153
ApiParameter(
145-
original_name='',
154+
original_name=param_name,
146155
param_location='body',
147156
param_schema=schema,
148157
description=description,

tests/unittests/tools/openapi_tool/common/test_common.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,24 @@ def test_api_parameter_keyword_rename(self):
7474
)
7575
assert param.py_name == 'param_in'
7676

77+
def test_api_parameter_uses_location_default_when_name_missing(self):
78+
schema = Schema(type='string')
79+
param = ApiParameter(
80+
original_name='',
81+
param_location='body',
82+
param_schema=schema,
83+
)
84+
assert param.py_name == 'body'
85+
86+
def test_api_parameter_uses_value_default_when_location_unknown(self):
87+
schema = Schema(type='integer')
88+
param = ApiParameter(
89+
original_name='',
90+
param_location='',
91+
param_schema=schema,
92+
)
93+
assert param.py_name == 'value'
94+
7795
def test_api_parameter_custom_py_name(self):
7896
schema = Schema(type='integer')
7997
param = ApiParameter(

tests/unittests/tools/openapi_tool/openapi_spec_parser/test_operation_parser.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,40 @@ def test_process_request_body_no_name():
164164
assert parser._params[0].param_location == 'body'
165165

166166

167+
def test_process_request_body_one_of_schema_assigns_name():
168+
"""Ensures oneOf bodies result in a named parameter."""
169+
operation = Operation(
170+
operationId='one_of_request',
171+
requestBody=RequestBody(
172+
content={
173+
'application/json': MediaType(
174+
schema=Schema(
175+
oneOf=[
176+
Schema(
177+
type='object',
178+
properties={
179+
'type': Schema(type='string'),
180+
'stage': Schema(type='string'),
181+
},
182+
)
183+
],
184+
discriminator={'propertyName': 'type'},
185+
)
186+
)
187+
}
188+
),
189+
responses={'200': Response(description='ok')},
190+
)
191+
parser = OperationParser(operation)
192+
params = parser.get_parameters()
193+
assert len(params) == 1
194+
assert params[0].original_name == 'body'
195+
assert params[0].py_name == 'body'
196+
schema = parser.get_json_schema()
197+
assert 'body' in schema['properties']
198+
assert '' not in schema['properties']
199+
200+
167201
def test_process_request_body_empty_object():
168202
"""Test _process_request_body with a schema that is of type object but with no properties."""
169203
operation = Operation(

0 commit comments

Comments
 (0)