🐍 Python Bottleフレームワーク徹底解説:シンプルさで始めるWeb開発入門

Web開発

軽量マイクロフレームワークBottleの魅力を探る

はじめに:Bottleとは? 🤔

Bottleは、Pythonで書かれた、高速かつシンプル、軽量なWSGIマイクロWebフレームワークです。2009年にMarcel Hellkamp氏によって開発が始められました。その最大の特徴は、Python標準ライブラリ以外に依存関係がなく、単一のbottle.pyファイルのみで構成されている点です。これにより、非常に簡単に導入でき、デプロイも容易になります。

Bottleの哲学は「シンプルさ」と「軽量さ」にあります。機能は最小限に抑えられていますが、ルーティング、テンプレートエンジン、組み込みの開発用HTTPサーバーなど、Webアプリケーション開発に必要な基本的な機能はしっかりと提供されています。特に、Web開発の初学者や、小規模なアプリケーション、API、プロトタイピングなどを迅速に開発したい場合に最適なフレームワークと言えるでしょう。

Bottleの主な特徴 ✨
  • 軽量・高速: 依存関係が少なく、動作が速い。
  • シンプル: bottle.py という単一ファイルで構成。学習コストが低い。
  • ルーティング: デコレータを使った簡単なURLルーティング。
  • テンプレートエンジン: Pythonicな組み込みテンプレートエンジン(SimpleTemplate)を提供。Jinja2なども利用可能。
  • ユーティリティ: フォームデータ、ファイルアップロード、Cookie、ヘッダーなどへの簡単なアクセス。
  • 開発サーバー: 開発用のHTTPサーバーを内蔵。
  • WSGI準拠: Gunicorn, uWSGI, Pasteなど、様々なWSGIサーバーで動作可能。
  • プラグインシステム: 機能拡張のためのプラグイン機構を持つ。

Bottleのインストール 🛠️

Bottleのインストールは非常に簡単です。Pythonのパッケージマネージャであるpipを使用するのが一般的です。プロジェクトごとに仮想環境を作成し、その中にインストールすることを推奨します。

# 仮想環境を作成 (例: venv という名前で作成)
python3 -m venv venv

# 仮想環境を有効化 (Windowsの場合は venv\Scripts\activate)
source venv/bin/activate

# Bottleをインストール
pip install bottle

または、Bottleの公式サイトからbottle.pyファイルを直接ダウンロードし、プロジェクトディレクトリに配置するだけでも利用可能です。依存関係がないため、これだけで動作します。

wget https://bottlepy.org/bottle.py

BottleはPython標準ライブラリ以外に依存関係がないため、非常に手軽に始められます。

Hello World! 👋 – Bottleを使ってみる

まずは定番の “Hello World!” アプリケーションを作成してみましょう。以下のコードをapp.pyなどのファイル名で保存します。

from bottle import route, run, template

# ルーティング設定: '/hello' または '/hello/<name>' へのアクセスを処理
@route('/hello')
@route('/hello/<name>')
def index(name='World'):
    # テンプレートを使ってHTMLを生成
    return template('<h1 class="title is-1 has-text-centered has-text-primary">Hello {{name}}!</h1>', name=name)

# 開発用サーバーを起動
# host='localhost' はローカルからのアクセスのみ許可
# port=8080 は使用するポート番号
# debug=True はデバッグモードを有効化(エラー表示や自動リロード)
# reloader=True はコード変更時にサーバーを自動再起動
run(host='localhost', port=8080, debug=True, reloader=True)

このスクリプトを実行します。

python app.py

ブラウザで http://localhost:8080/hello または http://localhost:8080/hello/Bottle にアクセスしてみてください。”Hello World!” または “Hello Bottle!” と表示されれば成功です🎉

