以前こんなんをやっていました。
やってて思うのは、意外とFlaskって紹介記事が少ないんです。特に日本語。
ということで、正攻法で勉強していくしかないと思います。 今回は公式のチュートリアルをなぞって、正しい書き方を勉強したいと思います。
公式HPはこちら。
やるとこ
ここだけ見ていきます。
- Quickstart
- Tutorial
ここさえ写経すればあとは必要に応じて参照すれば問題ないでしょう。 全部なぞるわけではないですが、なんとなく一通り読んでいきます。
Quickstart
HelloWorld的な何か
from flask import Flask app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello, World!'
このシンプルなコードでやっているのは下記の4つです。
- まず、 Flask クラスをインポートしました。このクラスのインスタンスは WSGIアプリケーションです。 まず、このアプリケーションのモジュール名について述べます。 もしあなたが使うモジュールが一つだけなら、 name を使わなければなりません。 それがアプリケーションとして起動したときとモジュールとしてインポートされたときで名前が異なるからです (アプリケーションとして起動したときは 'main' 、インポートされたときはそのインポート名)。 もっと詳しく知りたければ、 Flask のドキュメントを参照してください。
- 次にインスタンスを生成します。モジュールやパッケージの名前は要りません。 このインスタンスはFlaskがテンプレートファイルやスタティックファイルなどをどこから探すのかを 認識するために必要です。
- route() デコレータを使用し、ファンクションを起動するURLをFlaskに教えます。
- そのファンクション名は特定のファンクションに対してURLを生成するためにも使われ、 ファンクションはユーザーのブラウザ上で表示したいメッセージを返します。
(参考:https://a2c.bitbucket.io/flask/quickstart.html)
と、こんな感じです。 Flask1.0系だと実行の方法が以前と違うので、こんな感じで実行する必要があります。
$ export FLASK_APP=hello.py $ flask run * Running on http://127.0.0.1:5000/
これだと外部からはアクセスできないのでご注意を。 外部PCからアクセス可能にするには--host=0.0.0.0のオプションをつけます。
$ export FLASK_APP=hello.py $ flask run --host=0.0.0.0 * Running on http://127.0.0.1:5000/
これでOSにパブリックIPを参照するように通知します。
routing
エンドポイントを指定して、エンドポイントに対する処理を記述します。
@app.route('/') def index(): return 'Index Page' @app.route('/hello') def hello(): return 'Hello World'
@app.route()でエンドポイントを指定し、その直下のメソッドにエンドポイントアクセス時の処理を記述します。
変数ルール
エンドポイントの値の動的読み取りが可能になっている。
@app.route('/user/<username>') def show_user_profile(username): # show the user profile for that user pass @app.route('/post/<int:post_id>') def show_post(post_id): # show the post with the given id, the id is an integer pass
この場合だと、エンドポイント末尾にusernameに指定した場合に、usernameを変数としてメソッドの引数にすることが可能になっている。 下の例では、型も指定している。このようにurlの一部を変数として利用できる。
リダイレクト
エンドポイントの末尾に"/"(スラッシュ)を入れるかどうかで挙動が変わる。
@app.route('/projects/') def projects(): pass @app.route('/about') def about(): pass
上の例では、スラッシュなしでアクセスすると、強引にスラッシュありにリダイレクトされる。 下の例だと、ファイル同じように認識されるため、スラッシュ付きでアクセスしようとすると404エラーになる。
HTTP メソッド
HTTPにはGETやPOSTといったメソッドが分かれています。 methodにそれらを指定することが可能です。
@app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': do_the_login() else: show_the_login_form()
このように、request.methodでメソッドの種類を取得可能です。
リクエストオブジェクト
リクエストにくっついて来るオブジェクトを使用するときには requestをimportします。
from flask import request
使い方はこんな感じ。
@app.route('/login', methods=['POST', 'GET']) def login(): error = None if request.method == 'POST': if valid_login(request.form['username'], request.form['password']): return log_the_user_in(request.form['username']) else: error = 'Invalid username/password' # this is executed if the request method was GET or the # credentials were invalid
requestに格納されているオブジェクトに.formでアクセスしています。
session
セッション情報は session というオブジェクトをimportすることで使用可能です。
from flask import Flask, session, redirect, url_for, escape, request app = Flask(__name__) @app.route('/') def index(): if 'username' in session: return 'Logged in as %s' % escape(session['username']) return 'You are not logged in' @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': session['username'] = request.form['username'] return redirect(url_for('index')) return ''' <form action="" method="post"> <p><input type=text name=username> <p><input type=submit value=Login> </form> ''' @app.route('/logout') def logout(): # remove the username from the session if its there session.pop('username', None) return redirect(url_for('index')) # set the secret key. keep this really secret: app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
Tutorial
チュートリアルでは、”Flaskr”というアプリケーションを作成します。 日本語のチュートリアルと最新(1.0.2)の英語版のチュートリアルで内容がかなり違うのでご注意ください。 今回は最新のチュートリアルをやっていきます。
最新の完全なコードはこちらだそうです。
ディレクトリ階層
最終的にできるディレクトリ階層はこんな感じです。
/home/user/Projects/flask-tutorial ├── flaskr/ │ ├── __init__.py │ ├── db.py │ ├── schema.sql │ ├── auth.py │ ├── blog.py │ ├── templates/ │ │ ├── base.html │ │ ├── auth/ │ │ │ ├── login.html │ │ │ └── register.html │ │ └── blog/ │ │ ├── create.html │ │ ├── index.html │ │ └── update.html │ └── static/ │ └── style.css ├── tests/ │ ├── conftest.py │ ├── data.sql │ ├── test_factory.py │ ├── test_db.py │ ├── test_auth.py │ └── test_blog.py ├── venv/ ├── setup.py └── MANIFEST.in
セットアップ
では、まずflask-tutorialというプロジェクトディレクトリの中に、'flaskr'というディレクトリを作成します。
次に、flaskr/init.pyにアプリケーションファクトリとPythonにパッケージの範囲を通知する旨を記述します。
以下のようにコーディングします。
import os from flask import Flask def create_app(test_config=None): # create and configure the app app = Flask(__name__, instance_relative_config=True) app.config.from_mapping( SECRET_KEY='dev', DATABASE=os.path.join(app.instance_path, 'flaskr.sqlite'), ) if test_config is None: # load the instance config, if it exists, when not testing app.config.from_pyfile('config.py', silent=True) else: # load the test config if passed in app.config.from_mapping(test_config) # ensure the instance folder exists try: os.makedirs(app.instance_path) except OSError: pass # a simple page that says hello @app.route('/hello') def hello(): return 'Hello, World!' return app
create_appがアプリケーションファクトリ関数です。
- app = Flask(name, instance_relative_config=True)
- Flaskインスタンスを作ります。
- app.config.from_mapping()
- いくつかのデフォルトの設定を読み込みます。
- app.config.from_pyfile()
- config.pyから取得した設定値をオーバーライドします。
- os.makedirs()
- @app.route()
- エンドポイントを設定します
アプリケーションを実行してみます。
export FLASK_APP=flaskr export FLASK_ENV=development flask run * Serving Flask app "flaskr" (lazy loading) * Environment: development * Debug mode: on * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Restarting with stat * Debugger is active! * Debugger PIN: 273-900-170
http://127.0.0.1:5000/helloにアクセスすると、HelloWorldが見れるのでは無いでしょうか?
データベース接続
こんどはDB(SQLite3)に接続します。
flaskr/db.pyを作成して、以下のように記述します。
import sqlite3 import click from flask import current_app, g from flask.cli import with_appcontext def get_db(): if 'db' not in g: g.db = sqlite3.connect( current_app.config['DATABASE'], detect_types=sqlite3.PARSE_DECLTYPES ) g.db.row_factory = sqlite3.Row return g.db def close_db(e=None): db = g.pop('db', None) if db is not None: db.close()
gは特殊なオブジェクトで、リクエストごとに固有なオブジェクトとなっています。 ここでは、同じリクエストの中でDBアクセスが複数発生する際にコネクションを一度だけ取得するようにしています。
current_appも特殊なオブジェクトで、Flaskアプリケーションがリクエストをコントロールするために使用されます。 リクエストを排他制御するために使用されます。
DBの初期化にはflaskr/schema.sqlを作成します。
DROP TABLE IF EXISTS user; DROP TABLE IF EXISTS post; CREATE TABLE user ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password TEXT NOT NULL ); CREATE TABLE post ( id INTEGER PRIMARY KEY AUTOINCREMENT, author_id INTEGER NOT NULL, created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, title TEXT NOT NULL, body TEXT NOT NULL, FOREIGN KEY (author_id) REFERENCES user (id) );
flaskr/db.pyに下記を追加します。
def init_db(): db = get_db() with current_app.open_resource('schema.sql') as f: db.executescript(f.read().decode('utf8')) @click.command('init-db') @with_appcontext def init_db_command(): """Clear the existing data and create new tables.""" init_db() click.echo('Initialized the database.')
close_db とinit_db_command関数をアプリケーションインスタンスに登録します。 flaskr/db.pyに下記を追加します。
def init_app(app): app.teardown_appcontext(close_db) app.cli.add_command(init_db_command)
最後にflaskr/init.pyに追記します。
from . import db db.init_app(app) return app
DBの初期化は下記のようにコマンドを実行します。
$flask init-db Initialized the database. flask-tutorial
Blueprints と Views
Blueprintは関連するViewとコードを連携する手法です。
flaskr/auth.pyに下記のように記述します。
import functools from flask import ( Blueprint, flash, g, redirect, render_template, request, session, url_for ) from werkzeug.security import check_password_hash, generate_password_hash from flaskr.db import get_db bp = Blueprint('auth', __name__, url_prefix='/auth')
これによって、authという名のBlueprintが作成されました。 flaskr/init.pyに登録します。
def create_app(): app = ... # existing code omitted from . import auth app.register_blueprint(auth.bp) return app
次に、登録画面を作ります。
flaskr/auth.pyに下記を追記します。
@bp.route('/register', methods=('GET', 'POST')) def register(): if request.method == 'POST': username = request.form['username'] password = request.form['password'] db = get_db() error = None if not username: error = 'Username is required.' elif not password: error = 'Password is required.' elif db.execute( 'SELECT id FROM user WHERE username = ?', (username,) ).fetchone() is not None: error = 'User {} is already registered.'.format(username) if error is None: db.execute( 'INSERT INTO user (username, password) VALUES (?, ?)', (username, generate_password_hash(password)) ) db.commit() return redirect(url_for('auth.login')) flash(error) return render_template('auth/register.html')
次に、Loginに関する挙動を登録します。 再びflaskr/auth.pyに下記を追記します。
@bp.route('/login', methods=('GET', 'POST')) def login(): if request.method == 'POST': username = request.form['username'] password = request.form['password'] db = get_db() error = None user = db.execute( 'SELECT * FROM user WHERE username = ?', (username,) ).fetchone() if user is None: error = 'Incorrect username.' elif not check_password_hash(user['password'], password): error = 'Incorrect password.' if error is None: session.clear() session['user_id'] = user['id'] return redirect(url_for('index')) flash(error) return render_template('auth/login.html')
最後にログアウトの挙動を記載します。 flaskr/auth.pyに下記を追記します。
@bp.route('/logout') def logout(): session.clear() return redirect(url_for('index'))
登録・ログイン周りができたところで、ログイン制約を付けます。
def login_required(view): @functools.wraps(view) def wrapped_view(**kwargs): if g.user is None: return redirect(url_for('auth.login')) return view(**kwargs) return wrapped_view
テンプレートの使用
ベースレイアウトはこんな感じです。
flaskr/templates/base.html
<!doctype html> <title>{% block title %}{% endblock %} - Flaskr</title> <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> <nav> <h1>Flaskr</h1> <ul> {% if g.user %} <li><span>{{ g.user['username'] }}</span> <li><a href="{{ url_for('auth.logout') }}">Log Out</a> {% else %} <li><a href="{{ url_for('auth.register') }}">Register</a> <li><a href="{{ url_for('auth.login') }}">Log In</a> {% endif %} </ul> </nav> <section class="content"> <header> {% block header %}{% endblock %} </header> {% for message in get_flashed_messages() %} <div class="flash">{{ message }}</div> {% endfor %} {% block content %}{% endblock %} </section>
その他画面は下記のようになります。
flaskr/templates/auth/register.html
{% extends 'base.html' %} {% block header %} <h1>{% block title %}Register{% endblock %}</h1> {% endblock %} {% block content %} <form method="post"> <label for="username">Username</label> <input name="username" id="username" required> <label for="password">Password</label> <input type="password" name="password" id="password" required> <input type="submit" value="Register"> </form> {% endblock %}
flaskr/templates/auth/login.html
{% extends 'base.html' %} {% block header %} <h1>{% block title %}Log In{% endblock %}</h1> {% endblock %} {% block content %} <form method="post"> <label for="username">Username</label> <input name="username" id="username" required> <label for="password">Password</label> <input type="password" name="password" id="password" required> <input type="submit" value="Log In"> </form> {% endblock %}
静的ファイル
最後にスタイルシートを作成します。 flaskr/static/style.cssに下記を記述します。
html { font-family: sans-serif; background: #eee; padding: 1rem; } body { max-width: 960px; margin: 0 auto; background: white; } h1 { font-family: serif; color: #377ba8; margin: 1rem 0; } a { color: #377ba8; } hr { border: none; border-top: 1px solid lightgray; } nav { background: lightgray; display: flex; align-items: center; padding: 0 0.5rem; } nav h1 { flex: auto; margin: 0; } nav h1 a { text-decoration: none; padding: 0.25rem 0.5rem; } nav ul { display: flex; list-style: none; margin: 0; padding: 0; } nav ul li a, nav ul li span, header .action { display: block; padding: 0.5rem; } .content { padding: 0 1rem 1rem; } .content > header { border-bottom: 1px solid lightgray; display: flex; align-items: flex-end; } .content > header h1 { flex: auto; margin: 1rem 0 0.25rem 0; } .flash { margin: 1em 0; padding: 1em; background: #cae6f6; border: 1px solid #377ba8; } .post > header { display: flex; align-items: flex-end; font-size: 0.85em; } .post > header > div:first-of-type { flex: auto; } .post > header h1 { font-size: 1.5em; margin-bottom: 0; } .post .about { color: slategray; font-style: italic; } .post .body { white-space: pre-line; } .content:last-child { margin-bottom: 0; } .content form { margin: 1em 0; display: flex; flex-direction: column; } .content label { font-weight: bold; margin-bottom: 0.5em; } .content input, .content textarea { margin-bottom: 1em; } .content textarea { min-height: 12em; resize: vertical; } input.danger { color: #cc2f2e; } input[type=submit] { align-self: start; min-width: 10em; }
実行
実行してみます。
flask-tutorial $flask run
http://127.0.0.1:5000/auth/loginにアクセスすればこんな感じのログイン画面が出るかと思います。
公式では、こんな感じに書いていくみたいですね。
この先もうちょっと続きますが、興味あるところは見れたので今回はこのへんで。。。
感想
駆け足でサラッと流し読みしましたが、基本的な部分は大体抑えられたんではなかろうか?と思ってます。 今まで書いていたのは若干古い書き方のようなので、これからはお行儀の良いFlaskコードを書いていくようにしたいと思います。
そうはいってもシンプルなので、すぐ書けますね。 これぐらい簡単だと使いやすくていいですね。