mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2024-11-29 12:53:44 +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
|
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.**
|
If you have old code, **just use old versions** of the library! There is
|
||||||
It comes with a cost, and new projects will greatly benefit from using
|
nothing wrong with that other than not getting new updates or fixes, but
|
||||||
``asyncio`` by default such as increased speed and easier reasoning about
|
using a fixed version with ``pip install telethon==0.19.1.6`` is easy
|
||||||
the code flow. You should only enable it for old projects you don't have
|
enough to do.
|
||||||
the time to upgrade to ``asyncio``.
|
|
||||||
|
|
||||||
There exists a fair amount of code online using Telethon before it reached
|
You might want to consider using `Virtual Environments
|
||||||
its 1.0 version, where it became fully asynchronous by default. Since it was
|
<https://docs.python.org/3/tutorial/venv.html>`_ in your projects.
|
||||||
necessary to clean some things, compatibility was not kept 100% but the
|
|
||||||
changes are simple:
|
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
|
.. code-block:: python
|
||||||
|
|
||||||
# 1. The library no longer uses threads.
|
# 1. Import the client from telethon.sync
|
||||||
# Add this at the **beginning** of your script to work around that.
|
from telethon.sync import TelegramClient
|
||||||
from telethon import full_sync
|
|
||||||
full_sync.enable()
|
|
||||||
|
|
||||||
# 2. client.connect() no longer returns True.
|
# 2. Change this monster...
|
||||||
# Change this...
|
try:
|
||||||
assert client.connect()
|
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:
|
# ...for this:
|
||||||
client.connect()
|
with client:
|
||||||
|
... # REST OF YOUR CODE
|
||||||
|
|
||||||
# 3. client.idle() no longer exists.
|
# 3. client.idle() no longer exists.
|
||||||
# Change this...
|
# Change this...
|
||||||
|
@ -52,11 +69,10 @@ changes are simple:
|
||||||
# ...to this:
|
# ...to this:
|
||||||
client.add_event_handler(handler)
|
client.add_event_handler(handler)
|
||||||
|
|
||||||
# 5. It's good practice to stop the full_sync mode once you're done
|
|
||||||
try:
|
In addition, all the update handlers must be ``async def``, and you need
|
||||||
... # all your code in here
|
to ``await`` method calls that rely on network requests, such as getting
|
||||||
finally:
|
the chat or sender. If you don't use updates, you're done!
|
||||||
full_sync.stop()
|
|
||||||
|
|
||||||
|
|
||||||
Convenience
|
Convenience
|
||||||
|
@ -75,8 +91,8 @@ Convenience
|
||||||
This makes the examples shorter and easier to think about.
|
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
|
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.
|
forget about ``asyncio`` and just work with sequential code. This can prove
|
||||||
This can prove to be a powerful hybrid for running under the Python REPL too.
|
to be a powerful hybrid for running under the Python REPL too.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
@ -122,7 +138,7 @@ Speed
|
||||||
|
|
||||||
When you're ready to micro-optimize your application, or if you simply
|
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,
|
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
|
.. code-block:: python
|
||||||
|
|
||||||
|
|
|
@ -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, full_sync
|
from . import version, events, utils, errors
|
||||||
|
|
||||||
|
|
||||||
__version__ = version.__version__
|
__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