コード解説:

  • from bottle import route, run, template: Bottleから必要な関数(route, run, template)をインポートします。
  • @route('/hello') / @route('/hello/<name>'): デコレータを使ってURLパスと関数を結びつけます。/helloまたは/hello/<name>というURLにアクセスがあった場合に、直下のindex関数が呼び出されます。<name>はURLの一部を変数として受け取るための「ワイルドカード」です。
  • def index(name='World'):: URLに対応する処理を行う関数(リクエストハンドラ)です。ワイルドカード<name>の値が引数nameとして渡されます。デフォルト値として’World’を設定しています。
  • return template('<h1>Hello {{name}}!</h1>', name=name): template関数を使ってHTML文字列を生成し、返却します。{{name}}の部分が引数nameの値で置き換えられます。
  • run(host='localhost', port=8080, debug=True, reloader=True): 開発用の組み込みサーバーを起動します。debug=Trueにすると、エラー発生時に詳細な情報が表示され、reloader=Trueにするとコードを変更して保存した際に自動でサーバーが再起動されるため、開発が捗ります。(本番環境ではdebug=False, reloader=Falseに設定し、WSGIサーバーを使用することが推奨されます。)

ルーティング (Routing) 🧭

ルーティングは、特定のURLパスへのリクエストを、対応するPython関数(リクエストハンドラ)に振り分ける仕組みです。Bottleでは@route()デコレータを使用して簡単に設定できます。

基本的なルーティング

from bottle import route, run

@route('/')
def home():
    return '<p>Welcome to the Home Page!</p>'

@route('/contact')
def contact_page():
    return '<p>This is the Contact Page.</p>'

# run(...) は省略

上記の例では、/へのアクセスはhome関数、/contactへのアクセスはcontact_page関数が処理します。このような固定されたパスを静的ルート (Static Routes)と呼びます。

動的ルート (Dynamic Routes)

URLの一部を可変にしたい場合、ワイルドカードを使用します。ワイルドカードは<変数名>の形式で記述し、その部分にマッチした文字列が関数の引数として渡されます。

from bottle import route, run, template

@route('/user/<username>')
def show_user_profile(username):
    # username変数にはURLの<username>部分が入る
    return template('<h2 class="subtitle">Profile page for: {{user}}</h2>', user=username)

@route('/post/<post_id:int>')
def show_post(post_id):
    # post_idは整数(int)のみにマッチし、関数には整数型で渡される
    assert isinstance(post_id, int)
    return template('<p>Showing post number {{pid}}</p>', pid=post_id)

@route('/files/<filepath:path>')
def serve_static_file(filepath):
    # filepathはスラッシュを含むパス全体にマッチする
    return template('<p>Serving file: {{fp}}</p>', fp=filepath)

# run(...) は省略

ワイルドカードにはフィルタを指定できます。<変数名:フィルタ名>のように記述します。

  • :int: 数字(整数)にのみマッチし、関数には整数型で渡されます。
  • :float: 浮動小数点数にのみマッチし、関数には浮動小数点数型で渡されます。
  • :path: スラッシュ (/) を含む残りのパス全体にマッチします。ファイルパスなどに便利です。
  • :re: 正規表現にマッチします。例: <param:re:[a-z]+>

フィルタを使用しない場合、ワイルドカードはスラッシュを含まない任意の文字列(1文字以上)にマッチします([^/]+相当)。

HTTPメソッドの指定

デフォルトでは、@route()はGETリクエストのみを受け付けます。他のHTTPメソッド(POST, PUT, DELETEなど)を許可するには、method引数を指定します。

from bottle import route, request, run

@route('/login', method='GET')
def login_form():
    return '''
        <form action="/login" method="post" class="box">
            <div class="field">
              <label class="label">Username</label>
              <div class="control">
                <input class="input" type="text" name="username" placeholder="e.g. Alex Smith">
              </div>
            </div>

            <div class="field">
              <label class="label">Password</label>
              <div class="control">
                <input class="input" type="password" name="password" placeholder="********">
              </div>
            </div>

            <button class="button is-primary">Sign in</button>
        </form>
    '''

