Лучший способ принять несколько типов запросов в одном методе просмотра?
Я пытаюсь разоблачить 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 после сетевой икоты, даже не сообщая первоначальному клиенту, что он пытался дважды, но на практике это никогда не вызывало у меня проблем.