Skip to content

Commit 3cca614

Browse files
committed
feat: add support for lists of basic python types
Added support for lists of strings, floats, integers and booleans.
1 parent 11adc3a commit 3cca614

File tree

7 files changed

+335
-0
lines changed

7 files changed

+335
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
- Added support for octet-stream content type (#116)
1111
- Support for [nullable](https://swagger.io/docs/specification/data-models/data-types/#null) (#99)
1212
- Union properties defined using oneOf (#98)
13+
- Added support for lists of strings, integers, floats and booleans (#165). Thanks @Maistho!
1314

1415

1516
## 0.5.3 - 2020-08-13

end_to_end_tests/fastapi_app/__init__.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,30 @@ def get_list(
5757
return
5858

5959

60+
@test_router.get("/basic_lists/strings", response_model=List[str], operation_id="getBasicListOfStrings")
61+
def get_basic_list_of_strings():
62+
""" Get a list of strings """
63+
return
64+
65+
66+
@test_router.get("/basic_lists/integers", response_model=List[int], operation_id="getBasicListOfIntegers")
67+
def get_basic_list_of_integers():
68+
""" Get a list of integers """
69+
return
70+
71+
72+
@test_router.get("/basic_lists/floats", response_model=List[float], operation_id="getBasicListOfFloats")
73+
def get_basic_list_of_floats():
74+
""" Get a list of floats """
75+
return
76+
77+
78+
@test_router.get("/basic_lists/booleans", response_model=List[bool], operation_id="getBasicListOfBooleans")
79+
def get_basic_list_of_booleans():
80+
""" Get a list of booleans """
81+
return
82+
83+
6084
@test_router.post("/upload")
6185
async def upload_file(some_file: UploadFile = File(...), keep_alive: bool = Header(None)):
6286
""" Upload a file """

end_to_end_tests/fastapi_app/openapi.json

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,110 @@
9494
}
9595
}
9696
},
97+
"/tests/basic_lists/strings": {
98+
"get": {
99+
"tags": [
100+
"tests"
101+
],
102+
"summary": "Get Basic List Of Strings",
103+
"description": "Get a list of strings ",
104+
"operationId": "getBasicListOfStrings",
105+
"responses": {
106+
"200": {
107+
"description": "Successful Response",
108+
"content": {
109+
"application/json": {
110+
"schema": {
111+
"title": "Response Get Basic List Of Strings Tests Basic Lists Strings Get",
112+
"type": "array",
113+
"items": {
114+
"type": "string"
115+
}
116+
}
117+
}
118+
}
119+
}
120+
}
121+
}
122+
},
123+
"/tests/basic_lists/integers": {
124+
"get": {
125+
"tags": [
126+
"tests"
127+
],
128+
"summary": "Get Basic List Of Integers",
129+
"description": "Get a list of integers ",
130+
"operationId": "getBasicListOfIntegers",
131+
"responses": {
132+
"200": {
133+
"description": "Successful Response",
134+
"content": {
135+
"application/json": {
136+
"schema": {
137+
"title": "Response Get Basic List Of Integers Tests Basic Lists Integers Get",
138+
"type": "array",
139+
"items": {
140+
"type": "integer"
141+
}
142+
}
143+
}
144+
}
145+
}
146+
}
147+
}
148+
},
149+
"/tests/basic_lists/floats": {
150+
"get": {
151+
"tags": [
152+
"tests"
153+
],
154+
"summary": "Get Basic List Of Floats",
155+
"description": "Get a list of floats ",
156+
"operationId": "getBasicListOfFloats",
157+
"responses": {
158+
"200": {
159+
"description": "Successful Response",
160+
"content": {
161+
"application/json": {
162+
"schema": {
163+
"title": "Response Get Basic List Of Floats Tests Basic Lists Floats Get",
164+
"type": "array",
165+
"items": {
166+
"type": "number"
167+
}
168+
}
169+
}
170+
}
171+
}
172+
}
173+
}
174+
},
175+
"/tests/basic_lists/booleans": {
176+
"get": {
177+
"tags": [
178+
"tests"
179+
],
180+
"summary": "Get Basic List Of Booleans",
181+
"description": "Get a list of booleans ",
182+
"operationId": "getBasicListOfBooleans",
183+
"responses": {
184+
"200": {
185+
"description": "Successful Response",
186+
"content": {
187+
"application/json": {
188+
"schema": {
189+
"title": "Response Get Basic List Of Booleans Tests Basic Lists Booleans Get",
190+
"type": "array",
191+
"items": {
192+
"type": "boolean"
193+
}
194+
}
195+
}
196+
}
197+
}
198+
}
199+
}
200+
},
97201
"/tests/upload": {
98202
"post": {
99203
"tags": [

end_to_end_tests/golden-master/my_test_api_client/api/tests.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,66 @@ def get_user_list(
5050
raise ApiResponseError(response=response)
5151

5252

53+
def get_basic_list_of_strings(*, client: Client,) -> List[str]:
54+
55+
""" Get a list of strings """
56+
url = "{}/tests/basic_lists/strings".format(client.base_url)
57+
58+
headers: Dict[str, Any] = client.get_headers()
59+
60+
response = httpx.get(url=url, headers=headers,)
61+
62+
if response.status_code == 200:
63+
return [str(item) for item in cast(List[str], response.json())]
64+
else:
65+
raise ApiResponseError(response=response)
66+
67+
68+
def get_basic_list_of_integers(*, client: Client,) -> List[int]:
69+
70+
""" Get a list of integers """
71+
url = "{}/tests/basic_lists/integers".format(client.base_url)
72+
73+
headers: Dict[str, Any] = client.get_headers()
74+
75+
response = httpx.get(url=url, headers=headers,)
76+
77+
if response.status_code == 200:
78+
return [int(item) for item in cast(List[int], response.json())]
79+
else:
80+
raise ApiResponseError(response=response)
81+
82+
83+
def get_basic_list_of_floats(*, client: Client,) -> List[float]:
84+
85+
""" Get a list of floats """
86+
url = "{}/tests/basic_lists/floats".format(client.base_url)
87+
88+
headers: Dict[str, Any] = client.get_headers()
89+
90+
response = httpx.get(url=url, headers=headers,)
91+
92+
if response.status_code == 200:
93+
return [float(item) for item in cast(List[float], response.json())]
94+
else:
95+
raise ApiResponseError(response=response)
96+
97+
98+
def get_basic_list_of_booleans(*, client: Client,) -> List[bool]:
99+
100+
""" Get a list of booleans """
101+
url = "{}/tests/basic_lists/booleans".format(client.base_url)
102+
103+
headers: Dict[str, Any] = client.get_headers()
104+
105+
response = httpx.get(url=url, headers=headers,)
106+
107+
if response.status_code == 200:
108+
return [bool(item) for item in cast(List[bool], response.json())]
109+
else:
110+
raise ApiResponseError(response=response)
111+
112+
53113
def upload_file_tests_upload_post(
54114
*, client: Client, multipart_data: BodyUploadFileTestsUploadPost, keep_alive: Optional[bool] = None,
55115
) -> Union[

end_to_end_tests/golden-master/my_test_api_client/async_api/tests.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,70 @@ async def get_user_list(
5151
raise ApiResponseError(response=response)
5252

5353

54+
async def get_basic_list_of_strings(*, client: Client,) -> List[str]:
55+
56+
""" Get a list of strings """
57+
url = "{}/tests/basic_lists/strings".format(client.base_url,)
58+
59+
headers: Dict[str, Any] = client.get_headers()
60+
61+
async with httpx.AsyncClient() as _client:
62+
response = await _client.get(url=url, headers=headers,)
63+
64+
if response.status_code == 200:
65+
return [str(item) for item in cast(List[str], response.json())]
66+
else:
67+
raise ApiResponseError(response=response)
68+
69+
70+
async def get_basic_list_of_integers(*, client: Client,) -> List[int]:
71+
72+
""" Get a list of integers """
73+
url = "{}/tests/basic_lists/integers".format(client.base_url,)
74+
75+
headers: Dict[str, Any] = client.get_headers()
76+
77+
async with httpx.AsyncClient() as _client:
78+
response = await _client.get(url=url, headers=headers,)
79+
80+
if response.status_code == 200:
81+
return [int(item) for item in cast(List[int], response.json())]
82+
else:
83+
raise ApiResponseError(response=response)
84+
85+
86+
async def get_basic_list_of_floats(*, client: Client,) -> List[float]:
87+
88+
""" Get a list of floats """
89+
url = "{}/tests/basic_lists/floats".format(client.base_url,)
90+
91+
headers: Dict[str, Any] = client.get_headers()
92+
93+
async with httpx.AsyncClient() as _client:
94+
response = await _client.get(url=url, headers=headers,)
95+
96+
if response.status_code == 200:
97+
return [float(item) for item in cast(List[float], response.json())]
98+
else:
99+
raise ApiResponseError(response=response)
100+
101+
102+
async def get_basic_list_of_booleans(*, client: Client,) -> List[bool]:
103+
104+
""" Get a list of booleans """
105+
url = "{}/tests/basic_lists/booleans".format(client.base_url,)
106+
107+
headers: Dict[str, Any] = client.get_headers()
108+
109+
async with httpx.AsyncClient() as _client:
110+
response = await _client.get(url=url, headers=headers,)
111+
112+
if response.status_code == 200:
113+
return [bool(item) for item in cast(List[bool], response.json())]
114+
else:
115+
raise ApiResponseError(response=response)
116+
117+
54118
async def upload_file_tests_upload_post(
55119
*, client: Client, multipart_data: BodyUploadFileTestsUploadPost, keep_alive: Optional[bool] = None,
56120
) -> Union[

openapi_python_client/parser/responses.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,25 @@ def constructor(self) -> str:
5151
return f"{self.reference.class_name}.from_dict(cast(Dict[str, Any], response.json()))"
5252

5353

54+
@dataclass
55+
class ListBasicResponse(Response):
56+
""" Response is a list of some basic type """
57+
58+
openapi_type: InitVar[str]
59+
python_type: str = field(init=False)
60+
61+
def __post_init__(self, openapi_type: str) -> None:
62+
self.python_type = openapi_types_to_python_type_strings[openapi_type]
63+
64+
def return_string(self) -> str:
65+
""" How this Response should be represented as a return type """
66+
return f"List[{self.python_type}]"
67+
68+
def constructor(self) -> str:
69+
""" How the return value of this response should be constructed """
70+
return f"[{self.python_type}(item) for item in cast(List[{self.python_type}], response.json())]"
71+
72+
5473
@dataclass
5574
class BasicResponse(Response):
5675
""" Response is a basic type """
@@ -118,6 +137,12 @@ def response_from_data(*, status_code: int, data: Union[oai.Response, oai.Refere
118137
return Response(status_code=status_code)
119138
if response_type == "array" and isinstance(schema_data.items, oai.Reference):
120139
return ListRefResponse(status_code=status_code, reference=Reference.from_ref(schema_data.items.ref),)
140+
if (
141+
response_type == "array"
142+
and isinstance(schema_data.items, oai.Schema)
143+
and schema_data.items.type in openapi_types_to_python_type_strings
144+
):
145+
return ListBasicResponse(status_code=status_code, openapi_type=schema_data.items.type)
121146
if response_type in openapi_types_to_python_type_strings:
122147
return BasicResponse(status_code=status_code, openapi_type=response_type)
123148
return ParseError(data=data, detail=f"Unrecognized type {schema_data.type}")

tests/test_openapi_parser/test_responses.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,46 @@ def test_constructor(self, mocker):
5454
assert r.constructor() == "SuperCoolClass.from_dict(cast(Dict[str, Any], response.json()))"
5555

5656

57+
class TestListBasicResponse:
58+
def test_return_string(self):
59+
from openapi_python_client.parser.responses import ListBasicResponse
60+
61+
r = ListBasicResponse(200, "string")
62+
63+
assert r.return_string() == "List[str]"
64+
65+
r = ListBasicResponse(200, "number")
66+
67+
assert r.return_string() == "List[float]"
68+
69+
r = ListBasicResponse(200, "integer")
70+
71+
assert r.return_string() == "List[int]"
72+
73+
r = ListBasicResponse(200, "boolean")
74+
75+
assert r.return_string() == "List[bool]"
76+
77+
def test_constructor(self):
78+
from openapi_python_client.parser.responses import ListBasicResponse
79+
80+
r = ListBasicResponse(200, "string")
81+
82+
assert r.constructor() == "[str(item) for item in cast(List[str], response.json())]"
83+
84+
r = ListBasicResponse(200, "number")
85+
86+
assert r.constructor() == "[float(item) for item in cast(List[float], response.json())]"
87+
88+
r = ListBasicResponse(200, "integer")
89+
90+
assert r.constructor() == "[int(item) for item in cast(List[int], response.json())]"
91+
92+
r = ListBasicResponse(200, "boolean")
93+
94+
assert r.constructor() == "[bool(item) for item in cast(List[bool], response.json())]"
95+
96+
5797
class TestBasicResponse:
5898
def test_return_string(self):
5999
from openapi_python_client.parser.responses import BasicResponse
@@ -193,6 +233,23 @@ def test_response_from_data_array(self, mocker):
193233
ListRefResponse.assert_called_once_with(status_code=status_code, reference=from_ref())
194234
assert response == ListRefResponse()
195235

236+
def test_response_from_basic_array(self, mocker):
237+
status_code = mocker.MagicMock(autospec=int)
238+
data = oai.Response.construct(
239+
content={
240+
"application/json": oai.MediaType.construct(
241+
media_type_schema=oai.Schema.construct(type="array", items=oai.Schema.construct(type="string"))
242+
)
243+
}
244+
)
245+
ListBasicResponse = mocker.patch(f"{MODULE_NAME}.ListBasicResponse")
246+
from openapi_python_client.parser.responses import response_from_data
247+
248+
response = response_from_data(status_code=status_code, data=data)
249+
250+
ListBasicResponse.assert_called_once_with(status_code=status_code, openapi_type="string")
251+
assert response == ListBasicResponse.return_value
252+
196253
def test_response_from_data_basic(self, mocker):
197254
status_code = mocker.MagicMock(autospec=int)
198255
data = oai.Response.construct(

0 commit comments

Comments
 (0)