[python]python-oauth2使用時にカンマ区切りのクエリでTwitter APIに失敗する件
Twitter APIのお話。
sitestream api, user look apiはユーザIDのリストをカンマ区切りで渡すが
oauthモジュールだと%2C変換かかって失敗する。
%2Cを,にdecode backしたら上手くいったね!
#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)
[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)
c拡張でPyUnicodeObject, PyStringObjectの相互変換を楽にする II
前回マクロ化した文字列変換処理にバグがあった。
Py関数でオブジェクトを生成すると、内部的に参照カウントがインクリメントされる。
使わなくなったオブジェクトへの参照カウントは明示的にデクリメントしなくてはならない。
Pythonオブジェクトは参照カウントが0になったときに解放されるので、忘れるとメモリリークします。
malloc()したのにfree()忘れたようなイメージ。
前回作成したマクロは参照カウントを無視した内容になっていた。
/* * PyUnicode_AsEncodedString はPyUnicodeObjectへの参照を返す。 * 同時に、返したPyUnicodeObjectの参照カウントが +1 される。 * このマクロでは +1 した参照カウントを -1 してないのでメモリ上に残ったままになる。 */ #define UNI_STR(item) PyString_AsString(PyUnicode_AsEncodedString(item, ENCODING_CHARSET, ENCODING_ONERROR))
以下、参照カウントを明示的に-1するよう修正したコード。
#include <Python.h> #include "common.h" /* * FUNCTION: * uni_str * * ARGS: * unicode pythonユニコード文字列 * * RETURN: * ON SUCCESS: PyStrinbObject * * ON_ERROR: NULL * * DESCRIPTION: * PyUnicodeObjectをPyStringObjectに変換する。 * Encode先のcharsetはcommon.h, ENCODING_CHARSETにて切り替える。 * */ char *uni_str(PyObject *unicode) { PyObject *encoded_str; char *ret; encoded_str = PyUnicode_AsEncodedString(unicode, ENCODING_CHARSET, ENCODING_ONERROR); if (encoded_str == NULL) return NULL; ret = PyString_AsString(encoded_str); Py_DECREF(encoded_str); if (ret == NULL) return NULL; return ret; } /* * FUNCTION: * uni_cat * * ARGS: * unicode pythonユニコード文字列にconst char *をappend * * RETURN: * ON SUCCESS: PyUnicodeObject* * ON_ERROR: NULL * * DESCRIPTION: * PyUnicodeObjectに文字列連結。 * */ PyObject *uni_cat(PyObject *unicode, const char *str) { PyObject *ret; PyObject *decoded_uni; decoded_uni = PyUnicode_Decode(str, strlen(str), ENCODING_CHARSET, ENCODING_ONERROR); if (decoded_uni == NULL) return NULL; ret = PyUnicode_Concat(unicode, decoded_uni); if (ret == NULL) return NULL; Py_DECREF(decoded_uni); return ret; } /* * FUNCTION: * str_uni * * ARGS: * str c言語の文字列表現 * * RETURN: * ON SUCCESS: PyUnicodeObject* * ON_ERROR: NULL * * DESCRIPTION: * c言語の文字列表現をPyUnicodeObjectにデコードする。 * */ PyObject *str_uni(const char *str) { PyObject *ret; ret = PyUnicode_Decode(str, strlen(str), ENCODING_CHARSET, ENCODING_ONERROR); if (ret == NULL) return NULL; return ret; }
c拡張でPyUnicodeObject, PyStringObjectの相互変換を楽にする
'ほげほげ'.decode('utf-8', 'ignore') u'ほげほげ'.encode('utf-8', 'ignore')
内部的にはunicodeはPyUnicodeObject, stringはPyStringObjectという構造体で管理してらっしゃる。この2つをCで楽に相互変換したい。ちなみに、デコードはこんな感じになる。
char *str = "ほげほげですよ"; PyUnicode_Decode(str, strlen(str), "utf-8", "ignore");
長いw マクロ化しちゃいます。
#ifndef __COMMON_H__ #define __COMMON_H_ #define DEBUG #ifdef DEBUG #define LOG printf #else #define LOG #endif #define ENCODING_CHARSET "cp932" #define ENCODING_ONERROR "ignore" /* * PyUnicodeObject → (char *) */ #define UNI_STR(item) PyString_AsString(PyUnicode_AsEncodedString(item, ENCODING_CHARSET, ENCODING_ONERROR)) /* * (char *) → PyUnicodeObject */ #define STR_UNI(str) PyUnicode_Decode(str, strlen(str), ENCODING_CHARSET, ENCODING_ONERROR) /* * PyUnicodeObject + (char *) */ #define UNI_CAT(item, str) PyUnicode_Concat(item, \ STR_UNI(str)) #endif
c拡張でのunicode, strの扱い
ユニコードを受け取ってutf-8文字列に変換する。これだけでも難しかった…
#include <Python.h> /* モジュールの関数 */ static PyObject *hello(PyObject* self, PyObject *args, PyObject *kwds) { PyObject *first = NULL; PyObject *encoded = NULL; // PyObjectとして引数を取得する。 if (!PyArg_ParseTuple(args, "O", &first)) { return NULL; } if (PyString_Check(first)) { printf("first=%s\n", PyString_AsString(first)); } else if(PyUnicode_Check(first)) { encoded = PyUnicode_AsUTF8String(first); printf("first=%s, slength=%d, ulength=%d\n", PyString_AsString(encoded), PyString_Size(encoded), PyUnicode_GetSize(first)); } else { return NULL; } Py_RETURN_NONE; } /* モジュールのメソッドテーブル */ static PyMethodDef methods[] = { {"hello", (PyCFunction)hello, METH_VARARGS | METH_KEYWORDS, "print hello world.\n"}, {NULL, NULL, 0, NULL} }; /* モジュールの初期化関数 */ PyMODINIT_FUNC initpypysample(void) { (void)Py_InitModule("pypysample", methods); }
Python C APiで拡張 on windows
cソースはmingw使ってビルド。
#include <Python.h> /* モジュールの関数 */ static PyObject *hello(PyObject* self, PyObject *args) { printf("Hello World!!\n"); Py_RETURN_NONE; } /* モジュールのメソッドテーブル */ static PyMethodDef methods[] = { {"hello", (PyCFunction)hello, METH_VARARGS, "print hello world.\n"}, {NULL, NULL, 0, NULL} }; /* モジュールの初期化関数 */ PyMODINIT_FUNC initsample(void) { (void)Py_InitModule("sample", methods); }
ビルドコマンド
gcc -o ext_sample.o -c ext_sample.c -I"C:\python27\include" gcc -shared ext_sample.o -o ext_sample.pyd -L"C:\python27\libs" -lpython27
注意点
- initsample()だた、命名規則として「init<モジュール名>」とする必要がある。
matplotlibで日本語
日本語を扱う場合はfontファイルのパスを直値で設定するしかないのか?今のところそれ以外の解決方法が見つけられていないがメモ。
import matplotlib.pyplot as plt import matplotlib.font_manager as mng # MS ゴシック fProp = mng.FontProperties(fname=u'C:\\Windows\\Fonts\\msgothic.ttc') axis = plt.subplot(111) axis.bar(x, seqList) # グラフタイトルに日本語 plt.title(u'ぴったり10GB 11月', fontproperties=fProp)