mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-02-18 04:20:57 +03:00
Make raw API types immutable
This commit is contained in:
parent
d426099bf5
commit
070af28e85
|
@ -775,3 +775,5 @@ raise_last_call_error is now the default rather than ValueError
|
|||
self-produced updates like getmessage now also trigger a handler
|
||||
|
||||
input_peer removed from get_me; input peers should remain mostly an impl detail
|
||||
|
||||
raw api types and fns are now immutable. this can enable optimizations in the future.
|
||||
|
|
|
@ -8,6 +8,7 @@ import time
|
|||
import typing
|
||||
import ipaddress
|
||||
import dataclasses
|
||||
import functools
|
||||
|
||||
from .. import version, __name__ as __base_name__, _tl
|
||||
from .._crypto import rsa
|
||||
|
@ -182,7 +183,8 @@ def init(
|
|||
default_device_model = system.machine
|
||||
default_system_version = re.sub(r'-.+','',system.release)
|
||||
|
||||
self._init_request = _tl.fn.InitConnection(
|
||||
self._init_request = functools.partial(
|
||||
_tl.fn.InitConnection,
|
||||
api_id=self._api_id,
|
||||
device_model=device_model or default_device_model or 'Unknown',
|
||||
system_version=system_version or default_system_version or '1.0',
|
||||
|
@ -190,8 +192,6 @@ def init(
|
|||
lang_code=lang_code,
|
||||
system_lang_code=system_lang_code,
|
||||
lang_pack='', # "langPacks are for official apps only"
|
||||
query=None,
|
||||
proxy=None
|
||||
)
|
||||
|
||||
self._sender = MTProtoSender(
|
||||
|
@ -272,10 +272,8 @@ async def connect(self: 'TelegramClient') -> None:
|
|||
# Need to send invokeWithLayer for things to work out.
|
||||
# Make the most out of this opportunity by also refreshing our state.
|
||||
# During the v1 to v2 migration, this also correctly sets the IPv* columns.
|
||||
self._init_request.query = _tl.fn.help.GetConfig()
|
||||
|
||||
config = await self._sender.send(_tl.fn.InvokeWithLayer(
|
||||
_tl.LAYER, self._init_request
|
||||
_tl.LAYER, self._init_request(query=_tl.fn.help.GetConfig())
|
||||
))
|
||||
|
||||
for dc in config.dc_options:
|
||||
|
@ -318,7 +316,6 @@ async def disconnect(self: 'TelegramClient'):
|
|||
def set_proxy(self: 'TelegramClient', proxy: typing.Union[tuple, dict]):
|
||||
init_proxy = None
|
||||
|
||||
self._init_request.proxy = init_proxy
|
||||
self._proxy = proxy
|
||||
|
||||
# While `await client.connect()` passes new proxy on each new call,
|
||||
|
@ -408,8 +405,9 @@ async def _create_exported_sender(self: 'TelegramClient', dc_id):
|
|||
))
|
||||
self._log[__name__].info('Exporting auth for new borrowed sender in %s', dc)
|
||||
auth = await self(_tl.fn.auth.ExportAuthorization(dc_id))
|
||||
self._init_request.query = _tl.fn.auth.ImportAuthorization(id=auth.id, bytes=auth.bytes)
|
||||
req = _tl.fn.InvokeWithLayer(_tl.LAYER, self._init_request)
|
||||
req = _tl.fn.InvokeWithLayer(_tl.LAYER, self._init_request(
|
||||
query=_tl.fn.auth.ImportAuthorization(id=auth.id, bytes=auth.bytes)
|
||||
))
|
||||
await sender.send(req)
|
||||
return sender
|
||||
|
||||
|
|
|
@ -35,10 +35,11 @@ async def _call(self: 'TelegramClient', sender, request, ordered=False, flood_sl
|
|||
if flood_sleep_threshold is None:
|
||||
flood_sleep_threshold = self.flood_sleep_threshold
|
||||
requests = (request if utils.is_list_like(request) else (request,))
|
||||
new_requests = []
|
||||
for r in requests:
|
||||
if not isinstance(r, _tl.TLRequest):
|
||||
raise _NOT_A_REQUEST()
|
||||
await r.resolve(self, utils)
|
||||
r = await r.resolve(self, utils)
|
||||
|
||||
# Avoid making the request if it's already in a flood wait
|
||||
if r.CONSTRUCTOR_ID in self._flood_waited_requests:
|
||||
|
@ -59,6 +60,9 @@ async def _call(self: 'TelegramClient', sender, request, ordered=False, flood_sl
|
|||
if self._no_updates:
|
||||
r = _tl.fn.InvokeWithoutUpdates(r)
|
||||
|
||||
new_requests.append(r)
|
||||
request = new_requests if utils.is_list_like(request) else new_requests[0]
|
||||
|
||||
request_index = 0
|
||||
last_error = None
|
||||
self._last_request = time.time()
|
||||
|
|
|
@ -155,4 +155,4 @@ class TLRequest(TLObject):
|
|||
return reader.tgread_object()
|
||||
|
||||
async def resolve(self, client, utils):
|
||||
pass
|
||||
return self
|
||||
|
|
|
@ -85,6 +85,9 @@ def _write_modules(
|
|||
# Import struct for the .__bytes__(self) serialization
|
||||
builder.writeln('import struct')
|
||||
|
||||
# Import dataclasses in order to freeze the instances
|
||||
builder.writeln('import dataclasses')
|
||||
|
||||
# Import datetime for type hinting
|
||||
builder.writeln('from datetime import datetime')
|
||||
|
||||
|
@ -187,37 +190,9 @@ def _write_source_code(tlobject, kind, builder, type_constructors):
|
|||
def _write_class_init(tlobject, kind, type_constructors, builder):
|
||||
builder.writeln()
|
||||
builder.writeln()
|
||||
builder.writeln('@dataclasses.dataclass(init=False, frozen=True)')
|
||||
builder.writeln('class {}({}):', tlobject.class_name, kind)
|
||||
|
||||
# Define slots to help reduce the size of the objects a little bit.
|
||||
# It's also good for knowing what fields an object has.
|
||||
builder.write('__slots__ = (')
|
||||
sep = ''
|
||||
for arg in tlobject.real_args:
|
||||
builder.write('{}{!r},', sep, arg.name)
|
||||
sep = ' '
|
||||
builder.writeln(')')
|
||||
|
||||
# Class-level variable to store its Telegram's constructor ID
|
||||
builder.writeln('CONSTRUCTOR_ID = {:#x}', tlobject.id)
|
||||
builder.writeln('SUBCLASS_OF_ID = {:#x}',
|
||||
crc32(tlobject.result.encode('ascii')))
|
||||
builder.writeln()
|
||||
|
||||
# Convert the args to string parameters, flags having =None
|
||||
args = ['{}: {}{}'.format(
|
||||
a.name, a.type_hint(), '=None' if a.is_flag or a.can_be_inferred else '')
|
||||
for a in tlobject.real_args
|
||||
]
|
||||
|
||||
# Write the __init__ function if it has any argument
|
||||
if not tlobject.real_args:
|
||||
return
|
||||
|
||||
if any(a.name in dir(builtins) for a in tlobject.real_args):
|
||||
builder.writeln('# noinspection PyShadowingBuiltins')
|
||||
|
||||
builder.writeln("def __init__({}):", ', '.join(['self'] + args))
|
||||
builder.writeln('"""')
|
||||
if tlobject.is_function:
|
||||
builder.write(':returns {}: ', tlobject.result)
|
||||
|
@ -236,47 +211,83 @@ def _write_class_init(tlobject, kind, type_constructors, builder):
|
|||
|
||||
builder.writeln('"""')
|
||||
|
||||
# Set the arguments
|
||||
# Define slots to help reduce the size of the objects a little bit.
|
||||
# It's also good for knowing what fields an object has.
|
||||
builder.write('__slots__ = (')
|
||||
sep = ''
|
||||
for arg in tlobject.real_args:
|
||||
if not arg.can_be_inferred:
|
||||
builder.writeln('self.{0} = {0}', arg.name)
|
||||
builder.write('{}{!r},', sep, arg.name)
|
||||
sep = ' '
|
||||
builder.writeln(')')
|
||||
|
||||
# Currently the only argument that can be
|
||||
# inferred are those called 'random_id'
|
||||
elif arg.name == 'random_id':
|
||||
# Endianness doesn't really matter, and 'big' is shorter
|
||||
code = "int.from_bytes(os.urandom({}), 'big', signed=True)" \
|
||||
.format(8 if arg.type == 'long' else 4)
|
||||
# Class-level variable to store its Telegram's constructor ID
|
||||
builder.writeln('CONSTRUCTOR_ID = {:#x}', tlobject.id)
|
||||
builder.writeln('SUBCLASS_OF_ID = {:#x}',
|
||||
crc32(tlobject.result.encode('ascii')))
|
||||
builder.writeln()
|
||||
|
||||
if arg.is_vector:
|
||||
# Currently for the case of "messages.forwardMessages"
|
||||
# Ensure we can infer the length from id:Vector<>
|
||||
if not next(a for a in tlobject.real_args
|
||||
if a.name == 'id').is_vector:
|
||||
raise ValueError(
|
||||
'Cannot infer list of random ids for ', tlobject
|
||||
)
|
||||
code = '[{} for _ in range(len(id))]'.format(code)
|
||||
# Because we're using __slots__ and frozen instances, we cannot have flags = None directly.
|
||||
# See https://stackoverflow.com/q/50180735 (Python 3.10 does offer a solution).
|
||||
# Write the __init__ function if it has any argument.
|
||||
if tlobject.real_args:
|
||||
# Convert the args to string parameters
|
||||
for a in tlobject.real_args:
|
||||
builder.writeln('{}: {}', a.name, a.type_hint())
|
||||
|
||||
builder.writeln(
|
||||
"self.random_id = random_id if random_id "
|
||||
"is not None else {}", code
|
||||
)
|
||||
else:
|
||||
raise ValueError('Cannot infer a value for ', arg)
|
||||
# Convert the args to string parameters, flags having =None
|
||||
args = ['{}: {}{}'.format(
|
||||
a.name, a.type_hint(), '=None' if a.is_flag or a.can_be_inferred else '')
|
||||
for a in tlobject.real_args
|
||||
]
|
||||
|
||||
builder.end_block()
|
||||
if any(a.name in dir(builtins) for a in tlobject.real_args):
|
||||
builder.writeln('# noinspection PyShadowingBuiltins')
|
||||
|
||||
builder.writeln("def __init__({}):", ', '.join(['self'] + args))
|
||||
|
||||
# Set the arguments
|
||||
for arg in tlobject.real_args:
|
||||
builder.writeln("object.__setattr__(self, '{0}', {0})", arg.name)
|
||||
|
||||
builder.end_block()
|
||||
|
||||
|
||||
def _write_resolve(tlobject, builder):
|
||||
if tlobject.is_function and any(
|
||||
(arg.type in AUTO_CASTS
|
||||
or ((arg.name, arg.type) in NAMED_AUTO_CASTS
|
||||
and tlobject.fullname not in NAMED_BLACKLIST))
|
||||
for arg in tlobject.real_args
|
||||
(arg.can_be_inferred
|
||||
or arg.type in AUTO_CASTS
|
||||
or ((arg.name, arg.type) in NAMED_AUTO_CASTS and tlobject.fullname not in NAMED_BLACKLIST))
|
||||
for arg in tlobject.real_args
|
||||
):
|
||||
builder.writeln('async def resolve(self, client, utils):')
|
||||
builder.writeln('r = {}') # hold replacements
|
||||
|
||||
for arg in tlobject.real_args:
|
||||
if arg.can_be_inferred:
|
||||
builder.writeln('if self.{} is None:', arg.name)
|
||||
|
||||
# Currently the only argument that can be
|
||||
# inferred are those called 'random_id'
|
||||
if arg.name == 'random_id':
|
||||
# Endianness doesn't really matter, and 'big' is shorter
|
||||
code = "int.from_bytes(os.urandom({}), 'big', signed=True)" \
|
||||
.format(8 if arg.type == 'long' else 4)
|
||||
|
||||
if arg.is_vector:
|
||||
# Currently for the case of "messages.forwardMessages"
|
||||
# Ensure we can infer the length from id:Vector<>
|
||||
if not next(a for a in tlobject.real_args if a.name == 'id').is_vector:
|
||||
raise ValueError('Cannot infer list of random ids for ', tlobject)
|
||||
|
||||
code = '[{} for _ in range(len(self.id))]'.format(code)
|
||||
|
||||
builder.writeln("r['{}'] = {}", arg.name, code)
|
||||
else:
|
||||
raise ValueError('Cannot infer a value for ', arg)
|
||||
|
||||
builder.end_block()
|
||||
continue
|
||||
|
||||
ac = AUTO_CASTS.get(arg.type)
|
||||
if not ac:
|
||||
ac = NAMED_AUTO_CASTS.get((arg.name, arg.type))
|
||||
|
@ -287,17 +298,17 @@ def _write_resolve(tlobject, builder):
|
|||
builder.writeln('if self.{}:', arg.name)
|
||||
|
||||
if arg.is_vector:
|
||||
builder.writeln('_tmp = []')
|
||||
builder.writeln('for _x in self.{0}:', arg.name)
|
||||
builder.writeln('_tmp.append({})', ac.format('_x'))
|
||||
builder.writeln("r['{}'] = []", arg.name)
|
||||
builder.writeln('for x in self.{0}:', arg.name)
|
||||
builder.writeln("r['{}'].append({})", arg.name, ac.format('x'))
|
||||
builder.end_block()
|
||||
builder.writeln('self.{} = _tmp', arg.name)
|
||||
else:
|
||||
builder.writeln('self.{} = {}', arg.name,
|
||||
ac.format('self.' + arg.name))
|
||||
builder.writeln("r['{}'] = {}", arg.name, ac.format('self.' + arg.name))
|
||||
|
||||
if arg.is_flag:
|
||||
builder.end_block()
|
||||
|
||||
builder.writeln('return dataclasses.replace(self, **r)')
|
||||
builder.end_block()
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user