4

I am having trouble finding any documentation on how to create a GSheet in a certain Google Drive directory using GSpread.

I have checked the documentation and had a look around some of the back end code.

I am currently using the code below to create the spreadsheet:

worksheet = sh.add_worksheet(title='Overview', rows='100', cols='9')

I want to be able to create the spreadsheet in a directory on a google drive, for example:

X > Y > Spreadsheet

Any help would be greatly appreciated,

Cheers.

Tanaike
  • 181,128
  • 11
  • 97
  • 165
Dylan Logan
  • 395
  • 7
  • 18

2 Answers2

11
  • You want to create new Spreadsheet to the specific folder.
  • You want to achieve this using Python.

If my understanding is correct, how about this answer?

Modification points:

  • Unfortunately, Sheets API cannot achieve this. In this case, it is required to use Drive API.
  • In your script, I think that you use gspread.authorize() like gc = gspread.authorize(credentials). In this modification, credentials is used.
  • The script in your question of worksheet = sh.add_worksheet(title='Overview', rows='100', cols='9') is used for adding a sheet to the existing Spreadsheet. When you create new Spreadsheet using gspread, please use sh = gc.create('A new spreadsheet').
    • In this case, the new Spreadsheet is created to the root folder.

Preparation:

Before you use the following script, please enable Drive API at API console, and please add the scope of https://www.googleapis.com/auth/drive. If you are using the scope of https://www.googleapis.com/auth/drive.file, please use this and the scope is not required to be modified to https://www.googleapis.com/auth/drive.

  • If you are using OAuth2, please remove the file including the refresh token. And then, please run the script and reauthorize again. By this, the added scope is reflected to the access token.

  • If you are using Service account, it is not required to remove the file.

Pattern 1:

The following sample script creates new Spreadsheet to the specific folder.

Sample script:

from apiclient import discovery

destFolderId = '###'  # Please set the destination folder ID.
title = '###'  # Please set the Spreadsheet name.

drive_service = discovery.build('drive', 'v3', credentials=credentials)  # Use "credentials" of "gspread.authorize(credentials)".
file_metadata = {
    'name': title,
    'mimeType': 'application/vnd.google-apps.spreadsheet',
    'parents': [destFolderId]
}
file = drive_service.files().create(body=file_metadata).execute()
print(file)

Pattern 2:

If you want to move the existing Spreadsheet to the specific folder, please use the following script.

Sample script:

from apiclient import discovery

spreadsheetId = '###'  # Please set the Spreadsheet ID.
destFolderId = '###'  # Please set the destination folder ID.

drive_service = discovery.build('drive', 'v3', credentials=credentials)  # Use "credentials" of "gspread.authorize(credentials)".
# Retrieve the existing parents to remove
file = drive_service.files().get(fileId=spreadsheetId,
                                 fields='parents').execute()
previous_parents = ",".join(file.get('parents'))
# Move the file to the new folder
file = drive_service.files().update(fileId=spreadsheetId,
                                    addParents=destFolderId,
                                    removeParents=previous_parents,
                                    fields='id, parents').execute()

References:

If I misunderstood your question and this was not the direction you want, I apologize.

Edit:

When you want to share the folder, please use the following script.

Sample script:

drive_service = discovery.build('drive', 'v3', credentials=credentials)  # Use "credentials" of "gspread.authorize(credentials)".
folderId = "###"  # Please set the folder ID.
permission = {
    'type': 'user',
    'role': 'writer',
    'emailAddress': '###',  # Please set the email address of the user that you want to share.
}
res = drive_service.permissions().create(fileId=folderId, body=permission).execute()
print(res)

Reference:

