-
Notifications
You must be signed in to change notification settings - Fork 509
Description
Describe the bug:
When attempting to assign to the create field of an OfflineUserDataJobOperation protocol buffer object (which is part of a oneof field including remove and remove_all), an AttributeError occurs, despite HasField() explicitly reporting that none of the oneof fields (create, remove, remove_all) are currently set on the object.
This issue is reproducible on both a Databricks worker (DBR 15.4, Python 3.11) and a clean local virtual environment (Python 3.11).
Steps to Reproduce:
-
Create a Python 3.11 virtual environment.
-
Install the following library versions:
google-ads==27.0.0
protobuf==4.25.8
grpcio==1.60.0
googleapis-common-protos==1.63.0
google-api-core==2.18.0
google-auth==2.29.0
proto-plus==1.23.0
3.Use the following minimal Python script (replace placeholders with valid Google Ads API credentials that can access customers):
from google.ads.googleads.client import GoogleAdsClient
import os
config_dict = {
#REDACTED login/account information
"use_proto_plus": False
}
client = GoogleAdsClient.load_from_dict(config_dict=config_dict, version="v18")
try:
# TEST 1: Verify basic client connectivity (Works correctly)
print("Attempting to get Accessible Customers...")
customer_service = client.get_service("CustomerService")
accessible_customers = customer_service.list_accessible_customers()
print(f"Successfully listed accessible customers: {accessible_customers.resource_names[:5]}...")
print("-" * 30)
# TEST 2: The problematic OfflineUserDataJobOperation (Fails)
print("Attempting to get OfflineUserDataJobOperation...")
operation = client.get_type("OfflineUserDataJobOperation")
print(f"Initial operation.HasField('create'): {operation.HasField('create')}")
print(f"Initial operation.HasField('remove'): {operation.HasField('remove')}")
print(f"Initial operation.HasField('remove_all'): {operation.HasField('remove_all')}")
user_data = client.get_type("UserData")
user_identifier = client.get_type("UserIdentifier")
user_identifier.hashed_email = "[email protected]"
user_data.user_identifiers.append(user_identifier)
print("Attempting to assign operation.create = user_data")
operation.create = user_data # <--- THIS LINE FAILS
print("Successfully assigned operation.create!")
print(f"Final operation.HasField('create'): {operation.HasField('create')}")
except AttributeError as e:
print(f"AttributeError: {e}")
except Exception as e:
print(f"An unexpected error occurred: {e}")
Observed Output:
Attempting to get Accessible Customers...
Successfully listed accessible customers: ['customers/XXXXXXXXXX', 'customers/YYYYYYYYYY', ...]
------------------------------
Attempting to get OfflineUserDataJobOperation...
Initial operation.HasField('create'): False
Initial operation.HasField('remove'): False
Initial operation.HasField('remove_all'): False
Attempting to assign operation.create = user_data
AttributeError: Assignment not allowed to message, map, or repeated field "create" in protocol message object.
Expected behavior:
The assignment operation.create = user_data should succeed without an AttributeError since HasField() explicitly reports that no field within the oneof (including create itself) is currently set.
Client library version and API version:
Client library version: 27.0.0
Google Ads API version: v18
Output of pip freeze:
cachetools==5.5.2
certifi==2025.6.15
charset-normalizer==3.4.2
google-ads==27.0.0
google-api-core==2.18.0
google-auth==2.29.0
google-auth-oauthlib==1.2.2
googleapis-common-protos==1.63.0
grpcio==1.60.0
grpcio-status==1.60.0
idna==3.10
oauthlib==3.3.1
proto-plus==1.23.0
protobuf==4.25.8
pyasn1==0.6.1
pyasn1_modules==0.4.2
PyYAML==6.0.2
requests==2.32.4
requests-oauthlib==2.0.0
rsa==4.9.1
urllib3==2.5.0
Request/Response Logs:
n/a
Anything else we should know about your project / environment:
Python Version: 3.11.x (tested on 3.11.x locally, and Databricks DBR 15.4 which uses 3.11)
Google Ads API Python Client Library Version: 27.0.0
Protobuf Version: 4.25.8
grpcio Version: 1.60.0
Google Ads API Version targeted: v18
OS: Linux (Databricks + local testing)
Additional Finding / Potential Workaround
Further testing has revealed a potential workaround for this issue: if use_proto_plus is set to True during GoogleAdsClient initialization, the AttributeError on OfflineUserDataJobOperation.create does not occur, and the assignment succeeds.
Steps to Reproduce (Successful with Workaround):
-
(Identical setup as above)
-
Modify the client initialization to:
config_dict = {
# REDACTED login/account config
"use_proto_plus": True # <--- CHANGED TO TRUE
}
client = GoogleAdsClient.load_from_dict(config_dict=config_dict, version="v18")
- Execute the same minimal test script (removing the HasField calls, as they raise AttributeError for proto-plus objects, which is expected behavior).
Observed Output with Workaround
Attempting to get Accessible Customers...
Successfully listed accessible customers: ['customers/XX', 'customers/XX', 'customers/37314XX04745', 'customers/XX', 'customers/XX']
------------------------------
Attempting to get OfflineUserDataJobOperation...
Attempting to assign operation.create = user_data
Successfully assigned operation.create!
Implication
This suggests the bug might reside in the interaction between the protobuf library's HasField method (or its internal logic) and the oneof implementation for OfflineUserDataJobOperation specifically when using classic protobuf.message.Message objects (use_proto_plus=False). The proto-plus wrapper seems to handle or mask this inconsistency.