1111# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212# See the License for the specific language governing permissions and
1313# limitations under the License.
14+ # pylint: disable=(consider-using-f-string)
1415
1516"""
1617docs-mail-merge.py (Python 2.x or 3.x)
2223
2324import time
2425
25- from googleapiclient import discovery
26- from httplib2 import Http
27- from oauth2client import client , file , tools
26+ import google . auth
27+ from googleapiclient . discovery import build
28+ from googleapiclient . errors import HttpError
2829
2930# Fill-in IDs of your Docs template & any Sheets data source
30- DOCS_FILE_ID = 'YOUR_TMPL_DOC_FILE_ID'
31- SHEETS_FILE_ID = 'YOUR_SHEET_DATA_FILE_ID'
31+ DOCS_FILE_ID = "195j9eDD3ccgjQRttHhJPymLJUCOUjs-jmwTrekvdjFE"
32+ SHEETS_FILE_ID = "11pPEzi1vCMNbdpqaQx4N43rKmxvZlgEHE9GqpYoEsWw"
3233
3334# authorization constants
34- CLIENT_ID_FILE = 'credentials.json'
35- TOKEN_STORE_FILE = 'token.json'
35+
3636SCOPES = ( # iterable or space-delimited string
3737 'https://www.googleapis.com/auth/drive' ,
3838 'https://www.googleapis.com/auth/documents' ,
5050 'New York, NY 10011-4962' ),
5151)
5252
53-
54- def get_http_client ():
55- """Uses project credentials in CLIENT_ID_FILE along with requested OAuth2
56- scopes for authorization, and caches API tokens in TOKEN_STORE_FILE.
57- """
58- store = file .Storage (TOKEN_STORE_FILE )
59- creds = store .get ()
60- if not creds or creds .invalid :
61- flow = client .flow_from_clientsecrets (CLIENT_ID_FILE , SCOPES )
62- creds = tools .run_flow (flow , store )
63- return creds .authorize (Http ())
64-
53+ creds , _ = google .auth .default ()
54+ # pylint: disable=maybe-no-member
6555
6656# service endpoints to Google APIs
67- HTTP = get_http_client ()
68- DRIVE = discovery . build ('drive' , 'v3 ' , http = HTTP )
69- DOCS = discovery . build ('docs' , 'v1' , http = HTTP )
70- SHEETS = discovery . build ('sheets' , 'v4' , http = HTTP )
57+
58+ DRIVE = build ('drive' , 'v2 ' , credentials = creds )
59+ DOCS = build ('docs' , 'v1' , credentials = creds )
60+ SHEETS = build ('sheets' , 'v4' , credentials = creds )
7161
7262
7363def get_data (source ):
7464 """Gets mail merge data from chosen data source.
7565 """
76- if source not in {'sheets' , 'text' }:
77- raise ValueError ('ERROR: unsupported source %r; choose from %r' % (
78- source , SOURCES ))
79- return SAFE_DISPATCH [source ]()
66+ try :
67+ if source not in {'sheets' , 'text' }:
68+ raise ValueError (f"ERROR: unsupported source { source } ; "
69+ f"choose from { SOURCES } " )
70+ return SAFE_DISPATCH [source ]()
71+ except HttpError as error :
72+ print (f"An error occurred: { error } " )
73+ return error
8074
8175
8276def _get_text_data ():
@@ -91,7 +85,9 @@ def _get_sheets_data(service=SHEETS):
9185 (header) row. Use any desired data range (in standard A1 notation).
9286 """
9387 return service .spreadsheets ().values ().get (spreadsheetId = SHEETS_FILE_ID ,
94- range = 'Sheet1' ).execute ().get ('values' )[1 :] # skip header row
88+ range = 'Sheet1' ).execute ().get (
89+ 'values' )[1 :]
90+ # skip header row
9591
9692
9793# data source dispatch table [better alternative vs. eval()]
@@ -102,32 +98,41 @@ def _copy_template(tmpl_id, source, service):
10298 """(private) Copies letter template document using Drive API then
10399 returns file ID of (new) copy.
104100 """
105- body = {'name' : 'Merged form letter (%s)' % source }
106- return service .files ().copy (body = body , fileId = tmpl_id ,
107- fields = 'id' ).execute ().get ('id' )
101+ try :
102+ body = {'name' : 'Merged form letter (%s)' % source }
103+ return service .files ().copy (body = body , fileId = tmpl_id ,
104+ fields = 'id' ).execute ().get ('id' )
105+ except HttpError as error :
106+ print (f"An error occurred: { error } " )
107+ return error
108108
109109
110110def merge_template (tmpl_id , source , service ):
111111 """Copies template document and merges data into newly-minted copy then
112112 returns its file ID.
113113 """
114- # copy template and set context data struct for merging template values
115- copy_id = _copy_template (tmpl_id , source , service )
116- context = merge .iteritems () if hasattr ({}, 'iteritems' ) else merge .items ()
117-
118- # "search & replace" API requests for mail merge substitutions
119- reqs = [{'replaceAllText' : {
120- 'containsText' : {
121- 'text' : '{{%s}}' % key .upper (), # {{VARS}} are uppercase
122- 'matchCase' : True ,
123- },
124- 'replaceText' : value ,
125- }} for key , value in context ]
126-
127- # send requests to Docs API to do actual merge
128- DOCS .documents ().batchUpdate (body = {'requests' : reqs },
129- documentId = copy_id , fields = '' ).execute ()
130- return copy_id
114+ try :
115+ # copy template and set context data struct for merging template values
116+ copy_id = _copy_template (tmpl_id , source , service )
117+ context = merge .iteritems () if hasattr ({},
118+ 'iteritems' ) else merge .items ()
119+
120+ # "search & replace" API requests for mail merge substitutions
121+ reqs = [{'replaceAllText' : {
122+ 'containsText' : {
123+ 'text' : '{{%s}}' % key .upper (), # {{VARS}} are uppercase
124+ 'matchCase' : True ,
125+ },
126+ 'replaceText' : value ,
127+ }} for key , value in context ]
128+
129+ # send requests to Docs API to do actual merge
130+ DOCS .documents ().batchUpdate (body = {'requests' : reqs },
131+ documentId = copy_id , fields = '' ).execute ()
132+ return copy_id
133+ except HttpError as error :
134+ print (f"An error occurred: { error } " )
135+ return error
131136
132137
133138if __name__ == '__main__' :
0 commit comments