Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use sphinx rst format in all docstrings #18

Merged
merged 1 commit into from
Jan 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/18.docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Rewrite all docstrings into proper Sphinx format, instead of using markdown.
39 changes: 28 additions & 11 deletions mcproto/buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,26 @@ def __init__(self, *args, **kwargs):
self.pos = 0

def write(self, data: bytes) -> None:
"""Write new data into the buffer."""
"""Write/Store given ``data`` into the buffer."""
self.extend(data)

def read(self, length: int) -> bytearray:
"""Read data stored in the buffer.

Reading data doesn't remove that data, rather that data is treated as already read, and
next read will start from the first unread byte. If freeing the data is necessary, check the clear function.
next read will start from the first unread byte. If freeing the data is necessary, check
the :meth:`.clear` function.

Trying to read more data than is available will raise an IOError, however it will deplete the remaining data
and the partial data that was read will be a part of the error message. This behavior is here to mimic reading
from a socket connection.
:param length:
Amount of bytes to be read.

If the requested amount can't be read (buffer doesn't contain that much data/buffer
doesn't contain any data), an :exc:`IOError` will be reaised.

If there were some data in the buffer, but it was less than requested, this remaining
data will still be depleted and the partial data that was read will be a part of the
error message in the :exc:`IOError`. This behavior is here to mimic reading from a real
socket connection.
"""
end = self.pos + length

Expand All @@ -43,11 +51,16 @@ def read(self, length: int) -> bytearray:
self.pos = end

def clear(self, only_already_read: bool = False) -> None:
"""
Clear out the stored data and reset position.
"""Clear out the stored data and reset position.

If `only_already_read` is True, only clear out the data which was already read, and reset the position.
This is mostly useful to avoid keeping large chunks of data in memory for no reason.
:param only_already_read:
When set to ``True``, only the data that was already marked as read will be cleared,
and the position will be reset (to start at the remaining data). This can be useful
for avoiding needlessly storing large amounts of data in memory, if this data is no
longer useful.

Otherwise, if set to ``False``, all of the data is cleared, and the position is reset,
essentially resulting in a blank buffer.
"""
if only_already_read:
del self[: self.pos]
Expand All @@ -56,7 +69,11 @@ def clear(self, only_already_read: bool = False) -> None:
self.pos = 0

def reset(self) -> None:
"""Reset the position in the buffer."""
"""Reset the position in the buffer.

Since the buffer doesn't automatically clear the already read data, it is possible to simply
reset the position and read the data it contains again.
"""
self.pos = 0

def flush(self) -> bytearray:
Expand All @@ -67,5 +84,5 @@ def flush(self) -> bytearray:

@property
def remaining(self) -> int:
"""Get the amount of bytes that's still remaining in be buffer to be read."""
"""Get the amount of bytes that's still remaining in the buffer to be read."""
return len(self) - self.pos
111 changes: 90 additions & 21 deletions mcproto/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@


class SyncConnection(BaseSyncReader, BaseSyncWriter, ABC):
"""Base class for all classes handling synchronous connections."""

__slots__ = ("closed",)

def __init__(self):
Expand All @@ -36,11 +38,19 @@ def __init__(self):
@classmethod
@abstractmethod
def make_client(cls, address: tuple[str, int], timeout: float) -> Self:
"""Construct a client connection to given address."""
"""Construct a client connection (Client -> Server) to given server ``address``.

:param address: Address of the server to connection to.
:param timeout:
Amount of seconds to wait for the connection to be established.
If connection can't be established within this time, :exc:`TimeoutError` will be raised.
This timeout is then also used for any further data receiving.
"""
raise NotImplementedError

@abstractmethod
def _close(self) -> None:
"""Close the underlying connection."""
raise NotImplementedError

def close(self) -> None:
Expand All @@ -58,6 +68,8 @@ def __exit__(self, *a, **kw) -> None:


class AsyncConnection(BaseAsyncReader, BaseAsyncWriter, ABC):
"""Base class for all classes handling asynchronous connections."""

__slots__ = ("closed",)

