Validate and save this JSON. Ensure (and return) a unique amendment id

def add_new_amendment(self,
                          json_repr,
                          auth_info,
                          commit_msg=''):
        """Validate and save this JSON. Ensure (and return) a unique amendment id"""
        amendment = self._coerce_json_to_amendment(json_repr)
        if amendment is None:
            msg = "File failed to parse as JSON:\n{j}".format(j=json_repr)
            raise ValueError(msg)
        if not self._is_valid_amendment_json(amendment):
            msg = "JSON is not a valid amendment:\n{j}".format(j=json_repr)
            raise ValueError(msg)

        # Mint any needed ottids, update the document accordingly, and
        # prepare a response with
        #  - per-taxon mapping of tag to ottid
        #  - resulting id (or URL) to the stored amendment
        # To ensure synchronization of ottids and amendments, this should be an
        # atomic operation!

        # check for tags and confirm count of new ottids required (if provided)
        num_taxa_eligible_for_ids = 0
        for taxon in amendment.get("taxa"):
            # N.B. We don't require 'tag' in amendment validation; check for it now!
            if "tag" not in taxon:
                raise KeyError('Requested Taxon is missing "tag" property!')
            # allow for taxa that have already been assigned (use cases?)
            if "ott_id" not in taxon:
                num_taxa_eligible_for_ids += 1
        if 'new_ottids_required' in amendment:
            requested_ids = amendment['new_ottids_required']
            try:
                assert (requested_ids == num_taxa_eligible_for_ids)
            except:
                m = 'Number of OTT ids requested ({r}) does not match eligible taxa ({t})'
                m = m.format(r=requested_ids, t=num_taxa_eligible_for_ids)
                raise ValueError(m)

        # mint new ids and assign each to an eligible taxon
        with self._growing_shard._doc_counter_lock:
            # build a map of tags to ottids, to return to the caller
            tag_to_id = {}
            first_new_id = self._growing_shard.next_ott_id
            last_new_id = first_new_id + num_taxa_eligible_for_ids - 1
            if last_new_id < first_new_id:
                # This can happen if ther are no eligible taxa! In this case,
                # repeat and "burn" the next ottid (ie, it will be used to
                # identify this amendment, but it won't be assigned)
                last_new_id = first_new_id
            new_id = first_new_id
            for taxon in amendment.get("taxa"):
                if "ott_id" not in taxon:
                    taxon["ott_id"] = new_id
                    ttag = taxon["tag"]
                    tag_to_id[ttag] = new_id
                    new_id += 1
                    ptag = taxon.get("parent_tag")
                    if ptag is not None:
                        taxon["parent"] = tag_to_id[ptag]
            if num_taxa_eligible_for_ids > 0:
                try:
                    assert (new_id == (last_new_id + 1))
                except:
                    applied = last_new_id - first_new_id + 1
                    raise ValueError(
                        'Number of OTT ids requested ({r}) does not match ids actually applied ({a})'.format(
                            r=requested_ids, a=applied))

            # Build a proper amendment id, in the format '{subtype}-{first ottid}-{last-ottid}'
            amendment_subtype = 'additions'
            # TODO: Handle other subtypes (beyond additions) by examining JSON?
            amendment_id = "{s}-{f}-{l}".format(s=amendment_subtype, f=first_new_id, l=last_new_id)

            # Check the proposed id for uniqueness (just to be safe), then
            # "reserve" it using a placeholder value.
            with self._index_lock:
                if amendment_id in self._doc2shard_map:
                    # this should never happen!
                    raise KeyError('Amendment "{i}" already exists!'.format(i=amendment_id))
                self._doc2shard_map[amendment_id] = None

            # Set the amendment's top-level "id" property to match
            amendment["id"] = amendment_id

            # pass the id and amendment JSON to a proper git action
            new_amendment_id = None
            r = None
            try:
                # assign the new id to a shard (important prep for commit_and_try_merge2master)
                gd_id_pair = self.create_git_action_for_new_amendment(new_amendment_id=amendment_id)
                new_amendment_id = gd_id_pair[1]
                # For amendments, the id should not have changed!
                try:
                    assert new_amendment_id == amendment_id
                except:
                    raise KeyError('Amendment id unexpectedly changed from "{o}" to "{n}"!'.format(
                        o=amendment_id, n=new_amendment_id))
                try:
                    # it's already been validated, so keep it simple
                    r = self.commit_and_try_merge2master(file_content=amendment,
                                                         doc_id=new_amendment_id,
                                                         auth_info=auth_info,
                                                         parent_sha=None,
                                                         commit_msg=commit_msg,
                                                         merged_sha=None)
                except:
                    self._growing_shard.delete_doc_from_index(new_amendment_id)
                    raise

                # amendment is now in the repo, so we can safely reserve the ottids
                first_minted_id, last_minted_id = self._growing_shard._mint_new_ott_ids(
                    how_many=max(num_taxa_eligible_for_ids, 1))
                # do a final check for errors!
                try:
                    assert first_minted_id == first_new_id
                except:
                    raise ValueError('First minted ottid is "{m}", expected "{e}"!'.format(
                        m=first_minted_id, e=first_new_id))
                try:
                    assert last_minted_id == last_new_id
                except:
                    raise ValueError('Last minted ottid is "{m}", expected "{e}"!'.format(
                        m=last_minted_id, e=last_new_id))
                # Add the tag-to-ottid mapping to the response, so a caller
                # (e.g. the curation webapp) can provisionally assign them
                r['tag_to_ottid'] = tag_to_id
            except:
                with self._index_lock:
                    if new_amendment_id in self._doc2shard_map:
                        del self._doc2shard_map[new_amendment_id]
                raise

        with self._index_lock:
            self._doc2shard_map[new_amendment_id] = self._growing_shard
        return new_amendment_id, r