googleapis/python-analytics-data

Authenticating from json file not working

nithiniz opened this issue ยท 8 comments

Environment details

  • OS type and version: Linux 20.x
  • Python version: Python 3
  • pip version: 20.0.2
  • google-analytics-data version: 0.10.0

Steps to reproduce

  1. Make sure the env variable is not set for the credentials file
  2. Create client object with path to json file BetaAnalyticsDataClient().from_service_account_json(credentials_json_path)
  3. use the client object to get any report data -> Auth error will be thrown

Code example

The same code as https://github.com/googleapis/python-analytics-data/blob/main/samples/snippets/quickstart_json_credentials.py

This works only if the environment variable GOOGLE_APPLICATION_CREDENTIALS is set. If we are setting this env variable, there is no need for 'from_service_account_json' object

# example

Stack trace

google.auth.exceptions.DefaultCredentialsError: Could not automatically determine credentials. Please set GOOGLE_APPLICATION_CREDENTIALS or explicitly create credentials and re-run the application. For more information, please see https://cloud.google.com/docs/authentication/getting-started

Hey @nithiniz,

The exception message you shared seems to suggest the library is falling through to default credentials (so the credential from from_service_account_json doesn't seem to be used). Are you using https://github.com/googleapis/python-analytics-data/blob/main/samples/snippets/quickstart_json_credentials.py exactly? If not, please share a bit more of your code.

@busunkim96 : I am exactly using the quickstart_json_credentials.py file. But for some reason, it is falling through to use the file from env, not from the passed json file path.

Hi,

I'm not able to reproduce the behavior with the sample. When I pass a service account JSON file it is correctly preferred over environment credentials.

Could you try printing out the credentials attached to the client before the first request is sent?

client = BetaAnalyticsDataClient().from_service_account_json(credentials_json_path)
print(client.transport._credentials)  # should be google.oauth2.service_account.Credentials

You can also try creating a service account credential separately and passing it into the client constructor. This is effectively what from_service_account_json does, but maybe we'll see different results.

from google.oauth2 import service_account

credentials = service_account.Credentials.from_service_account_file(credentials_json_path)
client = BetaAnalyticsDataClient(credentials=credentials)

Hi @nithiniz, I'm going to close this issue due to inactivity but please feel free to re-open it with more information.

This is still an issue. The quickstart suggests this line of code.

client = BetaAnalyticsDataClient().from_service_account_json(credentials_json_path)

This was working for my coworker but not for me. The reason is the BetaAnalyticsDataClient will throw an exception if it doesn't find any default credentials, so this line never gets an opportunity to call from_service_account.

Furthermore, one of the default credentials it searches for is the default gcloud credentials on a users computer. The reason it was "working" for my coworker was because it was grabbing gcloud credentials from a completely unrelated project.

In my case, I had none of the initial credentials the constructor checks for, and so I couldn't use the client. The docs should be updated to use the answer provided by @busunkim96. Alternatively you can pass the credentials path string directly into the constructor via client_options

client_options = {"credentials_file": credentials_json_path}
client = BetaAnalyticsDataClient(client_options=client_options)
gh640 commented

I bumped into this issue.

I found that the sample code in quickstart_json_credentials.py is wrong. We need to use from_service_account_json() as a class method before creating an instance of BetaAnalyticsDataClient.

wrong:

client = BetaAnalyticsDataClient().from_service_account_json(credentials_json_path)

correct:

client = BetaAnalyticsDataClient.from_service_account_json(credentials_json_path)

The sample that doesn't work:

def sample_run_report(property_id="YOUR-GA4-PROPERTY-ID", credentials_json_path=""):
"""Runs a simple report on a Google Analytics 4 property."""
# TODO(developer): Uncomment this variable and replace with your
# Google Analytics 4 property ID before running the sample.
# property_id = "YOUR-GA4-PROPERTY-ID"
# [START analyticsdata_json_credentials_initialize]
# TODO(developer): Uncomment this variable and replace with a valid path to
# the credentials.json file for your service account downloaded from the
# Cloud Console.
# credentials_json_path = "/path/to/credentials.json"
# Explicitly use service account credentials by specifying
# the private key file.
client = BetaAnalyticsDataClient().from_service_account_json(credentials_json_path)

from_service_account_file() is implemented with @classmethod.

@classmethod
def from_service_account_file(cls, filename: str, *args, **kwargs):
"""Creates an instance of this client using the provided credentials
file.
Args:
filename (str): The path to the service account private key json
file.
args: Additional arguments to pass to the constructor.
kwargs: Additional arguments to pass to the constructor.
Returns:
BetaAnalyticsDataClient: The constructed client.
"""
credentials = service_account.Credentials.from_service_account_file(filename)
kwargs["credentials"] = credentials
return cls(*args, **kwargs)
from_service_account_json = from_service_account_file


It might be better to adjust the test in quickstart_json_credentials_test.py. The test sets environmental variable GOOGLE_APPLICATION_CREDENTIALS beforehand. It's confusing as it's automatically used by BetaAnalyticsDataClient's constructor.

def test_quickstart(capsys):
# Create a temporary service account credentials JSON file to be used by
# the test.
TEST_PROPERTY_ID = os.getenv("GA_TEST_PROPERTY_ID")
CREDENTIALS_JSON_PATH = os.getenv("GOOGLE_APPLICATION_CREDENTIALS")
quickstart_json_credentials.sample_run_report(
TEST_PROPERTY_ID, CREDENTIALS_JSON_PATH
)
out, _ = capsys.readouterr()
assert "Report result" in out

thank you @gh640