Posted at

Flaskで任意のデータを引数として受け取る方法

More than 1 year has passed since last update.


1. はじめに

前回はFlaskの拡張ポイントを利用して、全てのURLにPrefixを追加する方法について説明しました。今回も同様にFlaskの拡張ポイントを利用して、@app.route()関数デコレータが付与された関数の引数として、任意のデータを受け取る方法について説明したいと思います。


1.1. (おさらい)FlaskのURLパス変数について

FlaskではURLの一部の値をパス変数として、引数で受け取ることができます。その際、Converterを利用してデータ型の変換を行うことも可能です。

http://flask.pocoo.org/docs/0.12/quickstart/#variable-rules


Variable Rules

To add variable parts to a URL you can mark these special sections as <variable_name>. Such a part is then passed as a keyword argument to your function. Optionally a converter can be used by specifying a rule with <converter:variable_name>. Here are some nice examples:


公式ドキュメントのサンプルより引用

@app.route('/user/<username>')

def show_user_profile(username):
# show the user profile for that user
return 'User %s' % username
@app.route('/post/<int:post_id>')
def show_post(post_id):
# show the post with the given id, the id is an integer
return 'Post %d' % post_id



  • Flaskがデフォルトで用意しているConverterの一覧(公式ドキュメントより引用)

データ型
説明

string
accepts any text without a slash (the default)

int
accepts integers

float
like int but for floating point values

path
like the default but also accepts slashes

any
matches one of the items provided

uuid
accepts UUID strings

Flaskのデフォルトの仕様では、パス変数を定義したら、必ず引数として定義しなければなりません。

その反対も同様で、引数として定義したものは、パス変数として定義されている必要があります。


1.2. 検証環境


  • Python 2.7.13

  • Flask 0.12.2


2. 任意のデータを引数として受け取るにはurl_value_preprocessorを定義

任意のデータを引数として受け取るにはurl_value_preprocessorを定義します。

url_value_preprocessorについてFlaskの公式ドキュメントを見てましょう。

http://flask.pocoo.org/docs/0.12/api/#flask.Flask.url_value_preprocessor


url_value_preprocessor(f)

Registers a function as URL value preprocessor for all view functions of the application. It’s called before the view functions are called and can modify the url values provided.


url_value_preprocessorとは?


  • 全てのView Function(@app.route()関数デコレータが付与された関数)で利用される。

  • 「URL Value(パス変数)を処理する関数」を登録する機能である。

  • View Functionの実行前に呼び出される。

  • 引数として渡されるURL Value(パス変数)を操作(追加、変更、削除)することができる。

つまり独自のurl_value_preprocessorを定義することで、任意のデータを引数として受け取ることができるようになります。


2.1. ソースコード

url_value_preprocessorのサンプルとして、URLパスには含まれていないユーザ情報をURL Value(パス変数)に追加する処理を実装してみます。なお、ユーザ情報の取得は本来であればデータベースやKVS、外部の連携システム等から取得すると思いますが、今回は適当なダミーデータとします。要件に応じて自由に実装してください。

URL Value(パス変数)は辞書型(キー:引数名、値:引数として受け取りたいもの)のため、いろいろなデータ型の情報を追加することができます。

今回は独自に定義したクラス(User_Info)のオブジェクトを追加し、引数として受け取ることを試してみます。

クラスを定義するのが面倒な場合、クラスではなく辞書型のデータを利用することも可能です。


url_value_preprocessor_app.py

# -*- coding: utf-8 -*-

import os
import datetime
from flask import Flask, make_response, jsonify
from werkzeug.routing import Rule

# flask
app = Flask(__name__)

# original data class for use as an argument ★ポイント1
class User_Info():
userid = "dummy"
roll = "staff"
registered_at = None

def __str__(self):
return "User_Info=[userid={0}, roll={1}, registered_at={2}]" \
.format(self.userid, self.roll, self.registered_at)

# original url_value_preprocessor ★ポイント2
@app.url_value_preprocessor
def add_user_info(endpoint, values):
# this fuction is called by all request
# if specific request only, judge by the value of endpoint
print "url_value_preprocessor={0}, endpoint={1}".format("add_user_info", endpoint)

