mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2024-11-29 12:53:44 +03:00
Properly set future results
This commit is contained in:
parent
9477c75fce
commit
56b09c0c9d
|
@ -79,11 +79,34 @@ class MTProtoSender:
|
||||||
self._recv_loop_handle.cancel()
|
self._recv_loop_handle.cancel()
|
||||||
|
|
||||||
async def send(self, request):
|
async def send(self, request):
|
||||||
# TODO Should the asyncio.Future creation belong here?
|
"""
|
||||||
request.result = asyncio.Future()
|
This method enqueues the given request to be sent.
|
||||||
|
|
||||||
|
The request will be wrapped inside a `TLMessage` until its
|
||||||
|
response arrives, and the `Future` response of the `TLMessage`
|
||||||
|
is immediately returned so that one can further ``await`` it:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
async def method():
|
||||||
|
# Sending (enqueued for the send loop)
|
||||||
|
future = await sender.send(request)
|
||||||
|
# Receiving (waits for the receive loop to read the result)
|
||||||
|
result = await future
|
||||||
|
|
||||||
|
Designed like this because Telegram may send the response at
|
||||||
|
any point, and it can send other items while one waits for it.
|
||||||
|
Once the response for this future arrives, it is set with the
|
||||||
|
received result, quite similar to how a ``receive()`` call
|
||||||
|
would otherwise work.
|
||||||
|
|
||||||
|
Since the receiving part is "built in" the future, it's
|
||||||
|
impossible to await receive a result that was never sent.
|
||||||
|
"""
|
||||||
message = TLMessage(self.session, request)
|
message = TLMessage(self.session, request)
|
||||||
self._pending_messages[message.msg_id] = message
|
self._pending_messages[message.msg_id] = message
|
||||||
await self._send_queue.put(message)
|
await self._send_queue.put(message)
|
||||||
|
return message.future
|
||||||
|
|
||||||
# Loops
|
# Loops
|
||||||
|
|
||||||
|
@ -129,7 +152,7 @@ class MTProtoSender:
|
||||||
inner_code = reader.read_int(signed=False)
|
inner_code = reader.read_int(signed=False)
|
||||||
reader.seek(-4)
|
reader.seek(-4)
|
||||||
|
|
||||||
message = self._pending_messages.pop(message_id)
|
message = self._pending_messages.pop(message_id, None)
|
||||||
if inner_code == 0x2144ca19: # RPC Error
|
if inner_code == 0x2144ca19: # RPC Error
|
||||||
reader.seek(4)
|
reader.seek(4)
|
||||||
if self.session.report_errors and message:
|
if self.session.report_errors and message:
|
||||||
|
@ -142,17 +165,23 @@ class MTProtoSender:
|
||||||
reader.read_int(), reader.tgread_string()
|
reader.read_int(), reader.tgread_string()
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO Acknowledge that we received the error request_id
|
await self._send_queue.put(
|
||||||
# TODO Set message.request exception
|
TLMessage(self.session, MsgsAck([msg_id])))
|
||||||
|
|
||||||
|
if not message.future.cancelled():
|
||||||
|
message.future.set_exception(error)
|
||||||
|
return
|
||||||
elif message:
|
elif message:
|
||||||
# TODO Make on_response result.set_result() instead replacing it
|
|
||||||
if inner_code == GzipPacked.CONSTRUCTOR_ID:
|
if inner_code == GzipPacked.CONSTRUCTOR_ID:
|
||||||
with BinaryReader(GzipPacked.read(reader)) as compressed_reader:
|
with BinaryReader(GzipPacked.read(reader)) as compressed_reader:
|
||||||
message.on_response(compressed_reader)
|
result = message.request.read_result(compressed_reader)
|
||||||
else:
|
else:
|
||||||
message.on_response(reader)
|
result = message.request.read_result(reader)
|
||||||
|
|
||||||
# TODO Process possible entities
|
# TODO Process possible entities
|
||||||
|
if not message.future.cancelled():
|
||||||
|
message.future.set_result(result)
|
||||||
|
return
|
||||||
|
|
||||||
# TODO Try reading an object
|
# TODO Try reading an object
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import asyncio
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
from . import TLObject, GzipPacked
|
from . import TLObject, GzipPacked
|
||||||
|
@ -5,7 +6,20 @@ from ..tl.functions import InvokeAfterMsgRequest
|
||||||
|
|
||||||
|
|
||||||
class TLMessage(TLObject):
|
class TLMessage(TLObject):
|
||||||
"""https://core.telegram.org/mtproto/service_messages#simple-container"""
|
"""
|
||||||
|
https://core.telegram.org/mtproto/service_messages#simple-container.
|
||||||
|
|
||||||
|
Messages are what's ultimately sent to Telegram:
|
||||||
|
message msg_id:long seqno:int bytes:int body:bytes = Message;
|
||||||
|
|
||||||
|
Each message has its own unique identifier, and the body is simply
|
||||||
|
the serialized request that should be executed on the server. Then
|
||||||
|
Telegram will, at some point, respond with the result for this msg.
|
||||||
|
|
||||||
|
Thus it makes sense that requests and their result are bound to a
|
||||||
|
sent `TLMessage`, and this result can be represented as a `Future`
|
||||||
|
that will eventually be set with either a result, error or cancelled.
|
||||||
|
"""
|
||||||
def __init__(self, session, request, after_id=None):
|
def __init__(self, session, request, after_id=None):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
del self.content_related
|
del self.content_related
|
||||||
|
@ -13,6 +27,7 @@ class TLMessage(TLObject):
|
||||||
self.seq_no = session.generate_sequence(request.content_related)
|
self.seq_no = session.generate_sequence(request.content_related)
|
||||||
self.request = request
|
self.request = request
|
||||||
self.container_msg_id = None
|
self.container_msg_id = None
|
||||||
|
self.future = asyncio.Future()
|
||||||
|
|
||||||
# After which message ID this one should run. We do this so
|
# After which message ID this one should run. We do this so
|
||||||
# InvokeAfterMsgRequest is transparent to the user and we can
|
# InvokeAfterMsgRequest is transparent to the user and we can
|
||||||
|
|
|
@ -5,36 +5,10 @@ from threading import Event
|
||||||
|
|
||||||
class TLObject:
|
class TLObject:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.rpc_error = None
|
# TODO Perhaps content_related makes more sense as another type?
|
||||||
self.result = None # An asyncio.Future set later
|
# Something like class TLRequest(TLObject), request inherit this
|
||||||
|
|
||||||
# These should be overrode
|
|
||||||
self.content_related = False # Only requests/functions/queries are
|
self.content_related = False # Only requests/functions/queries are
|
||||||
|
|
||||||
# Internal parameter to tell pickler in which state Event object was
|
|
||||||
self._event_is_set = False
|
|
||||||
self._set_event()
|
|
||||||
|
|
||||||
def _set_event(self):
|
|
||||||
self.confirm_received = Event()
|
|
||||||
|
|
||||||
# Set Event state to 'set' if needed
|
|
||||||
if self._event_is_set:
|
|
||||||
self.confirm_received.set()
|
|
||||||
|
|
||||||
def __getstate__(self):
|
|
||||||
# Save state of the Event object
|
|
||||||
self._event_is_set = self.confirm_received.is_set()
|
|
||||||
|
|
||||||
# Exclude Event object from dict and return new state
|
|
||||||
new_dct = dict(self.__dict__)
|
|
||||||
del new_dct["confirm_received"]
|
|
||||||
return new_dct
|
|
||||||
|
|
||||||
def __setstate__(self, state):
|
|
||||||
self.__dict__ = state
|
|
||||||
self._set_event()
|
|
||||||
|
|
||||||
# These should not be overrode
|
# These should not be overrode
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def pretty_format(obj, indent=None):
|
def pretty_format(obj, indent=None):
|
||||||
|
@ -164,8 +138,9 @@ class TLObject:
|
||||||
raise TypeError('Cannot interpret "{}" as a date.'.format(dt))
|
raise TypeError('Cannot interpret "{}" as a date.'.format(dt))
|
||||||
|
|
||||||
# These are nearly always the same for all subclasses
|
# These are nearly always the same for all subclasses
|
||||||
def on_response(self, reader):
|
@staticmethod
|
||||||
self.result = reader.tgread_object()
|
def read_result(reader):
|
||||||
|
return reader.tgread_object()
|
||||||
|
|
||||||
def __eq__(self, o):
|
def __eq__(self, o):
|
||||||
return isinstance(o, type(self)) and self.to_dict() == o.to_dict()
|
return isinstance(o, type(self)) and self.to_dict() == o.to_dict()
|
||||||
|
|
|
@ -142,7 +142,7 @@ def _write_source_code(tlobject, builder, type_constructors):
|
||||||
_write_to_dict(tlobject, builder)
|
_write_to_dict(tlobject, builder)
|
||||||
_write_to_bytes(tlobject, builder)
|
_write_to_bytes(tlobject, builder)
|
||||||
_write_from_reader(tlobject, builder)
|
_write_from_reader(tlobject, builder)
|
||||||
_write_on_response(tlobject, builder)
|
_write_read_result(tlobject, builder)
|
||||||
|
|
||||||
|
|
||||||
def _write_class_init(tlobject, type_constructors, builder):
|
def _write_class_init(tlobject, type_constructors, builder):
|
||||||
|
@ -333,7 +333,7 @@ def _write_from_reader(tlobject, builder):
|
||||||
'{0}=_{0}'.format(a.name) for a in tlobject.real_args))
|
'{0}=_{0}'.format(a.name) for a in tlobject.real_args))
|
||||||
|
|
||||||
|
|
||||||
def _write_on_response(tlobject, builder):
|
def _write_read_result(tlobject, builder):
|
||||||
# Only requests can have a different response that's not their
|
# Only requests can have a different response that's not their
|
||||||
# serialized body, that is, we'll be setting their .result.
|
# serialized body, that is, we'll be setting their .result.
|
||||||
#
|
#
|
||||||
|
@ -354,9 +354,10 @@ def _write_on_response(tlobject, builder):
|
||||||
return
|
return
|
||||||
|
|
||||||
builder.end_block()
|
builder.end_block()
|
||||||
builder.writeln('def on_response(self, reader):')
|
builder.writeln('@staticmethod')
|
||||||
|
builder.writeln('def read_result(reader):')
|
||||||
builder.writeln('reader.read_int() # Vector ID')
|
builder.writeln('reader.read_int() # Vector ID')
|
||||||
builder.writeln('self.result = [reader.read_{}() '
|
builder.writeln('return [reader.read_{}() '
|
||||||
'for _ in range(reader.read_int())]', m.group(1))
|
'for _ in range(reader.read_int())]', m.group(1))
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user