mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2024-11-22 17:36:34 +03:00
Get rid of full_sync
This commit is contained in:
parent
c70943bb0e
commit
8e6b98669a
|
@ -14,31 +14,48 @@ is there to tell you when these important changes happen.
|
|||
Compatibility
|
||||
*************
|
||||
|
||||
.. important::
|
||||
Some decisions when developing will inevitable be proven wrong in the future.
|
||||
One of these decisions was using threads. Now that Python 3.4 is reaching EOL
|
||||
and using ``asyncio`` is usable as of Python 3.5 it makes sense for a library
|
||||
like Telethon to make a good use of it.
|
||||
|
||||
**You should not enable the thread-compatibility mode for new projects.**
|
||||
It comes with a cost, and new projects will greatly benefit from using
|
||||
``asyncio`` by default such as increased speed and easier reasoning about
|
||||
the code flow. You should only enable it for old projects you don't have
|
||||
the time to upgrade to ``asyncio``.
|
||||
If you have old code, **just use old versions** of the library! There is
|
||||
nothing wrong with that other than not getting new updates or fixes, but
|
||||
using a fixed version with ``pip install telethon==0.19.1.6`` is easy
|
||||
enough to do.
|
||||
|
||||
There exists a fair amount of code online using Telethon before it reached
|
||||
its 1.0 version, where it became fully asynchronous by default. Since it was
|
||||
necessary to clean some things, compatibility was not kept 100% but the
|
||||
changes are simple:
|
||||
You might want to consider using `Virtual Environments
|
||||
<https://docs.python.org/3/tutorial/venv.html>`_ in your projects.
|
||||
|
||||
There's no point in maintaining a synchronous version because the whole point
|
||||
is that people don't have time to upgrade, and there has been several changes
|
||||
and clean-ups. Using an older version is the right way to go.
|
||||
|
||||
Sometimes, other small decisions are made. These all will be reflected in the
|
||||
:ref:`changelog` which you should read when upgrading.
|
||||
|
||||
If you want to jump the ``asyncio`` boat, here are some of the things you will
|
||||
need to start migrating really old code:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# 1. The library no longer uses threads.
|
||||
# Add this at the **beginning** of your script to work around that.
|
||||
from telethon import full_sync
|
||||
full_sync.enable()
|
||||
# 1. Import the client from telethon.sync
|
||||
from telethon.sync import TelegramClient
|
||||
|
||||
# 2. client.connect() no longer returns True.
|
||||
# Change this...
|
||||
# 2. Change this monster...
|
||||
try:
|
||||
assert client.connect()
|
||||
if not client.is_user_authorized():
|
||||
client.send_code_request(phone_number)
|
||||
me = client.sign_in(phone_number, input('Enter code: '))
|
||||
|
||||
... # REST OF YOUR CODE
|
||||
finally:
|
||||
client.disconnect()
|
||||
|
||||
# ...for this:
|
||||
client.connect()
|
||||
with client:
|
||||
... # REST OF YOUR CODE
|
||||
|
||||
# 3. client.idle() no longer exists.
|
||||
# Change this...
|
||||
|
@ -52,11 +69,10 @@ changes are simple:
|
|||
# ...to this:
|
||||
client.add_event_handler(handler)
|
||||
|
||||
# 5. It's good practice to stop the full_sync mode once you're done
|
||||
try:
|
||||
... # all your code in here
|
||||
finally:
|
||||
full_sync.stop()
|
||||
|
||||
In addition, all the update handlers must be ``async def``, and you need
|
||||
to ``await`` method calls that rely on network requests, such as getting
|
||||
the chat or sender. If you don't use updates, you're done!
|
||||
|
||||
|
||||
Convenience
|
||||
|
@ -75,8 +91,8 @@ Convenience
|
|||
This makes the examples shorter and easier to think about.
|
||||
|
||||
For quick scripts that don't need updates, it's a lot more convenient to
|
||||
forget about ``full_sync`` or ``asyncio`` and just work with sequential code.
|
||||
This can prove to be a powerful hybrid for running under the Python REPL too.
|
||||
forget about ``asyncio`` and just work with sequential code. This can prove
|
||||
to be a powerful hybrid for running under the Python REPL too.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
@ -122,7 +138,7 @@ Speed
|
|||
|
||||
When you're ready to micro-optimize your application, or if you simply
|
||||
don't need to call any non-basic methods from a synchronous context,
|
||||
just get rid of both ``telethon.sync`` and ``telethon.full_sync``:
|
||||
just get rid of ``telethon.sync`` and work inside an ``async def``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
|
|
@ -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, full_sync
|
||||
from . import version, events, utils, errors
|
||||
|
||||
|
||||
__version__ = version.__version__
|
||||
|
|
|
@ -1,184 +0,0 @@
|
|||
"""
|
||||
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 . import events
|
||||
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, rename=None):
|
||||
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, rename or method_name, syncified)
|
||||
|
||||
|
||||
def _syncify(*types, loop, thread_ident):
|
||||
for t in types:
|
||||
# __enter__ and __exit__ need special care (VERY dirty hack).
|
||||
#
|
||||
# Normally we want them to raise if the loop is running because
|
||||
# the user can't await there, and they need the async with variant.
|
||||
#
|
||||
# However they check if the loop is running to raise, which it is
|
||||
# with full_sync enabled, so we patch them with the async variant.
|
||||
if hasattr(t, '__aenter__'):
|
||||
_syncify_wrap(
|
||||
t, '__aenter__', loop, thread_ident, rename='__enter__')
|
||||
|
||||
_syncify_wrap(
|
||||
t, '__aexit__', loop, thread_ident, rename='__exit__')
|
||||
|
||||
for name in dir(t):
|
||||
if not name.startswith('_') or name == '__call__':
|
||||
meth = getattr(t, name)
|
||||
meth = getattr(meth, '__tl.sync', meth)
|
||||
if inspect.iscoroutinefunction(meth):
|
||||
_syncify_wrap(t, name, loop, thread_ident)
|
||||
elif isasyncgenfunction(meth):
|
||||
_syncify_wrap(t, 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
|
||||
)
|
||||
|
||||
event_cls = filter(None, (
|
||||
getattr(getattr(events, name), 'Event', None)
|
||||
for name in dir(events)
|
||||
))
|
||||
_syncify(TelegramClient, Draft, Dialog, MessageButton, ChatGetter,
|
||||
SenderGetter, Forward, Message, InlineResult, Conversation,
|
||||
*event_cls,
|
||||
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