Mensajes de protobuf

La versión 14.0.0 de la biblioteca cliente de Python introduce un nuevo parámetro de configuración obligatorio llamado use_proto_plus que especifica si deseas que la biblioteca muestre mensajes proto-plus o mensajes protobuf. Para obtener detalles sobre cómo establecer este parámetro, consulta los documentos de configuración.

En esta sección, se describen las consecuencias en el rendimiento de la elección de los tipos de mensajes que se usarán. Por lo tanto, te recomendamos que leas y comprendas las opciones para tomar una decisión fundamentada. Sin embargo, si deseas actualizar a la versión 14.0.0 sin realizar cambios en el código, puedes establecer use_proto_plus en True para evitar cambios de interfaz.

Mensajes de proto+ frente a protobuf

En la versión 10.0.0, la biblioteca cliente de Python migró a una nueva canalización del generador de código que integró proto-plus como una forma de mejorar la ergonomía de la interfaz de mensajes de protobuf y hacer que se comporten de manera similar a los objetos nativos de Python. La compensación de esta mejora es que el protocolo más y el protocolo introduce una sobrecarga de rendimiento.

Rendimiento de los protocolos superiores

Uno de los beneficios principales de proto-plus es que convierte mensajes de protobuf y tipos conocidos en tipos nativos de Python mediante un proceso llamado ordenamiento de tipos.

El ordenamiento ocurre cuando se accede a un campo en una instancia de mensaje proto-plus, especialmente cuando se lee o configura un campo, por ejemplo, en una definición de protobuf:

syntax = "proto3";

message Dog {
  string name = 1;
}

Cuando esta definición se convierte en una clase proto-plus, se vería de la siguiente manera:

import proto

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

Luego, puedes inicializar la clase Dog y acceder a su campo name como lo harías con cualquier otro objeto de Python:

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

Cuando se lee y configura el campo name, el valor se convierte de un tipo str de Python nativo a un tipo string a fin de que el valor sea compatible con el entorno de ejecución de protobuf.

En el análisis que realizamos desde el lanzamiento de la versión 10.0.0, determinamos que el tiempo dedicado a realizar estas conversiones de tipo tiene un impacto de rendimiento lo suficientemente grande que es importante darles a los usuarios la opción de usar los mensajes protobuf.

Casos de uso de los mensajes proto-plus y protobuf

Casos de uso de mensajes proto+
Proto+ ofrece varias mejoras ergonómicas con respecto a los mensajes protobuf, por lo que son ideales para escribir código legible y que se pueda mantener. Debido a que exponen objetos nativos de Python, son más fáciles de usar y comprender.
Casos de uso de mensajes de protocolo
Usa protobufs para casos de uso sensibles al rendimiento, específicamente en apps que necesitan procesar grandes informes con rapidez o que compilan solicitudes de mutación con una gran cantidad de operaciones, por ejemplo, con BatchJobService o OfflineUserDataJobService.

Tipos de mensajes que cambian de forma dinámica

Después de seleccionar el tipo de mensaje adecuado para tu app, es posible que necesites usar el otro tipo para un flujo de trabajo específico. En este caso, es fácil alternar entre los dos tipos de manera dinámica con las utilidades que ofrece la biblioteca cliente. Con la misma clase de mensaje Dog anterior:

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)

Diferencias entre la interfaz de mensajes de protobuf

La interfaz proto-plus está documentada en detalle, pero aquí destacaremos algunas diferencias clave que afectan los casos de uso comunes de la biblioteca cliente de Google Ads.

Serialización de bytes

Mensajes de proto+
serialized = type(campaign).serialize(campaign)
deserialized = type(campaign).deserialize(serialized)
Mensajes de protobuf
serialized = campaign.SerializeToString()
deserialized = campaign.FromString(serialized)

Serialización de JSON

Mensajes de proto+
serialized = type(campaign).to_json(campaign)
deserialized = type(campaign).from_json(serialized)
Mensajes de protobuf
from google.protobuf.json_format import MessageToJson, Parse

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

Mascarillas de campo

El método auxiliar de máscara de campo que proporciona api-core está diseñado para usar instancias de mensaje protobuf. Por lo tanto, cuando uses mensajes proto-plus, conviértelos en mensajes protobuf para usar el asistente:

Mensajes de proto+
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)
Mensajes de protobuf
from google.api_core.protobuf_helpers import field_mask

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

Enumeradores

Las enumeraciones expuestas por mensajes proto-plus son instancias del tipo enum nativo de Python y, por lo tanto, heredan una serie de métodos útiles.

Recuperación de tipos Enum

Cuando se usa el método GoogleAdsClient.get_type para recuperar enumeraciones, los mensajes que se muestran son ligeramente diferentes según si usas mensajes proto-plus o protobuf. Por ejemplo:

Mensajes de proto+
val = client.get_type("CampaignStatusEnum").CampaignStatus.PAUSED
Mensajes de protobuf
val = client.get_type("CampaignStatusEnum").PAUSED

Para simplificar la recuperación de enumeraciones, hay un atributo de conveniencia en las instancias GoogleAdsClient que tiene una interfaz coherente, sin importar el tipo de mensaje que uses:

