[History] add EpisodeHistoryEntry.create_entry()
[mygpo.git] / mygpo / db / couchdb / __init__.py
blob3a9bffb829d319c765c4fb6516c0c4c7341593bc
1 from operator import itemgetter
2 from collections import namedtuple
4 from couchdbkit.ext.django import loading
5 from couchdbkit import MultipleResultsFound
6 from couchdbkit import *
8 import logging
9 logger = logging.getLogger(__name__)
12 def get_userdata_database():
13 return loading.get_db('userdata')
16 class BulkException(Exception):
18 def __init__(self, errors):
19 self.errors = errors
22 BulkError = namedtuple('BulkError', 'doc error reason')
25 def __default_reload(db, obj):
26 doc = db[obj._id]
27 return obj.__class__.wrap(doc)
30 __get_obj = itemgetter(0)
32 def bulk_save_retry(obj_funs, db, reload_f=__default_reload):
33 """ Saves multiple documents and retries failed ones
35 Objects to be saved are passed as (obj, mod_f), where obj is the CouchDB
36 and mod_f is the modification function that should be applied to it.
38 If saving a document fails, it is again fetched from the database, the
39 modification function is applied again and saving is retried. """
41 errors = []
43 while True:
45 # apply modification function (and keep funs)
46 obj_funs = [(f(o), f) for (o, f) in obj_funs]
48 # filter those with obj None
49 obj_funs = filter(lambda of: __get_obj(of) is not None, obj_funs)
51 # extract objects
52 objs = map(__get_obj, obj_funs)
54 if not objs:
55 return
57 try:
58 db.save_docs(objs)
59 return
61 except BulkSaveError as ex:
63 new_obj_funs = []
64 for res, (obj, f) in zip(ex.results, obj_funs):
65 if res.get('error', False) == 'conflict':
67 # reload conflicted object
68 obj = reload_f(db, obj)
69 new_obj_funs.append( (obj, f) )
71 elif res.get('error', False):
72 # don't retry other errors
73 err = BulkError(obj, res['error'], res.get('reason', None))
74 errors.append(err)
76 obj_funs = new_obj_funs
78 if errors:
79 logger.warn('Errors at bulk-save: %s', errors)
80 raise BulkException(errors)
83 def get_single_result(db, view, **query_args):
84 """ return a single CouchDB view result
86 Logs an error if multiple results are returned, and uses the first result.
87 This can happen as CouchDB can not guarantee uniqueness of attributes other
88 than _id. If no result are fetched, None is returned. """
90 r = db.view(view, **query_args)
92 if not r:
93 return None
95 try:
96 result = r.one()
98 except MultipleResultsFound as ex:
99 logger.info('Multiple results found in %s with params %s',
100 view, query_args)
101 # use the first result as fallback
102 result = r.first()
104 # we can only set the db if the result has been
105 # wrapped (depending on query_args)
106 if hasattr(result, 'set_db'):
107 result.set_db(db)
109 return result