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

Commit b98f5e6

Browse files
authored
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 9e2eab5 commit b98f5e6

File tree

476 files changed

+7381
-2500
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

+7381
-2500
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
@@ -4076,7 +4076,6 @@ protected void handleMethodResponse(Operation operation,
40764076
op.examples = new ExampleGenerator(schemas, this.openAPI).generateFromResponseSchema(exampleStatusCode, responseSchema, getProducesInfo(this.openAPI, operation));
40774077
}
40784078

4079-
op.defaultResponse = toDefaultValue(responseSchema);
40804079
op.returnType = cm.dataType;
40814080
op.returnFormat = cm.dataFormat;
40824081
op.hasReference = schemas != null && schemas.containsKey(op.returnBaseType);
@@ -4214,6 +4213,7 @@ public CodegenOperation fromOperation(String path,
42144213
}
42154214
if (Boolean.TRUE.equals(r.isDefault)) {
42164215
op.defaultReturnType = Boolean.TRUE;
4216+
op.defaultResponse = r;
42174217
}
42184218
// check if any 4xx or 5xx response has an error response object defined
42194219
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: 40 additions & 17 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,22 +256,35 @@ 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:
264-
raise exceptions.ApiException(api_response=api_response)
283+
raise exceptions.ApiException(
284+
status=response.status,
285+
reason=response.reason,
286+
api_response=api_response
287+
)
265288

266289
return api_response
267290

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

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# coding: utf-8
22

33
{{>partial_header}}
4+
import dataclasses
5+
import typing
6+
7+
from urllib3._collections import HTTPHeaderDict
48

59

610
class OpenApiException(Exception):
@@ -90,30 +94,25 @@ class ApiKeyError(OpenApiException, KeyError):
9094
super(ApiKeyError, self).__init__(full_msg)
9195

9296

93-
class ApiException(OpenApiException):
97+
T = typing.TypeVar("T")
9498

95-
def __init__(self, status=None, reason=None, api_response: '{{packageName}}.api_client.ApiResponse' = None):
96-
if api_response:
97-
self.status = api_response.response.status
98-
self.reason = api_response.response.reason
99-
self.body = api_response.response.data
100-
self.headers = api_response.response.getheaders()
101-
else:
102-
self.status = status
103-
self.reason = reason
104-
self.body = None
105-
self.headers = None
99+
100+
@dataclasses.dataclass
101+
class ApiException(OpenApiException, typing.Generic[T]):
102+
status: int
103+
reason: str
104+
api_response: typing.Optional[T] = None
106105

107106
def __str__(self):
108107
"""Custom error messages for exception"""
109108
error_message = "({0})\n"\
110109
"Reason: {1}\n".format(self.status, self.reason)
111-
if self.headers:
112-
error_message += "HTTP response headers: {0}\n".format(
113-
self.headers)
114-
115-
if self.body:
116-
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)
117116

118117
return error_message
119118

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: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
The version of the OpenAPI document: 0.0.1
99
Generated by: https://openapi-generator.tech
1010
"""
11+
import dataclasses
12+
import typing
13+
14+
from urllib3._collections import HTTPHeaderDict
1115

1216

1317
class OpenApiException(Exception):
@@ -97,30 +101,25 @@ def __init__(self, msg, path_to_item=None):
97101
super(ApiKeyError, self).__init__(full_msg)
98102

99103

100-
class ApiException(OpenApiException):
104+
T = typing.TypeVar("T")
101105

102-
def __init__(self, status=None, reason=None, api_response: 'unit_test_api.api_client.ApiResponse' = None):
103-
if api_response:
104-
self.status = api_response.response.status
105-
self.reason = api_response.response.reason
106-
self.body = api_response.response.data
107-
self.headers = api_response.response.getheaders()
108-
else:
109-
self.status = status
110-
self.reason = reason
111-
self.body = None
112-
self.headers = None
106+
107+
@dataclasses.dataclass
108+
class ApiException(OpenApiException, typing.Generic[T]):
109+
status: int
110+
reason: str
111+
api_response: typing.Optional[T] = None
113112

114113
def __str__(self):
115114
"""Custom error messages for exception"""
116115
error_message = "({0})\n"\
117116
"Reason: {1}\n".format(self.status, self.reason)
118-
if self.headers:
119-
error_message += "HTTP response headers: {0}\n".format(
120-
self.headers)
121-
122-
if self.body:
123-
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)
124123

125124
return error_message
126125

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: 20 additions & 6 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,14 +135,21 @@ 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

137147
if not 200 <= response.status <= 299:
138-
raise exceptions.ApiException(api_response=api_response)
148+
raise exceptions.ApiException(
149+
status=response.status,
150+
reason=response.reason,
151+
api_response=api_response
152+
)
139153

140154
return api_response
141155

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: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,14 +123,21 @@ 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

132135
if not 200 <= response.status <= 299:
133-
raise exceptions.ApiException(api_response=api_response)
136+
raise exceptions.ApiException(
137+
status=response.status,
138+
reason=response.reason,
139+
api_response=api_response
140+
)
134141

135142
return api_response
136143

0 commit comments

Comments
 (0)