Skip to content

Commit

Permalink
Improvements in async executor, now it can run async and blocking mod…
Browse files Browse the repository at this point in the history
…ules differently, so we never block executor's event loop.

Introduce `async def` syntax in non-blocking modules
  • Loading branch information
aluminiumgeek committed Mar 22, 2016
1 parent 7a1055f commit 89d8255
Show file tree
Hide file tree
Showing 5 changed files with 25 additions and 15 deletions.
20 changes: 14 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# cc-telegram
Bot framework for Telegram Messenger
Async bot framework for Telegram Messenger designed for Python >= 3.5

[![tip for next commit](https://tip4commit.com/projects/43141.svg)](https://tip4commit.com/github/aluminiumgeek/cc-telegram)

Expand All @@ -21,11 +21,19 @@ To get all available options check:
`python3 cc.py --help`

## Modules ##
Several kinds of modules for bot's reaction to different types of messages are supported:
* If filename starts with `user_` or `owner_`, the command will be available with `/command` syntax. A module will be called with args as splitted text after `/command` statement. See `modules/user_echo.py` for example.
* If filename starts with `audio_`, `video_`, `text_`, etc, bot will call module with [`message`](https://core.telegram.org/bots/api#message) and `update` objects.

You can return any message from module using `return` statement (see `modules/user_lsmod.py`). You also able to call bot's methods directly from module (to call telegram api or to send chat action, for example).
All modules should be written with async in mind, since modules executor is non-blocking and built on top of Python's `asyncio`.
However, if you write blocking module, it will not block main event loop, CC is designed to run blocking module in another thread.

Async modules should define `main` method as `async def main(bot, *args, **kwargs)` and use `await` keyword where it's needed. You can check some modules inside `modules` dir of this repo and see differences between blocking and non-blocking modules.

Module types:
- `user_*` - command called with '/command' syntax, available for all users
- `admin_*` - command called with '/command' syntax, available only for admin
- `audio_*` - module will be called on each audio message
- `video_*` - called on each video message
- `text_*` - called on each text message
- `photo_*` - called on each message with photo
- `sticker_*` - called on each message with sticker

Check currently available modules by running `/lsmod` command in chat.

Expand Down
2 changes: 1 addition & 1 deletion modules/user_date.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from datetime import datetime


def main(bot, *args, **kwargs):
async def main(bot, *args, **kwargs):
"""
date
Current datetime
Expand Down
5 changes: 2 additions & 3 deletions modules/user_echo.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@

def main(bot, *args, **kwargs):
async def main(bot, *args, **kwargs):
"""
echo <text>
Echoes input text
"""
bot.send(chat_id=kwargs.get('chat_id'), text=' '.join(args))
return ' '.join(args)
2 changes: 1 addition & 1 deletion modules/user_lsmod.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

def main(bot, *args, **kwargs):
async def main(bot, *args, **kwargs):
"""
lsmod
Show loaded modules.
Expand Down
11 changes: 7 additions & 4 deletions telegram.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,13 @@ def call(self, module, *args, **kwargs):
task.add_done_callback(functools.partial(self.callback, chat_id=chat_id))
return task

@asyncio.coroutine
def run(self, module, *args, **kwargs):
future = self.loop.run_in_executor(None, functools.partial(module, *args, **kwargs))
yield from future
async def run(self, module, *args, **kwargs):
if asyncio.iscoroutinefunction(module):
return await module(*args, **kwargs)
else:
# Run blocking modules in another thread, so it won't block event loop anyway
future = self.loop.run_in_executor(None, functools.partial(module, *args, **kwargs))
return await future

def close(self):
self.loop.close()
Expand Down

0 comments on commit 89d8255

Please sign in to comment.