DjangoなRESTful APIの使用方法

Python,TIPSDjango,ipstack API,RESTful API

RESTful APIについてはすでに皆さんどんなものかは理解していると思いますのでザックリ説明します。
そもそもRESTというのはそもそもRepresentational State Transferの略称でWEB APIの仕様の土台となる基本的な考え方で、所謂アーキテクチャスタイルです。

続いてAPIというのはGUIを使用しないインタラクティブなソフトウェアアプリケーションである、Application Programming interface の略称です。
WEB開発では通常RESTful APIについて示す際はWEBサービスを対象としています。
アプリケーションの一部をサードパーティへ公開する手法としてRESTful APIは一般的なものになりました。

WEBサービスはXMLやJSONといった共通の形式を使用する事でデータベースに格納している情報を利用できるようになるという点でデータ指向することができます。
これにより、サードパーティ製のアプリはデータベースに直接接続せずデータを操作することができます。

DjangoにおいてもRESTful APIを利用・提供することがどちらも可能です。
この投稿ではDjangoでRESTful APIを利用する方法について紹介します。

もしDjangoでAPIを提供したい場合は Django REST framework をオススメします。
このフレームワークを活用するととても簡単にAPIを公開することができます。
実際にAPIを用意し公開する方法については今度改めて投稿しようと思います。

留意すべき点

DjangoアプリでサードパーティのREST APIを使用する場合、いくつかの点に留意する必要があります。

速度は遅くなる可能性がある
既存の処理に追加でHTTPリクエストがサーバ側で実行されるためある程度は慎重に実装する必要があります。 キャッシュを活用しパフォーマンスを保証する必要があるでしょう。

突然動かなくなる可能性がある
サードパーティのAPIを使用する場合、予告なしで停止したり仕様の変更などが発生する可能性があります。
よって例外処理を用意しておくなどの対策をしておいた方が無難です。

実行に制限がある場合がある
ほとんどのAPIプロパイダーは1時間あたりの要求可能数に制限を掛けています。 制限内容は千差万別ですが、実装時にはこの制限を考慮する必要があります。
キャッシュはこのレート制限への対応策としても機能します。

認証が必要な場合もある
一部のAPIでは認証が必要となることもあります。
この認証情報は当たり前ですがPublicなリポジトリなどに公開してしまわないようにして下さい。

Python用のクライアントがあるか確認する
基本的にPythonクライアントを使用してAPIへアクセスすることをお勧めします。
この方が認証プロセスやそのリソースの使用が簡単です。 Pythonクライアントが用意されているか最初に確認しておきましょう。

ドキュメントの存在が命綱
APIの仕様をまとめたドキュメントが存在しなければ殆ど使い物になりません。
ドキュメントがAPIを使用する上での唯一のガイダンスだからです。
オープンソースのAPIでもない限りGoogleで検索しても適切な情報を手に入れるのは困難です。
従って実装する前にAPIプロパイダが信頼性の高い(更新が適切にされている)ドキュメントを公開しているか確認しましょう。


実装方法

Public APIのいくつかを実際に実装していきます。
その中でAPIが提供している機能などについても紹介します。
この投稿の最後に今回作成したコードをGitHubへアップしていますので試してみてください。

まずはrequestsを導入しておきましょう。

pip install requests

ipstack API (旧GEO API)

最初の例ではipstack APIを使用してみましょう。 このAPIはIPアドレスに基づいて国やタイムゾーン、緯度経度などの地理的情報を提供しています。
APIへのアクセスキーが必要となりますので先ほどの公式ページでサインアップをしてください。
無料プランがあり、特にメールなどの認証も必要ありません。

サインアップが完了するとクイックスタートページにリダイレクトされます。
ここに表示されているAPIアクセスキーを使用していきます。

※ 表示されているKeyは既にリセットを掛け無効化しています。


RESTful APIの良いところは殆どの場合ブラウザで直接エンドポイントを開くことで気軽にデバッグできることです。
認証が必要な場合もcURLなどのツールを使用してターミナルから簡単に行うことができます。

