3

I have a blob data. I would like to upload this to a cell in google sheet using google sheet API v4.

I have looked at the documentation here. https://developers.google.com/sheets/api/guides/values

I have also looked at SO questions here. Insert image into Google Sheets cell using Google Sheets API

result = service.spreadsheets().values().update(
spreadsheetId=spreadsheet_id, range=range_name,
valueInputOption=value_input_option, body=body).execute()

I am not seeing any service described to insert the blob as the image. Please help.

After suggestions below, we implemented the Webapp from here - Insert image into Google Sheets cell using Google Sheets API

This is how we are calling the web app from our python code

        dropoff_signature = "ZGF0YT <clip > WVhSaA=="
        web_app_url     = "https://script.google.com/macros/s/A < clip > y/exec"        
        image_data  = "data:image/png;base64," + dropoff_signature
        data_to_post = {            
            'spreadsheetid' : spreadsheet_Id, 
            'sheetname' : 'Sheet1',     
            'imageurl'  : image_data,              
            'column'    : 5, 
            'row'       : 5             
            }
        encoded_data = urllib.urlencode(data_to_post)
        # Send encoded data to application-2
        url_result = urlfetch.fetch(web_app_url, encoded_data, method='POST')           

We are seeing the following error in our Webapp.

result : 200 content : {"status":"error","defaultMessage":"Error retrieving image from URL or bad URL: data:image/png;base64, <clip> ","name":"Exception","fileName":"Code (Insert image into spreadsheet)","lineNumber":42,"stack":"\tat Code (Insert image into spreadsheet):42 (doPost)\n"}}

Can you please help?

Made this change. Still getting the bad URL error.

dropoff_signature = "ZGF0YTpp<clip>WVhSaA=="
        web_app_url     = "https://script.google.com/macros/s/A<clip>y/exec"        
        image_data  = "data:image/png;base64," + dropoff_signature
        data_to_post = {            
            'spreadsheetid' : spreadsheet_Id, 
            'sheetname' : 'Sheet1',     
            'imageurl'  : image_data,              
            'column'    : 5, 
            'row'       : 5             
            }
        # encoded_data = urllib.urlencode(data_to_post)
        # Send encoded data to application-2
        # url_result = urlfetch.fetch(web_app_url, encoded_data, method='POST')         
        url_result = urlfetch.fetch(url=web_app_url, payload=json.dumps(data_to_post), method='POST', headers={'Content-type': 'application/json'})



result : 200 content : {"status":"error","defaultMessage":"Error retrieving 
image from URL or bad URL: 
data:image/png;base64,Z<clip>A==","error": 
{"message":"Error retrieving image from URL or bad URL: data:image/png;base64,Z<clip>A==","name":"Exception","fileName":"Code (Insert image into spreadsheet)","lineNumber":42,"stack":"\tat Code (Insert image into spreadsheet):42 (doPost)\n"}}

Here is the Webapp that we are using.

function doGet(e) {
  return ContentService.createTextOutput("Authorization: Bearer " + 
  ScriptApp.getOAuthToken())
}

//
// Example curl command to insert an image:
// 
// curl -L -d '{ "spreadsheetid": "1xNDWJXOekpBBV2hPseQwCRR8Qs4LcLOcSLDadVqDA0E","sheetname": "Sheet1", "imageurl": "https://www.google.com/images/srpr/logo3w.png", "column": 1, "row": 1 }' \
// -H "Authorization: Bearer <INSERT TOKEN RETURNED FROM GET HERE>" \
// -H 'Content-Type: application/json' \
// https://script.google.com/a/tillerhq.com/macros/s/AKfycbzjFgIrgCfZTvOHImuX54G90VuAgmyfz2cmaKjrsNFrTzcLpNk0/exec
//

var REQUIRED_PARAMS = [
  'spreadsheetid', // example: "1xNDWJXOekpBBV2hPseQwCRR8Qs4LcLOcSLDadVqDA0E"
  'sheetname',     // Case-sensitive; example: "Sheet1"
  'imageurl',      // Can be an url such as "https://www.google.com/images/srpr/logo3w.png"
                   // or alternately "data:image/png;base64,iVBOR...<snip>...gg=="
  'column', // 1-based (i.e. top left corner is column 1)
  'row'     // 1-based (i.e. top left corner is row 1)
];

function doPost(e) {

  var result = {
    status: "ok",
    defaultMessage: "Image inserted."
  }

  try {
    var params = (e.postData && e.postData.type == "application/x-www-form-urlencoded") ? e.parameter
    : (e.postData && e.postData.type == "application/json") ? JSON.parse(e.postData.contents)
    : undefined;


    if (!params) throw new Error('Unsupported content-type, must be either application/x-www-form-urlencoded or application/json.');

    REQUIRED_PARAMS.forEach(function(requiredParam) {
      if (!params[requiredParam]) throw new Error('Missing required parameter ' + requiredParam);
    });

    SpreadsheetApp.openById(params.spreadsheetid).getSheetByName(params.sheetname).insertImage(params.imageurl, params.column, params.row);  

  } catch(e) {

    console.error(e); 

    result.status = "error";
    result.error = e;
    result.defaultMessage = e.message;

  }  

  return ContentService.createTextOutput(JSON.stringify(result))
    .setMimeType(ContentService.MimeType.JSON)  
}
Soroush Chehresa
  • 5,490
  • 1
  • 14
  • 29
