Skip to content

Commit 2c38314

Browse files
author
Doug Borg
committed
feat: relaxed request body param handling and improved operationId derivation
1 parent 9430d18 commit 2c38314

File tree

1 file changed

+64
-53
lines changed

1 file changed

+64
-53
lines changed

src/openapi_python_generator/language_converters/python/service_generator.py

Lines changed: 64 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import re
2-
from typing import Dict
3-
from typing import List
4-
from typing import Literal
5-
from typing import Optional
6-
from typing import Tuple
7-
from typing import Union
2+
from typing import Dict, List, Literal, Optional, Tuple, Union
83

94
import click
10-
from openapi_pydantic.v3.v3_0 import Reference, Schema, Operation, Parameter, RequestBody, Response, MediaType, PathItem
5+
from openapi_pydantic.v3.v3_0 import (
6+
Reference,
7+
Schema,
8+
Operation,
9+
Parameter,
10+
RequestBody,
11+
Response,
12+
MediaType,
13+
PathItem,
14+
)
1115

1216
from openapi_python_generator.language_converters.python import common
1317
from openapi_python_generator.language_converters.python.common import normalize_symbol
@@ -28,39 +32,52 @@
2832

2933

3034
def generate_body_param(operation: Operation) -> Union[str, None]:
35+
"""Return JSON body expression; tolerant of missing or primitive schema types.
36+
37+
Behavior:
38+
- Reference body -> data.dict()
39+
- Array of model-like items -> [i.dict() for i in data]
40+
- Array of primitives / unknown -> data
41+
- Object / object-like (has properties/allOf/oneOf/anyOf/additionalProperties) -> data
42+
- Primitive / missing type -> data
43+
"""
3144
if operation.requestBody is None:
3245
return None
33-
else:
34-
if isinstance(operation.requestBody, Reference):
35-
return "data.dict()"
36-
37-
if operation.requestBody.content is None:
38-
return None # pragma: no cover
39-
40-
if operation.requestBody.content.get("application/json") is None:
41-
return None # pragma: no cover
42-
43-
media_type = operation.requestBody.content.get("application/json")
44-
45-
if media_type is None:
46-
return None # pragma: no cover
47-
48-
if isinstance(media_type.media_type_schema, Reference):
49-
return "data.dict()"
50-
elif isinstance(media_type.media_type_schema, Schema):
51-
schema = media_type.media_type_schema
52-
if schema.type == "array":
46+
if isinstance(operation.requestBody, Reference):
47+
return "data.dict()"
48+
content = getattr(operation.requestBody, "content", None)
49+
if not isinstance(content, dict):
50+
return None
51+
mt = content.get("application/json")
52+
if mt is None:
53+
return None
54+
schema = getattr(mt, "media_type_schema", None)
55+
if isinstance(schema, Reference):
56+
return "data.dict()"
57+
if isinstance(schema, Schema):
58+
# Array handling
59+
if getattr(schema, "type", None) == "array":
60+
items = getattr(schema, "items", None)
61+
if isinstance(items, Reference):
5362
return "[i.dict() for i in data]"
54-
elif schema.type == "object":
55-
return "data"
56-
else:
57-
raise Exception(
58-
f"Unsupported schema type for request body: {schema.type}"
59-
) # pragma: no cover
60-
else:
61-
raise Exception(
62-
f"Unsupported schema type for request body: {type(media_type.media_type_schema)}"
63-
) # pragma: no cover
63+
if isinstance(items, Schema):
64+
if getattr(items, "type", None) == "object" or any(
65+
getattr(items, attr, None) for attr in ["properties", "allOf", "oneOf", "anyOf"]
66+
):
67+
return "[i.dict() for i in data]"
68+
return "data"
69+
# Object-like
70+
if (
71+
getattr(schema, "type", None) == "object"
72+
or any(
73+
getattr(schema, attr, None)
74+
for attr in ["properties", "allOf", "oneOf", "anyOf", "additionalProperties"]
75+
)
76+
):
77+
return "data"
78+
# Primitive / unspecified
79+
return "data"
80+
return None
6481

6582

6683
def generate_params(operation: Operation) -> str:
@@ -150,17 +167,14 @@ def _generate_params_from_content(content: Union[Reference, Schema]):
150167
return params + default_params
151168

152169

153-
def generate_operation_id(
154-
operation: Operation, http_op: str, path_name: Optional[str] = None
155-
) -> str:
156-
if operation.operationId is not None:
170+
def generate_operation_id(operation: Operation, http_op: str, path_name: Optional[str] = None) -> str:
171+
if operation.operationId:
157172
return common.normalize_symbol(operation.operationId)
158-
elif path_name is not None:
159-
return common.normalize_symbol(f"{http_op}_{path_name}")
160-
else:
161-
raise Exception(
162-
f"OperationId is not defined for {http_op} of path_name {path_name} --> {operation.summary}"
163-
) # pragma: no cover
173+
if path_name:
174+
# Insert underscore before parameter placeholders so /lists/{listId} -> lists_{listId}
175+
cleaned = re.sub(r"\{([^}]+)\}", r"_\1", path_name)
176+
return common.normalize_symbol(f"{http_op}_{cleaned}")
177+
raise RuntimeError("Missing operationId and path_name for operation")
164178

165179

166180
def _generate_params(
@@ -203,13 +217,10 @@ def generate_return_type(operation: Operation) -> OpReturnType:
203217
if isinstance(chosen_response, Response) and chosen_response.content is not None:
204218
media_type_schema = chosen_response.content.get("application/json")
205219
elif isinstance(chosen_response, Reference):
206-
media_type_schema = MediaType(
207-
media_type_schema=chosen_response
208-
) # pragma: no cover
220+
# Wrap reference as a MediaType so downstream logic can treat uniformly
221+
media_type_schema = MediaType(schema=chosen_response) # type: ignore[arg-type]
209222
else:
210-
return OpReturnType(
211-
type=None, status_code=good_responses[0][0], complex_type=False
212-
)
223+
return OpReturnType(type=None, status_code=good_responses[0][0], complex_type=False)
213224

214225
if isinstance(media_type_schema, MediaType):
215226
if isinstance(media_type_schema.media_type_schema, Reference):

0 commit comments

Comments
 (0)