Package: aws-error-utils
aws-error-utilsMaking botocore.exceptions.ClientError easier to deal withAll AWS service exceptions are raised byboto3as abotocore.exceptions.ClientError, with the contents of the exception indicating what kind of exception happened.
This is not very pythonic, and the contents themselves are rather opaque, most being held in dicts rather than as properties.
The functions in this package help dealing with that, to make your code less verbose and require less memorization ofClientErrorcontents.InstallationThe package is on PyPIfor pip-installing, but you can also just copy theaws_error_utils.pyfileinto your project (note that the standalone file requires Python 3.7 or greater).UsageIf you've got code like this:importboto3,botocore.exceptionss3=boto3.client('s3')try:s3.get_object(Bucket='my-bucket',Key='example')exceptbotocore.exceptions.ClientErroraserror:iferror.response['Error']['Code']=='NoSuchBucket':print(error.response['Error']['Message'])# error handlingelse:raiseyou can replace it with:importboto3fromaws_error_utilsimporterrorss3=boto3.client('s3')try:s3.get_object(Bucket='my-bucket',Key='example')excepterrors.NoSuchBucketaserror:# or# except catch_aws_error('NoSuchBucket') as error:print(error.message)# error handlingIf you have trouble remembering where all the contents inClientErrorare, like these:client_error.response['Error']['Code']client_error.response['Error']['Message']client_error.response['ResponseMetadata']['HTTPStatusCode']client_error.operation_nameyou can replace it with:importboto3fromaws_error_utilsimportget_aws_error_infoerr_info=get_aws_error_info(client_error)err_info.codeerr_info.messageerr_info.http_status_codeerr_info.operation_nameIf you're usingerrorsorcatch_aws_error(), you can skip theget_aws_error_info()step, because the fields are set directly on theClientErrorobject:importboto3fromaws_error_utilsimporterrorss3=boto3.client('s3')try:s3.get_object(Bucket='my-bucket',Key='example')excepterrors.NoSuchBucketaserror:error.codeerror.messageerror.http_status_codeerror.operation_nameAdditionally, when youdoneed to useClientError, it's imported byaws_error_utilsso you can just useaws_error_utils.ClientErrorrather thanbotocore.exceptions.ClientError(the same is true forBotoCoreError, the base class of all non-ClientErrorexceptions).errorsIt's easiest to use theerrorsclass if you don't have complex conditions to match.
Using the error code as a field name in anexceptblock will match that error code.
Additionally, when you use this style, it sets the fields fromAWSErrorInfo(see below) directly on theClientErrorobject.
For example:importboto3fromaws_error_utilsimporterrorss3=boto3.client('s3')try:s3.get_object(Bucket='my-bucket',Key='example')excepterrors.NoSuchBucketaserror:print(error.message)# error handlingYou can include multiple error codes in anexceptstatement, though this is slower than combining them with a singlecatch_aws_error()call.importboto3fromaws_error_utilsimporterrorss3=boto3.client('s3')try:s3.get_object(Bucket='my-bucket',Key='example')except(errors.NoSuchBucket,errors.NoSuchKey)aserror:print(error.message)# error handlingYou can catch allClientErrors witherrors.ALL.You can only use this style for error codes that work as Python property names.
For error codes like EC2'sInvalidInstanceID.NotFound, you have to usecatch_aws_error()(see below).Unfortunately, you cannot get tab completion for error codes on theerrorsclass, as a comprehensive list of error codes is not available as a Python package (botocorehas a small number, but they are few and incomplete).Note that the value oferrors.NoSuchBucketis not an exception class representing theNoSuchBucketerror, it is an alias forcatch_aws_error('NoSuchBucket').
It can only be used in anexceptstatement; it will raiseRuntimeErrorotherwise.
You also cannot instantiate theerrorsclass.catch_aws_error()The function takes as input error code(s), and optionally operation name(s), to match against the current raised exception. If the exception matches, theexceptblock is executed.
If your error handling still needs the error object, you can still use anasexpression, otherwise it can be omitted (justexcept catch_aws_error(...):).
Additionally,catch_aws_error()sets the fields fromAWSErrorInfo(see below) directly on theClientErrorobject.importboto3fromaws_error_utilsimportcatch_aws_errors3=boto3.client('s3')try:s3.get_object(Bucket='my-bucket',Key='example')exceptcatch_aws_error('NoSuchBucket')aserror:print(error.message)# error handlingYou can provide error codes either as positional args, or as thecodekeyword argument with either as a single string or a list of strings.catch_aws_error('NoSuchBucket')catch_aws_error(code='NoSuchBucket')catch_aws_error('NoSuchBucket','NoSuchKey')catch_aws_error(code=['NoSuchBucket','NoSuchKey'])If there are multiple API calls in thetryblock, and you want to match against specific ones, theoperation_namekeyword argument can help.
Similar to thecodekeyword argument, the operation name(s) can be provided as either as a single string or a list of strings.importboto3fromaws_error_utilsimportcatch_aws_errortry:s3=boto3.client('s3')s3.list_objects_v2(Bucket='bucket-1')s3.get_object(Bucket='bucket-2',Key='example')exceptcatch_aws_error('NoSuchBucket',operation_name='GetObject')aserror:# This will be executed if the GetObject operation raises NoSuchBucket# but not if the ListObjects operation raises itYou must provide an error code.
To match exclusively against operation name, use theaws_error_utils.ALL_CODEStoken.
For completeness, there is also anALL_OPERATIONStoken.importboto3fromaws_error_utilsimportcatch_aws_errortry:s3=boto3.client('s3')s3.list_objects_v2(Bucket='bucket-1')s3.get_object(Bucket='bucket-1',Key='example')exceptcatch_aws_error(ALL_CODES,operation_name='ListObjectsV2')ase:# This will execute for all ClientError exceptions raised by the ListObjectsV2 callFor more complex conditions, instead of providing error codes and operation names, you can provide a callable to evaluate the exception.
Note that unlike error codes, you can only provide a single callable.importreimportboto3fromaws_error_utilsimportcatch_aws_error,get_aws_error_infodefmatcher(e):info=get_aws_error_info(e)returnre.search('does not exist',info.message)try:s3=boto3.client('s3')s3.list_objects_v2(Bucket='bucket-1')exceptcatch_aws_error(matcher)ase:# This will be executed if e is a ClientError and matcher(e) returns True# Note the callable can assume the exception is a ClientErrorget_aws_error_info()This function takes a returns anAWSErrorInfoobject, which is acollections.NamedTuplewith the following fields:codemessagehttp_status_codeoperation_nameresponse(the raw response dictionary)If you're not modifying yourexceptstatements to usecatch_aws_error(), this function can be useful instead of remembering exactly how this information is stored in theClientErrorobject.If you're usingcatch_aws_error(), this function isn't necessary, because it sets these fields directly on theClientErrorobject.aws_error_matches()This is the matching logic behindcatch_aws_error().
It takes aClientError, with the rest of the arguments being error codes and operation names identical tocatch_aws_error(), except that it does not support providing a callable.make_aws_error()For testing, you can createClientErrors with themake_aws_error()function, which takes the following arguments:code(required)message(required)operation_name(required)http_status_coderesponseIf aresponsedict is provided, it will be copied rather than modified.