Mensagens Protobuf

A versão 14.0.0 da biblioteca de cliente do Python introduz um novo parâmetro de configuração obrigatório chamado use_proto_plus, que especifica se você quer que a biblioteca retorne protos ou mensagens protobuf. Para detalhes sobre como definir esse parâmetro, consulte os documentos de configuração.

Esta seção descreve as implicações de desempenho ao escolher quais tipos de mensagens usar. Portanto, recomendamos que você leia e entenda as opções para tomar uma decisão fundamentada. No entanto, se você quiser fazer upgrade para a versão 14.0.0 sem fazer mudanças no código, defina use_proto_plus como True para evitar alterações na interface.

Proto-plus mensagens x protobuf messages

Na versão 10.0.0, a biblioteca de cliente do Python migrou para um novo pipeline de gerador de código que integrou o proto-plus como uma maneira de melhorar a ergonomia da interface de mensagem protobuf fazendo com que elas se comportassem mais como objetos Python nativos. A vantagem dessa melhoria é que a proto-plus introduz a sobrecarga de desempenho.

Desempenho de proto-plus

Um dos principais benefícios do proto-plus é que ele converte mensagens protobuf e tipos conhecidos (link em inglês) para tipos nativos do Python usando um processo chamado tipo marshaling.

O marshaling ocorre quando um campo é acessado em uma instância de mensagem proto-plus, especificamente quando um campo é lido ou definido, por exemplo, em uma definição protobuf{/1}:

syntax = "proto3";

message Dog {
  string name = 1;
}

Quando essa definição é convertida em uma classe proto-plus, a aparência será semelhante a esta:

import proto

class Dog(proto.Message):
    name = proto.Field(proto.STRING, number=1)

Em seguida, inicialize a classe Dog e acesse o campo name, como em qualquer outro objeto Python:

dog = Dog()
dog.name = "Scruffy"
print(dog.name)

Ao ler e definir o campo name, o valor é convertido de um tipo nativo do Python str para um tipo string. Assim, o valor é compatível com o ambiente de execução protobuf.

Na análise que realizamos desde o lançamento da versão 10.0.0, determinamos que o tempo gasto com essas conversões de tipo tem um impacto de desempenho grande o suficiente. Por isso, é importante oferecer aos usuários a opção de usar mensagens protobuf.

Casos de uso para mensagens proto-plus e protobuf

Casos de uso de mensagens proto-plus
O Proto-Plus oferece várias melhorias ergonômicas sobre as mensagens protobuf, então elas são ideais para programar códigos legíveis e legíveis. Como eles expõem objetos Python nativos, eles são mais fáceis de usar e entender.
Casos de uso de mensagens Protobuf
Use protobufs para casos de uso sensíveis ao desempenho, especificamente em apps que precisam processar relatórios grandes rapidamente ou que criam solicitações de modificação com um grande número de operações, por exemplo, com BatchJobService ou OfflineUserDataJobService.

Como alterar dinamicamente os tipos de mensagem

Depois de selecionar o tipo de mensagem apropriado para seu app, você pode descobrir que precisa usar o outro tipo para um fluxo de trabalho específico. Nesse caso, é fácil alternar dinamicamente entre os dois tipos usando utilitários oferecidos pela biblioteca de cliente. Usando a mesma classe de mensagem Dog acima:

from google.ads.googleads import util

# Proto-plus message type
dog = Dog()

# Protobuf message type
dog = util.convert_proto_plus_to_protobuf(dog)

# Back to proto-plus message type
dog = util.convert_protobuf_to_proto_plus(dog)

Diferenças na interface de mensagens Protobuf

A interface proto-plus é documentada em detalhes, mas destacaremos algumas diferenças importantes que afetam casos de uso comuns da biblioteca de cliente do Google Ads.

Serialização de bytes

Mensagens Proto-plus
serialized = type(campaign).serialize(campaign)
deserialized = type(campaign).deserialize(serialized)
Mensagens Protobuf
serialized = campaign.SerializeToString()
deserialized = campaign.FromString(serialized)

