From 9cb292dcb6e14c52b91321bab05b3c585745b32d Mon Sep 17 00:00:00 2001 From: Philippe Auriach Date: Mon, 25 Sep 2023 17:02:03 +0200 Subject: [PATCH 1/3] Add response content_types --- chalice_spec/docs.py | 49 +++++++++++++++++++++++-------------------- tests/test_chalice.py | 11 ++++++++-- 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/chalice_spec/docs.py b/chalice_spec/docs.py index 50d1c3e..9055adb 100644 --- a/chalice_spec/docs.py +++ b/chalice_spec/docs.py @@ -6,6 +6,7 @@ DEFAULT_DESCRIPTION = "Success" DEFAULT_CODE = 200 +DEFAULT_CONTENT_TYPE = "application/json" class Response: @@ -15,10 +16,11 @@ class Response: and an optional description. """ - def __init__(self, model: type, code: int = 200, description: str = "Success"): + def __init__(self, model: type, code: int = 200, description: str = "Success", content_type: str = DEFAULT_CONTENT_TYPE): self.model = model self.code = code self.description = description + self.content_type = content_type class Operation: @@ -59,19 +61,19 @@ def __init__( def _populate_response(self, response: Union[Response, type]): if isinstance(response, Response): # If this is a Response object, we can track it as-is. - self.responses = {response.code: response} + self.responses = {response.code: {DEFAULT_CONTENT_TYPE: response}} else: # If not, we will use sensible defaults - self.responses = {DEFAULT_CODE: Response(model=response)} + self.responses = {DEFAULT_CODE: {DEFAULT_CONTENT_TYPE: Response(model=response)}} def _populate_responses(self, responses: List[Response]): self.responses = {} for response in responses: - if response.code in self.responses: - raise TypeError( - "You must only specify one response per HTTP status code" - ) - self.responses[response.code] = response + if response.code not in self.responses: + self.responses[response.code] = {} + if response.content_type in self.responses[response.code]: + raise TypeError(f"Multiple responses defined for {response.code} — {response.content_type}") + self.responses[response.code][response.content_type] = response Method = Union[Type[BaseModel], Operation] @@ -139,7 +141,7 @@ def _build_operation_from_operation( if method.content_types else content_types[0] if content_types - else "application/json" + else DEFAULT_CONTENT_TYPE ) operation["requestBody"] = { "content": { @@ -152,21 +154,22 @@ def _build_operation_from_operation( if method.responses: responses = {} - for code, response in method.responses.items(): - if response.model.__name__ not in spec.components.schemas: - spec.components.schema( - response.model.__name__, - model=response.model, - spec=spec, - ) - responses[code] = { - "description": response.description, - "content": { - "application/json": { - "schema": response.model.__name__, + for code, response_contents in method.responses.items(): + for content_type, response in response_contents.items(): + if response.model.__name__ not in spec.components.schemas: + spec.components.schema( + response.model.__name__, + model=response.model, + spec=spec, + ) + if code not in responses: + responses[code] = { + "description": response.description, + "content": {}, } - }, - } + responses[code]['content'][content_type] = { + "schema": response.model.__name__ + } operation["responses"] = responses diff --git a/tests/test_chalice.py b/tests/test_chalice.py index bf0de42..33edcdd 100644 --- a/tests/test_chalice.py +++ b/tests/test_chalice.py @@ -421,16 +421,18 @@ def get_post(): }, } - # Test 9: different content_types def test_content_types(): app, spec = setup_test() + with pytest.raises(TypeError): + Op(responses=[Resp(model=TestSchema), Resp(model=TestSchema)]) + @app.route( "/posts", methods=["POST"], content_types=["multipart/form-data"], - docs=Docs(request=TestSchema, response=AnotherSchema), + docs=Docs(request=TestSchema, responses=[Resp(model=AnotherSchema, content_type="application/json"), Resp(model=AnotherSchema, content_type="application/xml")]), ) def get_post(): pass @@ -454,6 +456,11 @@ def get_post(): "schema": { "$ref": "#/components/schemas/AnotherSchema" } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/AnotherSchema" + } } }, } From ee3559a8d00d2243abdbbfcdd8c6730410dd5252 Mon Sep 17 00:00:00 2001 From: Philippe Auriach Date: Mon, 25 Sep 2023 17:13:50 +0200 Subject: [PATCH 2/3] format --- chalice_spec/docs.py | 16 +++++++++++++--- tests/test_chalice.py | 8 +++++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/chalice_spec/docs.py b/chalice_spec/docs.py index 9055adb..7b455ca 100644 --- a/chalice_spec/docs.py +++ b/chalice_spec/docs.py @@ -16,7 +16,13 @@ class Response: and an optional description. """ - def __init__(self, model: type, code: int = 200, description: str = "Success", content_type: str = DEFAULT_CONTENT_TYPE): + def __init__( + self, + model: type, + code: int = 200, + description: str = "Success", + content_type: str = DEFAULT_CONTENT_TYPE + ): self.model = model self.code = code self.description = description @@ -64,7 +70,9 @@ def _populate_response(self, response: Union[Response, type]): self.responses = {response.code: {DEFAULT_CONTENT_TYPE: response}} else: # If not, we will use sensible defaults - self.responses = {DEFAULT_CODE: {DEFAULT_CONTENT_TYPE: Response(model=response)}} + self.responses = { + DEFAULT_CODE: {DEFAULT_CONTENT_TYPE: Response(model=response)} + } def _populate_responses(self, responses: List[Response]): self.responses = {} @@ -72,7 +80,9 @@ def _populate_responses(self, responses: List[Response]): if response.code not in self.responses: self.responses[response.code] = {} if response.content_type in self.responses[response.code]: - raise TypeError(f"Multiple responses defined for {response.code} — {response.content_type}") + raise TypeError( + f"Multiple responses defined for {response.code} — {response.content_type}" + ) self.responses[response.code][response.content_type] = response diff --git a/tests/test_chalice.py b/tests/test_chalice.py index 33edcdd..358a6a5 100644 --- a/tests/test_chalice.py +++ b/tests/test_chalice.py @@ -432,7 +432,13 @@ def test_content_types(): "/posts", methods=["POST"], content_types=["multipart/form-data"], - docs=Docs(request=TestSchema, responses=[Resp(model=AnotherSchema, content_type="application/json"), Resp(model=AnotherSchema, content_type="application/xml")]), + docs=Docs( + request=TestSchema, + responses=[ + Resp(model=AnotherSchema, content_type="application/json"), + Resp(model=AnotherSchema, content_type="application/xml") + ], + ), ) def get_post(): pass From 44ceaf3fa2cf283d2f5d7aba997145a2002dea47 Mon Sep 17 00:00:00 2001 From: Philippe Auriach Date: Mon, 25 Sep 2023 17:16:50 +0200 Subject: [PATCH 3/3] black --- chalice_spec/docs.py | 12 ++++++------ tests/test_chalice.py | 5 +++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/chalice_spec/docs.py b/chalice_spec/docs.py index 7b455ca..a856708 100644 --- a/chalice_spec/docs.py +++ b/chalice_spec/docs.py @@ -17,11 +17,11 @@ class Response: """ def __init__( - self, - model: type, - code: int = 200, - description: str = "Success", - content_type: str = DEFAULT_CONTENT_TYPE + self, + model: type, + code: int = 200, + description: str = "Success", + content_type: str = DEFAULT_CONTENT_TYPE, ): self.model = model self.code = code @@ -177,7 +177,7 @@ def _build_operation_from_operation( "description": response.description, "content": {}, } - responses[code]['content'][content_type] = { + responses[code]["content"][content_type] = { "schema": response.model.__name__ } diff --git a/tests/test_chalice.py b/tests/test_chalice.py index 358a6a5..db20b5a 100644 --- a/tests/test_chalice.py +++ b/tests/test_chalice.py @@ -421,6 +421,7 @@ def get_post(): }, } + # Test 9: different content_types def test_content_types(): app, spec = setup_test() @@ -436,7 +437,7 @@ def test_content_types(): request=TestSchema, responses=[ Resp(model=AnotherSchema, content_type="application/json"), - Resp(model=AnotherSchema, content_type="application/xml") + Resp(model=AnotherSchema, content_type="application/xml"), ], ), ) @@ -467,7 +468,7 @@ def get_post(): "schema": { "$ref": "#/components/schemas/AnotherSchema" } - } + }, }, } },