1. はじめに
前回はFlaskの拡張ポイントを利用して、全てのURLにPrefixを追加する方法について説明しました。今回も同様にFlaskの拡張ポイントを利用して、@app.route()
関数デコレータが付与された関数の引数として、任意のデータを受け取る方法について説明したいと思います。
1.1. (おさらい)FlaskのURLパス変数について
FlaskではURLの一部の値をパス変数として、引数で受け取ることができます。その際、Converterを利用してデータ型の変換を行うことも可能です。
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)のオブジェクトを追加し、引数として受け取ることを試してみます。
クラスを定義するのが面倒な場合、クラスではなく辞書型のデータを利用することも可能です。
# -*- 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. 動作確認
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(パス変数)に存在するため、引数として受け取ることができるようになります。
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
の値で判断して処理をスキップする
url_value_preprocessor=add_user_info, endpoint=None
127.0.0.1 - - [05/Oct/2017 21:27:57] "GET /hello2 HTTP/1.1" 404 -
存在しないパスのためendpoint
はNone
になりますが、この場合でもurl_value_preprocessor
は処理が実行されます。つまり、全てのリクエストが処理対象になるのが確認できたかと思います。
url_value_preprocessor
を定義する際は、この前提を意識して設計や実装を行う必要があります。
3. さいごに
今回はFlaskの拡張ポイント(url_value_preprocessor
)を利用して、任意のデータを引数として受け取る方法について説明しました。
Javaでspring mvcを利用したことがある方には
org.springframework.web.method.support.HandlerMethodArgumentResolver
と同様の機能であることが分かるかと思います。
開発言語は違ってもWebアプリケーション用のフレームワークでは、同じような機能が実装されている可能性があると思って調べてみるのもよいかもしれません。