1
+ from dataclasses import dataclass
2
+ import typing
1
3
import gspread
2
4
import gspread_formatting
3
5
from enum import Enum
4
- from googleapiclient .discovery import build
5
- import numpy as np
6
6
7
7
FONT_SIZE_PTS = 10
8
8
PTS_PIXELS_RATIO = 4 / 3
@@ -16,15 +16,21 @@ class FILE_OVERRIDE_BEHAVIORS(Enum):
16
16
EXIT_IF_IN_SAME_PLACE = 2
17
17
EXIT_ANYWHERE = 3
18
18
19
+
19
20
class WORKSHEET_OVERRIDE_BEHAVIORS (Enum ):
20
21
OVERRIDE = 1
21
22
EXIT = 2
22
23
24
+
23
25
class COLUMN_FORMAT_OPTIONS (Enum ):
24
26
DEFAULT = 1
25
27
PERCENT_UNCOLORED = 2
26
28
PERCENT_COLORED = 3
27
29
30
+
31
+ class CHART_TYPES (Enum ):
32
+ LINE = "LINE"
33
+
28
34
DEFAULT_SHEET_FORMATTING_OPTIONS = {
29
35
"bold_header" : True ,
30
36
"center_header" : True ,
@@ -41,7 +47,7 @@ def authenticate_gspread(authentication_response):
41
47
gc = gspread .authorize (extract_credentials (authentication_response ))
42
48
return gc
43
49
44
- def authenticate_drive_api (authentication_response ):
50
+ def authenticate_google_api (authentication_response ):
45
51
"""Authenticates the Drive API using the response from api.authenticate"""
46
52
return authentication_response [0 ]
47
53
@@ -107,21 +113,21 @@ def search_for_folder_id(drive_api, folder_name, allow_trashed = False, allow_du
107
113
return [file ["id" ] for file in files_exact_match ]
108
114
109
115
110
- def create_sheet_in_folder (authentication_response , sheet_name , parent_folder_name = None , override_behavior = FILE_OVERRIDE_BEHAVIORS .EXIT_ANYWHERE ):
116
+ def create_sheet_in_folder (drive_authentication_response , sheet_name , parent_folder_name = None , override_behavior = FILE_OVERRIDE_BEHAVIORS .EXIT_ANYWHERE ):
111
117
"""
112
118
Create a new sheet in the project with the given name and parent folder.
113
119
Returns the new sheet.
114
120
115
- :param authentication_response : the service parameters tuple
121
+ :param drive_authentication_response : the service parameters tuple
116
122
:param sheet_name: the name of the new sheet
117
123
:param parent_folder_name: the name of the parent folder for the new sheet
118
124
:param override_behavior: the behavior to take if the sheet already exists
119
125
:returns: the gspread.Spreadsheet object of the new sheet
120
126
:rtype: gspread.Spreadsheet
121
127
"""
122
128
# Build Drive API
123
- gc = authenticate_gspread (authentication_response )
124
- drive_api = authenticate_drive_api ( authentication_response )
129
+ gc = authenticate_gspread (drive_authentication_response )
130
+ drive_api = authenticate_google_api ( drive_authentication_response )
125
131
parent_folder_id = None if parent_folder_name is None else search_for_folder_id (drive_api , parent_folder_name )[0 ]
126
132
127
133
# Check if sheet already exists and handle based on input
@@ -309,4 +315,124 @@ def fill_spreadsheet_with_df_dict(sheet, df_dict, overlapBehavior, sheet_formatt
309
315
sheet , df , worksheet_name , overlapBehavior ,
310
316
sheet_formatting_options = sheet_formatting_options .get (worksheet_name , DEFAULT_SHEET_FORMATTING_OPTIONS ),
311
317
column_formatting_options = column_formatting_options .get (worksheet_name , {})
312
- )
318
+ )
319
+
320
+ def update_sheet_raw (sheets_authentication_response , sheet , * updates ):
321
+ """
322
+ Directly call the Google Sheets api to update the specified sheet with the optional arguments.
323
+ """
324
+ assert len (updates ) > 0
325
+ sheets_api = authenticate_google_api (sheets_authentication_response )
326
+ sheet_id = sheet .id
327
+ body = {"requests" : list (updates )}
328
+ response = (
329
+ sheets_api .spreadsheets ()
330
+ .batchUpdate (spreadsheetId = sheet_id , body = body )
331
+ .execute ()
332
+ )
333
+ return response
334
+
335
+ REQUIRED_CHART_ARGS = []
336
+
337
+ DEFAULT_CHART_ARGS = {
338
+ "title" : "" ,
339
+ "x_axis_title" : "" ,
340
+ "y_axis_title" : "" ,
341
+ "chart_position" : None # Means it will be created in a new sheet
342
+ }
343
+
344
+ @dataclass
345
+ class WorksheetRange :
346
+ worksheet : gspread .worksheet .Worksheet
347
+ top_left : gspread .cell .Cell
348
+ bottom_right : gspread .cell .Cell
349
+
350
+ @property
351
+ def range_dict (self ):
352
+ return {
353
+ "sheetId" : self .worksheet .id ,
354
+ "startRowIndex" : self .top_left .row - 1 ,
355
+ "endRowIndex" : self .bottom_right .row - 1 ,
356
+ "startColumnIndex" : self .top_left .col - 1 ,
357
+ "endColumnIndex" : self .bottom_right .col - 1 ,
358
+ }
359
+
360
+ def _cell_to_grid_coordinate (cell , worksheet ):
361
+ return {
362
+ "sheetId" : worksheet .id ,
363
+ "rowIndex" : cell .row - 1 ,
364
+ "columnIndex" : cell .col - 1 ,
365
+ }
366
+
367
+ def add_chart_to_sheet (sheets_authentication_response , sheet , worksheet , chart_type , domain , series , ** chart_args ):
368
+ complete_chart_args = {** DEFAULT_CHART_ARGS , ** chart_args }
369
+ print (worksheet .id )
370
+ if complete_chart_args ["chart_position" ] is not None :
371
+ position_dict = {
372
+ "overlayPosition" : {
373
+ "anchorCell" : _cell_to_grid_coordinate (complete_chart_args ["chart_position" ], worksheet )
374
+ }
375
+ }
376
+ else :
377
+ position_dict = {"newSheet" : True }
378
+ formatted_domains = [
379
+ {
380
+ "domain" : {
381
+ #TODO: would be nice to also support column references https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/other#DataSourceColumnReference
382
+ "sourceRange" : {
383
+ "sources" : [
384
+ domain .range_dict
385
+ ],
386
+ },
387
+ },
388
+ },
389
+ ]
390
+ formatted_series = [
391
+ {
392
+ "series" : {
393
+ "sourceRange" : {
394
+ "sources" : [
395
+ series_source .range_dict
396
+ ],
397
+ },
398
+ },
399
+ "targetAxis" : "LEFT_AXIS" ,
400
+ }
401
+ for series_source in series
402
+ ]
403
+ formatted_axis = []
404
+ if complete_chart_args ["x_axis_title" ]:
405
+ formatted_axis .append ({
406
+ "title" : complete_chart_args ["x_axis_title" ],
407
+ "position" : "BOTTOM_AXIS" ,
408
+ })
409
+ if complete_chart_args ["y_axis_title" ]:
410
+ formatted_axis .append ({
411
+ "title" : complete_chart_args ["y_axis_title" ],
412
+ "position" : "LEFT_AXIS" ,
413
+ })
414
+ print (formatted_domains )
415
+ print (formatted_series )
416
+ request = {
417
+ "addChart" : {
418
+ "chart" : {
419
+ "spec" : {
420
+ "title" : complete_chart_args ["title" ],
421
+ #TODO: insert legend position
422
+ #TODO: insert axis positions
423
+ "basicChart" : {
424
+ "axis" : formatted_axis ,
425
+ "chartType" : chart_type .value ,
426
+ "domains" : formatted_domains ,
427
+ "headerCount" : 1 , #TODO: not sure what this means
428
+ "series" : formatted_series ,
429
+ },
430
+ },
431
+ "position" : position_dict
432
+ },
433
+ },
434
+ }
435
+ print (request )
436
+
437
+ response = update_sheet_raw (sheets_authentication_response , sheet , request )
438
+ return response
0 commit comments