Подтвердить что ты не робот

Boto3, python и способы обработки ошибок

Я просто подобрал python как мой язык для написания сценариев, и я пытаюсь понять, как правильно обрабатывать ошибки с помощью 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 завершается успешно, я получаю аккуратный объект, который содержит код состояния HTTP вызова API и данные вновь созданного пользователя.

Пример:

{'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 ("Произошла ошибка (EntityAlreadyExists) при вызове операции CreateUser: пользователь с опущенным именем уже существует. ',)

Это (AFAIK) делает обработку ошибок очень сложной, потому что я не могу просто включить полученный код статуса http (409 для пользователя уже существует в соответствии с документами AWS API для IAM). Это заставляет меня думать, что я должен делать что-то не так. Оптимальным способом было бы boto3 никогда не генерировать исключения, но juts всегда возвращают объект, который отражает способ вызова API.

Может кто-нибудь просветить меня по этому вопросу или указать мне в правильном направлении?

Спасибо большое!

4b9b3361

Ответ 1

Используйте ответ, содержащийся в исключении. Вот пример:

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)

Диктовый ответ в исключении будет содержать следующее:

  • ['Error']['Code'] например, 'EntityAlreadyExists' или 'ValidationError'
  • ['ResponseMetadata']['HTTPStatusCode'] например, 400
  • ['ResponseMetadata']['RequestId'] например, 'd2b06652-88d7-11e5-99d0-812348583a35'
  • ['Error']['Message'] например, "Произошла ошибка (EntityAlreadyExists)..."
  • ['Error']['Type'] например, 'Отправитель'

Для получения дополнительной информации см. Обработка ошибок Botocore.

[Обновлено: 2018-03-07]

AWS Python SDK начал предоставлять сервисные исключения на клиентах (но не на ресурсах), которые вы можете явно перехватить, поэтому теперь можно написать этот код примерно так:

import boto3
from botocore.exceptions import ClientError, ParamValidationError

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 ParamValidationError as e:
    print("Parameter validation error: %s" % e)
except ClientError as e:
    print("Unexpected error: %s" % e)

К сожалению, в настоящее время нет документации для этих исключений.

Ответ 2

Просто обновление проблемы "нет исключений по ресурсам", на которую указывает @jarmod (пожалуйста, не стесняйтесь обновлять свой ответ, если ниже кажется подходящим)

Я проверил приведенный ниже код, и он работает нормально. Он использует "ресурсы" для выполнения client.exceptions, но ловит client.exceptions - хотя он "выглядит" несколько неправильно... он хорошо тестирует, классы исключений показывают и сопоставляют, когда рассматривают использование отладчика во время исключения...

Это может быть применимо не ко всем ресурсам и клиентам, но работает для папок с данными (так называемые сегменты s3).

lab_session = boto3.Session() 
c = lab_session.client('s3') #this client is only for exception catching

try:
    b = s3.Bucket(bucket)
    b.delete()
except c.exceptions.NoSuchBucket as e:
    #ignoring no such bucket exceptions
    logger.debug("Failed deleting bucket. Continuing. {}".format(e))
except Exception as e:
    #logging all the others as warning
    logger.warning("Failed deleting bucket. Continuing. {}".format(e))

Надеюсь это поможет...

Ответ 3

Я нашел очень полезным, поскольку исключения не задокументированы, перечислять все исключения на экране для этого пакета. Вот код, который я использовал для этого:

import botocore.exceptions
def listexns(mod):
    #module = __import__(mod)
    exns = []
    for name in botocore.exceptions.__dict__:
        if (isinstance(botocore.exceptions.__dict__[name], Exception) or
            name.endswith('Error')):
            exns.append(name)
    for name in exns:
        print('%s.%s is an exception type' % (str(mod), name))
    return

if __name__ == '__main__':
    import sys
    if len(sys.argv) <= 1:
        print('Give me a module name on the $PYTHONPATH!')
    print('Looking for exception types in module: %s' % sys.argv[1])
    listexns(sys.argv[1])

Что приводит к:

Looking for exception types in module: boto3
boto3.BotoCoreError is an exception type
boto3.DataNotFoundError is an exception type
boto3.UnknownServiceError is an exception type
boto3.ApiVersionNotFoundError is an exception type
boto3.HTTPClientError is an exception type
boto3.ConnectionError is an exception type
boto3.EndpointConnectionError is an exception type
boto3.SSLError is an exception type
boto3.ConnectionClosedError is an exception type
boto3.ReadTimeoutError is an exception type
boto3.ConnectTimeoutError is an exception type
boto3.ProxyConnectionError is an exception type
boto3.NoCredentialsError is an exception type
boto3.PartialCredentialsError is an exception type
boto3.CredentialRetrievalError is an exception type
boto3.UnknownSignatureVersionError is an exception type
boto3.ServiceNotInRegionError is an exception type
boto3.BaseEndpointResolverError is an exception type
boto3.NoRegionError is an exception type
boto3.UnknownEndpointError is an exception type
boto3.ConfigParseError is an exception type
boto3.MissingParametersError is an exception type
boto3.ValidationError is an exception type
boto3.ParamValidationError is an exception type
boto3.UnknownKeyError is an exception type
boto3.RangeError is an exception type
boto3.UnknownParameterError is an exception type
boto3.AliasConflictParameterError is an exception type
boto3.PaginationError is an exception type
boto3.OperationNotPageableError is an exception type
boto3.ChecksumError is an exception type
boto3.UnseekableStreamError is an exception type
boto3.WaiterError is an exception type
boto3.IncompleteReadError is an exception type
boto3.InvalidExpressionError is an exception type
boto3.UnknownCredentialError is an exception type
boto3.WaiterConfigError is an exception type
boto3.UnknownClientMethodError is an exception type
boto3.UnsupportedSignatureVersionError is an exception type
boto3.ClientError is an exception type
boto3.EventStreamError is an exception type
boto3.InvalidDNSNameError is an exception type
boto3.InvalidS3AddressingStyleError is an exception type
boto3.InvalidRetryConfigurationError is an exception type
boto3.InvalidMaxRetryAttemptsError is an exception type
boto3.StubResponseError is an exception type
boto3.StubAssertionError is an exception type
boto3.UnStubbedResponseError is an exception type
boto3.InvalidConfigError is an exception type
boto3.InfiniteLoopConfigError is an exception type
boto3.RefreshWithMFAUnsupportedError is an exception type
boto3.MD5UnavailableError is an exception type
boto3.MetadataRetrievalError is an exception type
boto3.UndefinedModelAttributeError is an exception type
boto3.MissingServiceIdError is an exception type

Ответ 4

Или сравнение по имени класса, например

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

Поскольку они динамически созданы, вы никогда не сможете импортировать класс и поймать его с помощью реального Python.

Ответ 5

Если вы вызываете API sign_up (AWS Cognito) с использованием Python3, вы можете использовать следующий код.

def registerUser(userObj):
    ''' Registers the user to AWS Cognito.
    '''

    # Mobile number is not a mandatory field. 
    if(len(userObj['user_mob_no']) == 0):
        mobilenumber = ''
    else:
        mobilenumber = userObj['user_country_code']+userObj['user_mob_no']

    secretKey = bytes(settings.SOCIAL_AUTH_COGNITO_SECRET, 'latin-1')
    clientId = settings.SOCIAL_AUTH_COGNITO_KEY 

    digest = hmac.new(secretKey,
                msg=(userObj['user_name'] + clientId).encode('utf-8'),
                digestmod=hashlib.sha256
                ).digest()
    signature = base64.b64encode(digest).decode()

    client = boto3.client('cognito-idp', region_name='eu-west-1' ) 

    try:
        response = client.sign_up(
                    ClientId=clientId,
                    Username=userObj['user_name'],
                    Password=userObj['password1'],
                    SecretHash=signature,
                    UserAttributes=[
                        {
                            'Name': 'given_name',
                            'Value': userObj['given_name']
                        },
                        {
                            'Name': 'family_name',
                            'Value': userObj['family_name']
                        },
                        {
                            'Name': 'email',
                            'Value': userObj['user_email']
                        },
                        {
                            'Name': 'phone_number',
                            'Value': mobilenumber
                        }
                    ],
                    ValidationData=[
                        {
                            'Name': 'email',
                            'Value': userObj['user_email']
                        },
                    ]
                    ,
                    AnalyticsMetadata={
                        'AnalyticsEndpointId': 'string'
                    },
                    UserContextData={
                        'EncodedData': 'string'
                    }
                )
    except ClientError as error:
        return {"errorcode": error.response['Error']['Code'],
            "errormessage" : error.response['Error']['Message'] }
    except Exception as e:
        return {"errorcode": "Something went wrong. Try later or contact the admin" }
    return {"success": "User registered successfully. "}

error.response ['Error'] ['Code'] будет InvalidPasswordException, UsernameExistsException и т.д. Таким образом, в основной функции или в том месте, где вы вызываете функцию, вы можете написать логику для предоставления пользователю значимого сообщения.

Пример ответа (error.response):

{
  "Error": {
    "Message": "Password did not conform with policy: Password must have symbol characters",
    "Code": "InvalidPasswordException"
  },
  "ResponseMetadata": {
    "RequestId": "c8a591d5-8c51-4af9-8fad-b38b270c3ca2",
    "HTTPStatusCode": 400,
    "HTTPHeaders": {
      "date": "Wed, 17 Jul 2019 09:38:32 GMT",
      "content-type": "application/x-amz-json-1.1",
      "content-length": "124",
      "connection": "keep-alive",
      "x-amzn-requestid": "c8a591d5-8c51-4af9-8fad-b38b270c3ca2",
      "x-amzn-errortype": "InvalidPasswordException:",
      "x-amzn-errormessage": "Password did not conform with policy: Password must have symbol characters"
    },
    "RetryAttempts": 0
  }
}

Для дальнейшего использования:https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/cognito-idp.html#CognitoIdentityProvider.Client.sign_up

Ответ 6

Вам нужно что-то сделать, когда он не справится с проблемой. Сейчас вы возвращаете фактическое исключение. Например, если это не проблема, что пользователь уже существует, и вы хотите использовать его как функцию 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.

Тем не менее, может быть, это проблема для вашего приложения, и в этом случае вы хотите захотеть поместить обработчик исключений вокруг кода, который вызвал вашу пользовательскую функцию, и позволить вызывающей функции определять, как с этим бороться, например, попросив пользователя ввести другое имя пользователя или что-то имеющее смысл для вашего приложения.