Skip to content

Commit 5f27b7e

Browse files
authored
Implement experimental asyncio support #101 (#340)
Close #101
1 parent 90cc583 commit 5f27b7e

14 files changed

+2489
-2
lines changed

README.rst

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1238,6 +1238,107 @@ Message
12381238
Hints
12391239
-----
12401240

1241+
Experimental Asyncio support
1242+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1243+
1244+
The LINE Messaging API SDK for Python includes experimental asyncio support.
1245+
(API may change without notice in a future version)
1246+
1247+
.. code:: python
1248+
import os
1249+
import sys
1250+
from argparse import ArgumentParser
1251+
1252+
import asyncio
1253+
import aiohttp
1254+
from aiohttp import web
1255+
1256+
import logging
1257+
1258+
from aiohttp.web_runner import TCPSite
1259+
1260+
from linebot import (
1261+
AsyncLineBotApi, WebhookParser
1262+
)
1263+
from linebot.aiohttp_async_http_client import AiohttpAsyncHttpClient
1264+
from linebot.exceptions import (
1265+
InvalidSignatureError
1266+
)
1267+
from linebot.models import (
1268+
MessageEvent, TextMessage, TextSendMessage,
1269+
)
1270+
1271+
# get channel_secret and channel_access_token from your environment variable
1272+
channel_secret = os.getenv('LINE_CHANNEL_SECRET', None)
1273+
channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN', None)
1274+
if channel_secret is None:
1275+
print('Specify LINE_CHANNEL_SECRET as environment variable.')
1276+
sys.exit(1)
1277+
if channel_access_token is None:
1278+
print('Specify LINE_CHANNEL_ACCESS_TOKEN as environment variable.')
1279+
sys.exit(1)
1280+
1281+
1282+
class Handler:
1283+
def __init__(self, line_bot_api, parser):
1284+
self.line_bot_api = line_bot_api
1285+
self.parser = parser
1286+
1287+
async def echo(self, request):
1288+
signature = request.headers['X-Line-Signature']
1289+
body = await request.text()
1290+
1291+
try:
1292+
events = self.parser.parse(body, signature)
1293+
except InvalidSignatureError:
1294+
return web.Response(status=400, text='Invalid signature')
1295+
1296+
for event in events:
1297+
if not isinstance(event, MessageEvent):
1298+
continue
1299+
if not isinstance(event.message, TextMessage):
1300+
continue
1301+
1302+
await self.line_bot_api.reply_message(
1303+
event.reply_token,
1304+
TextSendMessage(text=event.message.text)
1305+
)
1306+
1307+
return web.Response(text="OK\n")
1308+
1309+
1310+
async def main(port=8000):
1311+
session = aiohttp.ClientSession()
1312+
async_http_client = AiohttpAsyncHttpClient(session)
1313+
line_bot_api = AsyncLineBotApi(channel_access_token, async_http_client)
1314+
parser = WebhookParser(channel_secret)
1315+
1316+
handler = Handler(line_bot_api, parser)
1317+
1318+
app = web.Application()
1319+
app.add_routes([web.post('/callback', handler.echo)])
1320+
1321+
runner = web.AppRunner(app)
1322+
await runner.setup()
1323+
site = TCPSite(runner=runner, port=port)
1324+
await site.start()
1325+
while True:
1326+
await asyncio.sleep(3600) # sleep forever
1327+
1328+
1329+
if __name__ == "__main__":
1330+
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO)
1331+
1332+
arg_parser = ArgumentParser(
1333+
usage='Usage: python ' + __file__ + ' [--port <port>] [--help]'
1334+
)
1335+
arg_parser.add_argument('-p', '--port', type=int, default=8000, help='port')
1336+
options = arg_parser.parse_args()
1337+
1338+
asyncio.run(main(options.port))
1339+
1340+
1341+
12411342
Examples
12421343
~~~~~~~~
12431344

