Document the extensions/ module

This commit is contained in:
Lonami Exo 2017-11-26 17:14:28 +01:00
parent 71eb542626
commit 57a70d0d47
3 changed files with 65 additions and 37 deletions

View File

@ -1,3 +1,6 @@
"""
This module contains the BinaryReader utility class.
"""
import os import os
from datetime import datetime from datetime import datetime
from io import BufferedReader, BytesIO from io import BufferedReader, BytesIO
@ -30,32 +33,32 @@ class BinaryReader:
# "All numbers are written as little endian." # "All numbers are written as little endian."
# https://core.telegram.org/mtproto # https://core.telegram.org/mtproto
def read_byte(self): def read_byte(self):
"""Reads a single byte value""" """Reads a single byte value."""
return self.read(1)[0] return self.read(1)[0]
def read_int(self, signed=True): def read_int(self, signed=True):
"""Reads an integer (4 bytes) value""" """Reads an integer (4 bytes) value."""
return int.from_bytes(self.read(4), byteorder='little', signed=signed) return int.from_bytes(self.read(4), byteorder='little', signed=signed)
def read_long(self, signed=True): def read_long(self, signed=True):
"""Reads a long integer (8 bytes) value""" """Reads a long integer (8 bytes) value."""
return int.from_bytes(self.read(8), byteorder='little', signed=signed) return int.from_bytes(self.read(8), byteorder='little', signed=signed)
def read_float(self): def read_float(self):
"""Reads a real floating point (4 bytes) value""" """Reads a real floating point (4 bytes) value."""
return unpack('<f', self.read(4))[0] return unpack('<f', self.read(4))[0]
def read_double(self): def read_double(self):
"""Reads a real floating point (8 bytes) value""" """Reads a real floating point (8 bytes) value."""
return unpack('<d', self.read(8))[0] return unpack('<d', self.read(8))[0]
def read_large_int(self, bits, signed=True): def read_large_int(self, bits, signed=True):
"""Reads a n-bits long integer value""" """Reads a n-bits long integer value."""
return int.from_bytes( return int.from_bytes(
self.read(bits // 8), byteorder='little', signed=signed) self.read(bits // 8), byteorder='little', signed=signed)
def read(self, length): def read(self, length):
"""Read the given amount of bytes""" """Read the given amount of bytes."""
result = self.reader.read(length) result = self.reader.read(length)
if len(result) != length: if len(result) != length:
raise BufferError( raise BufferError(
@ -67,7 +70,7 @@ class BinaryReader:
return result return result
def get_bytes(self): def get_bytes(self):
"""Gets the byte array representing the current buffer as a whole""" """Gets the byte array representing the current buffer as a whole."""
return self.stream.getvalue() return self.stream.getvalue()
# endregion # endregion
@ -75,8 +78,9 @@ class BinaryReader:
# region Telegram custom reading # region Telegram custom reading
def tgread_bytes(self): def tgread_bytes(self):
"""Reads a Telegram-encoded byte array, """
without the need of specifying its length Reads a Telegram-encoded byte array, without the need of
specifying its length.
""" """
first_byte = self.read_byte() first_byte = self.read_byte()
if first_byte == 254: if first_byte == 254:
@ -95,11 +99,11 @@ class BinaryReader:
return data return data
def tgread_string(self): def tgread_string(self):
"""Reads a Telegram-encoded string""" """Reads a Telegram-encoded string."""
return str(self.tgread_bytes(), encoding='utf-8', errors='replace') return str(self.tgread_bytes(), encoding='utf-8', errors='replace')
def tgread_bool(self): def tgread_bool(self):
"""Reads a Telegram boolean value""" """Reads a Telegram boolean value."""
value = self.read_int(signed=False) value = self.read_int(signed=False)
if value == 0x997275b5: # boolTrue if value == 0x997275b5: # boolTrue
return True return True
@ -110,13 +114,13 @@ class BinaryReader:
def tgread_date(self): def tgread_date(self):
"""Reads and converts Unix time (used by Telegram) """Reads and converts Unix time (used by Telegram)
into a Python datetime object into a Python datetime object.
""" """
value = self.read_int() value = self.read_int()
return None if value == 0 else datetime.utcfromtimestamp(value) return None if value == 0 else datetime.utcfromtimestamp(value)
def tgread_object(self): def tgread_object(self):
"""Reads a Telegram object""" """Reads a Telegram object."""
constructor_id = self.read_int(signed=False) constructor_id = self.read_int(signed=False)
clazz = tlobjects.get(constructor_id, None) clazz = tlobjects.get(constructor_id, None)
if clazz is None: if clazz is None:
@ -135,7 +139,7 @@ class BinaryReader:
return clazz.from_reader(self) return clazz.from_reader(self)
def tgread_vector(self): def tgread_vector(self):
"""Reads a vector (a list) of Telegram objects""" """Reads a vector (a list) of Telegram objects."""
if 0x1cb5c415 != self.read_int(signed=False): if 0x1cb5c415 != self.read_int(signed=False):
raise ValueError('Invalid constructor code, vector was expected') raise ValueError('Invalid constructor code, vector was expected')
@ -145,21 +149,23 @@ class BinaryReader:
# endregion # endregion
def close(self): def close(self):
"""Closes the reader, freeing the BytesIO stream."""
self.reader.close() self.reader.close()
# region Position related # region Position related
def tell_position(self): def tell_position(self):
"""Tells the current position on the stream""" """Tells the current position on the stream."""
return self.reader.tell() return self.reader.tell()
def set_position(self, position): def set_position(self, position):
"""Sets the current position on the stream""" """Sets the current position on the stream."""
self.reader.seek(position) self.reader.seek(position)
def seek(self, offset): def seek(self, offset):
"""Seeks the stream position given an offset from the """
current position. The offset may be negative Seeks the stream position given an offset from the current position.
The offset may be negative.
""" """
self.reader.seek(offset, os.SEEK_CUR) self.reader.seek(offset, os.SEEK_CUR)

View File

@ -27,12 +27,13 @@ DEFAULT_URL_RE = re.compile(b'\\[\0(.+?)\\]\0\\(\0(.+?)\\)\0')
def parse(message, delimiters=None, url_re=None): def parse(message, delimiters=None, url_re=None):
""" """
Parses the given message and returns the stripped message and a list Parses the given markdown message and returns its stripped representation
of MessageEntity* using the specified delimiters dictionary (or default plus a list of the MessageEntity's that were found.
if None). The dictionary should be a mapping {delimiter: entity class}.
The url_re(gex) must contain two matching groups: the text to be :param message: the message with markdown-like syntax to be parsed.
clickable and the URL itself, and be utf-16le encoded. :param delimiters: the delimiters to be used, {delimiter: type}.
:param url_re: the URL bytes regex to be used. Must have two groups.
:return: a tuple consisting of (clean message, [message entities]).
""" """
if url_re is None: if url_re is None:
url_re = DEFAULT_URL_RE url_re = DEFAULT_URL_RE
@ -130,8 +131,13 @@ def parse(message, delimiters=None, url_re=None):
def get_inner_text(text, entity): def get_inner_text(text, entity):
"""Gets the inner text that's surrounded by the given entity or entities. """
Gets the inner text that's surrounded by the given entity or entities.
For instance: text = 'hey!', entity = MessageEntityBold(2, 2) -> 'y!'. For instance: text = 'hey!', entity = MessageEntityBold(2, 2) -> 'y!'.
:param text: the original text.
:param entity: the entity or entities that must be matched.
:return: a single result or a list of the text surrounded by the entities.
""" """
if not isinstance(entity, TLObject) and hasattr(entity, '__iter__'): if not isinstance(entity, TLObject) and hasattr(entity, '__iter__'):
multiple = True multiple = True

View File

@ -1,4 +1,6 @@
# Python rough implementation of a C# TCP client """
This module holds a rough implementation of the C# TCP client.
"""
import errno import errno
import socket import socket
from datetime import timedelta from datetime import timedelta
@ -7,7 +9,14 @@ from threading import Lock
class TcpClient: class TcpClient:
"""A simple TCP client to ease the work with sockets and proxies."""
def __init__(self, proxy=None, timeout=timedelta(seconds=5)): def __init__(self, proxy=None, timeout=timedelta(seconds=5)):
"""
Initializes the TCP client.
:param proxy: the proxy to be used, if any.
:param timeout: the timeout for connect, read and write operations.
"""
self.proxy = proxy self.proxy = proxy
self._socket = None self._socket = None
self._closing_lock = Lock() self._closing_lock = Lock()
@ -33,8 +42,11 @@ class TcpClient:
self._socket.settimeout(self.timeout) self._socket.settimeout(self.timeout)
def connect(self, ip, port): def connect(self, ip, port):
"""Connects to the specified IP and port number. """
'timeout' must be given in seconds Tries connecting forever to IP:port unless an OSError is raised.
:param ip: the IP to connect to.
:param port: the port to connect to.
""" """
if ':' in ip: # IPv6 if ':' in ip: # IPv6
# The address needs to be surrounded by [] as discussed on PR#425 # The address needs to be surrounded by [] as discussed on PR#425
@ -65,12 +77,13 @@ class TcpClient:
raise raise
def _get_connected(self): def _get_connected(self):
"""Determines whether the client is connected or not."""
return self._socket is not None and self._socket.fileno() >= 0 return self._socket is not None and self._socket.fileno() >= 0
connected = property(fget=_get_connected) connected = property(fget=_get_connected)
def close(self): def close(self):
"""Closes the connection""" """Closes the connection."""
if self._closing_lock.locked(): if self._closing_lock.locked():
# Already closing, no need to close again (avoid None.close()) # Already closing, no need to close again (avoid None.close())
return return
@ -86,7 +99,11 @@ class TcpClient:
self._socket = None self._socket = None
def write(self, data): def write(self, data):
"""Writes (sends) the specified bytes to the connected peer""" """
Writes (sends) the specified bytes to the connected peer.
:param data: the data to send.
"""
if self._socket is None: if self._socket is None:
raise ConnectionResetError() raise ConnectionResetError()
@ -105,13 +122,11 @@ class TcpClient:
raise raise
def read(self, size): def read(self, size):
"""Reads (receives) a whole block of 'size bytes """
from the connected peer. Reads (receives) a whole block of size bytes from the connected peer.
A timeout can be specified, which will cancel the operation if :param size: the size of the block to be read.
no data has been read in the specified time. If data was read :return: the read data with len(data) == size.
and it's waiting for more, the timeout will NOT cancel the
operation. Set to None for no timeout
""" """
if self._socket is None: if self._socket is None:
raise ConnectionResetError() raise ConnectionResetError()
@ -141,5 +156,6 @@ class TcpClient:
return buffer.raw.getvalue() return buffer.raw.getvalue()
def _raise_connection_reset(self): def _raise_connection_reset(self):
"""Disconnects the client and raises ConnectionResetError."""
self.close() # Connection reset -> flag as socket closed self.close() # Connection reset -> flag as socket closed
raise ConnectionResetError('The server has closed the connection.') raise ConnectionResetError('The server has closed the connection.')