@route('/login', method='POST')
def do_login():
    username = request.forms.get('username')
    password = request.forms.get('password')
    if username == 'admin' and password == 'password': # 実際には安全な認証を行うこと!
        return "<p class='has-text-success'>Login successful! Welcome back, admin.</p>"
    else:
        return "<p class='has-text-danger'>Login failed. Please try again.</p>"

# run(...) は省略

GETリクエストで/loginにアクセスするとログインフォームが表示され、フォームを送信(POSTリクエスト)するとdo_login関数が処理を行います。request.formsについては後述します。

複数のメソッドを許可する場合は、リストで指定します。

@route('/item/<id>', method=['GET', 'POST'])
def handle_item(id):
    if request.method == 'GET':
        # GETリクエストの処理
        return f"Displaying item {id}"
    elif request.method == 'POST':
        # POSTリクエストの処理
        item_data = request.forms.get('data')
        return f"Updated item {id} with data: {item_data}"

Webアプリケーションでは、ユーザーからのリクエストに含まれるデータ(クエリパラメータ、フォームデータ、ヘッダー、Cookieなど)を取得する必要があります。Bottleではrequestオブジェクトを通じてこれらの情報にアクセスできます。

from bottle import request, route, run

@route('/search')
def search():
    # クエリパラメータ (?keyword=python&limit=10)
    keyword = request.query.get('keyword', 'default') # 'keyword'パラメータを取得、なければ'default'
    limit = request.query.get('limit', '10')        # 'limit'パラメータを取得、なければ'10'

    # クエリパラメータ全体を辞書として取得
    all_query_params = request.query.decode() # デコードして通常の辞書に

    return f'<p>Searching for: {keyword}, Limit: {limit}</p><p>All params: {all_query_params}</p>'

@route('/submit', method='POST')
def submit_form():
    # POSTされたフォームデータ
    name = request.forms.get('name')
    email = request.forms.get('email')

    # ファイルアップロード
    upload = request.files.get('upload')
    if upload:
        # ファイルを保存するなどの処理 (安全なファイル名を使用すること!)
        # upload.save('/path/to/save/' + upload.filename, overwrite=True)
        file_info = f"Uploaded file: {upload.filename} ({upload.content_type})"
    else:
        file_info = "No file uploaded."

    # Cookieの取得
    session_id = request.get_cookie('session_id', secret='some-secret-key') # 署名付きCookieの場合secretを指定

    # ヘッダーの取得
    user_agent = request.headers.get('User-Agent')

    return f"""
        <div class="box">
            <p>Received Form Data:</p>
            <ul>
                <li>Name: {name}</li>
                <li>Email: {email}</li>
            </ul>
            <p>{file_info}</p>
            <p>Session ID from Cookie: {session_id}</p>
            <p>User Agent: {user_agent}</p>
        </div>
    """

# run(...) は省略

主なrequestオブジェクトの属性:

  • request.query: URLのクエリパラメータ (?key=value&...) を格納する辞書ライクなオブジェクト。.get(key, default)で値を取得。
  • request.forms: POSTリクエストのフォームデータ (application/x-www-form-urlencoded または multipart/form-data) を格納する辞書ライクなオブジェクト。
  • request.json: リクエストボディがJSONの場合、パースされたPythonオブジェクトを返す。 (Content-Typeがapplication/jsonの場合)
  • request.files: アップロードされたファイル (multipart/form-data) を格納する辞書ライクなオブジェクト。値はFileUploadオブジェクト。
  • request.cookies: クライアントから送信されたCookieを格納する辞書ライクなオブジェクト。.get(key).get_cookie(key, secret=...) で取得。
  • request.headers: HTTPリクエストヘッダーを格納する辞書ライクなオブジェクト。
  • request.method: リクエストのHTTPメソッド ('GET', 'POST' など)。
  • request.url: 完全なリクエストURL。
  • request.path: リクエストされたパス部分。
  • request.remote_addr: クライアントのIPアドレス。

