@@ -64,9 +64,14 @@ def _get_client_kwargs_from_yaml(cls, yaml_str):
6464 token_uri = _DEFAULT_TOKEN_URI )
6565 credentials .refresh (google .auth .transport .requests .Request ())
6666
67+ login_customer_id = config_data .get ('login_customer_id' )
68+ login_customer_id = str (
69+ login_customer_id ) if login_customer_id else None
70+
6771 return {'credentials' : credentials ,
6872 'developer_token' : config_data ['developer_token' ],
69- 'endpoint' : config_data .get ('endpoint' )}
73+ 'endpoint' : config_data .get ('endpoint' ),
74+ 'login_customer_id' : login_customer_id }
7075 else :
7176 raise ValueError ('A required field in the configuration data was'
7277 'not found. The required fields are: %s'
@@ -142,17 +147,22 @@ def load_from_storage(cls, path=None):
142147
143148 return cls .load_from_string (yaml_str )
144149
145- def __init__ (self , credentials , developer_token , endpoint = None ):
150+ def __init__ (self , credentials , developer_token , endpoint = None ,
151+ login_customer_id = None ):
146152 """Initializer for the GoogleAdsClient.
147153
148154 Args:
149155 credentials: a google.oauth2.credentials.Credentials instance.
150156 developer_token: a str developer token.
151157 endpoint: a str specifying an optional alternative API endpoint.
158+ login_customer_id: a str specifying a login customer ID.
152159 """
160+ _validate_login_customer_id (login_customer_id )
161+
153162 self .credentials = credentials
154163 self .developer_token = developer_token
155164 self .endpoint = endpoint
165+ self .login_customer_id = login_customer_id
156166
157167 def get_service (self , name , version = _DEFAULT_VERSION ):
158168 """Returns a service client instance for the specified service_name.
@@ -192,7 +202,7 @@ def get_service(self, name, version=_DEFAULT_VERSION):
192202
193203 channel = grpc .intercept_channel (
194204 channel ,
195- MetadataInterceptor (self .developer_token ),
205+ MetadataInterceptor (self .developer_token , self . login_customer_id ),
196206 ExceptionInterceptor (),
197207 )
198208
@@ -247,32 +257,64 @@ def _get_request_id(self, trailing_metadata):
247257
248258 return None
249259
260+ def _handle_grpc_exception (self , exception ):
261+ """Handles gRPC exceptions of type RpcError by attempting to
262+ convert them to a more readable GoogleAdsException. Certain types of
263+ exceptions are not converted; if the object's trailing metadata does
264+ not indicate that it is a GoogleAdsException, or if it falls under
265+ a certain category of status code, (INTERNAL or RESOURCE_EXHAUSTED).
266+ See documentation for more information about gRPC status codes:
267+ https://github.com/grpc/grpc/blob/master/doc/statuscodes.md
250268
251-
252- def intercept_unary_unary (self , continuation , client_call_details , request ):
253- """Intercepts and wraps exceptions in the rpc response.
254-
255- Overrides abstract method defined in grpc.UnaryUnaryClientInterceptor.
269+ Args:
270+ exception: an exception of type RpcError.
256271
257272 Raises:
258- GoogleAdsException: if the Google Ads API response contains an
259- exception and GoogleAdsFailure is in the trailing metadata.
273+ GoogleAdsException: If the exception's trailing metadata
274+ indicates that it is a GoogleAdsException.
275+ RpcError: If the exception's trailing metadata is empty or is not
276+ indicative of a GoogleAdsException, or if the exception has a
277+ status code of INTERNAL or RESOURCE_EXHAUSTED.
260278 """
261- response = continuation (client_call_details , request )
262- ex = response .exception ()
263-
264- if ex and response ._state .code not in self ._RETRY_STATUS_CODES :
265- trailing_metadata = response .trailing_metadata ()
266- google_ads_failure = self ._get_google_ads_failure (trailing_metadata )
279+ if exception ._state .code not in self ._RETRY_STATUS_CODES :
280+ trailing_metadata = exception .trailing_metadata ()
281+ google_ads_failure = self ._get_google_ads_failure (
282+ trailing_metadata )
267283
268284 if google_ads_failure :
269285 request_id = self ._get_request_id (trailing_metadata )
270286
271287 raise google .ads .google_ads .errors .GoogleAdsException (
272- ex , response , google_ads_failure , request_id )
288+ exception , exception , google_ads_failure , request_id )
273289 else :
274290 # Raise the original exception if not a GoogleAdsFailure.
275- raise ex
291+ raise exception
292+ else :
293+ # Raise the original exception if error has status code
294+ # INTERNAL or RESOURCE_EXHAUSTED.
295+ raise exception
296+
297+ def intercept_unary_unary (self , continuation , client_call_details , request ):
298+ """Intercepts and wraps exceptions in the rpc response.
299+
300+ Overrides abstract method defined in grpc.UnaryUnaryClientInterceptor.
301+
302+ Raises:
303+ GoogleAdsException: If the exception's trailing metadata
304+ indicates that it is a GoogleAdsException.
305+ RpcError: If the exception's trailing metadata is empty or is not
306+ indicative of a GoogleAdsException, or if the exception has a
307+ status code of INTERNAL or RESOURCE_EXHAUSTED.
308+ """
309+ try :
310+ response = continuation (client_call_details , request )
311+ except grpc .RpcError as ex :
312+ self ._handle_grpc_exception (ex )
313+
314+ if response .exception ():
315+ # Any exception raised within the continuation function that is not
316+ # an RpcError will be set on the response object and raised here.
317+ raise response .exception ()
276318
277319 return response
278320
@@ -298,8 +340,11 @@ def intercept_unary_unary(self, continuation, client_call_details, request):
298340class MetadataInterceptor (grpc .UnaryUnaryClientInterceptor ):
299341 """An interceptor that appends custom metadata to requests."""
300342
301- def __init__ (self , developer_token ):
343+ def __init__ (self , developer_token , login_customer_id ):
302344 self .developer_token_meta = ('developer-token' , developer_token )
345+ self .login_customer_id_meta = (
346+ ('login-customer-id' , login_customer_id ) if login_customer_id
347+ else None )
303348
304349 def intercept_unary_unary (self , continuation , client_call_details , request ):
305350 """Intercepts and appends custom metadata.
@@ -313,6 +358,9 @@ def intercept_unary_unary(self, continuation, client_call_details, request):
313358
314359 metadata .append (self .developer_token_meta )
315360
361+ if self .login_customer_id_meta :
362+ metadata .append (self .login_customer_id_meta )
363+
316364 client_call_details = grpc ._interceptor ._ClientCallDetails (
317365 client_call_details .method , client_call_details .timeout , metadata ,
318366 client_call_details .credentials
@@ -336,3 +384,20 @@ def _get_version(name):
336384 raise ValueError ('Specified Google Ads API version "%s" does not exist.'
337385 % name )
338386 return version
387+
388+
389+ def _validate_login_customer_id (login_customer_id ):
390+ """Validates a login customer ID.
391+
392+ Args:
393+ login_customer_id: a str from config indicating a login customer ID.
394+
395+ Raises:
396+ ValueError: If the login customer ID is not
397+ an int in the range 0 - 9999999999.
398+ """
399+ if login_customer_id is not None :
400+ if not login_customer_id .isdigit () or len (login_customer_id ) != 10 :
401+ raise ValueError ('The specified login customer ID is invalid. '
402+ 'It must be a ten digit number represented '
403+ 'as a string, i.e. "1234567890"' )
0 commit comments