requestsライブラリを使わず ‘botocore.vendored.requests’ を置き換える

Boto3のbotocore.vendored.requestsが使えなくなりました。 そこで本記事では、requestsライブラリを追加で導入することなく置き換える方法をご紹介します。
2019.10.27

Pythonでrequestsライブラリを使いたいとき、Boto3のbotocore.vendored.requestsを使っている方もいると思います。

requestsライブラリをデプロイパッケージに含まなくて良いのは楽ちんなのです。 特にLambda関数でrequestsしか使っていない場合とかですね。

しかし、下記にあるように、botocore.vendored.requestsが無くなりました。

そこで本記事では、requestsライブラリを追加で導入することなく置き換える方法をご紹介します。

Lambdaのbotocore.vendored.requestsを置き換える方法

2つの方法があります。

  • requestsライブラリを導入する
    • メリット
      • コードの変更は import部分だけで済む
    • デメリット
      • デプロイフローの変更が必要
        • デプロイパッケージにrequestsを含むようにする
        • ただし、他のライブラリを使用している場合、このデメリットは無い
  • 標準のurllib.requestsを使用する
    • メリット
      • デプロイフローの変更が不要
    • デメリット
      • コードの変更量が多い

本記事では後者を扱います。

ただし、オススメはrequestsライブラリの導入

コードの変更はimportの変更だけで済みます。

また、Lambdaのベストプラクティス的にも、依存関係(ライブラリ等)はデプロイパッケージに含むことがオススメされています。

AWS Lambda 実行環境には、Node.js および Python ランタイムの AWS SDK などのライブラリがいくつか含まれています。最新の機能やセキュリティ更新プログラムを有効にするために、Lambda ではこれらのライブラリを定期的に更新します。この更新に伴って、Lambda 関数の動作が微妙に変わる場合があります。関数で使用する依存関係を完全に制御するには、すべての依存関係をデプロイパッケージでパッケージングすることをお勧めします。

今回のBoto3の変更についても、デプロイパッケージにBoto3(特定バージョン)を含んでおけば、すぐにLambdaで使えなくなることはありません。

標準のurllib.requestsを使用する

公式ドキュメントは下記です。

使い方の例

下記のように使用できます。

http_client.py

import json
import urllib.request

def post(url: str, data: dict, headers:dict ={}):
    req = urllib.request.Request(url,
                                 data=json.dumps(data).encode('utf-8'),
                                 headers=headers,
                                 method='POST')
    return urllib.request.urlopen(req)

def put(url: str, data: dict, headers:dict ={}):
    req = urllib.request.Request(url,
                                 data=json.dumps(data).encode('utf-8'),
                                 headers=headers,
                                 method='PUT')
    return urllib.request.urlopen(req)

def delete(url: str, data: dict, headers:dict ={}):
    req = urllib.request.Request(url,
                                 data=json.dumps(data).encode('utf-8'),
                                 headers=headers,
                                 method='DELETE')
    return urllib.request.urlopen(req)

def get(url: str):
    req = urllib.request.Request(url, method='GET')
    return urllib.request.urlopen(req)

sample.py

import urllib
import http_client as requests

def sample_get():
    try:
        res = requests.get('https://checkip.amazonaws.com/')
    except urllib.error.HTTPError as e:
        print(e.code)
        print(e.reason)
    else:
        print(res.status)
        print(res.read().decode('utf-8'))

def sample_post():
    headers = {
        'Content-Type': 'application/json; charset=utf-8'
    }
    payload = {
        'hoge': 'fuga'
    }
    try:
        res = requests.post('https://xxxx.com', payload, headers)
    except urllib.error.HTTPError as e:
        print(e.code)
        print(e.reason)
    else:
        print(res.status)
        print(res.read().decode('utf-8'))

if __name__ == "__main__":
    sample_get()
    sample_post()

import http_client as requestsとして利用すれば、呼び出し時のI/Fをそのまま利用できるため、少しは楽になります。 (夏目さんに教えていただきました!)

例外処理を書く必要があるため、冗長にならざるを得ません。 このあたりもhttp_client.pyにまとめて、requestsライブラリと同じように使えるようにするとさらに便利かと思います。 (だけどそこまでするなら、素直にrequestsライブラリを使ったほうが良いかもしれない……)

上記を実行してみた結果

次のようになります。(IPアドレスは適当に変更しています)

$ python sample.py
200
111.111.111.111

403
Forbidden

さいごに

HTTP通信しかしないような単純なLambdaであれば、簡単に利用できそうですね。 (Slack通知するだけのLambdaとか)

参考