Skip to content

Commit 8657cf1

Browse files
author
Doug Borg
committed
feat: relaxed request body param handling and improved operationId derivation
1 parent 72f56a8 commit 8657cf1

File tree

1 file changed

+55
-47
lines changed

1 file changed

+55
-47
lines changed

src/openapi_python_generator/language_converters/python/service_generator.py

Lines changed: 55 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
Operation,
77
PathItem,
88
Reference,
9+
RequestBody,
910
Response,
1011
Schema,
1112
)
@@ -94,46 +95,56 @@ def is_schema_type(obj: Any) -> bool:
9495

9596

9697
def generate_body_param(operation: Operation) -> Union[str, None]:
98+
"""Return JSON body expression; tolerant of missing or primitive schema types.
99+
100+
Behavior:
101+
- Reference body -> data.dict()
102+
- Array of model-like items -> [i.dict() for i in data]
103+
- Array of primitives / unknown -> data
104+
- Object / object-like (has properties/allOf/oneOf/anyOf/additionalProperties) -> data
105+
- Primitive / missing type -> data
106+
"""
97107
if operation.requestBody is None:
98108
return None
99-
else:
100-
if isinstance(operation.requestBody, Reference30) or isinstance(
101-
operation.requestBody, Reference31
102-
):
103-
return "data.dict()"
104-
105-
if operation.requestBody.content is None:
106-
return None # pragma: no cover
107-
108-
if operation.requestBody.content.get("application/json") is None:
109-
return None # pragma: no cover
110-
111-
media_type = operation.requestBody.content.get("application/json")
112-
113-
if media_type is None:
114-
return None # pragma: no cover
115-
116-
if isinstance(
117-
media_type.media_type_schema, (Reference, Reference30, Reference31)
118-
):
119-
return "data.dict()"
120-
elif hasattr(media_type.media_type_schema, "ref"):
121-
# Handle Reference objects from different OpenAPI versions
122-
return "data.dict()"
123-
elif isinstance(media_type.media_type_schema, (Schema, Schema30, Schema31)):
124-
schema = media_type.media_type_schema
125-
if schema.type == "array":
109+
# Check for Reference across all versions
110+
if isinstance(operation.requestBody, (Reference, Reference30, Reference31)):
111+
return "data.dict()"
112+
# Defensive access to content attribute
113+
content = getattr(operation.requestBody, "content", None)
114+
if not isinstance(content, dict):
115+
return None
116+
mt = content.get("application/json")
117+
if mt is None:
118+
return None
119+
schema = getattr(mt, "media_type_schema", None)
120+
# Check for Reference across all versions
121+
if isinstance(schema, (Reference, Reference30, Reference31)):
122+
return "data.dict()"
123+
# Check for Schema across all versions
124+
if isinstance(schema, (Schema, Schema30, Schema31)):
125+
# Array handling
126+
if getattr(schema, "type", None) == "array":
127+
items = getattr(schema, "items", None)
128+
if isinstance(items, (Reference, Reference30, Reference31)):
126129
return "[i.dict() for i in data]"
127-
elif schema.type == "object":
128-
return "data"
129-
else:
130-
raise Exception(
131-
f"Unsupported schema type for request body: {schema.type}"
132-
) # pragma: no cover
133-
else:
134-
raise Exception(
135-
f"Unsupported schema type for request body: {type(media_type.media_type_schema)}"
136-
) # pragma: no cover
130+
if isinstance(items, (Schema, Schema30, Schema31)):
131+
if getattr(items, "type", None) == "object" or any(
132+
getattr(items, attr, None) for attr in ["properties", "allOf", "oneOf", "anyOf"]
133+
):
134+
return "[i.dict() for i in data]"
135+
return "data"
136+
# Object-like
137+
if (
138+
getattr(schema, "type", None) == "object"
139+
or any(
140+
getattr(schema, attr, None)
141+
for attr in ["properties", "allOf", "oneOf", "anyOf", "additionalProperties"]
142+
)
143+
):
144+
return "data"
145+
# Primitive / unspecified
146+
return "data"
147+
return None
137148

138149

139150
def generate_params(operation: Operation) -> str:
@@ -225,17 +236,14 @@ def _generate_params_from_content(content: Any):
225236
return params + default_params
226237

227238

228-
def generate_operation_id(
229-
operation: Operation, http_op: str, path_name: Optional[str] = None
230-
) -> str:
231-
if operation.operationId is not None:
239+
def generate_operation_id(operation: Operation, http_op: str, path_name: Optional[str] = None) -> str:
240+
if operation.operationId:
232241
return common.normalize_symbol(operation.operationId)
233-
elif path_name is not None:
234-
return common.normalize_symbol(f"{http_op}_{path_name}")
235-
else:
236-
raise Exception(
237-
f"OperationId is not defined for {http_op} of path_name {path_name} --> {operation.summary}"
238-
) # pragma: no cover
242+
if path_name:
243+
# Insert underscore before parameter placeholders so /lists/{listId} -> lists_{listId}
244+
cleaned = re.sub(r"\{([^}]+)\}", r"_\1", path_name)
245+
return common.normalize_symbol(f"{http_op}_{cleaned}")
246+
raise RuntimeError("Missing operationId and path_name for operation")
239247

240248

241249
def _generate_params(

0 commit comments

Comments
 (0)