Serialização de JSON

Mensagens Proto-plus
serialized = type(campaign).to_json(campaign)
deserialized = type(campaign).from_json(serialized)
Mensagens Protobuf
from google.protobuf.json_format import MessageToJson, Parse

serialized = MessageToJson(campaign)
deserialized = Parse(serialized, campaign)

Máscaras de campo

O método auxiliar de máscara de campo fornecido pela api-core foi projetado para usar instâncias de mensagens protobuf. Portanto, ao usar mensagens de proto-plus, converta-as em mensagens protobuf para usar o auxiliar:

Mensagens Proto-plus
from google.api_core.protobuf_helpers import field_mask

campaign = client.get_type("Campaign")
protobuf_campaign = util.convert_proto_plus_to_protobuf(campaign)
mask = field_mask(None, protobuf_campaign)
Mensagens Protobuf
from google.api_core.protobuf_helpers import field_mask

campaign = client.get_type("Campaign")
mask = field_mask(None, campaign)

Enums

Enums expostos por mensagens de proto-plus são instâncias do tipo enum nativo do Python e, portanto, herdam vários métodos de conveniência.

Recuperação do tipo de enumeração

Ao usar o método GoogleAdsClient.get_type para recuperar enumerações, as mensagens retornadas são um pouco diferentes, dependendo se você está usando mensagens proto-plus ou protobuf. Exemplo:

Mensagens Proto-plus
val = client.get_type("CampaignStatusEnum").CampaignStatus.PAUSED
Mensagens Protobuf
val = client.get_type("CampaignStatusEnum").PAUSED

Para simplificar a recuperação de enumerações, há um atributo de conveniência em instâncias GoogleAdsClient que tem uma interface consistente, independentemente do tipo de mensagem que você está usando:

val = client.enums.CampaignStatusEnum.PAUSED

Recuperação de valores de enumeração

Às vezes, é útil saber o valor ou ID do campo de um determinado enum. Por exemplo, PAUSED no CampaignStatusEnum corresponde a 3:

Mensagens Proto-plus
campaign = client.get_type("Campaign")
campaign.status = client.enums.CampaignStatusEnum.PAUSED
# To read the value of campaign status
print(campaign.status.value)
Mensagens Protobuf
campaign = client.get_type("Campaign")
status_enum = client.enums.CampaignStatusEnum
campaign.status = status_enum.PAUSED
# To read the value of campaign status
print(status_enum.CampaignStatus.Value(campaign.status))

Recuperação de nome de enumeração

Às vezes, é útil saber o nome de um campo de enumeração. Por exemplo, ao ler objetos da API, é possível saber qual status da campanha o 3 corresponde:

Mensagens Proto-plus
campaign = client.get_type("Campaign")
campaign.status = client.enums.CampaignStatusEnum.PAUSED
# To read the name of campaign status
print(campaign.status.name)
Mensagens Protobuf
campaign = client.get_type("Campaign")
status_enum = client.enums.CampaignStatusEnum
# Sets the campaign status to the int value for PAUSED
campaign.status = status_enum.PAUSED
# To read the name of campaign status
status_enum.CampaignStatus.Name(campaign.status)

Campos repetidos

Conforme descrito nos documentos proto-plus (em inglês), os campos repetidos geralmente são equivalentes a listas digitadas, o que significa que eles se comportam de maneira quase idêntica a uma list.

Como anexar a campos escalares repetidos

Ao adicionar valores a campos de tipo escalador repetidos, por exemplo, campos string ou int64, a interface é a mesma, independentemente do tipo de mensagem:

Mensagens Proto-plus
ad.final_urls.append("https://www.example.com")
Mensagens Protobuf
ad.final_urls.append("https://www.example.com")

Isso também inclui todos os outros métodos list comuns, por exemplo, extend:

Mensagens Proto-plus
ad.final_urls.extend(["https://www.example.com", "https://www.example.com/2"])
Mensagens Protobuf
ad.final_urls.extend(["https://www.example.com", "https://www.example.com/2"])

