【スクリプト】Flaskで簡単なAPIを実装

今回はインターンシップの事前準備としてFlaskでREST APIを実装したときのことについて書きます。

今回はUdemy講師酒井潤さんの「現役シリコンバレーエンジニアが教えるPython 3 入門 + 応用 +アメリカのシリコンバレー流コードスタイル」の157. Flaskを参考に実装しました。上記の講座のリンクは以下の通りです。（ご本人から許可をいただいたうえでブログを掲載しております）

[https://www.udemy.com/course/python-beginner/learn/lecture/8451958#overview](https://www.udemy.com/course/python-beginner/?couponCode=KEEPLEARNING)

また、こちらの記事も参考にさせていただきました。

https://qiita.com/ichi_zamurai/items/ebf9c0d2c4ea3fafe63e

先に実装の流れと処理の流れについて全体像を示します。

## 実装の手順

1. **必要なライブラリのインポート**
    
    Flaskや必要なモジュールをインポートします。
    <br>
    
2. **main関数の作成**（サーバーの起動）
    
    Flaskアプリケーションを初期化し、サーバーを起動する関数を定義します。
    <br>

3. **エンドポイントの設定**
    
    各APIエンドポイントのルーティングを@app.routeデコレータで設定します。
    <br>

4. **HTMLファイルの作成**
    
    templatesフォルダ内にHTMLファイルを作成し、必要なコンテンツを記述します。
    <br>
    
5. **レンダリング設定**
    
    render_template関数を使用して、HTMLファイルをレンダリングする処理を実装します。
    <br>
6. **CRUD処理の実装**
    
    データの作成、読み取り、更新、削除の各機能を実装します。
    <br>
    
7. **DB接続設定**
    
    データベースへの接続、切断、テーブル作成などの処理を実装します。
    <br>
    
8. **テスト用requestの作成**
    
    APIのテストを行うためのPythonスクリプトを作成し、各エンドポイントをテストします。
    

## 処理の流れ

- `main()`
- リクエスト受信
- APIを叩いてリクエストを送信
- リクエストのHTTPメソッドに応じて処理が実行される
    - GET   : データの取得 `def example1()`
    - POST  : 新規データの作成 `def example2()`
    - PUT   : データの更新 `def example3()`
    - DELETE: データの削除 `def example4()`
- HTMLをレンダリングし、クライアントに表示
    - `example.html`
<br>
次に、それぞれについてみていきましょう。

また、今回はデモとして、サッカーのクラブ名を自由に追加したり削除したりできるようなアプリケーションを作りましょう。

1. 必要なライブラリのインポート

```python
import sqlite3

from flask import Flask
from flask import g
from flask import render_template
from flask import request
from flask import Response
```

とりあえずインポートしました。個々のライブラリについては出てきたときに説明します。

1. main関数（サーバーの起動）

```python
app = Flask(__name__)
```

```python
def main():
    create_table() 
    app.run(debug=True) 

if __name__ == '__main__':
    main()
```

このように記述します。

`app`でFlask アプリケーションを作成します。

`app.run`は通常main関数内に記述します。
<br>
2.エンドポイントの設定

```python
@app.route('/')
@app.route('/clubs', methods=['GET'])
@app.route('/club', methods=['POST'])
@app.route('/club/<name>', methods=['DELETE'])

```

3.HTMLファイルを記述

```html
./templates/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Soccer Clubs</title>
</head>
<body>
    <h1>Welcome to Soccer Club Management</h1>
    <p><a href="/clubs">View Club List</a></p>
</body>
</html>
```

```html
./templates/clubs.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Soccer Clubs</title>
</head>
<body>
    <h1>Soccer Clubs List</h1>
    <ul>
        {% for club in clubs %}
            <li>{{ club[0] }}</li>
        {% endfor %}
    </ul>

    <h2>Add a New Club</h2>
    <form action="/club" method="POST">
        <input type="text" name="name" placeholder="Club Name" required>
        <button type="submit">Add Club</button>
    </form>
</body>
</html>
```

templatesディレクトリ内の`clubs.html`に記述します。

clubnameについては、後ほどDBで`clubname`カラムを定義します
<br>
4.レンダリング設定

```python
# メインエンドポイント
@app.route('/')
def index():
    return render_template('index.html')
```

レンダリングには`render_template` を使用します。

`render_template`は、Flaskアプリケーション内の`templates`内に置かれているHTMLファイルを読み込み、レンダリングします。そして、必要な変数を埋め込んだ結果をブラウザに表示します。
<br>
5.DB接続設定

```python
# サッカークラブのテーブルを作成
def create_table():
    db = get_db()
    db.execute(
        'CREATE TABLE IF NOT EXISTS clubs (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)'
    )
    db.commit()
```

```python
# データベースの接続を取得
def get_db():
    if 'db' not in g:
        g.db = sqlite3.connect('soccer_clubs.db')
    return g.db
```

```python
# リクエスト終了時にデータベース接続を閉じる
@app.teardown_appcontext
def close_db(exception):
    db = g.pop('db', None)
    if db is not None:
        db.close()
```

ここでは以下の処理を行います。

① `clubs` テーブルの作成

②  DBを呼び出す関数の作成（`get_db`）

③  DB接続を切る関数の作成（`close_db`）
<br>
6.CLUD処理

```python
# クラブの一覧表示
@app.route('/clubs', methods=['GET'])
def list_clubs():
    db = get_db()
    clubs = db.execute('SELECT name FROM clubs').fetchall()
    return render_template('clubs.html', clubs=clubs)
    
# クラブの追加
@app.route('/club', methods=['POST'])
def add_club():
    club_name = request.form['name']
    db = get_db()
    db.execute('INSERT INTO clubs (name) VALUES (?)', (club_name,))
    db.commit()
    return jsonify(message=f"Club '{club_name}' added."), 201

# クラブの削除
@app.route('/club/<name>', methods=['DELETE'])
def delete_club(name):
    db = get_db()
    db.execute('DELETE FROM clubs WHERE name = ?', (name,))
    db.commit()
    return jsonify(message=f"Club '{name}' deleted."), 200
```

ここでは以下の処理を行います。

①  HTTPメソッド（`GET`, `POST`, `DELETE`）に応じたルーティング

②  DBコネクションをとってくる(`db = get_db()`)

③  SQL文の実行（`db.execute()`）

④  DB内のデータの変更を確定（`db.commit()`）
<br>
7.テスト用request

```python
import requests

def test_list_clubs():
    response = requests.get("http://127.0.0.1:5000/clubs")
    print(f"List Clubs Response: {response.status_code}")
    print(response.text)  # HTMLを表示
    
def test_add_club(club_name):
    response = requests.post("http://127.0.0.1:5000/club", data={'name': club_name})
    print(f"Add Club Response: {response.status_code}, {response.json()}")

def test_delete_club(club_name):
    response = requests.delete("http://127.0.0.1:5000/club/{club_name}")
    print(f"Delete Club Response: {response.status_code}, {response.json()}")

if __name__ == "__main__":
    # クラブの追加
    test_add_club("Arsenal")
    test_add_club("Manchester United")
    
    # クラブの一覧表示
    test_list_clubs()

    # クラブの削除
    test_delete_club("Arsenal")
    
    # 削除後に再度クラブの一覧表示
    test_list_clubs()

```

ここでは、`request` ライブラリを使ってAPIリクエストを送信するテスト用ファイルを作成します。

## まとめ

　上記のような流れで実装していくことで、簡単なREST APIが実装できるかと思います。なお、現時点で動作確認はまだ行っていないためこの通りにやっても上手くいかない可能性があります。不具合等ございましたら、お手数ですがコメントにて教えていただけると幸いです。