[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)