ざっくりPythonのおさらい その2.5。Web系


Web周りのライブラリについておさらいしています。

その他のおさらい事項にについては、以下ご参照ください。

[ざっくりPythonのおさらい その1。基本文法など]
https://qiita.com/48hands/items/31b7ac2b49addbb8658c

[ざっくりPythonのおさらい その2。おもにデータ保存]
https://qiita.com/48hands/items/ab86b7463268e669d216

[ざっくりPythonのおさらい その3。Numpy, Pandas]
https://qiita.com/48hands/items/b1a71000533e129b27f2


勉強に使っている教材は、酒井潤さんがUdemyで提供しているこちらの講座です。
Pythonに入門するときに一度通しで勉強したのですが、入門書として私の中ではベストでした。

[講座名]
Python3入門 + 応用 + アメリカのシリコンバレー流コードスタイルを学び、実践的なアプリ開発の準備をする
https://www.udemy.com/python-beginner/

入門〜応用までかなり幅広く網羅しているので、市販で売ってる入門書籍よりも個人的にかなりおすすめです。
一方で他言語を学んでいない初心者には中半〜後半は難しいかもしれません。
説明は非常にわかりやすいです。

今回は酒井先生の講義の丸パクリになってしまいました。

## Web

### HTTPリクエスト

Webに対してのリクエストサードパーティの`requests`ライブラリがいい感じとのこと。

`import requests`で利用する。

#### GET

```python
import requests

# URLストリングパラメータ
# 以下と同様
# http://httpbin.org/get?key1=value1&key2=value2
payload = {'key1': 'value1', 'key2': 'value2'}

r = requests.get('http://httpbin.org/get', params=payload, timeout=1)

print(r.status_code)
print(r.headers)
print(r.json())
```


#### POST

```python
import requests

payload = {'key1': 'value1', 'key2': 'value2'}

r = requests.post('http://httpbin.org/post', data=payload, timeout=1)

print(r.status_code)
print(r.headers)
print(r.json())
```

#### PUT

```python
payload = {'key1': 'value1', 'key2': 'value2'}

r = requests.put('http://httpbin.org/put', data=payload, timeout=1)

print(r.status_code)
print(r.headers)
print(r.json())
```

#### DELETE

```python
import requests

payload = {'key1': 'value1', 'key2': 'value2'}

r = requests.delete('http://httpbin.org/delete', data=payload)

print(r.status_code)
print(r.headers)
print(r.json())
```

### ソケット通信

#### TCPソケット通信

サーバ側

```server.py
import socket

# TCPソケット通信
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind(('127.0.0.1', 50000)) # 50000番ポートで待受ける
    s.listen(1) # コネクション数を1に設定している

    while True:
        # クライアントが接続してきたらconn,addrを取得する
        conn, addr  = s.accept()
        with conn:
            while True:
                # チャンクサイズを1024byteにしている
                data = conn.recv(1024)
                # データがなくなったら
                if not data:
                    break
                # 受け取ったデータとアドレスアドレス情報を表示
                print('data: {}, addr: {}'.format(data, addr))

                # クライアントにデータを送信している
                conn.sendall(b'Received: ' + data)
```

クライアント側

```client.py
import socket

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    # サーバに接続
    s.connect(('127.0.0.1', 50000))
    # メッセージを送信
    s.sendall(b'Hello')
    # サーバからメッセージを受信
    data = s.recv(1024)
    print(repr(data))
```

#### UDPソケット通信

サーバ側

```server.py
import socket

# socket.SOCK_DGRAMを使う
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
    s.bind(('127.0.0.1', 50000))
    while True:
        data, addr = s.recvfrom(1024)
        print('data: {}, addr: {}'.format(data, addr))
```

クライアント側

```client.py
import socket

with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
    s.sendto(b'Hello by UDP', ('127.0.0.1', 50000))
```


## Flask

軽量Webフレームワーク。
簡単なAPIを作るときに向いてそうです。
驚くほど簡単。

[公式]
http://flask.pocoo.org/

### 超基本

```main.py
from flask import Flask
from flask import g
from flask import render_template
from flask import request
from flask import Response

# グローバルでファイル名を指定している
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello world!'


@app.route('/goodbye')
def goodbye_world():
    return 'good-bye world!'

def main():
    app.debug = True
    app.run(host='localhost', port=5000)

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

起動方法は以下。

```
export FLASK_APP=main_app.py
flask run
```

これだけで、以下のURLにGETメソッドでアクセスして表示までできる。
* http://localhost:5000/
* http://localhost:5000/goodbye


### テンプレート

Rubyのerbみたいなテンプレートにも対応している。
テンプレートファイル`template/hello.html`を作成する。

```template/hello.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Hello</title>
</head>
<body>

{% if username %}
Hello {{ username }}
{% else %}
Hello
{% endif %}

