Subclass TLRequest for content-related objects

This commit is contained in:
Lonami Exo 2018-06-12 20:05:05 +02:00
parent d1afc70963
commit 3f16c92eb3
7 changed files with 71 additions and 81 deletions

View File

@ -3,17 +3,17 @@ import itertools
from .telegrambaseclient import TelegramBaseClient from .telegrambaseclient import TelegramBaseClient
from .. import errors, utils from .. import errors, utils
from ..tl import TLObject, types, functions from ..tl import TLObject, TLRequest, types, functions
_NOT_A_REQUEST = TypeError('You can only invoke requests, not types!')
class UserMethods(TelegramBaseClient): class UserMethods(TelegramBaseClient):
async def __call__(self, request, retries=5, ordered=False): async def __call__(self, request, retries=5, ordered=False):
requests = (request,) if not utils.is_list_like(request) else request for r in (request if utils.is_list_like(request) else (request,)):
if not all(isinstance(x, TLObject) and if not isinstance(r, TLRequest):
x.content_related for x in requests): raise _NOT_A_REQUEST
raise TypeError('You can only invoke requests, not types!')
for r in requests:
await r.resolve(self, utils) await r.resolve(self, utils)
for _ in range(retries): for _ in range(retries):

View File

@ -8,6 +8,7 @@ from ..crypto import AES
from ..errors import SecurityError, BrokenAuthKeyError from ..errors import SecurityError, BrokenAuthKeyError
from ..extensions import BinaryReader from ..extensions import BinaryReader
from ..tl.core import TLMessage from ..tl.core import TLMessage
from ..tl.tlobject import TLRequest
__log__ = logging.getLogger(__name__) __log__ = logging.getLogger(__name__)
@ -43,7 +44,7 @@ class MTProtoState:
""" """
return TLMessage( return TLMessage(
msg_id=self._get_new_msg_id(), msg_id=self._get_new_msg_id(),
seq_no=self._get_seq_no(obj.content_related), seq_no=self._get_seq_no(isinstance(obj, TLRequest)),
obj=obj, obj=obj,
after_id=after.msg_id if after else None after_id=after.msg_id if after else None
) )

View File

@ -1 +1 @@
from .tlobject import TLObject from .tlobject import TLObject, TLRequest

View File

@ -1,7 +1,7 @@
import gzip import gzip
import struct import struct
from .. import TLObject from .. import TLObject, TLRequest
class GzipPacked(TLObject): class GzipPacked(TLObject):
@ -21,7 +21,7 @@ class GzipPacked(TLObject):
""" """
data = bytes(request) data = bytes(request)
# TODO This threshold could be configurable # TODO This threshold could be configurable
if request.content_related and len(data) > 512: if isinstance(request, TLRequest) and len(data) > 512:
gzipped = bytes(GzipPacked(data)) gzipped = bytes(GzipPacked(data))
return gzipped if len(gzipped) < len(data) else data return gzipped if len(gzipped) < len(data) else data
else: else:

View File

