summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/gdata_lib.py24
-rw-r--r--scripts/sync_package_status.py38
-rw-r--r--scripts/sync_package_status_unittest.py1
3 files changed, 58 insertions, 5 deletions
diff --git a/lib/gdata_lib.py b/lib/gdata_lib.py
index 8f71b7630..77452a79f 100644
--- a/lib/gdata_lib.py
+++ b/lib/gdata_lib.py
@@ -16,6 +16,7 @@ import xml.dom.minidom
# pylint: disable=W0404
import gdata.projecthosting.client
+import gdata.service
import gdata.spreadsheet.service
from chromite.lib import operation
@@ -361,6 +362,20 @@ class SpreadsheetRow(dict):
raise TypeError('deleting item in SpreadsheetRow not supported')
+class SpreadsheetError(RuntimeError):
+ """Base class for spreadsheet communication errors."""
+
+def ReadWriteDecorator(func):
+ """Raise SpreadsheetWriteError if appropriate."""
+ def f(self, *args, **kwargs):
+ try:
+ return func(self, *args, **kwargs)
+ except gdata.service.RequestError as ex:
+ raise SpreadsheetError(str(ex))
+
+ f.__name__ = func.__name__
+ return f
+
class SpreadsheetComm(object):
"""Class to manage communication with one Google Spreadsheet worksheet."""
@@ -487,6 +502,7 @@ class SpreadsheetComm(object):
oper.Die('Unable to find worksheet "%s" in spreadsheet "%s"' %
(ws_name, ss_key))
+ @ReadWriteDecorator
def GetColumns(self):
"""Return tuple of column names in worksheet.
@@ -494,6 +510,7 @@ class SpreadsheetComm(object):
"""
return self.columns
+ @ReadWriteDecorator
def GetColumnIndex(self, colName):
"""Get the column index (starting at 1) for column |colName|"""
try:
@@ -502,10 +519,12 @@ class SpreadsheetComm(object):
except ValueError:
return None
+ @ReadWriteDecorator
def GetRows(self):
"""Return tuple of SpreadsheetRow objects in order."""
return self.rows
+ @ReadWriteDecorator
def GetRowCacheByCol(self, column):
"""Return a dict for looking up rows by value in |column|.
@@ -530,11 +549,13 @@ class SpreadsheetComm(object):
return row_cache
+ @ReadWriteDecorator
def InsertRow(self, row):
"""Insert |row| at end of spreadsheet."""
self.gd_client.InsertRow(row, self.ss_key, self.ws_key)
self._ClearCache(keep_columns=True)
+ @ReadWriteDecorator
def UpdateRowCellByCell(self, rowIx, row):
"""Replace cell values in row at |rowIx| with those in |row| dict."""
for colName in row:
@@ -543,16 +564,19 @@ class SpreadsheetComm(object):
self.ReplaceCellValue(rowIx, colIx, row[colName])
self._ClearCache(keep_columns=True)
+ @ReadWriteDecorator
def DeleteRow(self, ss_row):
"""Delete the given |ss_row| (must be original spreadsheet row object."""
self.gd_client.DeleteRow(ss_row)
self._ClearCache(keep_columns=True)
+ @ReadWriteDecorator
def ReplaceCellValue(self, rowIx, colIx, val):
"""Replace cell value at |rowIx| and |colIx| with |val|"""
self.gd_client.UpdateCell(rowIx, colIx, val, self.ss_key, self.ws_key)
self._ClearCache(keep_columns=True)
+ @ReadWriteDecorator
def ClearCellValue(self, rowIx, colIx):
"""Clear cell value at |rowIx| and |colIx|"""
self.ReplaceCellValue(rowIx, colIx, None)
diff --git a/scripts/sync_package_status.py b/scripts/sync_package_status.py
index 8033c1a68..539240f37 100644
--- a/scripts/sync_package_status.py
+++ b/scripts/sync_package_status.py
@@ -293,14 +293,35 @@ class Syncer(object):
if not self.pretend:
oper.Info('Creating Tracker issue for package %s with details:\n%s' %
(pkg, issue))
+
+ # Before actually creating the Tracker issue, confirm that writing
+ # to this spreadsheet row is going to work.
+ try:
+ self.scomm.ClearCellValue(rowIx, self.tracker_col_ix)
+ except gdata_lib.SpreadsheetError as ex:
+ oper.Error('Unable to write to row %d, package %r. Aborting issue'
+ ' creation. Error was:\n%s' % (rowIx, pkg, ex))
+ raise SyncError
+
issue_id = self.tcomm.CreateTrackerIssue(issue)
oper.Info('Inserting new Tracker issue %d for package %s' %
(issue_id, pkg))
ss_issue_val = self._GenSSLinkToIssue(issue_id)
- self.scomm.ReplaceCellValue(rowIx, self.tracker_col_ix, ss_issue_val)
- oper.Notice('Created Tracker issue %d for row %d, package %r' %
- (issue_id, rowIx, pkg))
+ # This really should not fail since write access was checked before.
+ try:
+ self.scomm.ReplaceCellValue(rowIx, self.tracker_col_ix, ss_issue_val)
+ oper.Notice('Created Tracker issue %d for row %d, package %r' %
+ (issue_id, rowIx, pkg))
+ except gdata_lib.SpreadsheetError as ex:
+ oper.Error('Failed to write link to new issue %d into'
+ ' row %d, package %r:\n%s' %
+ (issue_id, rowIx, pkg, ex))
+ oper.Error('This means that the spreadsheet will have no record of'
+ ' this Tracker Issue and will create one again next time'
+ ' unless the spreadsheet is edited by hand!')
+ raise SyncError
+
else:
oper.Notice('Would create and insert issue for row %d, package %r' %
(rowIx, pkg))
@@ -315,8 +336,15 @@ class Syncer(object):
pkg = row[COL_PACKAGE]
if not self.pretend:
- oper.Notice('Clearing Tracker issue for package %s' % pkg)
- self.scomm.ClearCellValue(rowIx, self.tracker_col_ix)
+ try:
+ self.scomm.ClearCellValue(rowIx, self.tracker_col_ix)
+ oper.Notice('Cleared Tracker issue from row %d, package %r' %
+ (rowIx, pkg))
+ except gdata_lib.SpreadsheetError as ex:
+ oper.Error('Error while clearing Tracker issue for'
+ ' row %d, package %r:\n%s' % (rowIx, pkg, ex))
+ raise SyncError
+
else:
oper.Notice('Would clear Tracker issue from row %d, package %r' %
(rowIx, pkg))
diff --git a/scripts/sync_package_status_unittest.py b/scripts/sync_package_status_unittest.py
index 5f14b3487..5e3ab2091 100644
--- a/scripts/sync_package_status_unittest.py
+++ b/scripts/sync_package_status_unittest.py
@@ -467,6 +467,7 @@ class SyncerTest(test_lib.MoxTestCase):
ss_issue_val = 'Hyperlink%d' % issue_id
# Replay script
+ mocked_scomm.ClearCellValue(row_ix, mocked_syncer.tracker_col_ix)
mocked_tcomm.CreateTrackerIssue(issue).AndReturn(issue_id)
mocked_syncer._GenSSLinkToIssue(issue_id).AndReturn(ss_issue_val)
mocked_scomm.ReplaceCellValue(row_ix, mocked_syncer.tracker_col_ix,