Skip to content

AttributeError: 'Credentials' object has no attribute 'request' in Google Sheets API script #2537

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Demianeen opened this issue Dec 18, 2024 · 2 comments
Assignees

Comments

@Demianeen
Copy link

Demianeen commented Dec 18, 2024

I wanted to write a small Python script for myself, but found that whenever I use Sheets service I get the following error:
AttributeError: 'Credentials' object has no attribute 'request'.

I am relatively new to Python, so bear with me if I missed something obvious.

Environment details

  • OS type and version: MacOS 15.1.1 (24B91)
  • Chip: M1 Pro
  • Python version: 3.12.6
  • poetry version: 1.8.4
  • google-api-python-client version: 2.155.0

Steps to reproduce

  1. poetry new app
  2. poetry add google-api-python-client google-auth-httplib2 google-auth-oauthlib
  3. Add credentials.json file
  4. Add code from Code example section
  5. poetry run python -m app

Code example

app/__main__.py:

import os.path
from io import BytesIO

from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.http import MediaIoBaseUpload
from googleapiclient.discovery import Resource, build

SCOPES = [
    "https://www.googleapis.com/auth/documents",
    "https://www.googleapis.com/auth/drive",
    "https://www.googleapis.com/auth/spreadsheets",
]


def authenticate(
    scopes: list[str],
    *,
    token_filepath: str = "token.json",
    credentials_filepath: str = "credentials.json",
):
    creds = None

    if os.path.exists(token_filepath):
        creds = Credentials.from_authorized_user_file(token_filepath)

        if not creds.has_scopes(scopes):
            print("Token scopes do not match the required scopes. Regenerating token.")
            os.remove(token_filepath)
            creds = None

    # If there are no credentials available or not all scopes are avaliable, let the user log in.
    if not creds:
        print("No credentials available. Requesting new token")
        flow = InstalledAppFlow.from_client_secrets_file(credentials_filepath, scopes)
        creds = flow.run_local_server(port=0)
        # Save the credentials for the next run
        with open(token_filepath, "w") as token:
            token.write(creds.to_json())

    if not creds.valid and creds.expired and creds.refresh_token:
        creds.refresh(Request())

    return creds


if __name__ == "__main__":
    creds = authenticate(SCOPES)
    sheets_service = build("sheets", "v4", creds)

    spreadsheet = {"properties": {"title": "test"}}
    spreadsheet = ( # <--- error occurs because of this line
        sheets_service.spreadsheets()
        .create(body=spreadsheet, fields="spreadsheetId")
        .execute()
    )

Stack trace

~/projects/repros/app ❯ poetry run python -m app
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/Users/demian/projects/repros/app/app/__main__.py", line 56, in <module>
    .execute()
     ^^^^^^^^^
  File "/Users/demian/Library/Caches/pypoetry/virtualenvs/app-PJXYrkgl-py3.12/lib/python3.12/site-packages/googleapiclient/_helpers.py", line 130, in positional_wrapper
    return wrapped(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/demian/Library/Caches/pypoetry/virtualenvs/app-PJXYrkgl-py3.12/lib/python3.12/site-packages/googleapiclient/http.py", line 923, in execute
    resp, content = _retry_request(
                    ^^^^^^^^^^^^^^^
  File "/Users/demian/Library/Caches/pypoetry/virtualenvs/app-PJXYrkgl-py3.12/lib/python3.12/site-packages/googleapiclient/http.py", line 191, in _retry_request
    resp, content = http.request(uri, method, *args, **kwargs)
                    ^^^^^^^^^^^^
AttributeError: 'Credentials' object has no attribute 'request'```

Poetry dependencies:

[tool.poetry.dependencies]
python = "^3.12"
google-api-python-client = "^2.155.0"
google-auth-httplib2 = "^0.2.0"
google-auth-oauthlib = "^1.2.1"
@0x78f1935
Copy link

Hi,

First I thought perhaps your issue was related to my issue, but diving deeper into your problem I don't think that was the case.
The reason why I had to dive deeper into your problem is because you are using a different authentication scope then I do.

In addition, you also use a service account, and I use an access token.

I think this is important information. Oh, also, I'm just shooting here, I'm not sure if this is the reason why your Credentials don't work. But let me share anyways:

The first thing that I noticed, was that your scope difference from mine. Mine looks like this

email profile https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile openid

The scope is obtained from the access token in my single sign on flow. I had to know if you simply forgot some keywords in your scope, since I have email, profile and openid. So I checked the docs about scopes.

In my honest opinion, google docs are a bit strange and overwhelming, but you can click the Apps Script API, v1 title above the table where your scopes are located.

It opens this page, with a very annoying warning;
Warning: The Apps Script API does not work with [service accounts](https://developers.google.com/identity/protocols/OAuth2ServiceAccount).

My guess, is that your Credentials have no request attribute because service accounts probably cannot use the API at all. Which explains why there is no request attribute.

I do think the library should give you a proper warning / error when this happens if I'm correct
I wouldn't mind a second opinion to confirm my way of thoughts

@Demianeen
Copy link
Author

@0x78f1935 thanks! You gave me idea to check if quickstart code works for me, and it did! Then I started to partially replace it with my code and found that the issue is in how I pass creds to build command, I passed it as normal argument instead of named one:
build("sheets", "v4", creds) -> build("sheets", "v4", credentials=creds)

This small typing mismatch caused the entire error.

That brings me to another important point: type safety seems lacking in the google-api-python-client library. The build function doesn't specify parameter types, making it hard to catch these mistakes. I discovered there is a google-api-python-client-stubs package that solves this issue, but it’s not mentioned in the docs.

Original types:
CleanShot 2024-12-19 at 07 27 51@2x

After installing type stubs:
CleanShot 2024-12-19 at 07 45 20@2x

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants