Skip to content

Commit d1c96c2

Browse files
committed
7.0
1 parent 2f418dc commit d1c96c2

20 files changed

+1262
-0
lines changed

CHANGELOG.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#Changelog
2+
3+
4+
## 7.0.0 (2014-11-13)
5+
6+
### 增加
7+
* 简化上传接口
8+
* 自动选择断点续上传还是直传
9+
* 重构代码,内部结构更清晰,便于更换不同的http实现
10+
* 同时支持python 2.x 和 3.x
11+
* 增加pfop支持

CONTRIBUTING.md

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# 贡献代码指南
2+
3+
我们非常欢迎大家来贡献代码,我们会向贡献者致以最诚挚的敬意。
4+
5+
一般可以通过在Github上提交[Pull Request](https://github.com/qiniu/python-sdk)来贡献代码。
6+
7+
## Pull Request要求
8+
9+
- **代码规范** 遵从pep8,pythonic。
10+
11+
- **代码格式** 提交前 请按 pep8 进行格式化。
12+
13+
- **必须添加测试!** - 如果没有测试(单元测试、集成测试都可以),那么提交的补丁是不会通过的。
14+
15+
- **记得更新文档** - 保证`README.md`以及其他相关文档及时更新,和代码的变更保持一致性。
16+
17+
- **考虑我们的发布周期** - 我们的版本号会服从[SemVer v2.0.0](http://semver.org/),我们绝对不会随意变更对外的API。
18+
19+
- **创建feature分支** - 最好不要从你的master分支提交 pull request。
20+
21+
- **一个feature提交一个pull请求** - 如果你的代码变更了多个操作,那就提交多个pull请求吧。
22+
23+
- **清晰的commit历史** - 保证你的pull请求的每次commit操作都是有意义的。如果你开发中需要执行多次的即时commit操作,那么请把它们放到一起再提交pull请求。
24+
25+
## 运行测试
26+
27+
``` bash
28+
py.test
29+
30+
```

LICENSE

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2014 Qiniu, Ltd.<[email protected]>
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
22+

README.md

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Qiniu Python SDK
2+
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.md)
3+
[![Build Status](https://travis-ci.org/qiniu/python-sdk.svg)](https://travis-ci.org/qiniu/python-sdk)
4+
[![Latest Stable Version](https://badge.fury.io/co/Qiniu.png)](https://github.com/qiniu/python-sdk/releases)
5+
6+
## 安装
7+
8+
通过pip
9+
10+
```bash
11+
pip install qiniu
12+
```
13+
14+
## 运行环境
15+
16+
| Qiniu SDK版本 | Python 版本 |
17+
|:--------------------:|:---------------------------:|
18+
| 7.x | 2.6, 2.7, 3.3, 3.4 |
19+
| 6.x | 2.6, 2.7 |
20+
21+
## 使用方法
22+
23+
```python
24+
import qiniu
25+
...
26+
q = qiniu.Auth(access_key, secret_key)
27+
key = 'hello'
28+
data = 'hello qiniu!'
29+
token = q.upload_token(bucket_name)
30+
ret, info = put_data(token, key, data)
31+
if ret is not None:
32+
print('All is OK')
33+
else:
34+
print(info) # error message in info
35+
...
36+
```
37+
38+
## 测试
39+
40+
``` bash
41+
py.test
42+
```
43+
44+
## 常见问题
45+
46+
- 第二个参数info保留了请求响应的信息,失败情况下ret 为none, 将info可以打印出来,提交给我们。
47+
- API 的使用 demo 可以参考 [单元测试](https://github.com/qiniu/python-sdk/blob/master/test_qiniu.py)
48+
49+
## 代码贡献
50+
51+
详情参考[代码提交指南](https://github.com/qiniu/python-sdk/blob/master/CONTRIBUTING.md)
52+
53+
## 贡献记录
54+
55+
- [所有贡献者](https://github.com/qiniu/python-sdk/contributors)
56+
57+
## 联系我们
58+
59+
- 如果需要帮助,请提交工单(在portal右侧点击咨询和建议提交工单,或者直接向 [email protected] 发送邮件)
60+
- 如果有什么问题,可以到问答社区提问,[问答社区](http://qiniu.segmentfault.com/)
61+
- 更详细的文档,见[官方文档站](http://developer.qiniu.com/)
62+
- 如果发现了bug, 欢迎提交 [issue](https://github.com/qiniu/python-sdk/issues)
63+
- 如果有功能需求,欢迎提交 [issue](https://github.com/qiniu/python-sdk/issues)
64+
- 如果要提交代码,欢迎提交 pull request
65+
- 欢迎关注我们的[微信](http://www.qiniu.com/#weixin) [微博](http://weibo.com/qiniutek),及时获取动态信息。
66+
67+
## 代码许可
68+
69+
The MIT License (MIT).详情见 [License文件](https://github.com/qiniu/python-sdk/blob/master/LICENSE).

qiniu/__init__.py

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# -*- coding: utf-8 -*-
2+
'''
3+
Qiniu Resource Storage SDK for Python
4+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5+
6+
For detailed document, please see:
7+
<http://developer.qiniu.com>
8+
'''
9+
10+
# flake8: noqa
11+
12+
__version__ = '7.0.0'
13+
14+
from .auth import Auth
15+
16+
from .config import set_default
17+
18+
from .services.storage.bucket import BucketManager, build_batch_copy, build_batch_rename, build_batch_move, build_batch_stat, build_batch_delete
19+
from .services.storage.uploader import put_data, put_file, put_stream
20+
from .services.processing.pfop import PersistentFop
21+
from .services.processing.cmd import build_op, pipe_cmd, op_save
22+
23+
from .utils import urlsafe_base64_encode, urlsafe_base64_decode, etag, entry

qiniu/auth.py

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import hmac
4+
import time
5+
from hashlib import sha1
6+
7+
from requests.auth import AuthBase
8+
9+
from .compat import urlparse, json, b
10+
from .utils import urlsafe_base64_encode
11+
12+
13+
_policy_fields = set([
14+
'callbackUrl',
15+
'callbackBody',
16+
'callbackHost',
17+
18+
'returnUrl',
19+
'returnBody',
20+
21+
'endUser',
22+
'saveKey',
23+
'insertOnly',
24+
25+
'detectMime',
26+
'mimeLimit',
27+
'fsizeLimit',
28+
29+
'persistentOps',
30+
'persistentNotifyUrl',
31+
'persistentPipeline',
32+
])
33+
34+
_deprecated_policy_fields = set([
35+
'asyncOps'
36+
])
37+
38+
39+
class Auth(object):
40+
41+
def __init__(self, access_key, secret_key):
42+
self.__checkKey(access_key, secret_key)
43+
self.__access_key, self.__secret_key = access_key, secret_key
44+
self.__secret_key = b(self.__secret_key)
45+
46+
def __token(self, data):
47+
data = b(data)
48+
hashed = hmac.new(self.__secret_key, data, sha1)
49+
return urlsafe_base64_encode(hashed.digest())
50+
51+
def token(self, data):
52+
return '{0}:{1}'.format(self.__access_key, self.__token(data))
53+
54+
def token_with_data(self, data):
55+
data = urlsafe_base64_encode(data)
56+
return '{0}:{1}:{2}'.format(self.__access_key, self.__token(data), data)
57+
58+
def token_of_request(self, url, body=None, content_type=None):
59+
parsed_url = urlparse(url)
60+
query = parsed_url.query
61+
path = parsed_url.path
62+
data = path
63+
if query != '':
64+
data = ''.join([data, '?', query])
65+
data = ''.join([data, "\n"])
66+
67+
if body:
68+
mimes = [
69+
'application/x-www-form-urlencoded',
70+
]
71+
if content_type in mimes:
72+
data += body
73+
74+
return '{0}:{1}'.format(self.__access_key, self.__token(data))
75+
76+
@staticmethod
77+
def __checkKey(access_key, secret_key):
78+
if not (access_key and secret_key):
79+
raise ValueError('invalid key')
80+
81+
def private_download_url(self, url, expires=3600):
82+
'''
83+
* return private url
84+
'''
85+
86+
deadline = int(time.time()) + expires
87+
if '?' in url:
88+
url += '&'
89+
else:
90+
url += '?'
91+
url = '{0}e={1}'.format(url, str(deadline))
92+
93+
token = self.token(url)
94+
return '{0}&token={1}'.format(url, token)
95+
96+
def upload_token(self, bucket, key=None, expires=3600, policy=None, strict_policy=True):
97+
if bucket is None or bucket == '':
98+
raise ValueError('invalid bucket name')
99+
100+
scope = bucket
101+
if key is not None:
102+
scope = '{0}:{1}'.format(bucket, key)
103+
104+
args = dict(
105+
scope=scope,
106+
deadline=int(time.time()) + expires,
107+
)
108+
109+
if policy is not None:
110+
self.__copy_policy(policy, args, strict_policy)
111+
112+
return self.__upload_token(args)
113+
114+
def __upload_token(self, policy):
115+
data = json.dumps(policy, separators=(',', ':'))
116+
return self.token_with_data(data)
117+
118+
def verify_callback(self, origin_authorization, url, body):
119+
token = self.token_of_request(url, body, 'application/x-www-form-urlencoded')
120+
authorization = 'QBox {0}'.format(token)
121+
return origin_authorization == authorization
122+
123+
@staticmethod
124+
def __copy_policy(policy, to, strict_policy):
125+
for k, v in policy.items():
126+
if k in _deprecated_policy_fields:
127+
raise ValueError(k + ' has deprecated')
128+
if (not strict_policy) or k in _policy_fields:
129+
to[k] = v
130+
131+
132+
class RequestsAuth(AuthBase):
133+
def __init__(self, auth):
134+
self.auth = auth
135+
136+
def __call__(self, r):
137+
token = None
138+
if r.body is not None and r.headers['Content-Type'] == 'application/x-www-form-urlencoded':
139+
token = self.auth.token_of_request(r.url, r.body, 'application/x-www-form-urlencoded')
140+
else:
141+
token = self.auth.token_of_request(r.url)
142+
r.headers['Authorization'] = 'QBox {0}'.format(token)
143+
return r

qiniu/compat.py

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
pythoncompat
5+
"""
6+
7+
import sys
8+
9+
try:
10+
import simplejson as json
11+
except (ImportError, SyntaxError):
12+
# simplejson does not support Python 3.2, it thows a SyntaxError
13+
# because of u'...' Unicode literals.
14+
import json # noqa
15+
16+
17+
# -------
18+
# Pythons
19+
# -------
20+
21+
_ver = sys.version_info
22+
23+
#: Python 2.x?
24+
is_py2 = (_ver[0] == 2)
25+
26+
#: Python 3.x?
27+
is_py3 = (_ver[0] == 3)
28+
29+
30+
# ---------
31+
# Specifics
32+
# ---------
33+
34+
if is_py2:
35+
from urlparse import urlparse # noqa
36+
import StringIO
37+
StringIO = BytesIO = StringIO.StringIO
38+
39+
builtin_str = str
40+
bytes = str
41+
str = unicode # noqa
42+
basestring = basestring # noqa
43+
numeric_types = (int, long, float) # noqa
44+
45+
def b(s):
46+
return s
47+
48+
def s(b):
49+
return b
50+
51+
def u(s):
52+
return unicode(s, 'unicode_escape') # noqa
53+
54+
elif is_py3:
55+
from urllib.parse import urlparse # noqa
56+
import io
57+
StringIO = io.StringIO
58+
BytesIO = io.BytesIO
59+
60+
builtin_str = str
61+
str = str
62+
bytes = bytes
63+
basestring = (str, bytes)
64+
numeric_types = (int, float)
65+
66+
def b(s):
67+
if isinstance(s, str):
68+
return s.encode('utf-8')
69+
return s
70+
71+
def s(b):
72+
if isinstance(b, bytes):
73+
b = b.decode('utf-8')
74+
return b
75+
76+
def u(s):
77+
return s

0 commit comments

Comments
 (0)