Reusable Google Sheets library: HttpError 404 - python

Using the Python Quickstart example for the v4 Google Sheets API as a starting point, I've tried to make a library with read and write functions which can then be used by higher-level classes to easily interact with my sheet. This only works if I use the library itself as a script to call the read/write functions. Both read and write throw the following error if I use them after importing into an external script located in a different directory:
HttpError 404 when requesting https://sheets.googleapis.com/$discovery/v4/spreadsheets/
That URL looks malformed with "$discovery" in it.
Here's my library with a main section which works well if this library is run as a script:
# sheetlib.py
""" Google Docs Spreadsheets wrapper
"""
import httplib2
import os
import json
json.JSONEncoder.default=str
from apiclient import discovery
from oauth2client import client
from oauth2client import tools
from oauth2client.file import Storage
SCOPES = 'https://www.googleapis.com/auth/spreadsheets'
CLIENT_SECRET_FILE = 'client_secret.json'
CREDENTIAL_PATH = 'sheets.googleapis.test.json'
APPLICATION_NAME = 'Test Sheet'
SPREADSHEET_ID = 'abcdefg'
def get_credentials():
"""Gets valid user credentials from storage.
If nothing has been stored, or if the stored credentials are invalid,
the OAuth2 flow is completed to obtain the new credentials.
Returns:
Credentials, the obtained credential.
"""
store = Storage(CREDENTIAL_PATH)
print 'Environment: {}'.format(json.dumps(os.environ.__dict__['data']))
print 'Loaded store from {}: {}'.format(CREDENTIAL_PATH, json.dumps(store.__dict__))
credentials = store.get()
if not credentials or credentials.invalid:
flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES)
flow.user_agent = APPLICATION_NAME
credentials = tools.run_flow(flow, store)
print 'Storing credentials to ' + CREDENTIAL_PATH
return credentials
def build_service():
""" Returns service object for reading/writing to spreadsheet """
credentials = get_credentials()
print "credentials: {}".format(json.dumps(credentials.__dict__))
http = credentials.authorize(httplib2.Http())
discoveryUrl = ('https://sheets.googleapis.com/$discovery/rest?version=v4')
service = discovery.build('sheets', 'v4', http=http, discoveryServiceUrl=discoveryUrl)
print 'service: {}'.format(json.dumps(service._http.__dict__))
return service
def write(range, values):
service = build_service()
body = {
'values': values
}
service.spreadsheets().values().append(
spreadsheetId=SPREADSHEET_ID, range=range,
valueInputOption='RAW', body=body, insertDataOption='INSERT_ROWS').execute()
def read(range):
""" Pass a range to read, like 'RawData!A:E' """
service = build_service()
resp = service.spreadsheets().values().get(spreadsheetId=SPREADSHEET_ID, range=range).execute()
return resp
class Magic():
"""Reads and writes to the Magic tab of sheet"""
def spell_list(self):
return [r for r in read('Magic!A1:G100')['values'][1:]]
if __name__ == '__main__':
m = Magic()
print m.spell_list()
If I move the Magic class to another file located in a different directory and try to use imported read, it throws the 404 error:
# magic_test.py
from sheetlib import read
class BadMagic():
"""Reads and writes to the Magic tab of sheet"""
def spell_list(self):
return [r for r in read('Magic!A1:G100')['values'][1:]]
m = BadMagic()
m.spell_list()
Traceback (most recent call last):
File "magic_test.py", line 0, in main
return [r[0] for r in read('Magic!A2:A100')['values']]
File "sheetlib.py", line 0, in read
resp = service.spreadsheets().values().get(spreadsheetId=SPREADSHEET_ID, range=range).execute()
File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/lib/google-api-python-client/apiclient/http.py", line 292, in execute
raise HttpError(resp, content, self.uri)
apiclient.errors.HttpError: <HttpError 404 when requesting https://sheets.googleapis.com/$discovery/v4/spreadsheets/abcdefg/values/Magic%21A2%3AA100?alt=json returned "Not Found">
Exploring further, I see that the credentials: and service: output from the build_service() function is different depending on which script is using it:
Calling from sheetlib.py (working)
credentials:
{
"scopes": "set([u'https://www.googleapis.com/auth/spreadsheets'])",
"revoke_uri": "https://accounts.google.com/o/oauth2/revoke",
"access_token": "asdf",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"token_info_uri": "https://www.googleapis.com/oauth2/v3/tokeninfo",
"token_response": {
"access_token": "asdf",
"token_type": "Bearer",
"expires_in": 3600
},
"invalid": false,
"refresh_token": "qwer",
"client_id": "1234.apps.googleusercontent.com",
"id_token": null,
"client_secret": "zxcv",
"token_expiry": "2017-03-08 17:01:42",
"store": "<oauth2client.file.Storage object at 0x10bbd6690>",
"user_agent": "Magic Sheet"
}
service:
{
"force_exception_to_status_code": false,
"forward_authorization_headers": false,
"authorizations": [],
"proxy_info": "<function proxy_info_from_environment at 0x10af9aed8>",
"follow_redirects": true,
"cache": null,
"request": "<function new_request at 0x10b3dba28>",
"connections": {},
"certificates": "<httplib2.KeyCerts object at 0x10b3df3d0>",
"optimistic_concurrency_methods": [
"PUT",
"PATCH"
],
"follow_all_redirects": false,
"timeout": null,
"ignore_etag": false,
"ca_certs": null,
"credentials": "<httplib2.Credentials object at 0x10b3df410>",
"disable_ssl_certificate_validation": false
}
Calling from magic_test.py (broken)
credentials:
{
"access_token": "asdf",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"invalid": false,
"refresh_token": "qwer",
"client_id": "1234.apps.googleusercontent.com",
"id_token": null,
"client_secret": "zxcv",
"token_expiry": "2017-03-08 17:01:42",
"store": "<oauth2client.file.Storage object at 0x1101a2e50>",
"user_agent": "Accounting Sheet"
}
service:
{
"force_exception_to_status_code": false,
"forward_authorization_headers": false,
"authorizations": [],
"proxy_info": "<bound method type.from_environment of <class 'httplib2.ProxyInfo'>>",
"follow_redirects": true,
"cache": null,
"request": "<function new_request at 0x1101bae60>",
"connections": {
"https:sheets.googleapis.com": "<httplib2.HTTPSConnectionWithTimeout instance at 0x1101b1ea8>"
},
"certificates": "<httplib2.KeyCerts object at 0x1101c2890>",
"optimistic_concurrency_methods": [
"PUT",
"PATCH"
],
"follow_all_redirects": false,
"timeout": null,
"ignore_etag": false,
"ca_certs": null,
"credentials": "<httplib2.Credentials object at 0x1101c28d0>",
"disable_ssl_certificate_validation": false
}
Any clue why different parts of http2lib would be used depending on which script called it?

