Skip to content
This repository was archived by the owner on Dec 25, 2024. It is now read-only.

Commit 94bbb92

Browse files
committed
v2 adds generics, OpenApiResponse + ApiException updates (#86)
* Adds generics for mypy * Adds generic to exception * Removes api_response from ApiException * Fixes tests * Samples regenerated * Fixes exceptions.ApiException reason, body, and headers
1 parent b62bd35 commit 94bbb92

File tree

476 files changed

+5012
-2035
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

476 files changed

+5012
-2035
lines changed

modules/openapi-json-schema-generator/src/main/java/org/openapitools/codegen/CodegenOperation.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import io.swagger.v3.oas.models.tags.Tag;
2222

2323
import java.util.*;
24+
import java.util.stream.Collectors;
2425

2526
public class CodegenOperation {
2627
public final List<CodegenProperty> responseHeaders = new ArrayList<CodegenProperty>();
@@ -33,7 +34,7 @@ public class CodegenOperation {
3334
hasErrorResponseObject; // if 4xx, 5xx responses have at least one error object defined
3435
public CodegenProperty returnProperty;
3536
public String path, operationId, returnType, returnFormat, httpMethod, returnBaseType,
36-
returnContainer, summary, unescapedNotes, notes, baseName, defaultResponse;
37+
returnContainer, summary, unescapedNotes, notes, baseName;
3738
public CodegenDiscriminator discriminator;
3839
public List<Map<String, String>> consumes, produces, prioritizedContentTypes;
3940
public List<CodegenServer> servers = new ArrayList<CodegenServer>();
@@ -51,6 +52,7 @@ public class CodegenOperation {
5152
public List<CodegenSecurity> authMethods;
5253
public List<Tag> tags;
5354
public List<CodegenResponse> responses = new ArrayList<CodegenResponse>();
55+
public CodegenResponse defaultResponse = null;
5456
public List<CodegenCallback> callbacks = new ArrayList<>();
5557
public Set<String> imports = new HashSet<String>();
5658
public List<Map<String, String>> examples;
@@ -197,6 +199,10 @@ public boolean getAllResponsesAreErrors() {
197199
return responses.stream().allMatch(response -> response.is4xx || response.is5xx);
198200
}
199201

202+
public List<CodegenResponse> getNonDefaultResponses() {
203+
return responses.stream().filter(response -> !response.isDefault).collect(Collectors.toList());
204+
}
205+
200206
/**
201207
* @return contentTypeToOperation
202208
* returns a map where the key is the request body content type and the value is the current CodegenOperation

modules/openapi-json-schema-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4078,7 +4078,6 @@ protected void handleMethodResponse(Operation operation,
40784078
op.examples = new ExampleGenerator(schemas, this.openAPI).generateFromResponseSchema(exampleStatusCode, responseSchema, getProducesInfo(this.openAPI, operation));
40794079
}
40804080

4081-
op.defaultResponse = toDefaultValue(responseSchema);
40824081
op.returnType = cm.dataType;
40834082
op.returnFormat = cm.dataFormat;
40844083
op.hasReference = schemas != null && schemas.containsKey(op.returnBaseType);
@@ -4216,6 +4215,7 @@ public CodegenOperation fromOperation(String path,
42164215
}
42174216
if (Boolean.TRUE.equals(r.isDefault)) {
42184217
op.defaultReturnType = Boolean.TRUE;
4218+
op.defaultResponse = r;
42194219
}
42204220
// check if any 4xx or 5xx response has an error response object defined
42214221
if ((Boolean.TRUE.equals(r.is4xx) || Boolean.TRUE.equals(r.is5xx)) && r.getContent() != null) {

modules/openapi-json-schema-generator/src/main/resources/python/api_client.handlebars

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -932,12 +932,19 @@ class TypedDictInputVerifier:
932932
)
933933

934934

935-
class OpenApiResponse(JSONDetector, TypedDictInputVerifier):
935+
T = typing.TypeVar("T")
936+
937+
938+
@dataclasses.dataclass
939+
class OpenApiResponse(JSONDetector, TypedDictInputVerifier, typing.Generic[T]):
936940
__filename_content_disposition_pattern = re.compile('filename="(.+?)"')
941+
response_cls: typing.Type[T]
942+
content: typing.Optional[typing.Dict[str, MediaType]]
943+
headers: typing.Optional[typing.Dict[str, HeaderParameterWithoutName]]
937944

938945
def __init__(
939946
self,
940-
response_cls: typing.Type[ApiResponse] = ApiResponse,
947+
response_cls: typing.Type[T],
941948
content: typing.Optional[typing.Dict[str, MediaType]] = None,
942949
headers: typing.Optional[typing.Dict[str, HeaderParameterWithoutName]] = None,
943950
):
@@ -1022,7 +1029,7 @@ class OpenApiResponse(JSONDetector, TypedDictInputVerifier):
10221029
for part in msg.get_payload()
10231030
}
10241031

1025-
def deserialize(self, response: urllib3.HTTPResponse, configuration: Configuration) -> ApiResponse:
1032+
def deserialize(self, response: urllib3.HTTPResponse, configuration: Configuration) -> T:
10261033
content_type = response.getheader('content-type')
10271034
deserialized_body = unset
10281035
streamed = response.supports_chunked_reads()

modules/openapi-json-schema-generator/src/main/resources/python/endpoint.handlebars

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,25 @@ _servers = (
9191
{{/unless}}
9292
{{#unless isStub}}
9393

94-
_status_code_to_response = {
95-
{{#each responses}}
96-
{{#if isDefault}}
97-
'default': response_for_default.response,
98-
{{else}}
99-
'{{code}}': response_for_{{code}}.response,
94+
95+
{{#if defaultResponse}}
96+
default_response = response_for_default.response
10097
{{/if}}
98+
{{#if getNonDefaultResponses}}
99+
__StatusCodeToResponse = typing_extensions.TypedDict(
100+
'__StatusCodeToResponse',
101+
{
102+
{{#each getNonDefaultResponses}}
103+
'{{code}}': api_client.OpenApiResponse[response_for_{{code}}.ApiResponse],
104+
{{/each}}
105+
}
106+
)
107+
_status_code_to_response = __StatusCodeToResponse({
108+
{{#each getNonDefaultResponses}}
109+
'{{code}}': response_for_{{code}}.response,
101110
{{/each}}
102-
}
111+
})
112+
{{/if}}
103113
{{/unless}}
104114
{{#each produces}}
105115
{{#if @first}}
@@ -246,19 +256,28 @@ class BaseApi(api_client.Api):
246256
if skip_deserialization:
247257
api_response = api_client.ApiResponseWithoutDeserialization(response=response)
248258
else:
249-
response_for_status = _status_code_to_response.get(str(response.status))
250-
if response_for_status:
251-
api_response = response_for_status.deserialize(response, self.api_client.configuration)
259+
{{#if getNonDefaultResponses}}
260+
status = str(response.status)
261+
if status in _status_code_to_response:
262+
status: typing_extensions.Literal[
263+
{{#each getNonDefaultResponses}}
264+
'{{code}}',
265+
{{/each}}
266+
]
267+
api_response = _status_code_to_response[status].deserialize(response, self.api_client.configuration)
252268
else:
253-
{{#if hasDefaultResponse}}
254-
default_response = _status_code_to_response.get('default')
255-
if default_response:
256-
api_response = default_response.deserialize(response, self.api_client.configuration)
257-
else:
258-
api_response = api_client.ApiResponseWithoutDeserialization(response=response)
269+
{{#if defaultResponse}}
270+
api_response = default_response.deserialize(response, self.api_client.configuration)
259271
{{else}}
260272
api_response = api_client.ApiResponseWithoutDeserialization(response=response)
261273
{{/if}}
274+
{{else}}
275+
{{#if defaultResponse}}
276+
api_response = default_response.deserialize(response, self.api_client.configuration)
277+
{{else}}
278+
api_response = api_client.ApiResponseWithoutDeserialization(response=response)
279+
{{/if}}
280+
{{/if}}
262281

263282
if not 200 <= response.status <= 299:
264283
raise exceptions.ApiException(

modules/openapi-json-schema-generator/src/main/resources/python/exceptions.handlebars

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -103,28 +103,16 @@ class ApiException(OpenApiException, typing.Generic[T]):
103103
reason: str
104104
api_response: typing.Optional[T] = None
105105

106-
@property
107-
def body(self) -> typing.Union[str, bytes, None]:
108-
if not self.api_response:
109-
return None
110-
return self.api_response.response.data
111-
112-
@property
113-
def headers(self) -> typing.Optional[HTTPHeaderDict]:
114-
if not self.api_response:
115-
return None
116-
return self.api_response.response.getheaders()
117-
118106
def __str__(self):
119107
"""Custom error messages for exception"""
120108
error_message = "({0})\n"\
121109
"Reason: {1}\n".format(self.status, self.reason)
122-
if self.headers:
123-
error_message += "HTTP response headers: {0}\n".format(
124-
self.headers)
125-
126-
if self.body:
127-
error_message += "HTTP response body: {0}\n".format(self.body)
110+
if self.api_response:
111+
if self.api_response.response.getheaders():
112+
error_message += "HTTP response headers: {0}\n".format(
113+
self.api_response.response.getheaders())
114+
if self.api_response.response.data:
115+
error_message += "HTTP response body: {0}\n".format(self.api_response.response.data)
128116

129117
return error_message
130118

samples/openapi3/client/3_0_3_unit_test/python/unit_test_api/api_client.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -936,12 +936,19 @@ def _verify_typed_dict_inputs_oapg(cls: typing.Type[typing_extensions.TypedDict]
936936
)
937937

938938

939-
class OpenApiResponse(JSONDetector, TypedDictInputVerifier):
939+
T = typing.TypeVar("T")
940+
941+
942+
@dataclasses.dataclass
943+
class OpenApiResponse(JSONDetector, TypedDictInputVerifier, typing.Generic[T]):
940944
__filename_content_disposition_pattern = re.compile('filename="(.+?)"')
945+
response_cls: typing.Type[T]
946+
content: typing.Optional[typing.Dict[str, MediaType]]
947+
headers: typing.Optional[typing.Dict[str, HeaderParameterWithoutName]]
941948

942949
def __init__(
943950
self,
944-
response_cls: typing.Type[ApiResponse] = ApiResponse,
951+
response_cls: typing.Type[T],
945952
content: typing.Optional[typing.Dict[str, MediaType]] = None,
946953
headers: typing.Optional[typing.Dict[str, HeaderParameterWithoutName]] = None,
947954
):
@@ -1026,7 +1033,7 @@ def __deserialize_multipart_form_data(
10261033
for part in msg.get_payload()
10271034
}
10281035

1029-
def deserialize(self, response: urllib3.HTTPResponse, configuration: Configuration) -> ApiResponse:
1036+
def deserialize(self, response: urllib3.HTTPResponse, configuration: Configuration) -> T:
10301037
content_type = response.getheader('content-type')
10311038
deserialized_body = unset
10321039
streamed = response.supports_chunked_reads()

samples/openapi3/client/3_0_3_unit_test/python/unit_test_api/exceptions.py

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -110,28 +110,16 @@ class ApiException(OpenApiException, typing.Generic[T]):
110110
reason: str
111111
api_response: typing.Optional[T] = None
112112

113-
@property
114-
def body(self) -> typing.Union[str, bytes, None]:
115-
if not self.api_response:
116-
return None
117-
return self.api_response.response.data
118-
119-
@property
120-
def headers(self) -> typing.Optional[HTTPHeaderDict]:
121-
if not self.api_response:
122-
return None
123-
return self.api_response.response.getheaders()
124-
125113
def __str__(self):
126114
"""Custom error messages for exception"""
127115
error_message = "({0})\n"\
128116
"Reason: {1}\n".format(self.status, self.reason)
129-
if self.headers:
130-
error_message += "HTTP response headers: {0}\n".format(
131-
self.headers)
132-
133-
if self.body:
134-
error_message += "HTTP response body: {0}\n".format(self.body)
117+
if self.api_response:
118+
if self.api_response.response.getheaders():
119+
error_message += "HTTP response headers: {0}\n".format(
120+
self.api_response.response.getheaders())
121+
if self.api_response.response.data:
122+
error_message += "HTTP response body: {0}\n".format(self.api_response.response.data)
135123

136124
return error_message
137125

samples/openapi3/client/3_0_3_unit_test/python/unit_test_api/paths/request_body_post_additionalproperties_allows_a_schema_which_should_validate_request_body/post/__init__.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,16 @@
3232
from . import request_body
3333

3434

35-
_status_code_to_response = {
35+
36+
__StatusCodeToResponse = typing_extensions.TypedDict(
37+
'__StatusCodeToResponse',
38+
{
39+
'200': api_client.OpenApiResponse[response_for_200.ApiResponse],
40+
}
41+
)
42+
_status_code_to_response = __StatusCodeToResponse({
3643
'200': response_for_200.response,
37-
}
44+
})
3845

3946

4047
class BaseApi(api_client.Api):
@@ -128,9 +135,12 @@ class instances
128135
if skip_deserialization:
129136
api_response = api_client.ApiResponseWithoutDeserialization(response=response)
130137
else:
131-
response_for_status = _status_code_to_response.get(str(response.status))
132-
if response_for_status:
133-
api_response = response_for_status.deserialize(response, self.api_client.configuration)
138+
status = str(response.status)
139+
if status in _status_code_to_response:
140+
status: typing_extensions.Literal[
141+
'200',
142+
]
143+
api_response = _status_code_to_response[status].deserialize(response, self.api_client.configuration)
134144
else:
135145
api_response = api_client.ApiResponseWithoutDeserialization(response=response)
136146

samples/openapi3/client/3_0_3_unit_test/python/unit_test_api/paths/request_body_post_additionalproperties_allows_a_schema_which_should_validate_request_body/post/__init__.pyi

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,12 @@ class BaseApi(api_client.Api):
123123
if skip_deserialization:
124124
api_response = api_client.ApiResponseWithoutDeserialization(response=response)
125125
else:
126-
response_for_status = _status_code_to_response.get(str(response.status))
127-
if response_for_status:
128-
api_response = response_for_status.deserialize(response, self.api_client.configuration)
126+
status = str(response.status)
127+
if status in _status_code_to_response:
128+
status: typing_extensions.Literal[
129+
'200',
130+
]
131+
api_response = _status_code_to_response[status].deserialize(response, self.api_client.configuration)
129132
else:
130133
api_response = api_client.ApiResponseWithoutDeserialization(response=response)
131134

samples/openapi3/client/3_0_3_unit_test/python/unit_test_api/paths/request_body_post_additionalproperties_are_allowed_by_default_request_body/post/__init__.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,16 @@
3232
from . import request_body
3333

3434

35-
_status_code_to_response = {
35+
36+
__StatusCodeToResponse = typing_extensions.TypedDict(
37+
'__StatusCodeToResponse',
38+
{
39+
'200': api_client.OpenApiResponse[response_for_200.ApiResponse],
40+
}
41+
)
42+
_status_code_to_response = __StatusCodeToResponse({
3643
'200': response_for_200.response,
37-
}
44+
})
3845

3946

4047
class BaseApi(api_client.Api):
@@ -128,9 +135,12 @@ class instances
128135
if skip_deserialization:
129136
api_response = api_client.ApiResponseWithoutDeserialization(response=response)
130137
else:
131-
response_for_status = _status_code_to_response.get(str(response.status))
132-
if response_for_status:
133-
api_response = response_for_status.deserialize(response, self.api_client.configuration)
138+
status = str(response.status)
139+
if status in _status_code_to_response:
140+
status: typing_extensions.Literal[
141+
'200',
142+
]
143+
api_response = _status_code_to_response[status].deserialize(response, self.api_client.configuration)
134144
else:
135145
api_response = api_client.ApiResponseWithoutDeserialization(response=response)
136146

samples/openapi3/client/3_0_3_unit_test/python/unit_test_api/paths/request_body_post_additionalproperties_are_allowed_by_default_request_body/post/__init__.pyi

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,12 @@ class BaseApi(api_client.Api):
123123
if skip_deserialization:
124124
api_response = api_client.ApiResponseWithoutDeserialization(response=response)
125125
else:
126-
response_for_status = _status_code_to_response.get(str(response.status))
127-
if response_for_status:
128-
api_response = response_for_status.deserialize(response, self.api_client.configuration)
126+
status = str(response.status)
127+
if status in _status_code_to_response:
128+
status: typing_extensions.Literal[
129+
'200',
130+
]
131+
api_response = _status_code_to_response[status].deserialize(response, self.api_client.configuration)
129132
else:
130133
api_response = api_client.ApiResponseWithoutDeserialization(response=response)
131134

samples/openapi3/client/3_0_3_unit_test/python/unit_test_api/paths/request_body_post_additionalproperties_can_exist_by_itself_request_body/post/__init__.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,16 @@
3232
from . import request_body
3333

3434

35-
_status_code_to_response = {
35+
36+
__StatusCodeToResponse = typing_extensions.TypedDict(
37+
'__StatusCodeToResponse',
38+
{
39+
'200': api_client.OpenApiResponse[response_for_200.ApiResponse],
40+
}
41+
)
42+
_status_code_to_response = __StatusCodeToResponse({
3643
'200': response_for_200.response,
37-
}
44+
})
3845

3946

4047
class BaseApi(api_client.Api):
@@ -128,9 +135,12 @@ class instances
128135
if skip_deserialization:
129136
api_response = api_client.ApiResponseWithoutDeserialization(response=response)
130137
else:
131-
response_for_status = _status_code_to_response.get(str(response.status))
132-
if response_for_status:
133-
api_response = response_for_status.deserialize(response, self.api_client.configuration)
138+
status = str(response.status)
139+
if status in _status_code_to_response:
140+
status: typing_extensions.Literal[
141+
'200',
142+
]
143+
api_response = _status_code_to_response[status].deserialize(response, self.api_client.configuration)
134144
else:
135145
api_response = api_client.ApiResponseWithoutDeserialization(response=response)
136146

0 commit comments

Comments
 (0)