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
from datetime import datetime
from io import BufferedReader, BytesIO
@ -30,32 +33,32 @@ class BinaryReader:
# "All numbers are written as little endian."
# https://core.telegram.org/mtproto
def read_byte(self):
"""Reads a single byte value"""
"""Reads a single byte value."""
return self.read(1)[0]
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)
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)
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]
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]
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(
self.read(bits // 8), byteorder='little', signed=signed)
def read(self, length):
"""Read the given amount of bytes"""
"""Read the given amount of bytes."""
result = self.reader.read(length)
if len(result) != length:
raise BufferError(
@ -67,7 +70,7 @@ class BinaryReader:
return result
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()
# endregion
@ -75,8 +78,9 @@ class BinaryReader:
# region Telegram custom reading
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()
if first_byte == 254:
@ -95,11 +99,11 @@ class BinaryReader:
return data
def tgread_string(self):
"""Reads a Telegram-encoded string"""
"""Reads a Telegram-encoded string."""
return str(self.tgread_bytes(), encoding='utf-8', errors='replace')
def tgread_bool(self):
"""Reads a Telegram boolean value"""
"""Reads a Telegram boolean value."""
value = self.read_int(signed=False)
if value == 0x997275b5: # boolTrue
return True
@ -110,13 +114,13 @@ class BinaryReader:
def tgread_date(self):
"""Reads and converts Unix time (used by Telegram)
into a Python datetime object
into a Python datetime object.
"""
value = self.read_int()
return None if value == 0 else datetime.utcfromtimestamp(value)
def tgread_object(self):
"""Reads a Telegram object"""
"""Reads a Telegram object."""
constructor_id = self.read_int(signed=False)
clazz = tlobjects.get(constructor_id, None)
if clazz is None:
@ -135,7 +139,7 @@ class BinaryReader:
return clazz.from_reader(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):
raise ValueError('Invalid constructor code, vector was expected')
@ -145,21 +149,23 @@ class BinaryReader:
# endregion
def close(self):
"""Closes the reader, freeing the BytesIO stream."""
self.reader.close()
# region Position related
def tell_position(self):
"""Tells the current position on the stream"""
"""Tells the current position on the stream."""
return self.reader.tell()
def set_position(self, position):
"""Sets the current position on the stream"""
"""Sets the current position on the stream."""
self.reader.seek(position)
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)

View File

@ -27,12 +27,13 @@ DEFAULT_URL_RE = re.compile(b'\\[\0(.+?)\\]\0\\(\0(.+?)\\)\0')
def parse(message, delimiters=None, url_re=None):
"""
Parses the given message and returns the stripped message and a list
of MessageEntity* using the specified delimiters dictionary (or default
if None). The dictionary should be a mapping {delimiter: entity class}.
Parses the given markdown message and returns its stripped representation
plus a list of the MessageEntity's that were found.
The url_re(gex) must contain two matching groups: the text to be
clickable and the URL itself, and be utf-16le encoded.
:param message: the message with markdown-like syntax to be parsed.
: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:
url_re = DEFAULT_URL_RE
@ -130,8 +131,13 @@ def parse(message, delimiters=None, url_re=None):
def get_inner_text(text, entity):
"""Gets the inner text that's surrounded by the given entity or entities.
For instance: text = 'hey!', entity = MessageEntityBold(2, 2) -> 'y!'.
"""
Gets the inner text that's surrounded by the given entity or entities.
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__'):
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 socket
from datetime import timedelta
@ -7,7 +9,14 @@ from threading import Lock
class TcpClient:
"""A simple TCP client to ease the work with sockets and proxies."""
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._socket = None
self._closing_lock = Lock()
@ -33,8 +42,11 @@ class TcpClient:
self._socket.settimeout(self.timeout)
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
# The address needs to be surrounded by [] as discussed on PR#425
@ -65,12 +77,13 @@ class TcpClient:
raise
def _get_connected(self):
"""Determines whether the client is connected or not."""
return self._socket is not None and self._socket.fileno() >= 0
connected = property(fget=_get_connected)
def close(self):
"""Closes the connection"""
"""Closes the connection."""
if self._closing_lock.locked():
# Already closing, no need to close again (avoid None.close())
return
@ -86,7 +99,11 @@ class TcpClient:
self._socket = None
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:
raise ConnectionResetError()
@ -105,13 +122,11 @@ class TcpClient:
raise
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
no data has been read in the specified time. If data was read
and it's waiting for more, the timeout will NOT cancel the
operation. Set to None for no timeout
:param size: the size of the block to be read.
:return: the read data with len(data) == size.
"""
if self._socket is None:
raise ConnectionResetError()
@ -141,5 +156,6 @@ class TcpClient:
return buffer.raw.getvalue()
def _raise_connection_reset(self):
"""Disconnects the client and raises ConnectionResetError."""
self.close() # Connection reset -> flag as socket closed
raise ConnectionResetError('The server has closed the connection.')