You may refer with this thread. Be noted that the body arg included in update(), although shown in the documentation as json, actually needs to be a regular python dictionary. Also, your 404 error means that you requested something that doesn't exist (or that it doesn't want you to know exists). Here's an article which might help you in fixing 404 error.

Related

DefaultCredentialsError in video upload youde on youtube

I am trying to follow tutorials on youtube and a lot of tutorials from official google dev. But I can not find out where is my error
my code:
import os
import google.auth
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
path_to_key = "client_secret.json"
# set the environment variable
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = path_to_key
def upload_video_to_youtube(path_to_video, video_title, video_description, video_tags):
try:
# authenticate and build the YouTube API client
client = build("youtube", "v3", credentials=google.auth.default())
# define the video metadata
video_metadata = {
"snippet": {
"title": video_title,
"description": video_description,
"tags": video_tags,
"categoryId": 22
},
"status": {
"privacyStatus": "public"
}
}
# create the request to upload the video
request = client.videos().insert(
part=",".join(video_metadata.keys()),
body=video_metadata,
media_body=path_to_video
)
# execute the request and upload the video
response = None
while response is None:
status, response = request.next_chunk()
if status:
print("Uploaded %d%%." % int(status.progress() * 100))
print("Video ID: ", response["id"])
except HttpError as error:
print("An error occurred while uploading the video: ", error)
my error is:
google.auth.exceptions.DefaultCredentialsError: The file client_secret.json does not have a valid type. Type is None, expected one of ('authorized_user', 'service_account', 'external_account', 'impersonated_service_account', 'gdch_service_account')
I do not know where is the problem because I chack my json and it is correct. I had new one google claud console project
my json:
{
"installed": {
"client_id": "280376606200-a1luulq********j3fmo0qege7.apps.googleusercontent.com",
"project_id": "bilionare-livestyle-shorts",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_secret": "GOCSPX-2cxBjDE*********xmHHVKtJV4aS ",
"redirect_uris": [
"http://localhost"
]
}
}

