LoginSignup
5
1

More than 5 years have passed since last update.

API Star を使って、YouTube Data API から動画情報を取得して表示する

Last updated at Posted at 2017-10-01

はじめに

今年のPyCon 2017(アメリカ)で、「API Star」という Web API FrameWork が発表され、そのトークを見て、密かにこのWAFに興味を持ったので、今回ちょっと触ってみました。

作ってみたものは、ただのAPI Client なので、完全に車輪の再発明ですが、APIStarがどんな感じか知りたかったので、まあよしなに。。

トーク動画はこちら↓
https://www.youtube.com/watch?v=Rk6MHZdust4&t=27s

目次

  1. この記事ですること
  2. セットアップ
  3. API Client の実装
  4. Viewへの表示
  5. まとめ

この記事ですること

  • 何らかの検索キーワードをなげて、YouTube Data API の結果を取得
  • 気持ち寂しいので、Vue.jsで取得結果も表示してみる

セットアップ

  • 今回使用するPythonのバージョンは、現時点で最新版の3.6.2です。

セットアップの手順は、READMEの記載そのままにすすめる

pip3 install apistar
apistar new youtube_api_client
apistar/app.py
apistar/tests.py

こちらは、async/awaitに対応したセットアップも可能で、興味深いですが、今回は、割愛

API Client の実装

まずは、google-api-python-clientの追加と、APIキーを管理するための設定を追加

pip install google-api-client python-dotenv
touch .env settings.py

.envファイルには、発行されたAPIキーを。settings.pyは以下のように作成

API_KEY=xxxxxx
settings.py
# settings.py
import os
from os.path import join, dirname
from dotenv import load_dotenv

dotenv_path = join(dirname(__file__), '.env')
load_dotenv(dotenv_path)

API_KEY= os.environ.get("API_KEY")

次に、APIの結果を取得するクラスを作成
リクエスト可能なパラメータは、検索キーワード、検索順を指定の2つ

youtube_api_client/youtube_client.py
#!/usr/bin/python
from apiclient.discovery import build
from apiclient.errors import HttpError
import settings

# Set DEVELOPER_KEY to the API key value from the APIs & auth > Registered apps
# tab of
#   https://cloud.google.com/console
# Please ensure that you have enabled the YouTube Data API for your project.
DEVELOPER_KEY = settings.API_KEY
YOUTUBE_API_SERVICE_NAME = "youtube"
YOUTUBE_API_VERSION = "v3"

class Client(object):
    def search(self, query, order_by):
        youtube = build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, developerKey=DEVELOPER_KEY)

        try:
            search_response = youtube.search().list(
                q=query,
                part='id,snippet',
                type='video',
                maxResults=25,
                order=order_by
            ).execute()

            return [search_result for search_result in search_response.get('items', [])]
        except HttpError as e:
            print("An HTTP error %d occurred:\n%s" % (e.resp.status, e.content))
            raise e

そして、いよいよAPIStarの機能を使って、YouTube APIの取得結果から、このアプリケーションのAPIとしてURLを追加する。

app.py
import typing, json
from apistar import Include, Route
from apistar.frameworks.wsgi import WSGIApp as App
from apistar.handlers import docs_urls, static_urls
from apistar import typesystem
from youtube_client import Client


class Query(typesystem.String):
    min_length = 1
    max_length = 250

class Order(typesystem.Enum):
    enum = ['date', 'rating', 'relevance', 'title', 'videoCount', 'viewCount']

def welcome(name=None):
    if name is None:
        return {'message': 'Welcome to API Star!'}
    return {'message': 'Welcome to API Star, %s!' % name}

def search_video(query: Query, order_by: Order):
    if query is None:
        return {'message': 'query is empty!'}
    else:
        videos = Client().search(query, order_by)

        return videos


routes = [
    Route('/', 'GET', welcome),
    Route('/search', 'GET', search_video),
    Include('/docs', docs_urls),
    Include('/static', static_urls)
]

app = App(routes=routes)


if __name__ == '__main__':
    app.main()

少し解説すると、まずclass Query, class Orderで、Pythonの型ヒントを利用したapistar.typesystemで、パラメータのバリデーションを行うことができる。
例えば、class Queryでは、String型として、文字数の最小・最大数や、文字列が指定した正規表現にマッチするかなどがバリデーションとして設定できる。
それぞれの説明については、READMEのTypeSystem:API Referenceに記載されている。

