Check if workbook exists using gspread library? - google-drive-api

How to check if workbook exists using gspread library?
My problem is that if I run gspread.create(title) second time it does not rewrite the previous file but creates one more with the same name. Any aidea how to avoid it?
Thanks.
Petro.

I believe your goal as follows.
You want to check whether the Spreadsheet is existing using the Spreadsheet name.
When the Spreadsheet is not existing, you want to create new Spreadsheet, and retrieve the created Spreadsheet.
When the Spreadsheet is existing, you want to retrieve the existing Spreadsheet.
You want to use gspread.
In this case, in order to checking whether the Spreadsheet is existing using the Spreadsheet name, it is required to use Drive API. The sample script is as follows.
Sample script:
Please copy and paste the following script. This script doesn't include the authorization script. So, please add your script for retrieving client = gspread.authorize(credentials).
spreadsheetName = "sample Spreadsheet name" # Please set the Spreadsheet name you want to check.
client = gspread.authorize(credentials)
url = "https://www.googleapis.com/drive/v3/files?q=mimeType%3D%27application%2Fvnd.google-apps.spreadsheet%27%20and%20name%3D%27" + spreadsheetName + "%27%20"
res = requests.get(url, headers={"Authorization": "Bearer " + credentials.access_token})
files = res.json().get("files")
spreadsheet = client.open_by_key(files[0]['id']) if files else client.create(spreadsheetName)
# Sample script for testing "spreadsheet".
sheetNameOf1stTab = spreadsheet.sheet1.title
print(sheetNameOf1stTab)
In this case. please add import requests. The method of "Files: list in The Drive API is used with request library.
When above script is run, at first, the Spreadsheet name of spreadsheetName is searched using the method of "Files: list in The Drive API v3. And, when the Spreadsheet is existing, the existing Spreadsheet is retrieved as the Spreadsheet object for gspread. When the Spreadsheet is NOT existing, new Spreadsheet is created and the created Spreadsheet object for gspread is retrieved.
If you want to search the Spreadsheet from the specific folder, please use url = "https://www.googleapis.com/drive/v3/files?q=mimeType%3D%27application%2Fvnd.google-apps.spreadsheet%27%20and%20name%3D%27" + spreadsheetName + "%27%20and%20%27" + folderId + "%27%20in%20parents".
In this case, please set folderId.
Note:
If an error related to the scope occurs, please include https://www.googleapis.com/auth/drive or https://www.googleapis.com/auth/drive.readonly.
Reference:
Files: list

You can check it with try/except + gc.open():
from gspread.exceptions import SpreadsheetNotFound
spreadsheetName = '' # Enter your spreadsheet name
gc = gspread.authorize(credentials)
try:
gc.open(spreadsheetName)
# Code if spreadsheet exists:
print("Spreadsheet exists")
except SpreadsheetNotFound:
# Code if spreadsheet doesn't exist:
print("Spreadsheet doesn't exist")
If spreadsheet exists, then gc.open() returns value and try block is run.
If spreadsheet doesn't exist, then gc.open() raises error "SpreadsheetNotFound" and except block is run.

Related

Read a CSV from GitHub to Google sheets

Similar to this, I wish to read data stored in csv format in a GitHub repository directly into Google sheets.
An example of the data in question is here. When I tell google sheets to import the (raw) data from this url, it finds no matching file: .
Can a direct import be achieved this simply, or does it necessarily involve using an API?
In your situation, how about the following patterns?
Pattern 1:
In this pattern, IMPORTDATA is used. A sample formula is as follows. Please put this formula into a cell of Spreadsheet.
=IMPORTDATA("https://raw.githubusercontent.com/emagar/elecRetrns/master/data/pred1964-on.csv")
Pattern 2:
In this pattern, Google Apps Script is used. Please copy and paste the following script to the script editor of Spreadsheet and run the script. By this, the retrieved CSV data is put into the active sheet.
function myFunction() {
// Retrieve CSV data and parse it.
const url = "https://raw.githubusercontent.com/emagar/elecRetrns/master/data/pred1964-on.csv";
const str = UrlFetchApp.fetch(url).getContentText();
const ar = Utilities.parseCsv(str);
// Put the values into the active sheet.
const sheet = SpreadsheetApp.getActiveSheet(); // or SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1")
sheet.getRange(1, 1, ar.length, ar[0].length).setValues(ar);
}
References:
IMPORTDATA
fetch(url)
parseCsv(csv)
If you have to do that only once, this can be helpful -
Have you tried the following steps:
Copy and paste the data from Here to notepad
Save the notepad file as xyz.csv