def __init__(self):
Expand All @@ -66,11 +78,19 @@ def __init__(self):
@classmethod
@abstractmethod
async def make_client(cls, address: tuple[str, int], timeout: float) -> Self:
"""Construct a client connection to given address."""
"""Construct a client connection (Client -> Server) to given server ``address``.

:param address: Address of the server to connection to.
:param timeout:
Amount of seconds to wait for the connection to be established.
If connection can't be established within this time, :exc:`TimeoutError` will be raised.
This timeout is then also used for any further data receiving.
"""
raise NotImplementedError

@abstractmethod
async def _close(self) -> None:
"""Close the underlying connection."""
raise NotImplementedError

async def close(self) -> None:
Expand All @@ -88,6 +108,8 @@ async def __aexit__(self, *a, **kw) -> None:


class TCPSyncConnection(SyncConnection, Generic[T_SOCK]):
"""Synchronous connection using a TCP :class:`~socket.socket`."""

__slots__ = ("socket",)

def __init__(self, socket: T_SOCK):
Expand All @@ -96,16 +118,25 @@ def __init__(self, socket: T_SOCK):

@classmethod
def make_client(cls, address: tuple[str, int], timeout: float) -> Self:
"""Construct a client connection to given address."""
"""Construct a client connection (Client -> Server) to given server ``address``.

:param address: Address of the server to connection to.
:param timeout:
Amount of seconds to wait for the connection to be established.
If connection can't be established within this time, :exc:`TimeoutError` will be raised.
This timeout is then also used for any further data receiving.
"""
sock = socket.create_connection(address, timeout=timeout)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
return cls(sock)

def read(self, length: int) -> bytearray:
"""Receive data sent through the connection.

`length` controls how many bytes we want to receive. If the requested amount
of bytes isn't available to be received, IOError will be raised.
:param length:
Amount of bytes to be received. If the requested amount can't be received
(server didn't send that much data/server didn't send any data), an :exc:`IOError`
will be raised.
"""
result = bytearray()
while len(result) < length:
Expand All @@ -124,15 +155,17 @@ def read(self, length: int) -> bytearray:
return result

def write(self, data: bytes) -> None:
"""Send given data over the connection."""
"""Send given ``data`` over the connection."""
self.socket.send(data)

def _close(self) -> None:
"""Close the connection (it cannot be used after this)."""
"""Close the underlying connection."""
self.socket.close()


class TCPAsyncConnection(AsyncConnection, Generic[T_STREAMREADER, T_STREAMWRITER]):
"""Asynchronous TCP connection using :class:`~asyncio.StreamWriter` and :class:`~asyncio.StreamReader`."""

__slots__ = ("reader", "writer", "timeout")