それではまずはブラウザでエンドポイントを開いてみましょう。
YOUR_ACCESS_KEYの部分をご自身の取得したアクセスキーに置き換えてください。

http://api.ipstack.com/check?access_key=YOUR_ACCESS_KEY

上述したエンドポイントへのアクセスではあなたのIPアドレス情報が返されます。
それではこれをPythonで再現してみましょう。

urls.py

from django.urls import path
from . import views


urlpatterns = [
    path('', views.home, name="home"),
]

views.py

def home(request):
    response = requests.get('http://api.ipstack.com/check?access_key=c4cd904aa739fe6671677ef2415477b9')
    geodata = response.json()
    return render(request, 'apisumple/home.html', {
        'ip': geodata['ip'],
        'country': geodata['country_name']
    })

上記のviewsではエンドポイントに GETリクエストを実行しgeodata変数にJSON 形式のデータを読みます。今回はテンプレートに直接その中から必要な情報のみを渡します。

home.html

{% extends 'base.html' %}

{% block title %}Django Samples | Home{% endblock %}

{% block page_description %}
    <h2>Django Sumples Home</h2>
{% endblock page_description %}

{% block content %}
    <p>あなたのIPアドレスは <strong>{{ ip }}</strong> で、 あなたは恐らく今 <strong>{{ country }}</strong> に居ます。</p>
{% endblock %}

結果はこのようにレンダリングされました。

さて、ipstack APIから取得できた情報は様々なことに活用できます。
例として今回はGoogleマップのAPIを使用し現在地を画面にiframeで表示してみましょう。

注意: あくまでもこれはサンプルです! 各種APIキーをソースコードに直接記述したりPublicリポジトリに公開したりしないでください。

views.py

def home(request):
    IPSTACK_KEY = 'c4cd904aa739fe6671677ef2415477b9'
    response = requests.get('http://api.ipstack.com/check?access_key={}'.format(IPSTACK_KEY))
    geodata = response.json()
    ip_address = geodata['ip']

    response = requests.get('http://api.ipstack.com/{0}?access_key={1}'.format(ip_address, IPSTACK_KEY))
    geodata = response.json()
    return render(request, 'apisumple/home.html', {
        'ip': geodata['ip'],
        'country': geodata['country_name'],
        'latitude': geodata['latitude'],
        'longitude': geodata['longitude'],
        'api_key': 'AIzaSyAB5W4YymfLVlozLFWMQPJ8Gi--XcysIOA' # 本来は埋め込みNG!!
    })

home.html

{% extends 'base.html' %}

{% block title %}Django Samples | Home{% endblock %}

{% block page_description %}
    <h2>Django Sumples Home</h2>
{% endblock page_description %}

{% block content %}
    <p>あなたのIPアドレスは <strong>{{ ip }}</strong> で、 あなたは恐らく今 <strong>{{ country }}</strong> に居ます。</p>

    <iframe width="600"
            height="450"
            frameborder="0"
            style="border:0"
            src="https://www.google.com/maps/embed/v1/view?center={{ latitude }},{{ longitude }}&zoom=8&key={{ api_key }}"
            allowfullscreen></iframe>
{% endblock %}

このようにうまく表示されました。
VPNなどを使用してIPアドレスを変更すれば表示される位置情報もブラウザをリロードするたび変更されます。

キャッシュの利用

前項で作成したipstack API + Googleマップの例ではhomeへのアクセスやページリロードをしてからレンダリングされるまでに少し時間が掛かります。
これはGETされるたびにAPIサーバへのリクエストを都度実行しているためです。

今回のケースでは、IPアドレス及びそこに紐づく地理情報はVPNなどで意図的に変更しない限り一般的に短時間で大きな変更はされません。
そこで一度取得した地理情報をキャッシュしておくことが出来ます。

