I am developing a Python 3.7 app which aims to be deployed on Google App Engine. The goal of this app is to query the Google Workspace Enterprise License Manager API in order to retrieve the list of users per Google Workspace license. So to summarize, the main goal of this app is to query the method licenseAssignments.listForProduct of the API.
I already developed an app which manage GDrive file via GSuite API, using a Service Account. So for this new app, and for testing purpose, I keep using the same Service Account. Our GSuite Admin granted this Service Account with the scope
"https://www.googleapis.com/auth/apps.licensing".
I follow the reference guide so the Enterprise License Manager API is enable on my project on GCP, and the API is enabled on Google Workspace Admin console (our GSuite Admin use the Google Workspace Enterprise License Manager API via Powershell successfully).
Here is my code:
from google.oauth2 import service_account
from googleapiclient.discovery import build
CUSTOMER_ID = 'HIDDEN_FOR_SECURITY_PURPOSE'
SERVICE_ACCOUNT_FILE = "service_account_file.json"
SCOPES = "https://www.googleapis.com/auth/apps.licensing"
def get_cred():
try:
creds = service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES
)
except Exception as e:
print(e)
return creds
if __name__ == "__main__":
creds = get_cred()
service = build('licensing', 'v1', credentials=creds)
try:
response = service.licenseAssignments().listForProduct(customerId='HIDDEN_FOR_SECURITY_PURPOSE', productId='Google-Apps' ).execute()
except Exception as e:
print(e)
When running this code via App Engine, or in my computer via Jupyter Notebook, I get this error:
('No access token in response.', {'id_token': 'JWT_TOKEN_HIDDEN_FOR_SECURITY_PURPOSE'})
Lib versions:
Python 3.7
google-api-python-client==1.7.11
google-auth==1.20.1
Thanks for your help!
Edit:
I deployed the following code on GAE:
from google.cloud import bigquery
from google.oauth2 import service_account
from googleapiclient.discovery import build
import requests
import json
import pandas as pd
SERVICE_ACCOUNT_FILE = "service_account_file.json"
SCOPES = ["https://www.googleapis.com/auth/apps.licensing"]
app = Flask(__name__)
def get_cred():
try:
creds = service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES
)
except Exception as e:
print(e)
return e
return creds
#app.route("/")
def main():
print("## Start")
creds = get_cred()
service = build("licensing", "v1", credentials=creds)
try:
response = (
service.licenseAssignments()
.listForProduct(customerId="HIDDEN_FOR_SECURITY_PURPOSE", productId="Google-Apps")
.execute()
)
print("## Response={}".format(response))
except Exception as e:
print(e)
print("## End")
return "OK"
if __name__ == "__main__":
# This is used when running locally only. When deploying to Google App
# Engine, a webserver process such as Gunicorn will serve the app. This
# can be configured by adding an `entrypoint` to app.yaml.
app.run(host="127.0.0.1", port=8080, debug=True)
# [END gae_python37_app]
I get the error <HttpError 503 when requesting https://licensing.googleapis.com/apps/licensing/v1/product/Google-Apps/users?customerId=[HIDDEN_FOR_SECURITY_PURPOSE]&alt=json returned "Backend Error". Details: "Backend Error">.
is your service account using Domain wide delegation? and if so is it impersonating a user that has RBAC permissions to view the licenses?
Use the API explorer https://developers.google.com/admin-sdk/licensing/reference/rest/v1/licenseAssignments/listForProduct?apix_params=%7B%22productId%22%3A%22Google-Apps%22%2C%22customerId%22%3A%22XXXXXX%20PUT%20YOUR%20CUST%20ID%20HERE%22%7D&apix=true
And autenticate against the user you are trying to impersonate, and see if you get a response.
If you dont you likley need to impersonate a Super Admin user, and if your not doing impersonateion that needs to be enabled in the GCP console first, then the scope needs to be autorized on the domain, which you mentioned has happened.
Related
I have an error when I try to access secret manager from Google App Engine Standard, but if I access from my laptop with the JSON key is fine.
requirements.txt:
Flask
google-cloud-storage
google-cloud-secret-manager
psycopg2
app.yaml
runtime: python39
automatic_scaling:
max_instances : 2
max_idle_instances : 1
target_cpu_utilization : 0.9
target_throughput_utilization : 0.9
max_concurrent_requests : 80
Code:
from flask import Flask,render_template, request
from werkzeug.utils import secure_filename
import os
app = Flask(__name__)
#app.route('/s1',methods=['GET'])
def s1():
from google.cloud import secretmanager
client = secretmanager.SecretManagerServiceClient()
return client.access_secret_version(request={"name": "projects/363745113141/secrets/API_OCR/versions/1"}).payload.data.decode("UTF-8")
if __name__ == '__main__':
app.run()
error 1:
grpc._channel._InactiveRpcError: <_InactiveRpcError of RPC that terminated with:
at ._end_unary_response_blocking ( /layers/google.python.pip/pip/lib/python3.9/site-packages/grpc/_channel.py:849 )
at .__call__ ( /layers/google.python.pip/pip/lib/python3.9/site-packages/grpc/_channel.py:946 )
at .error_remapped_callable ( /layers/google.python.pip/pip/lib/python3.9/site-packages/google/api_core/grpc_helpers.py:50 )
error 2:
google.api_core.exceptions.PermissionDenied: 403 Permission 'secretmanager.versions.access' denied for resource 'projects/363745113141/secrets/API_OCR/versions/1' (or it may not exist).
at .error_remapped_callable ( /layers/google.python.pip/pip/lib/python3.9/site-packages/google/api_core/grpc_helpers.py:52 )
at .retry_target ( /layers/google.python.pip/pip/lib/python3.9/site-packages/google/api_core/retry.py:190 )
at .retry_wrapped_func ( /layers/google.python.pip/pip/lib/python3.9/site-packages/google/api_core/retry.py:283 )
at .__call__ ( /layers/google.python.pip/pip/lib/python3.9/site-packages/google/api_core/gapic_v1/method.py:154 )
at .access_secret_version ( /layers/google.python.pip/pip/lib/python3.9/site-packages/google/cloud/secretmanager_v1/services/secret_manager_service/client.py:1439 )
at .accessSecret ( /srv/lib/secretManager.py:18 )
at .s1 ( /srv/main.py:39 )
at .dispatch_request ( /layers/google.python.pip/pip/lib/python3.9/site-packages/flask/app.py:1509 )
at .full_dispatch_request ( /layers/google.python.pip/pip/lib/python3.9/site-packages/flask/app.py:1523 )
at .full_dispatch_request ( /layers/google.python.pip/pip/lib/python3.9/site-packages/flask/app.py:1525 )
at .wsgi_app ( /layers/google.python.pip/pip/lib/python3.9/site-packages/flask/app.py:2077 )
In order to have access to a secret version, you must grant access to it by adding the proper role.
As described in the documentation Accessing a secret version:
Accessing a secret version requires the Secret Manager Secret Accessor role (roles/secretmanager.secretAccessor) on the secret, project, folder, or organization.
In this tutorial, Share your secrets between your teams and applications with Secret Manager on Google Cloud Platform, it shows how to give access to a given secret:
If we want to give control the access to our secrets from Secret Manager console, we have to select one secret and press “Show Info Panel” button
In the info panel, we want review the actual permission and we can add new member:
And from the info panel, we can modify the access (only if the role is not inherited)
Here we add the App Engine default service account (or the account you've created to run your App Engine if you have to do so).
In the Viewing the App Engine default service account documentation, you can view your service accounts:
In the Cloud console, go to the Service accounts page.
Go to Service accounts
Select your project.
In the list, locate the email address of the App Engine default service account:
YOUR_PROJECT_ID#appspot.gserviceaccount.com
See also:
Changing service account permissions
Another approach is to use the account you've been using to do the tests on your laptop (the account used to generate the JSON key) as the account to run your application on App Engine:
By adding the account on your app.yaml file:
service_account: {SERVICE_ACCOUNT_YOU_WANT_TO_USE_TO_ACCESS_APP_ENGINE}
By running the following command to deploy your application:
gcloud beta app deploy --service-account=<your_service_account> app.yaml
App Engine app's identity is not restricted to the AppEngine default service account anymore. You can deploy with custom service account for each AppEngine app now by following https://cloud.google.com/appengine/docs/standard/python/user-managed-service-accounts#app.yaml.
These approaches are taken from the following question: Custom service account for App Engine.
I am using Google Analytics API to insert a custom dimension. I have set up the required Oauth2 credentials and obtained an access token and a refresh token. However, when I called my function that inserts a custom dimension to a specified Account and webProperty in Google Analytics, I get an error message that reads as follows:
<HttpError 403 "Request had insufficient authentication scopes.". Details: "[{'message': 'Insufficient Permission', 'domain': 'global', 'reason': 'insufficientPermissions'}]">
However, using the same code, I was able to fetch all accounts and webProperties in Google Analytics but apparently it can neither insert nor update custom dimensions and I don't know what I am missing in my code.
Here is my code:
from apiclient.discovery import build
from oauth2client.service_account import ServiceAccountCredentials
from googleapiclient.errors import HttpError
import os
from google.oauth2.credentials import Credentials
from google.auth.transport.requests import Request
scope = ['https://www.googleapis.com/auth/analytics']
def get_service():
"""
authorises user to access GA resources and builds a service object
"""
creds = None
# The file token.json stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
# It can also be created manually and populated with the required credentials
if os.path.exists('token.json'):
creds = Credentials.from_authorized_user_file('token.json', scopes=scope)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'client_secrets.json', scopes=scope)
creds = flow.run_local_server(port=80)
# Save the credentials for the next run
with open('token.json', 'w') as token:
token.write(creds.to_json())
# build a service object
service = build('analytics', 'v3', credentials=creds)
return service
def insert_customDimension(service):
# Inserts a custom Dimension at a specific index.
try:
service.management().customDimensions().insert(
accountId=ACCOUNT_ID,
webPropertyId=WEB_PROPERTY_ID,
body={'name': 'free to use',
'scope': 'HIT',
'active': False,
'index': 200
}
).execute()
except HttpError as error:
print(error)
except TypeError as er:
print(er)
I would like to understand what I am doing wrong or what do I need to change in my code?
I have deleted the token.json file and ran the code but I was unable to complete the authorization flow and no token.json was automatically created. I then manually obtained the refresh and access token and created the file. I have admin privileges to the Google Analytics account I am trying to insert custom dimensions into.
Is it possible to authenticate against the Google Drive API using a Google Service Account, rather than an OAuth flow ?
The Python examples for Google Drive use OAuth - Google drive Python Quick start
However I can't find any Service Account examples.
The majority of the other Google APIs I use (Translate, Cloud Vision) do use Service Account however, so I'd like to deprecate my Google Drive OAuth code for consistency.
The best service account python example that i know of is the one for Google analytics
It should be something like this.
from apiclient.discovery import build
from oauth2client.service_account import ServiceAccountCredentials
SCOPES = ['https://www.googleapis.com/auth/drive.readonly']
KEY_FILE_LOCATION = '<REPLACE_WITH_JSON_FILE>'
VIEW_ID = '<REPLACE_WITH_VIEW_ID>'
def initialize_drive():
"""Initializes an service object.
Returns:
An authorized service object.
"""
creds = ServiceAccountCredentials.from_json_keyfile_name(
KEY_FILE_LOCATION, SCOPES)
# Build the service object.
service = build('drive', 'v3', credentials=creds)
return service
Once you have the drive service you should be able to use the rest of the code you have from the other tutorial. Its just the auth method that is diffrent.
Just remember to create service account credentials and not Oauth credentials.
You can use Credentials oauth2 object using the following,
import json
from googleapiclient.discovery import build
from google.oauth2.service_account import Credentials
SCOPES = ['https://www.googleapis.com/auth/drive']
service_account_info = json.load(open('service_account.json'))
creds=Credentials.from_service_account_info(
service_account_info, scopes=SCOPES)
drive_service = build('drive', 'v3', credentials=creds)
I have been exploring the YouTube Data API. The premise of my project is simple: using the API, authenticate (yes, I have the credentials for the account) and then simply retrieve the list of all my videos, public and private.
I have been able to accomplish this successfully, except for the fully automated part. I have used code from various sources and when I run it on the command line, it provides me a link to be used in a browser so that the authorization takes place.
It looks something like this:
Please visit this URL to authorize this application: https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=7932902759886-cb8ai84grcqshe24nn459ka46uh45ssj.apps.googleusercontent.com&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fyoutube.readonly&state=zNVvgEyO47nmacvdEEAhDsQipY194k&prompt=consent&access_type=offline&code_challenge=aF7uTCghjwgwjg49o3fgiIU-_ryK19rDeX4l1uzr37w&code_challenge_method=S256
Enter the authorization code:
....
Here's a snippet of my python code:
import google_auth_oauthlib.flow
import googleapiclient.discovery
import googleapiclient.errors
...
...
# Get credentials and create an API client
flow = google_auth_oauthlib.flow.InstalledAppFlow.from_client_secrets_file(
client_secrets_file, scopes)
credentials = flow.run_console()
youtube = googleapiclient.discovery.build(
api_service_name, api_version, credentials=credentials)
## MAKE youtube SEARCH REQUEST
last_date = '2018-10-01T00:00:00Z'
request = youtube.search().list(
part="snippet",
forMine=True,
maxResults=50,
order="date",
type="video"
)
all_items = []
response = request.execute()
My question here is the following: Is it possible to programatically perform the authorization so that the app can run standalone and not have to wait for this user action (to literally copy the URL from CMD, visit to get the token, and the copy and paste the token again)? I'd like to schedule this and therefore would like it to run and authenticate without human intervention. Is this possible at all? If so, can someone please point me to some working examples and/or other resources to help me get there? Thanks a million.
# -*- coding: utf-8 -*-
# Sample Python code for youtube.channels.list
# See instructions for running these code samples locally:
# https://developers.google.com/explorer-help/guides/code_samples#python
#!/usr/bin/python3.7
import os
import pickle
import google_auth_oauthlib.flow
import googleapiclient.discovery
import googleapiclient.errors
scopes = ["https://www.googleapis.com/auth/youtube.readonly"]
client_secrets_file = "client_secret.json"
api_service_name = "youtube"
api_version = "v3"
def main():
# Disable OAuthlib's HTTPS verification when running locally.
# *DO NOT* leave this option enabled in production.
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"
# Get credentials and create an API client
flow = google_auth_oauthlib.flow.InstalledAppFlow.from_client_secrets_file(
client_secrets_file, scopes)
youtube = get_authenticated_service()
request = youtube.channels().list(
part="contentDetails",
mine=True
)
response = request.execute()
print(response)
def get_authenticated_service():
if os.path.exists("CREDENTIALS_PICKLE_FILE"):
with open("CREDENTIALS_PICKLE_FILE", 'rb') as f:
credentials = pickle.load(f)
else:
flow = google_auth_oauthlib.flow.InstalledAppFlow.from_client_secrets_file(client_secrets_file, scopes)
credentials = flow.run_console()
with open("CREDENTIALS_PICKLE_FILE", 'wb') as f:
pickle.dump(credentials, f)
return googleapiclient.discovery.build(
api_service_name, api_version, credentials=credentials)
if __name__ == "__main__":
main()
The Credentials instance from credentials = flow.run_console() has a built-in functionality to refresh token.
It'll will refresh the token when a request being execute if needed.
Therefore you can save the credentials object into pickle, and read it back when need it
A few alteration on Google python sample code:
def get_authenticated_service():
if os.path.exists(CREDENTIALS_PICKLE_FILE):
with open(CREDENTIALS_PICKLE_FILE, 'rb') as f:
credentials = pickle.load(f)
else:
flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRETS_FILE, SCOPES)
credentials = flow.run_console()
with open(CREDENTIALS_PICKLE_FILE, 'wb') as f:
pickle.dump(credentials, f)
return build(API_SERVICE_NAME, API_VERSION, credentials = credentials)
copied from https://developers.google.com/identity/protocols/OAuth2InstalledApp
Step 3: Google prompts user for consent
In this step, the user decides whether to grant your application the requested access. At this stage, Google displays a consent window that shows the name of your application and the Google API services that it is requesting permission to access with the user's authorization credentials. The user can then consent or refuse to grant access to your application.
Your application doesn't need to do anything at this stage as it waits for the response from Google's OAuth 2.0 server indicating whether the access was granted. That response is explained in the following step.
Where this is important:
At this stage, Google displays a consent window that shows the name of your application and the Google API services that it is requesting permission to access with the user's authorization credentials.
So, at least as I interpret it, what you want to do should not be done for security reasons.
However: you can "simulate" a browser by how ever python have libs for do such. On the other hand: Once you got the auth-token you can re-use it instead of request a new token each time. I couldn't find it in provided doc on GitHub, but Java as example supports to store an obtained token along with its refresh token so it can be reused once obtained and auto-refreshed. Maybe python provides some way to store the obtained token (check if it contains a refresh token) and re-load it. Also: if you load such token, first you have to do is to refresh it before using it. Java provieds a way to just save a refresh token instead of the whole auth-token wich can be used in a later run to automatic obtain a new auth-token.
As response is a JSON maybe you could build some yourself if the lib doesn't already offer this.
// edit
In addition from https://github.com/googleapis/google-auth-library-python/blob/master/google/oauth2/credentials.py
There are methods to load a credential object either from an "authorized user info" (wich I also somewhere found can be loaded from file) or to load it directly from file. So, I guess you just have to figure out how to store the token. As doc says for from_authorized_user_file:
Creates a Credentials instance from an authorized user json file.
I guess that means you just have to save the token response you get after the initial authorization was done.
I am working on an application to pull the YouTube metrics using the YouTube analytics API. This is the first time I am working with google auth flow to authenticate my app so that it can silently pull the report.
I am following the below Google published article to work out the same:
https://developers.google.com/youtube/reporting/guides/authorization/server-side-web-apps
While following this article I am unable to figure out how to redirect the user to the auth_uri and obtain the auth_code.
Below is the code I have written for the auth flow so far:
API_SERVICE_NAME = 'youtubeAnalytics'
API_VERSION = 'v2'
CLIENT_SECRETS_FILE = 'C:/Users/Tushar/Documents/Serato_Video_Intelligence/client_secret_youtube.json'
def get_service():
global auth_code
global auth_uri
flow = client.flow_from_clientsecrets(
CLIENT_SECRETS_FILE,
scope='https://www.googleapis.com/auth/yt-analytics.readonly',
redirect_uri = "http://localhost:8080")
flow.params['access_type'] = 'offline'
flow.params['include_granted_scopes'] = True
auth_uri = flow.step1_get_authorize_url()
credentials = flow.step2_exchange(auth_code)
http_auth = credentials.authorize(httplib2.Http())
return build(API_SERVICE_NAME, API_VERSION, http=http_auth)
def execute_api_request(client_library_function, **kwargs):
response = client_library_function(
**kwargs
).execute()
if __name__ == '__main__':
# Disable OAuthlib's HTTPs verification when running locally.
# *DO NOT* leave this option enabled when running in production.
#os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'
youtubeAnalytics = get_service()
execute_api_request(
youtubeAnalytics.reports().query,
ids='channel==XXXXXXXXXXXXXX',
startDate='2019-04-12',
endDate='2019-08-13',
filters= 'video==XXXXXXXXXXXXXX',
metrics='audienceWatchRatio,relativeRetentionPerformance',
dimensions='elapsedVideoTimeRatio'
)
When I run this code I get an error
File "C:\Users\Tushar\Documents\Serato_Video_Intelligence\youtube_auth_testing.py", line 65, in <module>
youtubeAnalytics = get_service()
File "C:\Users\Tushar\Documents\Serato_Video_Intelligence\youtube_auth_testing.py", line 40, in get_service
credentials = flow.step2_exchange(auth_code)
NameError: name 'auth_code' is not defined
I have gone through the articles on Stack Overflow and Google but have been unable to figure out what to do. I certainly know there are couple of steps that I am missing but I am unable to resolve it.
You want to retrieve the access token with the authorization process of OAuth2.
You want to achieve this using oauth2client with Python.
If my understanding is correct, how about this modification?
Authorization flow:
When the access token is retrieved by the authorization process of OAuth2, at first, it is required to authorize the scopes by own browser. When the scopes are authorize, the authorization code can be retrieved. Using this code, the refresh token and access token can be retrieved.
Patten 1:
When your current script is modified, it becomes as follows. In this modification, please modify get_service().
Modified script:
import httplib2
from apiclient.discovery import build
from oauth2client import client
def get_service():
flow = client.flow_from_clientsecrets(
CLIENT_SECRETS_FILE,
scope='https://www.googleapis.com/auth/yt-analytics.readonly',
redirect_uri='urn:ietf:wg:oauth:2.0:oob')
flow.params['access_type'] = 'offline'
auth_uri = flow.step1_get_authorize_url()
print('Please go to this URL: {}'.format(auth_uri))
auth_code = input('Enter the authorization code: ')
credentials = flow.step2_exchange(auth_code)
http_auth = credentials.authorize(httplib2.Http())
return build(API_SERVICE_NAME, API_VERSION, http=http_auth)
Or (This is from the sample script of Reports: Query.)
def get_service():
flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRETS_FILE, SCOPES)
credentials = flow.run_console()
return build(API_SERVICE_NAME, API_VERSION, credentials = credentials)
When you run the script, Please go to this URL: ### is shown in the terminal. Please copy and paste it to your browser, and please authorize the scopes. Then, please retrieve the authorization code, and please paste it to the terminal. By this flow, the access token can be retrieved.
In above script, when the script is run, the authorization process is required to be done every time. So if you don't want to do this, how about the following pattern 2?
Patten 2:
In this pattern, when the script is run, the browser is automatically opened, and when the scopes are manually authorized, the authorization code is automatically retrieved.
Modified script:
import httplib2
import os
from apiclient.discovery import build
from oauth2client import client
from oauth2client import tools
from oauth2client.file import Storage
def get_service():
SCOPES = 'https://www.googleapis.com/auth/yt-analytics.readonly'
credential_path = os.path.join("./", 'tokens.json')
store = Storage(credential_path)
credentials = store.get()
if not credentials or credentials.invalid:
flow = client.flow_from_clientsecrets(CLIENT_SECRETS_FILE, SCOPES)
credentials = tools.run_flow(flow, store)
http = credentials.authorize(httplib2.Http())
return build(API_SERVICE_NAME, API_VERSION, http=http_auth)
In above script, when the script is run, when tokens.json is existing, the access token is retrieved using the refresh token in the file. By this, the authorization process using own browser is not required after 2nd run.
Patten 3:
Recently, google_auth_oauthlib is used for the authorization process. For example, you can see it at here. When this is reflected to your script, get_service() becomes as follows. Also you can see this flow at here.
Modified script:
import pickle
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
def get_service():
SCOPES = ['https://www.googleapis.com/auth/yt-analytics.readonly']
TOKEN_FILE = 'token.pickle'
creds = None
if os.path.exists(TOKEN_FILE):
with open(TOKEN_FILE, 'rb') as token:
creds = pickle.load(token)
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
CLIENT_SECRETS_FILE, SCOPES)
creds = flow.run_local_server()
with open(TOKEN_FILE, 'wb') as token:
pickle.dump(creds, token)
return build(API_SERVICE_NAME, API_VERSION, credentials=creds)
When the script is run, the browser is automatically opened, and when the scopes are manually authorized, the authorization code is automatically retrieved.
In above script, when the script is run, when token.pickle is existing, the access token is retrieved using the refresh token in the file. By this, the authorization process using own browser is not required after 2nd run.
Note:
In my environment, I could confirmed that when youtubeAnalytics of youtubeAnalytics = get_service() is used, execute_api_request() worked fine. If in your situation, an error occurs, please confirm whether API is enabled.
References:
Using OAuth 2.0 to Access Google APIs
Using OAuth 2.0 for Web Server Applications
Python Quickstart for Drive API
Reports: Query
If I misunderstood your question and this was not the direction you want, I apologize.