Merge "Add expose body decorator, status_code argument"

This commit is contained in:
Zuul 2020-11-18 06:52:46 +00:00 committed by Gerrit Code Review
commit b783db7004
2 changed files with 203 additions and 1 deletions

View File

@ -32,7 +32,7 @@ pecan_json_decorate = pecan.expose(
generic=False)
def expose():
def expose(status_code=None):
def decorate(f):
@ -40,6 +40,8 @@ def expose():
def callfunction(self, *args, **kwargs):
try:
result = f(self, *args, **kwargs)
if status_code:
pecan.response.status = status_code
except Exception:
try:
@ -58,6 +60,18 @@ def expose():
else:
pecan.response.status = 500
def _empty():
pecan.request.pecan['content_type'] = None
pecan.response.content_type = None
# never return content for NO_CONTENT
if pecan.response.status_code == 204:
return _empty()
# don't encode None for ACCEPTED responses
if result is None and pecan.response.status_code == 202:
return _empty()
return json.dumps(result)
pecan_json_decorate(callfunction)
@ -66,6 +80,31 @@ def expose():
return decorate
def body(body_arg):
"""Decorator which places HTTP request body JSON into a method argument
:param body_arg: Name of argument to populate with body JSON
"""
def inner_function(function):
@functools.wraps(function)
def inner_body(*args, **kwargs):
data = pecan.request.json
if isinstance(data, dict):
# remove any keyword arguments which pecan has
# extracted from the body
for field in data.keys():
kwargs.pop(field, None)
kwargs[body_arg] = data
return function(*args, **kwargs)
return inner_body
return inner_function
def format_exception(excinfo, debug=False):
"""Extract informations that can be sent to the client."""
error = excinfo[1]

View File

@ -0,0 +1,163 @@
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from http import client as http_client
import json
import pecan.rest
import pecan.testing
from ironic import api
from ironic.api.controllers import root
from ironic.api.controllers import v1
from ironic.api import method
from ironic.api import types as atypes
from ironic.common import args
from ironic.tests.unit.api import base as test_api_base
class MyThingController(pecan.rest.RestController):
_custom_actions = {
'no_content': ['GET'],
'response_content': ['GET'],
'response_custom_status': ['GET'],
'ouch': ['GET'],
}
@method.expose()
@args.validate(name=args.string, flag=args.boolean)
def get(self, name, flag):
return {name: flag}
@method.expose()
def no_content(self):
api.response.status_code = 204
return 'nothing'
@method.expose()
def response_content(self):
resp = atypes.PassthruResponse('nothing', status_code=200)
api.response.status_code = resp.status_code
return resp.obj
@method.expose(status_code=202)
def response_custom_status(self):
return 'accepted'
@method.expose()
def ouch(self):
raise Exception('ouch')
@method.expose(status_code=201)
@method.body('body')
@args.validate(body=args.schema({
'type': 'object',
'properties': {
'three': {'type': 'string'},
'four': {'type': 'string', 'maxLength': 4},
'five': {'type': 'string'},
},
'additionalProperties': False,
'required': ['three']
}))
def post(self, body):
return body
class MyV1Controller(v1.Controller):
things = MyThingController()
class MyRootController(root.RootController):
v1 = MyV1Controller()
class TestExpose(test_api_base.BaseApiTest):
block_execute = False
root_controller = '%s.%s' % (MyRootController.__module__,
MyRootController.__name__)
def test_expose(self):
self.assertEqual(
{'foo': True},
self.get_json('/things', name='foo', flag=True)
)
def test_expose_validation(self):
response = self.get_json('/things', name='foo', flag='truish',
expect_errors=True)
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
error = json.loads(response.json['error_message'])
self.assertEqual('Client', error['faultcode'])
self.assertIsNone(error['debuginfo'])
self.assertIn("Unrecognized value 'truish'", error['faultstring'])
def test_response_204(self):
response = self.get_json('/things/no_content', expect_errors=True)
self.assertEqual(http_client.NO_CONTENT, response.status_int)
self.assertIsNone(response.content_type)
self.assertEqual(b'', response.normal_body)
def test_response_content(self):
response = self.get_json('/things/response_content',
expect_errors=True)
self.assertEqual(http_client.OK, response.status_int)
self.assertEqual(b'"nothing"', response.normal_body)
self.assertEqual('application/json', response.content_type)
def test_response_custom_status(self):
response = self.get_json('/things/response_custom_status',
expect_errors=True)
self.assertEqual(http_client.ACCEPTED, response.status_int)
self.assertEqual(b'"accepted"', response.normal_body)
self.assertEqual('application/json', response.content_type)
def test_exception(self):
response = self.get_json('/things/ouch',
expect_errors=True)
error_message = json.loads(response.json['error_message'])
self.assertEqual(http_client.INTERNAL_SERVER_ERROR,
response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertEqual('Server', error_message['faultcode'])
self.assertEqual('ouch', error_message['faultstring'])
def test_post_body(self):
data = {
'three': 'three',
'four': 'four',
'five': 'five'
}
response = self.post_json('/things/', data, expect_errors=True)
self.assertEqual(http_client.CREATED, response.status_int)
self.assertEqual(data, response.json)
def test_post_body_validation(self):
data = {
'three': 'three',
'four': 'fourrrr',
'five': 'five'
}
response = self.post_json('/things/', data, expect_errors=True)
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
error = json.loads(response.json['error_message'])
self.assertEqual('Client', error['faultcode'])
self.assertIsNone(error['debuginfo'])
self.assertIn("Schema error for body:", error['faultstring'])