Improve handling of mis-writes

archive/original-v2.1
Mike Lang 6 years ago
parent dcb6d6ba6d
commit 1aa8f905f4

@ -2,12 +2,16 @@
which transparently handles re-connecting, sheets schemas and tracking rows by id. which transparently handles re-connecting, sheets schemas and tracking rows by id.
""" """
import random
import string
import gevent.lock import gevent.lock
from oauth2client.client import SignedJwtAssertionCredentials from oauth2client.client import SignedJwtAssertionCredentials
import gspread import gspread
from . import states
# schemas maps sheet name to schema. # schemas maps sheet name to schema.
# each schema contains a map from column names to column indexes (1-based) # each schema contains a map from column names to column indexes (1-based)
@ -148,8 +152,7 @@ class Sheet(object):
class Row(object): class Row(object):
"""Represents a row in a sheet. Values can be looked up by attribute. """Represents a row in a sheet. Values can be looked up by attribute.
Values can be updated with update(attr=value), which returns the updated row. Values can be updated with update(attr=value), which returns the updated row.
Note that a row must have an id to be updatable. Updating is permitted if no id is set If a row without an id is updated, an id will be randomly assigned.
only if id is one of the values being written.
You can also refresh the row (if it has an id) by calling row.refresh(), You can also refresh the row (if it has an id) by calling row.refresh(),
which returns the newly read row, or None if it can no longer be found. which returns the newly read row, or None if it can no longer be found.
""" """
@ -171,24 +174,38 @@ class Row(object):
return self.values[col] return self.values[col]
return "" return ""
def _raw_update(self, name, value):
col = self.schema[name]
self.sheet.worksheet.update_cell(self.index, col, value)
def update(self, **values): def update(self, **values):
with self.manager.lock: with self.manager.lock:
self.manager.refresh() self.manager.refresh()
# We attempt to detect races by: while True:
# Always refreshing our position before we begin (if we can) # We attempt to detect races by:
# Checking our position again afterwards. If it's changed, we probably mis-wrote. # Always refreshing our position before we begin (if we can)
if self.id: # Checking our position again afterwards. If it's changed, we probably mis-wrote.
before = self.refresh() if self.id:
# TODO handle before = None before = self.refresh()
else: if before is None:
before = self raise Exception("Cannot update row {}: Row is gone".format(self))
for name, value in values.items(): else:
col = self.schema[name] before = self
self.sheet.worksheet.update_cell(before.index, col, value) if 'id' not in values:
after = self.sheet.by_index(before.index) # auto-create id
new_id = values['id'] if 'id' in values else self.id values['id'] = ''.join(random.choice(string.letters + string.digits) for _ in range(12))
if after.id != new_id: for name, value in values.items():
raise Exception("Likely bad write: Row {} may have had row {} data partially written: {}".format(after, before, values)) before._raw_update(name, value)
after = self.sheet.by_index(before.index)
new_id = values['id'] if 'id' in values else self.id
if after.id != new_id:
logging.error("Likely bad write: Row {} may have had row {} data partially written: {}".format(after, before, values))
if hasattr(after, 'state'):
after._raw_update('state', states.ERROR)
if hasattr(after, 'notes'):
after._raw_update('notes', "This row may have had following data from row with id {} written to it: {}".format(new_id, values))
continue # retry
return
def refresh(self): def refresh(self):
return self.sheet[self.id] return self.sheet[self.id]

Loading…
Cancel
Save