How to use OAuth 2.0 credentials with blogger api v3 (python requests)

I am trying to automate my blog writing a bit by using Blogger API v3.0.
I have my API_KEY and by using it I managed to access my blog like that:
import requests
APIKEY = 'XXX-YYY-ZZZ'
BLOGID = '12345678'
get_blog = 'https://www.googleapis.com/blogger/v3/blogs/{BLOGID}?key={APIKEY}'.format(BLOGID=BLOGID, APIKEY=APIKEY)
response = requests.get(get_blog)
Next I tried to create a new post:
params = {
"kind": "blogger#post",
"blog": {
"id": BLOGID
},
"title": "A new post",
"content": "With <b>exciting</b> content..."
}
new_post = 'https://www.googleapis.com/blogger/v3/blogs/{blogID}/posts/?key={APIKEY}'.format(blogID=bereshitID, APIKEY=APIKEY)
response = requests.post(get_blog, params=params)
But I got an Error:
{u'error': {u'status': u'PERMISSION_DENIED', u'message': u'The caller does not have permission', u'code': 403, u'errors': [{u'reason': u'forbidden', u'message': u'The caller does not have permission', u'domain': u'global'}]}}
So I figured I need to have OAuth 2.0 credentials. So I created it and now I have client_id and client_secret and I tried to add it to the params:
CLIENT_SECRET = 'ABCD-EFGH'
CLIENT_ID = '1111'
params = {
"client_secret" : CLIENT_SECRET,
"client_id" : CLIENT_ID,
"kind": "blogger#post",
"blog": {
"id": BLOGID
},
"title": "A new post",
"content": "With <b>exciting</b> content..."
}
new_post = 'https://www.googleapis.com/blogger/v3/blogs/{blogID}/posts/?key={APIKEY}'.format(blogID=bereshitID, APIKEY=APIKEY)
response = requests.post(get_blog, params=params)
However I am still getting the error as before.
Clearly I am missing something here but I couldn't find a solution... So how should I use the OAuth 2.0 credentials correctly?
I found this guide by Rajashekar Jangam (ImRaj90) very informative.
I followed it and managed to work with my blog using the API.
Thank you Rajashekar.

Microsoft Graph API Synchronisation API, update secret token only works at second call

I'm implementing (in Python with the Microsoft Graph API) the creation of Azure AD application based on the AWS template. I'm stuck when implementing the automatic role provisioning like describe in this documentation : https://learn.microsoft.com/fr-fr/graph/application-provisioning-configure-api?tabs=http#step-3-authorize-access
When I call the servicePrincipals/{id}/synchronization/secrets API for the first time just after the creation of the synchronization job, I receive a HTTP error (400 - Bad Request) with the following body :
{
"error": {
"code": "BadRequest",
"message": "The credentials could not be saved. This is due to an internal storage issue in the Microsoft Azure AD service. For information on how to address this issue, please refer to https://go.microsoft.com/fwlink/?linkid=867915",
"innerError": {
"code": "CredentialStorageBadRequest",
"details": [],
"message": "The credentials could not be saved. This is due to an internal storage issue in the Microsoft Azure AD service. For information on how to address this issue, please refer to https://go.microsoft.com/fwlink/?linkid=867915",
"target": null,
"innerError": {
"code": "CredentialStorageBadRequest",
"details": [],
"message": "Message:The credentials could not be saved. This is due to an internal storage issue in the Microsoft Azure AD service. For information on how to address this issue, please refer to https://go.microsoft.com/fwlink/?linkid=867915",
"target": null
},
"date": "2021-01-05T15:53:59",
"request-id": "---",
"client-request-id": "---"
}
}
}
When a do a second same call (with MS Graph Explorer, Postman or directly in Python), it works, the second call returns an HTTP 204 like expected ! So I think my request is correct.
This is my implementation (which works because I retry the call a second time…) :
# Default value :
GRAPH_API_URL = "https://graph.microsoft.com/beta/{endpoint}"
class Azure:
# […]
# self._http_headers contains my token to access to MS Graph API
# self._aws_key_id and self._aws_access_key contains AWS credentials
def _save_sync_job_auth(self, principal_id):
self._put(
f"servicePrincipals/{principal_id}/synchronization/secrets",
{"value": [
{"key": "ClientSecret", "value": self._aws_key_id},
{"key": "SecretToken", "value": self._aws_access_key},
]},
retry=1 # If I put 0 here, my script fail
)
# […]
def _put(self, endpoint, json, retry=0):
return self._http_request(requests.put, endpoint, retry, json=json)
# […]
def _http_request(self, func, endpoint, retry=0, **kwargs):
url = GRAPH_API_URL.format(endpoint=endpoint)
response = func(url, headers=self._http_headers, **kwargs)
try:
response.raise_for_status()
except requests.HTTPError as e:
if retry:
logging.warning(f"Error when calling {func.__name__.upper()} {url}")
return self._http_request(func, endpoint, retry - 1, **kwargs)
else:
raise e
return response
Am I missing something ? Have you a solution to remove this "retry hack" ?

