Skip to content

Commit ec3e962

Browse files
committed
实现文件上传api
1 parent fb03abb commit ec3e962

File tree

7 files changed

+270
-168
lines changed

7 files changed

+270
-168
lines changed

example/wxapp_cloud_file.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#! -*- coding: utf-8 -*-
2+
from os import environ, path
3+
from pprint import pprint
4+
5+
from weixin import WxAppCloudAPI
6+
7+
appid = environ.get("WXAPP_APPID", "appid")
8+
secret = environ.get("WXAPP_SECRET", "secret")
9+
env = "test-id"
10+
11+
app_cloud = WxAppCloudAPI(
12+
appid=appid, app_secret=secret, grant_type="client_credential"
13+
)
14+
token = app_cloud.client_credential_for_access_token().get("access_token")
15+
16+
cloud_api = WxAppCloudAPI(access_token=token)
17+
18+
path = "test/file.py"
19+
filepath = "./file.py"
20+
resp = cloud_api.upload_file(json_body={"env": env, "path": path, "filepath": filepath})
21+
print(resp)

weixin/client.py

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@
1010
Description: Weixin OAuth2
1111
"""
1212

13+
import requests
14+
import xmltodict
15+
1316
from . import oauth2
14-
from .bind import bind_method
15-
from .helper import genarate_signature
17+
from .bind import bind_method, WeixinAPIError
18+
from .helper import genarate_signature, smart_bytes, smart_str
1619

1720

1821
SUPPORTED_FORMATS = ["", "json"]
@@ -317,13 +320,53 @@ def __init__(self, *args, **kwargs):
317320
response_type="entry",
318321
)
319322

320-
# 上传文件 获取文件上传链接
321-
upload_file = bind_method(
323+
pre_upload_file = bind_method(
322324
path="/tcb/uploadfile",
323325
method="POST",
324326
accepts_parameters=["json_body"],
325327
response_type="entry",
326328
)
329+
# 上传文件 获取文件上传链接
330+
def upload_file(self, json_body=None):
331+
"""
332+
获取文件下载链接
333+
参数:json_body 字典
334+
json_body 结构:
335+
env string 云环境ID
336+
path string 文件云存储的路径(包括文件名),比如: test/file.txt
337+
filepath string 文件本地路径
338+
upload_file(json_body={"env": "envid", "path": "test/file.txt", "filepath": "/home/user/test.txt"})
339+
"""
340+
try:
341+
with open(json_body["filepath"], "rb") as f:
342+
file_data = f.read()
343+
except Exception as e:
344+
raise WeixinAPIError("400", "000000", e)
345+
346+
filepath = json_body.pop("filepath", None)
347+
pre_resp = self.pre_upload_file(json_body=json_body)
348+
if pre_resp.get("errcode") == 0 and pre_resp.get("errmsg") == "ok":
349+
files = {
350+
"key": json_body.get("path"),
351+
"Signature": pre_resp.get("authorization"),
352+
"x-cos-security-token": pre_resp.get("token"),
353+
"x-cos-meta-fileid": pre_resp.get("cos_file_id"),
354+
"file": file_data, # file 一定要放到最后,血泪的教训
355+
}
356+
# encode
357+
params = {smart_str(k): v for k, v in files.items()}
358+
resp = requests.post(pre_resp.get("url"), files=params)
359+
status_code = resp.status_code
360+
if status_code == 204:
361+
return {"errcode": 0, "errmsg": "ok"}
362+
else:
363+
content_obj = xmltodict.parse(resp.content)
364+
results = content_obj.get("Error", {})
365+
raise WeixinAPIError(status_code, results["Code"], results["Message"])
366+
else:
367+
raise WeixinAPIError(
368+
pre_resp.status_code, pre_resp["errcode"], pre_resp["errmsg"]
369+
)
327370

328371
# 获取文件下载链接
329372
batch_download_file = bind_method(

weixin/config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# -*- coding: utf-8 -*-
22
from __future__ import unicode_literals
33

4-
AUTO_REPLY_CONTENT = '''
4+
AUTO_REPLY_CONTENT = """
55
欢迎关注在行!
66
「在行」是一个知识服务的平台。我们为想要解决问题的人找到行家,一对一面对面答疑解惑、出谋划策;我们为愿意分享知识和经验的人找到学员,让头脑中的资源发挥更大的价值。
77
期待你的加入!
88
www.zaih.com
9-
'''
9+
"""

weixin/helper.py

Lines changed: 53 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
File: client.py
66
Author: goodspeed
77
8-
Github: https://github.com/zongxiao
8+
Github: https://github.com/gusibi
99
Date: 2015-02-11
1010
Description: Weixin helpers
1111
"""
@@ -20,32 +20,28 @@
2020

2121
PY2 = sys.version_info[0] == 2
2222

23-
_always_safe = ('abcdefghijklmnopqrstuvwxyz'
24-
'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.-+')
23+
_always_safe = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.-+"
2524

2625
safe_char = _always_safe
2726

28-
error_dict = {
29-
'AppID 参数错误': {
30-
'errcode': 40013,
31-
'errmsg': 'invalid appid'
32-
}
33-
}
27+
error_dict = {"AppID 参数错误": {"errcode": 40013, "errmsg": "invalid appid"}}
3428

3529

3630
if PY2:
3731
text_type = unicode
3832
iteritems = lambda d, *args, **kwargs: d.iteritems(*args, **kwargs)
3933

40-
def to_native(x, charset=sys.getdefaultencoding(), errors='strict'):
34+
def to_native(x, charset=sys.getdefaultencoding(), errors="strict"):
4135
if x is None or isinstance(x, str):
4236
return x
4337
return x.encode(charset, errors)
38+
39+
4440
else:
4541
text_type = str
4642
iteritems = lambda d, *args, **kwargs: iter(d.items(*args, **kwargs))
4743

48-
def to_native(x, charset=sys.getdefaultencoding(), errors='strict'):
44+
def to_native(x, charset=sys.getdefaultencoding(), errors="strict"):
4945
if x is None or isinstance(x, str):
5046
return x
5147
return x.decode(charset, errors)
@@ -60,15 +56,18 @@ def to_native(x, charset=sys.getdefaultencoding(), errors='strict'):
6056

6157
try:
6258
import hashlib
59+
6360
md5_constructor = hashlib.md5
6461
md5_hmac = md5_constructor
6562
sha_constructor = hashlib.sha1
6663
sha_hmac = sha_constructor
6764
except ImportError:
6865
import md5
66+
6967
md5_constructor = md5.new
7068
md5_hmac = md5
7169
import sha
70+
7271
sha_constructor = sha.new
7372
sha_hmac = sha
7473

@@ -79,6 +78,7 @@ class Promise(object):
7978
the closure of the lazy function. It can be used to recognize
8079
promises in code.
8180
"""
81+
8282
pass
8383

8484

@@ -89,11 +89,10 @@ def __init__(self, obj, *args):
8989

9090
def __str__(self):
9191
original = UnicodeDecodeError.__str__(self)
92-
return '%s. You passed in %r (%s)' % (original, self.obj,
93-
type(self.obj))
92+
return "%s. You passed in %r (%s)" % (original, self.obj, type(self.obj))
9493

9594

96-
def smart_text(s, encoding='utf-8', strings_only=False, errors='strict'):
95+
def smart_text(s, encoding="utf-8", strings_only=False, errors="strict"):
9796
"""
9897
Returns a text object representing 's' -- unicode on Python 2 and str on
9998
Python 3. Treats bytestrings using the 'encoding' codec.
@@ -105,9 +104,14 @@ def smart_text(s, encoding='utf-8', strings_only=False, errors='strict'):
105104
return force_text(s, encoding, strings_only, errors)
106105

107106

108-
_PROTECTED_TYPES = six.integer_types + (type(None), float, Decimal,
109-
datetime.datetime, datetime.date,
110-
datetime.time)
107+
_PROTECTED_TYPES = six.integer_types + (
108+
type(None),
109+
float,
110+
Decimal,
111+
datetime.datetime,
112+
datetime.date,
113+
datetime.time,
114+
)
111115

112116

113117
def is_protected_type(obj):
@@ -118,7 +122,7 @@ def is_protected_type(obj):
118122
return isinstance(obj, _PROTECTED_TYPES)
119123

120124

121-
def force_text(s, encoding='utf-8', strings_only=False, errors='strict'):
125+
def force_text(s, encoding="utf-8", strings_only=False, errors="strict"):
122126
"""
123127
Similar to smart_text, except that lazy instances are resolved to
124128
strings, rather than kept as lazy objects.
@@ -136,7 +140,7 @@ def force_text(s, encoding='utf-8', strings_only=False, errors='strict'):
136140
s = six.text_type(s, encoding, errors)
137141
else:
138142
s = six.text_type(s)
139-
elif hasattr(s, '__unicode__'):
143+
elif hasattr(s, "__unicode__"):
140144
s = six.text_type(s)
141145
else:
142146
s = six.text_type(bytes(s), encoding, errors)
@@ -154,12 +158,11 @@ def force_text(s, encoding='utf-8', strings_only=False, errors='strict'):
154158
# working unicode method. Try to handle this without raising a
155159
# further exception by individually forcing the exception args
156160
# to unicode.
157-
s = ' '.join(force_text(arg, encoding, strings_only, errors)
158-
for arg in s)
161+
s = " ".join(force_text(arg, encoding, strings_only, errors) for arg in s)
159162
return s
160163

161164

162-
def smart_bytes(s, encoding='utf-8', strings_only=False, errors='strict'):
165+
def smart_bytes(s, encoding="utf-8", strings_only=False, errors="strict"):
163166
"""
164167
Returns a bytestring version of 's', encoded as specified in 'encoding'.
165168
If strings_only is True, don't convert (some) non-string-like objects.
@@ -170,18 +173,18 @@ def smart_bytes(s, encoding='utf-8', strings_only=False, errors='strict'):
170173
return force_bytes(s, encoding, strings_only, errors)
171174

172175

173-
def force_bytes(s, encoding='utf-8', strings_only=False, errors='strict'):
176+
def force_bytes(s, encoding="utf-8", strings_only=False, errors="strict"):
174177
"""
175178
Similar to smart_bytes, except that lazy instances are resolved to
176179
strings, rather than kept as lazy objects.
177180
If strings_only is True, don't convert (some) non-string-like objects.
178181
"""
179182
# Handle the common case first for performance reasons.
180183
if isinstance(s, bytes):
181-
if encoding == 'utf-8':
184+
if encoding == "utf-8":
182185
return s
183186
else:
184-
return s.decode('utf-8', errors).encode(encoding, errors)
187+
return s.decode("utf-8", errors).encode(encoding, errors)
185188
if strings_only and is_protected_type(s):
186189
return s
187190
if isinstance(s, Promise):
@@ -197,13 +200,14 @@ def force_bytes(s, encoding='utf-8', strings_only=False, errors='strict'):
197200
# An Exception subclass containing non-ASCII data that doesn't
198201
# know how to print itself properly. We shouldn't raise a
199202
# further exception.
200-
return b' '.join(force_bytes(arg, encoding,
201-
strings_only, errors)
202-
for arg in s)
203+
return b" ".join(
204+
force_bytes(arg, encoding, strings_only, errors) for arg in s
205+
)
203206
return six.text_type(s).encode(encoding, errors)
204207
else:
205208
return s.encode(encoding, errors)
206209

210+
207211
if six.PY3:
208212
smart_str = smart_text
209213
force_str = force_text
@@ -228,31 +232,32 @@ def force_bytes(s, encoding='utf-8', strings_only=False, errors='strict'):
228232
def genarate_js_signature(params):
229233
keys = params.keys()
230234
keys.sort()
231-
params_str = b''
235+
params_str = b""
232236
for key in keys:
233-
params_str += b'%s=%s&' % (smart_str(key), smart_str(params[key]))
237+
params_str += b"%s=%s&" % (smart_str(key), smart_str(params[key]))
234238
params_str = params_str[:-1]
235239
return sha1(params_str).hexdigest()
236240

237241

238242
def genarate_signature(params):
239243
sorted_params = sorted([v for k, v in params.items()])
240-
params_str = smart_str(''.join(sorted_params))
241-
return sha1(str(params_str).encode('utf-8')).hexdigest()
244+
params_str = smart_str("".join(sorted_params))
245+
return sha1(str(params_str).encode("utf-8")).hexdigest()
242246

243247

244248
def get_encoding(html=None, headers=None):
245249
try:
246250
import chardet
251+
247252
if html:
248-
encoding = chardet.detect(html).get('encoding')
253+
encoding = chardet.detect(html).get("encoding")
249254
return encoding
250255
except ImportError:
251256
pass
252257
if headers:
253-
content_type = headers.get('content-type')
258+
content_type = headers.get("content-type")
254259
try:
255-
encoding = content_type.split(' ')[1].split('=')[1]
260+
encoding = content_type.split(" ")[1].split("=")[1]
256261
return encoding
257262
except IndexError:
258263
pass
@@ -275,7 +280,7 @@ def iter_multi_items(mapping):
275280
yield item
276281

277282

278-
def url_quote(string, charset='utf-8', errors='strict', safe='/:', unsafe=''):
283+
def url_quote(string, charset="utf-8", errors="strict", safe="/:", unsafe=""):
279284
"""
280285
URL encode a single string with a given encoding.
281286
@@ -302,12 +307,12 @@ def url_quote(string, charset='utf-8', errors='strict', safe='/:', unsafe=''):
302307
if char in safe:
303308
rv.append(char)
304309
else:
305-
rv.extend(('%%%02X' % char).encode('ascii'))
310+
rv.extend(("%%%02X" % char).encode("ascii"))
306311
return to_native(bytes(rv))
307312

308313

309-
def url_quote_plus(string, charset='utf-8', errors='strict', safe=''):
310-
return url_quote(string, charset, errors, safe + ' ', '+').replace(' ', '+')
314+
def url_quote_plus(string, charset="utf-8", errors="strict", safe=""):
315+
return url_quote(string, charset, errors, safe + " ", "+").replace(" ", "+")
311316

312317

313318
def _url_encode_impl(obj, charset, encode_keys, sort, key):
@@ -321,40 +326,40 @@ def _url_encode_impl(obj, charset, encode_keys, sort, key):
321326
key = text_type(key).encode(charset)
322327
if not isinstance(value, bytes):
323328
value = text_type(value).encode(charset)
324-
yield url_quote_plus(key) + '=' + url_quote_plus(value)
329+
yield url_quote_plus(key) + "=" + url_quote_plus(value)
325330

326331

327-
def url_encode(obj, charset='utf-8', encode_keys=False, sort=False, key=None,
328-
separator=b'&'):
329-
separator = to_native(separator, 'ascii')
332+
def url_encode(
333+
obj, charset="utf-8", encode_keys=False, sort=False, key=None, separator=b"&"
334+
):
335+
separator = to_native(separator, "ascii")
330336
return separator.join(_url_encode_impl(obj, charset, encode_keys, sort, key))
331337

332338

333339
class WeixiErrorParser(html_parser.HTMLParser):
334-
335340
def __init__(self):
336341
html_parser.HTMLParser.__init__(self)
337342
self.recording = 0
338343
self.data = []
339344

340345
def handle_starttag(self, tag, attrs):
341-
if tag != 'h4':
346+
if tag != "h4":
342347
return
343348
if self.recording:
344349
self.recording += 1
345350
self.recording = 1
346351

347352
def handle_endtag(self, tag):
348-
if tag == 'h4' and self.recording:
353+
if tag == "h4" and self.recording:
349354
self.recording -= 1
350355

351356
def handle_data(self, data):
352357
if self.recording:
353358
self.data.append(data)
354359

355360

356-
def error_parser(error_html, encoding='gbk'):
357-
html = text_type(error_html, encoding or 'gbk')
361+
def error_parser(error_html, encoding="gbk"):
362+
html = text_type(error_html, encoding or "gbk")
358363
error_parser = WeixiErrorParser()
359364
error_parser.feed(html)
360365
if error_parser.data:

0 commit comments

Comments
 (0)