Add full_sync module

This commit is contained in:
Tulir Asokan 2018-10-02 02:18:26 +03:00
parent c2966297f1
commit 1189788c2c
2 changed files with 142 additions and 1 deletions

View File

@ -2,7 +2,7 @@ import logging
from .client.telegramclient import TelegramClient
from .network import connection
from .tl import types, functions, custom
from . import version, events, utils, errors
from . import version, events, utils, errors, full_sync
__version__ = version.__version__

141
telethon/full_sync.py Normal file
View File

@ -0,0 +1,141 @@
"""
This magical module will rewrite all public methods in the public interface of
the library so they can delegate the call to an asyncio event loop in another
thread and wait for the result. This rewrite may not be desirable if the end
user always uses the methods they way they should be ran, but it's incredibly
useful for quick scripts and legacy code.
"""
import asyncio
import functools
import inspect
import threading
from concurrent import futures
from async_generator import isasyncgenfunction
from .client.telegramclient import TelegramClient
from .tl.custom import (
Draft, Dialog, MessageButton, Forward, Message, InlineResult, Conversation
)
from .tl.custom.chatgetter import ChatGetter
from .tl.custom.sendergetter import SenderGetter
async def _proxy_future(af, cf):
try:
res = await af
cf.set_result(res)
except Exception as e:
cf.set_exception(e)
def _sync_result(loop, x):
f = futures.Future()
loop.call_soon_threadsafe(asyncio.ensure_future, _proxy_future(x, f))
return f.result()
def _syncify_coro(t, method_name, loop, thread_name):
method = getattr(t, method_name)
@functools.wraps(method)
def syncified(*args, **kwargs):
coro = method(*args, **kwargs)
return (
coro if threading.current_thread().name == thread_name
else _sync_result(loop, coro)
)
setattr(t, method_name, syncified)
class _SyncGen:
def __init__(self, loop, gen):
self.loop = loop
self.gen = gen
def __iter__(self):
return self
def __next__(self):
try:
return _sync_result(self.loop, self.gen.__anext__())
except StopAsyncIteration:
raise StopIteration from None
def _syncify_gen(t, method_name, loop, thread_name):
method = getattr(t, method_name)
@functools.wraps(method)
def syncified(*args, **kwargs):
coro = method(*args, **kwargs)
return (
coro if threading.current_thread().name == thread_name
else _SyncGen(loop, coro)
)
setattr(t, method_name, syncified)
def _syncify(*types, loop, thread_name):
for t in types:
for method_name in dir(t):
if not method_name.startswith('_') or method_name == '__call__':
if inspect.iscoroutinefunction(getattr(t, method_name)):
_syncify_coro(t, method_name, loop, thread_name)
elif isasyncgenfunction(getattr(t, method_name)):
_syncify_gen(t, method_name, loop, thread_name)
def enable(loop=None, thread_name="__telethon_async_thread__"):
if not loop:
loop = asyncio.get_event_loop()
old_init = TelegramClient.__init__
@functools.wraps(old_init)
def new_init(*args, **kwargs):
kwargs['loop'] = loop
return old_init(*args, **kwargs)
TelegramClient.__init__ = new_init
_syncify(TelegramClient, Draft, Dialog, MessageButton, ChatGetter,
SenderGetter, Forward, Message, InlineResult, Conversation,
loop=loop, thread_name=thread_name)
_syncify_coro(TelegramClient, "start", loop, thread_name)
old_add_event_handler = TelegramClient.add_event_handler
old_remove_event_handler = TelegramClient.remove_event_handler
proxied_event_handlers = {}
@functools.wraps(old_add_event_handler)
def add_proxied_event_handler(self, callback, event_type=None):
async def _proxy(event):
h_t = threading.Thread(target=callback, args=(event,))
h_t.start()
proxied_event_handlers[callback] = _proxy
return old_add_event_handler(self, _proxy, event_type)
@functools.wraps(old_remove_event_handler)
def remove_proxied_event_handler(self, callback, event_type=None):
return old_remove_event_handler(
self, proxied_event_handlers.get(callback, callback), event_type)
TelegramClient.add_event_handler = add_proxied_event_handler
TelegramClient.remove_event_handler = remove_proxied_event_handler
def run_until_disconnected(self):
return _sync_result(loop, self._run_until_disconnected())
TelegramClient.run_until_disconnected = run_until_disconnected
def start():
asyncio.set_event_loop(loop)
loop.run_forever()
asyncthread = threading.Thread(target=start, name=thread_name)
asyncthread.start()
return asyncthread