Лучший способ принять несколько типов запросов в одном методе просмотра?

Я пытаюсь разоблачить API для различных методов запросов (GET, url x-www-form-urlencoded POST и json POST):

@app.route('/create', methods=['GET', 'POST'])
def create_file():
    if request.method == 'GET':
        n = request.args.get('n')
        t = request.args.get('t')
    if request.method == 'POST':
        if request.json:
            n = request.json['n']
            t = request.json['t']
        else:
            n = request.form['n']
            t = request.form['t']
    try:
        n = int(n)
    except:
        n = 1
    ...

Вышеизложенное выглядит слишком подробным. Есть ли более простой или лучший способ написать это? Спасибо.

Ответ 1

Это выглядит лучше? На мой взгляд, это немного чище, если вы можете согласиться переместить запрос JSON POST на другой маршрут (который вы действительно должны делать в любом случае).

def _create_file(n, t):
    try:
        n = int(n)
    except:
        n = 1
    ...

@app.route('/create')
def create_file():
    n = request.args.get('n')
    t = request.args.get('t')
    return _create_file(n, t)

@app.route('/create', methods = ['POST'])
def create_file_form():
    n = request.form.get('n')
    t = request.form.get('t')
    return _create_file(n, t)

@app.route('/api/create', methods = ['POST'])
def create_file_json():
    if not request.json:
        abort(400); # bad request
    n = request.json.get('n')
    t = request.json.get('t')
    return _create_file(n, t)

Ответ 2

Нет ничего, что помешало бы вам переписать код:

@app.route('/create', methods=['GET', 'POST'])
def create_file():
    params = None
    if request.method == 'GET':
        params = request.args
    if request.method == 'POST':
        if request.json:
            params = request.json
        else:
            params = request.form

    n = params.get('n')
    t = params.get('t')

    try:
        n = int(n)
    except:
        n = 1
    ...

Ответ 3

Используйте Flask-Restful расширение, как это было предложено другими. Затем вы можете сделать что-то вроде:

class CreateFile(Resource):
    def get(self):
       args = parser.parse_args()
       n,t = args['n'], args['t']

    def post(self, todo_id):
       # do post stuff

и т.д.

Ответ 4

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

@app.route('/create', methods=['GET', 'POST'])
def create_file():
    n, t = get_nt_request_data()
    return process_n(n)

def get_nt_request_data():
    if request.method == 'GET':
        return get_nt_query_params()
    if request.method == 'POST':
        if request.json:
            return get_nt_json()
        else:
            return get_nt_form()
    return n, t

def get_nt_query_params():
    n = request.args.get('n')
    t = request.args.get('t')
    return n, t

def get_nt_json():
    n = request.json['n']
    t = request.json['t']
    return n, t

def get_nt_form():
    n = request.form['n']
    t = request.form['t']
    return n, t

def process_n(n):
    try:
        n = int(n)
    except:
        n = 1

Теперь это, конечно, не короче, но я лично думаю, что это намного яснее. Каждая индивидуальная функция имеет четко определенную цель и не засоряется. Я лично сделал бы "n, t" в объект с двумя полями, но это полностью зависит от вас и того, что работает для вашего приложения. Шаг 2: у нас есть довольно очевидная копия/вставка. Пусть очистит его.

@app.route('/create', methods=['GET', 'POST'])
def create_file():
    n, t = get_nt_request_data()
    return process_n(n)

def get_nt_request_data():
    if request.method == 'GET':
        return get_nt_query_params()
    if request.method == 'POST':
        if request.json:
            return get_nt_json()
        else:
            return get_nt_form()
    return n, t

def get_nt_query_params():
    return build_nt(request.args)

def get_nt_json():
    return build_nt(request.json)

def get_nt_form():
    return build_nt(request.form)

def build_nt(resource):
    return resource.get("n"), resource.get("t")

def process_n(n):
    try:
        n = int(n)
    except:
        n = 1

Теперь мы куда-то попадаем! Но если мы сделаем это для 20 разных ресурсов (предполагая, что ваши разные конечные точки соответствуют аналогичным правилам для HTTP-глаголов), у нас будет куча функций get_xx_request_data, которые в основном делают то же самое. Пусть параметризует!

@app.route('/create', methods=['GET', 'POST'])
def create_file():
    n, t = get_request_data(build_nt)
    return process_n(n)

def build_nt(resource):
    return resource.get("n"), resource.get("t")

def process_n(n):
    try:
        n = int(n)
    except:
        n = 1

# in a shared module somewhere
def get_request_data(builder):
    if request.method == 'GET':
        return builder(request.args)
    if request.method == 'POST':
        if request.json:
            return builder(request.json)
        else:
            return builder(request.form)
    return n, t

До 11 строк кода для конечной точки и общая функция, которую вы можете повторно использовать для других. (Я предполагаю, что это концептуально похоже на то, что делают доступные фреймворки, у меня не было возможности проверить их.)

Последнее примечание: создание файла с запросом GET позволит вам получить некоторые поднятые брови и, возможно, некоторые странные ошибки в зависимости от того, сколько у вас контроля над клиентами и промежуточными прокси. Предполагается, что GET idempotent, поэтому клиенты должны иметь возможность повторять их все волей-неволей, не ожидая каких-либо изменений состояния на сервере ( и создание чего-то определенно является изменением состояния). Теоретически, прокси-сервер должен иметь возможность воспроизводить команду GET после сетевой икоты, даже не сообщая первоначальному клиенту, что он пытался дважды, но на практике это никогда не вызывало у меня проблем.