Tanaike
  • 181,128
  • 11
  • 97
  • 165
  • Sorry I didn't include the code, I was already creating a new spreadsheet, I was just adding the sheet to the spreadsheet object. – Dylan Logan Aug 27 '19 at 10:34
  • I am going to try using pattern 2, as using pattern 1 would result in reformatting multiple blocks of code. I will report my findings in the near future, thank you for all your help! – Dylan Logan Aug 27 '19 at 10:35
  • 1
    @Dylan Logan Thank you for replying. I would like to wait for it. By the way, how about your previous question? – Tanaike Aug 27 '19 at 22:29
  • Using pattern 2, would I need to create the folders first? Also how would I share the folders with users. Thanks. – Dylan Logan Aug 28 '19 at 13:16
  • 1
    @Dylan Logan Thank you for replying. At the pattern 2, it supposes that the folder has already been existing. Before you run the script, please set the folder ID to `destFolderId`. And I couldn't notice that you want to share the folder. This is due to my poor English skill. I apologize for this. I updated my answer. Could you please confirm it? – Tanaike Aug 28 '19 at 22:27
  • Sorry for the extremely late response, I can now confirm that this is all working using pattern 2. – Dylan Logan Nov 11 '19 at 15:57
  • I have a problem with Pattern 1. The gspread doc says "If you’re using a service account, this new spreadsheet will be visible only to this account. To be able to access newly created spreadsheet from Google Sheets with your own Google account you must share it with your email." So I need to give permission to the 'file' somehow. – vesszabo Mar 20 '22 at 21:32
  • NOTE: Google API updated naming some time ago: it should: 'file_metadata = { 'title': title, ...' or you would get `Untitled` file name. – Constantine Kurbatov Jun 08 '22 at 19:43
  • @Constantine Kurbatov About `NOTE: Google API updated naming some time ago: it should: 'file_metadata = { 'title': title, ...' or you would get Untitled file name.`, in this case, when Drive API V2 is used, the filename is retrieved with the property of `title`. But, when Drive API V3 is used, the filename is retrieved with the property of `name`. Is this related to your comment? – Tanaike Jun 08 '22 at 23:09
  • 1
    @Constantine Kurbatov Thank you for replying. About `Just tried tests: discovery.build('drive', 'v2', credentials=credentials) and discovery.build('drive', 'v3', credentials=credentials) — both versions require title and doesn't work with name.`, in this case, it is required to know your current script. So, can you post it as a new question including both whole scripts? By this, I would like to confirm it. If you can cooperate to resolve your question, I'm glad. Can you cooperate to do it? – Tanaike Jun 09 '22 at 05:09
  • 1
    @Constantine Kurbatov By the way, the filename property of the file metadata of Drive API V2 and V3 can be seen at [here](https://developers.google.com/drive/api/v2/reference/files) and [here](https://developers.google.com/drive/api/v3/reference/files). – Tanaike Jun 09 '22 at 05:10
  • @Tanaike You are right, after detailed research and tests: V3 API — `{'name': }`, also: `drive_service.files().create(body=file_metadata).execute()` V2 API: `{'title': }`, also `drive_service.files().insert(body=file_metadata).execute()` – Constantine Kurbatov Jun 09 '22 at 05:53
0

Here is a solution that works for me.

It creates a new google spreadsheet within the folder on Google Drive.

NOTE 1: the folder must be shared with the Google Developer account (smth like getgooglesheets@<your-app-name>.iam.gserviceaccount.com). enter image description here

This email could be found in your keyfile.

GOOGLE API V2

from googleapiclient import discovery
from oauth2client.service_account import ServiceAccountCredentials

PATH_TO_KEYFILE = "./"
KEYFILE = "<appname>-<numbers>.json"  # Download from google dev account
DESTFOLDER_ID = "1lZs9O...xLW8D"  # Some folder on Google drive
TITLE = "My_New_Spread_Sheet"
EMAIL = "<new_owner_email>@gmail.com"

def share_file(...):
   """ Routine that changes rights and ownership of the file """
   ...
   return

file_metadata = {
    'title': TITLE,
    'mimeType': 'application/vnd.google-apps.spreadsheet',
    'parents': [{'id': DESTFOLDER_ID}]
}

scope = ['https://spreadsheets.google.com/feeds', 'https://www.googleapis.com/auth/drive']
keyfile = os.path.join(PATH_TO_KEYFILE, KEYFILE)
credentials = ServiceAccountCredentials.from_json_keyfile_name(keyfile, scope)

drive_service = discovery.build('drive', 'v2', credentials=credentials)  # VERSION 2 GOOGLE API

response = drive_service.files().insert(body=file_metadata).execute()  # Here file is created!

print(response)  # full Google API response
print(f" LINK: https://docs.google.com/spreadsheets/d/{file['id']}/edit?usp=drivesdk") # output for convenience...
share_file(file_id=file['id'], email=EMAIL, change_ownership=True)  # Here I change ownership of the file — that is out of the scope of the question

NOTE 2: the new file initially belongs to the developer account, not to the folder owner's account.

GOOGLE API VER 3

same as above, differs in the following:

...

drive_service = discovery.build('drive', 'v3', credentials=credentials)  # VERSION 3 GOOGLE API

file_metadata = {
    'name': TITLE,  #  use 'name' property 
    'mimeType': 'application/vnd.google-apps.spreadsheet',
    'parents': [DESTFOLDER_ID]  # no dictionary inside the list
}

response = drive_service.files().create(body=file_metadata).execute() # 'create()' instead of insert() 

...

Also, the response details will slightly differ.