PythonでwebApi作成 (CRUD) 初心者の方に向けて

# この記事の目的
DBに対するCRUD操作を行えるwebサーバを作成する。
丁寧に解説しました。
まさかりを歓迎します。

# 緒言&前提
○前提
・仮のDBとしてlist型を作成し、そこに対してCRUD操作を行います。
仮DBに対する操作を実際のDBに対する操作に書き換えれば良いかと思います。
・python、pip(pythonのパッケージマネージャ)のインストールを前提とします。
○緒言
pythonでwebApiを作成するモチベーションとしては、
・DBデータを数学的に扱うためにnumpyライブラリを使用したいから
というところでしたが、あとから調査してみると、web通信自体はｊｓなどに任せて、データ処理のみをpython+numpyに任せた方が高速だということでした。
参考：https://qiita.com/KatsunoriNakamura/items/dd567ea7cfaa99685453

## 言葉
・Flask: python上で動くウェブアプリケーションフレームワーク。かんたんにwebアプリが作れました。
・webApi: この記事では、URIを叩くとDBに対する操作をおこなってくれるwebサーバを指す。

# 概要
1.Flaskをインストール
2.Apiファイルを作成

## 1.Flaskをインストール
コマンドラインから、以下のコマンドを打ちます。

```
pip install Flask
```
## 2.Apiファイルを作成
説明の流れとして、まずコード全体を示したあとに、簡単に各コードの解説していきます。

### webAPIファイルの全貌
以下のファイルを作成し、実行することでサーバが起動します。
サーバ起動中に、CRUDメソッドに設定したURIを叩くことで、DBを操作できます。

```sampleapp.py
from flask import Flask, jsonify, request, render_template

app = Flask(__name__)

# DB
stores = [
    {
        'name': 'my_store',
        'items': [
            {
            'name': 'chocolate',
            'price': 120
            }
        ]
    }
]

# storeに対するCRUD--------------
# GET /store/<string:name>
@app.route('/stores/<string:name>') # 'http://127.0.0.1:5000/store/some_name'
def get_store(name):
    for store in stores:
        if store["name"] == name:
            return jsonify(store)
    return jsonify({"message": "no store named {} found.".format(name)})

# POST /store data: {name:}
@app.route('/stores', methods=['POST'])
def create_store():
    request_data = request.get_json()
    store = next(filter(lambda x: x["name"]==request_data["name"], stores), None)
    if store != None:
        return jsonify({"message": "store named {} already exist.".format(request_data["name"])})
    else:
        new_store = {
            "name": request_data["name"],
            "items": request_data["items"]
        }
        stores.append(new_store)
        return jsonify({"message": "{} store is added.".format(request_data["name"])})

# PUT /store data: {name:}
@app.route('/stores/<string:name>', methods=['PUT'])
def update_store(name):
    request_data = request.get_json()
    store = next(filter(lambda x: x["name"] == name, stores), None)
    if store == None:
        return jsonify({"message": "no store named {} found.".format(name)})
    else:
        store["items"] = request_data["items"]
        return jsonify({"message": "{} is updated.".format(name)})

# DELETE /stores/<string:name>
@app.route('/stores/<string:name>', methods=['DELETE'])
def delete_store(name):
    global stores
    stores = list(filter(lambda x: x["name"] != name, stores))
    return jsonify({"message": "Delete store named {}".format(name)})

# --------------------storeに対するCRUD

# GET /stores
@app.route('/stores')
def get_stores():
    return jsonify({'stores': stores})

# ホーム画面にhtmlを読み込んでみる。
# GET /
@app.route('/')
def home():
    return render_template("index.html")

app.run(port=5000)
```

### 解説

```sampleapp.py
from flask import Flask, jsonify, request, render_template
```
Flaskライブラリから必要なモジュールをインポートしています。

```sampleapp.py
app = Flask(__name__)
```
アプリケーション本体を作成します。
pythonでは、アンダースコアでかこまれた特別なデフォルト変数があり、nameはファイル名にあたりますので、今回の場合sampleappにあたります。
ただし、実行ファイル(ターミナルからpython ~で実行したファイル)のnameはmainを返します。
Flaskは実行ファイル上でしか正しく動作することができないため、呼び出されたファイルについてname=mainであることを確認しています。

