[python]python-oauth2使用時にカンマ区切りのクエリでTwitter APIに失敗する件

Twitter APIのお話。
sitestream api, user look apiはユーザIDのリストをカンマ区切りで渡すが
urllib.urlencodeちゃんがカンマを%2Cに変換してくれちゃってBad request
が返ってくる。
urlencodeのカンマ変換なし版を作った。差分はquote_plus()にsafe=','を追
加しただけ。
oauthプロトコルのハンドル用に使ってるoauth2モジュールの
Request.to_url()も作ったurlencode()を使うようにちびっと変更。

#encoding=utf-8

'''
inet.py
'''

import sys
import urllib
from urllib import quote_plus, _is_unicode

import urlparse
from urlparse import parse_qs

import oauth2 as oauth


def urlencode(query, doseq=0):
    """Encode a sequence of two-element tuples or dictionary into a URL query string.

    If any values in the query arg are sequences and doseq is true, each
    sequence element is converted to a separate parameter.

    If the query arg is a sequence of two-element tuples, the order of the
    parameters in the output will match the order of parameters in the
    input.
    """

    if hasattr(query,"items"):
        # mapping objects
        query = query.items()
    else:
        # it's a bother at times that strings and string-like objects are
        # sequences...
        try:
            # non-sequence items should not work with len()
            # non-empty strings will fail this
            if len(query) and not isinstance(query[0], tuple):
                raise TypeError
            # zero-length sequences of all types will get here and succeed,
            # but that's a minor nit - since the original implementation
            # allowed empty dicts that type of behavior probably should be
            # preserved for consistency
        except TypeError:
            ty,va,tb = sys.exc_info()
            raise TypeError, "not a valid non-string sequence or mapping object", tb

    l = []
    if not doseq:
        # preserve old behavior
        for k, v in query:
            k = quote_plus(str(k), safe=',')
            v = quote_plus(str(v), safe=',')
            l.append(k + '=' + v)
    else:
        for k, v in query:
            k = quote_plus(str(k), safe=',')
            if isinstance(v, str):
                v = quote_plus(v, safe=',')
                l.append(k + '=' + v)
            elif _is_unicode(v):
                # is there a reasonable way to convert to ASCII?
                # encode generates a string, but "replace" or "ignore"
                # lose information and "strict" can raise UnicodeError
                v = quote_plus(v.encode("ASCII","replace"), safe=',')
                l.append(k + '=' + v)
            else:
                try:
                    # is this a sufficient test for sequence-ness?
                    len(v)
                except TypeError:
                    # not a sequence
                    v = quote_plus(str(v), safe=',')
                    l.append(k + '=' + v)
                else:
                    # loop over the sequence
                    for elt in v:
                        l.append(k + '=' + quote_plus(str(elt), safe=','))
    return '&'.join(l)



class EscapedRequest(oauth.Request):
	def __init__(self, *args, **kwargs):
		oauth.Request.__init__(self, *args, **kwargs)

	@classmethod
	def from_consumer_and_token(cls, consumer, token=None,
			http_method=oauth.HTTP_METHOD, http_url=None, parameters=None,
			body='', is_form_encoded=False):
		if not parameters:
			parameters = {}

		defaults = {
		    'oauth_consumer_key': consumer.key,
		    'oauth_timestamp': cls.make_timestamp(),
		    'oauth_nonce': cls.make_nonce(),
		    'oauth_version': cls.version,
		}

		defaults.update(parameters)
		parameters = defaults

		if token:
			parameters['oauth_token'] = token.key
			if token.verifier:
				parameters['oauth_verifier'] = token.verifier

		return EscapedRequest(http_method, http_url, parameters, body=body,
		               is_form_encoded=is_form_encoded)


	def to_url(self):
		#Serialize as a URL for a GET request
		base_url = urlparse.urlparse(self.url)
		query = base_url.query
		query = parse_qs(query)
		for k, v in self.items():
			# Decode comma back to ascii
			if '%2C' in v:
				v = v.replace('%2C', ',')
			query.setdefault(k, []).append(v)

		scheme = base_url.scheme
		netloc = base_url.netloc
		path = base_url.path
		params = base_url.params
		fragment = base_url.fragment

		url = (scheme, netloc, path, params,
		       urlencode(query, True), fragment)
		return urlparse.urlunparse(url)