examples/aiohttp-echo/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Flask Echo
2+
3+
Sample echo-bot using [aiohttp](https://docs.aiohttp.org/en/stable/)
4+
5+
## Getting started
6+
7+
```
8+
$ export LINE_CHANNEL_SECRET=YOUR_LINE_CHANNEL_SECRET
9+
$ export LINE_CHANNEL_ACCESS_TOKEN=YOUR_LINE_CHANNEL_ACCESS_TOKEN
10+
11+
$ pip install -r requirements.txt
12+
```
13+
14+
Run WebhookParser sample
15+
16+
```
17+
$ python app.py
18+
```
19+

examples/aiohttp-echo/app.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# -*- coding: utf-8 -*-
2+
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
4+
# not use this file except in compliance with the License. You may obtain
5+
# a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
# License for the specific language governing permissions and limitations
13+
# under the License.
14+
15+
import os
16+
import sys
17+
from argparse import ArgumentParser
18+
19+
import asyncio
20+
import aiohttp
21+
from aiohttp import web
22+
23+
import logging
24+
25+
from aiohttp.web_runner import TCPSite
26+
27+
from linebot import (
28+
AsyncLineBotApi, WebhookParser
29+
)
30+
from linebot.aiohttp_async_http_client import AiohttpAsyncHttpClient
31+
from linebot.exceptions import (
32+
InvalidSignatureError
33+
)
34+
from linebot.models import (
35+
MessageEvent, TextMessage, TextSendMessage,
36+
)
37+
38+
# get channel_secret and channel_access_token from your environment variable
39+
channel_secret = os.getenv('LINE_CHANNEL_SECRET', None)
40+
channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN', None)
41+
if channel_secret is None:
42+
print('Specify LINE_CHANNEL_SECRET as environment variable.')
43+
sys.exit(1)
44+
if channel_access_token is None:
45+
print('Specify LINE_CHANNEL_ACCESS_TOKEN as environment variable.')
46+
sys.exit(1)
47+
48+
49+
class Handler:
50+
def __init__(self, line_bot_api, parser):
51+
self.line_bot_api = line_bot_api
52+
self.parser = parser
53+
54+
async def echo(self, request):
55+
signature = request.headers['X-Line-Signature']
56+
body = await request.text()
57+
58+
try:
59+
events = self.parser.parse(body, signature)
60+
except InvalidSignatureError:
61+
return web.Response(status=400, text='Invalid signature')
62+
63+
for event in events:
64+
if not isinstance(event, MessageEvent):
65+
continue
66+
if not isinstance(event.message, TextMessage):
67+
continue
68+
69+
await self.line_bot_api.reply_message(
70+
event.reply_token,
71+
TextSendMessage(text=event.message.text)
72+
)
73+
74+
return web.Response(text="OK\n")
75+
76+
77+
async def main(port=8000):
78+
session = aiohttp.ClientSession()
79+
async_http_client = AiohttpAsyncHttpClient(session)
80+
line_bot_api = AsyncLineBotApi(channel_access_token, async_http_client)
81+
parser = WebhookParser(channel_secret)
82+
83+
handler = Handler(line_bot_api, parser)
84+
85+
app = web.Application()
86+
app.add_routes([web.post('/callback', handler.echo)])
87+
88+
runner = web.AppRunner(app)
89+
await runner.setup()
90+
site = TCPSite(runner=runner, port=port)
91+
await site.start()
92+
while True:
93+
await asyncio.sleep(3600) # sleep forever
94+
95+
96+
if __name__ == "__main__":
97+
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO)
98+
99+
arg_parser = ArgumentParser(
100+
usage='Usage: python ' + __file__ + ' [--port <port>] [--help]'
101+
)
102+
arg_parser.add_argument('-p', '--port', type=int, default=8000, help='port')
103+
options = arg_parser.parse_args()
104+
105+
asyncio.run(main(options.port))
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
line-bot-sdk
2+
aiohttp
3+
aiodns

linebot/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,18 @@
2121
from .api import ( # noqa
2222
LineBotApi,
2323
)
24+
from .async_api import ( # noqa
25+
AsyncLineBotApi,
26+
)
2427
from .http_client import ( # noqa
2528
HttpClient,
2629
RequestsHttpClient,
2730
HttpResponse,
2831
)
32+
from .async_http_client import ( # noqa
33+
AsyncHttpClient,
34+
AsyncHttpResponse,
35+
)
2936
from .webhook import ( # noqa
3037
SignatureValidator,
3138
WebhookParser,

0 commit comments

Comments
 (0)