def __init__(self, reader: T_STREAMREADER, writer: T_STREAMWRITER, timeout: float):
Expand All @@ -143,16 +176,25 @@ def __init__(self, reader: T_STREAMREADER, writer: T_STREAMWRITER, timeout: floa

@classmethod
async def make_client(cls, address: tuple[str, int], timeout: float) -> Self:
"""Construct a client connection to given address."""
"""Construct a client connection (Client -> Server) to given server ``address``.

:param address: Address of the server to connection to.
:param timeout:
Amount of seconds to wait for the connection to be established.
If connection can't be established within this time, :exc:`TimeoutError` will be raised.
This timeout is then also used for any further data receiving.
"""
conn = asyncio.open_connection(address[0], address[1])
reader, writer = await asyncio.wait_for(conn, timeout=timeout)
return cls(reader, writer, timeout)

async def read(self, length: int) -> bytearray:
"""Receive data sent through the connection.

`length` controls how many bytes we want to receive. If the requested amount
of bytes isn't available to be received, IOError will be raised.
:param length:
Amount of bytes to be received. If the requested amount can't be received
(server didn't send that much data/server didn't send any data), an :exc:`IOError`
will be raised.
"""
result = bytearray()
while len(result) < length:
Expand All @@ -171,20 +213,22 @@ async def read(self, length: int) -> bytearray:
return result

async def write(self, data: bytes) -> None:
"""Send given data over the connection."""
"""Send given ``data`` over the connection."""
self.writer.write(data)

async def _close(self) -> None:
"""Close the connection (it cannot be used after this)."""
"""Close the underlying connection."""
self.writer.close()

@property
def socket(self) -> socket.socket:
"""Obtain the underlying socket behind the asyncio transport."""
"""Obtain the underlying socket behind the :class:`~asyncio.Transport`."""
return self.writer.transport._sock # type: ignore


class UDPSyncConnection(SyncConnection, Generic[T_SOCK]):
"""Synchronous connection using a UDP :class:`~socket.socket`."""

__slots__ = ("socket", "address")

BUFFER_SIZE = 65535
Expand All @@ -196,15 +240,27 @@ def __init__(self, socket: T_SOCK, address: tuple[str, int]):

@classmethod
def make_client(cls, address: tuple[str, int], timeout: float) -> Self:
"""Construct a client connection to given address."""
"""Construct a client connection (Client -> Server) to given server ``address``.

:param address: Address of the server to connection to.
:param timeout:
Amount of seconds to wait for the connection to be established.
If connection can't be established within this time, :exc:`TimeoutError` will be raised.
This timeout is then also used for any further data receiving.
"""
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(timeout)
return cls(sock, address)

def read(self, length: Optional[int] = None) -> bytearray:
"""Receive data sent through the connection.

For UDP connections, `length` parameter is ignored and not required.
:param length:
For UDP connections, ``length`` parameter is ignored and not required.
Instead, UDP connections always read exactly :attr:`.BUFFER_SIZE` bytes.

If the requested amount can't be received (server didn't send that much
data/server didn't send any data), an :exc:`IOError` will be raised.
"""
result = bytearray()
while len(result) == 0:
Expand All @@ -213,15 +269,17 @@ def read(self, length: Optional[int] = None) -> bytearray:
return result

def write(self, data: bytes) -> None:
"""Send given data over the connection."""
"""Send given ``data`` over the connection."""
self.socket.sendto(data, self.address)

def _close(self) -> None:
"""Close the connection (it cannot be used after this)."""
"""Close the underlying connection."""
self.socket.close()


class UDPAsyncConnection(AsyncConnection, Generic[T_DATAGRAM_CLIENT]):
"""Asynchronous UDP connection using :class:`~asyncio_dgram.DatagramClient`."""

__slots__ = ("stream", "timeout")

def __init__(self, stream: T_DATAGRAM_CLIENT, timeout: float):
Expand All @@ -231,15 +289,26 @@ def __init__(self, stream: T_DATAGRAM_CLIENT, timeout: float):

@classmethod
async def make_client(cls, address: tuple[str, int], timeout: float) -> Self:
"""Construct a client connection to given address."""
"""Construct a client connection (Client -> Server) to given server ``address``.

:param address: Address of the server to connection to.
:param timeout:
Amount of seconds to wait for the connection to be established.
If connection can't be established within this time, :exc:`TimeoutError` will be raised.
This timeout is then also used for any further data receiving.
"""
conn = asyncio_dgram.connect(address)
stream = await asyncio.wait_for(conn, timeout=timeout)
return cls(stream, timeout)

async def read(self, length: Optional[int] = None) -> bytearray:
"""Receive data sent through the connection.

For UDP connections, `length` parameter is ignored and not required.
:param length:
For UDP connections, ``length`` parameter is ignored and not required.

If the requested amount can't be received (server didn't send that much
data/server didn't send any data), an :exc:`IOError` will be raised.
"""
result = bytearray()
while len(result) == 0:
Expand All @@ -248,9 +317,9 @@ async def read(self, length: Optional[int] = None) -> bytearray:
return result

async def write(self, data: bytes) -> None:
"""Send given data over the connection."""
"""Send given ``data`` over the connection."""
await self.stream.send(data)

async def _close(self) -> None:
"""Close the connection (it cannot be used after this)."""
"""Close the underlying connection."""
self.stream.close()
4 changes: 2 additions & 2 deletions mcproto/packets/interactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,13 @@ def _deserialize_packet(


def sync_write_packet(writer: BaseSyncWriter, packet: Packet, *, compressed: bool = False) -> None:
"""Write given packet."""
"""Write given ``packet``."""
data_buf = _serialize_packet(packet, compressed=compressed)
writer.write_bytearray(data_buf)


async def async_write_packet(writer: BaseAsyncWriter, packet: Packet, *, compressed: bool = False) -> None:
"""Write given packet."""
"""Write given ``packet``."""
data_buf = _serialize_packet(packet, compressed=compressed)
await writer.write_bytearray(data_buf)

Expand Down
Loading