Проверка пользовательских параметров с ответом JSON для Flask-RESTful

Я использую Flask-RESTful для создания конечных точек API и указываю URL-адрес следующим образом:

api.add_resource(ListVersionsByStaff, '/shots/versions/<int:project_id>/<int:staff_id>')
api.add_resource(ListSeasons, '/seasons/<int:project_id>')

Хотя Flask-RESTful вернет ответ об ошибке, если данный аргумент не равен int, он вернет ответ HTML.

Как я могу вернуть собственный ответ об ошибке JSON, например:

except InvalidParameter as err:
            abort(err.status_code, **err.to_dict())

Проверка значения таким способом тоже не работает, параметр всегда типа String

class SpecificProject(Resource):
    def get(self, project_id):
        print("DEBUG: project_id is [", project_id, "]", file=sys.stderr)
        print("DEBUG: Type is  [", type(project_id), "]", file=sys.stderr)
        if isinstance(project_id, int):
            pass
        else:
            message = "'{}' is not a valid project_id. Hint: this is a number representing primary key.".format(project_id)
            errors = {}
            errors['resource'] = "Project"
            errors['field'] = "project_id"
            errors['code'] = "invalid"
            errors['stack_trace'] = ""
            abort(400, message=message, errors=errors)

выход:

DEBUG: project_id is [ 1 ]
DEBUG: Type is  [ <class 'str'> ]

person Hanxue    schedule 24.08.2017    source источник
comment
Как я понял, вы хотите работать с project_id как с int и перенести проверку параметров запроса на другой уровень (из поля зрения)?   -  person Danila Ganchar    schedule 24.08.2017
comment
Да, я хочу убедиться, что project_id это int. Если это не так, я хочу вернуть пользовательское сообщение об ошибке JSON.   -  person Hanxue    schedule 24.08.2017
comment
У меня была такая же проблема раньше. И я долго искал решение. У Flask нет современных способов проверки (с преобразованием типов). Вы можете проверить параметры POST или GET, используя Flask-WTF или RequestParser. Но эти инструменты не преобразуют значения в указанный тип. Кроме того, они имеют громоздкие описания параметров запроса. Вы можете попробовать этот инструмент. В настоящее время эта библиотека не поддерживает Flask-RESTful («из коробки»), но это небольшая библиотека, и вы можете ее расширить.   -  person Danila Ganchar    schedule 25.08.2017
comment
в любом случае, если вы будете проверять все конечные точки вручную, это будет головная боль   -  person Danila Ganchar    schedule 25.08.2017
comment
@DanilaGanchar спасибо, что поделились github.com/d-ganchar/flask_request_validator , я мог бы использовать его в будущем   -  person Hanxue    schedule 30.08.2017
comment
Если вы найдете лучший способ, дайте мне знать, пожалуйста. удачи.   -  person Danila Ganchar    schedule 30.08.2017
comment
flask_restful в валидатор добавлена ​​поддержка . Вы можете использовать его в будущем.   -  person Danila Ganchar    schedule 08.09.2017


Ответы (1)


Мое решение состоит в том, чтобы расширить класс Flask-RESTful Api и реализовать собственный обработчик ошибок. официальная документация немного объясняет о расширении, но не вдаваться в достаточные подробности.

Пользовательское сообщение об ошибке

Все ошибки в моем приложении находятся в этой структуре

class InvalidParameter(Exception):
    status_code = 400

    def __init__(self, message, status_code=None, resource=None,
                field=None, code=None, stack_trace=None):
        Exception.__init__(self)
        self.message = message
        if status_code is not None:
            self.status_code = status_code
        self.resource = resource
        self.field = field
        self.code = code
        self.stack_trace = stack_trace

    def to_dict(self):
        rv = {}
        errors = {}
        rv['message'] = self.message
        errors['resource'] = self.resource
        errors['field'] = self.field
        errors['code'] = self.code
        errors['stack_trace'] = self.stack_trace
        rv['errors'] = errors
        return rv

Это то, что Flask-RESTful возвращает по умолчанию

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>404 Not Found</title>
<h1>Not Found</h1>
<p>The requested URL was not found on the server.  If you entered the URL manually please check your spelling and try again.</p>

Вывод должен быть JSON в этом формате

{
    "message": "The requested URL was not found on the server.  If you entered the URL manually please check your spelling and try again. You have requested this URI [/projects/1asd] but did you mean /projects/<int:project_id> or /projects or /project/new ?",
    "errors": {
        "resource": null,
        "field": null,
        "code": "invalid_url",
        "stack_trace": null
    }
}

Решение

Расширьте класс Api и перезапишите метод handle_error для 404 ошибок.

class CustomApi(Api):
    def handle_error(self, e):
        code = getattr(e, 'code', 404)
        if code == 404:
            response_dict = Api.handle_error(self, e).__dict__
            resp_message = response_dict['response'][0]
            error_message = re.sub(r'.*"(.*)".*', r'\1', resp_message.decode('utf-8'), flags=re.DOTALL)
            err = InvalidParameter(error_message, stack_trace=None,
                                    resource=None, code="invalid_url", field=None)
            return self.make_response(err.to_dict(), 404) #{'message': "something", 'error': {'sub1': 'val1'}}, 404)

        return super(Api, self).handle_error(e)

Словарь handle_error в этом формате

{'headers': Headers([('Content-Type', 'application/json'), ('Content-Length', '263')]), '_status_code': 404, '_status': '404 NOT FOUND', 'direct_passthrough': False, '_on_close': [], 'response': [b'{\n    "message": "The requested URL was not found on the server.  If you entered the URL manually please check your spelling and try again. You have requested this URI [/projects/1asd] but did you mean /projects/<int:project_id> or /projects or /project/new ?"\n}\n']}

Я хочу повторно использовать сгенерированный 'response':'message', но не в формате по умолчанию. message не был правильно отформатирован JSON, поэтому я удаляю все, кроме содержимого message, используя это регулярное выражение

error_message = re.sub(r'.*"(.*)".*', r'\1', resp_message.decode('utf-8'), flags=re.DOTALL)

Обратите внимание, что re.DOTALL необходимо для удаления \n, добавленного Flask-RESTful.

Код, который на самом деле формирует ответ JSON, — self.make_response(err.to_dict(), 404).

Для всех других ошибок, отличных от 404 (например, 400, 500, 503), ошибка просто передается в исходный класс Flask-RESTful Api.

Обратите внимание, что при создании приложения Flask вам необходимо использовать собственный класс API и отловить все ошибки 404:

app = Flask(__name__) 
api = CustomApi(app, catch_all_404s=True)
person Hanxue    schedule 30.08.2017
comment
Flask - грязные хаки для простых/элементарных задач... ))) - person Danila Ganchar; 30.08.2017
comment
Мне по-прежнему нравится простота Flask по сравнению с полнофункциональными альтернативами, такими как Django. - person Hanxue; 30.08.2017