2323from itertools import product
2424import multiprocessing
2525import time
26+ from typing import Any , Dict , Iterable , List , Tuple
2627
2728from google .ads .googleads .client import GoogleAdsClient
2829from google .ads .googleads .errors import GoogleAdsException
30+ from google .ads .googleads .v20 .errors .types import (
31+ ErrorLocation ,
32+ GoogleAdsError ,
33+ )
34+ from google .ads .googleads .v20 .services .services .google_ads_service import (
35+ GoogleAdsServiceClient ,
36+ )
37+ from google .ads .googleads .v20 .services .types import (
38+ GoogleAdsRow ,
39+ SearchGoogleAdsStreamResponse ,
40+ )
2941
3042# Maximum number of processes to spawn.
31- MAX_PROCESSES = multiprocessing .cpu_count ()
43+ MAX_PROCESSES : int = multiprocessing .cpu_count ()
3244# Timeout between retries in seconds.
33- BACKOFF_FACTOR = 5
45+ BACKOFF_FACTOR : int = 5
3446# Maximum number of retries for errors.
35- MAX_RETRIES = 5
47+ MAX_RETRIES : int = 5
3648
3749
38- def main (client , customer_ids ) :
50+ def main (client : GoogleAdsClient , customer_ids : List [ str ]) -> None :
3951 """The main method that creates all necessary entities for the example.
4052
4153 Args:
@@ -44,26 +56,29 @@ def main(client, customer_ids):
4456 """
4557
4658 # Define the GAQL query strings to run for each customer ID.
47- campaign_query = """
59+ campaign_query : str = """
4860 SELECT campaign.id, metrics.impressions, metrics.clicks
4961 FROM campaign
5062 WHERE segments.date DURING LAST_30_DAYS"""
51- ad_group_query = """
63+ ad_group_query : str = """
5264 SELECT campaign.id, ad_group.id, metrics.impressions, metrics.clicks
5365 FROM ad_group
5466 WHERE segments.date DURING LAST_30_DAYS"""
5567
56- inputs = generate_inputs (
68+ inputs : Iterable [ Tuple [ GoogleAdsClient , str , str ]] = generate_inputs (
5769 client , customer_ids , [campaign_query , ad_group_query ]
5870 )
5971 with multiprocessing .Pool (MAX_PROCESSES ) as pool :
6072 # Call issue_search_request on each input, parallelizing the work
6173 # across processes in the pool.
62- results = pool .starmap (issue_search_request , inputs )
74+ results : List [Tuple [bool , Dict [str , Any ]]] = pool .starmap (
75+ issue_search_request , inputs
76+ )
6377
6478 # Partition our results into successful and failed results.
65- successes = []
66- failures = []
79+ successes : List [Dict [str , Any ]] = []
80+ failures : List [Dict [str , Any ]] = []
81+ res : Tuple [bool , Dict [str , Any ]]
6782 for res in results :
6883 if res [0 ]:
6984 successes .append (res [1 ])
@@ -77,31 +92,37 @@ def main(client, customer_ids):
7792 )
7893
7994 print ("Successes:" ) if len (successes ) else None
95+ success : Dict [str , Any ]
8096 for success in successes :
8197 # success["results"] represents an array of result strings for one
8298 # customer ID / query combination.
83- result_str = "\n " .join (success ["results" ])
99+ result_str : str = "\n " .join (success ["results" ])
84100 print (result_str )
85101
86102 print ("Failures:" ) if len (failures ) else None
103+ failure : Dict [str , Any ]
87104 for failure in failures :
88- ex = failure ["exception" ]
105+ ex : GoogleAdsException = failure ["exception" ]
89106 print (
90107 f'Request with ID "{ ex .request_id } " failed with status '
91108 f'"{ ex .error .code ().name } " for customer_id '
92109 f'{ failure ["customer_id" ]} and query "{ failure ["query" ]} " and '
93110 "includes the following errors:"
94111 )
112+ error : GoogleAdsError
95113 for error in ex .failure .errors :
96114 print (f'\t Error with message "{ error .message } ".' )
97115 if error .location :
116+ field_path_element : ErrorLocation .FieldPathElement
98117 for (
99118 field_path_element
100119 ) in error .location .field_path_elements :
101120 print (f"\t \t On field: { field_path_element .field_name } " )
102121
103122
104- def issue_search_request (client , customer_id , query ):
123+ def issue_search_request (
124+ client : GoogleAdsClient , customer_id : str , query : str
125+ ) -> Tuple [bool , Dict [str , Any ]]:
105126 """Issues a search request using streaming.
106127
107128 Retries if a GoogleAdsException is caught, until MAX_RETRIES is reached.
@@ -111,27 +132,29 @@ def issue_search_request(client, customer_id, query):
111132 customer_id: a client customer ID str.
112133 query: a GAQL query str.
113134 """
114- ga_service = client .get_service ("GoogleAdsService" )
115- retry_count = 0
135+ ga_service : GoogleAdsServiceClient = client .get_service ("GoogleAdsService" )
136+ retry_count : int = 0
116137 # Retry until we've reached MAX_RETRIES or have successfully received a
117138 # response.
118139 while True :
119140 try :
120- stream = ga_service . search_stream (
121- customer_id = customer_id , query = query
141+ stream : Iterable [ SearchGoogleAdsStreamResponse ] = (
142+ ga_service . search_stream ( customer_id = customer_id , query = query )
122143 )
123144 # Returning a list of GoogleAdsRows will result in a
124145 # PicklingError, so instead we put the GoogleAdsRow data
125146 # into a list of str results and return that.
126- result_strings = []
147+ result_strings : List [str ] = []
148+ batch : SearchGoogleAdsStreamResponse
127149 for batch in stream :
150+ row : GoogleAdsRow
128151 for row in batch .results :
129- ad_group_id = (
152+ ad_group_id : str = (
130153 f"Ad Group ID { row .ad_group .id } in "
131154 if "ad_group.id" in query
132155 else ""
133156 )
134- result_string = (
157+ result_string : str = (
135158 f"{ ad_group_id } "
136159 f"Campaign ID { row .campaign .id } "
137160 f"had { row .metrics .impressions } impressions "
@@ -157,7 +180,11 @@ def issue_search_request(client, customer_id, query):
157180 )
158181
159182
160- def generate_inputs (client , customer_ids , queries ):
183+ def generate_inputs (
184+ client : GoogleAdsClient ,
185+ customer_ids : List [str ],
186+ queries : List [str ],
187+ ) -> Iterable [Tuple [GoogleAdsClient , str , str ]]:
161188 """Generates all inputs to feed into search requests.
162189
163190 A GoogleAdsService instance cannot be serialized with pickle for parallel
@@ -173,7 +200,7 @@ def generate_inputs(client, customer_ids, queries):
173200
174201
175202if __name__ == "__main__" :
176- parser = argparse .ArgumentParser (
203+ parser : argparse . ArgumentParser = argparse .ArgumentParser (
177204 description = "Download a set of reports in parallel from a list of "
178205 "accounts."
179206 )
@@ -192,11 +219,13 @@ def generate_inputs(client, customer_ids, queries):
192219 type = str ,
193220 help = "The login customer ID (optional)." ,
194221 )
195- args = parser .parse_args ()
222+ args : argparse . Namespace = parser .parse_args ()
196223
197224 # GoogleAdsClient will read the google-ads.yaml configuration file in the
198225 # home directory if none is specified.
199- googleads_client = GoogleAdsClient .load_from_storage (version = "v20" )
226+ googleads_client : GoogleAdsClient = GoogleAdsClient .load_from_storage (
227+ version = "v20"
228+ )
200229 # Override the login_customer_id on the GoogleAdsClient, if specified.
201230 if args .login_customer_id is not None :
202231 googleads_client .login_customer_id = args .login_customer_id
0 commit comments