我试图弄清楚如何使用boto3进行正确的错误处理。

我正在尝试创建一个IAM用户:

def create_user(username, iam_conn):
    try:
        user = iam_conn.create_user(UserName=username)
        return user
    except Exception as e:
        return e

当调用create_user成功时,我得到一个整洁的对象,其中包含API调用的http状态代码和新创建用户的数据。

例子:

{'ResponseMetadata': 
      {'HTTPStatusCode': 200, 
       'RequestId': 'omitted'
      },
 u'User': {u'Arn': 'arn:aws:iam::omitted:user/omitted',
           u'CreateDate': datetime.datetime(2015, 10, 11, 17, 13, 5, 882000, tzinfo=tzutc()),
           u'Path': '/',
           u'UserId': 'omitted',
           u'UserName': 'omitted'
          }
}

这很有效。但是当这个失败时(比如如果用户已经存在),我只得到一个botocore.exceptions.ClientError类型的对象,其中只有文本告诉我哪里出错了。

例子: ClientError('调用CreateUser操作时发生错误(EntityAlreadyExists):省略名称的用户已经存在。')

这(AFAIK)使得错误处理非常困难,因为我不能只是打开结果的http状态代码(409用户已经存在根据AWS API文档的IAM)。这让我觉得我一定是做错了什么。最优的方法是boto3永远不抛出异常,但juts总是返回一个反映API调用如何进行的对象。

有没有人能在这个问题上给我一些启发,或者给我指出正确的方向?


当前回答

使用异常中包含的响应。这里有一个例子:

import boto3
from botocore.exceptions import ClientError

try:
    iam = boto3.client('iam')
    user = iam.create_user(UserName='fred')
    print("Created user: %s" % user)
except ClientError as e:
    if e.response['Error']['Code'] == 'EntityAlreadyExists':
        print("User already exists")
    else:
        print("Unexpected error: %s" % e)

异常中的响应字典将包含以下内容:

(“错误”)(“代码”)。'EntityAlreadyExists'或'ValidationError' [' responsemetdata ']['HTTPStatusCode']例:400 [“ResponseMetadata”][' RequestId ']。“d2b06652 - 88 - d7 - 11 - e5 - 99 - d0 - 812348583 - a35” (“错误”)(“信息”)。"发生错误(EntityAlreadyExists)…" ['错误']['类型']。“发送”

有关更多信息,请参阅:

Boto3错误处理 Botocore错误处理

(更新:2018-03-07)

AWS Python SDK已经开始公开客户端上的服务异常(虽然不是资源上的异常),你可以显式地捕获这些异常,所以现在可以像这样编写代码:

import botocore
import boto3

try:
    iam = boto3.client('iam')
    user = iam.create_user(UserName='fred')
    print("Created user: %s" % user)
except iam.exceptions.EntityAlreadyExistsException:
    print("User already exists")
except botocore.exceptions.ParamValidationError as e:
    print("Parameter validation error: %s" % e)
except botocore.exceptions.ClientError as e:
    print("Unexpected error: %s" % e)

不幸的是,目前没有关于这些错误/异常的文档,但你可以得到一个核心错误列表,如下所示:

import botocore
import boto3
[e for e in dir(botocore.exceptions) if e.endswith('Error')]

注意,必须同时导入botocore和boto3。如果你只导入botocore,你会发现botocore没有名为exceptions的属性。这是因为异常被boto3动态地填充到botocore中。

您可以获得以下特定于服务的异常列表(根据需要将iam替换为相关的服务):

import boto3
iam = boto3.client('iam')
[e for e in dir(iam.exceptions) if e.endswith('Exception')]

(更新:2021-09-07)

除了前面提到的客户端异常方法之外,还有一个名为aws-error-utils的第三方帮助包。

其他回答

跟随@armod关于在客户端对象上添加异常的更新。我将展示如何查看为客户端类定义的所有异常。

异常是在使用session.create_client()或boto3.client()创建客户端时动态生成的。它在内部调用方法botocore.errorfactory.ClientExceptionsFactory._create_client_exceptions()并填充客户端。带有构造异常类的异常字段。

所有的类名都在client.exceptions中可用。_code_to_exception字典,所以你可以用下面的代码段列出所有类型:

client = boto3.client('s3')

for ex_code in client.exceptions._code_to_exception:
    print(ex_code)

希望能有所帮助。

