Skip to content

Commit 41e2a4f

Browse files
committed
"get_content" needs content-type header value. so, i changed interface. And modify kitchensink(use tempfile).
1 parent 3b1a64e commit 41e2a4f

File tree

7 files changed

+165
-117
lines changed

7 files changed

+165
-117
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,17 +114,17 @@ print(profile.picture_url)
114114
print(profile.status_message)
115115
```
116116

117-
#### get_content_stream(self, message_id, chunk_size=1024, timeout=None)
117+
#### get_content(self, message_id, timeout=None)
118118

119119
Retrieve image, video, and audio data sent by users.
120120

121121
https://devdocs.line.me/en/#get-content
122122

123123
```python
124-
stream = line_bot_api.get_content_stream(message_id)
124+
message_content = line_bot_api.get_content(message_id)
125125

126126
with open(file_path, 'wb') as fd:
127-
for chunk in stream:
127+
for chunk in message_content.iter_content():
128128
fd.write(chunk)
129129
```
130130

examples/flask-kitchensink/app.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import errno
1818
import os
1919
import sys
20+
import tempfile
2021
from argparse import ArgumentParser
2122

2223
from flask import Flask, request, abort
@@ -190,23 +191,29 @@ def handle_sticker_message(event):
190191
@handler.add(MessageEvent, message=(ImageMessage, VideoMessage, AudioMessage))
191192
def handle_content_message(event):
192193
if isinstance(event.message, ImageMessage):
193-
ext = '.jpg'
194+
ext = 'jpg'
194195
elif isinstance(event.message, VideoMessage):
195-
ext = '.mp4'
196+
ext = 'mp4'
196197
elif isinstance(event.message, AudioMessage):
197-
ext = '.m4a'
198+
ext = 'm4a'
198199
else:
199200
return
200201

201-
stream = line_bot_api.get_content_stream(event.message.id)
202-
file_path = os.path.join(static_tmp_path, event.message.id + ext)
203-
with open(file_path, 'wb') as f:
204-
for chunk in stream:
205-
f.write(chunk)
202+
message_content = line_bot_api.get_content(event.message.id)
203+
with tempfile.NamedTemporaryFile(dir=static_tmp_path, prefix=ext + '-', delete=False) as tf:
204+
for chunk in message_content.iter_content():
205+
tf.write(chunk)
206+
tempfile_path = tf.name
207+
208+
dist_path = tempfile_path + '.' + ext
209+
dist_name = os.path.basename(dist_path)
210+
os.rename(tempfile_path, dist_path)
206211

207212
line_bot_api.reply_message(
208-
event.reply_token,
209-
TextSendMessage(text='Save content at ' + file_path))
213+
event.reply_token, [
214+
TextSendMessage(text='Save content.'),
215+
TextSendMessage(text=request.host_url + os.path.join('static', 'tmp', dist_name))
216+
])
210217

211218

212219
@handler.add(FollowEvent)

linebot/api.py

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from .exceptions import LineBotApiError
2323
from .http_client import HttpClient, RequestsHttpClient
2424
from .models.error import Error
25-
from .models.responses import Profile
25+
from .models.responses import Profile, MessageContent
2626

2727

2828
class LineBotApi(object):
@@ -83,6 +83,7 @@ def reply_message(self, reply_token, messages, timeout=None):
8383
'replyToken': reply_token,
8484
'messages': [message.as_json_dict() for message in messages]
8585
}
86+
8687
self._post(
8788
'/v2/bot/message/reply', data=json.dumps(data), timeout=timeout
8889
)
@@ -109,6 +110,7 @@ def push_message(self, to, messages, timeout=None):
109110
'to': to,
110111
'messages': [message.as_json_dict() for message in messages]
111112
}
113+
112114
self._post(
113115
'/v2/bot/message/push', data=json.dumps(data), timeout=timeout
114116
)
@@ -129,36 +131,35 @@ def get_profile(self, user_id, timeout=None):
129131
:return:
130132
131133
"""
132-
body = self._get(
134+
response = self._get(
133135
'/v2/bot/profile/{user_id}'.format(user_id=user_id),
134136
timeout=timeout
135137
)
136-
return Profile.new_from_json_dict(json.loads(body))
137138

138-
def get_content_stream(self, message_id, chunk_size=1024, timeout=None):
139+
return Profile.new_from_json_dict(response.json)
140+
141+
def get_content(self, message_id, timeout=None):
139142
"""Call get content API.
140143
141144
https://devdocs.line.me/en/#get-content
142145
143146
Retrieve image, video, and audio data sent by users.
144147
145148
:param message_id: Message ID
146-
:param int chunk_size: Chunk size
147149
:param float|tuple(float, float) timeout: (optional) How long to wait for the server
148150
to send data before giving up, as a float,
149151
or a (connect timeout, readtimeout) float tuple.
150152
Default is self.http_client.timeout
151-
:rtype: iterator
153+
:rtype: linebot.models.MessageContent
152154
:return:
153155
154156
"""
155-
body_stream = self._get_stream(
156-
'/v2/bot/message/{message_id}/content'.format(
157-
message_id=message_id),
158-
chunk_size=chunk_size, timeout=timeout
157+
response = self._get(
158+
'/v2/bot/message/{message_id}/content'.format(message_id=message_id),
159+
stream=True, timeout=timeout
159160
)
160161

161-
return body_stream
162+
return MessageContent(response)
162163

163164
def leave_group(self, group_id, timeout=None):
164165
"""Call leave group API.
@@ -196,26 +197,15 @@ def leave_room(self, room_id, timeout=None):
196197
timeout=timeout
197198
)
198199

199-
def _get(self, path, timeout=None):
200+
def _get(self, path, stream=False, timeout=None):
200201
url = self.endpoint + path
201202

202203
response = self.http_client.get(
203-
url, headers=self.headers, timeout=timeout
204-
)
205-
206-
self.__check_error(response)
207-
return response.body
208-
209-
def _get_stream(self, path, chunk_size=1024, decode_unicode=False, timeout=None):
210-
url = self.endpoint + path
211-
212-
response = self.http_client.get_stream(
213-
url, headers=self.headers, timeout=timeout,
214-
chunk_size=chunk_size, decode_unicode=decode_unicode
204+
url, headers=self.headers, stream=stream, timeout=timeout
215205
)
216206

217207
self.__check_error(response)
218-
return response.body_stream
208+
return response
219209

220210
def _post(self, path, data=None, timeout=None):
221211
url = self.endpoint + path
@@ -227,12 +217,12 @@ def _post(self, path, data=None, timeout=None):
227217
)
228218

229219
self.__check_error(response)
230-
return response.body
220+
return response
231221

232222
@staticmethod
233223
def __check_error(response):
234224
if 200 <= response.status_code < 300:
235225
pass
236226
else:
237-
error = Error.new_from_json_dict(json.loads(response.body))
227+
error = Error.new_from_json_dict(response.json)
238228
raise LineBotApiError(response.status_code, error)

linebot/http_client.py

Lines changed: 80 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
from __future__ import unicode_literals
1818

19-
from abc import ABCMeta, abstractmethod
19+
from abc import ABCMeta, abstractmethod, abstractproperty
2020

2121
import requests
2222
from future.utils import with_metaclass
@@ -38,36 +38,20 @@ def __init__(self, timeout=DEFAULT_TIMEOUT):
3838
self.timeout = timeout
3939

4040
@abstractmethod
41-
def get(self, url, headers=None, params=None, timeout=None):
41+
def get(self, url, headers=None, params=None, stream=False, timeout=None):
4242
"""GET request.
4343
4444
:param str url: Request url
4545
:param dict headers: (optional) Request headers
4646
:param dict params: (optional) Request query parameter
47+
:param bool stream: (optional) get content as stream
4748
:param float|tuple(float, float) timeout: (optional), How long to wait for the server
4849
to send data before giving up, as a float,
4950
or a (connect timeout, readtimeout) float tuple.
5051
Default is DEFAULT_TIMEOUT
5152
"""
5253
raise NotImplementedError
5354

54-
@abstractmethod
55-
def get_stream(self, url, headers=None, params=None, timeout=None,
56-
chunk_size=1024, decode_unicode=False):
57-
"""GET request. Response is chunk content.
58-
59-
:param str url: Request url
60-
:param dict headers: (optional) Request headers
61-
:param dict params: (optional) Request query parameter
62-
:param float|tuple(float, float) timeout: (optional), How long to wait for the server
63-
to send data before giving up, as a float,
64-
or a (connect timeout, readtimeout) float tuple.
65-
Default is DEFAULT_TIMEOUT
66-
:param int chunk_size: (optional) Chunk size
67-
:param boole decode_unicode: (optional) Decode unicode
68-
"""
69-
raise NotImplementedError
70-
7155
@abstractmethod
7256
def post(self, url, headers=None, data=None, timeout=None):
7357
"""POST request.
@@ -96,12 +80,13 @@ def __init__(self, timeout=HttpClient.DEFAULT_TIMEOUT):
9680
"""
9781
super(RequestsHttpClient, self).__init__(timeout)
9882

99-
def get(self, url, headers=None, params=None, timeout=None):
83+
def get(self, url, headers=None, params=None, stream=False, timeout=None):
10084
"""GET request.
10185
10286
:param str url: Request url
10387
:param dict headers: (optional) Request headers
10488
:param dict params: (optional) Request query parameter
89+
:param bool stream: (optional) get content as stream
10590
:param float|tuple(float, float) timeout: (optional), How long to wait for the server
10691
to send data before giving up, as a float,
10792
or a (connect timeout, readtimeout) float tuple.
@@ -113,48 +98,10 @@ def get(self, url, headers=None, params=None, timeout=None):
11398
timeout = self.timeout
11499

115100
response = requests.get(
116-
url, headers=headers, params=params, timeout=timeout
117-
)
118-
119-
return HttpResponse(
120-
status_code=response.status_code, headers=response.headers,
121-
body=response.text)
122-
123-
def get_stream(self, url, headers=None, params=None, timeout=None,
124-
chunk_size=1024, decode_unicode=False):
125-
"""GET request. Response is chunk content.
126-
127-
:param str url: Request url
128-
:param dict headers: (optional) Request headers
129-
:param dict params: (optional) Request query parameter
130-
:param float|tuple(float, float) timeout: (optional), How long to wait for the server
131-
to send data before giving up, as a float,
132-
or a (connect timeout, readtimeout) float tuple.
133-
Default is DEFAULT_TIMEOUT
134-
:param int chunk_size: (optional) Chunk size
135-
:param boole decode_unicode: (optional) Decode unicode
136-
:rtype: HttpResponse
137-
:return:
138-
"""
139-
if timeout is None:
140-
timeout = self.timeout
141-
142-
response = requests.get(
143-
url, headers=headers, params=params, timeout=timeout,
144-
stream=True
101+
url, headers=headers, params=params, stream=stream, timeout=timeout
145102
)
146103

147-
if 200 <= response.status_code < 300:
148-
return HttpResponse(
149-
status_code=response.status_code, headers=response.headers,
150-
body_stream=response.iter_content(
151-
chunk_size=chunk_size, decode_unicode=decode_unicode
152-
)
153-
)
154-
else:
155-
return HttpResponse(
156-
status_code=response.status_code, headers=response.headers,
157-
body=response.text)
104+
return RequestsHttpResponse(response)
158105

159106
def post(self, url, headers=None, data=None, timeout=None):
160107
"""POST request.
@@ -176,23 +123,82 @@ def post(self, url, headers=None, data=None, timeout=None):
176123
url, headers=headers, data=data, timeout=timeout
177124
)
178125

179-
return HttpResponse(
180-
status_code=response.status_code, headers=response.headers,
181-
body=response.text)
126+
return RequestsHttpResponse(response)
127+
128+
129+
class HttpResponse(with_metaclass(ABCMeta)):
130+
"""HttpResponse."""
182131

132+
@abstractproperty
133+
def status_code(self):
134+
"""Get status code."""
135+
raise NotImplementedError
183136

184-
class HttpResponse(object):
185-
"""HttpResponse container."""
137+
@abstractproperty
138+
def headers(self):
139+
"""Get headers."""
140+
raise NotImplementedError
186141

187-
def __init__(self, status_code=None, body=None, headers=None, body_stream=None):
142+
@abstractproperty
143+
def text(self):
144+
"""Get request body as text-decoded."""
145+
raise NotImplementedError
146+
147+
@abstractproperty
148+
def content(self):
149+
"""Get request body as binary."""
150+
raise NotImplementedError
151+
152+
@abstractproperty
153+
def json(self):
154+
"""Get request body as json-decoded."""
155+
raise NotImplementedError
156+
157+
@abstractmethod
158+
def iter_content(self, chunk_size=1024, decode_unicode=False):
159+
"""Get request body as iterator content (stream)."""
160+
raise NotImplementedError
161+
162+
163+
class RequestsHttpResponse(HttpResponse):
164+
"""HttpResponse implemented by requests lib's response."""
165+
166+
def __init__(self, response):
188167
"""__init__ method.
189168
190-
:param str status_code: Status code
191-
:param str body: Response body as text
192-
:param dict headers: Response headers
193-
:param iterator body_stream:
169+
:param response: requests lib's response
170+
"""
171+
self.response = response
172+
173+
@property
174+
def status_code(self):
175+
"""Get status code."""
176+
return self.response.status_code
177+
178+
@property
179+
def headers(self):
180+
"""Get headers."""
181+
return self.response.headers
182+
183+
@property
184+
def text(self):
185+
"""Get request body as text-decoded."""
186+
return self.response.text
187+
188+
@property
189+
def content(self):
190+
"""Get request body as binary."""
191+
return self.response.content
192+
193+
@property
194+
def json(self):
195+
"""Get request body as json-decoded."""
196+
return self.response.json()
197+
198+
def iter_content(self, chunk_size=1024, decode_unicode=False):
199+
"""Get request body as iterator content (stream).
200+
201+
:param chunk_size:
202+
:param decode_unicode:
194203
"""
195-
self.status_code = status_code
196-
self.headers = headers
197-
self.body = body
198-
self.body_stream = body_stream
204+
return self.response.iter_content(chunk_size=chunk_size, decode_unicode=decode_unicode)

0 commit comments

Comments
 (0)