5

Im using GSpread trying to pass the content on my JSON file (Google API Service Application credentials) as a python Dictionary on my script. Im trying to not to carry a json file wherever I take my script.

I get the following error when I tried to pass a dictionary instead of a json file on the following line:

credentials = ServiceAccountCredentials.from_json_keyfile_name(auth_gdrive(), scope)

TypeError: expected str, bytes or os.PathLike object, not set

### auth_gdrive() returns a dictionary like this:

def auth_gdrive():
    dic = {
        "type": "miauuuuuu",
        "pass": "miauuuu"
    }

Im not allow to show whats really in the dic.

Alvaro Lamadrid
  • 355
  • 2
  • 13
  • the json file will be read as text, i don't think it inherently knows to translate the dictionary object to text – SuperStew Sep 19 '18 at 20:24
  • I think you should dig in the code of the `from_json_keyfile_name` method to see what it uses the parameter for/what it does, or look for alternative method, maybe `from_json_keyfile_dict` – Lohmar ASHAR Sep 19 '18 at 20:32

4 Answers4

10

Since I wanted to pass the credentials details from within my application , and not from a json file I couldn't use:

ServiceAccountCredentials.from_json_keyfile_name()

from_json_keyfile_name() expects a json file. But looking into the docs I found the following:

ServiceAccountCredentials.from_json_keyfile_dict()

This will expect an dict object , this is all I needed.

Link:

https://oauth2client.readthedocs.io/en/latest/source/oauth2client.service_account.html

Thank you everyone again

Alvaro Lamadrid
  • 355
  • 2
  • 13
2

Additional tip: I am using Google API to read Google Drive files, but also using AWS. I stored the service account credentials in AWS Secrets Manager, so that I did not need a file. I copy-pasted each key-value pair from the downloaded JSON file into AWS Secrets Manager. But I kept getting the error:

Traceback (most recent call last):
  File "./copy_from_google_drive_to_s3.py", line 301, in <module>
    sys.exit(main())
  File "./copy_from_google_drive_to_s3.py", line 96, in main
    keyfile_dict=keyDict, scopes=scopes,
  File "/usr/local/lib/python3.7/site-packages/oauth2client/service_account.py", line 253, in from_json_keyfile_dict
    revoke_uri=revoke_uri)
  File "/usr/local/lib/python3.7/site-packages/oauth2client/service_account.py", line 185, in _from_parsed_json_keyfile
    signer = crypt.Signer.from_string(private_key_pkcs8_pem)
  File "/usr/local/lib/python3.7/site-packages/oauth2client/_pure_python_crypt.py", line 182, in from_string
    raise ValueError('No key could be detected.')
ValueError: No key could be detected.

I had to convert the string representation of newline back into newline:

# Last part of using AWS Secrets Manager, returns json string.
sa_creds = get_secret_value_response['SecretString']
# Convert JSON string to dict.
sa_creds = json.loads(sa_creds)
# In the private key, 1-char newline got replaced with 2-char '\n'
sa_creds['private_key'] = sa_creds['private_key'].replace('\\n', '\n')
credentials = ServiceAccountCredentials.from_json_keyfile_dict(
    keyfile_dict=sa_creds,
    scopes=['https://www.googleapis.com/auth/drive.readonly',]
)
Bob McCormick
  • 207
  • 1
  • 10
2

My solution is close to Bob McCormick. The difference is that it's using the credentials method for using service account info instead of JSON file.

Here i'm using Googles Secret Manager to import service account information so that my code can connect to a different GCP project:

from google.cloud import secretmanager
from google.oauth2 import service_account

# Create the Secret Manager client.
secret_client = secretmanager.SecretManagerServiceClient()
    
# Build the resource name of the secret version.
name = f"projects/{project-id}/secrets/{very-secret-secret-name}/versions/latest"
    
# Access the secret version.
secret_response = secret_client.access_secret_version(request={"name": name})
    
# Getting the secret data                                                               
secret_payload = json.loads(secret_response.payload.data.decode("UTF-8"))
    
# Applying the credentials as INFO instead of JSON
credentials = service_account.Credentials.from_service_account_info(
                secret_payload,
                scopes=["https://www.googleapis.com/auth/cloud-platform"],
               )

Michel K
  • 641
  • 1
  • 6
  • 18
0

Since you're using ServiceAccountCredentials, I'm assuming you're using OAuth2 for authorization. You can skip the json file by using oauth2client.SignedJwtAssertionCredentials to create the appropriate credentials object and pass that to gspread.authorize.

import gspread
from oauth2client.client import SignedJwtAssertionCredentials

credentials = SignedJwtAssertionCredentials(service_account_name, private_key.encode(), 
                                            ['https://spreadsheets.google.com/feeds'])
gclient = gspread.authorize(credentials)

UPDATE: It appears that oauth2client.SignedJwtAssertionCredentials has been deprecated in favor of oauth2client.service_account.ServiceAccountCredentials, which only supports json and p12 keyfiles.

Daniel Ong
  • 283
  • 1
  • 12
  • Thank you everyone, like you said SignedJwtAssertionCredentials has been deprecated , and now we should be using ServiceAccountCredentials. – Alvaro Lamadrid Sep 21 '18 at 00:41