Skip to content

Commit e079b02

Browse files
committed
Support for different response content types
Now it is possible to define an special argument "accept" for the predict method, allowing the user to specify what response type is expected. This is passed to the predict enpoints, therefore we are able to return something different than JSON documents.
1 parent ec788f3 commit e079b02

File tree

4 files changed

+86
-3
lines changed

4 files changed

+86
-3
lines changed

deepaas/api/v2/predict.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,16 @@ def _get_model_response(model_name, model_obj):
3434

3535

3636
def _get_handler(model_name, model_obj):
37-
handler_args = webargs.core.dict2schema(model_obj.get_predict_args())
37+
aux = model_obj.get_predict_args()
38+
accept = aux.get("accept", None)
39+
if accept and "application/json" not in accept.validate.choices:
40+
accept.validate.choices.insert(0, "application/json")
41+
accept.validate.choices.append("*/*")
42+
accept.default = "application/json"
43+
accept.missing = "application/json"
44+
accept.location = "headers"
45+
46+
handler_args = webargs.core.dict2schema(aux)
3847
handler_args.opts.ordered = True
3948

4049
response = _get_model_response(model_name, model_obj)
@@ -49,7 +58,8 @@ def __init__(self, model_name, model_obj):
4958

5059
@aiohttp_apispec.docs(
5160
tags=["models"],
52-
summary="Make a prediction given the input data"
61+
summary="Make a prediction given the input data",
62+
produces=accept.validate.choices if accept else None,
5363
)
5464
@aiohttp_apispec.querystring_schema(handler_args)
5565
@aiohttp_apispec.response_schema(response(), 200)
@@ -63,6 +73,13 @@ async def post(self, request, wsk_args=None):
6373

6474
ret = task.result()
6575

76+
accept = args.get("accept", "application/json")
77+
if accept != "application/json":
78+
response = web.Response(
79+
body=ret,
80+
content_type=accept,
81+
)
82+
return response
6683
if self.model_obj.has_schema:
6784
self.model_obj.validate_response(ret)
6885
return web.json_response(ret)

deepaas/model/v2/test.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,50 @@ class TestModel(base.BaseModel):
4747
),
4848
}
4949

50+
_logo = (
51+
b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x1e\x00\x00\x00"
52+
b"\r\x08\x04\x00\x00\x00\x14{\x16\x1c\x00\x00\x02\x13IDAT(\xcf\x8d"
53+
b"\xd2?h\x94w\x00\xc6\xf1\xcf\xef\xbd\xfc9\xe3\x19\xacb\x8c\xa2C"
54+
b"\x13-\x81\xe8 (\x11\xa4\x831 \xb4v\xb0B\xc0B\xa5\x10\x1c\x1c\xc4"
55+
b"\xd2RQ\x14T\xecRE\xcd\xd0\x16\x11$\x8a\x82E\xba(\xddl\xc1:J\xa8CC"
56+
b"\xd2\x98\xd8\xd2\x10EI\xcc\xdd\xe9\xdd\xbdw^\xee=\x07\x15\x17"
57+
b"\x07\xbf\xd33<\xdf\xe1\x81\x87\xb7l\x95\xd5\xf6*\xb6\t\xcd\xb6"
58+
b"\x19\xb0!\x8a\xbc\x17[\xe5\xde\xc8\x9a]7\xaf\xa8l\x7f\xca\xb7"
59+
b"\xf6\xbdSh\xa0\xd7\x9d0\x9f\x8e*\tX,\xb7F\x8f5v\xfa\xdaM\x07u"
60+
b"\xd5>>\xb3E\xc5\xad\xcc\xc8G\x9ez\x18\xe6;\xb4\x9b\xecx\xf2\x1f!"
61+
b"\xd2o\xd8\xac\x11Cr\xda}\xe3\xb9X\xcds+\xd3B\xb3S\xca\xe6<S"
62+
b"\xf4\x9d\xd5\xae\xb9\xa9$\x96u44\xb2[\xc9y\xfdN+\xc9\xfbB\xc5e;"
63+
b"\xcc\xb9gQ\x94\xf2\xbd\xaa\x1f,u\\\xa2l@\xc5#\xbbu;\xach/\xe3~"
64+
b"\nQ\x9f\xa6\xe0\xa8\xbc?<\xd0m\xd4\x84\xce\x86\xe0\x88\x17NE\x8d"
65+
b"\xbeRv\xd1S\xbf\x89\xf5\x0f \n\xce\x19\xa7f\xd7\xeb\xfd\xbd\xf2"
66+
b"\xc6\x0c\x1b6e}\x149\xa0\xe2\xe7\xd0d\x97\x82+ZM\xb9\xadh\xed"
67+
b"\xeb\xf6\xe7\xaaL\x1a\x8c\xa2Ni\x0e\xc9\xb9\xa2n\xc6\xa6\x86`@"
68+
b"\xc5\xa5\xd0\xa4O\xde\x8d\xd0\xa2\xcf\x0bg\xc5\xb6\x7f\x824\xc7L"
69+
b"\xb1G\xd1\xa0\xcf\x9cT\x90\xb7\xd1\xbf\xb2\x86\xfc\xa2\xec\xba"
70+
b"\x16\xc1\xaf\x12W\r\x9auO\x8f\x8a1=\x96\xe8\x97uXH\xf9\xd2\xdfr"
71+
b"\xee\xbb\xe0\xae\xa5:\x9c7f\xd4\t\x0b\xdb\xf4\xb2\xdc\x8f\xfe7m"
72+
b"\xc8j]b\xa3b9%\x17\xb5\x04\xed\x92\xf0\xac\xbeB!\xca'\x8b\xc5"
73+
b"\xd2\x16\xc9\x84I\xea\x0b\x94\xb4j\x93\x0b9\xd5\xa8Z\xcbX\xe7"
74+
b"\x96MVXe$\xf5W\x924\xd8\xac\xb5\xfe\xd02IRU\x901%\xa3P\xef\xf5"
75+
b"\x81'>4\x1b\xddH>\xad7*\xd7\xb2V\n\xa8\xf8\x1djH)z\xa0\xd30\xfe1#"
76+
b"\x98V\xd5d\xce\x8c\xa2\x92\x89\xfa*\xd3xl^lZ\xde\x9f\xe27\xf7|"
77+
b"\tz\x97\xc9?\xa6\x13[\xb9\x00\x00\x00\x00IEND\xaeB`\x82"
78+
)
79+
5080
def warm(self):
5181
LOG.debug("Test model is warming...")
5282