⚠️ セキュリティ注意: ユーザーからの入力は常に信頼できないものとして扱ってください。特にファイル名やフォームデータなどを処理する際は、適切な検証(バリデーション)やエスケープ処理を行い、セキュリティリスク(XSS、ファイルパス操作など)を防ぐ必要があります。

レスポンスの生成 📤

リクエストハンドラ関数は、クライアントに返すレスポンスを生成します。単純な文字列以外にも、様々な形式でレスポンスを返すことができます。

JSONレスポンス

Pythonの辞書やリストを返すと、Bottleは自動的にJSON形式に変換し、Content-Typeヘッダーをapplication/jsonに設定してくれます。

from bottle import route, run

@route('/api/data')
def api_data():
    data = {
        'id': 123,
        'name': 'Example Item',
        'tags': ['python', 'bottle', 'web']
    }
    return data # 自動的にJSONに変換される

# run(...) は省略

リダイレクト

他のURLにリダイレクトさせたい場合は、redirect関数を使用します。

from bottle import route, redirect, run

@route('/old-page')
def old():
    redirect('/new-page', 301) # 301 Moved Permanently

@route('/new-page')
def new():
    return "<p>This is the new page!</p>"

# run(...) は省略

HTTPステータスコードの変更

デフォルトではステータスコード200 OKが返されますが、responseオブジェクトを使って変更できます。

from bottle import route, response, run

@route('/notfound')
def not_found():
    response.status = 404
    return "<h1 class='title is-1 has-text-danger'>404 Not Found</h1>"

@route('/create', method='POST')
def create_item():
    # ... (アイテム作成処理) ...
    response.status = 201 # 201 Created
    return {'status': 'created', 'id': 999} # JSONレスポンスも可能

# run(...) は省略

Cookieの設定

response.set_cookie()メソッドでクライアントにCookieを送信できます。

from bottle import route, request, response, run

@route('/setcookie')
def set_cookie():
    username = request.query.get('name', 'guest')
    # 'username'という名前のCookieを設定 (有効期限1時間)
    response.set_cookie('username', username, max_age=3600)
    # 署名付きCookie (改ざん防止)
    response.set_cookie('secure_data', 'important_value', secret='my-super-secret-key')
    return f"<p>Cookie 'username' set to {username}</p>"

# run(...) は省略

署名付きCookieは、secretキーを知らない限り内容を改ざんできないようにするためのものです。

ヘッダーの設定

response.set_header()response.add_header()でレスポンスヘッダーを追加・設定できます。

from bottle import route, response, run

@route('/custom-header')
def custom_header():
    response.set_header('X-Custom-Header', 'MyValue')
    response.content_type = 'text/plain; charset=utf-8' # Content-Typeの設定
    return "This response has a custom header."

# run(...) は省略

テンプレートエンジン 🎨

動的なHTMLコンテンツを生成するために、Bottleはテンプレートエンジンを提供しています。PythonコードとHTMLマークアップを分離することで、コードの見通しが良くなり、デザイナーとの分業もしやすくなります。

組み込みテンプレート (SimpleTemplate)

BottleにはSimpleTemplate (stpl) という、高速でPythonicな独自のテンプレートエンジンが組み込まれています。template()関数や@view()デコレータで使用できます。

デフォルトでは、Bottleは./views/ディレクトリからテンプレートファイル(拡張子.tpl)を探します。テンプレートファイルのパスはbottle.TEMPLATE_PATHで変更・追加できます。

例: `views/hello_template.tpl`

<!DOCTYPE html>
<html>
<head>
    <title>Hello Page</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
</head>
<body>
  <section class="section">
    <div class="container">
      %# Pythonのコメントです
      % name = name.title() # 埋め込みPythonコード: nameをタイトルケースに変換
      <h1 class="title">Hello {{name}}!</h1>

      % if name == 'World':
          <p class="notification is-info">This seems to be the default greeting.</p>
      % else:
          <p class="notification is-success">Nice to meet you, {{name}}!</p>
      % end

      <h2 class="subtitle">Items List:</h2>
      <ul class="list">
      % for item in items:
          <li class="list-item">{{item}}</li>
      % end
      </ul>

      %# 他のテンプレートをインクルード
      % include('footer', copyright_year=2025)

      <hr>
      <p>Raw HTML (エスケープ無効化): {{!raw_html}}</p>
    </div>
  </section>
