Python | 1 日 120 分 × 7 日アプリ学習:API取得アプリ(requests使用)(中級編)

Web APP Python
スポンサーリンク

5日目のゴール

5日目のテーマは
「“本物のAPI”を意識して、API層とアプリ層をきちんと分けて書く」 ことです。

ここまでであなたはすでに、
URLにアクセスしてJSONを受け取り、エラー処理を入れ、ネストしたJSONも読めるようになりました。

5日目では一歩進んで、

APIを叩くコード(API層)と、
それを使うアプリ側のコード(アプリ層)を分けて設計すること。

そして、「人間に見せるメッセージ」と「開発者向けの情報」を意識して書き分けること。
この二つをテーマにします。


API層とアプリ層を分ける、という考え方

「requests を直接あちこちで呼ばない」発想

小さなスクリプトなら、どこでも requests.get(...) を書いても動きます。
でも、アプリになってくると、それはだんだん「散らかったコード」になります。

そこで大事になるのが、

APIを叩く部分を「専用の関数(あるいはモジュール)」に閉じ込める
アプリ側は「その関数を呼んで結果を使うだけ」にする

という分離です。

イメージとしては、

外部APIと話すのは「通訳(API層)」
アプリ本体は「通訳に“こういう情報が欲しい”と頼むだけ」

という役割分担です。

この分け方ができると、

APIのURLが変わった
タイムアウトを変えたい
ヘッダーを追加したい

といった変更があっても、
API層だけを直せば済むようになります。


まずは「APIクライアントモジュール」を作る

request_json を“外部API専用の窓口”にする

5日目では、request_json を「API層の入口」として使います。
ここに、requests とエラー処理を全部閉じ込めます。

# api_client.py

import requests

def request_json(method, url, params=None, json_body=None, headers=None, timeout=5.0):
    try:
        response = requests.request(
            method=method,
            url=url,
            params=params,
            json=json_body,
            headers=headers,
            timeout=timeout,
        )
    except requests.exceptions.Timeout:
        # 通信レベルのエラー
        return {
            "ok": False,
            "error_type": "timeout",
            "status_code": None,
            "message": "タイムアウトしました。サーバーの応答がありません。",
        }
    except requests.exceptions.RequestException as e:
        return {
            "ok": False,
            "error_type": "network",
            "status_code": None,
            "message": f"通信エラーが発生しました: {e}",
        }

    if not (200 <= response.status_code < 300):
        return {
            "ok": False,
            "error_type": "http",
            "status_code": response.status_code,
            "message": f"サーバーからエラー応答が返されました。(ステータスコード: {response.status_code})",
        }

    try:
        data = response.json()
    except ValueError:
        return {
            "ok": False,
            "error_type": "json",
            "status_code": response.status_code,
            "message": "レスポンスをJSONとして解釈できませんでした。",
        }

    return {
        "ok": True,
        "error_type": None,
        "status_code": response.status_code,
        "data": data,
    }
Python

ここでの重要ポイントを深掘りします。

この関数は「例外を外に投げない」ようにしています。
代わりに、成功かどうかを ok というフラグで返しています。
失敗した場合は、error_typemessage に情報を詰めています。

つまり、アプリ側は try / except を書かなくても、

戻り値の ok を見て分岐するだけでよくなる

という設計です。

これが「API層でエラーを吸収して、アプリ層には“結果”として渡す」という考え方です。


アプリ層から見たときの「きれいな顔」を作る

「このアプリで何がしたいか」に名前を付ける

例えば、「ユーザー一覧を取得して表示したい」アプリを考えます。
アプリ層から見たときに、こんな関数があると気持ちいいです。

users = fetch_users()
Python

あるいは、

todo = fetch_todo(todo_id)
Python

ここで大事なのは、
アプリ層は「外部APIのURL」を知らなくていい、ということです。

URLやパラメータは、API層の中に閉じ込めます。

# api_service.py