5383
def predict(self, **kwargs):
5484
LOG.debug("Got the following kw arguments: %s", kwargs)
55-
return {
85+
d = {
5686
"date": "2019-01-1",
5787
"labels": [{"label": "foo", "probability": 1.0}]
5888
}
89+
if kwargs.get("accept") == "text/plain":
90+
return str(d)
91+
elif kwargs.get("accept") == "image/png":
92+
return self._logo
93+
return d
5994

6095
def train(self, *args, **kwargs):
6196
sleep = kwargs.get("sleep", 1)
@@ -81,6 +116,12 @@ def get_predict_args(self):
81116
"be one of the choices declared in 'enum'"),
82117
enum=["foo", "bar"],
83118
validate=validate.OneOf(["foo", "bar"]),
119+
),
120+
"accept": fields.Str(
121+
description=("Media type(s) that is/are acceptable for the "
122+
"response."),
123+
validate=validate.OneOf(["text/plain", "image/png"]),
124+
location="headers",
84125
)
85126
}
86127

deepaas/tests/test_v2_api.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ async def test_model_not_found(self):
9292
async def test_predict_no_parameters(self):
9393
ret = await self.client.post("/v2/models/deepaas-test/predict/")
9494
json = await ret.json()
95+
print(json)
9596
self.assertDictEqual(
9697
{
9798
'parameter': ['Missing data for required field.'],

doc/source/user/v2-api.rst

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,30 @@ In order to define a custom response, the ``response`` attribute is used:
241241

242242
schema = Response
243243

244+
Returning different content types
245+
*********************************
246+
247+
Sometimes it is useful to return something different than a JSON file. For such
248+
cases, you can define an additional argument ``accept`` defining the content
249+
types that you are able to return as follows::
250+
251+
def get_predict_args():
252+
return {
253+
"accept": fields.Str(
254+
description="Media type(s) that is/are acceptable for the response.",
255+
validate=validate.OneOf(["text/plain"]),
256+
}
257+
258+
Consequantly, the predict calls will receive an ``accept`` argument containing
259+
the content type requested by the user.
260+
261+
.. important:: Please be aware the the JSON content type (i.e.
262+
``application/json``) must be always supported and it will be defined and
263+
presented to the user even if you do not define it in your ``accept``
264+
argument. This means that if you do not receive an accept argument, or the
265+
accept argument is empty you **must** return content that can be redered as
266+
a JSON following the schema definition described above.
267+
244268
Using classes
245269
-------------
246270

0 commit comments

Comments
 (0)