はじめに
REST-APIを作ってみるシリーズ第三弾。Node.jsは、ブログ作成や個人アプリ開発で少しばかり触れているが、会社で触れる機会のあるPythonとSpringBootも基礎だけ勉強したい。
ということで、基本的なAPIをPythonで作ってみます。Pythonは完全に初心者なので、基本的なところから始めています。
REST-APIを作ってみるシリーズ
環境
- 実行環境:Windows 11
Pythonインストール・初期設定
Pythonインストール
Pythonをインストールします。Anacondaというディストリビューションを利用すると、環境構築に必要なパッケージもまとめてインストールしてくれる。
Anaconda | Anaconda Distribution
統合開発環境(IDE)をインストール
VSCodeで開発しようと思っていたが、調べるとPyCharmという開発環境が無難なようなので、インストールする。
PyCharm:JetBrainsによるプロ開発者向けPython IDE
プロジェクトを作成する
「File」→「New Project」で好きな場所にプロジェクトを作成する。
試しに実行してみる
プロジェクトフォルダ直下にmain.py
というファイルを作成して、実行してみる。
メインファイルは以下のように書くのが決まり文句のようなものらしい。(main()
関数の中に実行したい処理を書いて、main.py
を直接実行したときにのみ処理されるようにする。)
def main():
print('ためしに実行してみる')
if __name__ == '__main__':
main()
モジュールをインストールする
Flaskを利用する
WEB APIを作成するためのモジュールが必要になるため、Flaskという有名なものを利用する。
https://pypi.org/project/Flask/
モジュールの使い方
Node.jsではnpm install [モジュール名]
でインポートして、package.json
を使ってプロジェクトごとに利用モジュールを管理できた。
一方で、Pythonの場合は、pip
というコマンドを使ってインストールするようだが、基本はグローバルインストールになってしまう(該当マシンのpip
が入るフォルダみたいなところにインストールされてしまう。npm -g install
のようなもの?)ため、好ましくない。
適切に運用するには、プロジェクト単位の、pyenv
などを利用するなどして、仮想環境を構築してその中でpip
インストールをする必要があるとのこと。
ただ、pip
とAnaconda
は混ぜるな危険との話もあり、理解に時間がかかりそうなため今回は単純にPyCharmからモジュールを(グローバル)インストール。
PyCharmで「File」→「Settings」→「Project」→「Python Interpreter」→「+」
pip install flask
コマンドで実行しようとすると、ワーニングが発生してタイムアウトとなる。ちょっと理解しきれなかったので、モジュールのインストールに関しては、後日深堀りすることとする。
WARNING: pip is configured with locations that require TLS/SSL, however the ssl module in Python is not available.
モジュールをインポート
main.py
の中でモジュールをインポートする。
from flask import Flask
# from パッケージ名 import モジュール(関数)
APIを作成する
Flask
の基本構文を利用してAPIを作成する。
基本的なAPI
localhost:5000
にアクセスするとhello
という文字列を返却するAPIを作成する。
flask
のパッケージによって、アクセスされたURLによって、指示された値を返却するWEBサーバを作ることができる。
from flask import Flask
# appを宣言
app = Flask(__name__)
# appオブジェクトのrouteメソッドをデコレータとして記載
@app.route('/')def hello():
return 'hello'
def main():
app.debug = True
app.run()
if __name__ == '__main__':
main()
実行したい関数を定義し、app.route()
関数をデコレータにおくと、Webサーバのルーティングを実装することができる。
実行すると待ち受けが始まり、localhost:5000
(127.0.0.1:5000
と同じ)にアクセスすると、hello
という値が返ってくる。
PowerShellを使ってヘッダなどを見てみると以下の通り。
> curl http://127.0.0.1:5000/
StatusCode : 200
StatusDescription : OK
Content : hello
RawContent : HTTP/1.0 200 OK
Content-Length: 5
Content-Type: text/html; charset=utf-8
Date: Sun, 13 Nov 2022 06:57:17 GMT
Server: Werkzeug/2.0.3 Python/3.9.12
hello
Forms : {}
Headers : {[Content-Length, 5], [Content-Type, text/html; charset=utf-8], [Date, Sun, 13 Nov 2022 06:57:17 GM
T], [Server, Werkzeug/2.0.3 Python/3.9.12]}
Images : {}
InputFields : {}
Links : {}
ParsedHtml : mshtml.HTMLDocumentClass
RawContentLength : 5
メンバーリストを返却するAPIを作成
仕様とソースコード
メンバーのリストを照会できるAPIを作成する。
- パスパラメータ無しでアクセスされた場合、全メンバーの情報をリスト返却する。
- パスパラメータにメンバーIDを入れた場合、該当するメンバーIDの情報を返却する。
他のREST-APIを作ってみるシリーズと同じような仕様。
import json # 辞書型をJSON型に変換するために利用(標準パッケージ)
from flask import Flask
# appを宣言
app = Flask(__name__)
# 辞書型の配列
members = [
{"id": "1", "name": "Taro", "team": "A"},
{"id": "2", "name": "Jiro", "team": "B"},
{"id": "3", "name": "Saburo", "team": "A"}]
# パスパラメータがない場合
@app.route('/')
def root():
## 辞書型はそのまま返却できないのでjson.dumpsを使う
return json.dumps(members)
# パスパラメータがある場合
@app.route('/<member_id>')
def filter_by_id(member_id):
member = list(filter(lambda member: member['id'] == member_id, members))
return json.dumps(member)
def main():
app.debug = True
# hostとportを指定したいときは以下の記載とする。
app.run(host='127.0.0.1', port=5000)
if __name__ == '__main__':
main()
パスパラメータがない場合の解説
まず、members
はPythonの辞書型の配列で定義する。
members = [
{"id": "1", "name": "Taro", "team": "A"},
{"id": "2", "name": "Jiro", "team": "B"},
{"id": "3", "name": "Saburo", "team": "A"}]
ルートURLにアクセスした際は、members
をすべて返却するが、APIの返却は辞書型では不可であるためJSONに変換する。
def root():
return json.dumps(members)
ちなみにreturn members
のように返却すると、アクセス時に以下のエラーが発生する。
TypeError: The view function did not return a valid response. The return type must be a string, dict, tuple, Response instance, or WSGI callable, but it was a list.
パスパラメータがある場合の解説
localhost:5000/1
とアクセスした場合にid=1
のメンバー情報のみ返却されるようにする。
パスパラメータを利用したい場合は以下のように変数を設定する。
@app.route('/<member_id>')
すると、以下の関数でmember_id
が変数として利用できる。
members
配列の中で、id
がmember_id
と一致する行のみ抜き出すため以下の処理を記載する。
def filter_by_id(member_id):
member = list(filter(lambda member: member['id'] == member_id, members))
filter
関数は、第一引数に関数、第二引数に配列を取り、配列の要素1個1個を見て、関数を実行し、true
となる要素のみ返却する。
filter(関数, 配列)
関数部分には、Node.jsでもおなじみのラムダ式(無名関数)を利用する。Pythonでの書き方は以下のようになる。
lambda 引数: 返却値
filter
関数は、Generate Objectと呼ばれる直接読めない値を返却する。リストにするにはlist
関数ではさんで上げる必要がある。
list(filter(関数, 配列))
動作確認結果
パスパラメータがない場合
> curl http://127.0.0.1:5000
StatusCode : 200
StatusDescription : OK
Content : [{"id": "1", "name": "Taro", "team": "A"}, {"id": "2", "name": "Jiro", "team": "B"}, {"id": "3", "n
ame": "Saburo", "team": "A"}]
RawContent : HTTP/1.0 200 OK
Content-Length: 128
Content-Type: text/html; charset=utf-8
Date: Sun, 13 Nov 2022 07:31:32 GMT
Server: Werkzeug/2.0.3 Python/3.9.12
[{"id": "1", "name": "Taro", "team": "A"}, {"...
Forms : {}
Headers : {[Content-Length, 128], [Content-Type, text/html; charset=utf-8], [Date, Sun, 13 Nov 2022 07:31:32
GMT], [Server, Werkzeug/2.0.3 Python/3.9.12]}
Images : {}
InputFields : {}
Links : {}
ParsedHtml : mshtml.HTMLDocumentClass
RawContentLength : 128
パスパラメータがある場合
> curl http://127.0.0.1:5000/2
StatusCode : 200
StatusDescription : OK
Content : [{"id": "2", "name": "Jiro", "team": "B"}]
RawContent : HTTP/1.0 200 OK
Content-Length: 42
Content-Type: text/html; charset=utf-8
Date: Sun, 13 Nov 2022 07:32:00 GMT
Server: Werkzeug/2.0.3 Python/3.9.12
[{"id": "2", "name": "Jiro", "team": "B"}]
Forms : {}
Headers : {[Content-Length, 42], [Content-Type, text/html; charset=utf-8], [Date, Sun, 13 Nov 2022 07:32:00 G
MT], [Server, Werkzeug/2.0.3 Python/3.9.12]}
Images : {}
InputFields : {}
Links : {}
ParsedHtml : mshtml.HTMLDocumentClass
RawContentLength : 42
※存在しないid
を指定した場合の処理は未実装のためインデックスエラーが出る。
おわりに
PythonでAPIを作成することができた。
こまごましたところでバックエンドの知識が足りないので、本当はちゃんと学ばないと。。 Pythonは、スクレイピングで利用したいので、癖があるものの基本は身に着けたい。
関連記事
- REST-APIを作ってみる(Node.js + Express編) | エンジニアを目指す日常ブログ
- REST-APIを作ってみる(Node.js + Express + TypeScript編) | エンジニアを目指す日常ブログ
- REST-APIを作ってみる(Java+SpringBoot編その1) | エンジニアを目指す日常ブログ
残課題
- デプロイ
- メソッド(POST等)を変える場合
- DBとの接続
- テンプレートエンジン(余裕があれば)