この場合、全てのページ訪問者がそれぞれ異なった結果(IPアドレスと地理情報)を持っている可能性が高いのでセッションに結果を含ませるアプローチをとってみましょう。

views.py

def home(request):
    is_cached = ('geodata' in request.session)

    if not is_cached:
        IPSTACK_KEY = 'c4cd904aa739fe6671677ef2415477b9'
        response = requests.get('http://api.ipstack.com/check?access_key={}'.format(IPSTACK_KEY))
        result = response.json()
        response = requests.get('http://api.ipstack.com/{0}?access_key={1}'.format(result['ip'], IPSTACK_KEY))
        request.session['geodata'] = response.json()

    geodata = request.session['geodata']

    return render(request, 'apisumple/home.html', {
        'ip': geodata['ip'],
        'country': geodata['country_name'],
        'latitude': geodata['latitude'],
        'longitude': geodata['longitude'],
        'api_key': 'AIzaSyAB5W4YymfLVlozLFWMQPJ8Gi--XcysIOA', # 本来は埋め込みNG!!
        'is_cached': is_cached,
    })

home.html

{% extends 'base.html' %}

{% block title %}Django Samples | Home{% endblock %}

{% block page_description %}
    <h2>Django Sumples Home</h2>
{% endblock page_description %}

{% block content %}
    <p>あなたのIPアドレスは <strong>{{ ip }}</strong> で、 あなたは恐らく今 <strong>{{ country }}</strong> に居ます。</p>
    <p>このページは現在キャッシュされていま{{ is_cached|yesno:"す,せん" }}。 </p>
    <iframe width="600"
            height="450"
            frameborder="0"
            style="border:0"
            src="https://www.google.com/maps/embed/v1/view?center={{ latitude }},{{ longitude }}&zoom=8&key={{ api_key }}"
            allowfullscreen></iframe>
{% endblock %}

初回アクセス時は 「このページは現在キャッシュされていません。」と表示されます。
リロードなどを再アクセスすると「このページは現在キャッシュされています。」と表示されるようになります。

API の結果をキャッシュするのにはいくつかの方法があります。Memcached または Redis のような適切な仕組みが必須という意味ではありません。
Djangoのデフォルトではデータベース内にセッションでキャッシュできますが、採用するかどうかはユース ケースに依存します。
それぞれの手段は常にトレードオフの関係です。

GitHub Public API

GitHubが提供しているAPIを使用してみましょう。
いくつかの機能は認証を必要としていませんが、レート制限が非常に低く設定されています。

こちら から公式ドキュメントを確認できます。
GitHubのユーザーを検索するシンプルなアプリを作ってみましょう。

まずはドキュメントを確認します。

マニュアルを見ることでエンドポイントを知ることができます。
ユーザー取得のエンドポイントは GET /users/:username でした。

早速ブラウザからエンドポイントを開いてみましょう。

GitHubのユーザー名を渡すことで紐付けられている情報が取得できていますね。
青く囲った “name" , “public_repos" を今回は使用します。 ではこれをDjangoアプリにしてみましょう。

urls.py

from django.urls import path
from . import views


urlpatterns = [
    path('github/', views.github, name='github'),
]

views.py

from django.shortcuts import render
import requests

def github(request):
    user = {}
    if 'username' in request.GET:
        username = request.GET['username']
        url = 'https://api.github.com/users/%s' % username
        response = requests.get(url)
        user = response.json()
    return render(request, 'apisumple/github_api.html', {'user': user})

github_api.html

{% extends 'base.html' %}

{% block content %}
    <form method="get">
        <input type="text" name="username">
        <button class="btn btn-primary btn-flat" type="submit">GitHubを検索</button>
    </form>
    {% if user %}
        <p>
            <strong>{{ user.name }}</strong> はPublicリポジトリを<strong>{{ user.public_repos }}</strong> 個保有しています!
        </p>
    {% endif %}

{% endblock %}

CSSが効いてしまっていますがこのように動作します。

