ペイの技術MEMO

文系大学生の技術メモ

Python3学び直してみる

私について

人生ではじめて本気で学び始めた言語はJavaなので割と型がないと気持ち悪いです。

そんな静的型付け言語しかやってこなかった私でも、ノリでPHPやJS、Pythonは触っていました。

この記事を書いている意味

最近趣味でもインターンでもPythonを触る機会が多くなってきたので初心に戻り、学び直すというかしっかり学びましょうということでこの記事を書き始めました。

参考にする教科書は新・明解Python入門です。

www.bohyoh.com

Pythonとは

  • プログラミングパラダイム

  • スクリプト言語である

  • ライブラリが豊富
  • プログラミングの実行は高速ではない
  • 習得は簡単ではない
    • 記述性が高い = 短い記述の中に深い意味が潜んでいる
    • 内部はポインタ(参照)だらけ

Python基本

インタラクティブシェル(基本対話モード)

macはターミナルでpython3と入力して起動

起動すると>>>このように表示されるのでこの後にコマンドを入力する(基本プロンプト)

終了するにはquit()もしくはexit()

演算子オペランド

  • 演算子
    • +-などの演算を行うための記号のこと
  • オペランド
    • 演算の対象になる数字のこと
    • 1 + 1の場合左側を第一オペランド,左オペンラドという。右側も同様

四則演算子の優先順位などは他の言語や算術演算と同じ

入れ子にすることをネストするという ex)7 * ((3 + 5) % 2)

  • 7*+3など場合は7 * (+3)のこと

優先順位

x ** y > +x,-y > x*y, x/y, x//y, x%y, > x+y, x-y

数値型

Pythonの数値を表す型は三種類の数値型がある

  • int型
    • 整数を表す整数型
    • 値の制限はない <-まじか!
  • float型
  • complex型
7 + 3 # int + int = int
7.0 + 3 # float + int = float
7 + 3.0 # int + float = float 
7.0 + 3.0 # float + float = float
  • 数値を表す表記のことを数値リテラル
    • 7は整数リテラル
      • 整数リテラルは10進数,2進数,8進数,16進数がある
    • 3.0は浮動小数点数リテラル

10進数以外の整数リテラルは前置きが必要

0b10 #2進数の10 結果は10進数の2
0o10 #8進数の10 結果は10進数の8
0x10 #16進数の10
010 #10進数の前に0を書くとエラー

演算子を書かずに数値リテラルのみを書くと、その値が表示される。

  • 数字リテラルの途中に_下線を入れることができる
    • 桁が多くなることで見づらくなるのを防ぐ, 結果は下線が抜ける

文字列リテラルエスケープシーケンス

文字の並びは文字列と呼ばれる,その綴りを表記するのが文字列リテラル

'A' #'A'
'Hiyajo' #'Hiyajo'
'比屋定' #'比屋定'
'比' + '屋' + '定' #'比屋定'
'比屋定' * 3 #'比屋定比屋定比屋定'
3 * '比屋定' + 'です。' #'比屋定比屋定比屋定です。'
3 * '比屋定' * 2 #'比屋定比屋定比屋定比屋定比屋定比屋定'

文字列リテラルの型はstr型