from api_client import request_json

BASE_URL = "https://jsonplaceholder.typicode.com"

def fetch_users():
    url = f"{BASE_URL}/users"
    result = request_json("GET", url)

    if not result["ok"]:
        return None, result["message"]

    return result["data"], None

def fetch_todo(todo_id: int):
    url = f"{BASE_URL}/todos/{todo_id}"
    result = request_json("GET", url)

    if not result["ok"]:
        return None, result["message"]

    return result["data"], None
Python

ここでのポイントは二つです。

一つ目は、BASE_URL を一か所にまとめていること。
二つ目は、「成功したらデータとエラーなし」「失敗したら None とエラーメッセージ」という形で返していることです。

アプリ層から見ると、こうなります。

from api_service import fetch_users

users, error = fetch_users()
if error is not None:
    print("ユーザー取得に失敗:", error)
else:
    print("ユーザー数:", len(users))
Python

アプリ側は、
「このアプリで何をしたいか」に集中できていて、
HTTPだのJSONだのは意識しなくて済んでいます。


「人間向けメッセージ」と「開発者向け情報」を分ける

エラーの中身をどう扱うか

request_json の中では、
error_typestatus_code など、わりと細かい情報を持っています。

でも、アプリのユーザーに見せたいのは、
もっとシンプルなメッセージです。

例えば、

「ネットワークエラーが発生しました。時間をおいて再度お試しください。」
「サーバーからエラー応答が返されました。(ステータスコード: 500)」

などです。

一方で、開発者としては、
ログにはもう少し詳しい情報を残しておきたいこともあります。

5日目では、まずはシンプルに、

ユーザーには message をそのまま見せる
開発者向けには、必要なら error_typestatus_code を print する

くらいの分け方で十分です。

from api_service import fetch_todo

def main():
    todo_id_str = input("TODO ID を入力してください: ").strip()
    if not todo_id_str.isdigit():
        print("数字を入力してください。")
        return

    todo_id = int(todo_id_str)
    todo, error = fetch_todo(todo_id)

    if error is not None:
        print("取得に失敗しました。")
        print("詳細:", error)  # ユーザー向けメッセージ
        return

    print("ID:", todo["id"])
    print("タイトル:", todo["title"])
    print("完了:", todo["completed"])

if __name__ == "__main__":
    main()
Python

ここでの本質は、

「エラーの詳細をどこまで誰に見せるか」を意識すること

です。

GUIアプリになったときには、
このメッセージをラベルやダイアログに出すだけで、
「ちゃんとしたアプリ感」が一気に増します。


5日目の例題:ユーザー検索コンソールアプリ

仕様を言葉で整理する

ユーザー一覧APIを叩く。
名前に特定の文字列を含むユーザーだけを抽出する。
抽出結果を整形して表示する。
APIエラーが起きても落ちずにメッセージを出す。

API層とアプリ層を分けて書きます。


api_client.py(共通クライアント)

import requests

def request_json(method, url, params=None, json_body=None, headers=None, timeout=5.0):
    try:
        response = requests.request(
            method=method,
            url=url,
            params=params,
            json=json_body,
            headers=headers,
            timeout=timeout,
        )
    except requests.exceptions.Timeout:
        return {
            "ok": False,
            "error_type": "timeout",
            "status_code": None,
            "message": "タイムアウトしました。サーバーの応答がありません。",
        }
    except requests.exceptions.RequestException as e:
        return {
            "ok": False,
            "error_type": "network",
            "status_code": None,
            "message": f"通信エラーが発生しました: {e}",
        }

    if not (200 <= response.status_code < 300):
        return {
            "ok": False,
            "error_type": "http",
            "status_code": response.status_code,
            "message": f"サーバーからエラー応答が返されました。(ステータスコード: {response.status_code})",
        }

    try:
        data = response.json()
    except ValueError:
        return {
            "ok": False,
            "error_type": "json",
            "status_code": response.status_code,
            "message": "レスポンスをJSONとして解釈できませんでした。",
        }

    return {
        "ok": True,
        "error_type": None,
        "status_code": response.status_code,
        "data": data,
    }