</body>
</html>

例: `views/footer.tpl`

<footer class="footer">
  <div class="content has-text-centered">
    <p>
      © {{copyright_year}} My Awesome App.
    </p>
  </div>
</footer>

Pythonコード (`app.py`)

from bottle import route, view, run, static_file

# テンプレート内で利用する変数を辞書で渡す
@route('/greet/<name>')
@view('hello_template') # 'views/hello_template.tpl' を使用
def greet(name):
    items_list = ['Apple', 'Banana', 'Cherry']
    raw_html_content = '<b>Bold Text</b>'
    # テンプレートに渡す変数を辞書で返す
    return dict(name=name, items=items_list, raw_html=raw_html_content)

# 静的ファイル(CSSなど)の配信ルート
@route('/static/<filename:path>')
def send_static(filename):
    # './static/' ディレクトリからファイルを配信
    return static_file(filename, root='./static/')

# run(...) は省略
# テンプレートを探すパスを追加することも可能
# import bottle
# bottle.TEMPLATE_PATH.insert(0, '/path/to/my/templates/')

@view('hello_template')デコレータは、関数が返す辞書をテンプレートエンジンに渡し、結果のHTMLをレスポンスとして返します。template('hello_template', name=name, ...)のように直接関数を使うこともできます。

SimpleTemplateの主な構文:

  • {{ expression }}: Pythonの式を評価し、結果をHTMLエスケープして埋め込みます。XSS対策として重要です。
  • {{! expression }}: 式を評価し、結果をエスケープせずにそのまま埋め込みます。HTMLタグなどを意図的に出力する場合に使用しますが、セキュリティリスクに注意が必要です。
  • % python_code: 単一行のPythonコードを実行します。
  • <% python_block %>: 複数行のPythonコードブロックを実行します。
  • %# comment: テンプレート内のコメント。出力には含まれません。
  • % include('sub_template', **kwargs): 他のテンプレートファイルを読み込んでレンダリングし、その結果を埋め込みます。キーワード引数でサブテンプレートに変数を渡せます。
  • % rebase('base_template', **kwargs): 現在のテンプレートの内容を、指定したベーステンプレート内の特定のブロックに埋め込みます。レイアウトの継承などに使います。(詳細は公式ドキュメント参照)
  • 制御構文 (if/elif/else, for/while, try/except): Pythonと同様の制御構文が利用可能です。% if condition: ... % elif condition: ... % else: ... % end のように、ブロックの終わりには% endが必要です。

⚠️ 注意: SimpleTemplateはテンプレート内に任意のPythonコードを埋め込めるため、信頼できないテンプレートファイルをレンダリングすることは非常に危険です。

他のテンプレートエンジン (Jinja2, Makoなど)

Bottleは、Jinja2やMakoといった他の人気テンプレートエンジンの利用もサポートしています。別途ライブラリのインストールが必要です。

pip install jinja2
from bottle import route, jinja2_template as template, run

@route('/jinja/<name>')
def jinja_example(name):
    # Jinja2テンプレート (例: views/jinja_example.html) を使用
    # jinja2_templateはtemplate()と同じように使える
    return template('jinja_example', name=name, items=[1, 2, 3])

# run(...) は省略
# Jinja2テンプレートのパス設定なども可能

Jinja2を使用する場合、テンプレートファイル(例: views/jinja_example.html)はJinja2の構文で記述します。

静的ファイルの配信 📁

CSS、JavaScript、画像などの静的ファイルは、Webアプリケーションとは別に配信する必要があります。Bottleではstatic_file関数を使って簡単に実現できます。

from bottle import route, static_file, run

