(db, fn, ids, max_retries=3, verbose=False, chunksize=100)
return results
Map `fn` over every doc in `db` matching `ids`

    `fn` should accept a json dict from couch and return an instance of
    DocUpdate or None (which will skip the doc)

    iter_update returns an object with the following properties:
    'ignored_ids', 'not_found_ids', 'deleted_ids', 'updated_ids', 'error_ids'

    Ex: mark dimagi users as cool, delete the Canadians, and ignore the rest

        from corehq.util.couch import iter_update, DocUpdate

        def mark_cool(user_dict):
            user = CommCareUser.wrap(user_dict)
            if user.is_dimagi:
                user.is_cool = True
                return DocUpdate(doc=user)
            if user.language == "Canadian":
                return DocUpdate(doc=user, delete=True)

        iter_update(CommCareUser.get_db(), mark_cool, all_user_ids)

    This looks up and saves docs in chunks and is intended for large changes
    such as a migration.  If an id is not found, it is skipped.  In the
    event of a BulkSaveError, it will re-process the unsaved documents.
    Wrapping is optional, and this function will unwrap if needed.
Map `fn` over every doc in `db` matching `ids`
Map fn over every doc in db matching ids
def iter_update(db, fn, ids, max_retries=3, verbose=False, chunksize=100):
    """
    Map `fn` over every doc in `db` matching `ids`

    `fn` should accept a json dict from couch and return an instance of
    DocUpdate or None (which will skip the doc)

    iter_update returns an object with the following properties:
    'ignored_ids', 'not_found_ids', 'deleted_ids', 'updated_ids', 'error_ids'

    Ex: mark dimagi users as cool, delete the Canadians, and ignore the rest

        from corehq.util.couch import iter_update, DocUpdate

        def mark_cool(user_dict):
            user = CommCareUser.wrap(user_dict)
            if user.is_dimagi:
                user.is_cool = True
                return DocUpdate(doc=user)
            if user.language == "Canadian":
                return DocUpdate(doc=user, delete=True)

        iter_update(CommCareUser.get_db(), mark_cool, all_user_ids)

    This looks up and saves docs in chunks and is intended for large changes
    such as a migration.  If an id is not found, it is skipped.  In the
    event of a BulkSaveError, it will re-process the unsaved documents.
    Wrapping is optional, and this function will unwrap if needed.
    """
    fields = ['ignored_ids', 'not_found_ids', 'deleted_ids', 'updated_ids',
              'error_ids']
    IterResults = namedtuple('IterResults', fields)
    results = IterResults(set(), set(), set(), set(), set())

    def _get_updated_doc_id(doc_update):
        if isinstance(doc_update.doc, dict):
            updated_doc_id = doc_update.doc.get('_id')
        else:
            updated_doc_id = doc_update.doc._id
        return updated_doc_id

    def _iter_update(doc_ids, try_num):
        with IterDB(db, chunksize=chunksize) as iter_db:
            for chunk in chunked(doc_ids, chunksize):
                for res in send_keys_to_couch(db, keys=chunk):
                    raw_doc = res.get('doc')
                    doc_id = res.get('id', None)
                    if not raw_doc or not doc_id:
                        results.not_found_ids.add(res['key'])
                    else:
                        # copy the dictionary so we can tell if it changed
                        doc_update = fn(deepcopy(raw_doc))
                        if doc_update is None:
                            results.ignored_ids.add(doc_id)
                        elif (not isinstance(doc_update, DocUpdate)
                              or _get_updated_doc_id(doc_update) != doc_id):
                            results.error_ids.add(doc_id)
                        elif doc_update.delete:
                            iter_db.delete(raw_doc)
                        elif not _is_unchanged(doc_update.doc, raw_doc):
                            iter_db.save(doc_update.doc)
                        else:
                            results.ignored_ids.add(doc_id)

        results.updated_ids.update(iter_db.saved_ids)
        results.deleted_ids.update(iter_db.deleted_ids)

        if iter_db.error_ids:
            if try_num >= max_retries:
                results.error_ids.update(iter_db.error_ids)
                msg = ("The following documents did not correctly save:\n" +
                       ", ".join(results.error_ids))
                raise IterUpdateError(results, msg)
            else:
                _iter_update(iter_db.error_ids, try_num + 1)

    _iter_update(ids, 0)
    if results.error_ids:
        msg = ("The following docs didn't correctly save.  Are you sure fn {} "
               "returned either None or an instance of DocUpdate?  Did you "
               "change or remove the '_id' field?".format(fn.__name__) +
               ", ".join(results.error_ids))
        raise IterUpdateError(results, msg)

    if verbose:
        print("couldn't find {} docs".format(len(results.not_found_ids)))
        print("ignored {} docs".format(len(results.ignored_ids)))
        print("deleted {} docs".format(len(results.deleted_ids)))
        print("updated {} docs".format(len(results.updated_ids)))
    return results
def iter_update ( db , fn , ids , max_retries = 3 , verbose = False , chunksize = 100 ) : fields = [ 'ignored_ids' , 'not_found_ids' , 'deleted_ids' , 'updated_ids' , 'error_ids' ] IterResults = namedtuple ( 'IterResults' , fields ) results = IterResults ( set ( ) , set ( ) , set ( ) , set ( ) , set ( ) ) def _get_updated_doc_id ( doc_update ) : if isinstance ( doc_update . doc , dict ) : updated_doc_id = doc_update . doc . get ( '_id' ) else : updated_doc_id = doc_update . doc . _id return updated_doc_id def _iter_update ( doc_ids , try_num ) : with IterDB ( db , chunksize = chunksize ) as iter_db : for chunk in chunked ( doc_ids , chunksize ) : for res in send_keys_to_couch ( db , keys = chunk ) : raw_doc = res . get ( 'doc' ) doc_id = res . get ( 'id' , None ) if not raw_doc or not doc_id : results . not_found_ids . add ( res [ 'key' ] ) else : # copy the dictionary so we can tell if it changed doc_update = fn ( deepcopy ( raw_doc ) ) if doc_update is None : results . ignored_ids . add ( doc_id ) elif ( not isinstance ( doc_update , DocUpdate ) or _get_updated_doc_id ( doc_update ) != doc_id ) : results . error_ids . add ( doc_id ) elif doc_update . delete : iter_db . delete ( raw_doc ) elif not _is_unchanged ( doc_update . doc , raw_doc ) : iter_db . save ( doc_update . doc ) else : results . ignored_ids . add ( doc_id ) results . updated_ids . update ( iter_db . saved_ids ) results . deleted_ids . update ( iter_db . deleted_ids ) if iter_db . error_ids : if try_num >= max_retries : results . error_ids . update ( iter_db . error_ids ) msg = ( "The following documents did not correctly save:\n" + ", " . join ( results . error_ids ) ) raise IterUpdateError ( results , msg ) else : _iter_update ( iter_db . error_ids , try_num + 1 ) _iter_update ( ids , 0 ) if results . error_ids : msg = ( "The following docs didn't correctly save.  Are you sure fn {} " "returned either None or an instance of DocUpdate?  Did you " "change or remove the '_id' field?" . format ( fn . __name__ ) + ", " . join ( results . error_ids ) ) raise IterUpdateError ( results , msg ) if verbose : print ( "couldn't find {} docs" . format ( len ( results . not_found_ids ) ) ) print ( "ignored {} docs" . format ( len ( results . ignored_ids ) ) ) print ( "deleted {} docs" . format ( len ( results . deleted_ids ) ) ) print ( "updated {} docs" . format ( len ( results . updated_ids ) ) ) return results