git clone https://github.com/YasuhiroABE/docker-sccp-sinatra-sample.git docker-sccp-python-sample
1. はじめに
ここでは、openapi-generator-cli を利用して、Ruby以外のプログラミング言語、Python用のテンプレート(雛形)コードを生成する方法を説明します。
2. 利用するOpenAPI定義ファイル
チュートリアル「OpenAPI+Sinatraによる検索ページの作成 」で利用した、openapi.yaml ファイルを利用します。
この openapi.yaml ファイルを元に、openapi-generator-cli から、Python用のコードを出力します。
3. 作業の概要
-
ゼミ室10のThinkPadのいずれかにログインします。
-
Ruby用と同様にGitHubのプロジェクトをコピー(clone)します。
-
openapi-generatorコマンドを操作し、希望するプログラミング言語用のテンプレートコードを出力します。
-
動かし方を確認し、必要な改造を加えていきます。
-
検索結果の取得
-
データをHTMLとして表示する機能の実装
-
この作業を始める前に、チュートリアルOpenAPI+Sinatraによる検索ページの作成 の内容は一通り実施しておいてください。
3.1. プロジェクトのcloneと、テンプレートコードの生成
どのようなプログラミング言語(とフレームワーク)が選択できるかは、github.com/OpenAPITools#Overviewの"API stubs"に掲載されています。
まずThinkpad上の適当なディレクトリで、docker-sccp-sinatra-sampleをコピー(clone)します。
続いて作成されたディレクトリに移動します。
cd docker-sccp-python-sample
このプロジェクト自体は、Ruby言語(+Sinatra)を想定しているため、ここからは手作業で進めていきます。
3.2. Python用のテンプレートコードの出力
先ほど説明した"API stubs"の一覧には、"Python (Flask)"の記述がありますが、openapi-generator上では次のように確認できます。
make list | egrep 'python|^[A-Z]'
次のような出力が得られるはずです。
The following generators are available:
CLIENT generators:
- python
- python-experimental (experimental)
- python-legacy
SERVER generators:
- python-aiohttp
- python-blueplanet
- python-fastapi (beta)
- python-flask
DOCUMENTATION generators:
SCHEMA generators:
CONFIG generators:
ここで利用するのは、SERVER generators にリストされている機能です。
3.3. python-flaskを試す
ドキュメントが多いのは、flaskを利用したものだと思います。 ここではPython言語とFlaskフレームワークを利用することにします。
Makefileをcatやlessコマンドなどで確認すると、gen-code:タスクの中に、ruby-sinatra用のコマンドが書かれているので、真似をしてpython-flask用のコードを生成していきます。
なお、flaskの概要については、Flaskへ ようこそ が参考になると思います。
flaskの内部で、openapi.yamlを処理する機能は、connexionライブラリ を利用しています。
make OAGEN_TARGET=python-flask gen-code-only
次のようなメッセージが表示されます。
[main] INFO o.o.codegen.DefaultGenerator - Generating with dryRun=false
...
################################################################################
# Thanks for using OpenAPI Generator. #
# Please consider donation to help us maintain this project 🙏 #
# https://opencollective.com/openapi_generator/donate #
################################################################################
最終的に code/ ディレクトリが生成されていれば成功です。
次にこの生成されたディレクトリに移動し、動かしてみます。
cd code/
ls
コマンドで確認すると次のようなファイルが含まれているはずです。
Dockerfile openapi_server requirements.txt test-requirements.txt
git_push.sh README.md setup.py tox.ini
このプロジェクトでは、既にDockerfileなど、ruby-sinatraでは別途用意したファイルがあらかじめ準備されています。 続いて、README.mdを開いて、どのように利用すれば良いのか、ヒントがないか探します。
less README.md
すると、すぐに次のような説明が見つかると思います。
## Usage
To run the server, please execute the following from the root directory:
pip3 install -r requirements.txt
python3 -m openapi_server
and open your browser to here:
http://localhost:8080/ui/
Your OpenAPI definition lives here:
http://localhost:8080/openapi.json
ホストOSの環境に左右されないように、この手順に従う前にPython3の実行環境を整えます。
python3 -m venv venv
準備された./vevn/ディレクトリを利用するために、venv/bin/activateを読み込みます。
. venv/bin/activate
これで/usr/bin/python3ではなく、./venv/bin/python3を利用して必要なライブラリを./venv/ディレクトリの内部に保存できるようになります。
pip3コマンドを利用して必要なライブラリを./venv/lib/ディレクトリ以下にダウンロードします。
pip3 install -r requirements.txt
README.mdに記述されているようにスクリプトを起動します。
python3 -m openapi_server
何らかのエラーが表示されるので、それぞれ対応します。
3.3.1. 【エラー1】_spec_get がない場合
python3 -m openapi_server
を実行した後に、次のようなメッセージが表示されていることを確認します。
Failed to add operation for GET /.spec
Traceback (most recent call last):
...
File "/home/professor/yasu-abe/.local/lib/python3.6/site-packages/connexion/utils.py", line 68, in deep_getattr
return functools.reduce(getattr, attrs, obj)
AttributeError: module 'openapi_server.controllers.default_controller' has no attribute '_spec_get'
has no attribute '_spec_get' と表示されている場合には、次のように対応します。
最後にあったメッセージのとおり、"_spec_get"という属性がないといっているので、openapi_server/controllers/default_controller.pyを確認します。
$ less openapi_server/controllers/default_controller.py
この中に、次のようなコードが含まれています。
...
def spec_get(): # noqa: E501
"""spec_get
providing the openapi schema YAML file. # noqa: E501
:rtype: None
"""
return 'do some magic!'
def spec_get():は見えますが、先頭にアンダースコアがついた、_spec_get()はないようです。 おそらく、これは、openapi.yamlにURLのpathを/.specとしたために、先頭のピリオド('.')が、アンダースコアに変換されて、その名前で対応する関数を探していたためだと思われます。
このファイルの最後に、次のような2行を追加しておきます。
def _spec_get():
return spec_get()
あるいは、../openapi.yaml ファイルを編集して、path: /spec と定義を変更して、再度 openapi-generator-cli を実行します。
3.3.2. 【エラー2】itsdangerousモジュールのimport jsonがエラーになる
python3 -m openapi_server
を実行して、次のようなメッセージが表示された場合の対応を説明します。
File "/home/yasu/.local/lib/python3.8/site-packages/flask/json/__init__.py", line 15, in <module>
from itsdangerous import json as _json
ImportError: cannot import name 'json' from 'itsdangerous' (/home/yasu/.local/lib/python3.8/site-packages/itsdangerou
s/__init__.py)
この場合には、flaskと関連するモジュールのバージョンを以下のように変更します。
requirements.txt の最後に以下のようなコードを*追加* します。 この時に重複する Flask == 1.1.2 の行は削除してください。
Flask == 1.1.4
itsdangerous == 1.1.0
markupsafe == 1.1.1
werkzeug == 2.0.3
この状態で再度モジュールを導入すると起動するはずです。
pip3 install -r requirements.txt
python3 -m openapi_server
3.4. サーバーの起動
ここまでで、修正がうまくいけば、$ python3 -m openapi_server
を実行することでサーバーが起動し、次のようなメッセージを表示するはずです。
* Serving Flask app "__main__" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://0.0.0.0:8080/ (Press CTRL+C to quit)
次のようなURLをブラウザに入力するか、クリックしてサーバーに接続します。
リモートで作業している場合には、ThinkPadのIPアドレス(192.168.100.xx)を指定します。 次のようなコマンドで、自分のIPアドレスを表示して確認してください。
$ ip addr | grep 192.168
inet 192.168.100.26/24 brd 192.168.100.255 scope global dynamic noprefixroute enp0s25
IPアドレスが 192.168.100.29 の場合には、URL は http://192.168.100.29:8080/search?q= になります。
いずれにしても、正しいURLを入力するとWebブラウザに "do some magic!" と表示されます。
1度ここでサーバーを停止します。
C-c
3.5. テンプレートコードの編集
自動的に生成されたプログラムでは画面に文字を表示するところまで出来ました。
ここから実用的なアプリケーションにするために変更を加えていきます。
アプリケーションに動作を与えるため、openapi_server/controllers/default_controller.py ファイルを編集します。
Ruby+Sinatraで行なったのと同じような機能を持たせるためには、概ね、次のような機能をPythonで実装する必要があります。
-
Search Engine本体のURLへアクセスし、結果を取得する機能
-
出力するHTMLについては、RubyやPythonといった言語に依存しないので、そのまま流用可能
これらの機能を順番に実装していきます。
3.5.1. 検索エンジンにアクセスし、結果を表示する
Ruby+Sinatraで検索結果を取得するために利用したコードは次のようなものでした。
## Ruby言語での処理内容
require 'uri'
require 'httpclient'
client = HTTPClient.new
url = URI::HTTPS.build({:host => "opm00h.u-aizu.ac.jp", :path => '/solr/api/v1/search', :query => "q=sccp&wt=json"})
ret = client.get(url)
result = JSON.parse(ret.body)
result.to_json
これをPythonでも実装すれば、同様の機能が手に入ります。
## Python言語での同等のコード
import urllib.request
import urllib.parse
url = 'https://opm00h.u-aizu.ac.jp/solr/api/v1/search?%s&wt=json' % (urllib.parse.urlencode(dict(q=q)))
req = urllib.request.Request(url)
with urllib.request.urlopen(req) as res:
body = res.read()
return body
return "{}"
このコードを、search_get()
の最後にある return "do some magic"
の行を削除してから、その位置に上記のPython用のコードを挿入します。
pythonで外部のHTTPサーバーにアクセスするためのライブラリを検索すると、urllibと、http.clientの2つのライブラリが見つかりましたが、今回はurllibのみ利用しました。
この状態でサーバーを起動することはできますが、ブラウザーでアクセスするとエラーになります。
python3 -m openapi_server
エラーになることを確認したら、C-c
を入力し、サーバーを停止します。
これは出力形式が、"Content-Type: application/json" となっていて、本来は、"Content-Type: text/html"を期待しています。
そのため、openapi_server/openapi/openapi.yaml ファイルを編集し、/searchに対応するresponsesの項目にcontent:
からtype: string
までの4行を追加します。
この時、先頭の空白の数に意味があるので同じになるよう注意してください。
responses:
"200":
description: 200 response
content:
text/html:
schema:
type: string
x-openapi-router-controller: openapi_server.controllers.default_controller
この状態で、$ python3 -m openapi_serverを再度実行すると、エラーにならずに検索結果の生データが表示されるはずです。
q=パラメータに検索対象の文字列を付与すると、変更が加わって問題なく動作することが分かります。
curl http://localhost:8080/search?q=sccp | jq .
3.6. HTML出力の追加
ここから、Webページとして、ちゃんと出力結果がみえるよう、header.html, footer.htmlなどを出力し、体裁を整えると、Ruby+Sinatraのチュートリアルと同様のアプリケーションが作成できます。
例えば、先ほどのコードでは、body = res.read()の次に、return bodyとreturn "{}"がありましたが、これを削除して、次のコードに置き換えると、検索結果を箇条書きで表示してくれます。
import json
body_json = json.loads(body)
output = "<ul>"
for item in body_json["response"]["docs"]:
output += "<li><a href=\"%s\">%s</a>%s</li>" % (item["id"], item["id"], item["title"][0])
output += "<ul>"
return output
4. 今後の展望
4.1. テンプレートエンジンの採用
実用的なWebアプリケーションを開発するためには、少なくともプログラミングコード(Model/Controller)とHTML(View)の分離が必要です。
Rubyではデータを与えてHTMLを出力する仕組みにERB
を使っていまたが、PythonではJinja2
という仕組みを利用するのが一般的です。
いわゆるテンプレートエンジンと呼ばれているひな形にデータを与えて最終出力を得る仕組みは、多くのプログラミング言語に存在しますが、言語毎に違いが大きいことが特徴です。
OpenAPI-Generator-CLIはJava言語で記述されていますが、Pythonなどでも利用可能なMustache
がテンプレートエンジンとして採用されています。
4.2. データの受け渡し
検索ページとして機能するためには、検索したい文字列の入力が必要です。
これはq
パラメータに任意の文字列を渡すことで実現できるので、前述のコードで利用したoutput
変数に次のようなHTMLを含めれば可能になります。
output += '<form method="get">'
output += '<input name="q" />'
output += '<button type="submit">Submit</button>'
output += '</form>'
5. 新しい言語を利用する際の留意点
プログラミング言語は、似ているものもから、まったく異なるパラダイム(思想)によって作成されたものまで、あるいはそれらが混ざりあうなど多様です。
一般的には、よく利用される構造化プログラミング言語の要素を含むプログラミング言語の特徴は次のようなものです。
-
代入 (a=b)
-
条件分岐 (if-else)
-
繰り返し (for, while, loop)
-
関数化 (def, func)
この他に、文字列操作、変数の型、外部ライブラリの呼び出し、などについてプログラミング言語毎に把握することが、多くの場合必要になります。
まず一つC言語などの代表的なプログラミング言語を習得しながら、各プログラミング言語で、どのようにこれらの処理が行なわれるか、調べて試すことで、様々なプログラミング言語を使いこなすことができるようになります。
この他に最近流行りの、RustやScalaのようなパラダイムの少し異なるプログラミング言語を習得するためには、背景にある関数型プログラミング言語や、オブジェクト指向言語といったパラダイムについての知識も必要になるでしょう。
以上