```sampleapp.py
# DB
stores = [
    {
        'name': 'my_store',
        'items': [
            {
            'name': 'chocolate',
            'price': 120
            }
        ]
    }
]
```
仮のDBです。
ストアが複数格納され、各ストアはストア名と複数の商品データがはいるとしています。

```sampleapp.py
# GET /store/<string:name>
@app.route('/stores/<string:name>') # 'http://127.0.0.1:5000/store/some_name'
def get_store(name):
    for store in stores:
        if store["name"] == name:
            return jsonify({"stores": store})
    return jsonify({"message": "no store named {} found.".format(name)})
```
GETメソッドです。
ストア名を指定して、存在すればjson型のレスポンスで返しています。
ストアが存在しない場合もメッセージをjson型のレスポンスで返しています。

@app.route('/stores/<<string:name>>')でappにURIを登録しています。(デフォルトIPは'http://127.0.0.1:5000'に自動的に指定されます。)
このURIが叩かれたときに、下のメソッドが呼ばれます。引数にURI中の<<string:name>>を代入しています。
jsonify( )は辞書型からjson型を作成するメソッドです。

```sampleapp.py
# POST /store data: {name:}
def create_store():
    request_data = request.get_json()
    store = next(filter(lambda x: x["name"]==request_data["name"], stores), None)
    if store != None:
        return jsonify({"message": "store named {} already exist.".format(request_data["name"])})
    else:
        new_store = {
            "name": request_data["name"],
            "items": request_data["items"]
        }
        stores.append(new_store)
        return jsonify({"message": "{} store is added.".format(request_data["name"])})
```
POSTメソッドです。
URI指定の際に、POSTを指定しています。(デフォルトはGETが指定されています。)
request.get_json()でリクエストボディを取得できます。
同じ名前のストアがなければ、リクエストボディのデータを持ったストアをデータベースに加えます。


PUTメソッドとDELETEメソッドについては、特に追加情報がないので割愛します。

```sampleapp.py
# GET /stores
@app.route('/stores')
def get_stores():
    return jsonify({'stores': stores})
```
CRUD操作の確認用にDB全体を取得するAPIをつくっておきました。

```sampleapp.py
#ホーム画面にhtmlを読み込んでみる。
# GET /
@app.route('/')
def home():
    return render_template("index.html")
```
htmlファイルを呼び出せます。
呼び出し先で、DB全体に対するGETメソッドを呼び出すように設定しています。
![スクリーンショット 2020-10-06 1.45.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252245/b4babe05-685a-6ffe-3746-ee1956674f9c.png)

```sampleapp.py
app.run(port=5000)
```
最後にアプリケーションを起動する記述をします。
ポート番号を指定しなくてもデフォルト5000がデフォルトなのですが、明言するためにあえて書いています。
もちろん他のポート番号も指定できます。


## 動作確認
### 実行
```
$ python sampleapp.py
 * Serving Flask app "sampleapp" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
```
### 確認
Postman(HTTPリクエストが簡単に行えるツール)を使用して確認しました。
CRUDapiの動作が確認できました。

#### GET (stores)
stores全体のデータを取得します
![スクリーンショット 2020-10-05 23.49.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252245/20e019f6-929f-74ae-87ee-440042140c52.png)

#### POST
新しいストアをりクエストbodyに入れて登録します。
![スクリーンショット 2020-10-06 0.15.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252245/cc00ffe3-e58b-781e-f5e2-11a1615916f5.png)
確認 (GET (stores))
![スクリーンショット 2020-10-06 0.15.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252245/a33a2888-8a38-b73a-7e16-5246f60db1cb.png)

#### ホーム画面
![スクリーンショット 2020-10-06 0.25.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252245/dd011681-26d9-0068-6718-c45750b20a2b.png)

他のCRUDも同様に動作します。

## 感想
python/Flaskを使用すれば、
短時間に簡易なコードでwebAPIを作成していけそうです。

当初の目的には使用しないので...ボットwebApiとかとして弔ってあげたいと思います。