Thanks to visit codestin.com
Credit goes to github.com

Skip to content

AttributeError: Assignment not allowed to oneof field 'create' on OfflineUserDataJobOperation despite HasField reporting False (v27.0.0, protobuf 4.25.8) #1012

@toarndt

Description

@toarndt

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:

  1. Create a Python 3.11 virtual environment.

  2. 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):

  1. (Identical setup as above)

  2. 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")
  1. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingtriageNew issue; requires attention

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions