mirror of
				https://github.com/LonamiWebs/Telethon.git
				synced 2025-11-04 09:57:29 +03:00 
			
		
		
		
	Add full_sync module (#1016)
This commit is contained in:
		
							parent
							
								
									730cf31921
								
							
						
					
					
						commit
						653f3c043d
					
				| 
						 | 
					@ -2,7 +2,7 @@ import logging
 | 
				
			||||||
from .client.telegramclient import TelegramClient
 | 
					from .client.telegramclient import TelegramClient
 | 
				
			||||||
from .network import connection
 | 
					from .network import connection
 | 
				
			||||||
from .tl import types, functions, custom
 | 
					from .tl import types, functions, custom
 | 
				
			||||||
from . import version, events, utils, errors
 | 
					from . import version, events, utils, errors, full_sync
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
__version__ = version.__version__
 | 
					__version__ = version.__version__
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										161
									
								
								telethon/full_sync.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								telethon/full_sync.py
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,161 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					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.futures import Future, ThreadPoolExecutor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 = Future()
 | 
				
			||||||
 | 
					    loop.call_soon_threadsafe(asyncio.ensure_future, _proxy_future(x, f))
 | 
				
			||||||
 | 
					    return f.result()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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_wrap(t, method_name, loop, thread_ident, syncifier=_sync_result):
 | 
				
			||||||
 | 
					    method = getattr(t, method_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @functools.wraps(method)
 | 
				
			||||||
 | 
					    def syncified(*args, **kwargs):
 | 
				
			||||||
 | 
					        coro = method(*args, **kwargs)
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					            coro if threading.get_ident() == thread_ident
 | 
				
			||||||
 | 
					            else syncifier(loop, coro)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setattr(t, method_name, syncified)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _syncify(*types, loop, thread_ident):
 | 
				
			||||||
 | 
					    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_wrap(t, method_name, loop, thread_ident, _sync_result)
 | 
				
			||||||
 | 
					                elif isasyncgenfunction(getattr(t, method_name)):
 | 
				
			||||||
 | 
					                    _syncify_wrap(t, method_name, loop, thread_ident, _SyncGen)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					__asyncthread = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def enable(*, loop=None, executor=None, max_workers=1):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Enables the fully synchronous mode. You should enable this at
 | 
				
			||||||
 | 
					    the beginning of your script, right after importing, only once.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    **Please** make sure to call `stop` at the end of your script.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    You can define the event loop to use and executor, otherwise
 | 
				
			||||||
 | 
					    the default loop and ``ThreadPoolExecutor`` will be used, in
 | 
				
			||||||
 | 
					    which case `max_workers` will be passed to it. If you pass a
 | 
				
			||||||
 | 
					    custom executor, `max_workers` will be ignored.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    global __asyncthread
 | 
				
			||||||
 | 
					    if __asyncthread is not None:
 | 
				
			||||||
 | 
					        raise RuntimeError("full_sync can only be enabled once")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if not loop:
 | 
				
			||||||
 | 
					        loop = asyncio.get_event_loop()
 | 
				
			||||||
 | 
					    if not executor:
 | 
				
			||||||
 | 
					        executor = ThreadPoolExecutor(max_workers=max_workers)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def start():
 | 
				
			||||||
 | 
					        asyncio.set_event_loop(loop)
 | 
				
			||||||
 | 
					        loop.run_forever()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    __asyncthread = threading.Thread(
 | 
				
			||||||
 | 
					        target=start, name="__telethon_async_thread__", daemon=True
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    __asyncthread.start()
 | 
				
			||||||
 | 
					    __asyncthread.loop = loop
 | 
				
			||||||
 | 
					    __asyncthread.executor = executor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    TelegramClient.__init__ = functools.partialmethod(
 | 
				
			||||||
 | 
					        TelegramClient.__init__, loop=loop
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _syncify(TelegramClient, Draft, Dialog, MessageButton, ChatGetter,
 | 
				
			||||||
 | 
					             SenderGetter, Forward, Message, InlineResult, Conversation,
 | 
				
			||||||
 | 
					             loop=loop, thread_ident=__asyncthread.ident)
 | 
				
			||||||
 | 
					    _syncify_wrap(TelegramClient, "start", loop, __asyncthread.ident)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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, *args, **kwargs):
 | 
				
			||||||
 | 
					        async def _proxy(*pargs, **pkwargs):
 | 
				
			||||||
 | 
					            await loop.run_in_executor(
 | 
				
			||||||
 | 
					                executor, functools.partial(callback, *pargs, **pkwargs))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        proxied_event_handlers[callback] = _proxy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        args = (self, _proxy, *args)
 | 
				
			||||||
 | 
					        return old_add_event_handler(*args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @functools.wraps(old_remove_event_handler)
 | 
				
			||||||
 | 
					    def remove_proxied_event_handler(self, callback, *args, **kwargs):
 | 
				
			||||||
 | 
					        args = (self, proxied_event_handlers.get(callback, callback), *args)
 | 
				
			||||||
 | 
					        return old_remove_event_handler(*args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return __asyncthread
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def stop():
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Stops the fully synchronous code. You
 | 
				
			||||||
 | 
					    should call this before your script exits.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    global __asyncthread
 | 
				
			||||||
 | 
					    if not __asyncthread:
 | 
				
			||||||
 | 
					        raise RuntimeError("Can't find asyncio thread")
 | 
				
			||||||
 | 
					    __asyncthread.loop.call_soon_threadsafe(__asyncthread.loop.stop)
 | 
				
			||||||
 | 
					    __asyncthread.executor.shutdown()
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user