Running an Apps Script stored in Google Drive from Google Sheets

I have a google sheets workbook that builds a report based on user input, which is run by clicking a "button" (a square shape on the sheet itself). I want to share this workbook with my team, who need to create a copy of the workbook so they can generate multiple reports for themselves.
However, I also want to be able to make changes to the code at a later date, and avoid having them re-download the latest version, so I'm trying to decentralise the Apps Script file by putting it into my company's shared Google Drive, and then the workbook script replaced by a function that loads that file in the drive.
So far I have:
function getApp(){
var folderId = "<folder_id>";
var fileName = "<file_name>";
var scriptId = "<script_id>";
var folder = DriveApp.getFolderById(folderId);
var files = folder.getFilesByName(fileName);
var url = "https://script.google.com/feeds/download/export?id=" +
scriptId + "&format=json"
var options = {
"method": "GET",
"headers": {
"Authorization": "Bearer " + ScriptApp.getOAuthToken()
},
"muteHttpExceptions": true
};
var response = UrlFetchApp.fetch(url, options);
dataContentAsString = response.getContentText();
fileContents = JSON.parse(dataContentAsString);
var codeFile = fileContents.files[1];
if (codeFile){
var code = codeFile.source;
eval(code);
buildReport();
}
}
Which takes the "file" at index 1 (an object containing all functions in the script) and runs the buildReport function. When I do Logger.log(fileContents) I can see the entire script, so I know the retrieval from google drive is working.
buildReport() is the "main" function, which then calls other functions, however when running it, I get the error below, which indicates an Oauth issue:
Exception: You do not have permission to call
SpreadsheetApp.getActive. Required permissions:
(https://www.googleapis.com/auth/spreadsheets.currentonly ||
https://www.googleapis.com/auth/spreadsheets)
Does this mean that despite being able to access the file, the file itself doesn't have access to the sheets (that contain templates which the script manipulates based on the users initial inputs prior to clicking the button) where the macro is being run from?
Is this the best way to achieve what I want?
Update
I added a trigger to the workbook, which runs buildReport just fine when the spreadsheet is opened (not the desired behaviour, but still at least it's working in some way), however when clicking the "Build Report" button it shows the error still.
Why would the local script (i.e. local to the google sheet) be able to successfully import from google drive and run buildReport() when using a trigger, but not when clicking a button to do the same thing?
If you have an standalone script that will "modify" the user current Spreadsheet (or slides, or...) but the script bounded to the Spreadsheet only "calls" the external script, you can add a dummy/commented line to the bound script, so that when the user runs the bound script, permissions for the standalone script will also be asked. Sorry, for my english :D
Adding this line, anywhere, will do the trick:
//SpreadsheetApp.getActive()
BTW, I found very useful your way to share scripts!

Bulk copy files on google drive from a list of hyperlinks

I have a list of 200 hyperlinks saved on a spreadsheet. Those links are for files (particularly Google Slides files) all saved in Google Drive. They are scattered in sub folders under the same root folder that has ~1500 files
Link 1
Link 2
Link 3
...
Link 200
I want to make a copy of those 200 files only. There is no common search term or filter to pull them up on Google Drive search. So I need to work off that list
Thoughts on doing this? Thanks in advance!
I believe your current situation and your goal as follows.
You have the Spreadsheet including 200 hyperlinks of Google Slides like https://docs.google.com/presentation/d/FILE_ID.
You want to copy the Google Slides to the specific folder in your Google Drive.
You want to achieve this using Google Apps Script.
From the number of hyperlinks, I thought that in this case, the batch request might be useful for your situation. When the batch request is used, the process cost will become low because the batch request is run with the asynchronous process. So, in this answer, I would like to propose to copy the files of 200 hyperlinks using the batch request. The sample script is as follows.
Usage:
1. Install a Google Apps Script library.
In this script, in order to achieve the batch request, a Google Apps Script library is used. Ref I thought that the request of the batch request might be a bit complecate. Ref So I created this library for using the batch request with Google Apps Script. The library's project key is as follows.
1HLv6tWz0oXFOJHerBTP8HsNmhpRqssijJatC92bv9Ym6HSN69_UuzcDk
The method for installing the library can be seen at the official document. Ref
2. Sample script.
Please copy and paste the following script to the script editor of your Google Spreadsheet including 200 hyperlinks like https://docs.google.com/presentation/d/FILE_ID. This script used Drive API. So, before you use this script, please enable Drive API at Advanced Google services. And, run the function "myFunction".
function myFunction() {
const sheetName = "Sheet1"; // Please set the sheet name.
const destinationFolderId = "###"; // Please set the destination folder ID.
// 1. Retrieve file IDs from Spreadsheet.
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
const fileIds = sheet.getRange("A1:A" + sheet.getLastRow()).getValues().reduce((ar, [a]) => {
if ((/^https:\/\/docs.google.com\/presentation\/d/).test(a)) ar.push(a.split("/")[5]);
return ar;
}, []);
console.log(fileIds) // You can check the retrieved file ID.
// 2. Retrieve the filenames form the file IDs using the "Files: get" method with the batch request.
const requests1 = fileIds.map(id => ({
method: "GET",
endpoint: `https://www.googleapis.com/drive/v3/files/${id}?supportsAllDrives=true`,
}));
const res1 = BatchRequest.EDo({batchPath: "batch/drive/v3", requests: requests1});
console.log(res1) // You can check the retrieved file metadata.
// 3. Copy the files using the file IDs and filenames using the "Files: copy" method with the batch request.
const requests2 = res1 .map(({id, name}) => ({
method: "POST",
endpoint: `https://www.googleapis.com/drive/v3/files/${id}/copy?supportsAllDrives=true`,
requestBody: {parents: [destinationFolderId || "root"], name},
}));
const res2 = BatchRequest.EDo({batchPath: "batch/drive/v3", requests: requests2});
console.log(res2);
}
Note:
In this sample script, it supposes that the 200 hyperlinks are put in the column "A" of "Sheet1". So, please modify this for your actual situation. Please be careful this.
References:
Batch request of official document
Files: get
Files: copy
BatchRequest of Google Apps Script library
Assuming the links look something similar to this and they're stored in the A column:
https://docs.google.com/presentation/d/SLIDE_ID/edit
You can easily extract the slideId from the hyperlink which corresponds to the fileId by making use of this formula (by dragging it down the whole A column):
=REGEXEXTRACT(A1,"[-\w]{25,}")
Finally, in order to copy each file, you can make use of Apps Script’s DriveApp, something similar to this:
DriveApp.getFileById(“fileId”).makeCopy(“destination”);
However, since the fileId corresponds to a range in the sheet, you can pass directly the range - so instead of using “fileId”, you could use this:
let sheet = SpreadsheetApp.openById(“spreadsheetId”).getSheetByName(“sheetName”);
let fileId = sheet.getRange(1,2).getValue();
The snippet above is retrieving the sheet where the links are stored and then by making use of the getRange and getValue methods it retrieves the value from the B1 cell (assuming that the ids of the files will be in the B column after REGEXEXTRACT).
Note
Please bear in mind that you can extract the fileId as well directly in your script, depending on the workaround and programming language .
Reference
Files Class Apps Script;
Spreadsheet Class Apps Script;
Range Class Apps Script;
REGEXEXTRACT function.

google script openByUrl autorization, trigger activation

I'm quite new to programming and struggling with apps authorization :
I created a code that has to access other files of my Gdrive, with the command openByUrl, which sends back an error message, telling me it doesn't have the authorization to perform openByUrl.
I went through the oauth2 scopes but can't understand how to give the authorization to my script.
I already have an internet copied script that runs DriveApp.getFolderById, but it runs through a menu.
I want both getFolderById and openByUrl to run on a trigger (each day at midnight) thus, it can't run through a menu.
The goal of my script is to list all files in a specified folder, get the spreadsheet ones, and extract specific cells from these spreadsheets.
I got something running with importance, but clearly, it's too bulky to run properly on more than 3000 files.
Could anyone explain to me how to give authorizations to openByUrl please? (even better if in French ;-) )
Many thanks in advance for your help.
Answer
I would create a new project and copy the whole code.
If you want to check why in a particular project you have this error, you can open the manifest file that has the required scopes. You can find it clicking on Project Settings on the left bar and then Show "appsscript.json" manifest file in editor. The field oauthScopes has to contain all the required scopes or has to be deleted.
Methods explanation:
DriveApp.getFoldersByName(name) get all the folders with a specified name. Take the first one with .next()
Folder.getFilesByType get all the files with a specific mimetype
SpreadsheetApp.openById open a specific Spreadsheet document
Spreadsheet.getSheets() get all the sheets in an array
Sheet.getRange() get a specific range
Range.getValues() get the values of a specified range
Updated code:
function main() {
var folderName = 'folder'
var type = MimeType.GOOGLE_SHEETS
var folders = DriveApp.getFoldersByName(folderName).next().getFolders()
while (folders.hasNext()) {
var folder = folders.next()
console.log(folder.getName())
var files = folder.getFilesByType(type)
while (files.hasNext()) {
var file = files.next()
console.log(file.getName() + ': ' + file.getid())
var sheets = SpreadsheetApp.openById(file.getid()).getSheets()
var values = sheets[0].getRange('A1:B2').getValues()
console.log(values)
}
}
}

Google Sheets - Script to “Replace” instead of “Create”, when file name is the same

EDIT to try and make this easier to understand:
Here is what I did:
I created a Google Spreadsheet. I created a SCRIPT that saves it to a Google Drive Folder using a File Name based on the date of service and Customer Name.
This is the Script I currently have:
// This creates a custom Menu Function so that I can save the file.
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Choice Menu')
.addItem('Submit Form','saveAsSpreadsheet')
.addToUi(); }
// Saves Spreadsheet in Google Drive Folder
function saveAsSpreadsheet() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var destFolder = DriveApp.getFolderById("0B8xnkPYxGFbUMktOWm14TVA3Yjg");
DriveApp.getFileById(sheet.getId()).makeCopy(getFilename(), destFolder);
}
//This Function uses a cell "G4" (Which is Date and Customer Name) to create a file name. This file name will be then used for the above Script.
function getFilename() {
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getSheetByName('Manifest');
var cell = sheet.getRange('G4');
var filename = cell.getValue();
return filename;}
So My code works GREAT except for one Problem.
Here is my Problem:
When I Save the Spreadsheet a 2nd time using the above Script, it saves a new file. I want to save it as a new file unless the Filename is the same. IF the file name is the same, I want to delete the original file, and then save the new file.
What I tried:
From what I understand reading the comments below, I need to run a code that "1. will need to run a query to see if any files exist with your chosen name, and then act accordingly. 2. If there are no files with that name, then go ahead and create one (using the function I listed above). If there is a file with the same name, 3. delete the original file and create a new one with the same name.
I tried for several days coming trying different options, and none of it worked, so I am looking to start over.
Appreciate any coding solutions or direction on where to go!
Your script is the container-bound script of Spreadsheet.
When the Spreadsheet with the searched filename is existing in a specific folder, you want to replace it to the active Spreadsheet.
In the current stage, a file cannot be replaced while the file ID is not changed. So in this modified script, the active Spreadsheet is copied and the searched file is deleted.
When the searched file is not existing in a specific folder, you want to copy the active Spreadsheet to the folder.
You want to use the value returned from getFilename() as the filename.
If my understanding is correct, how about this modification? I think that there are several solutions for your situation. So please think of this as just one of them.
Modified script:
Please modify saveAsSpreadsheet() as follows.
function saveAsSpreadsheet() {
var folderId = "0B8xnkPYxGFbUMktOWm14TVA3Yjg";
var folder = DriveApp.getFolderById(folderId);
var files = folder.getFilesByName(getFilename());
if (files.hasNext()) {
files.next().setTrashed(true);
}
var sheet = SpreadsheetApp.getActiveSpreadsheet();
DriveApp.getFileById(sheet.getId()).makeCopy(getFilename(), folder);
}
Note:
In order to search the file, I used getFilesByName() because I thought the filename seems constant.
setTrashed() for removing the file is used for this script. In this case, the file is put in the trash box. If you want to directly delete the file, you can achieve it using Drive API. At that time, please tell me.
References:
getFilesByName()
setTrashed()
Edit:
If you want to directly delete the file, please modify the script as follows.
From:
files.next().setTrashed(true);
To:
var params = {method: "delete", headers: {Authorization: "Bearer " + ScriptApp.getOAuthToken()}};
UrlFetchApp.fetch("https://www.googleapis.com/drive/v3/files/" + files.next().getId(), params);
I used the method using UrlFetchApp because I thought that in your situation, Drive API has already been enabled. In this case, you can use the delete method of Drive API by only replacing the script.
Reference:
delete method of Drive API
You need to take a step back and change your thinking about filenames.
In GDrive, files are identified by their unique ID. The name is just a metadata property alongside timeModified, owner, mimeType, etc. So having multiple files with the same name is as natural as having multiple files with the same mimeType.
Your code will need to run a query to see if any files exist with your chosen name, and then act accordingly. If there are no files with that name, then go ahead and create one. If there are, then you have the choice to update the existing file, or delete the original file(s) and create a new one with the same name.