Youtube upload api

Does anybody here have any experience in using the YouTube API? I'm currently trying to automate uploading my videos by having an OAuth application, but, because its not verified, my videos get locked instantly and set to private. I cannot get my OAuth application verified due to not having a website.
Is there any other way I can do this without using an OAuth application?
def uploadClip(clip):
# Disable OAuthlib's HTTPS verification when running locally.
# *DO NOT* leave this option enabled in production.
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"
api_service_name = "youtube"
api_version = "v3"
client_secrets_file = "client_secrets.json"
# 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)
request = youtube.videos().insert(
part="snippet,status",
body={
"snippet": {
"categoryId": "22",
"description": ".",
"title": "H "
},
"status": {
"privacyStatus": "public"
}
},
# TODO: For this request to work, you must replace "YOUR_FILE"
# with a pointer to the actual file you are uploading.
media_body=clip
)
response = request.execute()

Microsoft Graph API update user authentication method - Access Denied

I'm creating an application in Azure AD as a daemon to get user phone
authentication methods using the python msal library and calling the following following endpoint GET https://graph.microsoft.com/beta/users/{id | UPN}/authentication/phoneMethods but i get the following error
{
"error": {
"code": "accessDenied",
"message": "Request Authorization failed",
"innerError": {
"message": "Request Authorization failed",
"date": "2020-11-19T19:26:28",
"request-id": "11975e07-ee6b-4bd2-9a74-7c175c5da560",
"client-request-id": "11975e07-ee6b-4bd2-9a74-7c175c5da560"
}
}
}
My app has the required application permissions to get the info i'm looking for, which are UserAuthenticationMethod.Read.All and UserAuthenticationMethod.ReadWrite.All and it already works with different end points such as GET https://graph.microsoft.com/beta/users/{id | UPN}, this is the code i'm using in order to get the access token required and call the graph api
import json
import logging
import requests
import msal
config = {
"authority": "https://login.microsoftonline.com/TENANT_NAME",
"client_id": "APP_ID",
"scope": ["https://graph.microsoft.com/.default"],
"secret": "APP_SECRET",
"endpoint": "https://graph.microsoft.com/beta/users/{USER_ID}/authentication/phoneMethods"
}
app = msal.ConfidentialClientApplication(
config["client_id"], authority=config["authority"],
client_credential=config["secret"],
)
result = None
result = app.acquire_token_silent(config["scope"], account=None)
if not result:
logging.info("No suitable token exists in cache. Let's get a new one from AAD.")
result = app.acquire_token_for_client(scopes=config["scope"])
if "access_token" in result:
graph_data = requests.get(
config["endpoint"],
headers={'Authorization': 'Bearer ' + result['access_token']}, ).json()
print("Graph API call result: ")
print(json.dumps(graph_data, indent=2))
else:
print(result.get("error"))
print(result.get("error_description"))
print(result.get("correlation_id"))
i tried to do the same thing using curl or postman and i get the exact same error, so i'm guessing it's an access token issue maybe ?
Thanks in advance
The api call does not support application permissions. You need to grant delegated permissions to the application, and then use the auth code flow to obtain the token.

Categories

Resources