Jack tileman
  • 813
  • 2
  • 11
  • 26
  • 1. Can I ask you about the blob in your question? 2. In the current stage, unfortunately, there are no methods for directly inserting the image with Sheets API. So it is required to use the workaround. I think that the thread of `I have also looked at SO questions here. [Insert image into Google Sheets cell using Google Sheets API](https://stackoverflow.com/q/43664483)` in your question will resolve your issue. About this, can I ask you about your current issue? – Tanaike Oct 01 '19 at 00:42
  • How do I call SpreadsheetApp from my python application in GAE? – Jack tileman Oct 01 '19 at 00:49
  • Thank you for replying. I apologize for my poor English skill. If you cannot understand my comment, please tell me. I would like to modify it. – Tanaike Oct 01 '19 at 00:51
  • I am trying to understand how to implement the work around. function showImage() { var ss=SpreadsheetApp.getActiveSpreadsheet() var formulaSheet = ss.getSheetByName("Sheet1"); var formulaCell = formulaSheet.getRange("B5"); formulaCell.setFormula('=IMAGE("http://finviz.com/fut_chart.ashx?t=ES&p&p=m5&s=m",4,100,200)') } Is this what you recommend I implement? – Jack tileman Oct 01 '19 at 00:53
  • Thank you for replying. If you can request the POST method, I recommend to use Web Apps for directly inserting the image. Because when `.setFormula('=IMAGE("finviz.com/…) ` is used, the uploaded image data is required to be created as a file and shared publicly. So I would like to recommend to use `insertImage` with a blob. In this case, you can send the base64 data to Web Apps and insert it as an image in the script of Web Apps. For this, Google Apps Script is used. Is this useful for your situation? – Tanaike Oct 01 '19 at 01:03
  • Let me try the POST method and get back to you. Thanks for your quick responses. – Jack tileman Oct 01 '19 at 02:25
  • Is this how we can execute the script from our application? https://developers.google.com/apps-script/api/how-tos/execute . Unfortunately it states that we cannot use the service_accounts . We use service accounts for authentication. How do you propose we execute the Web app scripts from our python application in GAE? – Jack tileman Oct 01 '19 at 02:49
  • Thank you for replying. I have to apologize for my poor English skill. I think that you misunderstood about Web Apps. You can see the document about Web Apps at [here](https://developers.google.com/apps-script/guides/web). – Tanaike Oct 01 '19 at 03:13
  • Thanks for both your comments. I have implemented this - dropoff_signature = "ZGF0YT WVhSaA==" web_app_url = "https://script.google.com/macros/s/A < clip > y/exec" image_data = "data:image/png;base64," + dropoff_signature data_to_post = { 'spreadsheetid' : spreadsheet_Id, 'sheetname' : 'Sheet1', 'imageurl' : image_data, 'column' : 5, 'row' : 5 } encoded_data = urllib.urlencode(data_to_post) # Send encoded data to application-2 url_result = urlfetch.fetch(web_app_url, encoded_data, method='POST') – Jack tileman Oct 01 '19 at 22:58
  • But we are getting the following error - result : 200 content : {"status":"error","defaultMessage":"Error retrieving image from URL or bad URL: data:image/png;base64, – Jack tileman Oct 01 '19 at 22:58
  • we followed the web-app script here - https://stackoverflow.com/questions/43664483/insert-image-into-google-sheets-cell-using-google-sheets-api – Jack tileman Oct 01 '19 at 22:59
  • Thank you for replying and updating your question. In your current situation, you are using the Google Apps Script from [this answer](https://stackoverflow.com/a/48919960/7108653). Is my understanding correct? – Tanaike Oct 01 '19 at 23:31
  • Correct. That is the webapp we are using. – Jack tileman Oct 01 '19 at 23:58
  • Thank you for replying. how about removing `encoded_data = urllib.urlencode(data_to_post)` and modifying `url_result = urlfetch.fetch(web_app_url, encoded_data, method='POST')` to `url_result = urlfetch.fetch(url=web_app_url, payload=json.dumps(data_to_post), method='POST', headers={'Content-type': 'application/json'})`. Unfortunately, in this case, I cannot test this script. So if this didn't resolve your issue, I apologize. – Tanaike Oct 02 '19 at 00:18
  • Can you test the following points? 1. How about redeploying Web Apps as new version? By this, the latest script is reflected to Web Apps. 2. For example, when `image_data` is sent as a URL of `https://www.google.com/images/srpr/logo3w.png`, what result will you retrieve? 3. When you tested this using the curl command including your script, what result did you retrieve? 4. Can the base64 data of `dropoff_signature` decode as an image data of PNG? – Tanaike Oct 02 '19 at 03:16
  • I am able to confirm that the webapp is able insert a image in Google sheet when the URL is "https://www.google.com/images/srpr/logo3w.png". But unfortunately my problem is when the image is a blob, instead of a public URL. Not sure if the issues is with the webapp not able to insert a blob or if my base64data is corrupted? How do we test / confirm this? Any idea? – Jack tileman Oct 02 '19 at 04:04
  • From your replying, I could confirm that your both scripts of python and Google Apps Script are correct for working. About the base64 data, for example, how about directly running a script of `function myFunction() {SpreadsheetApp.getActiveSheet().insertImage("data:image/png;base64,ZA==", 1, 1)}`? In this case, please copy and paste the script to the bound script of Spreadsheet. By this, you can know whether your base64 data can be used for inserting to Spreadsheet. In my environment, I could confirm that an image can be inserted with a sample base64 data. – Tanaike Oct 02 '19 at 04:58
  • If the base64 data could be used for inserting as an image, please check whether there is an issue for the character code when the values are sent from python script to Google Apps Script. – Tanaike Oct 02 '19 at 05:02
  • Ok. Will do. I am really not sure how to check this. Do you have any suggestions? – Jack tileman Oct 02 '19 at 05:15
  • Unfortunately, I cannot understand about `I am really not sure how to check this. Do you have any suggestions?`. – Tanaike Oct 02 '19 at 05:20
  • How do I check if there is an issue for the character code when the values are sent from python script to Google Apps Script? Is there a way to check if my base64 data is corrupt? – Jack tileman Oct 02 '19 at 05:26
  • I have to apologize for my poor English skill. I proposed the method for checking the base64 using Google Apps Script at the latest comment. I comment it again as follows. – Tanaike Oct 02 '19 at 05:30
  • From your replying, I could confirm that your both scripts of python and Google Apps Script are correct for working. About the base64 data, for example, how about directly running a script of `function myFunction() {SpreadsheetApp.getActiveSheet().insertImage("data:image/png;base64,ZA==", 1, 1)}`? In this case, please copy and paste the script to the bound script of Spreadsheet. By this, you can know whether your base64 data can be used. – Tanaike Oct 02 '19 at 05:31

1 Answers1

0

Solution 1:

From your Python application you can use the following code to set an image with the IMAGE formula using Sheets API [1]. You would need to put your Spreadsheet ID and change the range where you want the image.

spreadsheet_id = '[SPREADSHEET-ID]'
range_name = 'D13'

service = build('sheets', 'v4', credentials=creds)

values = [
    [
       '=IMAGE("https://google.com","google")'
    ]
]
body = {
    'values': values
}
result = service.spreadsheets().values().update(
    spreadsheetId=spreadsheet_id, range=range_name,
    valueInputOption='USER_ENTERED', body=body).execute()

Solution 2:

If instead you want to use the insertImage function [2] from Apps Script, to insert an over the grid image into the Sheets and not an image linked to a cell. You can deploy a Web App [3] with a doPost() function where you can do this and call the Web App from your Python application using the service account credentials. Also you need to deploy the Web App to execute as "user accessing the web app" so all the requests you do from the Web App will be made with the service account credentials.

Python script:

from google.oauth2 import service_account
import requests
import json
import google.auth.transport.requests

SCOPES = ['https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/spreadsheets']
SERVICE_ACCOUNT_FILE = 'service_account.json'

credentials = service_account.Credentials.from_service_account_file(
    SERVICE_ACCOUNT_FILE, scopes=SCOPES)

delegated_credentials = credentials.with_subject('[USER-EMAIL-TO-IMPERSONATE]')
delegated_credentials.refresh(google.auth.transport.requests.Request())
token = delegated_credentials.token

headers = {'content-type': 'application/json', 'Authorization': 'Bearer ' + token}
url = '[WEB-APP-URL]'
data = {"file": '[blob]'}
response = requests.post(url, data=json.dumps(data), headers=headers)

Web App script:

function doPost(e) { 
  var ss = SpreadsheetApp.openById('[SPREADSHEET-ID]');
  var sheet = ss.getSheets()[0];
  var blob = DriveApp.getFileById("[IMAGE-ID]").getBlob();
  sheet.insertImage(blob, 4, 14);

  return ContentService.createTextOutput("Good");
}

I tested my code with an image obtained from Drive in the Web App. You could skip that part and send the blob directly from your Python application in the data payload.

To use a service account remember to give API access to all the scopes needed, you need to go to admin.google.com->security->Setting->Advance Settings->Manage API client access and use the service account client ID.

[1] https://developers.google.com/sheets/api/guides/values

[2] https://developers.google.com/apps-script/reference/spreadsheet/sheet#insertImage(BlobSource,Integer,Integer)

[3] https://developers.google.com/apps-script/guides/web

Andres Duarte
  • 3,166
  • 1
  • 7
  • 14
  • Regarding solution 2: can the do post method be called by a service account? how to call it? what are the necessary credentials/permissions etc? – Peter K Jun 03 '21 at 06:14
  • Yes, the service account authentication is being made in the Python script sending the token in the "Authorization" header of the request, we get the token using the google.auth2 library and the [.json credentials file](https://cloud.google.com/docs/authentication/getting-started#creating_a_service_account) – Andres Duarte Jun 07 '21 at 20:07