Maps from one ID scheme to another using the UniProt service.
        list_of_ids should be a list of strings.
        This function was adapted from http://www.uniprot.org/faq/28#id_mapping_examples which also gives examples of
        valid values for from_scheme and to_scheme.
        Note that some conversions are not directly possible e.g. PDB_ID (PDB) to UPARC (UniParc). They need to go through
        an intermediary format like ACC (UniProtKB AC) or ID (UniProtKB ID).
        This function returns a dict mapping the IDs in from_scheme to a list of sorted IDs in to_scheme.

def uniprot_map(from_scheme, to_scheme, list_of_from_ids, cache_dir = None, silent = True):
    '''Maps from one ID scheme to another using the UniProt service.
        list_of_ids should be a list of strings.
        This function was adapted from http://www.uniprot.org/faq/28#id_mapping_examples which also gives examples of
        valid values for from_scheme and to_scheme.
        Note that some conversions are not directly possible e.g. PDB_ID (PDB) to UPARC (UniParc). They need to go through
        an intermediary format like ACC (UniProtKB AC) or ID (UniProtKB ID).
        This function returns a dict mapping the IDs in from_scheme to a list of sorted IDs in to_scheme.
    '''
    try:
        assert(hasattr(list_of_from_ids, '__iter__'))
    except:
        raise Exception('The list_of_from_ids argument should be an iterable type (e.g. list).')
    full_mapping = {}
    cached_mapping_file = None
    if cache_dir:
        cached_mapping_file = os.path.join(cache_dir, '%s.%s' % (from_scheme, to_scheme))
        if os.path.exists(cached_mapping_file):
            full_mapping = simplejson.loads(read_file(cached_mapping_file))
    list_of_from_ids = set(list_of_from_ids)

    requested_mapping = {}
    remaining_ids = []
    for id in list_of_from_ids:
        if full_mapping.get(id):
            requested_mapping[id] = full_mapping[id]
        else:
            remaining_ids.append(id)
    assert(set(remaining_ids + requested_mapping.keys()) == set(list_of_from_ids))

    if remaining_ids:
        if not silent:
            print("Getting %s->%s mapping" % (from_scheme, to_scheme))
        url = 'http://www.uniprot.org/mapping/'
        params = {
            'from'      : from_scheme,
            'to'        : to_scheme,
            'format'    : 'tab',
            'query'     : ' '.join(sorted(list(list_of_from_ids))),
        }

        data = urllib.urlencode(params)
        request = urllib2.Request(url, data)
        contact = "" # Please set your email address here to help us debug in case of problems.
        request.add_header('User-Agent', 'Python %s' % contact)
        response = urllib2.urlopen(request)
        page = response.read(200000)
        lines = page.split("\n")

        assert(lines[-1] == '')
        assert(lines[0].split("\t") == ['From', 'To'])
        for line in lines[1:-1]:
            tokens = line.split("\t")
            assert(len(tokens) == 2)
            assert(tokens[0] in list_of_from_ids)
            full_mapping[tokens[0]] = full_mapping.get(tokens[0], [])
            full_mapping[tokens[0]].append(tokens[1])
            requested_mapping[tokens[0]] = requested_mapping.get(tokens[0], [])
            requested_mapping[tokens[0]].append(tokens[1])

    # Sort the IDs
    for k, v in requested_mapping.iteritems():
        #assert(len(v) == len(set(v)))
        requested_mapping[k] = sorted(set(v))
    for k, v in full_mapping.iteritems():
        #assert(len(v) == len(set(v)))
        full_mapping[k] = sorted(set(v))

    if remaining_ids and cached_mapping_file:
        write_file(cached_mapping_file, simplejson.dumps(full_mapping))
    return requested_mapping