

# Programar o Amazon DynamoDB com Python e Boto3
<a name="programming-with-python"></a>

Este guia fornece uma orientação para programadores que desejam usar o Amazon DynamoDB com Python. Saiba mais sobre as diferentes camadas de abstração, gerenciamento de configuração, tratamento de erros, controle de políticas de novas tentativas, gerenciamento de keep-alive e muito mais.

**Topics**
+ [Sobre o Boto](#programming-with-python-about)
+ [Usar a documentação do Boto](#programming-with-python-documentation)
+ [Noções básicas sobre as camadas de abstração do cliente e de recursos](#programming-with-python-client-resource)
+ [Usar o recurso de tabela batch\$1writer](#programming-with-python-batch-writer)
+ [Exemplos de código adicionais que exploram as camadas do cliente e de recursos](#programming-with-python-additional-code)
+ [Noções básicas sobre como os objetos Client e Resource interagem com sessões e threads](#programming-with-python-sessions-thread-safety)
+ [Personalizar o objeto Config](#programming-with-python-config)
+ [Tratamento de erros](#programming-with-python-error-handling)
+ [Registro em log](#programming-with-python-logging)
+ [Hooks de eventos](#programming-with-python-event-hooks)
+ [Paginação e o paginador](#programming-with-python-pagination)
+ [Waiters](#programming-with-python-waiters)

## Sobre o Boto
<a name="programming-with-python-about"></a>

É possível acessar o DynamoDB por meio do Python usando o SDK da AWS oficial para Python, geralmente chamado de **Boto3**. O nome Boto se origina de um golfinho de água doce nativo do Rio Amazonas. A biblioteca Boto3 é a terceira versão principal da biblioteca, lançada pela primeira vez em 2015. A biblioteca Boto3 é bem grande, pois comporta todos os serviços da AWS, não apenas o DynamoDB. Essa orientação visa somente às partes do Boto3 relevantes para o DynamoDB.

O Boto é mantido pela AWS como um projeto de código aberto hospedado no GitHub. Ele é dividido em dois pacotes: [Botocore](https://github.com/boto/botocore) e [Boto3](https://github.com/boto/boto3).
+ O **Botocore** oferece a funcionalidade de nível inferior. No Botocore, você encontrará o cliente, a sessão, as credenciais, a configuração e as classes de exceção. 
+ O **Boto3** se baseia no Botocore. Ele oferece uma interface de nível superior, mais relacionada ao Python. Especificamente, ele expõe uma tabela do DynamoDB como um recurso e oferece uma interface mais simples e elegante em comparação à interface de cliente orientada a serviços, de nível inferior.

Como esses projetos estão hospedados no GitHub, é possível visualizar o código-fonte, rastrear problemas abertos ou enviar seus próprios problemas.

## Usar a documentação do Boto
<a name="programming-with-python-documentation"></a>

Comece a usar a documentação do Boto com os seguintes recursos:
+ Comece com a [seção Início rápido](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html), que fornece um ponto de partida sólido para a instalação do pacote. Acesse-a para receber instruções sobre como instalar o Boto3, caso ele ainda não esteja instalado (em geral, o Boto3 é disponibilizado automaticamente nos serviços da AWS, como o AWS Lambda.
+ Depois disso, concentre-se no [guia do DynamoDB](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/dynamodb.html) da documentação. Ele mostra como realizar as atividades básicas do DynamoDB: criar e excluir tabelas, manipular itens, além de realizar operações em lote, consultas e verificações. Seus exemplos usam a interface de **recursos**. A exibição de `boto3.resource('dynamodb')` indica que você está usando a interface de **recursos** de nível superior.
+ Depois do guia, você pode analisar a [referência do DynamoDB](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb.html). Essa página de pouso fornece uma lista completa das classes e dos métodos disponíveis. Na parte superior, será exibida a classe `DynamoDB.Client`. Ela concede acesso de nível inferior a todas as operações do ambiente de gerenciamento e do plano de dados. Na parte inferior, é exibida a classe `DynamoDB.ServiceResource`. Trata-se da interface relacionada ao Python de nível superior. Com ela, é possível criar tabelas, realizar operações em lote entre tabelas ou ter acesso a uma instância `DynamoDB.ServiceResource.Table` para ações específicas de tabelas.

## Noções básicas sobre as camadas de abstração do cliente e de recursos
<a name="programming-with-python-client-resource"></a>

As duas interfaces com as quais você trabalhará são a interface do **cliente** e a interface de **recursos**. 
+ A interface do **cliente** de nível inferior oferece um mapeamento de um para um para a API de serviço subjacente. Todas as APIs oferecidas pelo DynamoDB estão disponíveis por meio do cliente. Isso significa que a interface do cliente pode fornecer funcionalidade completa, mas geralmente é mais detalhada e complexa de usar.
+ A interface de **recursos** de nível superior não fornece um mapeamento de um para um da API de serviço subjacente. No entanto, ela oferece métodos que tornam mais conveniente o acesso ao serviço, como `batch_writer`.

Veja a seguir um exemplo de inserção de um item usando a interface do cliente. Observe como todos os valores são transmitidos como um mapa com a chave indicando o tipo (“S” para string, “N” para número) e o valor como string. Isso é conhecido como formato JSON do DynamoDB.

```
import boto3

dynamodb = boto3.client('dynamodb')

dynamodb.put_item(
    TableName='YourTableName',
    Item={
        'pk': {'S': 'id#1'},
        'sk': {'S': 'cart#123'},
        'name': {'S': 'SomeName'},
        'inventory': {'N': '500'},
        # ... more attributes ...
    }
)
```

Aqui está a mesma operação `PutItem` usando a interface de recursos. A digitação dos dados está implícita:

```
import boto3

dynamodb = boto3.resource('dynamodb')

table = dynamodb.Table('YourTableName')

table.put_item(
    Item={
        'pk': 'id#1',
        'sk': 'cart#123',
        'name': 'SomeName',
        'inventory': 500,
        # ... more attributes ...
    }
)
```

Se necessário, será possível converter entre JSON normal e JSON do DynamoDB usando as classes `TypeSerializer` e `TypeDeserializer` fornecidas com o boto3:

```
def dynamo_to_python(dynamo_object: dict) -> dict:
    deserializer = TypeDeserializer()
    return {
        k: deserializer.deserialize(v) 
        for k, v in dynamo_object.items()
    }  
  
def python_to_dynamo(python_object: dict) -> dict:
    serializer = TypeSerializer()
    return {
        k: serializer.serialize(v)
        for k, v in python_object.items()
    }
```

Veja como realizar uma consulta usando a interface do cliente. Ela expressa a consulta como uma estrutura JSON. Ela usa uma string `KeyConditionExpression` que exige a substituição de variáveis para lidar com possíveis conflitos de palavras-chave:

```
import boto3

client = boto3.client('dynamodb')

# Construct the query
response = client.query(
    TableName='YourTableName',
    KeyConditionExpression='pk = :pk_val AND begins_with(sk, :sk_val)',
    FilterExpression='#name = :name_val',
    ExpressionAttributeValues={
        ':pk_val': {'S': 'id#1'},
        ':sk_val': {'S': 'cart#'},
        ':name_val': {'S': 'SomeName'},
    },
    ExpressionAttributeNames={
        '#name': 'name',
    }
)
```

A mesma operação de consulta usando a interface de recursos pode ser reduzida e simplificada:

```
import boto3
from boto3.dynamodb.conditions import Key, Attr

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('YourTableName')

response = table.query(
    KeyConditionExpression=Key('pk').eq('id#1') & Key('sk').begins_with('cart#'),
    FilterExpression=Attr('name').eq('SomeName')
)
```

Como exemplo final, imagine que você queira ter o tamanho aproximado de uma tabela (que são metadados mantidos na tabela que são atualizados a cada seis horas). Com a interface do cliente, você precisa realizar uma operação `describe_table()` e extrair a resposta da estrutura JSON exibida:

```
import boto3

dynamodb = boto3.client('dynamodb')

response = dynamodb.describe_table(TableName='YourTableName')
size = response['Table']['TableSizeBytes']
```

Com a interface de recursos, a tabela realiza a operação describe implicitamente e apresenta os dados diretamente como um atributo:

```
import boto3

dynamodb = boto3.resource('dynamodb')

table = dynamodb.Table('YourTableName')
size = table.table_size_bytes
```

**nota**  
Ao pensar na possibilidade de desenvolver usando a interface do cliente ou de recursos, esteja ciente de que novos recursos não serão adicionados à interface de recursos de acordo com a [documentação de recursos](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/resources.html): “A equipe do AWS SDK para Python não pretende adicionar novos recursos à interface de recursos no boto3. As interfaces existentes continuarão funcionando durante o ciclo de vida do boto3. Os clientes podem encontrar acesso a novos recursos de serviço por meio da interface do cliente”.

## Usar o recurso de tabela batch\$1writer
<a name="programming-with-python-batch-writer"></a>

Uma praticidade disponível somente com o recurso de tabela de nível superior é o `batch_writer`. O DynamoDB comporta operações de gravação em lote, permitindo até 25 operações put ou delete em uma solicitação de rede. O agrupamento em lotes como esse melhora a eficiência ao minimizar as viagens de ida e volta da rede.

Com a biblioteca de cliente de nível inferior, você deve usar a operação `client.batch_write_item()` para executar lotes. É necessário dividir manualmente o trabalho em lotes de 25. Depois de cada operação, você também precisa solicitar o recebimento de uma lista de itens não processados (algumas das operações de gravação podem ser bem-sucedidas, enquanto outras podem falhar). Depois, é necessário transmitir esses itens não processados novamente para uma operação `batch_write_item()` posterior. Há uma quantidade significativa de código clichê.

O método [Table.batch\$1writer](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb/table/batch_writer.html) cria um gerenciador de contexto para gravar objetos em um lote. Ele apresenta uma interface em que parece que você está escrevendo itens um de cada vez, mas internamente está armazenando em buffer e enviando os itens em lotes. Ele também lida com novas tentativas de itens não processados implicitamente.

```
dynamodb = boto3.resource('dynamodb')

table = dynamodb.Table('YourTableName')

movies = # long list of movies in {'pk': 'val', 'sk': 'val', etc} format
with table.batch_writer() as writer:
    for movie in movies:
        writer.put_item(Item=movie)
```

## Exemplos de código adicionais que exploram as camadas do cliente e de recursos
<a name="programming-with-python-additional-code"></a>

Também é possível consultar os seguintes repositórios de exemplos de código que exploram o uso das várias funções, usando o cliente e o recurso:
+ [Exemplos oficiais de código de ação única da AWS.](https://docs.aws.amazon.com/code-library/latest/ug/python_3_dynamodb_code_examples.html) 
+ [Exemplos oficiais de código orientado por cenários da AWS.](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/python)
+ [Exemplos de códigos de ação única mantidos pela comunidade.](https://github.com/aws-samples/aws-dynamodb-examples/tree/master/examples/SDK/python)

## Noções básicas sobre como os objetos Client e Resource interagem com sessões e threads
<a name="programming-with-python-sessions-thread-safety"></a>

O objeto Resource não é seguro para threads e não deve ser compartilhado entre threads ou processos. Consulte o [guia sobre recursos](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/resources.html#multithreading-or-multiprocessing-with-resources) para ter mais detalhes.

O objeto Client, por outro lado, geralmente é seguro para threads, exceto para recursos avançados específicos. Consulte o [guia sobre clientes](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/clients.html#multithreading-or-multiprocessing-with-clients) para ter mais detalhes. 

O objeto Session não é seguro para threads. Portanto, toda vez que você criar um cliente ou um recurso em um ambiente de vários threads, primeiro será necessário criar uma sessão e, depois, o cliente ou o recurso por meio da sessão. Consulte o [guia sobre sessões](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/session.html#multithreading-or-multiprocessing-with-sessions) para ter mais detalhes. 

Ao chamar o `boto3.resource()`, você está usando implicitamente a sessão padrão. Isso é conveniente para escrever código de thread único. Ao escrever código de vários threads, primeiro é necessário criar uma sessão para cada thread e, depois, recuperar o recurso dessa sessão:

```
# Explicitly create a new Session for this thread 
session = boto3.Session()
dynamodb = session.resource('dynamodb')
```

## Personalizar o objeto Config
<a name="programming-with-python-config"></a>

Ao criar um objeto Client ou Resource, é possível transmitir parâmetros nomeados opcionais para personalizar o comportamento. O parâmetro chamado `config` disponibiliza uma série de funcionalidades. É uma instância de `botocore.client.Config` e a [documentação de referência do Config](https://botocore.amazonaws.com/v1/documentation/api/latest/reference/config.html) mostra todos os itens expostos a serem controlados. O [guia de configuração](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html) fornece uma visão geral útil.

**nota**  
É possível modificar muitas dessas configurações comportamentais em nível de sessão, no arquivo de configuração AWS ou como variáveis de ambiente.

**Configuração de tempos limite**

Um dos usos de uma configuração personalizada é ajustar os comportamentos de rede:
+ **connect\$1timeout (float ou int)**: o tempo em segundos até que uma exceção de tempo limite seja lançada ao tentar fazer uma conexão. O padrão é 60 segundos.
+ **connect\$1timeout (float ou int)**: o tempo em segundos até que uma exceção de tempo limite seja lançada ao tentar estabelecer uma conexão. O padrão é 60 segundos.

Tempos limite de sessenta segundos são excessivos para o DynamoDB. Isso significa que uma falha transitória na rede causará um minuto de atraso para o cliente antes que ele possa tentar novamente. O código a seguir reduz os tempos limite para um segundo:

```
import boto3
from botocore.config import Config

my_config = Config(
   connect_timeout = 1.0,
   read_timeout = 1.0
)
dynamodb = boto3.resource('dynamodb', config=my_config)
```

Para ler mais discussões sobre tempos limite, consulte [Tuning AWS Java SDK HTTP request settings for latency-aware DynamoDB applications](https://aws.amazon.com/blogs/database/tuning-aws-java-sdk-http-request-settings-for-latency-aware-amazon-dynamodb-applications/). Observe que o SDK do Java tem mais configurações de tempo limite do que o Python.

**Configuração de keep-alive**

Se estiver usando o botocore 1.27.84 ou posterior, você também poderá controlar o **TCP Keep-Alive**:
+ **tcp\$1keepalive** (bool): habilita a opção de soquete TCP Keep-Alive usada ao criar conexões, se definida como `True` (o padrão é `False`). O recurso está disponível apenas a partir do botocore 1.27.84.

Configurar o TCP Keep-Alive como `True` pode reduzir as latências médias. Veja um exemplo de código que define condicionalmente o TCP Keep-Alive como verdadeiro quando você tem a versão correta do botocore:

```
import botocore
import boto3
from botocore.config import Config
from distutils.version import LooseVersion

required_version = "1.27.84"
current_version = botocore.__version__

my_config = Config(
   connect_timeout = 0.5,
   read_timeout = 0.5
)
if LooseVersion(current_version) > LooseVersion(required_version):
    my_config = my_config.merge(Config(tcp_keepalive = True))

dynamodb = boto3.resource('dynamodb', config=my_config)
```

**nota**  
O TCP Keep-Alive é diferente do HTTP Keep-Alive. Com o TCP Keep-Alive, pacotes pequenos são enviados pelo sistema operacional subjacente pela conexão do soquete para manter a conexão ativa e detectar imediatamente qualquer queda. Com o HTTP Keep-Alive, a conexão da web baseada no soquete subjacente é reutilizada. O HTTP Keep-Alive está sempre habilitado com o boto3.

Há um limite de quanto tempo uma conexão inativa pode ser mantida ativa. Pense em enviar solicitações periódicas (digamos a cada minuto) se tiver uma conexão inativa, mas quiser que a próxima solicitação use uma conexão já estabelecida.

**Configuração de novas tentativas**

A configuração também aceita um dicionário chamado **novas tentativas**, no qual é possível especificar o comportamento de novas tentativas desejado. As novas tentativas acontecem no SDK quando o SDK recebe um erro e o erro é de um tipo transitório. Se um erro for repetido internamente (e uma nova tentativa, por fim, produzir uma resposta bem-sucedida), do ponto de vista do código de chamada, não haverá erro, apenas uma latência ligeiramente elevada. Veja os valores que você pode especificar:
+ **max\$1attempts**: um número inteiro que representa o número máximo de novas tentativas que serão feitas em uma única solicitação. Por exemplo, definir esse valor como dois fará com que a solicitação seja repetida no máximo duas vezes após a solicitação inicial. Definir esse valor como zero não vai ocasionar nenhuma nova tentativa após a solicitação inicial. 
+ **total\$1max\$1attempts**: um número inteiro que representa o número máximo total de tentativas que serão feitas em uma única solicitação. Isso inclui a solicitação inicial, portanto, um valor de um indica que nenhuma solicitação será repetida. Se `total_max_attempts` e `max_attempts` forem fornecidos, `total_max_attempts` terá precedência. `total_max_attempts` é preferível a `max_attempts` porque é associado à variável de ambiente `AWS_MAX_ATTEMPTS` e ao valor do arquivo de configuração `max_attempts`.
+ **mode**: uma string que representa o tipo de modo de nova tentativa que o botocore deve usar. Os valores válidos são:
  + **legacy**: o modo padrão. Espera 50 ms na primeira tentativa e, depois, usa recuo exponencial com um fator de base dois. Em relação ao DynamoDB, ele executa até dez tentativas no total, (a menos que seja substituído pelo valor acima).
**nota**  
Com um recuo exponencial, a última tentativa aguardará quase 13 segundos.
  + **standard**: padrão nomeado porque é mais consistente com outros SDKs da AWS. Espera por um período aleatório que varia de 0 ms a 1.000 ms pela primeira nova tentativa. Se outra nova tentativa for necessária, ele escolherá outro período aleatório de 0 ms a 1.000 ms e o multiplicará por 2. Se for necessária uma nova tentativa, ele fará a mesma escolha aleatória multiplicada por quatro e assim por diante. Cada espera é limitada a 20 segundos. Esse modo executará novas tentativas em mais condições de falha detectadas do que o modo `legacy`. Em relação ao DynamoDB, ele executa até três tentativas no total, (a menos que seja substituído pelo valor acima). 
  + **adaptável**: modo de novas tentativas experimental que inclui toda a funcionalidade do modo padrão, mas inclui controle de utilização automática do lado do cliente. Com a limitação de taxa adaptável, os SDKs podem diminuir a taxa na qual as solicitações são enviadas para acomodar melhor a capacidade dos serviços da AWS. Esse é um modo provisório cujo comportamento pode mudar. 

Uma definição expandida desses modos de novas tentativas pode ser encontrada no [guia de novas tentativas](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/retries.html), bem como no [tópico Retry behavior na referência do SDK](https://docs.aws.amazon.com/sdkref/latest/guide/feature-retry-behavior.html).

Veja um exemplo que usa explicitamente a política de novas tentativas `legacy` com, no máximo, três 3 solicitações no total (duas novas tentativas):

```
import boto3
from botocore.config import Config

my_config = Config(
   connect_timeout = 1.0,
   read_timeout = 1.0,
   retries = {
     'mode': 'legacy',
     'total_max_attempts': 3
   }
)
dynamodb = boto3.resource('dynamodb', config=my_config)
```

Como o DynamoDB é um sistema de alta disponibilidade e baixa latência, convém adotar uma postura mais incisiva em relação à velocidade das novas tentativas do que as respectivas políticas permitem. É possível implementar sua própria política de novas tentativas definindo o máximo de tentativas como zero, detectando por conta própria as exceções e tentando novamente, conforme apropriado, com o próprio código, em vez de recorrer ao boto3 para fazer novas tentativas implícitas.

Se você gerencia sua própria política de novas tentativas, convém diferenciar entre controles de utilização e erros:
+ Um **controle de utilização** (designado por um `ProvisionedThroughputExceededException` ou `ThrottlingException`) indica um serviço íntegro que está informando que você excedeu sua capacidade de leitura ou gravação em uma tabela ou partição do DynamoDB. A cada milissegundo que se passa, um pouco mais de capacidade de leitura ou gravação é disponibilizada e, portanto, é possível realizar novas tentativas com rapidez (por exemplo, a cada 50 ms) para tentar acessar essa capacidade recém-liberada. Com os controles de utilização, não é especialmente necessário um recuo exponencial porque os controles de utilização são leves para o DynamoDB exibir e não cobram por solicitação. O recuo exponencial atribui atrasos maiores aos threads do cliente que já esperaram por mais tempo, o que estende estatisticamente o p50 e o p99.
+ Um **erro** (designado por um `InternalServerError` ou um `ServiceUnavailable`, entre outros) indica um problema transitório com o serviço. Isso pode se relacionar à toda a tabela ou possivelmente apenas à partição na qual você está lendo ou gravando. Com erros, é possível pausar por mais tempo antes de novas tentativas, (como 250 ms ou 500 ms), e usar a instabilidade para escalonar as novas tentativas.

**Configuração de conexões máximas de grupo**

Por fim, a configuração permite controlar o tamanho do grupo de conexões:
+ **max\$1pool\$1connections (int)**: o número máximo de conexões a serem mantidas em um grupo de conexões. Se esse valor não for definido, será usado o valor padrão dez.

Essa opção controla o número máximo de conexões HTTP a serem mantidas agrupadas para reutilização. Um grupo diferente é mantido por sessão. Ao prever que mais de dez threads serão direcionados para clientes ou recursos criados na mesma sessão, pense em aumentar isso, para que os threads não precisem esperar por outros threads usando uma conexão em grupo.

```
import boto3
from botocore.config import Config

my_config = Config(
   max_pool_connections = 20
)

# Setup a single session holding up to 20 pooled connections
session = boto3.Session(my_config)

# Create up to 20 resources against that session for handing to threads
# Notice the single-threaded access to the Session and each Resource
resource1 = session.resource('dynamodb')
resource2 = session.resource('dynamodb')
# etc
```

## Tratamento de erros
<a name="programming-with-python-error-handling"></a>

Nem todas as exceções de serviço da AWS são definidas estaticamente no Boto3. Isso ocorre porque os erros e as exceções dos serviços da AWS variam muito e estão sujeitos a alterações. O Boto3 agrupa todas as exceções de serviço como um `ClientError` e expõe os detalhes como JSON estruturado. Por exemplo, uma resposta de erro pode ser estruturada assim:

```
{
    'Error': {
        'Code': 'SomeServiceException',
        'Message': 'Details/context around the exception or error'
    },
    'ResponseMetadata': {
        'RequestId': '1234567890ABCDEF',
        'HostId': 'host ID data will appear here as a hash',
        'HTTPStatusCode': 400,
        'HTTPHeaders': {'header metadata key/values will appear here'},
        'RetryAttempts': 0
    }
}
```

O código a seguir captura todas as exceções `ClientError` e examina o valor da string do `Code` no `Error` para determinar qual ação realizar:

```
import botocore
import boto3

dynamodb = boto3.client('dynamodb')

try:
    response = dynamodb.put_item(...)

except botocore.exceptions.ClientError as err:
    print('Error Code: {}'.format(err.response['Error']['Code']))
    print('Error Message: {}'.format(err.response['Error']['Message']))
    print('Http Code: {}'.format(err.response['ResponseMetadata']['HTTPStatusCode']))
    print('Request ID: {}'.format(err.response['ResponseMetadata']['RequestId']))

    if err.response['Error']['Code'] in ('ProvisionedThroughputExceededException', 'ThrottlingException'):
        print("Received a throttle")
    elif err.response['Error']['Code'] == 'InternalServerError':
        print("Received a server error")
    else:
        raise err
```

Alguns códigos de exceção (mas não todos) foram materializados como classes de nível superior. É possível optar por lidar com eles diretamente. Ao usar a interface do cliente, essas exceções são preenchidas dinamicamente no cliente e você captura essas exceções usando sua instância cliente, da seguinte maneira:

```
except ddb_client.exceptions.ProvisionedThroughputExceededException:
```

Ao usar a interface de recursos, é necessário usar `.meta.client` para fazer o percurso entre o recurso e o cliente subjacente a fim de acessar as exceções, da seguinte maneira:

```
except ddb_resource.meta.client.exceptions.ProvisionedThroughputExceededException:
```

Para analisar a lista de tipos de exceções materializadas, é possível gerar a lista dinamicamente:

```
ddb = boto3.client("dynamodb")
print([e for e in dir(ddb.exceptions) if e.endswith('Exception') or e.endswith('Error')])
```

Ao realizar uma operação de gravação com uma expressão condicional, é possível solicitar que, se a expressão falhar, o valor do item seja exibido na resposta de erro.

```
try:
    response = table.put_item(
        Item=item,
        ConditionExpression='attribute_not_exists(pk)',
        ReturnValuesOnConditionCheckFailure='ALL_OLD'
    )
except table.meta.client.exceptions.ConditionalCheckFailedException as e:
    print('Item already exists:', e.response['Item'])
```

Para ler mais sobre tratamento de erros e exceções:
+ O [guia do boto3 sobre tratamento de erros](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/error-handling.html) tem mais informações sobre técnicas de tratamento de erros. 
+ A [seção do Guia do desenvolvedor do DynamoDB sobre erros de programação](Programming.Errors.md) lista os erros que você pode encontrar. 
+ A [seção Common Errors na referência da API](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/CommonErrors.html).
+ A documentação sobre cada operação de API lista quais erros essa chamada pode gerar (por exemplo, [BatchWriteItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchWriteItem.html)).

## Registro em log
<a name="programming-with-python-logging"></a>

A biblioteca do boto3 se integra ao módulo de registro em log integrado do Python para monitorar o que acontece durante uma sessão. Para controlar os níveis de registro em log, é possível configurar o módulo de registro em log:

```
import logging

logging.basicConfig(level=logging.INFO)
```

Isso configura o logger raiz para registrar em log `INFO` e mensagens de nível superior. Mensagens de registro em log que forem menos rígidas do que o nível serão ignoradas. Os níveis de registro em log incluem `DEBUG`, `INFO`, `WARNING`, `ERROR` e `CRITICAL`. O padrão é `WARNING`.

Os loggers no boto3 são hierárquicos. A biblioteca usa alguns loggers diferentes, cada um correspondendo a diferentes partes da biblioteca. É possível controlar separadamente o comportamento de cada um:
+ **boto3**: o logger principal do módulo boto3.
+ **botocore**: o logger principal do pacote do botocore.
+ **botocore.auth**: usado para registrar em log a criação de assinaturas da AWS para solicitações.
+ **botocore.credentials**: usado para registrar em log o processo de busca e atualização de credenciais.
+ **botocore.endpoint**: usado para registrar em log a criação da solicitação antes de ser enviada pela rede.
+ **botocore.hooks**: usado para registrar em log eventos acionados na biblioteca.
+ **botocore.loaders**: usado para registrar em log quando partes dos modelos de serviço da AWS são carregadas.
+ **botocore.parsers**: usado para registrar em log as respostas do serviço da AWS antes de serem analisadas.
+ **botocore.retryhandler**: usado para registrar em log o processamento de novas tentativas de solicitação do serviço da AWS (modo herdado).
+ **botocore.retries.standard**: usado para registrar em log o processamento de novas tentativas de solicitação do serviço da AWS (modo padrão ou adaptável).
+ **botocore.utils**: usado para registrar em log atividades diversas na biblioteca.
+ **botocore.waiter**: usado para registrar em log a funcionalidade dos waiters, que pesquisam um serviço da AWS até que determinado estado seja atingido. 

Outras bibliotecas também realizam o registro em log. Internamente, o boto3 usa o urllib3 de terceiros para lidar com a conexão HTTP. Quando a latência é importante, é possível observar os logs para garantir que o grupo esteja sendo bem utilizado, vendo quando o urllib3 estabelece uma nova conexão ou fecha uma ociosa.
+ **urllib3.connectionpool:** use para registrar em log eventos de tratamento de eventos do grupo de conexões.

O trecho de código a seguir define a maioria dos registros em log como `INFO` com o registro em log de `DEBUG` da atividade do endpoint e do grupo de conexões:

```
import logging

logging.getLogger('boto3').setLevel(logging.INFO)
logging.getLogger('botocore').setLevel(logging.INFO)
logging.getLogger('botocore.endpoint').setLevel(logging.DEBUG)
logging.getLogger('urllib3.connectionpool').setLevel(logging.DEBUG)
```

## Hooks de eventos
<a name="programming-with-python-event-hooks"></a>

O Botocore emite eventos durante várias partes de sua execução. É possível registrar manipuladores para esses eventos para que, sempre que um evento for emitido, seu manipulador seja chamado. Isso permite que você estenda o comportamento do botocore sem precisar modificar os componentes internos.

Por exemplo, digamos que você queira acompanhar cada vez que uma operação `PutItem` for chamada em qualquer tabela do DynamoDB na aplicação. É possível se inscrever no evento `'provide-client-params.dynamodb.PutItem'` para capturar e registrar toda vez que uma operação `PutItem` for invocada na sessão associada. Veja um exemplo abaixo:

```
import boto3
import botocore
import logging

def log_put_params(params, **kwargs):
    if 'TableName' in params and 'Item' in params:
        logging.info(f"PutItem on table {params['TableName']}: {params['Item']}")

logging.basicConfig(level=logging.INFO)

session = boto3.Session()
event_system = session.events

# Register our interest in hooking in when the parameters are provided to PutItem
event_system.register('provide-client-params.dynamodb.PutItem', log_put_params)

# Now, every time you use this session to put an item in DynamoDB,
# it will log the table name and item data.
dynamodb = session.resource('dynamodb')
table = dynamodb.Table('YourTableName')
table.put_item(
    Item={
        'pk': '123',
        'sk': 'cart#123',
        'item_data': 'YourItemData',
        # ... more attributes ...
    }
)
```

No manipulador, é possível até mesmo manipular os parâmetros programaticamente para alterar o comportamento:

```
params['TableName'] = "NewTableName"
```

Para ter mais informações sobre eventos, consulte a [documentação do botocore sobre eventos](https://botocore.amazonaws.com/v1/documentation/api/latest/topics/events.html) e a [documentação do boto3 sobre eventos](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/events.html).

## Paginação e o paginador
<a name="programming-with-python-pagination"></a>

Algumas solicitações, como Query e Scan, limitam o tamanho dos dados exibidos em uma única solicitação e exigem que você faça solicitações repetidas para extrair as páginas subsequentes.

É possível controlar o número máximo de itens a serem lidos em cada página com o parâmetro `limit`. Por exemplo, se você quiser os dez últimos itens, poderá usar `limit` para recuperar somente os últimos dez itens. Observe que esse limite é quantos itens devem ser lidos da tabela antes que qualquer filtragem seja aplicada. Não há como especificar que você deseja exatamente dez após a filtragem; só é possível controlar a contagem pré-filtrada e conferir o lado do cliente quando realmente tiver recuperado dez itens. Independentemente do limite, cada resposta sempre tem um tamanho máximo de 1 MB.

Se a resposta incluir uma `LastEvaluatedKey`, isso indica que a resposta foi encerrada porque atingiu um limite de contagem ou tamanho. A chave é a última avaliada para a resposta. É possível recuperar essa `LastEvaluatedKey` e transmiti-la para uma chamada de acompanhamento como `ExclusiveStartKey` para ler a próxima parte desse ponto de partida. Quando não há `LastEvaluatedKey` exibida, isso significa que não há mais itens que correspondam a Query ou Scan.

Veja um exemplo simples (usando a interface de recursos, mas a interface do cliente tem o mesmo padrão) que lê no máximo cem itens por página e se repete até que todos os itens tenham sido lidos.

```
import boto3

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('YourTableName')

query_params = {
    'KeyConditionExpression': Key('pk').eq('123') & Key('sk').gt(1000),
    'Limit': 100
}

while True:
    response = table.query(**query_params)

    # Process the items however you like
    for item in response['Items']:
        print(item)

    # No LastEvaluatedKey means no more items to retrieve
    if 'LastEvaluatedKey' not in response:
        break

    # If there are possibly more items, update the start key for the next page
    query_params['ExclusiveStartKey'] = response['LastEvaluatedKey']
```

Por conveniência, o boto3 pode fazer isso por você com paginadores. No entanto, ele só funciona com a interface do cliente. Veja o código reformulado para usar paginadores:

```
import boto3

dynamodb = boto3.client('dynamodb')

paginator = dynamodb.get_paginator('query')

query_params = {
    'TableName': 'YourTableName',
    'KeyConditionExpression': 'pk = :pk_val AND sk > :sk_val',
    'ExpressionAttributeValues': {
        ':pk_val': {'S': '123'},
        ':sk_val': {'N': '1000'},
    },
    'Limit': 100
}

page_iterator = paginator.paginate(**query_params)

for page in page_iterator:
    # Process the items however you like
    for item in page['Items']:
        print(item)
```

Para ter mais informações, consulte o [Guia sobre paginadores](https://botocore.amazonaws.com/v1/documentation/api/latest/topics/events.html) e a [Referência da API para DynamoDB.Paginator.Query](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb/paginator/Query.html).

**nota**  
Os paginadores também têm suas próprias configurações chamadas `MaxItems`, `StartingToken` e `PageSize`. Para paginar com o DynamoDB, você deve ignorar essas configurações.

## Waiters
<a name="programming-with-python-waiters"></a>

Os waiters oferecem a capacidade de esperar que algo seja concluído antes de continuar. No momento, eles comportam apenas a espera pela criação ou a exclusão de uma tabela. Em segundo plano, a operação waiter faz uma verificação para você a cada 20 segundos até 25 vezes. É possível fazer isso por conta própria, mas o uso de um waiter é uma solução mais sofisticada ao elaborar automações.

Esse código mostra como esperar que uma tabela específica seja criada:

```
# Create a table, wait until it exists, and print its ARN
response = client.create_table(...)
waiter = client.get_waiter('table_exists')
waiter.wait(TableName='YourTableName')
print('Table created:', response['TableDescription']['TableArn']
```

Para ter mais informações, consulte o [Guia de waiters](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/clients.html#waiters) e a [Referência sobre waiters](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb.html#waiters).