Python3学び直してみる
私について
人生ではじめて本気で学び始めた言語はJavaなので割と型がないと気持ち悪いです。
そんな静的型付け言語しかやってこなかった私でも、ノリでPHPやJS、Pythonは触っていました。
この記事を書いている意味
最近趣味でもインターンでもPythonを触る機会が多くなってきたので初心に戻り、学び直すというかしっかり学びましょうということでこの記事を書き始めました。
参考にする教科書は新・明解Python入門
です。
Pythonとは
プログラミングパラダイム
- 命令型プログラミング
- 手続き型プログラミング
- 関数型プログラミング
- オブジェクト思考プログラミング
スクリプト言語である
- 記述性が高い
- 可読性が高い
- インタプリタ形式である
- ライブラリが豊富
- プログラミングの実行は高速ではない
- インタプリタだから
- 習得は簡単ではない
- 記述性が高い = 短い記述の中に深い意味が潜んでいる
- 内部はポインタ(参照)だらけ
Python基本
インタラクティブシェル(基本対話モード)
macはターミナルでpython3
と入力して起動
起動すると>>>
このように表示されるのでこの後にコマンドを入力する(基本プロンプト)
終了するにはquit()
もしくはexit()
演算子とオペランド
四則演算子の優先順位などは他の言語や算術演算と同じ
入れ子にすることをネストする
という
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の数値を表す型は三種類の数値型
がある
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は
浮動小数点数リテラル
- 7は
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 = 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を定義できるのでありがたいです。(他にも色々方法はありますが)
インストール
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 .
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 コンテナの操作をまとめます。
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ポートで公開していますが、コンテナポートと呼ばれるコンテナ内限定のポートです。
ではどうすれば良いかと言うと、外からきたリクエストをコンテナ内で実行しているアプリケーションまで到達させてあげれば良いです。
上記に図のように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イメージをビルドすると言います。
Dockerfileではイメージを構築するための手順を書いています。
DockerfileがDockerイメージになっているわけではありません。
Dockerイメージは次に説明するDockerコンテナを作成する為のテンプレートになります。
Dockerコンテナとは
一連の流れで説明したコンテナを起動する
ではDockerイメージを元に実際にアプリを実行させることを示しています。
前述の通りDockerイメージをを元にコンテナが作成されます。
DockerfileのCMD,ENTRYPOINTで定義されているアプリケーションの実行を開始します。
Dockerイメージからは複数のコンテナを作成することができます。
最後に
複雑に見えますが、Dockerの基本操作はイメージに関する操作とコンテナに関する操作です。
今回は概念とDockerを使った一連の流れをまとめたので次回以降は具体的にイメージとコンテナのそれぞれの操作についてまとめていきます。
FlaskをDockerで動かす
目標
前回Flaskで作成したAPIをDockerで動かします。具体的にはlocalhostからDockerコンテナ内にアクセスし、レスポンスを取得します。
ここではDockerを中心に説明するのでAPIのコードは下記を参考にお願いします。
注意としては下記の記事ではsqliteをインメモリで動かしているのでsqliteをファイルで動くように変更してください。参考
環境
- docker-version 18.09.1
イメージ
下記の図のようなイメージで作成します。
https://deparkes.co.uk/2018/03/02/simple-docker-flask-sqlite-api/
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" }
参考
FlaskでAPIを作る
はじめに
Flaskで簡単にAPIを作成してみます。Flaskは超軽量なフレームワークなので、書き方は割と自由です。
PythonのORMである、SQLAlchemyを利用し、接続先をsqliteのインメモリにします。
今回はテストでインメモリデータベースを使いますが、実行するたびにテーブルのデータは消えてしまうので、本番環境では利用しないようにお願いします。
環境構築
- Python 3.7.2
- Flask 1.0.2
前回環境構築について説明したので、これを元に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)