文字列 + 数値の演算はできない

  • エスケープシーケンス

    • \を先頭にした、2個、あるいは、それ以上の個数の文字で、単一の文字を表す表記方法がエスケープシーケンス
    • 例えば'(単一引用符)を表記したい場合は'\''と書く
  • 文字列リテラルの表記方法

    • 単一引用符で囲む 'hoge'
    • 二重引用符で囲む "hoge"
    • 3個の単一引用符で囲む  '''hoge'''
    • 3個の二重引用符で囲む """hoge"""
      • ''' """を使うと基本プロンプトで改行してかける。その際に表示される・・・は補助プロンプトが表示される

基本的には''で囲み,単一引用符が含まれる時に""で囲む

  • 現文字列リテラル
    • rもしくはRを文字列の前にかくことでエスケープシーケンスが、その綴りのまま解釈されるもの
hoge = r'改行\nコード'
print(hoge)
# 改行\nコード (エスケープシーケンスがそのまま解釈されている)

変数と型

  • 変数

    • 値を格納する箱
    • 型を確認する方法はtype()で式の型が得られる
>>> x = 10
>>> type(x)
<class 'int'>

>>> x = 10.5
>>> type(x)
<class 'float'>

>>> x = 'hello'
>>> type(x)
<class 'str'>
  • 変数名の規則

    • つける文字はアルファベットと数字,下線
    • アルファベットは大文字と小文字は区別される
    • 先頭に数字は書けない
  • 式と文

    • x = 17は文
    • x + 17は式
  • 代入文

    • =演算子ではなく、代入文
    • 複数の変数へ同じ値を一括代入
      • x = y = 1 結果はxもyも1
    • 複数の変数へ異なる値を一括代入
      • x, y, z = 1, 2, 3 xは1,yは2,zは3
    • 応用 
>>> x= 6
>>> y=2
>>> x, y = y + 2, x + 3 # xは4 yは9

同時にxとyに代入されています。

最後に

インタラクティブシェルにてimport thisと入力すると Tim Peters氏がまとめたThe Zen of PythonというPythonプログラミングの指針が表示されるので読んでみましょう

direnvでディレクトリ毎の環境変数を定義する

direnvとは

direnvとはディレクトリ毎に環境変数を定義できる優れものです。任意のディレクトリに移動した時のみ環境変数が有効になります。

Pythonやっている人からしたらディレクトリ毎にPythonのversionを定義できるのでありがたいです。(他にも色々方法はありますが)

direnv.net

インストール

git clone git@github.com:direnv/direnv.git
cd direnv
make

##/usr/localにインストールする場合
make install

インストールが終わったらシェルにフックさせます。

## bash ~/.bashrcの末尾に以下を追加
eval "$(direnv hook bash)"

## zsh ~/.zshrcの末尾に以下を追加
eval "$(direnv hook zsh)"

他のシェルをお使いの方は公式のSetupを参照してください

使ってみる

簡単なFlaskアプリを作ることを想定してみます。

その場合に必要となるものはPythonのVersionの指定、DBのURLを環境変数に入れるなどがあげられます。

アプリ作成しているルートディレクトリに下記コマンドを打ちます。

direnv edit .

vim等が立ち上がると思うので環境変数を書きます。

layout python3
export FLASK_APP=app.py
export FLASK_DEBUG=1
export DATABASE_URL=hoge

やっていることとしては、

Python3系を使い、Flaskを立ち上げるファイルの定義、デバッグ有効、データベースURLの定義を

このディレクトリになった時に有効にするといったことをしています。

保存できたらディクレトリ上に.envrcというファイルが作成されます。

direnv: loading .envrc
direnv: export +DATABASE_URL +FLASK_APP +FLASK_DEBUG +VIRTUAL_ENV ~PATH

vimを抜けると変更されているのが確認できると思います。

うまく変更されない場合は下記コマンドお試しください。

direnv allow .

最後に

.envrcファイルは.gitignore等に書きコミットしないようにしましょう。

特に上記の場合だとDBのURLを書いているので危険です。

Docker コンテナの操作

はじめに

前回はDocker イメージ操作をまとめたので、今回はDocker コンテナの操作をまとめます。

gihyo.jp

Dockerコンテナの操作

Dockerコンテナとは?

  • Dockerイメージを基に作成されたもの
  • ファイルシステムとアプリケーションが同梱されている箱のようなもの

Dockerコンテナのライフサイクル

Dockerコンテナは実行中停止破棄という3つの状態に分類されます。

同じDockerイメージから作成されたDockerコンテナはそれぞれ個別に状態をもちます。

実行中
  • docker container runでDockerイメージを基にコンテナが作成され、Dockerfileの内容を基にアプリケーションが実行を開始する
  • 上記の状態がDockerコンテナは実行中であるということ
停止
  • docker container stopかコンテナで実行されているアプリケーションが正常・異常を問わず終了した場合に停止する
  • 停止してもディスクにはコンテナ終了時の状態が保持されている
破棄
  • 破棄しないかぎりホストOSを終了させてもディスクに残り続ける
    • 実行・停止を繰り返す度ディスクを専有するので不要なコンテナは破棄する
  • 完全に同じ状態をもったコンテナを新しく作成することは不可能(コンテナが開始した時間、実行時間等)なため破棄する際は注意が必要

コンテナの作成と実行

docker container runはライフサイクルにおいて実行中の状態にするために使います。

docker container run [options] イメージ名[:タグ名] [コマンド] [コマンド引数...]
docker container run [options] イメージID [コマンド] [コマンド引数...]

Dockerはサーバーアプリケーションで利用することが多い為、バックグラウド、ポートフォワーディングできるようにするコマンドが多くなります。

バックグラウンド
docker container run -d example/echo:latest
  • -dオプションでバックグラウンドでコンテナを実行できます。
ポートフォワーディング

Dockerコンテナは仮想環境ですが、外から1つの独立したマシンのように扱えます。

少し理解するのに時間がかかったので簡単なFlaskのアプリを使って説明したいと思います。

main.py

下記はホスト0.0.0.0のポート5000で動くFlaskのアプリです。/にアクセスするとHello World!と返します。

from flask import Flask

app = Flask(__name__)

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

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

ではhttpieを使って確認してみます。

http localhost:5000

HTTP/1.0 200 OK
Content-Length: 12
Content-Type: text/html; charset=utf-8
Date: Sat, 11 May 2019 02:21:00 GMT
Server: Werkzeug/0.15.2 Python/3.7.2

Hello world!

そしたらDockerfileを書いてDockerコンテナで動かしてみます。

Dockerfile
FROM python:3.7-slim

RUN mkdir /echo
RUN pip install flask

COPY main.py /echo

CMD ["python3", "/echo/main.py"]
docker image build -t example/echo .
docker container run -d example/echo

上記のコマンド2つでDockerfileを基にイメージの作成と、コンテナの起動を行ってます。

そしたら確認してみます。

http localhost:5000

http: error: ConnectionError:

コネクションエラーがおきました。

これはポートの5000番にアクセスできないということです。

なぜかと言うと上述のようにDockerは独立したマシンのように扱える特徴があるので、Flaskで作ったアプリは5000ポートで公開していますが、コンテナポートと呼ばれるコンテナ内限定のポートです。

ではどうすれば良いかと言うと、外からきたリクエストをコンテナ内で実行しているアプリケーションまで到達させてあげれば良いです。

f:id:PeeI:20190511113909j:plain

上記に図のようにDockerコンテナ内は5000で公開しているのでそのコンテナに8080でアクセスできるように紐付けます。

docker container run -d -p 8080:5000 example/hello
  • -pオプションでポートフォワーディングをしている
  • -p {ホスト側のポート}:{コンテナポート}と指定する

確認します。

http localhost:8080

HTTP/1.0 200 OK
Content-Length: 12
Content-Type: text/html; charset=utf-8
Date: Sat, 11 May 2019 02:52:24 GMT
Server: Werkzeug/0.15.2 Python/3.7.3

Hello world!
名前付きコンテナ

docker container lsでコンテナ一覧を確認するとNAMESに適当な名前が自動でつけられます。

コンテナを制御するコマンドを実行する際はコンテナIDかコンテナ名を指定します。

コンテナ名を指定するとなんどもコンテナIDを調べる必要がなくなり開発効率がよくなります。

docker container run --name [コンテナ名] [イメージ名]:[タグ]

コンテナの一覧

docker container lsは実行中のコンテナ、終了したコンテナ一覧を表示するためのコマンドです。

docker container ls [options]

下記は項目の意味

項目 内容
CONTAINER ID コンテナに付与される一意のID
IMAGE コンテナ作成に使用されたDockerイメージ
COMMAND コンテナで実行されているアプリケーションのプロセス
CREATED コンテナが作成されてから経過した時間
STATUS Up(実行中)、Exited(終了)といったコンテナの実行状態
PORTS ホストのポートとコンテナポートの紐付け(ポートフォワーディング)
NAMES コンテナにつけられた名前
コンテナIDだけ抽出する
docker container ls -q
filterを使う

docker container lsの結果を条件の一致したもののみ抽出するには下記のコマンドをつかいます。

docker container ls --filter "filter名=値"

filter名で指定するものは多いので下記を参考

ps — Docker-docs-ja 17.06.Beta ドキュメント

終了したコンテナを取得する
docker container ls -a

コンテナの停止

実行しているコンテナを終了するにはdocker container stopコマンドを実行します。

docker container stop コンテナIDまたはコンテナ名

コンテナの再起動

一度停止したコンテナは破棄しない限り、docker container restartコマンドで再実行できます。

docker container restart コンテナIDまたはコンテナ名

コンテナの破棄

上述のコンテナのライフサイクルで述べたようにdocker container stopではディスクにコンテナが残っているので完全に破棄するにはdocker container rmコマンドを使用します。

docker container rm コンテナIDまたはコンテナ名

実行中のコマンドは破棄できないので、停止して破棄する以外に-fオプションをつけることで実行中のコンテナを停止・削除まで行うことができます。

停止の際にコンテナを破棄する

docker container run --rmは停止時にコンテナを破棄します。

標準出力の取得

下記のコマンドは実行している特定のDockerコンテナの標準出力を表示できます。

docker container logs [options] コンテナIDまたはコンテナ名

実行中コンテナのコマンド実行

Dockerコンテナの中で任意のコマンドを実行するには下記のコマンドを実行します。

docker container exec [options] コンテナIDまたはコンテナ名 コンテナ内で実行するコマンド

標準入力を開いたままにする-iオプションと、仮想ターミナルを割り当てる-tオプションを組み合わせると、コンテナをシェル経由で操作できます。(本番環境では注意が必要)

docker container exec -it 36a00a9fddc8 sh

# pwd
/

ファイルのコピー

docker container cpコマンドはコンテナ間、コンテナ・ホスト間でファイルをコピーする際に使います。

docker container cp [options] コンテナIDまたはコンテナ名:コンテナ内のコピー元 ホストのコピー先
docker container cp [options] ホストのコピー元 コンテナIDまたはコンテナ名:コンテナ内のコピー先

例えばホストにあるhoge.txtをコンテナにコピーする際は以下のようにします。

docker container cp hoge.txt 36a00a9fddc8:/tmp

コンテナの中に入って確認してみます。

docker container exec -it 36a00a9fddc8 sh
# ls
bin  boot  dev  echo  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
# cd tmp
# ls
hoge.txt

次は逆にコンテナにあるfoo.txtをホストにコピーします。

コンテナ内でfoo.txtファイルを作って抜けた後ホストで確認しています。

docker container exec -it 36a00a9fddc8 sh

# cd tmp 
# ls
hoge.txt
# touch foo.txt
# ls
foo.txt  hoge.txt
ls

Dockerfile  foo.txt  hoge.txt   main.py

docker container cpは上記のようにコンテナの中に入ってファイルを直接いじるようなことより、コンテナの中で生成されたファイルをホストにコピーして確認するようなデバッグ用途で使われます。

また、破棄されていない終了したコンテナに対しても実行できます。

Docker イメージの操作

はじめに

前回はDocker/Kubernetes 実践コンテナ開発入門の概念とDockerを使った一連の流れをまとめたので、今回はイメージの操作をまとめます。 gihyo.jp

Dockerイメージの操作

Dockerイメージとは?

イメージのビルド

下記のコマンドはDockerfileを元にDockerイメージを作成するコマンドです。

docker image build -t イメージ名[:タグ名] Dockerfile配置ディレクトリのパス
  • -tはほぼ必須
  • image buildはDockerfileが必要
-fオプション
docker image build -f Dockerfile-test -t example/echo:latest .
  • デフォルトではDockerfileと言う名前のファイルを探すが、任意の名前をつける場合は-fをオプションをつけて名前の指定を行う
-pullオプション
docker image build -pull=true -t example/echo:latest .
  • FROMで指定したベースを毎回リモートから取得する。
  • 逆に言うと--pullを指定しないと一度取得したDockerイメージはホストから参照される。
  • ビルド時間は遅くなる。

イメージの検索

以下のコマンドはDocker Hubのレジストリに登録されているリポジトリを検索するコマンドです。

docker search [option] 検索キーワード

イメージの取得

下記はDockerレジストリからDockerイメージを取得するコマンドです。

イメージをダウンロードするのでそのままDockerコンテナとして利用することができます。

docker image pull [options] リポジトリ名[:タグ名]
  • リポジトリ名とタグ名はDocker Hubに存在するものを指定
  • タグを省略した場合はデフォルトタグが利用される

イメージの一覧

Dockerホストに保持されているイメージの一覧を表示します。

リモートからpullしてきたイメージ、docker image buildでビルドしたイメージがDockerホストのディスクに保持されます。

docker image ls [options] [リポジトリ[:タグ]]

結果

docker image ls
REPOSITORY                                 TAG                 IMAGE ID            CREATED             SIZE
gihyodocker/concreatetest                  latest              624fdc0f159e        36 hours ago        4.41MB
  • IMAGE IDはイメージのIDの為、コンテナのIDとは別物
  • イメージとコンテナの管理は別々に管理されていると覚えるとOK

イメージのタグ付け

ビルドする際やプルする際にはリポジトリ名の後に:[タグ名]を指定していました。

逆になぜタグ名を指定するかと言うとDockerイメージを識別しやすくするためです。

リソース番号をつけてイメージを管理しやすくするために使われます。

docker image tag 元イメージ名[:タグ] 新イメージ名[:タグ]

例)example/echoのlatestタグを0.1.0へタグを新しくつける場合

docker image tag example/echo:latest example/echo:0.1.0

Docker/Kubernetes 実践コンテナ開発入門

はじめに

現在使用したことある仮想化ソフトウェアとしてはVirtualboxがありますが、Mac(ホスト)側でデスクトップアプリをパッケージ化し、そのパッケージをwindows(ゲスト)に送り... のように作業も多く、なおかつ環境を作るのにとても時間がかかり大変です。そこらへんと比べてDockerとは何か?を理解できたらと思います。

何回か記事を分けてDocker/Kubernetes 実践コンテナ開発入門を進めていきます。 gihyo.jp

Dockerとは?

  • コンテナ型仮想化技術を実現するために実行される常駐アプリケーション
  • 上記を操作する為のCLIから成るプロダクト
  • アプリケーションをデプロイすることに特化した箱

前回書いた記事ではFlaskをDockerで動かし、APIを作成しています。 アプリケーションをデプロイすることに特化した箱なのでローカルで検証するだけでなく、実際のアプリケーションとして機能します。

Dockerの苦手なこと

  • Dockerのコンテナ内部はLinux系OSのような構成をしている為、OSとしての振る舞いを完全に再現しているわけではない

あくまでもアプリケーションをデプロイすることに特化した箱です。

コンテナ型仮想化技術

  • 仮想化ソフトウェアなしでOSのリソースを隔離して仮想OSにすることをコンテナと呼ぶ
  • 他の仮想化ソフトウェアと比べると呼び出す負荷(オーバーヘッド)が少なくすむ
  • 高速に起動・終了できる

他の仮想化技術は?

  • OSの上のインストールした仮想化ソフトウェアを利用して、ゲストOSを作り出すホストOS型(例 VMware Player, VirtualBox)
  • サーバーへ直接インストールし仮想マシンを稼働させるハイパーバイザ型(例 Hyper-V Server)

Dockerの利用方法

Dockerは前述の通りアプリケーションをデプロイすることに特化した箱です。

具体的にはアプリケーションと、実行環境を同梱してデプロイします。

実際に以下のスクリプトを使ってDockerの流れを追ってみたいと思います。

#! /bin/sh

echo "Hello, World"

上記にhelloworldと名前をつけて保存します。

まずは普通にこのシェルスクリプトを実行させてみます。

sh helloworld

結果

Hello, World

当然ですね。

使ってるPCがmacならmacの環境で動かしたことになりますし、windowsならwindowsで動いたことになります。

ではDockerを使ってUbuntuを同梱させて実行させてみます。

上記のhelloworldスクリプトがあるディレクトリにDockerfileという名前でファイルを作り下記の内容を書きます。

FROM ubuntu:16.04

COPY helloworld /user/local/bin
RUN chmod +x /user/local/bin/helloworld

CMD ["helloworld"]

今回はDockerをつかった流れの説明なのでDockerfileの詳しい説明は次回以降します。

  • FROMではイメージになるOSを指定
  • COPYではホスト(helloworldを作ったパソコン)のhelloworldファイルをDockerコンテナ内の/user/local/binにコピー
  • RUNでは上述でコピーしたスクリプトに実行権限を与えてます
  • CMDではアプリケーションを実行するコマンドを指定

*Dockerのインストールに関しては割愛

Dockerfileがあるディレクトリでビルドします。

docker image build -t helloworld:latest .

ビルドができたらコンテナを実行します。

docker container run helloworld:latest

結果

Hello, World

以上でシェルスクリプトUbuntuに同梱してコンテナとして実行することができました。

上記でやった流れをまとめてみます。

Dockerfileとシェルスクリプト(アプリと考えて)を用意する
↓
ビルドする
↓
コンテナを起動する

これだけです。

では一連の流れをもう少し詳しくみてみます。

Dockerイメージとは

上記で述べてるビルドするというのはDockerコンテナの元になるイメージを作っています。

このことをDockerイメージをビルドすると言います。

f:id:PeeI:20190508225614j:plain

Dockerfileではイメージを構築するための手順を書いています。

DockerfileがDockerイメージになっているわけではありません。

Dockerイメージは次に説明するDockerコンテナを作成する為のテンプレートになります。

Dockerコンテナとは

一連の流れで説明したコンテナを起動するではDockerイメージを元に実際にアプリを実行させることを示しています。

f:id:PeeI:20190508231420j:plain

前述の通りDockerイメージをを元にコンテナが作成されます。

DockerfileのCMD,ENTRYPOINTで定義されているアプリケーションの実行を開始します。

Dockerイメージからは複数のコンテナを作成することができます。

最後に

複雑に見えますが、Dockerの基本操作はイメージに関する操作とコンテナに関する操作です。

今回は概念とDockerを使った一連の流れをまとめたので次回以降は具体的にイメージとコンテナのそれぞれの操作についてまとめていきます。

FlaskをDockerで動かす

目標

前回Flaskで作成したAPIをDockerで動かします。具体的にはlocalhostからDockerコンテナ内にアクセスし、レスポンスを取得します。

ここではDockerを中心に説明するのでAPIのコードは下記を参考にお願いします。

注意としては下記の記事ではsqliteをインメモリで動かしているのでsqliteをファイルで動くように変更してください。参考

peei.hatenablog.com

環境

  • docker-version 18.09.1

イメージ

下記の図のようなイメージで作成します。

https://deparkes.co.uk/2018/03/02/simple-docker-flask-sqlite-api/ https://deparkes.co.uk/wp-content/uploads/2018/02/DockerFlaskSQLite_schematic.png

Dockerfile

app.py(API)と同じディレクトリDockerfileを作ります。

flask/
    ├ Dockerfile
    └ app.py

↓Dockerfile

FROM python:3.7-slim

COPY app.py /root/app.py

RUN pip install flask Flask-SQLAlchemy 

ENV FLASK_APP /root/app.py

CMD ["flask", "run", "-h", "0.0.0.0"]

FROM

Dockerイメージのベースになるイメージを指定します。イメージの取得はDocker Hubレジストリからできます。

Dockerfileはビルドする際にFROMで指定されているイメージをダウンロードしてから実行します。

今回の場合pythonのイメージを指定し、:3.7-slimタグを指定しています。

:以降はタグと呼ばれ、イメージのバージョンなどを指定します。

COPY

ホストマシン上のファイル、ディレクトリをDockerコンテナ内にコピーします。

今回はapp.pyにapiの処理を書いているので、Dockerコンテナ内のrootに配置します。

RUN

Dockerイメージビルド時に、Dockerコンテナ内で実行するコマンドです。

引数はターミナル等で書く感じで大丈夫です。APIはFlaskとsqlalchemyをpip installしないと動かないようになっているので、Dockerコンテナ内でも同じことをします。

ENV

Dockerコンテナ内で使える環境変数を指定します。

Flaskではローカルでapp.pyをflask runコマンドを使って動かしたい時export FLASK_APP = app.pyのようにエクスポートします。

そのため、Dockerコンテナ内でflask runを使用するにはENVに環境変数を指定しておく必要があります。(指定しなくても直接ファイルを動かすようにもできます。)

CMD

Dockerコンテナ内で実行するコマンドです。CMDは起動時に一度実行されるのでコンテナそのものを動かすコマンドと考えましょう。

上記のENVでflask runが使えるのでflask run -h 0.0.0.0を実行させるようにします。

CMDではRUNのように普通のターミナルに書くような感じでは書けなくて1つのコマンドを空白で分割し、配列形式で指定します。

flask runではDockerコンテナ自体を動かしているので、flask runだけでは動きません。最初のイメージの図で紹介したようにDocker内のホストを指定してあげます。

ビルドする

ではDockerfileをビルドしてイメージを作成してみます。

docker build -t flask_docker .

ビルドするにはdocker buildをします。

オプションの-tはビルドが成功した後にリポジトリ名をつけるときに指定します。なので上記の場合はflask_dockerというリポジトリ名ができます。

末尾の.はDockerfile配置ディレクトリのパスを指定します。

ビルドすると以下のようにステップ実行されてる内容がステップ毎にわかります。

Step 1/5 : FROM python:3.7-slim
 ---> 6ba8638f69d7
Step 2/5 : COPY app.py /root/app.py
 ---> Using cache
 ---> ef6c6d4ab332
Step 3/5 : RUN pip install flask Flask-SQLAlchemy
 ---> Using cache
 ---> 01e50e93be98
Step 4/5 : ENV FLASK_APP /root/app.py
 ---> Using cache
 ---> 592afeb1b548
Step 5/5 : CMD ["flask", "run", "-h", "0.0.0.0"]
 ---> Using cache
 ---> 8f6c2251f151
Successfully built 8f6c2251f151
Successfully tagged flask_docker:latest

docker imagesコマンドでイメージが作成されてるのを確認しましょう。

コンテナの起動

ビルドし、イメージができたのでコンテナを起動してみます。

docker run -p 5000:5000 -it flask_docker

コンテナを実行するにはdocker runコマンドを使用します。

-pではポートを指定しています。ホスト5000番からDocker内で動かしているFlaskの5000番にアクセスします。参考

-itで先ほど作成したイメージのリポジトリ名を指定しています。

APIを叩く

コンテナが起動したので、違うターミナルに移動してAPIを叩いてみます。

http localhost:5000/users

HTTP/1.0 200 OK
Content-Length: 3
Content-Type: application/json
Date: Fri, 26 Apr 2019 14:33:40 GMT
Server: Werkzeug/0.15.2 Python/3.7.3

[]

http localhost:5000/users name=hoge

HTTP/1.0 201 CREATED
Content-Length: 23
Content-Type: application/json
Date: Fri, 26 Apr 2019 14:34:31 GMT
Server: Werkzeug/0.15.2 Python/3.7.3

{
    "id": 1,
    "name": "hoge"
}

参考

DockerコンテナでFlaskを起動し, JSONデータのPOSTとGET - Qiita

Simple Docker Flask SQLite API - deparkes

FlaskでAPIを作る

はじめに

Flaskで簡単にAPIを作成してみます。Flaskは超軽量なフレームワークなので、書き方は割と自由です。

PythonのORMである、SQLAlchemyを利用し、接続先をsqliteのインメモリにします。

今回はテストでインメモリデータベースを使いますが、実行するたびにテーブルのデータは消えてしまうので、本番環境では利用しないようにお願いします。

環境構築

peei.hatenablog.com

前回環境構築について説明したので、これを元にAPIのファイルを作成していきます。

ファイルを変更するたびに、実行しないといけないのも大変なので、flask runで実行し、保存したら自動的に変更内容が反映されるようにします。

export FLASK_ENV=development
export FLASK_APP=app.py

app.pyは今回APIを作成するファイルです。これでflask runしたときにこのファイルが実行されます。

また、FlakとSQLAlchemyを使うので、pip等で使えるようにしておきます。

pip install flask
pip install Flask-SQLAlchemy

Hello World

まずは簡単にusersにリクエストしたら、Hello Worldと返すようなAPIを作成します。

app.pyを以下のように変更します。

from flask import Flask, jsonify

app = Flask(__name__)


@app.route('/users')
def list_user():
    return jsonify({
        'message': 'Hello World'
    })

flask runを実行します。

そしたら別のシェルを開き、curl等を投げてみます。

ただ、curlはオプションをつけると、少々複雑化してしまうので、今回はHTTPieをつかいます。

brew install httpie

早速リクエストしてみます。

http http://localhost:5000/users

HTTPieを使うと以下のようにレスポンスされるかと思います。

HTTP/1.0 200 OK
Content-Length: 31
Content-Type: application/json
Date: Sun, 31 Mar 2019 09:12:30 GMT
Server: Werkzeug/0.15.1 Python/3.7.2

{
    "message": "Hello World"
}

usersにアクセスすると、Flaskのライブラリであるjsonifyを利用してjson形式でレスポンスしているのが分かります。

SQLAlchemyを使ってみる

PythonのORMであるSQLAlchemyを環境構築でinstallしていると思うので、app.pyに読み込んで、そこにデータを保存していけるようにします。

from flask import Flask, request, jsonify #元からある
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__) #元からある
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'

db = SQLAlchemy(app)


class User(db.Model):
    __tablename__ = 'hoge'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Text)


with app.app_context():
    db.create_all()

以下のconfigでデータベースの接続先を記述しています。

app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'

'sqlite:///:memory:'とすることで、sqliteのIn-memory上に一旦保存することができます。

インメモリ上ではflask run(今回の場合はファイルを変更)するたびにデータベースに保存した内容は消えます。


SQLAlchemyの詳細は以下のクイックスタートが参考になりました。

Quickstart — Flask-SQLAlchemy Documentation (2.3)

クイックスタートではPythonインタプリタを起動して、db.create_all()をしています。

ただテーブルとデータベースを作成するのに、インタプリタを開くのは結構面倒なので、app.pyが読み込まれた時点でデータベースも初期化できるようにフックします。

それが以下の書き方です。

with app.app_context():
    db.create_all()

CRUD

上記で準備ができたので、CRUD処理を書いていきます。

仕様としては、idは一意にして、nameをそれぞれCRUDできるようにします。

@app.route('/users')
def list_user():
    users = User.query.all()
    data = [
        {
            'id': i.id,
            'name': i.name,
        }
        for i in users
    ]
    return jsonify(data)


@app.route('/users', methods=['POST'])
def create_user():
    user = User()
    user.name = request.json['name']
    db.session.add(user)
    db.session.commit()
    return jsonify({
        'id': user.id,
        'name': user.name
    }), 201


@app.route('/users/<id>', methods=['PUT'])
def update_user(id):
    user = User.query.get(id)
    if not user:
        return jsonify({
            'message': 'userは存在しません。'
        }), 404
    user.name = request.json['name']
    db.session.add(user)
    db.session.commit()
    return jsonify({
        'id': user.id,
        'name': user.name
    })
    return "", 201


@app.route('/users/<id>', methods=['DELETE'])
def delete_user(id):
    user = User.query.get(id)
    if not user:
        return jsonify({
            'message': 'idが一致していません。'
        }), 404
    db.session.delete(user)
    db.session.commit()
    return "", 201

GET

http http://localhost:5000/users

HTTP/1.0 200 OK
Content-Length: 3
Content-Type: application/json
Date: Sun, 31 Mar 2019 09:58:05 GMT
Server: Werkzeug/0.15.1 Python/3.7.2

[]

POST

http POST http://localhost:5000/users name=hoge

HTTP/1.0 201 CREATED
Content-Length: 33
Content-Type: application/json
Date: Sun, 31 Mar 2019 09:58:52 GMT
Server: Werkzeug/0.15.1 Python/3.7.2

{
    "id": 1,
    "name": "hoge"
}

PUT

http PUT http://localhost:5000/users/1 name=hogehoge

HTTP/1.0 200 OK
Content-Length: 37
Content-Type: application/json
Date: Sun, 31 Mar 2019 09:59:25 GMT
Server: Werkzeug/0.15.1 Python/3.7.2

{
    "id": 1,
    "name": "hogehoge"
}

DELETE

http DELETE http://localhost:5000/users/1

HTTP/1.0 201 CREATED
Content-Length: 0
Content-Type: text/html; charset=utf-8
Date: Sun, 31 Mar 2019 09:59:43 GMT
Server: Werkzeug/0.15.1 Python/3.7.2

やっていることは単純でPOSTしているときは、idは一意なので、被ることはありません。 なので、nameをPOSTするだけでデータベースに保存されるようになっています。

PUTはidを引数にとり、データベースに存在しなかったら更新できないようにしています。

DELETEもidを引数にとり、データベースに存在しなかったら削除できないようにしています。

また、APIを叩き、status codeがわからないと、削除した時などは特に分かりづらいのでreturnする際にstatus codeも返すようにします。

さいごに

本来データベースに登録する処理はバリデート処理などを事前に挟めないといけませんが、今回はAPIを叩き、レスポンスをもらうことを中心に書いたので、別の機会で詳しく書きたいと思います。

参考

Quickstart — Flask-SQLAlchemy Documentation (2.3)

FlaskでREST APIを作ってみる – Nyle Engineering Blog – Medium

API — Flask 1.0.2 documentation