Python3.6とBottleでRESTful APIサーバを構築する

最近Python3.6を勉強し始めました。
チュートリアル等を読んでいますが、普段は[Apache Groovy](http://groovy-lang.org/)と[Grails](https://grails.org/)を使ってWebアプリケーションの開発運用をしているので、どうしてもWeb周りのコードで色々実験してみたくなります。
Djangoがやはり有名なものだと思うのですが、そこまで行くとちょっとDjango自体の理解に時間がかかるし。。。
ということで、Python用のMicro web frameworkと謳われている[Bottle](https://bottlepy.org/docs/dev/index.html)を試してみましたので、その備忘録としてこの記事を書いています。

# 仮想環境の作成

Python3.6では、標準で仮想環境を以下のように作成できるようです。

```
python -m venv $HOME/work/bottle
source $HOME/work/bottle/bin/activate
```

# Bottleのインストール
上記ですでに仮想環境に入っているので、以下のコマンドを叩くだけでOKです。

```
pip install -U bottle
```

# コード
データベースを用意するのが面倒くさかったので、とりあえず簡易DBとして、リストの中に辞書を突っ込むようにしました。


```python:bottle-sample.py
from bottle import route, run 
from bottle import post, get, put, delete, request, response
import json

books = [ 
        {'id': 1, 'title': 'book 1'},
        {'id': 2, 'title': 'book 2'},
        {'id': 3, 'title': 'book 3'},
]

@get('/books')
def books_list():
    response.headers['Content-Type'] = 'application/json'
    response.headers['Cache-Control'] = 'no-cache'
    return json.dumps(books)

@get('/books/<id:int>')
def a_book(id):
    search = filter(lambda book: book['id'] == id, books)
    obj = next(search, None)

    # https://bottlepy.org/docs/dev/tutorial.html#generating-content
    # returnする値が辞書（もしくはそのサブタイプ）場合、Bottoleが自動的にレスポンスヘッダにapplication/jsonを付けてくれる！
    if obj is not None:
        return obj
    else:
        response.status = 404 
        return {}

@post('/books')
def create_book():
    # 泥臭い以下のような書き方もできるけど。。。？
    #try:
    #    body = json.load(request.body)
    #except:
    #    raise ValueError
    #books.append(body)

    # HttpリクエストヘッダのcontentTypeが'application/json'であれば、requset.jsonが利用できる。
    # https://bottlepy.org/docs/dev/tutorial.html#json-content
    books.append(request.json)
    return request.json

@put('/books/<id:int>')
def update_book(id):
    search = filter(lambda book: book['id'] == id, books)
    obj = next(search, None)

    if obj is not None:
        index = books.index(obj)
        books[index] = request.json
        return request.json
    else:
        response.status = 404 
        return {}

@delete('/books/<id:int>')
def update_book(id):
    search = filter(lambda book: book['id'] == id, books)
    obj = next(search, None)

    if obj is not None:
        index = books.index(obj)
        del books[index]
        return {}
    else:
        response.status = 404
        return {}

run (host='localhost', port=8081, debug=True, reloader=True)
```

# 実際に試す
後は普通にcurlコマンドを叩けば動作を確認することが出来ます。

まずは`python bottle-sample.py`でサーバを起動します。

```shell-session
[koji:~]$ curl http://localhost:8081/books
[{"id": 1, "title": "book 1"}, {"id": 2, "title": "book 2"}, {"id": 3, "title": "book 3"}]% 

[koji:~]$ curl http://localhost:8081/books/3
{"id": 3, "title": "book 3"}% 
[koji:~]$ 
```

ちゃんとデータが取れました。
ではPOSTでデータを追加してみます。

```shell-session
[koji:~]$ curl -H 'Content-Type:application/json' -d '{"id": 4, "title": "book4"}' http://localhost:8081/books
{"id": 4, "title": "book4"}%
[koji:~]$ 

[koji:~]$ curl http://localhost:8081/books                                                                    
[{"id": 1, "title": "book 1"}, {"id": 2, "title": "book 2"}, {"id": 3, "title": "book 3"}, {"id": 4, "title": "book4"}]%
[koji:~]$ 

[koji:~]$ curl http://localhost:8081/books/4
{"id": 4, "title": "book4"}%
[koji:~]$
```
問題なく新しくデータが追加できました。
それでは今度は既存のデータをアップデートしてみます。

```shell-session
[koji:~]$ curl -X PUT -H 'Content-Type:application/json' -d '{"id": 2, "title": "Book2 second edition"}' http://localhost:8081/books/2 
{"id": 2, "title": "Book2 second edition"}%

[koji:~]$ curl http://localhost:8081/books                                                                                               
[{"id": 1, "title": "book 1"}, {"id": 2, "title": "Book2 second edition"}, {"id": 3, "title": "book 3"}, {"id": 4, "title": "book4"}]%

[koji:~]$ curl http://localhost:8081/books/2
{"id": 2, "title": "Book2 second edition"}%
[koji:~]$
```
更新出来ました。
では最後に削除してみます。

```shell-session
[koji:~]$ curl -X DELETE http://localhost:8081/books/3 
[koji:~]$ 
[koji:~]$ curl http://localhost:8081/books
[{"id": 1, "title": "book 1"}, {"id": 2, "title": "Book2 second edition"}, {"id": 4, "title": "book4"}]%
[koji:~]$ curl http://localhost:8081/books/3
[koji:~]$ 
```

削除も動作しています。

# まとめ
まだPython力が足りないので、リスト周りの検索方法、そしてエラー処理の部分がだいぶガバガバになってしまっています。
ただ、今までJVM畑で過して来たので、Pythonスクリプトのサクサク動くこの感じはかなり素敵だな感じました。