Last active
March 3, 2018 17:14
-
-
Save remcoboerma/a8b999239565d124a46c08ab63784d94 to your computer and use it in GitHub Desktop.
updater.py code for https://hashnode.com/post/crafting-tools-update-in-db-documents-cjeaouljk086tiqwupltpitvj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from __future__ import print_function | |
from collections import namedtuple | |
# don't use the semver library, it doesn't work well with compare, sort and those simple things. | |
# the parse_version result from setuptools does a lot better job. | |
# found this gem at https://stackoverflow.com/a/21065570/3563630 | |
# Seems to be https://www.python.org/dev/peps/pep-0440/ compliant | |
from pkg_resources import parse_version | |
FixInfo = namedtuple('FixInfo', 'seq,fn') | |
class StopUpgradingException(Exception): | |
'''StopUpgradingException is thrown to notify fix_record an error has occurred in a decorated fix method. ''' | |
def __init__(self, record, fn, exception): | |
super(StopUpgradingException, self).__init__(self) | |
self.record = record | |
self.fn = fn | |
self.exception = exception | |
class Upgrader(object): | |
def __init__(self): | |
self.fixes = [] | |
def greater_version(self,a,b): | |
return a > b | |
def update_record_version(self, record, version): | |
'''Updates record with the new version.''' | |
record['version'] = version | |
def version_getter(self, record): | |
'''Get the version for a given record. Should be comparable to sequence. ''' | |
return record.get('version', -1) | |
def commit(self, record, final=False): | |
'''For subclasses, overwrite to handle altered records if they need special treatment. | |
Final is true only once per distinct record after all fixes have been applied. Commit will also be called | |
after each successful upgrade with final=False. | |
''' | |
pass | |
def abort(self, record, stop_exception): | |
'''For subclasses, abort is issued when the fix raised an exception. | |
investigate stop_exception.fn, .record and .exception for more details | |
''' | |
print('Aborting upgrade of ', record, ' using ', stop_exception.fn, ' because of ', stop_exception.exception) | |
def register_fix(self, sequence): | |
'''Register a fix function. | |
Decorate a fixer with @register_fix(sequence). All decorated functions are registered so when upgrading a record | |
all applicable fixes will be applied. In the fixing process, if no Exception is raised, self.commit(record) is | |
issued. This is ussued on every fix per record. In case of an error, self.abort() | |
''' | |
# create the decorator | |
def deco(fn): | |
# this is the patch function, which will wrap the real fix. | |
def patch(record, *p, **kw): | |
'''Execute fn on record. If succesfull: upgrade the record version and commit, raise StopUpgradingException otherwise. ''' | |
try: | |
# try the function | |
fn(record, *p, **kw) | |
except Exception as e: | |
# if anything went wrong, signal fix_record to cancel the upgrade process for this record | |
raise StopUpgradingException(record, fn, e) | |
else: | |
# if all went well, upgrade the version and commit this record | |
self.update_record_version(record, sequence) | |
self.commit(record, final=False) | |
# the following is only run once per registration of a new function. | |
# check if the list of fixes is still in order | |
sort_order_is_okay = self.fixes and self.greater_version(sequence,self.fixes[-1].seq) | |
# append this new fix | |
self.fixes.append(FixInfo(fn=patch, seq=sequence)) | |
if not sort_order_is_okay: | |
# reorder is so required | |
self.fixes.sort(key=lambda rec: rec.seq) | |
return patch | |
return deco | |
def fix_record(self, record, *p, **kw): | |
'''Fix a given record by applying all fixes registered for higher version in sequential order. | |
calls self.abort on failure. | |
*p and **kw are passed as is onto the fixes ''' | |
try: | |
dirty = False | |
for fix in self.fixes: | |
# find the fix whose version matches a newer version than that of the record | |
if self.greater_version(fix.seq,self.version_getter(record)): | |
# mark a possible commit later | |
dirty = True | |
print('>>>> Dirty is true!!', fix.seq, self.version_getter(record)) | |
# apply the fix | |
fix.fn(record, *p, **kw) | |
except StopUpgradingException as e: | |
# hang on, something went wrong? | |
# let's notify Updater to do whatever necessary to handle this error | |
self.abort(record, e) | |
else: | |
# if all went well | |
if dirty: | |
# and we have changed the record then commit for the last time | |
# signalling commit that all in-between commits for this record have been committed. | |
self.commit(record, final=True) | |
class SemverUpgrader(Upgrader): | |
def register_fix(self, sequence): | |
return super(SemverUpgrader, self).register_fix(parse_version(sequence)) | |
def update_record_version(self, record, version): | |
super(SemverUpgrader, self).update_record_version(record, str(version)) | |
def version_getter(self, record): | |
version = super(SemverUpgrader, self).version_getter(record) | |
version = '0.0.0' if version == -1 else version | |
return parse_version(version) | |
if __name__=='__main__': | |
import pprint | |
from upgrader import Upgrader, SemverUpgrader | |
upgrader = Upgrader() | |
@upgrader.register_fix(1) | |
def fix1(record, **kwp): | |
record['load'] += 1 | |
@upgrader.register_fix(2) | |
def fix2(record, bla='bla'): | |
print(record, 1, bla) | |
0 / 0 | |
print(record, 2) | |
record['load'] += 10 | |
@upgrader.register_fix(4) | |
def fix4(record, **kwp): | |
print(record, 4) | |
record['load'] += 1000 | |
@upgrader.register_fix(3) | |
def fix3(record, **kwp): | |
print(record, 3) | |
record['load'] += 100 | |
records = [dict(version=v, load=0) for v in (1, 2, 3, 4, 5)] | |
for r in records: | |
print('\nRecord: ', r) | |
upgrader.fix_record(r, bla='BLA!!') | |
pprint.pprint(records) | |
print('\n\n--- SEMVER ---') | |
# semver upgrader | |
svu = SemverUpgrader() | |
@svu.register_fix('1.0.0') | |
def u100alfa(record): | |
print(record, '1.0.0') | |
@svu.register_fix('1.0.99') | |
def u100beta(record): | |
print(record, '1.0.99') | |
@svu.register_fix('1.0.100') | |
def u100beta(record): | |
print(record, '1.0.100') | |
@svu.register_fix('1.0.100post1') | |
def u100beta(record): | |
print(record, '1.0.100post1') | |
@svu.register_fix('1.1.0') | |
def u100(record): | |
print(record, '1.1.0') | |
@svu.register_fix('0.1.0') | |
def u010(record): | |
print(record, '0.1.0') | |
records = [dict(version=v, load=0) for v in ('1.0.0', '1.0.1')] | |
for r in records: | |
print('\nRecord: ', r) | |
svu.fix_record(r) | |
pprint.pprint(svu.fixes) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment