google-ads-python icon indicating copy to clipboard operation
google-ads-python copied to clipboard

Feature Request: Add Option to Cache Access Token

Open kritzware opened this issue 7 years ago • 5 comments

Hi,

I'm trying to get a basic example working, but keep receiving the same error:

...
    return _end_unary_response_blocking(state, call, True, None)
  File "/Users/Louis/anaconda3/lib/python3.6/site-packages/grpc/_channel.py", line 466, in _end_unary_response_blocking
    raise _Rendezvous(state, None, None, deadline)
grpc._channel._Rendezvous: <_Rendezvous of RPC that terminated with:
	status = StatusCode.UNAUTHENTICATED
	details = "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project."
	debug_error_string = "{"created":"@1537546478.122567000","description":"Error received from peer","file":"src/core/lib/surface/call.cc","file_line":1099,"grpc_message":"Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.","grpc_status":16}"

The example code is shown below:

import google.ads.google_ads.client

customer_id = "123"
client = google.ads.google_ads.client.GoogleAdsClient.load_from_storage(
    "google-ads.yaml")
ga_service = client.get_service("GoogleAdsService")

query = ('SELECT campaign.id FROM campaign ORDER BY campaign.id')
results = ga_service.search(customer_id, query)

try:
    for row in results:
        print('Campaign with ID %d and name "%s" was found.'
              % (row.campaign.id.value, row.campaign.name.value))
except google.ads.google_ads.errors.GoogleAdsException as ex:
    print('Request with ID "%s" failed with status "%s" and includes the '
          'following errors:' % (ex.request_id, ex.error.code().name))

My credentials file google-ads.yaml is set up as follows:

client_id: CLIENT_ID
client_secret: CLIENT_SECRET
refresh_token: REFRESH_TOKEN
developer_token: DEVELOPER_TOKEN 

I assume the GoogleAdsClient generates an access_token from the refresh_token? Am I misunderstanding how to send an authenticated request?

Thanks,

Louis

kritzware avatar Sep 21 '18 16:09 kritzware

Hello Louis,

Thanks for the report! I've attempted to reproduce your issue with the code you provided in both Python 2.7.13 and Python 3.5.3. Thus far, I have not been successful outside of intentionally misconfiguring my google-ads.yaml file. To that end, I was able to replicate the reported error by either specifying an incorrect developer token, or valid credentials for a Google account that lack access to the Google Ads account.

Aside from that, the only minor issue with your code snippet is that it does not include campaign.name in the query, so that wouldn't appear in the response. It otherwise appears to work intended on my machine with a fresh install on a virtualenv for the aforementioned Python versions.

client_id: CLIENT_ID client_secret: CLIENT_SECRET refresh_token: REFRESH_TOKEN developer_token: DEVELOPER_TOKEN

This looks correct, provided that CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, and DEVELOPER_TOKEN are placeholders. If the client_id, client_secret, and refresh_token values were not valid, you would receive an invalid grant error. Although, that doesn't mean that the Google account you're using to Authorize has access to the Google Ads account you're attempting to access.

I assume the GoogleAdsClient generates an access_token from the refresh_token? Am I misunderstanding how to send an authenticated request?

Yes, the refresh token will be used to produce the access token.

I'd suggest verifying the developer token, and that the Google account used to authorize in the OAuth 2.0 flow has access to the Google Ads account you're trying to access.

Regards, Mark

msaniscalchi avatar Sep 21 '18 18:09 msaniscalchi

Hi Mark,

Thanks for the quick response on Friday, and apologies for my later response today. It turns out that I was using the incorrect refresh_token in my google-ads.yaml. Now that I've changed that, the example works as expected.

Can the client.credentials.token (access token) be cached? Or is a new access token generated when creating a new client instance?

Thanks,

Louis

kritzware avatar Sep 24 '18 10:09 kritzware

Hey Louis,

Happy to hear you were able to sort that out! :-)

Can the client.credentials.token (access token) be cached? Or is a new access token generated when creating a new client instance?

It's not a feature we have implemented at this time, but we can consider it for a future update. Can you describe your use-case?

Regards, Mark

msaniscalchi avatar Sep 24 '18 14:09 msaniscalchi

It could be useful for when performing many operations/reporting queries with many different clients (i.e. different customer ids), thus meaning it doesn't have to refresh the access token for these client instances every time if it's before the cached token expiry date 👍

kritzware avatar Sep 25 '18 09:09 kritzware

This issue is on hold pending a decision to make this functionality available for all supported client libraries.

wihl avatar Apr 19 '21 15:04 wihl

Hi all -

I'm going to mark this feature request as closed without implementing it. The rationale is that it hasn't become a priority at the team level, it hasn't been requested in a long time, and it's possible to achieve this functionality without any changes to the library by passing in an initialized google.oauth2.credentials.Credentials instance when instantiating the GoogleAdsClient class:

Retrieving an access token

# Using an already initialized GoogleAdsClient instance.
access_token = client.credentials.token

Initializing with an access token

from google.ads.googleads.client import GoogleAdsClient
from google.oauth2.credentials import Credentials

# Provide the access token from the above snippet, or another location.
credentials = Credentials(token=access_token)
# Provide the developer token from your google-ads.yaml file.
client = GoogleAdsClient(credentials=credentials, developer_token=developer_token)
# Retrieve a service and use it as normal until the above access token expires.
googleads_service = client.get_service("GoogleAdsService")

BenRKarl avatar Jan 23 '23 16:01 BenRKarl