# './static' ディレクトリ内のファイルを配信するルート
@route('/static/<filepath:path>')
def server_static(filepath):
    # root で起点となるディレクトリを指定
    # download=True にするとダウンロードダイアログが表示される
    return static_file(filepath, root='./static/')

# run(...) は省略

上記のコードでは、./static/ディレクトリを作成し、その中にCSSファイルなどを配置します。例えば、./static/css/style.cssというファイルがあれば、ブラウザからはhttp://localhost:8080/static/css/style.cssというURLでアクセスできるようになります。

HTMLテンプレート内では以下のようにリンクします。

<link rel="stylesheet" type="text/css" href="/static/css/style.css">
<script src="/static/js/script.js"></script>
<img src="/static/images/logo.png" alt="Logo">

static_file関数は、適切なMIMEタイプを推測し、Last-Modifiedヘッダーなどを設定して効率的な配信をサポートします。

💡 本番環境では: 開発中はBottleのstatic_fileで十分ですが、本番環境ではNginxやApacheなどのWebサーバーに静的ファイルの配信を任せる方が、パフォーマンスが高く効率的です。

プラグインシステム 🔌

Bottleのコア機能はシンプルですが、プラグインシステムを利用して機能を追加・拡張できます。データベース接続、認証、セッション管理など、様々なサードパーティ製プラグインが利用可能です。

プラグインはapp.install()メソッド(またはグローバルなinstall()関数)でアプリケーションに適用します。

import bottle
from bottle_sqlite import SQLitePlugin # 例: SQLiteプラグイン
from bottle_session import SessionPlugin # 例: セッションプラグイン

app = bottle.Bottle() # アプリケーションインスタンスを作成

# SQLiteプラグインをインストール (dbキーワード引数を有効化)
# dbfileでデータベースファイルを指定
sqlite_plugin = SQLitePlugin(dbfile='/tmp/mydatabase.db')
app.install(sqlite_plugin)

# セッションプラグインをインストール (sessionキーワード引数を有効化)
# cookie_lifetimeはクッキーの有効期間(秒)
session_plugin = SessionPlugin(cookie_lifetime=600)
app.install(session_plugin)

@app.route('/show/<item_id>')
def show(item_id, db): # プラグインによって 'db' キーワードが使えるようになる
    # db オブジェクトを使ってデータベース操作
    row = db.execute('SELECT * FROM items WHERE id = ?', (item_id,)).fetchone()
    if row:
        return template('item_details', item=row)
    else:
        return bottle.HTTPError(404, "Item not found.")

@app.route('/restricted')
def restricted_area(session): # プラグインによって 'session' キーワードが使えるようになる
    user = session.get('user')
    if not user:
        bottle.redirect('/login')
    return f"Welcome to the restricted area, {user}!"

@app.route('/login', method='POST')
def do_login(session): # セッションプラグインを使用
    username = bottle.request.forms.get('username')
    # ... 認証処理 ...
    if username: # 認証成功したと仮定
        session['user'] = username # セッションにユーザー情報を保存
        bottle.redirect('/restricted')
    else:
        return "Login failed"

# run(app=app, ...) # アプリケーションインスタンスを指定して起動

上記の例では、bottle-sqliteプラグインとbottle-sessionプラグインを使用しています(これらのプラグインは別途pip install bottle-sqlite bottle-session redisなどでインストールする必要があります)。

  • SQLitePlugin: ルート関数にdbというキーワード引数を追加し、SQLiteデータベースへの接続とカーソルを提供します。
  • SessionPlugin: ルート関数にsessionというキーワード引数を追加し、セッションデータの読み書きを可能にします。(このプラグインはRedisをバックエンドに使用します)

プラグインはルートごとに適用され、特定のキーワード引数をルート関数に追加したり、リクエスト処理の前後にフック処理を実行したりできます。