しかし、このままでは存在しないユーザー名を送信したとき"name"None"public_repos"0 が帰ってきてしまいます。
そこでレスポンスをチェックしてユーザーが存在しなかった場合の処理を作成してみます。

views.py

def github(request):
    search_result = {}
    if 'username' in request.GET:
        username = request.GET['username']
        url = 'https://api.github.com/users/%s' % username
        response = requests.get(url)
        search_was_successful = (response.status_code == 200)
        search_result = response.json()
        search_result['success'] = search_was_successful
        search_result['rate'] = {
            'limit': response.headers['X-RateLimit-Limit'],
            'remaining': response.headers['X-RateLimit-Remaining'],
        }
    return render(request, 'apisumple/github_api.html', {'search_result': search_result})

github_api.html

{% extends 'base.html' %}

{% block title %}Django Samples | GitHub API{% endblock %}

{% block page_description %}
    <h2>GitHub API Sumple</h2>
{% endblock page_description %}

{% block content %}
    <form method="get">
        <input type="text" name="username">
        <button type="submit">GitHubを検索</button>
    </form>
    {% if search_result %}
        {% if search_result.success %}
            <p>
                <strong>{{ search_result.name|default_if_none:search_result.login }}</strong> はPublicリポジトリを<strong>{{ search_result.public_repos }}</strong> 個保有しています!
            </p>
        {% else %}
            <p><em>{{ search_result.message }}</em></p>
        {% endif %}
        <p>API 実行可能数: {{ search_result.rate.remaining }}/{{ search_result.rate.limit }}</p>
    {% endif %}

{% endblock %}

これで存在しないユーザー名が送信された時にGitHubからのメッセージを表示できました。
また、今回APIのレート制限を取得し表示もしています。
制限を超えてしまった際は以下のようにメッセージが返ります。

クライアントを使用したGitHub API

先ほどの例ではGitHub APIを直接叩きにいきましたが、APIクライアントを介して行うこともできます。
APIクライアントは特定のプログラミング言語で実装されたライブラリであり、アプリケーションとサードパーティ製アプリケーションを繋ぐ橋渡しのような存在です。

GitHub APIにおけるオススメのPythonクライアントはPyGitHubです。
pip を使用してインストールすることができます。

pip install PyGithub

今度はクライアントを使用して先ほどと全く同じ処理を再現してみましょう。

urls.py

from django.urls import path
from . import views


urlpatterns = [
    path('github/', views.github, name='github'),
    path('github-client/', views.github_client, name='github-client'), # 追加
]

views.py

def github_client(request):
    search_result = {}
    if 'username' in request.GET:
        username = request.GET['username']
        client = Github()

        try:
            user = client.get_user(username)
            search_result['name'] = user.name
            search_result['login'] = user.login
            search_result['public_repos'] = user.public_repos
            search_result['success'] = True
        except GithubException as ge:
            search_result['message'] = ge.data['message']
            search_result['success'] = False

        rate_limit = client.get_rate_limit()
        search_result['rate'] = {
            'limit': rate_limit.rate.limit,
            'remaining': rate_limit.rate.remaining,
        }

    return render(request, 'apisumple/github_api.html', {'search_result': search_result})

先ほどとの大きな違いは、get_user()get_rate_limit()を使用してリクエストを行っていることです。
テンプレートは先ほどの物をそのまま使用しています。

APIクライアントはユーザー名とパスワードまたはトークンを使用し認証する簡単なインターフェイスなども提供されています。

from github import Github

client = Github('user', 'password')

# もしくはトークンを使用した認証の場合
client = Github('access_token')

まとめ

この投稿ではDjangoでサードパーティ製 API をいくつか試してみました。
GitHubに有志がまとめている素晴らしいPublic APIのリストがあります。 自身のアプリケーションに組み込みたい機能を探すのに役立つと思います。

ソースコードはGitHubへアップしてあります、https://github.com/mila411/django-restful-api/を参照してください。

ハリネズミかわいい!