ここでの、APIのイメージは、/search?query=検索キーワード&order_by=date にリクエストしたら、キーワードに関連する動画の情報を取得するというものである。
それを実現するのが、app.pyの以下の部分。

def search_video(query: Query, order_by: Order):
    if query is None:
        return {'message': 'query is empty!'}
    else:
        videos = Client().search(query, order_by)

        return videos

routes = [
    Route('/', 'GET', welcome),
    Route('/search', 'GET', search_video),
    Include('/docs', docs_urls),
    Include('/static', static_urls)
]

まず、routes内でRoute(path, http_method, method)というフォーマットで、ルーティングを定義できるのが、なんとなくわかる。
search_videoメソッドでは、引数にYouTube API のClientクラスを呼び出し、引数の型に、それぞれQuery,Orderを定義し、検索結果を取得する

個人的には、パラメータのバリデーションの責務をQuery,Orderが持つことになるので、search_videoメソッドでは、パラメータが正しいかどうかを気にする必要がなくなるので良さげ
(実際は、if query is Noneという条件分岐があるので、完全にバリデーションを気にしていないといえば嘘になる。これは、僕のAPIStarの理解がまだ乏しいので、class Query内で、検索キーワードが、Noneであるかどうかをチェックするバリデーション方法がちょっとわからなかったためです)

実際に起動して、結果を確認してみる

apistar run
* Running on http://127.0.0.1:8080/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 454-248-548

localhost:8080/docs
スクリーンショット 2017-10-01 0.18.18.png

localhost:8080/search?query=ももクロ&order_by=date
スクリーンショット 2017-10-01 0.20.32.png

確かに取得できている!!
なんということでしょう! メソッド定義して、routesにぶち込むだけで、ここまでやってくれる!なんて素敵なのかしら

APIStarの便利なところは、/docsがあるように、実装として、APIを追加すると、自動的にドキュメントとしても追加してくれることである。

僕が、このフレームワークに興味を持った大きな理由はこの機能があるからと言っても過言ではない。

実際のユースケースとしては、作者のTom Christieが、動画のトーク中に説明していたが、サーバーサイドとフロントエンドで、開発チームが分かれている際に、APIStarで、迅速にAPIのモックを作成して、フロントエンド開発のスタートを早めることができることだと思う。

以下の画像のように、PythonとJavaScriptそれぞれのClientの実装が用意されていることからも、そのような背景が、考慮されていることがわかる。

image.png

Viewへの表示

APIStarは、Web API作成に特化したフレームワークだが、通常のWebアプリケーションと同様に、テンプレートを作成して、Viewの表示をすることもできる。

せっかくなので、フロントエンドのフレームワークは、特に理由なくVue.jsを使用します。
なぜなら、Vue.jsは最高だからです:sunglasses:

HTMLで表示するページは、URLでいえば、/にしようと思います。
そのため、html,jsの各種ファイルをを作成して、app.pyの内容を少し変更します。

touch templates/index.html
touch static/js/app.js
app.py
import typing, json
+ from apistar import Include, Route, annotate
+ from apistar.interfaces import Templates
+ from apistar.renderers import HTMLRenderer
from apistar.frameworks.wsgi import WSGIApp as App
from apistar.handlers import docs_urls, static_urls
from apistar import typesystem
from youtube_client import Client


class Query(typesystem.String):
    min_length = 1
    max_length = 250

class Order(typesystem.Enum):
    enum = ['date', 'rating', 'relevance', 'title', 'videoCount', 'viewCount']

- def welcome(name=None):
-     if name is None:
-         return {'message': 'Welcome to API Star!'}
-     return {'message': 'Welcome to API Star, %s!' % name}
+ @annotate(renderers=[HTMLRenderer()])
+ def welcome(username: str, templates: Templates):
+     index = templates.get_template('index.html')
+     return index.render(username=username)

def search_video(query: Query, order_by: Order):
    if query is None:
        return {'message': 'query is empty!'}
    else:
        videos = Client().search(query, order_by)

        return videos


