├── .gitignore ├── .travis.yml ├── HISTORY.rst ├── MANIFEST.in ├── README.rst ├── chinaapi ├── __init__.py ├── decorators.py ├── douban │ ├── __init__.py │ └── open.py ├── exceptions.py ├── jsonDict.py ├── netease │ ├── __init__.py │ └── web.py ├── open.py ├── qq │ ├── __init__.py │ └── weibo │ │ ├── __init__.py │ │ └── open.py ├── renren │ ├── __init__.py │ ├── open.py │ └── web.py ├── request.py ├── sina │ ├── __init__.py │ └── weibo │ │ ├── __init__.py │ │ ├── apps.py │ │ ├── open.py │ │ └── web.py ├── sohu │ ├── __init__.py │ └── web.py ├── taobao │ ├── __init__.py │ └── open.py ├── utils.py ├── wap.py └── web.py ├── images └── pic.jpg ├── requirements-test.txt ├── requirements.txt ├── setup.py └── tests ├── __init__.py ├── decorator.py ├── test_decorators.py ├── test_exceptions.py ├── test_json_dict.py ├── test_open.py ├── test_qq_weibo.py ├── test_renren.py ├── test_request.py ├── test_sina_weibo.py ├── test_taobao.py ├── test_utils.py ├── test_wap.py └── test_web.py /.gitignore: -------------------------------------------------------------------------------- 1 | .coverage 2 | MANIFEST 3 | coverage.xml 4 | nosetests.xml 5 | junit-report.xml 6 | pylint.txt 7 | toy.py 8 | tox.ini 9 | violations.pyflakes.txt 10 | cover/ 11 | build/ 12 | dist/ 13 | chinaapi.egg-info/ 14 | *.pyc 15 | *.swp 16 | *.egg 17 | tests_local/ 18 | extends/ 19 | packages/ 20 | local/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | # command to install dependencies 5 | install: pip install -r requirements.txt && pip install -r requirements-test.txt 6 | # command to run tests 7 | script: python setup.py test -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | .. :changelog: 2 | 3 | Release History 4 | --------------- 5 | 6 | 0.8.6 (2014-03-12) 7 | ++++++++++++++++++ 8 | - 添加:ApiRequestError 9 | - 移除:InvalidApiError,NotExistApi,MutexApiParametersError 10 | 11 | 0.8.0 (2014-01-20) 12 | ++++++++++++++++++ 13 | - 添加新浪微博web登录函数 14 | - 添加人人web登录函数 15 | - 添加搜狐web登录函数 16 | - 添加网易web登录函数 17 | 18 | 0.7.3 (2014-01-04) 19 | ++++++++++++++++++ 20 | - 支持重试,默认的重试次数为3次(注:目前只针对淘宝和新浪微博一些特定的异常进行重试) 21 | 22 | 0.7.1 (2014-01-03) 23 | ++++++++++++++++++ 24 | - 调整代码架构:将utils中的文件移至根目录 25 | - 移除Response类,使用requests response hook解析响应结果 26 | 27 | 0.6.7 (2013-12-31) 28 | ++++++++++++++++++ 29 | - 新浪微博:增加赞操作 30 | - 添加异常类:MutexApiParameters 31 | 32 | 33 | 0.6.5 (2013-12-20) 34 | ++++++++++++++++++ 35 | - 允许Open Client不传入App参数 36 | 37 | 38 | 0.6.4 (2013-12-20) 39 | ++++++++++++++++++ 40 | - 添加两个公开的大型新浪微博应用App 41 | 42 | 43 | 0.6.2 (2013-12-14) 44 | ++++++++++++++++++ 45 | - 调整Token 46 | 47 | 48 | 0.6.0 (2013-12-14) 49 | ++++++++++++++++++ 50 | - 调整模块架构,分为Open(开放平台)、Web(Web网页)、Wap(手机网页)三种API 51 | 52 | 53 | 0.5.8 (2013-12-13) 54 | ++++++++++++++++++ 55 | - 支持新浪微博直接登录(直接使用账户密码登录,跳过获取code和回调环节) 56 | - 添加WeicoAndroidApp和WeicoIphoneApp两个App 57 | 58 | 59 | 0.5.7 (2013-12-12) 60 | ++++++++++++++++++ 61 | - 移除furl 62 | - 更改部分异常类的名称 63 | 64 | 65 | 0.5.6 (2013-12-11) 66 | ++++++++++++++++++ 67 | - 添加淘宝ApiOAuth2 68 | - 添加ApiResponseValueError 69 | 70 | 71 | 0.5.1 (2013-12-8) 72 | +++++++++++++++++ 73 | - 添加豆瓣ApiOAuth2 74 | 75 | 76 | 0.5.0 (2013-12-8) 77 | +++++++++++++++++ 78 | - 添加新浪微博、腾讯微博、人人的ApiOAuth2 79 | - 添加淘宝ApiOAuth 80 | 81 | 82 | 0.4.2 (2013-12-7) 83 | +++++++++++++++++ 84 | - 移除packages 85 | 86 | 0.4.0 (2013-12-7) 87 | +++++++++++++++++ 88 | 89 | - 添加Token,App等models,并添加测试 90 | - 添加ApiClientBase 91 | - 添加ApiError,并添加测试 92 | - 添加新浪微博、腾讯微博、淘宝、人人的ApiClient 93 | - 添加新浪微博、腾讯微博、淘宝、人人的ApiClient本地单元测试(测试成功,因含有帐号信息不上传到Github) 94 | 95 | 96 | 0.3.1 (2013-12-3) 97 | +++++++++++++++++ 98 | 99 | - 提取共用模块jsonDict,并增加测试 100 | 101 | 102 | 0.3.0 (2013-11-30) 103 | ++++++++++++++++++ 104 | 105 | - 上传到pipy,可通过: pip install chinaapi 进行安装 106 | 107 | 108 | 0.2.0 (2013-11-30) 109 | ++++++++++++++++++ 110 | 111 | **目前已引入的API Python SDK:** 112 | 113 | - 新浪微博:https://github.com/michaelliao/sinaweibopy 114 | - 腾讯微博:https://github.com/upbit/tweibo-pysdk 115 | - 淘宝:https://github.com/sempr/taobaopy -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst HISTORY.rst -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ChinaAPI 2 | ======== 3 | 4 | ChinaAPI是一个API库,使用Python语言编写。 5 | 6 | .. image:: https://travis-ci.org/smallcode/ChinaAPI.png 7 | :target: https://travis-ci.org/smallcode/ChinaAPI 8 | 9 | .. image:: https://badge.fury.io/py/chinaapi.png 10 | :target: http://badge.fury.io/py/chinaapi 11 | 12 | 支持 13 | ---- 14 | - 新浪微博 15 | - 腾讯微博 16 | - 淘宝 17 | - 人人 18 | - 豆瓣(OAuth2) 19 | 20 | 安装 21 | ---- 22 | 可以到项目所在地址下载:https://github.com/smallcode/ChinaAPI 23 | 24 | 或者直接用pip安装: 25 | 26 | .. code-block:: bash 27 | 28 | $ pip install chinaapi 29 | 30 | 注:ChinaAPI使用: `Requests`_ (Http请求模块) 31 | 32 | ---- 33 | 34 | 新浪微博API: 35 | -------- 36 | 37 | Client使用方法: 38 | 39 | .. code-block:: python 40 | 41 | from chinaapi.sina.weibo.open import Client 42 | 43 | 44 | client = Client() 45 | client.set_access_token('access_token') # 填上取得的token(可通过OAuth2取得) 46 | 47 | # 获取用户信息,对应的接口是:users/show 48 | r = client.users.show(uid=123456) 49 | print r.name # 显示用户名 50 | 51 | # 发布带图片的微博,对应的接口是:statuses/upload 52 | with open('pic.jpg', 'rb') as pic: 53 | r = client.statuses.upload(status=u'发布的内容', pic=pic) 54 | print r.id # 显示发布成功的微博的编号(即mid):1234567890123456 55 | 56 | 57 | Client调用规则:**斜杠(/)映射为点(.)** 58 | 59 | ====================================== ========================================= 60 | 新浪微博API 调 用 61 | ====================================== ========================================= 62 | users/show client.users.show() 63 | statuses/upload client.statuses.upload() 64 | ... ... 65 | ====================================== ========================================= 66 | 67 | 更多API请参见:`新浪微博API文档`_ 68 | 69 | OAuth2使用方法: 70 | 71 | .. code-block:: python 72 | 73 | from chinaapi.sina.weibo.open import OAuth2, App 74 | 75 | 76 | # 设置App,填上自己的app_key,app_secret;redirect_uri可不填 77 | app = App('app_key', 'app_secret', 'redirect_uri') 78 | 79 | # 获取授权链接 80 | oauth2 = OAuth2(app) 81 | url = oauth2.authorize() # 如果app中未设置redirect_uri,则此处必须传入 82 | print url # 显示授权链接(该url用于提供给用户进行登录授权,授权成功后会回调redirect_uri?code=****) 83 | 84 | # 获取Token 85 | token = oauth2.access_token(code='code') # code取自回调地址后所附的code参数 86 | print token.access_token # 显示访问令牌 87 | print token.expires_in # 显示令牌剩余授权时间的秒数 88 | print token.expired_at # 显示令牌到期日期,为timestamp格式 89 | 90 | # 取消授权 91 | r = oauth2.revoke('access_token') 92 | print r # 显示是否成功取消 93 | 94 | OAuth2调用规则:**斜杠(/)映射为点(.)** 95 | 96 | ====================================== ========================================= 97 | 新浪微博oauth2 API 调 用 98 | ====================================== ========================================= 99 | oauth2/authorize oauth2.authorize() 100 | oauth2/access_token oauth2.access_token() 101 | oauth2/get_token_info oauth2.get_token_info() 102 | oauth2/revokeoauth2 oauth2.revoke() 103 | ====================================== ========================================= 104 | 105 | ---- 106 | 107 | 淘宝API: 108 | ------ 109 | 110 | 111 | 使用示例: 112 | 113 | .. code-block:: python 114 | 115 | from chinaapi.taobao.open import Client, App 116 | 117 | 118 | # client的设置 119 | app = App('app_key', 'app_secret') # 填上自己的app_key,app_secret 120 | client = Client(app) 121 | 122 | # 获取淘宝客店铺列表,对应的接口是:taobao.tbk.shops.get 123 | # 返回结果r是json中tbk_shops_get_response的值 124 | # 所有的接口都直接返回response(键名为:接口+_response后缀)的值 125 | r = client.tbk.shops.get(cid=14, fields='user_id,seller_nick,shop_title,pic_url') 126 | print len(r.tbk_shops.tbk_shop) # 显示店铺列表的数量:40 127 | 128 | 129 | 调用规则:**直接映射(可省略前缀taobao.)** 130 | 131 | ====================================== ========================================= 132 | 淘宝API 调 用 133 | ====================================== ========================================= 134 | taobao.itemcats.get client.itemcats.get() 135 | 或者 client.taobao.itemcats.get() 136 | taobao.tbk.shops.get client.tbk.shops.get() 137 | 或者 client.taobao.tbk.shops.get() 138 | 139 | ====================================== ========================================= 140 | 141 | 更多API请参见:`淘宝API文档`_ 142 | 143 | ---- 144 | 145 | 腾讯微博API: 146 | -------- 147 | 148 | 使用方法: 149 | 150 | .. code-block:: python 151 | 152 | from chinaapi.qq.weibo.open import Client, App 153 | 154 | 155 | # client的设置 156 | app = App('app_key', 'app_secret') # 填上自己的app_key,app_secret 157 | client = Client(app, openid='openid') # 填上取得的openid 158 | client.set_access_token('access_token') # 填上取得的access_token 159 | 160 | # 获取当前登录用户的信息,对应的接口是:user/info 161 | # 返回结果r是json中的data值 162 | r = client.user.info() 163 | print r.name # 显示用户名 164 | 165 | # 发布一条带图片的微博,对应的接口是:t/add_pic 166 | with open('pic.jpg', 'rb') as pic: 167 | r = client.t.add_pic(content=u'发布的内容', pic=pic) 168 | print r.id # 显示微博的ID 169 | 170 | # 删除一条微博,对应的接口是:t/del 171 | r = client.t.delete(id=r.id) # 请将del替换为delete 172 | print r.id # 显示微博的ID 173 | 174 | # 有两种设置clientip的方法: 175 | # 1.全局设置,在该client所发起的所有调用中有效 176 | client.clientip = '220.181.111.85' 177 | # 2.临时设置,只在此次调用中有效,会覆盖全局设置 178 | client.t.upload_pic(pic=pic, pic_type=2, clientip='220.181.111.85') 179 | 180 | 181 | 调用规则:**斜杠(/)映射为点(.),del映射为delete(因del是Python保留字,无法作为方法名)** 182 | 183 | ====================================== ========================================= 184 | 腾讯微博API 调 用 185 | ====================================== ========================================= 186 | user/info client.user.info() 187 | t/add_pic client.t.add_pic() 188 | t/del client.t.delete() 189 | ====================================== ========================================= 190 | 191 | 更多API请参见:`腾讯微博API文档`_ 192 | 193 | ---- 194 | 195 | 人人API: 196 | ------ 197 | 198 | 使用方法: 199 | 200 | .. code-block:: python 201 | 202 | from chinaapi.renren.open import Client 203 | 204 | 205 | client = Client() 206 | client.set_access_token('access_token') # 填上取得的access_token 207 | 208 | # 获取用户信息,对应的接口是:/v2/user/get 209 | r = client.user.get(userId=334258249) 210 | print r.name # 显示用户名 211 | 212 | # 上传照片至用户相册,对应的接口是:/v2/photo/upload 213 | with open('pic.jpg', 'rb') as pic: 214 | r = client.photo.upload(file=pic) 215 | print r.id # 显示照片的ID 216 | 217 | 218 | 调用规则:**斜杠(/)映射为点(.)** 219 | 220 | ====================================== ========================================= 221 | 人人API 调 用 222 | ====================================== ========================================= 223 | /v2/user/get client.user.get() 224 | /v2/photo/upload client.photo.upload() 225 | 226 | ====================================== ========================================= 227 | 228 | 更多API请参见:`人人API文档`_ 229 | 230 | ---- 231 | 232 | 感谢以下Python SDK的开发者们的贡献: 233 | ----------------------- 234 | 235 | - 新浪微博:`sinaweibopy`_ 236 | - 腾讯微博:`tweibo`_ 237 | - 淘宝:`taobaopy`_ 238 | - 豆瓣:`douban-client`_ 239 | 240 | .. _`sinaweibopy`: https://github.com/michaelliao/sinaweibopy 241 | .. _`tweibo`: https://github.com/upbit/tweibo-pysdk 242 | .. _`taobaopy`: https://github.com/sempr/taobaopy 243 | .. _`douban-client`: https://github.com/douban/douban-client 244 | .. _`Requests`: https://github.com/kennethreitz/requests 245 | .. _`新浪微博API文档`: http://open.weibo.com/wiki/%E5%BE%AE%E5%8D%9AAPI 246 | .. _`淘宝API文档`: http://open.taobao.com/doc/category_list.htm?spm=0.0.0.0.MNfatw&id=102 247 | .. _`腾讯微博API文档`: http://wiki.open.t.qq.com/index.php/API%E6%96%87%E6%A1%A3 248 | .. _`人人API文档`: http://wiki.dev.renren.com/wiki/API2 249 | -------------------------------------------------------------------------------- /chinaapi/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | 4 | __title__ = 'chinaapi' 5 | __version__ = '0.8.9' 6 | __author__ = 'smallcode (45945756@qq.com)' 7 | __license__ = 'Apache 2.0' -------------------------------------------------------------------------------- /chinaapi/decorators.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from functools import wraps 3 | 4 | 5 | def retry(max_tries, exceptions=(Exception,), hook=None): 6 | """Retry calling the decorated function 7 | """ 8 | def deco_retry(f): 9 | @wraps(f) 10 | def f_retry(*args, **kwargs): 11 | tries = max_tries 12 | while tries > 1: 13 | try: 14 | return f(*args, **kwargs) 15 | except exceptions, e: 16 | if hook is not None: 17 | hook(e) 18 | tries -= 1 19 | return f(*args, **kwargs) 20 | 21 | return f_retry # true decorator 22 | return deco_retry -------------------------------------------------------------------------------- /chinaapi/douban/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | -------------------------------------------------------------------------------- /chinaapi/douban/open.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from chinaapi.exceptions import ApiResponseError 3 | from chinaapi.open import * 4 | 5 | 6 | class OAuth2(OAuth2Base): 7 | AUTH_URL = 'https://www.douban.com/service/auth2/auth' 8 | TOKEN_URL = 'https://www.douban.com/service/auth2/token' 9 | 10 | def _parse_token(self, response): 11 | r = response.json_dict() 12 | if 'code' in r and 'msg' in r: 13 | raise ApiResponseError(response, r.code, r.msg) 14 | return Token(**r) -------------------------------------------------------------------------------- /chinaapi/exceptions.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | class ApiError(IOError): 3 | """ API异常 """ 4 | 5 | def __init__(self, url='', code=0, message='', sub_code=0, sub_message=''): 6 | self.url = url 7 | self.code = code 8 | self.message = message 9 | self.sub_code = sub_code 10 | self.sub_message = sub_message 11 | super(ApiError, self).__init__(code, message) 12 | 13 | @staticmethod 14 | def format(code, message): 15 | return u'[%s]: %s, ' % (code, message) if code or message else '' 16 | 17 | def __str__(self): 18 | return u'%s%srequest: %s' % ( 19 | self.format(self.code, self.message), self.format(self.sub_code, self.sub_message), self.url) 20 | 21 | 22 | class ApiRequestError(ApiError): 23 | def __init__(self, request, code, message, sub_code=0, sub_message=''): 24 | self.request = request 25 | super(ApiRequestError, self).__init__(self.get_url(), code, message, sub_code, sub_message) 26 | 27 | def is_multipart(self): 28 | return 'multipart/form-data' in self.request.headers.get('Content-Type', '') 29 | 30 | def get_url(self): 31 | if not self.is_multipart() and self.request.body: 32 | return u'%s?%s' % (self.request.url, self.request.body) 33 | return self.request.url 34 | 35 | 36 | class ApiResponseError(ApiRequestError): 37 | """ 响应结果中包含的异常 """ 38 | 39 | def __init__(self, response, code=0, message='', sub_code=0, sub_message=''): 40 | self.response = response 41 | super(ApiResponseError, self).__init__(response.request, 42 | code or response.status_code, 43 | message or response.text, 44 | sub_code, 45 | sub_message) 46 | 47 | 48 | class OAuth2Error(ApiError): 49 | """ OAuth2异常 """ 50 | 51 | 52 | class MissingRedirectUri(OAuth2Error, ValueError): 53 | """ 缺少 redirect_uri """ -------------------------------------------------------------------------------- /chinaapi/jsonDict.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import json 3 | 4 | 5 | class JsonDict(dict): 6 | """ 7 | General json object that allows attributes to be bound to and also behaves like a dict. 8 | 9 | >>> jd = JsonDict(a=1, b='test') 10 | >>> jd.a 11 | 1 12 | >>> jd.b 13 | 'test' 14 | >>> jd['b'] 15 | 'test' 16 | >>> jd.c 17 | Traceback (most recent call last): 18 | ... 19 | AttributeError: 'JsonDict' object has no attribute 'c' 20 | >>> jd['c'] 21 | Traceback (most recent call last): 22 | ... 23 | KeyError: 'c' 24 | """ 25 | def __getattr__(self, attr): 26 | try: 27 | return self[attr] 28 | except KeyError: 29 | raise AttributeError(r"'JsonDict' object has no attribute '%s'" % attr) 30 | 31 | def __setattr__(self, attr, value): 32 | self[attr] = value 33 | 34 | 35 | def loads(string): 36 | """ 37 | Parse json string into JsonDict. 38 | 39 | >>> r = loads(r'{"name":"Michael","score":95}') 40 | >>> r.name 41 | u'Michael' 42 | >>> r['score'] 43 | 95 44 | """ 45 | return json.loads(string, object_hook=lambda pairs: JsonDict(pairs.iteritems())) -------------------------------------------------------------------------------- /chinaapi/netease/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | -------------------------------------------------------------------------------- /chinaapi/netease/web.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from chinaapi.exceptions import ApiResponseError 3 | from chinaapi.web import ClientBase 4 | from requests.utils import dict_from_cookiejar 5 | 6 | 7 | class Client(ClientBase): 8 | def login(self, username, password): 9 | data = dict( 10 | username=username, 11 | password=password, 12 | type=1, 13 | product=163, 14 | savelogin=1, 15 | url='http://www.163.com/special/0077450P/login_frame.html', 16 | url2='http://www.163.com/special/0077450P/login_frame.html', 17 | noRedirect=1, 18 | ) 19 | r = self._session.post('https://reg.163.com/logins.jsp', data=data) 20 | cookies = dict_from_cookiejar(r.cookies) 21 | if 'NTES_PASSPORT' not in cookies: 22 | raise ApiResponseError(r, 0, r.text) 23 | return r 24 | 25 | -------------------------------------------------------------------------------- /chinaapi/open.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import time 3 | from requests.utils import default_user_agent 4 | from .request import Request 5 | from .exceptions import ApiError, MissingRedirectUri 6 | from .utils import request_url 7 | from .decorators import retry 8 | from . import __version__, __title__ 9 | 10 | DEFAULT_RETRIES = 3 11 | 12 | 13 | class Method(object): 14 | GET = 'GET' 15 | POST = 'POST' 16 | 17 | 18 | class Token(object): 19 | def __init__(self, access_token=None, expires_in=None, refresh_token=None, **kwargs): 20 | """ 21 | access_token:访问令牌 22 | expired_at:令牌到期日期,为timestamp格式 23 | expires_in:令牌剩余授权时间的秒数 24 | refresh_token:用于刷新令牌 25 | """ 26 | self.access_token = access_token 27 | self.expired_at = None 28 | self.expires_in = expires_in 29 | self.refresh_token = refresh_token 30 | self._data = kwargs 31 | 32 | @staticmethod 33 | def _get_now(): 34 | return int(time.time()) 35 | 36 | def _get_expires_in(self): 37 | if self.expired_at: 38 | return self.expired_at - self._get_now() 39 | 40 | def _set_expires_in(self, expires_in): 41 | if expires_in: 42 | self.expired_at = int(expires_in) + self._get_now() 43 | 44 | expires_in = property(_get_expires_in, _set_expires_in) 45 | 46 | @property 47 | def is_expires(self): 48 | return not self.access_token or (self.expired_at is not None and self._get_now() > self.expired_at) 49 | 50 | def __getattr__(self, item): 51 | if item in self._data: 52 | return self._data[item] 53 | raise AttributeError 54 | 55 | 56 | class App(object): 57 | def __init__(self, key='', secret='', redirect_uri=''): 58 | self.key = key 59 | self.secret = secret 60 | self.redirect_uri = redirect_uri 61 | 62 | 63 | class ClientWrapper(object): 64 | def __init__(self, client, attr): 65 | """ 66 | segments:用于保存API路径片段 67 | """ 68 | self._client = client 69 | self._segments = [attr] 70 | 71 | def __call__(self, **kwargs): 72 | return self._client.request(self._segments, **kwargs) 73 | 74 | def __getattr__(self, attr): 75 | if not attr.startswith('_'): 76 | self._segments.append(attr) 77 | return self 78 | 79 | 80 | class ClientBase(Request): 81 | def __init__(self, app=App(), token=Token()): 82 | super(ClientBase, self).__init__() 83 | self.app = app 84 | self.token = token 85 | self._session.headers['User-Agent'] = default_user_agent('%s/%s requests' % (__title__, __version__)) 86 | 87 | def set_access_token(self, access_token, expires_in=None): 88 | self.token = Token(access_token, expires_in) 89 | 90 | def _prepare_method(self, segments): 91 | return Method.POST 92 | 93 | def _prepare_url(self, segments, queries): 94 | raise NotImplementedError 95 | 96 | def _prepare_queries(self, queries): 97 | pass 98 | 99 | def _prepare_body(self, queries): 100 | results = ({}, {}) 101 | for k, v in queries.items(): 102 | results[hasattr(v, 'read')][k] = v 103 | return results 104 | 105 | def _parse_response(self, response): 106 | return response 107 | 108 | def _is_retry_error(self, e): 109 | return False 110 | 111 | def prepare_request(self, segments, queries): 112 | url = self._prepare_url(segments, queries) 113 | method = self._prepare_method(segments) 114 | self._prepare_queries(queries) 115 | params, data, files = None, None, None 116 | if method == Method.POST: 117 | data, files = self._prepare_body(queries) 118 | else: 119 | params = queries 120 | return method, url, params, data, files 121 | 122 | def request(self, segments, **queries): 123 | method, url, params, data, files = self.prepare_request(segments, queries) 124 | 125 | def handle_error(e): 126 | if self._is_retry_error(e): 127 | if files: 128 | for f in files.values(): 129 | f.seek(0) 130 | else: 131 | raise e 132 | 133 | @retry(DEFAULT_RETRIES, ApiError, handle_error) 134 | def try_request(): 135 | response = self._session.request(method, url, params=params, data=data, files=files) 136 | return self._parse_response(response) 137 | 138 | return try_request() 139 | 140 | def __getattr__(self, attr): 141 | return ClientWrapper(self, attr) 142 | 143 | 144 | class OAuthBase(Request): 145 | def __init__(self, app=App()): 146 | super(OAuthBase, self).__init__() 147 | self.app = app 148 | 149 | 150 | class OAuth2Base(OAuthBase): 151 | AUTH_URL = '' 152 | TOKEN_URL = '' 153 | 154 | def _parse_token(self, response): 155 | return response.json_dict() 156 | 157 | def authorize(self, **kwargs): 158 | """ 授权 159 | 返回授权链接 160 | """ 161 | kwargs.setdefault('response_type', 'code') 162 | kwargs.setdefault('redirect_uri', self.app.redirect_uri) 163 | kwargs['client_id'] = self.app.key 164 | url = request_url(self.AUTH_URL, kwargs) 165 | if not kwargs['redirect_uri']: 166 | raise MissingRedirectUri(url) 167 | return url 168 | 169 | def access_token(self, **kwargs): 170 | """ 用code换取access_token,请求参数说明: 171 | 授权模式 所需参数 172 | authorization_code: code 和 redirect_uri(可选) 173 | refresh_token: refresh_token 174 | password: username 和 password 175 | client_credentials: 无 176 | @返回Token 177 | """ 178 | if 'code' in kwargs: 179 | grant_type = 'authorization_code' 180 | kwargs.setdefault('redirect_uri', self.app.redirect_uri) 181 | if not kwargs['redirect_uri']: 182 | raise MissingRedirectUri(self.TOKEN_URL) 183 | elif 'refresh_token' in kwargs: 184 | grant_type = 'refresh_token' 185 | elif 'username' in kwargs and 'password' in kwargs: 186 | grant_type = 'password' 187 | else: 188 | grant_type = 'client_credentials' 189 | kwargs.update(client_id=self.app.key, client_secret=self.app.secret, grant_type=grant_type) 190 | response = self._session.post(self.TOKEN_URL, data=kwargs) 191 | return self._parse_token(response) 192 | 193 | def refresh_token(self, refresh_token, **kwargs): 194 | kwargs['refresh_token'] = refresh_token 195 | return self.access_token(**kwargs) 196 | -------------------------------------------------------------------------------- /chinaapi/qq/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | -------------------------------------------------------------------------------- /chinaapi/qq/weibo/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | -------------------------------------------------------------------------------- /chinaapi/qq/weibo/open.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from chinaapi.open import ClientBase, Method, OAuth2Base, Token, App 3 | from chinaapi.exceptions import ApiResponseError 4 | from chinaapi.utils import parse_querystring 5 | 6 | 7 | IS_POST_METHOD = { 8 | 'user': lambda m: m in ['verify'], 9 | 'friends': lambda m: m in ['addspecial', 'delspecial', 'addblacklist', 'delblacklist'], 10 | 't': lambda m: m in ['re_add', 'reply', 'comment', 'like', 'unlike'], 11 | 'fav': lambda m: m in ['addht', 'addt', 'delht', 'delt'], 12 | 'vote': lambda m: m in ['createvote', 'vote'], 13 | 'list': lambda m: m != 'timeline', # 只有timeline接口是读接口,其他全是写接口 14 | 'lbs': lambda m: True # 全是写接口 15 | } 16 | 17 | DEFAULT_IS_POST_METHOD = lambda m: False 18 | 19 | RET = { 20 | 0: u'成功返回', 21 | 1: u'参数错误', 22 | 2: u'频率受限', 23 | 3: u'鉴权失败', 24 | 4: u'服务器内部错误', 25 | 5: u'用户错误', 26 | 6: u'未注册微博', 27 | 7: u'未实名认证' 28 | } 29 | 30 | 31 | def parse(response): 32 | r = response.json_dict() 33 | if 'ret' in r and r.ret != 0: 34 | raise ApiResponseError(response, r.ret, RET.get(r.ret, u''), r.get('errcode', ''), r.get('msg', '')) 35 | if 'data' in r: 36 | return r.data 37 | return r 38 | 39 | 40 | class Client(ClientBase): 41 | # 写接口 42 | _post_methods = ['add', 'del', 'create', 'delete', 'update', 'upload'] 43 | 44 | def __init__(self, app=App(), token=Token(), openid=None, clientip=None): 45 | super(Client, self).__init__(app, token) 46 | self.openid = openid 47 | self.clientip = clientip 48 | 49 | def _parse_response(self, response): 50 | return parse(response) 51 | 52 | def _prepare_url(self, segments, queries): 53 | """ 54 | 因del为Python保留字,无法作为方法名,需将del替换为delete,并在此处进行反向转换。 55 | """ 56 | if len(segments) == 2 and segments[0] != 'list' and segments[1] == 'delete': # list本身有delete方法,需排除 57 | segments[1] = segments[1].replace('delete', 'del') 58 | return 'https://open.t.qq.com/api/{0}'.format('/'.join(segments)) 59 | 60 | def _prepare_method(self, segments): 61 | model, method = tuple([segment.lower() for segment in segments]) 62 | if method.split('_')[0] in self._post_methods: 63 | return Method.POST 64 | elif IS_POST_METHOD.get(model, DEFAULT_IS_POST_METHOD)(method): 65 | return Method.POST 66 | return Method.GET 67 | 68 | def _prepare_queries(self, queries): 69 | queries.update(oauth_version='2.a', format='json', oauth_consumer_key=self.app.key) 70 | if not self.token.is_expires: 71 | queries['access_token'] = self.token.access_token 72 | if self.openid: 73 | queries['openid'] = self.openid 74 | if 'clientip' not in queries and self.clientip: 75 | queries['clientip'] = self.clientip 76 | 77 | 78 | class OAuth2(OAuth2Base): 79 | AUTH_URL = 'https://open.t.qq.com/cgi-bin/oauth2/authorize' 80 | TOKEN_URL = 'https://open.t.qq.com/cgi-bin/oauth2/access_token' 81 | 82 | def _parse_token(self, response): 83 | data = parse_querystring(response.text) 84 | if 'errorCode' in data: 85 | raise ApiResponseError(response, data['errorCode'], data.get('errorMsg', '').strip("'")) 86 | return Token(**data) 87 | 88 | def revoke(self, **kwargs): 89 | """ 取消授权 90 | 请求参数:oauth或openid&openkey标准参数 91 | 返回是否成功取消 92 | """ 93 | kwargs['format'] = 'json' 94 | response = self._session.get('http://open.t.qq.com/api/auth/revoke_auth', params=kwargs) 95 | parse(response) 96 | return True # 没有异常说明ret=0(ret: 0-成功,非0-失败) 97 | -------------------------------------------------------------------------------- /chinaapi/renren/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | -------------------------------------------------------------------------------- /chinaapi/renren/open.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from chinaapi.open import ClientBase, Method, OAuth2Base, Token, App 3 | from chinaapi.exceptions import ApiResponseError 4 | 5 | 6 | App = App 7 | 8 | 9 | class Client(ClientBase): 10 | #写入接口 11 | _post_methods = ['put', 'share', 'remove', 'upload'] 12 | 13 | def _prepare_url(self, segments, queries): 14 | return 'https://api.renren.com/v2/{0}'.format('/'.join(segments)) 15 | 16 | def _prepare_method(self, segments): 17 | if segments[-1].lower() in self._post_methods: 18 | return Method.POST 19 | return Method.GET 20 | 21 | def _prepare_queries(self, queries): 22 | if not self.token.is_expires: 23 | queries['access_token'] = self.token.access_token 24 | 25 | def _parse_response(self, response): 26 | r = response.json_dict() 27 | if 'error' in r and 'code' in r.error: 28 | raise ApiResponseError(response, r.error.code, r.error.get('message', '')) 29 | return r 30 | 31 | 32 | class OAuth2(OAuth2Base): 33 | AUTH_URL = 'https://graph.renren.com/oauth/authorize' 34 | TOKEN_URL = 'https://graph.renren.com/oauth/token' 35 | 36 | def _parse_token(self, response): 37 | r = response.json_dict() 38 | if 'error_code' in r: 39 | raise ApiResponseError(response, r.error_code, r.get('error_description', r.get('error', ''))) 40 | return Token(**r) 41 | 42 | 43 | -------------------------------------------------------------------------------- /chinaapi/renren/web.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import os 3 | import random 4 | import re 5 | from chinaapi.web import ClientBase 6 | 7 | 8 | class Client(ClientBase): 9 | @staticmethod 10 | def encrypt_password(e, m, s): 11 | def _encrypt_chunk(e, m, chunk): 12 | chunk = map(ord, chunk) 13 | 14 | # 补成偶数长度 15 | if not len(chunk) % 2 == 0: 16 | chunk.append(0) 17 | 18 | nums = [chunk[i] + (chunk[i + 1] << 8) for i in range(0, len(chunk), 2)] 19 | 20 | c = sum([n << i * 16 for i, n in enumerate(nums)]) 21 | 22 | encrypted = pow(c, e, m) 23 | 24 | # 转成16进制并且去掉开头的0x 25 | return hex(encrypted)[2:] 26 | 27 | 28 | CHUNK_SIZE = 30 # 分段加密 29 | 30 | e, m = int(e, 16), int(m, 16) 31 | chunks = [s[:CHUNK_SIZE], s[CHUNK_SIZE:]] if len(s) > CHUNK_SIZE else [s] 32 | result = [_encrypt_chunk(e, m, chunk) for chunk in chunks] 33 | return ' '.join(result)[:-1] # 去掉最后的'L' 34 | 35 | def get_encrypt_key(self): 36 | r = self._session.get('http://login.renren.com/ajax/getEncryptKey') 37 | return r.json() 38 | 39 | def get_show_captcha(self, email=None): 40 | r = self._session.post('http://www.renren.com/ajax/ShowCaptcha', data={'email': email}) 41 | return r.json() 42 | 43 | def get_icode(self, fn): 44 | r = self._session.get("http://icode.renren.com/getcode.do?t=web_login&rnd=%s" % random.random()) 45 | if r.status_code == 200 and r.raw.headers['content-type'] == 'image/jpeg': 46 | with open(fn, 'wb') as f: 47 | for chunk in r.iter_content(): 48 | f.write(chunk) 49 | else: 50 | raise Exception('get icode failure') 51 | 52 | def get_token(self, html=''): 53 | p = re.compile("get_check:'(.*)',get_check_x:'(.*)',env") 54 | 55 | if not html: 56 | r = self._session.get('http://www.renren.com') 57 | html = r.text 58 | 59 | result = p.search(html) 60 | return { 61 | 'requestToken': result.group(1), 62 | '_rtk': result.group(2) 63 | } 64 | 65 | def login(self, username, password): 66 | key = self.get_encrypt_key() 67 | 68 | if self.get_show_captcha(username) == 1: 69 | fn = 'icode.%s.jpg' % os.getpid() 70 | self.get_icode(fn) 71 | print "Please input the code in file '%s':" % fn 72 | icode = raw_input().strip() 73 | os.remove(fn) 74 | else: 75 | icode = '' 76 | 77 | data = { 78 | 'email': username, 79 | 'origURL': 'http://www.renren.com/home', 80 | 'icode': icode, 81 | 'domain': 'renren.com', 82 | 'key_id': 1, 83 | 'captcha_type': 'web_login', 84 | 'password': self.encrypt_password(key['e'], key['n'], password) if key['isEncrypt'] else password, 85 | 'rkey': key.get('rkey', '') 86 | } 87 | url = 'http://www.renren.com/ajaxLogin/login?1=1&uniqueTimestamp=%f' % random.random() 88 | r = self._session.post(url, data) 89 | result = r.json() 90 | if result['code']: 91 | self.email = username 92 | r = self._session.get(result['homeUrl']) 93 | return self.get_token(r.text) 94 | else: 95 | raise Exception('Login Error') -------------------------------------------------------------------------------- /chinaapi/request.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import types 3 | import re 4 | 5 | import requests 6 | 7 | from .jsonDict import JsonDict, loads 8 | from .exceptions import ApiResponseError 9 | 10 | 11 | def json_dict(self): 12 | try: 13 | return self.json(object_hook=lambda pairs: JsonDict(pairs.iteritems())) 14 | except ValueError, e: 15 | try: 16 | self.raise_for_status() 17 | except requests.RequestException, e: 18 | raise ApiResponseError(self, message=u'%s, response: %s' % (e, self.text)) 19 | else: 20 | raise ApiResponseError(self, e.__class__.__name__, u'%s, value: %s' % (e, self.text)) 21 | 22 | 23 | def jsonp_dict(self): 24 | return loads(re.search(r'(\{.*\})', self.text).group(1)) 25 | 26 | 27 | def add_method(response, *args, **kwargs): 28 | response.json_dict = types.MethodType(json_dict, response) 29 | response.jsonp_dict = types.MethodType(jsonp_dict, response) 30 | return response 31 | 32 | 33 | class Request(object): 34 | def __init__(self): 35 | self._session = requests.session() 36 | self._session.hooks = dict(response=add_method) -------------------------------------------------------------------------------- /chinaapi/sina/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | -------------------------------------------------------------------------------- /chinaapi/sina/weibo/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | -------------------------------------------------------------------------------- /chinaapi/sina/weibo/apps.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from chinaapi.open import App 3 | 4 | 5 | class WeicoAndroidApp(App): 6 | """ Weico.Android版 """ 7 | def __init__(self): 8 | super(WeicoAndroidApp, self).__init__('211160679', '63b64d531b98c2dbff2443816f274dd3') 9 | 10 | 11 | class WeicoIphoneApp(App): 12 | """ Weico.Iphone版 """ 13 | def __init__(self): 14 | super(WeicoIphoneApp, self).__init__('82966982', '72d4545a28a46a6f329c4f2b1e949e6a') 15 | -------------------------------------------------------------------------------- /chinaapi/sina/weibo/open.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import base64 3 | import hashlib 4 | import hmac 5 | from chinaapi.utils import parse_querystring 6 | from chinaapi.open import ClientBase, Method, OAuth2Base, Token, App 7 | from chinaapi.exceptions import ApiResponseError 8 | from chinaapi.jsonDict import loads 9 | 10 | App = App 11 | 12 | RETRY_CODES = { 13 | 10001: 'system error', 14 | 10011: 'RPC error', 15 | 20205: 'in block', 16 | 21405: "couldn't connect to host | Operation timed out after 2000 milliseconds", 17 | 23201: 'Backend Service Connect Timeout', 18 | } 19 | 20 | 21 | def parse(response): 22 | r = response.json_dict() 23 | if 'error_code' in r: 24 | raise ApiResponseError(response, r.error_code, r.get('error', '')) 25 | return r 26 | 27 | 28 | class Client(ClientBase): 29 | #写入接口 30 | _post_methods = ['create', 'add', 'destroy', 'update', 'upload', 'repost', 'reply', 'send', 'post', 'invite', 31 | 'shield', 'order'] 32 | #含下划线的写入接口,如:statuses/upload_url_text 33 | _underlined_post_methods = ['add', 'upload', 'destroy', 'update', 'set', 'cancel', 'not'] 34 | 35 | def _parse_response(self, response): 36 | return parse(response) 37 | 38 | def _prepare_url(self, segments, queries): 39 | if 'pic' in queries: 40 | prefix = 'upload.' 41 | elif 'remind' == segments[0]: 42 | prefix = 'rm.' 43 | else: 44 | prefix = '' 45 | url = 'https://{0}api.weibo.com/2/{1}.json'.format(prefix, '/'.join(segments)) 46 | if 'like' in segments: 47 | # like操作需source和access_token参数,否则无法执行 48 | queries['source'] = self.app.key 49 | if not self.token.is_expires: 50 | queries['access_token'] = self.token.access_token 51 | return url.replace('weibo.com', 'weibo.cn') 52 | return url 53 | 54 | def _prepare_method(self, segments): 55 | segment = segments[-1].lower() 56 | if segment in self._post_methods: 57 | return Method.POST 58 | elif '_' in segment: 59 | splits = segment.split('_') 60 | if splits[0] in self._underlined_post_methods or splits[-1] == 'update': 61 | return Method.POST 62 | return Method.GET 63 | 64 | def _prepare_queries(self, queries): 65 | if self.token.access_token is None: 66 | queries['source'] = self.app.key # 对于不需要授权的API操作需追加source参数 67 | if not self.token.is_expires: 68 | self._session.headers['Authorization'] = 'OAuth2 %s' % self.token.access_token 69 | 70 | def _is_retry_error(self, e): 71 | return e.code in RETRY_CODES 72 | 73 | 74 | class OAuth2(OAuth2Base): 75 | BASE_URL = 'https://api.weibo.com/oauth2/' 76 | AUTH_URL = BASE_URL + 'authorize' 77 | TOKEN_URL = BASE_URL + 'access_token' 78 | 79 | def _parse_token(self, response): 80 | data = parse(response) 81 | data['created_at'] = data.get('create_at', None) 82 | if 'expires_in' not in data: 83 | data['expires_in'] = data.get('expire_in', None) 84 | return Token(**data) 85 | 86 | def revoke(self, access_token): 87 | """ 取消授权 88 | 返回是否成功取消 89 | """ 90 | response = self._session.get(self.BASE_URL + 'revokeoauth2', params={'access_token': access_token}) 91 | return parse(response).result 92 | 93 | def get_token_info(self, access_token): 94 | """ 获取access_token详细信息 95 | 返回Token 96 | """ 97 | response = self._session.post(self.BASE_URL + 'get_token_info', data={'access_token': access_token}) 98 | token = self._parse_token(response) 99 | token.access_token = access_token 100 | return token 101 | 102 | def parse_signed_request(self, signed_request): 103 | """ 用于站内应用 104 | signed_request: 应用框架在加载时会通过向Canvas URL post的参数signed_request 105 | Returns: Token, is_valid (令牌, 是否有效) 106 | """ 107 | 108 | def base64decode(s): 109 | appendix = '=' * (4 - len(s) % 4) 110 | return base64.b64decode(s.replace('-', '+').replace('_', '/') + appendix) 111 | 112 | encoded_sign, encoded_data = signed_request.split('.', 1) 113 | sign = base64decode(encoded_sign) 114 | data = loads(base64decode(encoded_data)) 115 | token = Token(data.oauth_token, data.expires, uid=data.user_id, created_at=data.issued_at, **data) 116 | is_valid = data.algorithm == u'HMAC-SHA256' and hmac.new(self.app.key, encoded_data, 117 | hashlib.sha256).digest() == sign 118 | return token, is_valid 119 | 120 | def get_code(self, username, password, allow_redirects=True): 121 | data = {"client_id": self.app.key, 122 | "redirect_uri": self.app.redirect_uri, 123 | "userId": username, 124 | "passwd": password, 125 | "isLoginSina": "0", 126 | "action": "submit", 127 | "response_type": "code", 128 | } 129 | 130 | headers = { 131 | "User-Agent": "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36", 132 | "Host": "api.weibo.com", 133 | "Referer": self.authorize() 134 | } 135 | 136 | r = self._session.post(self.AUTH_URL, data=data, headers=headers, allow_redirects=allow_redirects) 137 | if allow_redirects: 138 | code_url = r.url 139 | else: 140 | code_url = r.headers['location'] 141 | query = parse_querystring(code_url) 142 | return query['code'] 143 | -------------------------------------------------------------------------------- /chinaapi/sina/weibo/web.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from chinaapi.utils import parse_querystring 3 | from chinaapi.web import ClientBase 4 | from chinaapi.exceptions import ApiResponseError 5 | import urllib 6 | import re 7 | import base64 8 | import rsa 9 | import binascii 10 | 11 | 12 | class Client(ClientBase): 13 | JS_CLIENT = 'ssologin.js(v1.4.11)' 14 | LOGIN_URL = 'http://login.sina.com.cn/sso/login.php' 15 | PRE_LOGIN_URL = 'http://login.sina.com.cn/sso/prelogin.php' 16 | URL_REGEX = re.compile(r"replace\(['\"]+(.*)['\"]+\)") 17 | 18 | @staticmethod 19 | def encrypt_password(password, pre_data): 20 | public_key = int(pre_data.pubkey, 16) 21 | key = rsa.PublicKey(public_key, 65537) 22 | message = str(pre_data.servertime) + '\t' + str(pre_data.nonce) + '\n' + str(password) 23 | return binascii.b2a_hex(rsa.encrypt(message, key)) 24 | 25 | def pre_login(self, su): 26 | params = { 27 | 'client': self.JS_CLIENT, 28 | 'entry': 'sso', 29 | 'callback': 'sinaSSOController.preloginCallBack', 30 | 'su': su, 31 | 'rsakt': 'mod', 32 | # '_': time.time(), 33 | } 34 | r = self._session.get(self.PRE_LOGIN_URL, params=params) 35 | return r.jsonp_dict() 36 | 37 | def login(self, username, password): 38 | su = base64.b64encode(urllib.quote(username)) 39 | pre_data = self.pre_login(su) 40 | sp = self.encrypt_password(password, pre_data) 41 | data = { 42 | 'client': self.JS_CLIENT, 43 | 'entry': 'weibo', 44 | 'gateway': '1', 45 | 'from': '', 46 | 'savestate': '0', 47 | 'userticket': '1', 48 | 'ssosimplelogin': '1', 49 | 'vsnf': '1', 50 | 'vsnval': '', 51 | 'su': su, 52 | 'service': 'miniblog', 53 | 'servertime': pre_data.servertime, 54 | 'nonce': pre_data.nonce, 55 | 'pwencode': 'rsa2', 56 | 'prelt': '164', 57 | 'sp': sp, 58 | 'encoding': 'UTF-8', 59 | 'url': 'http://weibo.com/ajaxlogin.php?framelogin=1&callback=parent.sinaSSOController.feedBackUrlCallBack', 60 | 'returntype': 'META', 61 | 'rsakv': pre_data.rsakv, 62 | } 63 | response = self._session.post(self.LOGIN_URL, data=data) 64 | url = self.URL_REGEX.findall(response.text)[0] 65 | query = parse_querystring(url) 66 | if query['retcode'] != '0': 67 | raise ApiResponseError(response, query['retcode'], query['reason']) 68 | return self._session.get(url) 69 | 70 | -------------------------------------------------------------------------------- /chinaapi/sohu/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | -------------------------------------------------------------------------------- /chinaapi/sohu/web.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from chinaapi.web import ClientBase 3 | from chinaapi.exceptions import ApiResponseError 4 | from hashlib import md5 5 | import time 6 | 7 | ERROR = { 8 | 'error3': u'用户名或密码错误', 9 | } 10 | 11 | 12 | class Client(ClientBase): 13 | def login(self, username, password): 14 | password = md5(password).hexdigest().lower() 15 | data = dict(userid=username, 16 | password=password, 17 | appid=9999, 18 | persistentcookie=1, 19 | s=time.time(), 20 | b=7, 21 | w=1440, 22 | pwdtype=1, 23 | v=26, 24 | ) 25 | r = self._session.post('https://passport.sohu.com/sso/login.jsp', data=data) 26 | if 'success' not in r.content: 27 | raise ApiResponseError(r, 0, r.content.replace('\n', '')) 28 | return r 29 | 30 | -------------------------------------------------------------------------------- /chinaapi/taobao/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | -------------------------------------------------------------------------------- /chinaapi/taobao/open.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import base64 3 | import hmac 4 | from hashlib import md5 5 | from datetime import datetime 6 | from urllib import unquote 7 | from chinaapi.open import ClientBase, OAuthBase, OAuth2Base, App, Token 8 | from chinaapi.exceptions import ApiResponseError 9 | from chinaapi.utils import parse_querystring 10 | 11 | DEFAULT_VALUE_TO_STR = lambda x: str(x) 12 | VALUE_TO_STR = { 13 | type(datetime.now()): lambda v: v.strftime('%Y-%m-%d %H:%M:%S'), 14 | type(u'a'): lambda v: v.encode('utf-8'), 15 | type(0.1): lambda v: "%.2f" % v, 16 | type(True): lambda v: str(v).lower(), 17 | } 18 | RETRY_SUB_CODES = ( 19 | 'isp.top-remote-connection-timeout', 20 | 'isp.top-remote-connection-timeout-tmall', 21 | 'isp.top-remote-service-unavailable', 22 | 'isp.top-remote-service-unavailable-tmall', 23 | 'isp.top-remote-unknown-error', 24 | 'isp.top-remote-unknown-error-tmall', 25 | 'isp.remote-connection-error', 26 | 'isp.remote-connection-error-tmall', 27 | 'isp.item-update-service-error:GENERIC_FAILURE', 28 | 'isp.item-update-service-error:IC_SYSTEM_NOT_READY_TRY_AGAIN_LATER', 29 | 'ism.json-decode-error', 30 | 'ism.demo-error', 31 | ) 32 | 33 | 34 | def join_dict(data): 35 | return ''.join(["%s%s" % (k, v) for k, v in sorted(data.iteritems())]) 36 | 37 | 38 | class Client(ClientBase): 39 | def __init__(self, app=App(), session=None): 40 | super(Client, self).__init__(app, Token(session)) 41 | 42 | def _get_session(self): 43 | return self.token.access_token 44 | 45 | def _set_session(self, session): 46 | self.token.access_token = session 47 | 48 | session = property(_get_session, _set_session) 49 | 50 | def _sign_by_hmac(self, data): 51 | message = join_dict(data) 52 | h = hmac.new(self.app.secret) 53 | h.update(message) 54 | return h.hexdigest().upper() 55 | 56 | def _prepare_url(self, segments, queries): 57 | if segments[0] != 'taobao': 58 | segments.insert(0, 'taobao') 59 | queries['method'] = '.'.join(segments) 60 | return 'http://gw.api.taobao.com/router/rest' 61 | 62 | def _prepare_queries(self, queries): 63 | if not self.token.is_expires: 64 | queries['session'] = self.token.access_token 65 | queries.update({'app_key': self.app.key, 'sign_method': 'hmac', 'format': 'json', 'v': '2.0', 66 | 'timestamp': datetime.now()}) 67 | 68 | def _prepare_body(self, queries): 69 | """ 70 | Return encoded data and files 71 | """ 72 | data, files = {}, {} 73 | for k, v in queries.items(): 74 | kk = k.replace('__', '.') 75 | if hasattr(v, 'read'): 76 | files[kk] = v 77 | elif v is not None: 78 | data[kk] = VALUE_TO_STR.get(type(v), DEFAULT_VALUE_TO_STR)(v) 79 | data['sign'] = self._sign_by_hmac(data) 80 | return data, files 81 | 82 | def _parse_response(self, response): 83 | r = response.json_dict() 84 | if 'error_response' in r: 85 | error = r.error_response 86 | raise ApiResponseError(response, error.get('code', ''), error.get('msg', ''), 87 | error.get('sub_code', ''), error.get('sub_msg', '')) 88 | else: 89 | keys = r.keys() 90 | if keys and keys[0].endswith('_response'): 91 | return r.get(keys[0]) 92 | 93 | def _is_retry_error(self, e): 94 | return e.sub_code in RETRY_SUB_CODES 95 | 96 | 97 | def parse(response): 98 | r = response.json_dict() 99 | if 'error' in r: 100 | raise ApiResponseError(response, r.error, r.get('error_description', '')) 101 | return r 102 | 103 | 104 | class OAuth2(OAuth2Base): 105 | AUTH_URL = 'https://oauth.taobao.com/authorize' 106 | TOKEN_URL = 'https://oauth.taobao.com/token' 107 | 108 | def _parse_token(self, response): 109 | data = parse(response) 110 | return Token(**data) 111 | 112 | def logoff(self, view='web'): 113 | """ 退出登录帐号,目前只支持web访问,起到的作用是清除taobao.com的cookie,并不是取消用户的授权。在WAP上访问无效。 114 | 返回:用于退出登录的链接 115 | """ 116 | return 'https://oauth.taobao.com/logoff?client_id={0}&view={1}'.format(self.app.key, view) 117 | 118 | 119 | class OAuth(OAuthBase): 120 | """ 121 | 基于TOP协议的登录授权方式 122 | """ 123 | URL = 'http://container.open.taobao.com/container' 124 | 125 | def _sign_by_md5(self, data): 126 | message = join_dict(data) + self.app.secret 127 | return md5(message).hexdigest().upper() 128 | 129 | def authorize(self): 130 | return self.URL + '?encode=utf-8&appkey={0}'.format(self.app.key) 131 | 132 | def refresh_token(self, refresh_token, top_session): 133 | params = dict(appkey=self.app.key, refresh_token=refresh_token, sessionkey=top_session) 134 | params['sign'] = self._sign_by_md5(params) 135 | response = self._session.get(self.URL + '/refresh', params=params) 136 | return parse(response) 137 | 138 | def validate_sign(self, top_parameters, top_sign, top_session): 139 | """ 验证签名是否正确(用于淘宝帐号授权)(已测试成功,不要更改) 140 | """ 141 | top_sign = unquote(top_sign) 142 | top_parameters = unquote(top_parameters) 143 | sign = base64.b64encode(md5(self.app.key + top_parameters + top_session + self.app.secret).digest()) 144 | return top_sign == sign 145 | 146 | @staticmethod 147 | def decode_parameters(top_parameters): 148 | """ 将top_parameters字符串解码并转换为字典,(已测试成功,不要更改) 149 | """ 150 | parameters = base64.decodestring(unquote(top_parameters)) 151 | return parse_querystring(parameters) 152 | -------------------------------------------------------------------------------- /chinaapi/utils.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from urlparse import urlparse 3 | from requests import PreparedRequest 4 | 5 | 6 | def parse_querystring(string): 7 | if '?' in string: 8 | string = urlparse(string).query 9 | return dict([item.split('=', 1) for item in string.split('&')]) 10 | 11 | 12 | def request_url(url, params): 13 | pre = PreparedRequest() 14 | pre.prepare_url(url, params) 15 | return pre.url -------------------------------------------------------------------------------- /chinaapi/wap.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from .request import Request 3 | 4 | 5 | class ClientBase(Request): 6 | def __init__(self): 7 | super(ClientBase, self).__init__() 8 | -------------------------------------------------------------------------------- /chinaapi/web.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from .request import Request 3 | 4 | 5 | class ClientBase(Request): 6 | def __init__(self): 7 | super(ClientBase, self).__init__() 8 | self._session.headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36' -------------------------------------------------------------------------------- /images/pic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smallcode/ChinaAPI/566807ecd2331fe57196833685a3e500b9250e39/images/pic.jpg -------------------------------------------------------------------------------- /requirements-test.txt: -------------------------------------------------------------------------------- 1 | httpretty>=0.8.3 2 | vcrpy==0.6.0 3 | fake-factory -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | rsa 2 | requests -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import chinaapi 3 | 4 | 5 | try: 6 | from setuptools import setup 7 | except ImportError: 8 | from distutils.core import setup 9 | 10 | packages = [ 11 | 'chinaapi', 12 | 'chinaapi.douban', 13 | 'chinaapi.qq', 14 | 'chinaapi.qq.weibo', 15 | 'chinaapi.renren', 16 | 'chinaapi.sina', 17 | 'chinaapi.sina.weibo', 18 | 'chinaapi.taobao', 19 | 'chinaapi.sohu', 20 | 'chinaapi.netease', 21 | ] 22 | 23 | install_requires = [ 24 | 'requests >= 2.1.0', 25 | 'rsa >= 3.1.2', 26 | ] 27 | 28 | tests_require = [ 29 | 'httpretty >= 0.8.3', 30 | 'vcrpy >= 0.6.0', 31 | 'fake-factory', 32 | ] 33 | 34 | with open('README.rst') as f: 35 | readme = f.read() 36 | with open('HISTORY.rst') as f: 37 | history = f.read() 38 | 39 | setup( 40 | name='chinaapi', 41 | version=chinaapi.__version__, 42 | description='Python SDK For China API: Sina Weibo, QQ Weibo, Taobao, Renren, Douban', 43 | long_description=readme + '\n\n' + history, 44 | author='smallcode', 45 | author_email='45945756@qq.com', 46 | url='https://github.com/smallcode/ChinaAPI', 47 | packages=packages, 48 | package_dir={'chinaapi': 'chinaapi'}, 49 | include_package_data=True, 50 | install_requires=install_requires, 51 | tests_require=tests_require, 52 | test_suite="tests.get_tests", 53 | license=chinaapi.__license__, 54 | zip_safe=False, 55 | classifiers=[ 56 | 'Development Status :: 5 - Production/Stable', 57 | 'Intended Audience :: Developers', 58 | 'License :: OSI Approved :: Apache Software License', 59 | 'Programming Language :: Python', 60 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 61 | 'Topic :: Software Development :: Libraries :: Python Modules' 62 | ], 63 | ) 64 | 65 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import os.path 3 | import unittest 4 | 5 | 6 | def get_tests(): 7 | start_dir = os.path.dirname(__file__) 8 | return unittest.TestLoader().discover(start_dir, pattern="*.py") -------------------------------------------------------------------------------- /tests/decorator.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from functools import wraps 3 | from vcr import VCR 4 | import os 5 | 6 | 7 | def use_cassette(vcr=VCR(), path='fixtures/vcr_cassettes/', **kwargs): 8 | """ 9 | Usage: 10 | @use_cassette() # the default path will be fixtures/vcr_cassettes/foo.yaml 11 | def foo(self): 12 | ... 13 | 14 | @use_cassette(path='fixtures/vcr_cassettes/synopsis.yaml', record_mode='one') 15 | def foo(self): 16 | ... 17 | """ 18 | 19 | def decorator(func): 20 | @wraps(func) 21 | def inner_func(*args, **kw): 22 | if os.path.isabs(path): 23 | file_path = path 24 | else: 25 | fun_path, fun_filename = os.path.split(func.func_code.co_filename) 26 | file_path = os.path.join(fun_path, path, os.path.splitext(fun_filename)[0]) 27 | 28 | if not os.path.splitext(file_path)[1]: 29 | serializer = kwargs.get('serializer', vcr.serializer) 30 | file_path = os.path.join(file_path, '{0}.{1}'.format(func.func_name.lower(), serializer)) 31 | 32 | with vcr.use_cassette(file_path, **kwargs): 33 | return func(*args, **kw) 34 | 35 | return inner_func 36 | 37 | return decorator -------------------------------------------------------------------------------- /tests/test_decorators.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from unittest import TestCase 3 | from chinaapi.decorators import retry 4 | 5 | 6 | class DecoratorsTest(TestCase): 7 | n = 0 8 | 9 | @staticmethod 10 | def handle_error(exception): 11 | print exception 12 | 13 | def test_retry(self): 14 | @retry(3, hook=self.handle_error) 15 | def retry_me(): 16 | self.n += 1 17 | raise Exception(u'exception in try %s' % self.n) 18 | 19 | self.assertRaises(Exception, retry_me) 20 | self.assertEqual(self.n, 3) 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/test_exceptions.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from unittest import TestCase 3 | from chinaapi.exceptions import ApiError 4 | 5 | 6 | class ExceptionTest(TestCase): 7 | def test_api_error(self): 8 | error = ApiError('http://request_url', 404, 'Request Api not found!') 9 | self.assertEqual('[404]: Request Api not found!, request: http://request_url', str(error)) 10 | 11 | def test_api_error_with_sub(self): 12 | error = ApiError('http://request_url', 404, 'Request Api not found!', 1000, 'sub error msg') 13 | self.assertEqual('[404]: Request Api not found!, [1000]: sub error msg, request: http://request_url', 14 | str(error)) 15 | -------------------------------------------------------------------------------- /tests/test_json_dict.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from unittest import TestCase 3 | from chinaapi.jsonDict import JsonDict, loads 4 | 5 | 6 | class JsonDictTest(TestCase): 7 | def test_loads(self): 8 | json = loads(r'{"name":"foo","age":100}') 9 | self.assertEqual('foo', json.name) 10 | self.assertEqual(100, json.age) 11 | 12 | def test_JsonDict(self): 13 | json = JsonDict(name="foo", age=100) 14 | self.assertEqual('foo', json.name) 15 | self.assertEqual(100, json.age) 16 | json.age = 99 17 | self.assertEqual(99, json.age) 18 | 19 | def test_AttributeError(self): 20 | json = JsonDict(name="foo", age=100) 21 | with self.assertRaises(AttributeError): 22 | _ = json.id 23 | -------------------------------------------------------------------------------- /tests/test_open.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from unittest import TestCase 3 | import httpretty 4 | from chinaapi.open import ClientBase, OAuth2Base, Method, Token, App 5 | from chinaapi.exceptions import MissingRedirectUri, ApiError 6 | from test_request import BASE_URL, TestBase 7 | 8 | 9 | class ApiClient(ClientBase): 10 | def _prepare_url(self, segments, queries): 11 | return BASE_URL + '/'.join(segments) 12 | 13 | def _prepare_method(self, segments): 14 | if segments[-1] == 'get': 15 | return Method.GET 16 | return super(ApiClient, self)._prepare_method(segments) 17 | 18 | def _parse_response(self, response): 19 | data = response.json_dict() 20 | if 'code' in data: 21 | raise ApiError(response, data.code, data.message) 22 | return super(ApiClient, self)._parse_response(response) 23 | 24 | def _is_retry_error(self, e): 25 | return super(ApiClient, self)._is_retry_error(e) or e.code == 10001 26 | 27 | 28 | class NotImplementedClient(ClientBase): 29 | pass 30 | 31 | 32 | class ApiOAuth2(OAuth2Base): 33 | AUTH_URL = 'http://test/oauth2/authorize' 34 | TOKEN_URL = 'http://test/oauth2/access_token' 35 | 36 | 37 | class RequestBase(TestBase): 38 | def setUp(self): 39 | self.app = App('key', 'secret', 'http://redirect_uri') 40 | 41 | 42 | class ClientTest(RequestBase): 43 | GET_URL = BASE_URL + 'get' 44 | POST_URL = BASE_URL + 'post' 45 | ERROR_URL = BASE_URL + 'error' 46 | 47 | def setUp(self): 48 | super(ClientTest, self).setUp() 49 | self.client = ApiClient(self.app) 50 | self.client.set_access_token('access_token', 60 * 60) 51 | 52 | def register_get_uri(self): 53 | httpretty.register_uri(httpretty.GET, self.GET_URL, body=self.JSON_BODY, content_type=self.CONTENT_TYPE) 54 | 55 | def register_post_uri(self): 56 | httpretty.register_uri(httpretty.POST, self.POST_URL, body=self.JSON_BODY, content_type=self.CONTENT_TYPE) 57 | 58 | def register_error_uri(self): 59 | body = '{"code": 10001, "message": "system error"}' 60 | httpretty.register_uri(httpretty.POST, self.ERROR_URL, body=body, content_type=self.CONTENT_TYPE) 61 | 62 | def assertPost(self, response): 63 | self.assertEqual(self.POST_URL, response.url) 64 | self.assertEqual(self.JSON_BODY, response.text) 65 | self.assertEqual(Method.POST, response.request.method) 66 | 67 | @httpretty.activate 68 | def test_get(self): 69 | self.register_get_uri() 70 | r = self.client.get() 71 | self.assertEqual(self.GET_URL, r.url) 72 | self.assertEqual(self.JSON_BODY, r.text) 73 | self.assertEqual(Method.GET, r.request.method) 74 | 75 | @httpretty.activate 76 | def test_post(self): 77 | self.register_post_uri() 78 | response = self.client.post(id=123, name='name') 79 | self.assertPost(response) 80 | self.assertEqual('name=name&id=123', response.request.body) 81 | 82 | @httpretty.activate 83 | def test_post_with_pic(self): 84 | self.register_post_uri() 85 | with open('images/pic.jpg', 'rb') as pic: 86 | response = self.client.post(pic=pic, id=123) 87 | self.assertPost(response) 88 | self.assertTrue('multipart/form-data' in response.request.headers['Content-Type']) 89 | 90 | @httpretty.activate 91 | def test_retry(self): 92 | self.register_error_uri() 93 | with self.assertRaises(ApiError): 94 | with open('images/pic.jpg', 'rb') as pic: 95 | self.client.error(img=pic) 96 | 97 | 98 | class NotImplementedClientTest(RequestBase): 99 | def setUp(self): 100 | super(NotImplementedClientTest, self).setUp() 101 | self.client = NotImplementedClient(self.app) 102 | 103 | def test_not_implemented_error(self): 104 | with self.assertRaises(NotImplementedError): 105 | self.client.not_implemented_error.get() 106 | 107 | 108 | class OAuth2Test(RequestBase): 109 | def setUp(self): 110 | super(OAuth2Test, self).setUp() 111 | self.oauth2 = ApiOAuth2(self.app) 112 | self.oauth_url = 'http://test/oauth2/authorize?redirect_uri=http%3A%2F%2Fredirect_uri&response_type=code&client_id=key' 113 | 114 | def _register_access_token_uri(self): 115 | self._register_response(self.JSON_BODY) 116 | 117 | def test_authorize(self): 118 | url = self.oauth2.authorize() 119 | self.assertEqual(self.oauth_url, url) 120 | 121 | def test_authorize_with_empty_redirect_uri(self): 122 | with self.assertRaises(MissingRedirectUri): 123 | self.oauth2.authorize(redirect_uri='') 124 | 125 | @httpretty.activate 126 | def test_access_token(self): 127 | self._register_access_token_uri() 128 | token = self.oauth2.access_token(code='code') 129 | self.assertToken(token) 130 | 131 | def test_access_token_with_empty_redirect_uri(self): 132 | with self.assertRaises(MissingRedirectUri): 133 | self.oauth2.access_token(code='code', redirect_uri='') 134 | 135 | @httpretty.activate 136 | def test_refresh_token(self): 137 | self._register_access_token_uri() 138 | token = self.oauth2.refresh_token('refresh_token') 139 | self.assertToken(token) 140 | 141 | @httpretty.activate 142 | def test_password(self): 143 | self._register_access_token_uri() 144 | token = self.oauth2.access_token(username='username', password='password') 145 | self.assertToken(token) 146 | 147 | @httpretty.activate 148 | def test_credentials(self): 149 | self._register_access_token_uri() 150 | token = self.oauth2.access_token() 151 | self.assertToken(token) 152 | 153 | 154 | class TokenTest(TestCase): 155 | def test_access_token(self): 156 | token = Token('token_string', 60 * 60) 157 | self.assertFalse(token.is_expires) 158 | 159 | def test_access_token_without_expired_at(self): 160 | token = Token('token_string') 161 | self.assertFalse(token.is_expires) 162 | 163 | def test_empty_access_token(self): 164 | token = Token('') 165 | self.assertTrue(token.is_expires) 166 | 167 | def test_expired_access_token(self): 168 | token = Token('token_string', - 60 * 60) 169 | self.assertTrue(token.is_expires) 170 | 171 | def test_set_expires_in(self): 172 | expires_in = 60 * 60 173 | token = Token('token_string', expires_in) 174 | self.assertAlmostEqual(expires_in, token.expires_in, -1) 175 | 176 | def test_get_attr(self): 177 | uid = 123 178 | token = Token(uid=uid) 179 | self.assertEqual(uid, token.uid) 180 | 181 | def test_get_not_exist_attr(self): 182 | token = Token() 183 | with self.assertRaises(AttributeError): 184 | _ = token.not_exist_attr -------------------------------------------------------------------------------- /tests/test_qq_weibo.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from unittest import TestCase 3 | from chinaapi.qq.weibo.open import Client, App 4 | from chinaapi.exceptions import ApiError 5 | 6 | 7 | class QqWeiboTest(TestCase): 8 | """ 9 | 注释部分的测试需填写app_key,app_secret,access_token 10 | """ 11 | 12 | def setUp(self): 13 | app = App('app_key', 'app_secret') # 填上自己的app_key,app_secret 14 | self.openid = 'openid' # 填上取得的openid 15 | self.client = Client(app) 16 | self.client.set_access_token('access_token') # 填上取得的access_token 17 | self.client.openid = self.openid 18 | 19 | # def test_user_info(self): 20 | # r = self.client.user.info() 21 | # self.assertEqual(self.openid, r.openid) 22 | # 23 | # def test_t_upload_pic(self): 24 | # with open('images/pic.jpg', 'rb') as pic: 25 | # r = self.client.t.upload_pic(pic=pic, pic_type=2, clientip='220.181.111.85') # clientip必填 26 | # self.assertIsNotNone(r.imgurl) 27 | 28 | def test_api_error(self): 29 | self.client.openid = '' 30 | with self.assertRaises(ApiError) as cm: 31 | self.client.user.info() 32 | self.assertEqual('missing parameter', cm.exception.sub_message) 33 | 34 | -------------------------------------------------------------------------------- /tests/test_renren.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from unittest import TestCase 3 | from chinaapi.renren.open import Client, App 4 | from chinaapi.exceptions import ApiError 5 | 6 | 7 | class RenRenTest(TestCase): 8 | """ 9 | 注释部分的测试需填写app_key,app_secret,access_token 10 | """ 11 | 12 | def setUp(self): 13 | app = App('app_key', 'app_secret') # 填上自己的app_key,app_secret 14 | self.client = Client(app) 15 | self.client.set_access_token('access_token') # 填上取得的access_token 16 | self.uid = 334258249 17 | 18 | # def test_users_get(self): 19 | # r = self.client.user.get(userId=self.uid) 20 | # self.assertEqual(self.uid, r.id) 21 | 22 | def test_api_error(self): 23 | self.client.token.access_token = '' 24 | with self.assertRaises(ApiError) as cm: 25 | self.client.user.get(userId=self.uid) 26 | self.assertEqual(u'验证参数错误。', cm.exception.message) 27 | -------------------------------------------------------------------------------- /tests/test_request.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from unittest import TestCase 3 | from chinaapi.request import Request 4 | from chinaapi.exceptions import ApiResponseError 5 | import httpretty 6 | 7 | 8 | BASE_URL = 'http://test/' 9 | 10 | 11 | class TestBase(TestCase): 12 | URL = BASE_URL + 'oauth2/access_token' 13 | JSON_BODY = '{"access_token":"access_token","expires_in":60, "refresh_token":"refresh_token","uid":"uid"}' 14 | CONTENT_TYPE = 'text/json' 15 | 16 | def assertToken(self, token): 17 | self.assertEqual('access_token', token.access_token) 18 | self.assertEqual(60, token.expires_in) 19 | self.assertEqual('refresh_token', token.refresh_token) 20 | self.assertEqual('uid', token.uid) 21 | 22 | def _register_response(self, body='', status=200): 23 | httpretty.register_uri(httpretty.POST, self.URL, body, status=status, content_type=self.CONTENT_TYPE) 24 | 25 | 26 | class RequestTest(TestBase): 27 | def setUp(self): 28 | self.request = Request() 29 | self.session = self.request._session 30 | 31 | @httpretty.activate 32 | def test_json_dict(self): 33 | self._register_response(self.JSON_BODY) 34 | response = self.session.post(self.URL) 35 | self.assertToken(response.json_dict()) 36 | 37 | @httpretty.activate 38 | def test_jsonp_dict(self): 39 | self._register_response("jsonp(%s)" % self.JSON_BODY) 40 | response = self.session.post(self.URL) 41 | self.assertToken(response.jsonp_dict()) 42 | 43 | @httpretty.activate 44 | def test_ApiResponseValueError(self): 45 | self._register_response('not json') 46 | with self.assertRaises(ApiResponseError): 47 | response = self.session.post(self.URL) 48 | response.json_dict() 49 | 50 | @httpretty.activate 51 | def test_NotExistApi(self): 52 | self._register_response(status=404) 53 | with self.assertRaises(ApiResponseError): 54 | response = self.session.post(self.URL) 55 | response.json_dict() 56 | 57 | @httpretty.activate 58 | def test_HTTPError(self): 59 | self._register_response(status=500) 60 | with self.assertRaises(ApiResponseError): 61 | response = self.session.post(self.URL) 62 | response.json_dict() 63 | -------------------------------------------------------------------------------- /tests/test_sina_weibo.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from unittest import TestCase 3 | from chinaapi.sina.weibo.open import Client 4 | from chinaapi.exceptions import ApiError 5 | 6 | 7 | class SinaWeiboTest(TestCase): 8 | """ 9 | 注释部分的测试需填写access_token 10 | """ 11 | 12 | def setUp(self): 13 | self.client = Client() 14 | self.client.set_access_token('access_token') # 填上取得的access_token 15 | self.uid = 3856184660 16 | 17 | # def test_users_show(self): 18 | # r = self.client.users.show(uid=self.uid) 19 | # self.assertEqual(self.uid, r.id) 20 | # 21 | # def test_statuses_upload(self): 22 | # with open('images/pic.jpg', 'rb') as pic: 23 | # r = self.client.statuses.upload(status=u'发布的内容', pic=pic) 24 | # self.assertIsNotNone(r.id) 25 | # self.assertEqual(self.uid, r.user.id) 26 | # self.client.statuses.destroy(id=r.id) 27 | 28 | def test_not_exist_api(self): 29 | with self.assertRaises(ApiError) as cm: 30 | self.client.not_exist_api.get() 31 | self.assertEqual('Request Api not found!', cm.exception.message) 32 | 33 | def test_is_retry_error(self): 34 | self.assertTrue(self.client._is_retry_error(ApiError('', 10001, ''))) 35 | self.assertFalse(self.client._is_retry_error(ApiError('', 10002, ''))) 36 | -------------------------------------------------------------------------------- /tests/test_taobao.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from unittest import TestCase 3 | from chinaapi.taobao.open import Client, App 4 | from chinaapi.exceptions import ApiError 5 | 6 | 7 | class TaobaoTest(TestCase): 8 | """ 9 | # 使用这里公开的app: 10 | https://github.com/jimboybo/itaobox/blob/083e66bdce899ff8b9ea8be5fd9280529c4ee216/u/system/config/app_config.php 11 | 注意:不同的app会有不同的权限,所以测试如果不成功,有可能是权限不足(详细原因可以查看返回的ApiError错误信息) 12 | """ 13 | 14 | def setUp(self): 15 | self.app = App('21532233', '1d5f36785a0bfb84952a69c5dd3203fd') 16 | self.client = Client(self.app) 17 | 18 | def test_session(self): 19 | self.client = Client(self.app, 'session') 20 | self.assertEqual(self.client.session, 'session') 21 | self.client.session = 'session2' 22 | self.assertEqual(self.client.session, 'session2') 23 | 24 | def test_is_retry_error(self): 25 | self.assertTrue(self.client._is_retry_error(ApiError('', 0, '', 'ism.demo-error'))) 26 | self.assertFalse(self.client._is_retry_error(ApiError('', 0, '', 'other_error'))) 27 | 28 | def test_item_cats_get(self): 29 | r = self.client.itemcats.get(cids=14) 30 | self.assertEqual(14, r.item_cats.item_cat[0].cid) 31 | 32 | def test_shop_cats_list_get(self): 33 | r = self.client.shopcats.list.get(cids=14) 34 | self.assertEqual(68, len(r.shop_cats.shop_cat)) 35 | 36 | def test_not_exist_api(self): 37 | with self.assertRaises(ApiError) as cm: 38 | self.client.not_exist_api.get() 39 | self.assertEqual('Invalid method', cm.exception.message) -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from unittest import TestCase 3 | from chinaapi.utils import parse_querystring 4 | 5 | 6 | class UtilsTest(TestCase): 7 | def test_parse_querystring(self): 8 | r = parse_querystring("http://test.com?foo=bar&n=1") 9 | self.assertEqual(2, len(r)) 10 | self.assertEqual(r['foo'], 'bar') 11 | self.assertEqual( r['n'], '1') -------------------------------------------------------------------------------- /tests/test_wap.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from unittest import TestCase 3 | from chinaapi.wap import ClientBase 4 | 5 | 6 | class ApiClient(ClientBase): 7 | def __init__(self): 8 | super(ApiClient, self).__init__() 9 | 10 | 11 | class ClientTest(TestCase): 12 | def test_init(self): 13 | client = ApiClient() 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/test_web.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from unittest import TestCase 3 | from chinaapi.web import ClientBase 4 | 5 | 6 | class ApiClient(ClientBase): 7 | def __init__(self): 8 | super(ApiClient, self).__init__() 9 | 10 | 11 | class ClientTest(TestCase): 12 | def test_init(self): 13 | client = ApiClient() 14 | 15 | 16 | --------------------------------------------------------------------------------