Programar o Amazon DynamoDB com Python e Boto3 - Amazon DynamoDB

Programar o Amazon DynamoDB com Python e Boto3

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.

Sobre o Boto

É 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 e 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

Comece a usar a documentação do Boto com os seguintes recursos:

  • Comece com a seção Início rápido, 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 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. 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

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: “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_writer

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_writer 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

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:

Noções básicas sobre como os objetos Client e Resource interagem com sessões e threads

O objeto Resource não é seguro para threads e não deve ser compartilhado entre threads ou processos. Consulte o guia sobre recursos 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 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 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

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 mostra todos os itens expostos a serem controlados. O guia de configuração 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_timeout (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_timeout (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. 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_keepalive (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_attempts: 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_max_attempts: 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, bem como no tópico Retry behavior na referência do SDK.

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_pool_connections (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

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:

Registro em log

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

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 e a documentação do boto3 sobre eventos.

Paginação e o paginador

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 e a Referência da API para DynamoDB.Paginator.Query.

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

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 e a Referência sobre waiters.