routes = [
    Route('/', 'GET', welcome),
    Route('/search', 'GET', search_video),
    Include('/docs', docs_urls),
    Include('/static', static_urls)
]

+ settings = {
+     'TEMPLATES': {
+         'ROOT_DIR': 'templates',
+         'PACKAGE_DIRS': ['apistar']
+     },
+     'STATICS': {
+        'ROOT_DIR': 'static',       # Include the 'static/' directory.
+        'PACKAGE_DIRS': ['apistar']  # Include the built-in apistar static files.
+     }
+ }

- app = App(routes=routes)
+ app = App(routes=routes, settings=settings) 


if __name__ == '__main__':
    app.main()

新たに、HTML表示するために必要なパッケージを導入して、レスポンスをHTML形式にしたいAPIに対して、関数デコレーターの@annotateで、HTMLで返すことを決めるイメージでしょうか。
ちなみに、json形式の場合は、以下のような感じ

from apistar.renderers import JSONRenderer

...
@annotate(renderers=[JSONRenderer()])
def download_current_dataset():
    # do something...

HTMLとjsをそれぞれ作成

templates/index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/foundation/6.3.1/css/foundation.min.css">
  <title>YouTube Client</title>
</head>
<body>
  <div class="container" id="app">
    <section class="callout secondary">
      <h3 class="text-center">[[ message ]]</h3>
      <form>
        <div class="row">
          <div class="large-3 columns text-center">
            <input v-model="query" placeholder="キーワードを入力">
            <select name="order_by" v-model="order_by">
              <option disabled value="">Please select one</option>
              <option value="date">date</option>
              <option value="rating">rating</option>
              <option value="relevance">relevance</option>
              <option value="title">title</option>
              <option value="videoCount">videoCount</option>
              <option value="viewCount">viewCount</option>
            </select>
            <a @click="search_video" class="button expanded">Search</a>
          </div>
        </div>
      </form>
    </sction>
    <div class="columns large-3 medium-6" v-for="result in results">
      <div class="card">
        <div class="card-divider">
          [[ result.snippet.title ]]
        </div>
        <img :src="result.snippet.thumbnails.high.url">
        <div class="card-section">
          <p>[[ result.snippet.description ]].</p>
        </div>
      </div>
    </div>
  </div>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.min.js"></script>
  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  <script src="/static/js/app.js"></script>
</body>
</html>

いろいろ、セットアップするのが面倒だったので、今回は簡単に、CDN経由で、ダイレクトにVue.jsAxiosを追加

static/js/app.js
const app = new Vue({
  el: '#app',
  delimiters: ["[[", "]]"],
  data: {
    message: 'YouTube Client',
    query: '',
    order_by: '',
    results: []
  },
  methods: {
    search_video: function () {
      axios.get('/search', {
        params: {
          query: this.query,
          order_by: this.order_by
        }
      })
      .then(response => { this.results = response.data; })
      .catch(error => { console.log(error);
      });
    }
  }
});

APIStarのテンプレートは、Jinja2なので、mustache記法が、二重波括弧({{ }})で、被ってしまうので、Vue.js側で、デリミタを変更する必要があります。

delimiters: ["[[", "]]"],

それ以外は、特筆すべき点はなく、検索キーワードと検索順をパラメータに投げるメソッドを追加しただけ

画面はこんな感じ

デザインのやる気のなさには、ご容赦ください:bow:

ここで表示しているのは、title, description, サムネ画像だけですが、ちゃんと表示された。

スクリーンショット 2017-10-01 17.24.48.png

まとめ

  • APIのデータ構造化を、Python3の型ヒントでうまく利用しているイメージ
  • さくっとモックを作れるし、しっかりAPIとして作りたいときも、フレームワークとして、JSONSchemaにも対応している
  • 認証機能があったり、サーバレス環境へのデプロイなどフレームワークとしての守備範囲は広そう
  • まだまだ開発途中なので、ドキュメントはREADMEとissueが頼り。フレームワーク自身の実装から、挙動を理解・確認する必要が多々あった。
  • Railsと違って、ありがたい先人たちの日本語情報はほぼ皆無。英語情報のみ(個人的には、メリット:star:)

(読み返してみると、そんなにAPIStarの機能使った共有になってないなぁ...:sweat_smile:)

参考

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1