19

Background

I'm developing a Python 2.7 script that analyzes data from an SQL table and at the end, generates a CSV file.

Once the file is generated, I'm logging into my google sheet account and use the import option to import my CSV file into the google spreadsheet

The manual labor is kinda stupid and I wish to add this ability to my script.

Google Sheets API V4

So, I followed this guide, Python Quickstart and was able to complete all the steps.

Then I followed Google Sheets API reference and looked into Method: spreadsheets.create. If I understand correctly, it does not provides the options to import from a file.

It seems like there is no API for the import functionality.

Question

How to import a CSV file using Google Sheets API V4? Is their an example/reference that I'm missing?

Community
  • 1
  • 1
idanshmu
  • 5,061
  • 6
  • 46
  • 92
  • I know that the OP is asking about importing CSV using only Google Sheets API, but another approach would be to use Google Drive API and create a spread sheet file based on the csv file using MimeType `application/vnd.google-apps.spreadsheet`. You can find an example using golang [here](https://gist.github.com/tanaikech/7ee103c80759a8297da198a5d1e92fc8) (couldn't find an example using python). – Edenshaw Jan 25 '22 at 05:05

5 Answers5

24

You have two options for importing g CSV file. You can use the Drive API to create a spreadsheet from a CSV, or you can use the Sheets API to create an empty spreadsheet and then use spreadsheets.batchUpdate with a PasteDataRequest to add CSV data.

Sam Berlin
  • 3,603
  • 12
  • 23
  • 3
    Would love some more info about how to do the batchUpdate with a PasteDataRequest! The documentation is scarce :/ – Sophia Mar 09 '18 at 14:47
  • 2
    It' frustrating that this information - "Use PasteDataRequest" - isn't covered in the documentation for the sheets API. – Cheeso Aug 08 '18 at 17:36
  • Codelabs has a complete example showing how to use a paste request, which is similar to the other responses, but is a complete cloud function example: https://codelabs.developers.google.com/codelabs/cloud-function2sheet/#8 – lardcanoe Jul 28 '21 at 14:27
20

I've spent couple of hours trying to make any of the other answers work. Libraries do not explain the authentication well, and don't work with google-provided way of handling credentials. On the other hand, Sam's answer doesn't elaborate on the details of using the API, which might be confusing at times. So, here is a full recipe of uploading CSVs to gSheets. It uses both Sam's and CapoChino's answers plus some of my own research.

  1. Authenticate/Setup. Generally, refer to the docs
    • Big blue button will get you credentials.json with no extra steps
    • quickstart.py can easily be adapted into authenticate.py
    • scopes should contain https://www.googleapis.com/auth/spreadsheets

Hopefully by now you have your credentials stored, so let's move to the actual code

  1. Recipe that should work out of the box:
import pickle
from googleapiclient.discovery import build

SPREADSHEET_ID = '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms' # Get this one from the link in browser
worksheet_name = 'Sheet2'
path_to_csv = 'New Folder/much_data.csv'
path_to_credentials = 'Credentials/token.pickle'


# convenience routines
def find_sheet_id_by_name(sheet_name):
    # ugly, but works
    sheets_with_properties = API \
        .spreadsheets() \
        .get(spreadsheetId=SPREADSHEET_ID, fields='sheets.properties') \
        .execute() \
        .get('sheets')

    for sheet in sheets_with_properties:
        if 'title' in sheet['properties'].keys():
            if sheet['properties']['title'] == sheet_name:
                return sheet['properties']['sheetId']


def push_csv_to_gsheet(csv_path, sheet_id):
    with open(csv_path, 'r') as csv_file:
        csvContents = csv_file.read()
    body = {
        'requests': [{
            'pasteData': {
                "coordinate": {
                    "sheetId": sheet_id,
                    "rowIndex": "0",  # adapt this if you need different positioning
                    "columnIndex": "0", # adapt this if you need different positioning
                },
                "data": csvContents,
                "type": 'PASTE_NORMAL',
                "delimiter": ',',
            }
        }]
    }
    request = API.spreadsheets().batchUpdate(spreadsheetId=SPREADSHEET_ID, body=body)
    response = request.execute()
    return response


# upload
with open(path_to_credentials, 'rb') as token:
    credentials = pickle.load(token)

API = build('sheets', 'v4', credentials=credentials)

push_csv_to_gsheet(
    csv_path=path_to_csv,
    sheet_id=find_sheet_id_by_name(worksheet_name)
)

Good thing about directly using batchUpdate is that it uploads thousands of rows in a second. On a low level gspread does the same and should be as performant. Also there is gspread-pandas.

p.s. the code is tested with python 3.5, but this thread seemed to be most appropriate to submit it to.

Ufos
  • 3,083
  • 2
  • 32
  • 36
  • Thank you for that well-done example. Your code sadly overrides all existing `.csv` data on the sheet. Is there a way to only add new items and skip duplicates? – BenjaminK Apr 30 '20 at 02:06
  • 1
    @BenjaminK this would mean you're performing a merge. I suspect, you want some more complicated logic rather than skipping duplicates, because duplicates which overwrite old entries do no affect your data. I'd suggest, that if you need some more complex logic, you do not use gSheet API as a Database engine. Instead: Pull your data, handle it locally, upload the whole thing again. – Ufos May 15 '20 at 11:17
  • To address you question, nonetheless. [This](https://developers.google.com/sheets/api/guides/batchupdate) will help. Intend of using `pasteData` from the example above, you will have to use a combination of `validate`, `update` and `append`. – Ufos May 15 '20 at 11:20
  • Thank you! my issue was that the `"rowIndex"`and `"columnIndex"` have to be sent as integer _within_ a string. Thanks!! – Kevin Delord Jan 07 '21 at 11:19
7

Another alternative to Sam Berlin's answer. If you're using Python, you can use the Drive API via gspread to import a CSV file. Here's an example:

import gspread

# Check how to get `credentials`:
# https://github.com/burnash/gspread

gc = gspread.authorize(credentials)

# Read CSV file contents
content = open('file_to_import.csv', 'r').read()

gc.import_csv('<SPREADSHEET_ID>', content)

Related question: Upload CSV to Google Sheets using gspread

Burnash
  • 3,181
  • 2
  • 31
  • 35
  • 3
    **NOTE** This method removes all other worksheets and then entirely replaces the contents of the first worksheet. – BenjaminK Apr 29 '20 at 12:15
  • @BenjaminK Exactly. It actually uses Drive API to upload the CSV file straight to Google Drive. No local processing is involved. – Burnash Apr 29 '20 at 16:38
  • 1
    @BenjaminK I didn't realize that you've copied the text verbatim from the note in [`import_csv`](https://gspread.readthedocs.io/en/latest/api.html#gspread.Client.import_csv) docs :) – Burnash Apr 29 '20 at 16:40
  • Yes, cause I'm looking for a solution myself and still haven't figured out an easy way to update the data and ignore duplicates :) – BenjaminK Apr 29 '20 at 16:47
  • One of the workarounds that I found was instead of working with CSV directly if the data could be loaded into a Pandas dataframe, gspread lets you [write the dataframe to a worksheet](https://gspread.readthedocs.io/en/latest/user-guide.html#using-gspread-with-pandas) and you can create multiple such worksheets. – shubhamsingh Jan 26 '21 at 05:15
  • updated / correct link to import_csv in the docs: https://docs.gspread.org/en/latest/api/client.html?highlight=csv#gspread.Client.import_csv – Andrew Paullin Sep 29 '22 at 14:44
5

I like Burnash's gspread library, but the import_csv function in his answer is limited. It always starts the paste at A1 of the first worksheet (tab) and deletes all other tabs.

I needed to paste starting at a particular tab and cell, so I took Sam Berlin's suggestion to use a PasteDataRequest. Here's my function:

def pasteCsv(csvFile, sheet, cell):
    '''
    csvFile - path to csv file to upload
    sheet - a gspread.Spreadsheet object
    cell - string giving starting cell, optionally including sheet/tab name
      ex: 'A1', 'MySheet!C3', etc.
    '''
    if '!' in cell:
        (tabName, cell) = cell.split('!')
        wks = sheet.worksheet(tabName)
    else:
        wks = sheet.sheet1
    (firstRow, firstColumn) = gspread.utils.a1_to_rowcol(cell)

    with open(csvFile, 'r') as f:
        csvContents = f.read()
    body = {
        'requests': [{
            'pasteData': {
                "coordinate": {
                    "sheetId": wks.id,
                    "rowIndex": firstRow-1,
                    "columnIndex": firstColumn-1,
                },
                "data": csvContents,
                "type": 'PASTE_NORMAL',
                "delimiter": ',',
            }
        }]
    }
    return sheet.batch_update(body)

Note that I used a raw pasteData request rather than the higher-level update_cells method to take advantage of Google's automatic (correct) handling of input data that contains quoted strings, which may contain non-delimeter commas.

CapoChino
  • 79
  • 1
  • 4
0

An alternative to Sam Berlin's answer, you can turn your CSV into a list of lists and set that to your POST payload.

Such a function looks something like this:

def preprocess(table):
    table.to_csv('pivoted.csv') # I use Pandas but use whatever you'd like
    _file = open('pivoted.csv')
    contents = _file.read()
    array = contents.split('\n')
    master_array = []
    for row in array:
        master_array.append(row.split(','))
    return master_array

That master array gets thrown into the following:

body = {
      'values': newValues
}

    result2 = service.spreadsheets().values().update(spreadsheetId=spreadsheetId, range=rangeName + str(len(values) + start + 1), valueInputOption="USER_ENTERED", body=body).execute()

It works just fine for me.