Skip to content

Commit

Permalink
Implement experimental asyncio support #101 (#340)
Browse files Browse the repository at this point in the history
Close #101
  • Loading branch information
tokuhirom committed Oct 4, 2021
1 parent 90cc583 commit 5f27b7e
Show file tree
Hide file tree
Showing 14 changed files with 2,489 additions and 2 deletions.
101 changes: 101 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1238,6 +1238,107 @@ Message
Hints
-----

Experimental Asyncio support
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The LINE Messaging API SDK for Python includes experimental asyncio support.
(API may change without notice in a future version)

.. code:: python
import os
import sys
from argparse import ArgumentParser
import asyncio
import aiohttp
from aiohttp import web
import logging
from aiohttp.web_runner import TCPSite
from linebot import (
AsyncLineBotApi, WebhookParser
)
from linebot.aiohttp_async_http_client import AiohttpAsyncHttpClient
from linebot.exceptions import (
InvalidSignatureError
)
from linebot.models import (
MessageEvent, TextMessage, TextSendMessage,
)
# get channel_secret and channel_access_token from your environment variable
channel_secret = os.getenv('LINE_CHANNEL_SECRET', None)
channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN', None)
if channel_secret is None:
print('Specify LINE_CHANNEL_SECRET as environment variable.')
sys.exit(1)
if channel_access_token is None:
print('Specify LINE_CHANNEL_ACCESS_TOKEN as environment variable.')
sys.exit(1)
class Handler:
def __init__(self, line_bot_api, parser):
self.line_bot_api = line_bot_api
self.parser = parser
async def echo(self, request):
signature = request.headers['X-Line-Signature']
body = await request.text()
try:
events = self.parser.parse(body, signature)
except InvalidSignatureError:
return web.Response(status=400, text='Invalid signature')
for event in events:
if not isinstance(event, MessageEvent):
continue
if not isinstance(event.message, TextMessage):
continue
await self.line_bot_api.reply_message(
event.reply_token,
TextSendMessage(text=event.message.text)
)
return web.Response(text="OK\n")
async def main(port=8000):
session = aiohttp.ClientSession()
async_http_client = AiohttpAsyncHttpClient(session)
line_bot_api = AsyncLineBotApi(channel_access_token, async_http_client)
parser = WebhookParser(channel_secret)
handler = Handler(line_bot_api, parser)
app = web.Application()
app.add_routes([web.post('/callback', handler.echo)])
runner = web.AppRunner(app)
await runner.setup()
site = TCPSite(runner=runner, port=port)
await site.start()
while True:
await asyncio.sleep(3600) # sleep forever
if __name__ == "__main__":
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO)
arg_parser = ArgumentParser(
usage='Usage: python ' + __file__ + ' [--port <port>] [--help]'
)
arg_parser.add_argument('-p', '--port', type=int, default=8000, help='port')
options = arg_parser.parse_args()
asyncio.run(main(options.port))
Examples
~~~~~~~~

Expand Down
19 changes: 19 additions & 0 deletions examples/aiohttp-echo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Flask Echo

Sample echo-bot using [aiohttp](https://docs.aiohttp.org/en/stable/)

## Getting started

```
$ export LINE_CHANNEL_SECRET=YOUR_LINE_CHANNEL_SECRET
$ export LINE_CHANNEL_ACCESS_TOKEN=YOUR_LINE_CHANNEL_ACCESS_TOKEN
$ pip install -r requirements.txt
```

Run WebhookParser sample

```
$ python app.py
```

105 changes: 105 additions & 0 deletions examples/aiohttp-echo/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# -*- coding: utf-8 -*-

# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

import os
import sys
from argparse import ArgumentParser

import asyncio
import aiohttp
from aiohttp import web

import logging

from aiohttp.web_runner import TCPSite

from linebot import (
AsyncLineBotApi, WebhookParser
)
from linebot.aiohttp_async_http_client import AiohttpAsyncHttpClient
from linebot.exceptions import (
InvalidSignatureError
)
from linebot.models import (
MessageEvent, TextMessage, TextSendMessage,
)

# get channel_secret and channel_access_token from your environment variable
channel_secret = os.getenv('LINE_CHANNEL_SECRET', None)
channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN', None)
if channel_secret is None:
print('Specify LINE_CHANNEL_SECRET as environment variable.')
sys.exit(1)
if channel_access_token is None:
print('Specify LINE_CHANNEL_ACCESS_TOKEN as environment variable.')
sys.exit(1)


class Handler:
def __init__(self, line_bot_api, parser):
self.line_bot_api = line_bot_api
self.parser = parser

async def echo(self, request):
signature = request.headers['X-Line-Signature']
body = await request.text()

try:
events = self.parser.parse(body, signature)
except InvalidSignatureError:
return web.Response(status=400, text='Invalid signature')

for event in events:
if not isinstance(event, MessageEvent):
continue
if not isinstance(event.message, TextMessage):
continue

await self.line_bot_api.reply_message(
event.reply_token,
TextSendMessage(text=event.message.text)
)

return web.Response(text="OK\n")


async def main(port=8000):
session = aiohttp.ClientSession()
async_http_client = AiohttpAsyncHttpClient(session)
line_bot_api = AsyncLineBotApi(channel_access_token, async_http_client)
parser = WebhookParser(channel_secret)

handler = Handler(line_bot_api, parser)

app = web.Application()
app.add_routes([web.post('/callback', handler.echo)])

runner = web.AppRunner(app)
await runner.setup()
site = TCPSite(runner=runner, port=port)
await site.start()
while True:
await asyncio.sleep(3600) # sleep forever


if __name__ == "__main__":
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO)

arg_parser = ArgumentParser(
usage='Usage: python ' + __file__ + ' [--port <port>] [--help]'
)
arg_parser.add_argument('-p', '--port', type=int, default=8000, help='port')
options = arg_parser.parse_args()

asyncio.run(main(options.port))
3 changes: 3 additions & 0 deletions examples/aiohttp-echo/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
line-bot-sdk
aiohttp
aiodns
7 changes: 7 additions & 0 deletions linebot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,18 @@
from .api import ( # noqa
LineBotApi,
)
from .async_api import ( # noqa
AsyncLineBotApi,
)
from .http_client import ( # noqa
HttpClient,
RequestsHttpClient,
HttpResponse,
)
from .async_http_client import ( # noqa
AsyncHttpClient,
AsyncHttpResponse,
)
from .webhook import ( # noqa
SignatureValidator,
WebhookParser,
Expand Down

0 comments on commit 5f27b7e

Please sign in to comment.