FlaskとBottleを比較する


Flaskのことをいろいろ調べていたら、同じようなPythonのWebフレームワークとしてBottleというのがあることが分かりました。
どちらも、マイクロフレームワークとして評価されているWebフレームワークです。
WEBで見る限り、非常によく似ていたので違いを試してみました。

Flask も Bottle も、見た目は全く同じにすることができます。




<インストール>
どちらもpipを使ってインストールを行います。
$ sudo apt install python3-pip python3-setuptools

$ python3 -m pip install flask

$ python3 -m pip list | grep Flask
Flask          1.1.2


$ sudo apt install python3-pip python3-setuptools

$ python3 -m pip install bottle

$ python3 -m pip list | grep bottle
bottle         0.12.19

<Pythonコード>
それぞれで使用するpythonコードは以下のようになります。
こちらがFlaskです。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from flask import Flask, render_template, redirect
import datetime
app = Flask(__name__)

@app.route("/")
def index():
    print("Start index")
    now = datetime.datetime.now()
    timeString = now.strftime("%Y-%m-%d %H:%M")
    templateData = {
       'title' : 'HELLO!',
       'name' : 'flask!',
       'ampm' : 1,
       'time': timeString
       }
    #./templates/sample.html
    return render_template('sample.html', **templateData)

@app.route("/hello")
def hello():
    return "Hello World!"

@app.route("/move")
def move():
    print("redirect to root")
    return redirect('/')

if __name__ == "__main__":
    print("Reloding...")
    print("app.url_map={}".format(app.url_map))
    app.run(host='0.0.0.0', port=8080, debug=True)

こちらがBottleです。
static()はテンプレートのstatic_fileから呼ばれる関数で、staticディレクトリへのフルパスを戻します。
dict()の最後のurlはurl()への関数ポインターで、こちらもテンプレートから呼ばれます。
Flaskではこのあたりのことはrender_template()がやってくれます。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from bottle import route, run, view, static_file, url, redirect
import datetime

@route('/static/<filepath:path>', name='static_file')
def static(filepath):
    print("Start staic")
    return static_file(filepath, root="./static")

@route('/')
@view("sample")
def index():
    print("Start index")
    now = datetime.datetime.now()
    timeString = now.strftime("%Y-%m-%d %H:%M")
    #../views/main.html
    #return template('sample', title='HELLO', name='bottle!', ampm=1, time=timeString, url=url)
    print("url={}".format(url))
    return dict(title='HELLO', name='bottle!', ampm=1, time=timeString, url=url)

@route('/hello')
def hello():
    return "Hello World!"

@route('/move')
def move():
    print("redirect to root")
    redirect("/")

if __name__ == "__main__":
    print("Reloading...")
    run(host='0.0.0.0', port=8081, debug=True, reloader=True)

<テンプレートファイル>
それぞれで使用するテンプレートファイルは以下のようになります。
こちらがFlaskです。
<!DOCTYPE html>
   <head>
      <title>{{ title }}</title>
      <link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"/>
   </head>

   <body>
      <h1>Hello, {{ name }}</h1>
{% if ampm == 0 %}
      <h2>Good Morning</h2>
      <h2>おはようございます</h2>
{% else %}
      <h2>Good Evening</h2>
      <h2>こんばんは</h2>
{% endif %}
      <h2>The date and time on the server is: {{ time }}</h2>
      <ul>
{% for no in range(10) %}
        <li>Number {{no}}</li>
{% endfor %}
      </ul>
      <img src="{{url_for('static', filename='images/smile-icon.png')}}">
   </body>
</html>

こちらがBottleです。
<!DOCTYPE html>
   <head>
      <title>{{ title }}</title>
      <link type="text/css" rel="stylesheet" href="{{ url('static_file', filepath='style.css') }}"/>
   </head>

   <body>
      <h1>Hello, {{ name }}</h1>
% if ampm == 0:
      <h2>Good Morning</h2>
      <h2>おはようございます</h2>
% else:
      <h2>Good Evening</h2>
      <h2>こんばんは</h2>
% end
      <h2>The date and time on the server is: {{ time }}</h2>
      <ul>
% for no in range(10):
        <li>Number {{no}}</li>
% end
      </ul>
      <img src="{{url('static_file', filepath="images/smile-icon.png")}}">
   </body>
</html>

<CSSファイル>
どちらも同じ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
Python Code $HOME/flask/sample.py
テンプレート $HOME/flask/templates/sample.html
css $HOME/flask/static/style.css
images $HOME/flask/static/images/smile-icon.png

Bottle
Python Code $HOME/bottle/sample.py
テンプレート $HOME/bottle/views/sample.html
css $HOME/bottle/static/style.css
images $HOME/bottle/static/images/smile-icon.png

<起動画面>




<日本語マニュアル>
Flask
https://a2c.bitbucket.io/flask/tutorial/index.html

Bottle
https://bottl-translate-ja.readthedocs.io/en/latest/01_1_tutorial.html


<英語チュートリアル>
Flask
https://flask.palletsprojects.com/en/1.1.x/tutorial/

Bottle
http://bottlepy.org/docs/dev/tutorial.html



少し前まではFlaskはpython3に対応してなかったみたいですが、今は対応しています。
ほとんど違いが無いのですが、テンプレートファイルに渡すテンプレートデータの渡し方が、Flaskのほうがスマートだと思います。
なお、FlaskでもBottleと同じように、ずらずらとテンプレートデータを並べて指定することができます。

Flask
templateData = {
      'title' : 'HELLO!',
      'name' : 'flask!',
      'ampm' : 1,
      'time': timeString
}
return render_template('sample.html', **templateData)

Bottol
return dict(title='HELLO', name='bottle!', ampm=1, time=timeString, url=url)

また、テンプレートファイルの指定方法が違います。
Flaskではrender_template()の第一引数でテンプレートファイルを指定しますが、
Bottleでは@view("sample")で指定します。

Bottleの決定的な違いは、テンプレート内にpythonコードを埋め込むことができます。
<!DOCTYPE html>
   <head>
      <title>{{ title }}</title>
      <link type="text/css" rel="stylesheet" href="{{ url('static_file', filepath='style.css') }}"/>
   </head>

   <body>
      <h1>Hello, {{ name }}</h1>
% if ampm == 0:
      <h2>Good Morning</h2>
      <h2>おはようございます</h2>
% else:
      <h2>Good Evening</h2>
      <h2>こんばんは</h2>
% end
      <h2>The date and time on the server is: {{ time }}</h2>
<%
import datetime
import locale
d = datetime.datetime.today()
date ='{0:d}年{1:d}月{2:d}日'.format(d.year, d.month, d.day)
%>
      <h2>date is: {{ date }}</h2>
      <ul>
% for no in range(10):
        <li>Number {{no}}</li>
% end
      </ul>
      <img src="{{url('static_file', filepath="images/smile-icon.png")}}">
   </body>
</html>




この機能はFlaskにはありません。
この違いは使っているテンプレートエンジンの違いによるものです。

Flaskのテンプレートエンジン
Jinja2 Template Engine

Bottleのテンプレートエンジン
Simple Template Engine

ドキュメントはJinja2(ニンジャと呼びます)の方がしっかりしています。
Bottleの方が一見、高機能に見えますが、ロジックと見た目(テンプレート)を分離するという点では
このような機能を持たないFlaskの方が正しい実装に見えます。

テンプレートエンジンの基本的な使い方はこちらを 見ると良く分かります。



FlaskとBottleでは採用しているWSGI Serverが違いますが、機能的には同じように見えます。
curl --dump-header - http:/HogeHogeで使用しているServerを確認する事ができます。

FlaskのWSGI Server
Server: Werkzeug/0.16.0 Python/2.7.15+

BottleのWSGI Server
Server: WSGIServer/0.1 Python/2.7.15+



BottleをCtrl-Cで止めると、再開時にこの様なエラーとなる事が有ります。
このエラーになったときは、一度セッションを終了すると復活します。
Flaskではこのようなエラーになる事は有りません。
エラー時の動作など、Flaskの方が安定して動きます。