From 769692959f1efa61d11c3926bf7b571f8d4686e2 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 12 Jun 2017 10:43:43 +0200 Subject: [PATCH] Make the TLGenerator class a lot more readable --- telethon_generator/tl_generator.py | 449 +++++++++++++++-------------- 1 file changed, 235 insertions(+), 214 deletions(-) diff --git a/telethon_generator/tl_generator.py b/telethon_generator/tl_generator.py index 8adf3dfe..22c123c8 100755 --- a/telethon_generator/tl_generator.py +++ b/telethon_generator/tl_generator.py @@ -110,223 +110,14 @@ class TLGenerator: TLGenerator.get_class_name(tlobject))) # Create the file for this TLObject - filename = os.path.join( - out_dir, - TLGenerator.get_file_name( - tlobject, add_extension=True)) + filename = os.path.join(out_dir, TLGenerator.get_file_name( + tlobject, add_extension=True + )) with open(filename, 'w', encoding='utf-8') as file: - # Let's build the source code! with SourceBuilder(file) as builder: - # Both types and functions inherit from - # MTProtoRequest so they all can be sent - builder.writeln('from {}.tl.mtproto_request import MTProtoRequest' - .format('.' * depth)) - - if any(a for a in tlobject.args if a.can_be_inferred): - # Currently only 'random_id' needs 'os' to be imported - builder.writeln('import os') - - builder.writeln() - builder.writeln() - builder.writeln('class {}(MTProtoRequest):'.format( - TLGenerator.get_class_name(tlobject))) - - # Write the original .tl definition, - # along with a "generated automatically" message - builder.writeln( - '"""Class generated by TLObjects\' generator. ' - 'All changes will be ERASED. TL definition below.') - builder.writeln('{}"""'.format(repr(tlobject))) - builder.writeln() - - # Class-level variable to store its constructor ID - builder.writeln( - "# Telegram's constructor (U)ID for this class") - builder.writeln('constructor_id = {}'.format( - hex(tlobject.id))) - builder.writeln() - - # Flag arguments must go last - args = [ - a for a in tlobject.sorted_args() - if not a.flag_indicator and not a.generic_definition - ] - - # Convert the args to string parameters, flags having =None - args = [ - (a.name if not a.is_flag and not a.can_be_inferred - else '{}=None'.format(a.name)) - for a in args - ] - - # Write the __init__ function - if args: - builder.writeln('def __init__(self, {}):'.format( - ', '.join(args))) - else: - builder.writeln('def __init__(self):') - - # Now update args to have the TLObject arguments, _except_ - # those which are calculated on send or ignored, this is - # flag indicator and generic definitions. - # - # We don't need the generic definitions in Python - # because arguments can be any type - args = [arg for arg in tlobject.args - if not arg.flag_indicator and - not arg.generic_definition] - - if args: - # Write the docstring, to know the type of the args - builder.writeln('"""') - for arg in args: - if not arg.flag_indicator: - builder.write( - ':param {}: Telegram type: "{}".' - .format(arg.name, arg.type) - ) - if arg.is_vector: - builder.write( - ' Must be a list.'.format(arg.name) - ) - if arg.is_generic: - builder.write( - ' Must be another MTProtoRequest.' - ) - builder.writeln() - - # We also want to know what type this request returns - # or to which type this constructor belongs to - builder.writeln() - if tlobject.is_function: - builder.write(':returns %s: ' % tlobject.result) - else: - builder.write('Constructor for %s: ' % 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 {}.'.format( - TLGenerator.get_class_name(constructors[0]) - )) - else: - builder.writeln('Instance of either {}.'.format( - ', '.join(TLGenerator.get_class_name(c) - for c in constructors) - )) - - builder.writeln('"""') - - builder.writeln('super().__init__()') - # Functions have a result object and are confirmed by default - if tlobject.is_function: - builder.writeln('self.result = None') - builder.writeln( - 'self.confirmed = True # Confirmed by default') - - # Set the arguments - if args: - # Leave an empty line if there are any args - builder.writeln() - - for arg in args: - if arg.can_be_inferred: - # Currently the only argument that can be - # inferred are those called 'random_id' - if arg.name == 'random_id': - builder.writeln( - "self.random_id = random_id if random_id " - "is not None else int.from_bytes(" - "os.urandom({}), signed=True, " - "byteorder='little')" - .format(8 if arg.type == 'long' else 4) - ) - else: - raise ValueError( - 'Cannot infer a value for ', arg) - else: - builder.writeln('self.{0} = {0}' - .format(arg.name)) - - builder.end_block() - - # Write the to_dict(self) method - builder.writeln('def to_dict(self):') - if args: - builder.writeln('return {') - - base_types = ['string', 'bytes', 'int', 'long', - 'int128', 'int256', 'double', 'Bool', - 'true', 'date'] - - for arg in args: - builder.writeln("'{}': ".format(arg.name)) - if arg.is_vector: - builder.writeln('[x{} for x in self.{}] if self.{} is not None else [],' - .format('.to_dict() if x is not None else None' - if arg.type not in base_types else '', - arg.name, arg.name)) - else: - builder.writeln( - 'self.{}{},'.format( - arg.name, - '.to_dict() if self.{} is not None else None' - .format(arg.name) if arg.type not in base_types else '')) - builder.write("}") - else: - builder.writeln('return {}') - builder.writeln() - builder.end_block() - - # Write the on_send(self, writer) function - builder.writeln('def on_send(self, writer):') - builder.writeln( - 'writer.write_int({}.constructor_id, signed=False)' - .format(TLGenerator.get_class_name(tlobject))) - - for arg in tlobject.args: - TLGenerator.write_onsend_code(builder, arg, - tlobject.args) - builder.end_block() - - # Write the empty() function, which returns an "empty" - # instance, in which all attributes are set to None - builder.writeln('@staticmethod') - builder.writeln('def empty():') - builder.writeln( - '"""Returns an "empty" instance (attributes=None)"""') - builder.writeln('return {}({})'.format( - TLGenerator.get_class_name(tlobject), ', '.join( - 'None' for _ in range(len(args))))) - builder.end_block() - - # Write the on_response(self, reader) function - builder.writeln('def on_response(self, reader):') - # Do not read constructor's ID, since - # that's already been read somewhere else - if tlobject.is_function: - TLGenerator.write_request_result_code(builder, tlobject) - else: - if tlobject.args: - for arg in tlobject.args: - TLGenerator.write_onresponse_code( - builder, arg, tlobject.args) - else: - # If there were no arguments, we still need an - # on_response method, and hence "pass" if empty - builder.writeln('pass') - builder.end_block() - - # Write the __repr__(self) and __str__(self) functions - builder.writeln('def __repr__(self):') - builder.writeln("return '{}'".format(repr(tlobject))) - builder.end_block() - - builder.writeln('def __str__(self):') - builder.writeln('return {}'.format(str(tlobject))) - # builder.end_block() # No need to end the last block + TLGenerator._write_source_code( + tlobject, builder, depth, type_constructors) # Step 3: Add the relative imports to the namespaces on __init__.py's init_py = os.path.join(get_output_path('functions'), '__init__.py') @@ -380,6 +171,236 @@ class TLGenerator: builder.current_indent -= 1 builder.writeln('}') + @staticmethod + def _write_source_code(tlobject, builder, depth, type_constructors): + """Writes the source code corresponding to the given TLObject + by making use of the 'builder' SourceBuilder. + + Additional information such as file path depth and + the Type: [Constructors] must be given for proper + importing and documentation strings. + '""" + + # Both types and functions inherit from + # MTProtoRequest so they all can be sent + builder.writeln('from {}.tl.mtproto_request import MTProtoRequest' + .format('.' * depth)) + + if any(a for a in tlobject.args if a.can_be_inferred): + # Currently only 'random_id' needs 'os' to be imported + builder.writeln('import os') + + builder.writeln() + builder.writeln() + builder.writeln('class {}(MTProtoRequest):'.format( + TLGenerator.get_class_name(tlobject))) + + # Write the original .tl definition, + # along with a "generated automatically" message + builder.writeln( + '"""Class generated by TLObjects\' generator. ' + 'All changes will be ERASED. TL definition below.' + ) + builder.writeln('{}"""'.format(repr(tlobject))) + builder.writeln() + + # Class-level variable to store its constructor ID + builder.writeln("# Telegram's constructor (U)ID for this class") + builder.writeln('constructor_id = {}'.format(hex(tlobject.id))) + builder.writeln() + + # Flag arguments must go last + args = [ + a for a in tlobject.sorted_args() + if not a.flag_indicator and not a.generic_definition + ] + + # Convert the args to string parameters, flags having =None + args = [ + (a.name if not a.is_flag and not a.can_be_inferred + else '{}=None'.format(a.name)) + for a in args + ] + + # Write the __init__ function + if args: + builder.writeln( + 'def __init__(self, {}):'.format(', '.join(args)) + ) + else: + builder.writeln('def __init__(self):') + + # Now update args to have the TLObject arguments, _except_ + # those which are calculated on send or ignored, this is + # flag indicator and generic definitions. + # + # We don't need the generic definitions in Python + # because arguments can be any type + args = [arg for arg in tlobject.args + if not arg.flag_indicator and + not arg.generic_definition] + + if args: + # Write the docstring, to know the type of the args + builder.writeln('"""') + for arg in args: + if not arg.flag_indicator: + builder.write( + ':param {}: Telegram type: "{}".' + .format(arg.name, arg.type) + ) + if arg.is_vector: + builder.write(' Must be a list.'.format(arg.name)) + + if arg.is_generic: + builder.write(' Must be another MTProtoRequest.') + + builder.writeln() + + # We also want to know what type this request returns + # or to which type this constructor belongs to + builder.writeln() + if tlobject.is_function: + builder.write(':returns {}: '.format(tlobject.result)) + else: + builder.write('Constructor for {}: '.format(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 {}.'.format( + TLGenerator.get_class_name(constructors[0]) + )) + else: + builder.writeln('Instance of either {}.'.format( + ', '.join(TLGenerator.get_class_name(c) + for c in constructors) + )) + + builder.writeln('"""') + + builder.writeln('super().__init__()') + # Functions have a result object and are confirmed by default + if tlobject.is_function: + builder.writeln('self.result = None') + builder.writeln( + 'self.confirmed = True # Confirmed by default') + + # Set the arguments + if args: + # Leave an empty line if there are any args + builder.writeln() + + for arg in args: + if arg.can_be_inferred: + # Currently the only argument that can be + # inferred are those called 'random_id' + if arg.name == 'random_id': + builder.writeln( + "self.random_id = random_id if random_id " + "is not None else int.from_bytes(" + "os.urandom({}), signed=True, " + "byteorder='little')" + .format(8 if arg.type == 'long' else 4) + ) + else: + raise ValueError('Cannot infer a value for ', arg) + else: + builder.writeln('self.{0} = {0}'.format(arg.name)) + + builder.end_block() + + # Write the to_dict(self) method + builder.writeln('def to_dict(self):') + if args: + builder.writeln('return {') + builder.current_indent += 1 + + base_types = ('string', 'bytes', 'int', 'long', 'int128', + 'int256', 'double', 'Bool', 'true', 'date') + + for arg in args: + builder.write("'{}': ".format(arg.name)) + if arg.type in base_types: + if arg.is_vector: + builder.write( + '[] if self.{0} is None else self.{0}[:]' + .format(arg.name) + ) + else: + builder.write('self.{}'.format(arg.name)) + else: + if arg.is_vector: + builder.write( + '[] if self.{0} is None else [None ' + 'if x is None else x.to_dict() for x in self.{0}]' + .format(arg.name) + ) + else: + builder.write( + 'None if self.{0} is None else self.{0}.to_dict()' + .format(arg.name) + ) + builder.writeln(',') + + builder.current_indent -= 1 + builder.write("}") + else: + builder.writeln('return {}') + + builder.writeln() + builder.end_block() + + # Write the on_send(self, writer) function + builder.writeln('def on_send(self, writer):') + builder.writeln( + 'writer.write_int({}.constructor_id, signed=False)' + .format(TLGenerator.get_class_name(tlobject))) + + for arg in tlobject.args: + TLGenerator.write_onsend_code(builder, arg, + tlobject.args) + builder.end_block() + + # Write the empty() function, which returns an "empty" + # instance, in which all attributes are set to None + builder.writeln('@staticmethod') + builder.writeln('def empty():') + builder.writeln( + '"""Returns an "empty" instance (attributes=None)"""') + builder.writeln('return {}({})'.format( + TLGenerator.get_class_name(tlobject), ', '.join( + 'None' for _ in range(len(args))))) + builder.end_block() + + # Write the on_response(self, reader) function + builder.writeln('def on_response(self, reader):') + # Do not read constructor's ID, since + # that's already been read somewhere else + if tlobject.is_function: + TLGenerator.write_request_result_code(builder, tlobject) + else: + if tlobject.args: + for arg in tlobject.args: + TLGenerator.write_onresponse_code( + builder, arg, tlobject.args) + else: + # If there were no arguments, we still need an + # on_response method, and hence "pass" if empty + builder.writeln('pass') + builder.end_block() + + # Write the __repr__(self) and __str__(self) functions + builder.writeln('def __repr__(self):') + builder.writeln("return '{}'".format(repr(tlobject))) + builder.end_block() + + builder.writeln('def __str__(self):') + builder.writeln('return {}'.format(str(tlobject))) + # builder.end_block() # No need to end the last block + + @staticmethod def get_class_name(tlobject): """Gets the class name following the Python style guidelines, in ThisClassFormat"""