val = client.enums.CampaignStatusEnum.PAUSED

Recuperación de valores de enumeración

A veces, es útil conocer el valor, o el ID de campo, de una enumeración determinada; por ejemplo, PAUSED en CampaignStatusEnum corresponde a 3:

Mensajes de proto+
campaign = client.get_type("Campaign")
campaign.status = client.enums.CampaignStatusEnum.PAUSED
# To read the value of campaign status
print(campaign.status.value)
Mensajes de 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))

Recuperación de nombres de enumeraciones

A veces, es útil conocer el nombre de un campo de enumeración. Por ejemplo, cuando lees objetos de la API, es posible que quieras saber a qué estado de la campaña se corresponde el 3 entero:

Mensajes de proto+
campaign = client.get_type("Campaign")
campaign.status = client.enums.CampaignStatusEnum.PAUSED
# To read the name of campaign status
print(campaign.status.name)
Mensajes de 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

Como se describe en los documentos proto-plus, los campos repetidos, en general, son equivalentes a listas enumeradas, lo que significa que se comportan casi de forma idéntica a list.

Cómo anexar a campos escalares repetidos

Cuando se agregan valores a campos repetidos de tipo escalar, por ejemplo, campos string o int64, la interfaz es la misma, independientemente del tipo de mensaje:

Mensajes de proto+
ad.final_urls.append("https://www.example.com")
Mensajes de protobuf
ad.final_urls.append("https://www.example.com")

Esto incluye todos los demás métodos list comunes, por ejemplo, extend:

Mensajes de proto+
ad.final_urls.extend(["https://www.example.com", "https://www.example.com/2"])
Mensajes de protobuf
ad.final_urls.extend(["https://www.example.com", "https://www.example.com/2"])

Agrega tipos de mensajes a campos repetidos

Si el campo repetido no es un tipo escalar, el comportamiento de agregarlo a los campos repetidos es un poco diferente:

Mensajes de proto+
frequency_cap = client.get_type("FrequencyCapEntry")
frequency_cap.cap = 100
campaign.frequency_caps.append(frequency_cap)
Mensajes de protobuf
# The add method initializes a message and adds it to the repeated field
frequency_cap = campaign.frequency_caps.add()
frequency_cap.cap = 100

Cómo asignar campos repetidos

En el caso de los campos repetidos y no escalares, puedes asignar listas al campo de diferentes maneras:

Mensajes de proto+
# In proto-plus it's possible to use assignment.
urls = ["https://www.example.com"]
ad.final_urls = urls
Mensajes de 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

Mensajes vacíos

A veces, es útil saber si una instancia de mensaje contiene información o si tiene alguno de sus campos configurados.

Mensajes de proto+
# When using proto-plus messages you can simply check the message for
# truthiness.
is_empty = bool(campaign)
is_empty = not campaign
Mensajes de protobuf
is_empty = campaign.ByteSize() == 0

Texto del mensaje

Para los mensajes proto-plus y protobuf, te recomendamos que uses el método auxiliar copy_from en el objeto GoogleAdsClient:

client.copy_from(campaign, other_campaign)

Campos de mensaje vacíos

El proceso para configurar campos de mensaje vacíos es el mismo, independientemente del tipo de mensaje que uses. Solo tienes que copiar un mensaje vacío en el campo en cuestión. Consulta la sección Texto del mensaje y la guía Campos de mensajes vacíos. A continuación, se muestra un ejemplo de cómo configurar un campo de mensaje vacío:

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

Nombres de campos con palabras reservadas

Cuando usas mensajes proto+, los nombres de campo aparecen automáticamente con un guion bajo final si el nombre también es una palabra reservada en Python. A continuación, se muestra un ejemplo de cómo trabajar con una instancia Asset:

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

La lista completa de nombres reservados se crea en el módulo generador gapic. También se puede acceder de manera programática.

Primero, instala el módulo:

python -m pip install gapic-generator

Luego, en una secuencia de comandos o REPL de Python:

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

Presencia de campo

Debido a que los campos de las instancias de mensajes de protobuf tienen valores predeterminados, no siempre es intuitivo saber si un campo se configuró o no.

Mensajes de proto+
# Use the "in" operator.
has_field = "name" in campaign
Mensajes de protobuf
campaign = client.get_type("Campaign")
# Determines whether "name" is set and not just an empty string.
campaign.HasField("name")

La interfaz de la clase Message de protobuf tiene un método HasField que determina si el campo en un mensaje se configuró, incluso si se estableció en un valor predeterminado.

Métodos de mensaje de Protobuf

La interfaz de mensajes de protobuf incluye algunos métodos convenientes que no forman parte de la interfaz proto-plus. Sin embargo, es sencillo acceder a ellos convirtiendo su mensaje proto-plus a su equivalente de 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())

Herramienta de seguimiento de problemas

Si tienes alguna pregunta sobre estos cambios o cualquier problema para migrar a la versión 14.0.0 de la biblioteca, informa un problema en nuestra herramienta de seguimiento.