当它不能处理问题时,你需要做些什么。现在您返回的是实际的异常。 例如,如果用户已经存在而你想使用它作为get_or_create函数,也许你可以通过返回现有的用户对象来处理这个问题。

try:
    user = iam_conn.create_user(UserName=username)
    return user
except botocore.exceptions.ClientError as e:

    #this exception could actually be other things other than exists, so you want to evaluate it further in your real code.
    if e.message.startswith(
        'enough of the exception message to identify it as the one you want')

        print('that user already exists.')
        user = iam_conn.get_user(UserName=username)
        return user

    elif e.message.some_other_condition:

         #something else
    else:
         #unhandled ClientError
         raise(e)
except SomeOtherExceptionTypeYouCareAbout as e:
    #handle it

# any unhandled exception will raise here at this point.
# if you want a general handler

except Exception as e:
    #handle it.

也就是说,这可能是你的应用程序的问题,在这种情况下,你想要在调用create user函数的代码周围放置异常处理程序并让调用函数决定如何处理它,例如,通过要求用户输入另一个用户名,或任何对你的应用程序有意义的东西。

使用异常中包含的响应。这里有一个例子:

import boto3
from botocore.exceptions import ClientError

try:
    iam = boto3.client('iam')
    user = iam.create_user(UserName='fred')
    print("Created user: %s" % user)
except ClientError as e:
    if e.response['Error']['Code'] == 'EntityAlreadyExists':
        print("User already exists")
    else:
        print("Unexpected error: %s" % e)

异常中的响应字典将包含以下内容:

(“错误”)(“代码”)。'EntityAlreadyExists'或'ValidationError' [' responsemetdata ']['HTTPStatusCode']例:400 [“ResponseMetadata”][' RequestId ']。“d2b06652 - 88 - d7 - 11 - e5 - 99 - d0 - 812348583 - a35” (“错误”)(“信息”)。"发生错误(EntityAlreadyExists)…" ['错误']['类型']。“发送”

有关更多信息,请参阅:

Boto3错误处理 Botocore错误处理

(更新:2018-03-07)

AWS Python SDK已经开始公开客户端上的服务异常(虽然不是资源上的异常),你可以显式地捕获这些异常,所以现在可以像这样编写代码:

import botocore
import boto3

try:
    iam = boto3.client('iam')
    user = iam.create_user(UserName='fred')
    print("Created user: %s" % user)
except iam.exceptions.EntityAlreadyExistsException:
    print("User already exists")
except botocore.exceptions.ParamValidationError as e:
    print("Parameter validation error: %s" % e)
except botocore.exceptions.ClientError as e:
    print("Unexpected error: %s" % e)

不幸的是,目前没有关于这些错误/异常的文档,但你可以得到一个核心错误列表,如下所示:

import botocore
import boto3
[e for e in dir(botocore.exceptions) if e.endswith('Error')]

注意,必须同时导入botocore和boto3。如果你只导入botocore,你会发现botocore没有名为exceptions的属性。这是因为异常被boto3动态地填充到botocore中。

您可以获得以下特定于服务的异常列表(根据需要将iam替换为相关的服务):

import boto3
iam = boto3.client('iam')
[e for e in dir(iam.exceptions) if e.endswith('Exception')]

(更新:2021-09-07)

除了前面提到的客户端异常方法之外,还有一个名为aws-error-utils的第三方帮助包。

或者是类名的比较。

except ClientError as e:
    if 'EntityAlreadyExistsException' == e.__class__.__name__:
        # handle specific error

因为它们是动态创建的,所以永远不能导入类并使用真正的Python捕获它。

如果你不得不处理不友好的日志客户端(CloudWatch logs put-log-events),这是我必须做的,以正确捕获Boto3客户端异常:

try:
    ### Boto3 client code here...

except boto_exceptions.ClientError as error:
    Log.warning("Catched client error code %s",
                error.response['Error']['Code'])

    if error.response['Error']['Code'] in ["DataAlreadyAcceptedException",
                                           "InvalidSequenceTokenException"]:
        Log.debug(
            "Fetching sequence_token from boto error response['Error']['Message'] %s",
            error.response["Error"]["Message"])
        # NOTE: apparently there's no sequenceToken attribute in the response so we have
        # to parse response["Error"]["Message"] string
        sequence_token = error.response["Error"]["Message"].split(":")[-1].strip(" ")
        Log.debug("Setting sequence_token to %s", sequence_token)

这在第一次尝试(空LogStream)和后续尝试时都有效。