</body>
</html>
```

`/hello/username`、もしくは`/hello`にアクセスしたときのルーティングを定義する。


```main.py
from flask import Flask
from flask import g
from flask import render_template
from flask import request
from flask import Response


app = Flask(__name__)

@app.route('/hello')
@app.route('/hello/<username>')
def hello_user(username=None):
    # return 'Hello {}'.format(username)
    return render_template('hello.html', username=username)

def main():
    app.debug = True
    app.run(host='localhost', port=5000)


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

### Method

```main.py
from flask import Flask
from flask import g
from flask import render_template
from flask import request
from flask import Response

app = Flask(__name__)


# GET, POST, DELETE, PUTを受け付けるようにしている
# methodsにリストで渡すだけでいい。
@app.route('/hoge', methods=['GET', 'POST', 'DELETE', 'PUT'])
def show_hoge():
    # リクエストで受け取ったusernameリターン
    return str(request.values['username'])


def main():
    app.debug = True
    app.run(host='localhost', port=5000)


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

試しに、以下のコードを実行する。

```client.py
import requests


r1 = requests.get(
    'http://localhost:5000/hoge', params={'username': 'Makio'}
)
print(r1.text)


r2 = requests.post(
    'http://localhost:5000/hoge', data={'username': 'Hanako'}
)
print(r2.text)

r3 = requests.put(
    'http://localhost:5000/hoge', data={'username': 'Satoshi'}
)
print(r3.text)

r4 = requests.delete(
    'http://localhost:5000/hoge', data={'username': 'Taro'}
)
print(r4.text)
```

実行結果

```
Makio
Hanako
Satoshi
Taro
```

### DB接続

DB接続のFlaskアプリコード

```main.py
import sqlite3

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

app = Flask(__name__)

# DBのコネクションを取得するメソッド
# g._databaseでグローバルな変数としてセットしているのだと思われる
def get_db():
    db = getattr(g, '_database', None)  # g._databaseを呼び出している?

    if db is None:
        db = g._database = sqlite3.connect('dev_sqlite.db')

    return db


# アプリケーションが終了したらクローズする
@app.teardown_appcontext
def close_connection(exception):
    db = getattr(g, '_database', None)
    if db is not None:
        db.close()


@app.route('/employee', methods=['POST', 'PUT', 'DELETE'])
@app.route('/employee/<name>', methods=['GET'])
def employee(name=None):
    db = get_db()
    curs = db.cursor()
    curs.execute('CREATE TABLE IF NOT EXISTS persons('
                 'id INTEGER PRIMARY KEY AUTOINCREMENT,'
                 'name STRING)')
    db.commit()

    name = request.values.get('name', name)

    if request.method == 'GET':
        curs.execute('SELECT * FROM persons WHERE name = "{}"'.format(name))
        person = curs.fetchone()
        if not person:
            return "No", 404

        user_id, name = person
        return '{}: {}'.format(user_id, name), 200

    if request.method == 'POST':
        curs.execute('INSERT INTO persons(name) values("{}")'.format(name))
        db.commit()
        return 'created {}'.format(name), 201

    if request.method == 'PUT':
        new_name = request.values['new_name']
        curs.execute(
            'UPDATE persons set name = "{}" WHERE name = "{}"'.format(new_name,
                                                                      name))
        db.commit()
        return 'updated {}: {}'.format(name, new_name), 200

    if request.method == 'DELETE':
        curs.execute('DELETE FROM persons WHERE name = "{}"'.format(name))
        db.commit()
        return 'deleted {}'.format(name), 200


# GET, POST, DELETE, PUTを受け付けるようにしている
@app.route('/hoge', methods=['GET', 'POST', 'DELETE', 'PUT'])
def show_hoge():
    # リクエストで受け取ったusernameリターン
    return str(request.values['username'])


def main():
    app.debug = True
    app.run(host='localhost', port=5000)


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

接続確認用のコード

```client.py
import requests

# GETリクエスト
r_get = requests.get('http://localhost:5000/employee/taro')
print(r_get.text)


# POSTリクエスト
r_post = requests.post('http://localhost:5000/employee',
    data={'name': 'taro'})
print(r_post.text)

r_get = requests.get('http://localhost:5000/employee/taro')
print(r_get.text)


# PUTリクエスト
r_put = requests.put('http://localhost:5000/employee',
    data={'name': 'taro', 'new_name': 'makio'})
print(r_put.text)

r_get = requests.get('http://localhost:5000/employee/makio')
print(r_get.text)
```

実行結果

```
No
created taro
2: taro
updated taro: makio
2: makio
deleted makio
No
```

業務都合上、Flaskを使うにしても大した用途で使う気がしないので、この程度にしておいてあとはドキュメント見ながら必要に応じて使って行く感じでいいかなと..
簡単な事がわかったので御の字。

時間があるときにでも追記していきます。