"'Credentials' object has no attribute 'access_token'" when using google-auth with gspread
Asked Answered
W

4

6

I'd like to use gspread module to edit Google sheets from Python. The setup instructions contain the following example:

import gspread
from oauth2client.service_account import ServiceAccountCredentials

scope = ['https://spreadsheets.google.com/feeds',
         'https://www.googleapis.com/auth/drive']

credentials = ServiceAccountCredentials.from_json_keyfile_name('gspread-april-2cd … ba4.json', scope)

gc = gspread.authorize(credentials)

However, according to https://pypi.org/project/oauth2client/ the oauth2client library is deprecated. So I've tried to adapt this as follows, using google-auth:

import gspread
from google.oauth2 import service_account

credentials = service_account.Credentials.from_service_account_file(
    'my_client_secrets.json')

scoped_credentials = credentials.with_scopes(
    ['https://www.googleapis.com/auth/spreadsheets'])

gc = gspread.authorize(scoped_credentials)

Unfortunately, I'm running into the following error:

(lucy-web-CVxkrCFK) bash-3.2$ python nps.py
Traceback (most recent call last):
  File "nps.py", line 54, in <module>
    gc = gspread.authorize(scoped_credentials)
  File "/Users/kurtpeek/.local/share/virtualenvs/lucy-web-CVxkrCFK/lib/python3.7/site-packages/gspread/__init__.py", line 38, in authorize
    client.login()
  File "/Users/kurtpeek/.local/share/virtualenvs/lucy-web-CVxkrCFK/lib/python3.7/site-packages/gspread/client.py", line 46, in login
    if not self.auth.access_token or \
AttributeError: 'Credentials' object has no attribute 'access_token'

If I drop into the debugger, I indeed see that credentials has a token attribute, but not an access_token one:

> /Users/kurtpeek/Documents/Dev/lucy2/lucy-web/scripts/nps.py(54)<module>()
     53 import ipdb; ipdb.set_trace()
---> 54 gc = gspread.authorize(scoped_credentials)
     55 

ipdb> type(credentials)
<class 'google.oauth2.service_account.Credentials'>
ipdb> type(scoped_credentials)
<class 'google.oauth2.service_account.Credentials'>
ipdb> dir(credentials)
['__abstractmethods__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_abc_impl', '_additional_claims', '_from_signer_and_info', '_make_authorization_grant_assertion', '_project_id', '_scopes', '_service_account_email', '_signer', '_subject', '_token_uri', 'apply', 'before_request', 'expired', 'expiry', 'from_service_account_file', 'from_service_account_info', 'has_scopes', 'project_id', 'refresh', 'requires_scopes', 'scopes', 'service_account_email', 'sign_bytes', 'signer', 'signer_email', 'token', 'valid', 'with_claims', 'with_scopes', 'with_subject']

Are the Credentials generated by google-auth not the same objects as the ones generated by oauth2client?

Wellborn answered 31/7, 2018 at 16:32 Comment(0)
E
5

As per the gspread documentation, the gspread.authorize method only supports credential objects that are created by the oauth2client library. To work with the new google-auth one, gspread should add support for it.

A possible workaroud, if you don't want to use the oauthclient2 that is deprecated, is to use authlib leveraging the session parameter of the gspread.Client class. There is a nice tutorial on how to do this here.

Update April 27 2020

Starting from version 3.4.0 gspread is now supporting google-auth. You can find all the details in the dedicated documentation. Here is an official statement from the author:

Older versions of gspread have used oauth2client. Google has deprecated it in favor of google-auth. If you’re still using oauth2client credentials, the library will convert these to google-auth for you, but you can change your code to use the new credentials to make sure nothing breaks in the future.

Eristic answered 12/8, 2018 at 12:46 Comment(3)
Since this was posted, GSpread has added access for the new google-auth credentials: gspread.readthedocs.io/en/latest/oauth2.htmlSunbow
@Sunbow is correct. gspread has been using google-auth since version 3.4.0.Inflammable
@Inflammable and Alec (Stackoverflow only allow me to have one tag) Thanks for pointing out the news. I've updated my answer accordingly, let me know if it looks good to you.Eristic
W
5

A year and a half later in quite the same situation, I found this to work for me:

import gspread
from google.oauth2 import service_account
from google.auth.transport.requests import AuthorizedSession

credentials = service_account.Credentials.from_service_account_file(
    'your_key_file.json')

scoped_credentials = credentials.with_scopes(
        ['https://spreadsheets.google.com/feeds',
         'https://www.googleapis.com/auth/drive']
        )

gc = gspread.Client(auth=scoped_credentials)
gc.session = AuthorizedSession(scoped_credentials)
sheet = gc.open_by_key('key_in_sharelink')
print(sheet.title)

Solution was adapted from this post in gspread github and the google-auth user guide.

Whalebone answered 11/1, 2020 at 21:59 Comment(1)
It also worked for me replacing oauth2client usage with google.oauth2. Thanks!Monde
H
0

As a band-aide fix, you can create a new class that inherits the credentials and adds a new access_token property that mirrors the token in the credentials. Then pass that into gspread.authorize and it should work.

# create the new class to fix credentials
class fixed_creds(service_account.Credentials):
    def __init__(self, creds):
        self.access_token = creds.token

# create new credential object with the access_token
gcreds = fixed_creds(credentials)

# pass new credentials into gspread
sheets = gspread.authorize(gcreds)

# create a new sheet to test it
new_sheet = sheets.create('TestSheet')

# give yourself permission to see the new sheet
sheets.insert_permission(
    new_sheet.id,
    '[email protected]',
    perm_type='user',
    role='writer'
)
Heriberto answered 13/10, 2018 at 16:40 Comment(0)
U
0

The band aid fix from Tom didnt work for me because token was initially None in Google OAuth2 library. Here's my band aid fix:

import gspread
import google.auth.transport.requests
from google.oauth2 import service_account
from oauth2client.service_account import ServiceAccountCredentials

class OAuth2ServiceAccountFromGoogleOAuth2(ServiceAccountCredentials):  # Hack based upon https://mcmap.net/q/1675553/-quot-39-credentials-39-object-has-no-attribute-39-access_token-39-quot-when-using-google-auth-with-gspread
    def __init__(self, google_oauth2_credentials):
        self.google_oauth2_credentials = google_oauth2_credentials
        self.access_token = google_oauth2_credentials.token

    def refresh(self, http):
        if self.access_token is None:
            request = google.auth.transport.requests.Request()
            self.google_oauth2_credentials.refresh(request)
            self.access_token = self.google_oauth2_credentials.token
        #end if

        print(f'access token in {self.access_token}')
    #end def
#end class

with open("credentials.json") as gs_key_file:
    google_credentials = service_account.Credentials.from_service_account_info(json.loads(gs_key_file.read()), scopes=['https://www.googleapis.com/auth/spreadsheets'])
gclient = gspread.authorize(OAuth2ServiceAccountFromGoogleOAuth2(google_credentials))
Unarmed answered 22/4, 2019 at 5:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.