Dennis Richard
Dennis Richard

Reputation: 1

Google API to create/update files on 'Shared with me' folders

I have been trying to use the Google API to create files on a folder that's been shared with me by another user (I made sure I have edit permissions on it). When I was using the files.create module with supportsAllDrives=True I got the following error message:

{ "errorMessage": "<HttpError 404 when requesting https://www.googleapis.com/upload/drive/v3/files?supportsTeamDrives=true&alt=json&uploadType=multipart returned "File not found: 1aLcUoiiI36mbCt7ZzWoHr8RN1nIPlPg7.". Details: "[{'domain': 'global', 'reason': 'notFound', 'message': 'File not found: 1aLcUoiiI36mbCt7ZzWoHr8RN1nIPlPg7.', 'locationType': 'parameter', 'location': 'fileId'}]">", "errorType": "HttpError", "requestId": "fc549b9e-9590-4ab4-8aaa-f5cea87ba4b6", "stackTrace": [ " File "/var/task/lambda_function.py", line 154, in lambda_handler\n upload_file(service, download_path, file_name, file_name, folder_id, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')\n", " File "/var/task/lambda_function.py", line 78, in upload_file\n file = service.files().create(\n", " File "/opt/python/googleapiclient/_helpers.py", line 131, in positional_wrapper\n return wrapped(*args, **kwargs)\n", " File "/opt/python/googleapiclient/http.py", line 937, in execute\n raise HttpError(resp, content, uri=self.uri)\n" ] }

After a bit of digging in, I found that 'Shared Drives' is different from 'Shared with me' and all the APIs I found so far apply to the 'Shared Drives' only. The supportsTeamDrives=True has been deprecated and I was not able to find a related replacement parameter in the docs. There is a parameter sharedWithMe=True for the file.list api and I'm not sure how I can use this in my code because file.create doesn't see the folderID for a 'Shared with me' folder anyway. Any suggestions are appreciated in advance!

My current code:

def upload_file(service, file_name_with_path, file_name, description, folder_id, mime_type):  
    
media_body = MediaFileUpload(file_name_with_path, mimetype=mime_type)

body = {
    'name': file_name,
    'title': file_name,
    'description': description,
    'mimeType': mime_type,
    'parents': [folder_id]
}

file = service.files().create(
    supportsAllDrives=True,
    supportsTeamDrives=True,
    body=body,
    media_body=media_body).execute()

Upvotes: 0

Views: 1504

Answers (2)

Dennis Richard
Dennis Richard

Reputation: 1

After a chat with a Google Workspace API specialist, turns out there is no API available to perform the above task. For clarity, refer the picture where my target folder lies.

Difference between 'Shared Drive' and 'Shared with me' (image)

Here's the response from the Support Agent:

I reviewed your code and everything was done perfectly, so I spoke to our Drive Specialists, and they have explained to me that "Shared with me" it's more than anything a label, and because you are not the owner of the file, (like you would be if they were in "My Drive" )nor the co-owner (if they were located in "Shared Drive") it does not allow you to use any type of API in order to automate file creation or deletion or anything for that matter.

In this case you can either make a copy on your Drive and automate it there, and just update it every now and then in the file that was shared with you, or just ask the user to move it to the "Shared Drive" and access it from there.

I confess I'm a little disappointed that there is no API way to add/delete/edit in another user's folder in spite of having permissions to do so. My understanding as a developer is that the CLI is the ultimate most powerful way to interact with any service. GUI comes second to CLI, it's just a more visually appealing medium. Often times, when we are not able to perform a task using the GUI, we turn to CLI and manage high granularity and precision.

But this was a completely upside down scenario! I'm failing to understand how I'm able to access the 'shared folder' and make adds and deletes through the GUI but unable to do the same using a script. I understand now that 'Shared with me' is just a label and not a 'location' for me to access the folder but surely I would have assumed there was another API way to access a folder that belonged to another user (using the person's username/ID for identification, folder path as target, verifying if I have permissions to make said changes for authentication, returning an error if I don't, lastly executing the API).

If someone's able to explain to me if there is a specific reason why this is not made available to end users, I would love to learn about it please.

EDIT

I'm a bit late posting the solution here, but the issue turned out to be that the google workspace service account that was being used by my API did not have write permissions to the Shared Drive I was trying to query. Once the service account was given the required edit permissions, my code worked perfectly.

Upvotes: 0

Yancy Godoy
Yancy Godoy

Reputation: 591

Modified answer to include more details:

You are correct 'Shared Drives' are different from 'Shared With Me'. First off, you need to get the ID from the shared with you folder, for this you can use files:list. To upload files to that folder or any type of folder you can use the modified code below:

from __future__ import print_function
import pickle
import os.path
from googleapiclient.http import MediaFileUpload
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from google.oauth2 import credentials, service_account

# Scopes required by this endpoint -> https://developers.google.com/drive/api/v3/reference/files/create
SCOPES = ['https://www.googleapis.com/auth/drive']
"""
To upload/create a file in to a 'Shared with me' folder this script has the following configured:

1. Project:
    * Create project 
    * Enable the Google Workspace API the service account will be using:     https://developers.google.com/workspace/guides/create-project

2.Consent screen:
    * Configure the consent screen for the application 
    * Create credentials for your service account depending on the type of application to be used with https://developers.google.com/workspace/guides/create-credentials#create_a_service_account 
    Once your Service Account is created you are taken back to the credentials list (https://console.cloud.google.com/apis/credential) click on the created Service Account, next click on ‘Advanced settings’ and copy your client ID

3. Scopes
    * Collect the scopes needed for your service account/application
     https://developers.google.com/identity/protocols/oauth2/scopes

4. Grant access to user data to a service account in Google Workspace https://admin.google.com/ac/owl/domainwidedelegation
    * In the "Client ID" field, paste the client ID  from your service account
    * In the "OAuth Scopes" field, enter a comma-delimited list of the scopes required by your application. This is the same set of scopes you defined when configuring the OAuth consent screen.
    * Click Authorize.

5. In your code you need to impersonate the account the folder was shared with, if it was your account, you add your account here:
    credentials = service_account.Credentials.from_service_account_file(
                SERVICE_ACCOUNT_FILE, scopes=SCOPES)
    delegated_creds = credentials.with_subject('[email protected]')
"""

def main():

    SERVICE_ACCOUNT_FILE = 'drive.json' #Service Account credentials from Step 2

    credentials = service_account.Credentials.from_service_account_file(
                SERVICE_ACCOUNT_FILE, scopes=SCOPES)
    delegated_creds = credentials.with_subject('[email protected]')

    service = build('drive', 'v3', credentials=delegated_creds)


    media = MediaFileUpload(
        'xfiles.jpg',
        mimetype='image/jpeg',
        resumable=True
        )
    request = service.files().create(
        media_body=media,
        body={'name': 'xfile new pic', 'parents': ['1Gb0BH1NFz30eau8SbwMgXYXDjTTITByE']} #In here 1Gb0BH1NFz3xxxxxxxxxxx is the 'Shared With ME'FolderID to upload this file to
        )

    response = None
    while response is None:
            status, response = request.next_chunk()
            if status:
                print("Uploaded %d%%." % int(status.progress() * 100))
    print("Upload Complete!")


if __name__ == '__main__':
    main()

Where:

parents is the ID of the folder shared with you.

See here for more documentation details

Upvotes: 0

Related Questions