この記事は Python その2 Advent Calendar 2018 - Qiita の1日目です。
responderとは
GitHub - kennethreitz/responder: a familiar HTTP Service Framework for Python
- 2018年10月に公開された イケてるPython WebFramework です。
- requestsやpipenvなどの開発者である Kenneth Reitz が(おそらく)今年のHacktoberfest 2018 - DigitalOcean 用に開発したものだと思われます。
- GitHubのタグを見ると(Topic: hacktoberfest2018 · GitHub )、hacktoberfestで2位
- 今年10月に公開されたのに関わらず既にStarが2000以上付いており、かなり勢いがあります
- この記事では、responderが他のWeb Frameworkと比べて何が素晴らしいのか ということと、responderから見えてくる Kenneth Reitzというエンジニア について書きたいと思います。
Python WebFrameworkは戦国時代だが…
みなさんが「PythonのWebFramework」と言われて思いつくものはなんでしょうか。Django、Flaskでしょうか。まず、Python WebFramework界隈がどんな状況か見てみます。主流なFrameworkを時系列にまとめてみました。
この図から下記のことがわかります。
- 毎年のように新しいWebFrameworkが出ており 乱立 しているとも言える
- 2011年以降はデファクトになっているWebFrameworkはない
- sanicはstar数は多いですが、導入例の数を見るとデファクトとは言えない気がします
- 2015年以降、GraphQL、asyncなどモダンな機能を備えたWebFrameworkが出てきているが、定着はしていない(未だにDjango、Flaskがメインに使われている)
PythonのWebFrameworkと聞くと、「また新しいWebFrameworkか(うんざり)」「DjangoとFlaskでいいじゃん」と思う方ももしかしたらいるかもしれませんが、今見たように、モダンな機能を備えたWebFrameworkが定着していない、という問題があったり、また今年はFlaskの作者がFlaskのメンテナンスを(実態として)辞めるという出来事もあり、実は今 新しいデファクトになるWebFrameworkが求められている と言えると思います。そしてこのresponderがそのデファクトになるのでは…と期待しながらこの記事を書いています。
そろそろ卒Flaskしたい理由のもう一つに、Flask作者のArminさんが最近どうもPythonからRustに軸足を移したように見えて、Flask、Werkzeugなどのメンテも Pallets という Organization に移って Armin さんはメンテしてない。
— INADA Naoki (@methane) 2018年10月20日
これからどんどん jQuery のような存在になっていきそうに感じる。
responderを使う理由
まず、responderの魅力について書いていきたいと思います。
基本的な思想
READMEには下のような記載があります。
The primary concept here is to bring the niceties that are brought forth from both Flask and Falcon and unify them into a single framework, along with some new ideas I have.
記載の通り、FlaskとFalconの良さを1つにまとめたのが基本的な思想で、そこにKenneth Reitzが持っていた幾つかアイディアを加えたのがresponderになります。
使いやすいAPI
responderの一番の魅力はフレンドリーで使っていて楽しいAPIだと思います。 ToyExampleでresponderと、FlaskとFalconで書いたものを比較してみたいと思います。
Hello World
まずはHello worldです。
responder
import responder api = responder.API() @api.route("/") def hello_world(req, resp): resp.text = "Hello, World!"
Flask
from flask import Flask app = Flask() @app.route("/") def hello_world(): return "Hello, World!"
Falcon
import falcon class HelloResource: def on_get(self, req, resp): resp.body = "Hello World!" app = falcon.API() app.add_route('/', HelloResource())
まさに、responderはFlaskとFalconの中間という感じですね。 これだけだとFalconよりはシンプルに書けそう、というのはわかりますが、さらにFlaskが簡単そうで、responderの魅力が伝わりませんね。
動的URL、QueryStringから情報取得&status_code、headerセットしてjsonをresponse
それでは次にほんの少しだけ複雑な例で見てみたいと思います。 URLとQueryStringからパラメータを受け取って、headerとstatus_codeを設定してjsonで返してみます。
responder
import responder api = responder.API() @api.route("/hello/{who}") def hello_to(req, resp, *, who): foo = req.params.get("foo", "") resp.headers["X-Pizza"] = "42" resp.status_code = 200 resp.media = { "Hello": who, "Foo": foo }
Flask
from flask import Flask, jsonify, make_response, request app = Flask() @app.route("/hello/<who>") def hello_to(who): foo = request.args.get("foo", "") body = jsonify({ "Hello": who, "Foo": foo }) resp = make_response(body, 200) resp.headers["X-Pizza"] = "42" return resp
途端にFlaskの方は気になる箇所が噴出してきます。
- responseの構築方法に一貫性がない
- 追加で何かやろうとするたびに
import
でオブジェクト/メソッドを呼び出さないといけない <>
記法がPythonに馴染みがない- QueryStringsへローカル変数でないrequestオブジェクトからアクセスしないといけない
- (ここに書いてないですが)status_codeの返し方など色々な書き方ができてしまう
などです。
※ ちなみに自分はFlaskに精通しているわけではないのでもっと洗練された書き方ができるのかもしれません。ただドキュメントページで一番最初に出てくる通りにやるとこうなります。
responderでは、これらの問題点が全て解決されていることがわかります。
- responseの構築方法が一貫している
- importする必要があるのは
responder
のみ {}
記法(f-string記法)で馴染みがある- QueryStringsへのアクセスはローカル変数のrequestオブジェクトから行える
- さらにこのreqestオブジェクトは requests ライブラリで互換している
これらの解決方法はすべて、Falconから得ています。 端的にいうと、responderは Flaskの少しイケてないところをFalconのデザインで圧倒的に改善した Frameworkだと言えると思います。
非同期処理が簡単に書ける
APIのデザインはFlask、Falconから得ていますが、responderにはそのどちらも持っていない機能のあります。その1つが非同期処理が簡単に書けることです。 公式docsに書いてあるExampleですが、例えば画像をアップロードするAPIなどで、「responseは先に返してバックグラウンドでアップロード処理を行いたい」場合に、async/await記法を使って簡単に書くことができます。
import time @api.route("/incoming") async def receive_incoming(req, resp): @api.background.task def process_data(data): """Just sleeps for three seconds, as a demo.""" time.sleep(3) data = await req.media() process_data(data) # Immediately respond that upload was successful. resp.media = {'success': True}
GraphQL APIが簡単に書ける
またGraphQL APIも簡単に書くことができます。grapheneというWebFrameworkを使っています。
import graphene class Query(graphene.ObjectType): hello = graphene.String(name=graphene.String(default_value="stranger")) def resolve_hello(self, info, name): return f"Hello {name}" schema = graphene.Schema(query=Query) view = responder.ext.GraphQLView(api=api, schema=schema) api.add_route("/graph", view)
他にもモダンな機能いろいろ…
その他にも、たくさんのモダンな機能を備えています。 この辺りは2018年に開発されただけあるな、という感じです。
- FlaskなどのWSGI Applicationのマウント
- SPA対応
- HSTS対応
詳しくは公式docを御覧ください。(既に公式docの完成度が高い…)
https://python-responder.org/en/latest/tour.html
結論: responderは素晴らしい
シンプルでわかりやすいAPI、GraphQL対応,モダンな機能などresponderは素晴らしいWeb Frameworkですね!
responderの裏側(構成要素)
外側から見たresponderの魅力がわかったので、内側も少しだけ解説しておこうと思います。
APIデザイン
APIデザインは上述したようにFlaskとFalconの良いとこ取りになっています。
ASGIサーバー
ASGIサーバー機能はresponderでは実装されておらず、uvicornというライブラリを使っています。
GraphQL
GraphQL機能もresponderでは実装されておらず、上述したようにgrapheneというWebFrameworkを使っています。
実は機能として実装しているものは少ない
このように、実はresponderは0から実装されたものではなく、既存の良いプロダクトを上手く組み合わせて作られている ことがわかります。これは responder の重要な特徴であり、また作者の Kenneth Reitz の性質を非常に表している要素です。
Kenneth Reitzが今まで開発したプロダクト
Kenneth Reitz というエンジニアの性質を知るために彼が今までに作ったプロダクトを見ていきたいと思います。
requests
GitHub - requests/requests: Python HTTP Requests for Humans™ ✨🍰✨
おそらく一番有名なライブラリだと思います。pythonを書いたことがある人の多くが一度は使ったことがあるのではないでしょうか。 requestsはpython標準moduleのurllibを使いやすくラップしたもの です。
pipenv
GitHub - pypa/pipenv: Python Development Workflow for Humans.
今年PyPAの公式Toolにも加えられた環境管理用のライブラリです。pipenvは主に標準moduleのvenvをラップしたもの です。
requests_html
GitHub - kennethreitz/requests-html: Pythonic HTML Parsing for Humans™
requests_htmlは、今年の上旬に公開されたライブラリで、requetsやbeautiful soup、pyppeteer、pyqueryなどをラップした 、htmlの取得、パースが簡単に行えるライブラリです。 知名度はまだそんなにないですが、responder同様かなりオススメです。
ライブラリの共通点
先ほど、responderについて、既存の良いプロダクトを上手く組み合わせて作られている と書きましたが、このように Kenneth Reitz の既存のライブラリは全てこの性質が当てはまることがわかります。Kenneth Reitzはrequests、pipenvのように、とても人気のあるライブラリを開発していますが、実は新しい機能の開発はほとんどしておらず、Kenneth Reitzのプロダクトの本質は APIデザイン にあると思います。
Kenneth Reitzは天才DX(Developer Experience)デザイナーである
Kenneth Reitzがどういうエンジニアなのか、ということを考えるときに「DX(Developer Experience)デザイナー」という言葉が自分の中でしっくりきます。
DX: Developer Experience (開発体験)とは、あるシステムを「気持ちよく開発・保守できるかどうか」を示すもの 開発者は開発・保守という行為を通じたそのシステムのユーザーであり、DXはUXの一種である DXがよいと日々の開発を楽しめるようになり、気持ちに余裕ができる
自分がrequestsやpipenv、そしてresponderを使っているとき、便利なのはもちろんなのですが、何よりも 「楽しい」 と感じます。Kenneth Reitzは楽しいと思えるAPIデザインをする天才だと思います。
例えば、前述したrequests_htmlでは下のようなAPIがあります。
from requests_html import HTMLSession session = HTMLSession() r = session.get('https://python.org/') r.html.find('.foo > .bar', first=True) # first=True!
これはselectorで要素を取得して最初の要素を返すAPIなのですが、
この first=True
というoptionがKenneth Reitzらしいと非常に感じます。
普通は下のように配列のインデックスとしてアクセスするようにのみAPIを設計するほうが一般的で first=True
は醜い、と感じる人も多いと思います。
r.html.find('.foo > .bar')[0]
でも実際に書いてみると、 first=True
の使い勝手の良さに気づくのではないでしょうか。実際、自分はこのAPIがとても気に入っています。
このAPIの例はかなり細かいものですが、Kenneth Reitzはこういった 「攻めている」だけど使うとしっくりくるAPIデザイン をすることにおいて天才的だといつも感じます。
requests_htmlでは他にも、1メソッドでHeadless Chromeインストールから実行までしてくれる下のオシャレなAPIも個人的なお気に入りです。
r.html.render() # Headless Chromeでrendering
Kenneth Reitz以外でDeveloper Experienceデザインに長けたエンジニアとして、真っ先に浮かぶのがkerasの作者のFrançois Cholletです。Theano、Tensorflowをわかりやすく楽しく開発できるようにラップしたkerasを開発した彼も間違いなく、Developer Experienceデザインの天才だと思います。
最後に
- Web Framework for Humanなresponderの紹介をして、作者のKenneth ReitzのDXの才能について書きました。
- 是非responderで楽しいWebApplication開発を体験してみてください。
- もしかしたら今後のデファクトスタンダードになるかもしれません…!
- 自分はこれからもKenneth Reitzのfor HumanでDXの高いプロダクトを楽しみにKenneth Reitzを応援していきたいと思います。