#!/usr/bin/env python
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Retrieves customers under a mcc manager and splits them according to their account_type."""
import argparse
import traceback
from typing import Any
from google.ads.searchads360.v0.enums.types.account_level import AccountLevelTypeEnum
from google.ads.searchads360.v0.services.types.search_ads360_service import SearchSearchAds360StreamRequest
from google.api_core import exceptions
from util_searchads360 import SearchAds360Client
def _map_data(data, data_dictionary) -> None:
"""Inserts customer data into its corresponding dictionary using id as the key."""
if data:
data_dictionary[data['id']] = data
def _prepare_sub_manager_data(customer) -> dict[str, Any]:
"""Using the customer object, prepares sub-manager info."""
sub_manager_id = customer.sub_manager_id
sub_managers_info = {
'id': sub_manager_id,
'name': customer.sub_manager_descriptive_name,
'type': AccountLevelTypeEnum.AccountLevelType.SUB_MANAGER.name,
'parent_id': customer.manager_id,
}
return sub_managers_info
def _prepare_associate_manager_data(customer) -> dict[str, Any] | None:
"""Using the customer object, prepares associate-manager info."""
associate_manager_id = customer.associate_manager_id
associate_manager_id_descriptive_name = str(
customer.associate_manager_descriptive_name
)
if not associate_manager_id_descriptive_name or associate_manager_id == 0:
return None
associate_manager_info = {
'id': associate_manager_id,
'name': associate_manager_id_descriptive_name,
'type': AccountLevelTypeEnum.AccountLevelType.ASSOCIATE_MANAGER.name,
'parent_id': customer.sub_manager_id,
}
return associate_manager_info
def _prepare_client_customer_data(customer) -> dict[str, Any]:
"""Using the customer object, prepares client-customer info."""
client_customer_id = customer.id
# Determine parent ID for the client account
parent_id = (
customer.associate_manager_id
or customer.sub_manager_id
or customer.manager_id
)
client_customer_info = {
'id': client_customer_id,
'name': customer.descriptive_name,
'type': customer.account_type.name,
'parent_id': parent_id,
}
return client_customer_info
def _get_all_accounts_details(
search_ads_360_service,
client_customer_id,
sub_managers_data,
associate_managers_data,
client_accounts_data,
) -> None:
"""Fetches details for a client customer and updates hierarchy maps."""
query = """
SELECT
customer.id,
customer.descriptive_name,
customer.account_type,
customer.manager_id,
customer.manager_descriptive_name,
customer.sub_manager_id,
customer.sub_manager_descriptive_name,
customer.associate_manager_id,
customer.associate_manager_descriptive_name,
customer.account_level
FROM customer"""
request = SearchSearchAds360StreamRequest()
request.customer_id = client_customer_id
request.query = query
try:
results_stream = search_ads_360_service.search_stream(request=request)
for response in results_stream:
for row in response.results:
customer = row.customer
_map_data(_prepare_sub_manager_data(customer), sub_managers_data)
_map_data(
_prepare_associate_manager_data(customer), associate_managers_data
)
_map_data(_prepare_client_customer_data(customer), client_accounts_data)
except exceptions.GoogleAPICallError as e:
print(
'ERROR: An unexpected error occurred while fetching customer'
f' information for {client_customer_id}.\nDetails: {e}\n'
)
def _get_client_customer_ids(search_ads_360_service, customer_id) -> list[str]:
"""Retrieves a list of client customer IDs (if the customer is a manager).
Args:
search_ads_360_service: The Search Ads 360 service client.
customer_id: The customer ID of the mcc manager customer.
Returns:
A list of client customer IDs.
"""
request = SearchSearchAds360StreamRequest()
query = """
SELECT
customer_client.id,
customer_client.manager
FROM customer_client
WHERE customer_client.manager = False
"""
customer_client_ids = []
request.customer_id = customer_id
request.query = query
try:
# Issues a search stream request.
results_stream = search_ads_360_service.search_stream(request=request)
for response in results_stream:
for row in response.results:
customer_client_ids.append(str(row.customer_client.id))
except exceptions.GoogleAPICallError as e:
print(
'ERROR: An unexpected error occurred while fetching client customer IDs'
f' for {customer_id}.\nDetails: {e}\n'
)
return customer_client_ids
def main(service_client, login_customer_id) -> None:
search_ads_360_service = service_client.get_service()
sub_managers_data = {}
associate_managers_data = {}
client_accounts_data = {}
# Get all client customer IDs under the login_customer_id (MCC manager)
customer_client_ids = _get_client_customer_ids(
search_ads_360_service, login_customer_id
)
for customer_client_id in customer_client_ids:
_get_all_accounts_details(
search_ads_360_service,
customer_client_id,
sub_managers_data,
associate_managers_data,
client_accounts_data,
)
if __name__ == '__main__':
# SearchAds360Client will read the search-ads-360.yaml configuration file in
# the home directory if none is specified.
search_ads_360_client = SearchAds360Client.load_from_file()
parser = argparse.ArgumentParser(
description=(
'Retrieves all customer_client for a mcc manager customer and runs a'
' query on each customer_client.'
)
)
# Arguments to provide to run the example.
parser.add_argument(
'-l',
'--login_customer_id',
type=str,
required=True,
help=(
'The Search Ads 360 MCC manager login customer ID (10 digits, no'
' dashes). This can be obtained from the UI or from calling'
' ListAccessibleCustomers - '
'https://developers.google.com/search-ads/reporting/concepts/login-customer-id.'
),
)
args = parser.parse_args()
search_ads_360_client.set_ids(args.login_customer_id, args.login_customer_id)
try:
main(search_ads_360_client, args.login_customer_id)
except Exception: # pylint: disable=broad-except
traceback.print_exc()