利用可能なプラグインの例:

  • データベース: bottle-sqlite, bottle-sqlalchemy, bottle-mongodb
  • 認証・認可: bottle-cork
  • セッション管理: bottle-session
  • テンプレートエンジン連携: bottle-renderer
  • その他: bottle-werkzeug (Werkzeugライブラリ統合), bottle-flash (フラッシュメッセージ) など

プラグインのリストは公式ドキュメントやPyPIで確認できます。また、独自のプラグインを作成することも可能です。

デプロイメント 🚀

開発中はBottle組み込みの開発サーバー(run()関数)が便利ですが、これはシングルプロセス・シングルスレッドで動作するため、本番環境での使用には適していません。本番環境では、より堅牢で高性能なWSGIサーバーを使用する必要があります。

WSGIサーバー

WSGI (Web Server Gateway Interface) は、Python WebアプリケーションとWebサーバー間の標準インターフェースです。BottleはWSGIに準拠しているため、様々なWSGIサーバー上で動作させることができます。

代表的なWSGIサーバー:

  • Gunicorn (Green Unicorn): Unix系OSで広く使われているWSGIサーバー。シンプルで設定が容易。
  • uWSGI: 高機能・高性能なWSGIサーバー。多くの設定オプションを持つ。
  • Waitress: Windows環境でも動作する純粋なPython製のWSGIサーバー。
  • mod_wsgi (Apache): Apache httpdサーバーのモジュールとして動作。

Gunicornでのデプロイ例

まず、Gunicornをインストールします。

pip install gunicorn

次に、アプリケーションファイル (例: app.py) を修正し、run()の呼び出しを削除またはif __name__ == '__main__':ブロックで囲みます。WSGIサーバーは、ファイル内のapplicationという名前のWSGI callable(Bottleインスタンスなど)を探します。

from bottle import Bottle, route, template

# Bottleインスタンスを作成 (グローバルな 'app' ではなくこちらを使う)
application = Bottle()

@application.route('/')
def index():
    return template('<h1 class="title">Deployed with Gunicorn!</h1>')

# 本番環境では以下の run() はWSGIサーバーが担当するため不要
# if __name__ == '__main__':
#    run(app=application, host='localhost', port=8080, debug=True, reloader=True)

Gunicornを起動します。

# app:application -> app.py ファイル内の application オブジェクトを指定
# -w 4 -> 4つのワーカープロセスを起動
# -b 0.0.0.0:8000 -> 全てのインターフェースのポート8000で待機
gunicorn app:application -w 4 -b 0.0.0.0:8000

これで、Gunicornがポート8000でリクエストを受け付け、Bottleアプリケーションに処理を渡すようになります。

リバースプロキシ (Nginx / Apache)

通常、本番環境ではWSGIサーバーを直接インターネットに公開せず、NginxApacheなどのWebサーバーをリバースプロキシとして手前に配置します。

リバースプロキシの役割:

  • 静的ファイルの配信
  • SSL/TLS終端 (HTTPS化)
  • 負荷分散 (ロードバランシング)
  • リクエスト/レスポンスのバッファリング
  • セキュリティ強化 (アクセス制御など)
  • Gzip圧縮

Nginxの設定例 (Gunicornがポート8000で動作している場合):