@ -11,13 +11,10 @@ class MessageContainer(TLObject):
CONSTRUCTOR_ID = 0x73f1f8dc CONSTRUCTOR_ID = 0x73f1f8dc
def __init__(self, messages): def __init__(self, messages):
super().__init__()
self.content_related = False
self.messages = messages self.messages = messages
def to_dict(self, recursive=True): def to_dict(self, recursive=True):
return { return {
'content_related': self.content_related,
'messages': 'messages':
([] if self.messages is None else [ ([] if self.messages is None else [
None if x is None else x.to_dict() for x in self.messages None if x is None else x.to_dict() for x in self.messages

View File

@ -3,16 +3,11 @@ from datetime import datetime, date
class TLObject: class TLObject:
def __init__(self):
# TODO Perhaps content_related makes more sense as another type?
# Something like class TLRequest(TLObject), request inherit this
self.content_related = False # Only requests/functions/queries are
# These should not be overrode
@staticmethod @staticmethod
def pretty_format(obj, indent=None): def pretty_format(obj, indent=None):
"""Pretty formats the given object as a string which is returned. """
If indent is None, a single line will be returned. Pretty formats the given object as a string which is returned.
If indent is None, a single line will be returned.
""" """
if indent is None: if indent is None:
if isinstance(obj, TLObject): if isinstance(obj, TLObject):
@ -136,11 +131,6 @@ 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
@staticmethod
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()
@ -153,16 +143,24 @@ class TLObject:
def stringify(self): def stringify(self):
return TLObject.pretty_format(self, indent=0) return TLObject.pretty_format(self, indent=0)
# These should be overrode
async def resolve(self, client, utils):
pass
def to_dict(self): def to_dict(self):
return {} raise NotImplementedError
def __bytes__(self): def __bytes__(self):
return b'' raise NotImplementedError
@classmethod @classmethod
def from_reader(cls, reader): def from_reader(cls, reader):
return TLObject() raise NotImplementedError
class TLRequest(TLObject):
"""
Represents a content-related `TLObject` (a request that can be sent).
"""
@staticmethod
def read_result(reader):
return reader.tgread_object()
async def resolve(self, client, utils):
pass

View File

@ -32,7 +32,8 @@ BASE_TYPES = ('string', 'bytes', 'int', 'long', 'int128',
'int256', 'double', 'Bool', 'true', 'date') 'int256', 'double', 'Bool', 'true', 'date')
def _write_modules(out_dir, depth, namespace_tlobjects, type_constructors): def _write_modules(
out_dir, depth, kind, namespace_tlobjects, type_constructors):
# namespace_tlobjects: {'namespace', [TLObject]} # namespace_tlobjects: {'namespace', [TLObject]}
os.makedirs(out_dir, exist_ok=True) os.makedirs(out_dir, exist_ok=True)
for ns, tlobjects in namespace_tlobjects.items(): for ns, tlobjects in namespace_tlobjects.items():
@ -41,7 +42,7 @@ def _write_modules(out_dir, depth, namespace_tlobjects, type_constructors):
SourceBuilder(f) as builder: SourceBuilder(f) as builder:
builder.writeln(AUTO_GEN_NOTICE) builder.writeln(AUTO_GEN_NOTICE)
builder.writeln('from {}.tl.tlobject import TLObject', '.' * depth) builder.writeln('from {}.tl.tlobject import {}', '.' * depth, kind)
builder.writeln('from typing import Optional, List, ' builder.writeln('from typing import Optional, List, '
'Union, TYPE_CHECKING') 'Union, TYPE_CHECKING')
@ -124,7 +125,7 @@ def _write_modules(out_dir, depth, namespace_tlobjects, type_constructors):
# Generate the class for every TLObject # Generate the class for every TLObject
for t in tlobjects: for t in tlobjects:
_write_source_code(t, builder, type_constructors) _write_source_code(t, kind, builder, type_constructors)
builder.current_indent = 0 builder.current_indent = 0
# Write the type definitions generated earlier. # Write the type definitions generated earlier.
@ -133,7 +134,7 @@ def _write_modules(out_dir, depth, namespace_tlobjects, type_constructors):
builder.writeln(line) builder.writeln(line)
def _write_source_code(tlobject, builder, type_constructors): def _write_source_code(tlobject, kind, builder, type_constructors):
""" """
Writes the source code corresponding to the given TLObject Writes the source code corresponding to the given TLObject
by making use of the ``builder`` `SourceBuilder`. by making use of the ``builder`` `SourceBuilder`.
@ -142,7 +143,7 @@ def _write_source_code(tlobject, builder, type_constructors):
the ``Type: [Constructors]`` must be given for proper the ``Type: [Constructors]`` must be given for proper
importing and documentation strings. importing and documentation strings.
""" """
_write_class_init(tlobject, type_constructors, builder) _write_class_init(tlobject, kind, type_constructors, builder)
_write_resolve(tlobject, builder) _write_resolve(tlobject, builder)
_write_to_dict(tlobject, builder) _write_to_dict(tlobject, builder)
_write_to_bytes(tlobject, builder) _write_to_bytes(tlobject, builder)
@ -150,10 +151,10 @@ def _write_source_code(tlobject, builder, type_constructors):
_write_read_result(tlobject, builder) _write_read_result(tlobject, builder)
def _write_class_init(tlobject, type_constructors, builder): def _write_class_init(tlobject, kind, type_constructors, builder):
builder.writeln() builder.writeln()
builder.writeln() builder.writeln()
builder.writeln('class {}(TLObject):', tlobject.class_name) builder.writeln('class {}({}):', tlobject.class_name, kind)
# Class-level variable to store its Telegram's constructor ID # Class-level variable to store its Telegram's constructor ID
builder.writeln('CONSTRUCTOR_ID = {:#x}', tlobject.id) builder.writeln('CONSTRUCTOR_ID = {:#x}', tlobject.id)
@ -165,46 +166,39 @@ def _write_class_init(tlobject, type_constructors, builder):
args = [(a.name if not a.is_flag and not a.can_be_inferred args = [(a.name if not a.is_flag and not a.can_be_inferred
else '{}=None'.format(a.name)) for a in tlobject.real_args] else '{}=None'.format(a.name)) for a in tlobject.real_args]
# Write the __init__ function # Write the __init__ function if it has any argument
if not tlobject.real_args:
return
builder.writeln('def __init__({}):', ', '.join(['self'] + args)) builder.writeln('def __init__({}):', ', '.join(['self'] + args))
if tlobject.real_args: # Write the docstring, to know the type of the args
# Write the docstring, to know the type of the args builder.writeln('"""')
builder.writeln('"""') for arg in tlobject.real_args:
for arg in tlobject.real_args: if not arg.flag_indicator:
if not arg.flag_indicator: builder.writeln(':param {} {}:', arg.type_hint(), arg.name)
builder.writeln(':param {} {}:', arg.type_hint(), arg.name) builder.current_indent -= 1 # It will auto-indent (':')
builder.current_indent -= 1 # It will auto-indent (':')
# We also want to know what type this request returns # We also want to know what type this request returns
# or to which type this constructor belongs to # or to which type this constructor belongs to
builder.writeln() builder.writeln()
if tlobject.is_function:
builder.write(':returns {}: ', tlobject.result)
else:
builder.write('Constructor for {}: ', tlobject.result)
constructors = type_constructors[tlobject.result]
if not constructors:
builder.writeln('This type has no constructors.')
elif len(constructors) == 1:
builder.writeln('Instance of {}.',
constructors[0].class_name)
else:
builder.writeln('Instance of either {}.', ', '.join(
c.class_name for c in constructors))
builder.writeln('"""')
builder.writeln('super().__init__()')
# Functions have a result object and are confirmed by default
if tlobject.is_function: if tlobject.is_function:
builder.writeln('self.result = None') builder.write(':returns {}: ', tlobject.result)
builder.writeln('self.content_related = True') else:
builder.write('Constructor for {}: ', tlobject.result)
constructors = type_constructors[tlobject.result]
if not constructors:
builder.writeln('This type has no constructors.')
elif len(constructors) == 1:
builder.writeln('Instance of {}.',
constructors[0].class_name)
else:
builder.writeln('Instance of either {}.', ', '.join(
c.class_name for c in constructors))
builder.writeln('"""')
# Set the arguments # Set the arguments
if tlobject.real_args:
builder.writeln()
for arg in tlobject.real_args: for arg in tlobject.real_args:
if not arg.can_be_inferred: if not arg.can_be_inferred:
builder.writeln('self.{0} = {0} # type: {1}', builder.writeln('self.{0} = {0} # type: {1}',
@ -453,7 +447,7 @@ def _write_arg_to_bytes(builder, arg, args, name=None):
builder.write("struct.pack('<d', {})", name) builder.write("struct.pack('<d', {})", name)
elif 'string' == arg.type: elif 'string' == arg.type:
builder.write('TLObject.serialize_bytes({})', name) builder.write('self.serialize_bytes({})', name)
elif 'Bool' == arg.type: elif 'Bool' == arg.type:
# 0x997275b5 if boolean else 0xbc799737 # 0x997275b5 if boolean else 0xbc799737
@ -463,10 +457,10 @@ def _write_arg_to_bytes(builder, arg, args, name=None):
pass # These are actually NOT written! Only used for flags pass # These are actually NOT written! Only used for flags
elif 'bytes' == arg.type: elif 'bytes' == arg.type:
builder.write('TLObject.serialize_bytes({})', name) builder.write('self.serialize_bytes({})', name)
elif 'date' == arg.type: # Custom format elif 'date' == arg.type: # Custom format
builder.write('TLObject.serialize_datetime({})', name) builder.write('self.serialize_datetime({})', name)
else: else:
# Else it may be a custom type # Else it may be a custom type
@ -651,9 +645,9 @@ def generate_tlobjects(tlobjects, layer, import_depth, output_dir):
namespace_types[tlobject.namespace].append(tlobject) namespace_types[tlobject.namespace].append(tlobject)
type_constructors[tlobject.result].append(tlobject) type_constructors[tlobject.result].append(tlobject)
_write_modules(get_file('functions'), import_depth, _write_modules(get_file('functions'), import_depth, 'TLRequest',
namespace_functions, type_constructors) namespace_functions, type_constructors)
_write_modules(get_file('types'), import_depth, _write_modules(get_file('types'), import_depth, 'TLObject',
namespace_types, type_constructors) namespace_types, type_constructors)
filename = os.path.join(get_file('all_tlobjects.py')) filename = os.path.join(get_file('all_tlobjects.py'))