Migration to Library Version 10.0.0

In version 10.0.0 of the Python client library we've migrated to a new code generator that outputs significantly different service client and protobuf message class interfaces. The primary goal of this migration is to provide an improved user experience. However, a number of breaking changes have been introduced, which are described in detail below.

If you'd like more context beyond the interface changes in this guide, take a look at the proto-plus-python module, which provides the wrapper for protobuf message classes that enable much of the new behavior. See the associated Proto Plus for Python documentation.

Module path

For consistency with other Google Ads client library conventions that do not use an additional underscore, the module path has changed from:

import google.ads.google_ads

to:

import google.ads.googleads

Service client method signatures

All service client method signatures have changed and can be used in one of two ways:

Keyword params

All positional params have been replaced by keyword params, for example:

response = googleads_service.search(customer_id, query)

Must now be:

response = googleads_service.search(customer_id=customer_id, query=query)

Request objects

It's also possible to construct a request object outside of a service method and pass it in as a single param, rather than passing in individual field values as keyword params.

This style is required if your request uses any optional fields in the request object. To determine if a request object field is required or optional, you can reference the proto definitions for services and look for fields that contain the annotation [(google.api.field_behavior) = REQUIRED]. For example, to set the optional page_size field on a GoogleAdsService.Search request, do the following:

request = client.get_type("SearchGoogleAdsRequest")
request.customer_id = customer_id
request.query = query
request.page_size = 10000

response = googleads_service.search(request=request)

Protobuf message interface changes

With proto-plus-python, the interface for protobuf messages has been updated so that the objects behave more like conventional Python objects. You can read more about the new behavior in the proto-plus documentation, but here we'll highlight some key changes that affect common use cases for this client library.

Wrapped versus native protobuf messages

The proto-plus-python library augments the behavior of native protobuf message instances (those seen in versions prior to 10.0.0 of this library) by wrapping them in new classes.

Since the wrapper class delegates attribute retrieval to the native protobuf message it contains, these wrappers are extremely cautious about exposing instance-level methods, and instead add methods as class-only. In order to access some of these methods you can use syntax that looks like this:

campaign = client.get_type("Campaign")
# Here the Campaign's class-level method `deserialize` is invoked to deserialize
# a serialized campaign message.
type(campaign).deserialize(serialized_campaign)

In some cases it may be necessary to access the native protobuf message class from the wrapper:

native_campaign = type(campaign).pb(campaign)

This invokes the class-level pb method to retrieve the native protobuf message from the passed-in Campaign instance. This method offers the ability to coerce objects into the specified type.

An alternative style, which is used behind the scenes when calling the pb method described above, involves accessing the private _pb attribute on the wrapper, for example:

native_campaign = campaign._pb

We use the second style in our code examples because it's easier to read.

Field masks

The field mask helper method provided by api-core is designed to use native protobuf message instances. So we need to pass those into the method in either of the ways described above:

from google.api_core.protobuf_helpers import field_mask

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

Enums

Wrapped enum messages are instances of Python's native enum type and therefore inherit a number of convenience methods.

Retrieving enums

When using get_type, it's now necessary to access the inner enum message on the outer message object. Previously the syntax looked like this:

val = client.get_type("CampaignStatus").PAUSED

Now it's necessary to access the values like this:

# Note the additional "CampaignStatus" attribute being accessed.
val = client.get_type("CampaignStatus").CampaignStatus.PAUSED

The new version of the library includes a new, simpler way to access Enums. This style is functionally identical to using get_type and is the recommended style to use:

val = client.enums.CampaignStatus.PAUSED

Retrieving enum values

If you want to know the value, or field ID, of a given Enum, you would previously have needed to do this:

campaign = client.get_type("Campaign")
status_enum = client.get_type("CampaignStatusEnum")
campaign.status = status_enum.PAUSED
# To read the value of campaign status
status_enum.CampaignStatus.Value(campaign.status)

Now you can simply do this:

campaign = client.get_type("Campaign")
campaign.status = client.enums.CampaignStatusEnum.PAUSED
# To read the value of campaign status
campaign.status.value

Retrieving enum names

Sometimes it's useful to know the name of an Enum field. For example, when reading objects from the API you might want to know which campaign status the int 3 corresponds to. Previously, to get such information you needed to do:

campaign = client.get_type("Campaign")
status_enum = client.get_type("CampaignStatusEnum")
campaign.status = status_enum.PAUSED
# To read the name of campaign status
status_enum.CampaignStatus.Name(campaign.status)

Now you can simply do:

campaign = client.get_type("Campaign")
campaign.status = client.enums.CampaignStatusEnum.PAUSED
# To read the value of campaign status
campaign.status.name

Repeated fields

As described in the proto-plus documentation, repeated fields are generally equivalent to typed lists, which means that they behave almost identically to a list. Previously, when inserting new items into them, you would do:

final_url = ad.final_urls.add()
final_url = "test"

Now items are added using the append method, as you would do with a list:

ad.final_urls.append("test")

This includes all other common list methods as well, for example extend:

ad.final_urls.extend(["test", "foo", "bar"])

If the repeated field is not a scalar type, then items must be defined before being appended, most likely by calling get_type to retrieve a new instance:

frequency_cap = client.get_type("FrequencyCapEntry")
frequency_cap.cap = 100
campaign.frequency_caps.append(frequency_cap)

Checking for empty messages

To know whether a protobuf message instance contains any information, or has any of its fields set, you can now test the object for truthiness. Previously you would need to check that its byte count was zero, for example:

is_empty = campaign.ByteSize() == 0

Now you can simply use one of the following:

# Cast to a bool
is_empty = bool(campaign)
# Or, use logical operators
is_empty = not campaign

Other tests for truthiness apply, so the above examples are not exhaustive.

Copying messages

Copying a message previously required calling the CopyFrom method on a native protobuf message with another message of the same type. For example:

campaign.CopyFrom(other_campaign)

We've added a convenience method to GoogleAdsClient that consolidates this behavior for both native protobuf messages and messages wrapped by proto-plus:

client.copy_from(campaign, other_campaign)

Setting empty message fields

The process for setting empty message fields is the same as it was in the previous codebase: you need to copy an empty message into the field in question. The difference is in copying messages (see the Copying messages section as well as the Empty Message Fields guide). Here's an example of how to set an empty message field:

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

Field names that are reserved words

If a protobuf field name is also a reserved word in Python, that field's name will contain a trailing underscore when the message object is initialized. Here's an example of working with an Asset instance:

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

The full list of reserved names is constructed in the gapic generator module. It can be accessed programmatically as well. First, install the module:

python -m pip install gapic-generator

Then in a Python repl or script:

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

Using the native HasField method

The native protobuf Message interface has a HasField method that determined whether the field on a message has been set. Because the fields on protobuf message instances have default values, it's not always intuitive to know whether a field has been set or not. In the previous version of our library you would need to do this:

campaign = client.get_type("Campaign")
# Determines whether "name" is set and not just an empty string.
campaign.HasField("name")

Now this can be done using the in operator:

has_field = "name" in campaign

Accessing any other native method

As mentioned in the Wrapped versus native protobuf messages section, you can access the native version of any wrapped protobuf instance if needed, making it easy to use any native protobuf functionality:

# Accessing the ListFields method
type(campaign).pb(campaign).ListFields()

# Accessing the Clear method
type(campaign).pb(campaign).Clear()

Issue tracker

If you have any questions about these changes or any problems migrating to version 10.0.0 of the library, file an issue on our tracker.