server {
    listen 80;
    server_name your_domain.com; # あなたのドメイン名

    location /static {
        alias /path/to/your/project/static; # 静的ファイルディレクトリのパス
    }

    location / {
        proxy_pass http://127.0.0.1:8000; # Gunicornへリクエストを転送
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

この設定により、ポート80へのアクセスはNginxが受け取り、/staticへのリクエストはNginxが直接ファイルを配信し、それ以外のリクエストはローカルのポート8000で動作しているGunicorn(とその先のBottleアプリ)に転送されます。

Heroku, PythonAnywhere, Google App EngineなどのPaaS (Platform as a Service) を利用すると、インフラの管理を任せて簡単にデプロイすることも可能です。

Bottleのユースケース 🎯

Bottleはそのシンプルさと軽量さから、以下のような用途に適しています。

  • 小規模なWebアプリケーション: 機能が限定的なシンプルなWebサイトや社内ツールなど。
  • Web API (RESTful API) の構築: JSONレスポンスの返しやすさやルーティングのシンプルさがAPI開発に向いています。
  • プロトタイピング: アイデアを素早く形にしたい場合。依存関係が少なくセットアップが容易なため、迅速な開発が可能です。
  • 学習目的: Webフレームワークの仕組みを理解するための最初のステップとして。コードベースが小さく(単一ファイル)、理解しやすいです。
  • 既存システムへの組み込み: ちょっとしたWebインターフェースを既存のPythonスクリプトに追加したい場合など。

大規模で複雑な機能(管理画面、ORM、高度な認証など)が多数必要なプロジェクトには、Djangoのようなフルスタックフレームワークの方が適している場合があります。しかし、Bottleでもプラグインを組み合わせることで、中規模程度のアプリケーション開発は十分可能です。

Pythonには多くのWebフレームワークが存在します。ここでは、特に比較されることの多いFlaskとDjangoとの違いを簡単に見てみましょう。

特徴 Bottle Flask Django
タイプ マイクロフレームワーク マイクロフレームワーク フルスタックフレームワーク
哲学 極限までシンプル、単一ファイル シンプル、拡張性が高い Batteries Included (全部入り)
依存関係 Python標準ライブラリのみ Werkzeug (WSGI), Jinja2 (Template) 多数 (自身のコンポーネント)
学習コスト 低い 低い~中程度 高い
柔軟性/自由度 高い 非常に高い やや低い (フレームワークの規約に従う)
組み込み機能 最小限 (Routing, Template, Server) 最小限 + α (セッション, テスト支援など) 豊富 (ORM, Admin, Auth, Formsなど)
テンプレートエンジン SimpleTemplate (組み込み), Jinja2など利用可 Jinja2 (デフォルト), Makoなど利用可 Django Template Language (DTL), Jinja2なども設定可
コミュニティ/ドキュメント 比較的小さい 大きい、活発 非常に大きい、活発
適した用途 小規模アプリ, API, プロトタイプ, 学習 小~大規模アプリ, API, 柔軟性が求められる場合 中~大規模アプリ, 開発速度重視, 管理画面が必要な場合
  • Bottle vs Flask: どちらもマイクロフレームワークですが、Bottleはよりシンプルで依存関係がありません。FlaskはWerkzeugとJinja2に依存しますが、より多くの拡張機能(Blueprintsによるモジュール化など)や活発なコミュニティを持ち、柔軟性と拡張性に優れています。Bottleの方がわずかに学習コストが低いかもしれませんが、情報量はFlaskの方が多い傾向があります。
  • Bottle vs Django: Bottleは最小限の機能を提供するのに対し、DjangoはWeb開発に必要な多くの機能(ORM、管理画面、認証システムなど)を最初から備えています。Djangoは「設定より規約」の考え方で、開発のレールがある程度敷かれているため、大規模開発では生産性が高くなることがあります。Bottleは自由度が高い反面、必要な機能は自分で選択・導入する必要があります。

どのフレームワークを選ぶかは、プロジェクトの規模、要件、開発者の好みや経験によって異なります。シンプルなものから始めたい、あるいはフレームワークの内部構造を学びたいならBottleは良い選択肢です。柔軟性とエコシステムを重視するならFlask、多くの機能がすぐに必要ならDjangoが有力候補となるでしょう。

まとめ 🏁

Bottleは、その驚くほどのシンプルさと軽量さが魅力のPythonマイクロWebフレームワークです。単一ファイルで依存関係がないため、導入やデプロイが非常に簡単で、Web開発の入門や小規模プロジェクト、API開発、プロトタイピングに最適です。

基本的なルーティング、テンプレート、リクエスト/レスポンス処理機能を備え、プラグインシステムによる拡張も可能です。学習コストが低く、フレームワークの仕組みを理解するのにも役立ちます。

コメント

タイトルとURLをコピーしました