if not endpoint is None:
# retrieve user infomation from database, kvs, or other system
# ex. return type is dictionary
# user_info = {'userid':'0123456789', 'roll':'employee', 'registered_at':'2017-09-01T07:32:45Z'}
# ex. return type is object of original class
user_info = User_Info()
user_info.userid = "0123456789"
user_info.roll = "employee"
user_info.registered_at = datetime.datetime(2017, 9, 1, 7, 32, 45)

# add original data to url_values
values["userInfo"] = user_info

# rest api ★ポイント3
@app.route('/hello', methods=['GET'])
def hello_world(userInfo):
# if forget url_value_preprocessor, an error occurs
# 'TypeError: hello_world() takes exactly 1 argument (0 given)'
print userInfo
return make_response(jsonify({'result':'hello world!'}))

@app.route('/goodbye', methods=['GET'])
def goodbye():
# an error occurs because not define argument
# 'TypeError: goodbye() takes no arguments (1 given)'
return make_response(jsonify({'result':'goodbye!'}))

# main
if __name__ == "__main__":
# 設定されている全ての url_value_preprocessor を表示
print app.url_value_preprocessors
app.run(host='localhost', port=3000)



2.2. 動作確認


引数にuserInfoを定義した関数(hello_world)を呼び出した場合

C:\demo>python url_value_preprocessor_app.py

{None: [<function add_user_info at 0x000000000312B358>]}
* Running on http://localhost:3000/ (Press CTRL+C to quit)
url_value_preprocessor=add_user_info, endpoint=hello_world
User_Info=[userid=0123456789, roll=employee, registered_at=2017-09-01 07:32:45]
127.0.0.1 - - [05/Oct/2017 21:26:57] "GET /hello HTTP/1.1" 200 -

標準出力の結果({None: [<function add_user_info at 0x000000000312B358>]})からurl_value_preprocessorとして、今回実装したadd_user_info関数が設定されていることが分かります。

/helloにアクセスすると、最初にurl_value_preprocessorであるadd_user_info関数が実行され、任意のデータ(今回はuserInfo)をURL Value(パス変数)に登録します。

次にhello_world関数が実行されますが、この時点でuserInfoがURL Value(パス変数)に存在するため、引数として受け取ることができるようになります。


引数にuserInfoを定義していない関数(goodbye)を呼び出した場合

url_value_preprocessor=add_user_info, endpoint=goodbye

[2017-10-05 21:27:20,566] ERROR in app: Exception on /goodbye [GET]
Traceback (most recent call last):
File "C:\Python27\lib\site-packages\flask\app.py", line 1982, in wsgi_app
response = self.full_dispatch_request()
File "C:\Python27\lib\site-packages\flask\app.py", line 1614, in full_dispatch_request
rv = self.handle_user_exception(e)
File "C:\Python27\lib\site-packages\flask\app.py", line 1517, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "C:\Python27\lib\site-packages\flask\app.py", line 1612, in full_dispatch_request
rv = self.dispatch_request()
File "C:\Python27\lib\site-packages\flask\app.py", line 1598, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
TypeError: goodbye() takes no arguments (1 given)
127.0.0.1 - - [05/Oct/2017 21:27:20] "GET /goodbye HTTP/1.1" 500 -

/goodbyeにアクセスすると、引数にuserInfoを定義していない関数(goodbye)が呼ばれるため、想定通りエラーになります。

エラーにならないようにするには、以下のどちらかを行う必要があります。


  • 引数にuserInfoを定義して受け取る


  • add_user_info関数で、endpointの値で判断して処理をスキップする


存在しないパス(/hello2)にアクセスした場合

url_value_preprocessor=add_user_info, endpoint=None

127.0.0.1 - - [05/Oct/2017 21:27:57] "GET /hello2 HTTP/1.1" 404 -

存在しないパスのためendpointNoneになりますが、この場合でもurl_value_preprocessorは処理が実行されます。つまり、全てのリクエストが処理対象になるのが確認できたかと思います。

url_value_preprocessorを定義する際は、この前提を意識して設計や実装を行う必要があります。


3. さいごに

今回はFlaskの拡張ポイント(url_value_preprocessor)を利用して、任意のデータを引数として受け取る方法について説明しました。

Javaでspring mvcを利用したことがある方には

org.springframework.web.method.support.HandlerMethodArgumentResolverと同様の機能であることが分かるかと思います。

開発言語は違ってもWebアプリケーション用のフレームワークでは、同じような機能が実装されている可能性があると思って調べてみるのもよいかもしれません。