Python

api_service.py(このアプリ専用のAPI層)

from api_client import request_json

BASE_URL = "https://jsonplaceholder.typicode.com"

def fetch_users():
    url = f"{BASE_URL}/users"
    result = request_json("GET", url)

    if not result["ok"]:
        return None, result["message"]

    return result["data"], None
Python

app.py(アプリ層:ユーザー検索ロジック)

from api_service import fetch_users

def search_users_by_name(keyword: str):
    users, error = fetch_users()
    if error is not None:
        return None, error

    keyword_lower = keyword.lower()
    matched = []

    for user in users:
        name = user["name"]
        if keyword_lower in name.lower():
            matched.append(user)

    return matched, None

def main():
    print("ユーザー検索アプリ(名前に含まれる文字列で検索)")
    keyword = input("検索キーワードを入力してください: ").strip()

    if keyword == "":
        print("キーワードが空です。終了します。")
        return

    results, error = search_users_by_name(keyword)

    if error is not None:
        print("検索に失敗しました。")
        print("詳細:", error)
        return

    if not results:
        print("該当するユーザーはいませんでした。")
        return

    print(f"{len(results)} 件ヒットしました。")
    for user in results:
        print(f"- {user['id']}: {user['name']} ({user['email']})")

if __name__ == "__main__":
    main()
Python

この三つのファイルで、5日目のテーマが全部入っています。

APIクライアント(requests とエラー処理)は api_client.py に閉じ込められている。
どのURLを叩くか、どのデータを返すかは api_service.py が担当している。
アプリ本体(検索ロジックと入出力)は app.py にだけ書かれている。


「APIキーが必要なAPI」を見据えた設計の話

今日はコードには書かないけれど、考え方だけ

多くの実用的なAPIは、「APIキー」や「トークン」が必要です。
それらはたいてい、ヘッダーかクエリパラメータで送ります。

例えば、ヘッダーならこんな感じです。

headers = {
    "Authorization": "Bearer YOUR_API_TOKEN",
}
Python

5日目で作った request_json は、すでに headers 引数を受け取れる形になっています。
つまり、APIキーが必要なAPIに切り替えても、

api_service.py の中でヘッダーを用意して
request_json(..., headers=headers) と渡すだけ

で対応できます。

ここでの本質は、

APIキーやURLのような「環境依存の情報」は、
アプリのあちこちに書かず、API層に閉じ込める

という設計の感覚です。


5日目で絶対に押さえてほしい本質

「requests を直接叩く人」から「API層を設計する人」へ

今日いちばん大事なのは、
頭の中の構造がこう変わることです。

今までは、

思いついたところで requests.get(...) を書く

だったのが、これからは、

外部APIと話すのは api_client(共通クライアント)
このアプリで必要なAPIは api_service がまとめる
アプリ本体は「fetch_xxx」を呼んで結果を使うだけ

という三層構造で考えられるようになることです。

技術的なキーワードをまとめると、

request_json で「成功かどうか」と「エラー情報」をまとめて返す
API層では「URL・メソッド・パラメータ・ヘッダー」を決める
アプリ層では「何をしたいか」に名前を付けた関数(fetch_users など)を呼ぶ
ユーザー向けメッセージと開発者向け情報を意識して分ける

ここまで来たあなたは、
もう「APIを叩ける人」ではなく、
「APIをアプリの中にきれいに組み込める人」 になりつつあります。

6日目・7日目では、
このAPI層と、これまで学んだ tkinter のGUIを組み合わせて、
「ボタンを押したらAPIからデータを取ってきて、画面に表示する」
という、いよいよ“中級者らしいAPI取得アプリ”を形にしていきましょう。

タイトルとURLをコピーしました