diff --git a/craton/api/__init__.py b/craton/api/__init__.py index 02e4a81..767cadb 100644 --- a/craton/api/__init__.py +++ b/craton/api/__init__.py @@ -1,6 +1,7 @@ +from datetime import date import os from paste import deploy -from flask import Flask +from flask import Flask, json from oslo_config import cfg @@ -48,10 +49,27 @@ def create_app(global_config, **local_config): return setup_app() +class JSONEncoder(json.JSONEncoder): + + def default(self, o): + if isinstance(o, date): + return o.isoformat() + return json.JSONEncoder.default(self, o) + + +RESTFUL_JSON = { + "indent": 2, + "sort_keys": True, + "cls": JSONEncoder, + "separators": (",", ": "), +} + + def setup_app(config=None): app = Flask(__name__, static_folder=None) app.config.update( - PROPAGATE_EXCEPTIONS=True + PROPAGATE_EXCEPTIONS=True, + RESTFUL_JSON=RESTFUL_JSON, ) app.register_blueprint(v1.bp, url_prefix='/v1') return app diff --git a/craton/api/v1/base.py b/craton/api/v1/base.py index 387a355..8404268 100644 --- a/craton/api/v1/base.py +++ b/craton/api/v1/base.py @@ -1,5 +1,6 @@ import functools import inspect +import json import re import urllib.parse as urllib @@ -8,6 +9,7 @@ import decorator import flask import flask_restful as restful +from craton import api from craton.api.v1.validators import ensure_project_exists from craton.api.v1.validators import request_validate from craton.api.v1.validators import response_filter @@ -22,10 +24,14 @@ class Resource(restful.Resource): response_filter] def error_response(self, status_code, message): - resp = flask.jsonify({ - 'status': status_code, - 'message': message - }) + body = json.dumps( + { + 'status': status_code, + 'message': message + }, + **api.RESTFUL_JSON, + ) + resp = flask.make_response("{body}\n".format(body=body)) resp.status_code = status_code return resp diff --git a/craton/api/v1/validators.py b/craton/api/v1/validators.py index 5d7a546..05f18ba 100644 --- a/craton/api/v1/validators.py +++ b/craton/api/v1/validators.py @@ -1,11 +1,10 @@ # The code is auto generated, your change will be overwritten by # code generating. -from datetime import date from functools import wraps from werkzeug.datastructures import MultiDict, Headers -from flask import request, current_app, json +from flask import request, current_app from flask_restful import abort from flask_restful.utils import unpack from jsonschema import Draft4Validator @@ -143,14 +142,6 @@ def normalize(schema, data, required_defaults=None): return _normalize(schema, data), errors -class JSONEncoder(json.JSONEncoder): - - def default(self, o): - if isinstance(o, date): - return o.isoformat() - return json.JSONEncoder.default(self, o) - - class FlaskValidatorAdaptor(object): def __init__(self, schema): @@ -282,13 +273,7 @@ def response_filter(view): if errors: abort(500, message='Expectation Failed', errors=errors) - return current_app.response_class( - json.dumps(resp, cls=JSONEncoder) + '\n', - status=status, - headers=headers, - mimetype='application/json' - ) - + return resp, status, headers return wrapper diff --git a/craton/tests/functional/__init__.py b/craton/tests/functional/__init__.py index dd96671..8aad04a 100644 --- a/craton/tests/functional/__init__.py +++ b/craton/tests/functional/__init__.py @@ -1,5 +1,6 @@ import contextlib import docker +import json import requests from retrying import retry from sqlalchemy import create_engine @@ -220,28 +221,49 @@ class TestCase(testtools.TestCase): def assertBadRequest(self, response): self.assertEqual(requests.codes.BAD_REQUEST, response.status_code) + def assertJSON(self, response): + if response.text: + try: + data = json.loads(response.text) + except json.JSONDecodeError: + self.fail("Response data is not JSON.") + else: + reference = "{formatted_data}\n".format( + formatted_data=json.dumps( + data, indent=2, sort_keys=True, separators=(',', ': ') + ) + ) + self.assertEqual( + reference, + response.text + ) + def get(self, url, headers=None, **params): resp = self.session.get( url, verify=False, headers=headers, params=params, ) + self.assertJSON(resp) return resp def post(self, url, headers=None, data=None): resp = self.session.post( url, verify=False, headers=headers, json=data, ) + self.assertJSON(resp) return resp def put(self, url, headers=None, data=None): resp = self.session.put( url, verify=False, headers=headers, json=data, ) + self.assertJSON(resp) return resp def delete(self, url, headers=None, body=None): resp = self.session.delete( url, verify=False, headers=headers, json=body, ) + self.assertJSON(resp) return resp def create_cloud(self, name, variables=None):