[Python] Boto3における例外クラスの同一性について

こんにちは。サービスグループの武田です。Boto3を利用した処理の例外処理を書いていて少しハマったので共有します。
2022.06.14

こんにちは。サービスグループの武田です。

Boto3を利用した処理の例外処理を書いていて少しハマったので共有します。

さていきなりですが、次のコードの実行結果を予想できるでしょうか。四択問題です。

import boto3

c1 = boto3.client("iam")
c2 = boto3.client("iam")

print(c1.exceptions.NoSuchEntityException == c2.exceptions.NoSuchEntityException)

s = boto3.Session()
c3 = s.client("iam")

print(c1.exceptions.NoSuchEntityException == c3.exceptions.NoSuchEntityException)

True/True と予想した人が多いでしょうか?少なくとも私はそう思っていました。

正解は True/False です。なんとclientを生成する元のオブジェクト(boto3やSession)が異なると例外クラスも別物となるようです。そのため次のようなコードは意図した挙動となりません。例外をキャッチできないのです。

import boto3


def aws_op():
    s = boto3.Session()
    iam = s.client("iam")
    iam.get_role(RoleName="not-exists-role")


iam = boto3.client("iam")

try:
    aws_op()
except iam.exceptions.NoSuchEntityException as e:
    print(e)

そのため、発生する例外の親クラスであるClientErrorをキャッチするよう、例外処理の部分を次のように書き換えることで意図した動作となります。具体的にどの例外が投げられたのかはCodeを見ることで分岐させます。

try:
    aws_op()
except iam.exceptions.ClientError as e:
    if e.response["Error"]["Code"] == "NoSuchEntity":
        print(e)
    else:
        raise e

または、例外クラスはbotocoreパッケージから参照しても問題ありません。ドキュメントなどではこちらの書き方をよく見ます。ただVSCodeの拡張でPylanceというLinterがあるのですが、この書き方をするとエラーが出てちょっと困ります(コメントで抑制は可能)。

try:
    aws_op()
except botocore.exceptions.ClientError as e:
    if e.response["Error"]["Code"] == "NoSuchEntity":
        print(e)
    else:
        raise e

ちなみに次のように同一オブジェクトを利用できるのであれば、わざわざCodeによる分岐は不要です。スッキリ書けてよいですね。

import boto3


def aws_op():
    s = boto3.Session()
    iam = s.client("iam")

    try:
        iam.get_role(RoleName="not-exists-role")
    except iam.exceptions.NoSuchEntityException as e:
        print(e)

aws_op()

まとめ

例外を投げる処理と同じ場所でキャッチするのであれば、直接その例外を書いてキャッチする書き方が好みです。一方で、離れた場所でキャッチする場合はClientErrorでのキャッチが必要です。またいつでも同じ書き方をしたいということであれば、ClientErrorで書いておけば問題ありません。

それでは、よいAWSライフを。

参考URL