Anexar tipos de mensagens a campos repetidos

Se o campo repetido não for de um tipo escalar, o comportamento ao adicioná-lo a campos repetidos será um pouco diferente:

Mensagens Proto-plus
frequency_cap = client.get_type("FrequencyCapEntry")
frequency_cap.cap = 100
campaign.frequency_caps.append(frequency_cap)
Mensagens Protobuf
# The add method initializes a message and adds it to the repeated field
frequency_cap = campaign.frequency_caps.add()
frequency_cap.cap = 100

Atribuir campos repetidos

Para os campos repetidos escalares e não escalares, é possível atribuir listas ao campo de diferentes maneiras:

Mensagens Proto-plus
# In proto-plus it's possible to use assignment.
urls = ["https://www.example.com"]
ad.final_urls = urls
Mensagens Protobuf
# Protobuf messages do not allow assignment, but you can replace the
# existing list using slice syntax.
urls = ["https://www.example.com"]
ad.final_urls[:] = urls

Mensagens vazias

Às vezes, é útil saber se uma instância de mensagem contém alguma informação ou se tem algum dos campos definidos.

Mensagens Proto-plus
# When using proto-plus messages you can simply check the message for
# truthiness.
is_empty = bool(campaign)
is_empty = not campaign
Mensagens Protobuf
is_empty = campaign.ByteSize() == 0

Cópia da mensagem

Para mensagens proto-plus e protobuf, recomendamos usar o método auxiliar copy_from no GoogleAdsClient:

client.copy_from(campaign, other_campaign)

Campos de mensagem vazios

O processo para definir campos de mensagem vazios é o mesmo, independentemente do tipo de mensagem que você está usando. Você só precisa copiar uma mensagem vazia para o campo em questão. Consulte a seção Cópia da mensagem e o guia Campos de mensagens em branco. Veja um exemplo de como definir um campo de mensagem vazio:

client.copy_from(campaign.manual_cpm, client.get_type("ManualCpm"))

Nomes de campos que são palavras reservadas

Ao usar mensagens proto-plus, os nomes dos campos aparecerão automaticamente com um sublinhado final se o nome também for uma palavra reservada no Python. Veja um exemplo de como trabalhar com uma instância Asset:

asset = client.get_type("Asset")
asset.type_ = client.enums.AssetTypeEnum.IMAGE

A lista completa de nomes reservados é criada no módulo do gerador gapica. Elas também podem ser acessadas de forma programática.

Primeiro, instale o módulo:

python -m pip install gapic-generator

Em seguida, em um REPL ou script Python:

import gapic.utils
print(gapic.utils.reserved_names.RESERVED_NAMES)

Presença de campo

Como os campos em instâncias de mensagem protobuf têm valores padrão, nem sempre é intuitivo saber se um campo foi definido ou não.

Mensagens Proto-plus
# Use the "in" operator.
has_field = "name" in campaign
Mensagens Protobuf
campaign = client.get_type("Campaign")
# Determines whether "name" is set and not just an empty string.
campaign.HasField("name")

A interface de classe Message do protobuf tem um método HasField que determina se o campo em uma mensagem foi definido, mesmo que tenha sido definido como um valor padrão.

Métodos de mensagem Protobuf

A interface de mensagem protobuf inclui alguns métodos de conveniência que não fazem parte da interface proto-plus. No entanto, é simples acessá-las convertendo uma mensagem proto-plus para a contraparte protobuf:

# Accessing the ListFields method
protobuf_campaign = util.convert_protobuf_to_proto_plus(campaign)
print(campaign.ListFields())

# Accessing the Clear method
protobuf_campaign = util.convert_protobuf_to_proto_plus(campaign)
print(campaign.Clear())

Issue Tracker

Se você tiver alguma dúvida sobre essas mudanças ou problemas ao migrar para a versão 14.0.0 da biblioteca, registre um problema no nosso rastreador.