From d62aa475a11d9986511fc17b8937c5237ce01868 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Mon, 28 Feb 2022 09:53:46 +0100 Subject: [PATCH 01/43] Add pyright dev dependency --- poetry.lock | 34 +++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 60989c7..7ccd53c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -172,6 +172,14 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "nodeenv" +version = "1.6.0" +description = "Node.js virtual environment builder" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "packaging" version = "21.3" @@ -261,6 +269,22 @@ python-versions = ">=3.6" [package.extras] diagrams = ["jinja2", "railroad-diagrams"] +[[package]] +name = "pyright" +version = "1.1.225" +description = "Command line wrapper for pyright" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +nodeenv = ">=1.6.0" +typing-extensions = {version = ">=3.7", markers = "python_version < \"3.8\""} + +[package.extras] +all = ["twine (>=3.4.1)"] +dev = ["twine (>=3.4.1)"] + [[package]] name = "pytest" version = "7.0.1" @@ -337,7 +361,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "3750687b24a352b625441ebaf1d20726351e3ebfc592fb386e4491455582ae49" +content-hash = "805914111993bc904b628a157e6c4dd0cf1e3e596b34642c13f977c941487777" [metadata.files] atomicwrites = [ @@ -494,6 +518,10 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] +nodeenv = [ + {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"}, + {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, +] packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, @@ -530,6 +558,10 @@ pyparsing = [ {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, ] +pyright = [ + {file = "pyright-1.1.225-py3-none-any.whl", hash = "sha256:ebb6de095972914b6b3b12d053474e29943389f585dd4bff8a684eb45c4d6987"}, + {file = "pyright-1.1.225.tar.gz", hash = "sha256:d638b616bdaea762502e8904307ca8247754d7629b7adf05f0bb2bf64cbb340f"}, +] pytest = [ {file = "pytest-7.0.1-py3-none-any.whl", hash = "sha256:9ce3ff477af913ecf6321fe337b93a2c0dcf2a0a1439c43f5452112c1e4280db"}, {file = "pytest-7.0.1.tar.gz", hash = "sha256:e30905a0c131d3d94b89624a1cc5afec3e0ba2fbdb151867d8e0ebd49850f171"}, diff --git a/pyproject.toml b/pyproject.toml index 7fa1a2b..0e8bc2e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ pytest = "^7.0.1" colorama = "^0.4.4" isort = "^5.10.1" pytest-asyncio = "^0.18.1" +pyright = "^1.1.225" [build-system] requires = ["poetry-core>=1.0.0"] From ea7157ab76379b0c238d580856985f1a6594ccd0 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Mon, 28 Feb 2022 10:57:37 +0100 Subject: [PATCH 02/43] Make registry typed This also fixes a slight issue where if a list/tuple was passed into the registry class as data, it was treated as the reversed data rather, and reversed data was the actual data. --- pymine_net/types/registry.py | 53 ++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/pymine_net/types/registry.py b/pymine_net/types/registry.py index 2aae5bb..90fe8af 100644 --- a/pymine_net/types/registry.py +++ b/pymine_net/types/registry.py @@ -1,35 +1,58 @@ -from typing import Union +from typing import Dict, Generic, Iterable, Mapping, Optional, TypeVar, Union, cast, overload + +T = TypeVar("T") +K = TypeVar("K") +V = TypeVar("V") __all__ = ("Registry",) -class Registry: +class Registry(Generic[K, V]): + data: Dict[K, V] + data_reversed: Dict[V, K] + + @overload def __init__( self, - data: Union[dict, list, tuple], - data_reversed: Union[dict, list, tuple] = None, + data: Iterable[V], + data_reversed: Optional[Iterable[V]] = None, ): - self.data_reversed = data_reversed + ... - if isinstance(data, dict): - self.data = data + @overload + def __init__( + self, + data: Mapping[K, T], + data_reversed: Optional[Mapping[T, K]] = None, + ): + ... + def __init__( + self, + data: Union[Iterable[V], Mapping[K, V]], + data_reversed: Optional[Union[Iterable[V], Mapping[V, K]]] = None + ): + if isinstance(data, Mapping): + data = cast(Mapping[K, V], data) + self.data = dict(data) if data_reversed is None: self.data_reversed = {v: k for k, v in data.items()} - elif isinstance(data, (list, tuple)): - self.data_reversed = data - self.data = {v: i for i, v in enumerate(self.data_reversed)} + else: + data_reversed = cast(Mapping[V, K], data_reversed) + self.data_reversed = dict(data_reversed) + elif isinstance(data, Iterable): + data = cast(Iterable[V], data) + self.data = cast(Dict[K, V], {i: v for i, v in enumerate(data)}) + self.data_reversed = {v: i for i, v in self.data.items()} else: - raise TypeError( - "Creating a registry from something other than a dict, tuple, or list isn't supported" - ) + raise TypeError(f"Can't make registry from {type(data)}, must be Iterable/Mapping.") - def encode(self, key: object) -> object: + def encode(self, key: K) -> V: """Key -> value, most likely an identifier to an integer.""" return self.data[key] - def decode(self, value: object) -> object: + def decode(self, value: V) -> K: """Value -> key, most likely a numeric id to a string identifier.""" return self.data_reversed[value] From 7dcbfa909f29b4ad9fff71f39c71c3b6f23ef8f1 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Mon, 28 Feb 2022 11:13:40 +0100 Subject: [PATCH 03/43] Improve from_list logic and fix type hints For some reason, the from_list method was originally making the instance in which it then (optionally) checked for duplicate packets based on ID. However during the original parsing, packets were stored as dicts with IDs as keys, this means even if there were some duplicates, they'd just get overridden and we'd only pass the last packet in the list with that ID. This meant the duplicate check would never actually catch any duplicate packets. This logic was also not ideal because it went through the list of packets server times for no reason, when it could all be done in a single loop. This is way more efficient which would become noticable if the list of packets was long enough. It also fixes an invalid type hint, which specified the packets argument should be a List[Type[Packet]], however this would mean we expect the list to contain the actual "Packet" class (or it's subtypes), when in fact we want instances of this type. Not only that, but the function can't even process any other type of packets than ClientBound and ServerBound ones, so we shouldn't specifiy that it takes any arbitrary packet, but rather a union of the two. --- pymine_net/types/packet_map.py | 56 +++++++++++++++------------------- 1 file changed, 25 insertions(+), 31 deletions(-) diff --git a/pymine_net/types/packet_map.py b/pymine_net/types/packet_map.py index 8856d08..be32c0f 100644 --- a/pymine_net/types/packet_map.py +++ b/pymine_net/types/packet_map.py @@ -1,11 +1,14 @@ from __future__ import annotations -from typing import Dict, List, Tuple, Type, Union +from typing import Dict, List, Tuple, Type, Union, TYPE_CHECKING from pymine_net.enums import GameState, PacketDirection from pymine_net.errors import DuplicatePacketIdError from pymine_net.types.packet import ClientBoundPacket, Packet, ServerBoundPacket +if TYPE_CHECKING: + from typing_extensions import Self + class StatePacketMap: """Stores a game state's packets""" @@ -22,36 +25,27 @@ def __init__( @classmethod def from_list( - cls, state: GameState, packets: List[Type[Packet]], *, check_duplicates: bool = False - ) -> StatePacketMap: - self = cls( - state, - {p.id: p for p in packets if issubclass(p, ServerBoundPacket)}, - {p.id: p for p in packets if issubclass(p, ClientBoundPacket)}, - ) - - if check_duplicates: - for packet_id in self.server_bound.keys(): - found = [ - p for p in packets if p.id == packet_id and issubclass(p, ServerBoundPacket) - ] - - if len(found) > 1: - raise DuplicatePacketIdError( - "unknown", state, packet_id, PacketDirection.SERVERBOUND - ) - - for packet_id in self.client_bound.keys(): - found = [ - p for p in packets if p.id == packet_id and issubclass(p, ClientBoundPacket) - ] - - if len(found) > 1: - raise DuplicatePacketIdError( - "unknown", state, packet_id, PacketDirection.CLIENTBOUND - ) - - return self + cls, + state: GameState, + packets: List[Union[ServerBoundPacket, ClientBoundPacket]], + *, + check_duplicates: bool = False, + ) -> Self: + server_bound = {} + client_bound = {} + for packet in packets: + if isinstance(packet, ServerBoundPacket): + if check_duplicates and packet.id in server_bound: + raise DuplicatePacketIdError("unknown", state, packet.id, PacketDirection.SERVERBOUND) + server_bound[packet.id] = packet + elif isinstance(packet, ClientBoundPacket): + if check_duplicates and packet.id in client_bound: + raise DuplicatePacketIdError("unknown", state, packet.id, PacketDirection.CLIENTBOUND) + client_bound[packet.id] = packet + else: + raise TypeError(f"Expected ServerBoundPacket or ClientBoundPacket, got {type(packet)}") + + return cls(state, server_bound, client_bound) class PacketMap: From 709677b01d6ed5cf696b2e8b07057bff7c6c855b Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Mon, 28 Feb 2022 11:19:23 +0100 Subject: [PATCH 04/43] Enum comparison should be is, not __eq__ --- pymine_net/types/packet_map.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymine_net/types/packet_map.py b/pymine_net/types/packet_map.py index be32c0f..2d572f6 100644 --- a/pymine_net/types/packet_map.py +++ b/pymine_net/types/packet_map.py @@ -58,7 +58,7 @@ def __init__(self, protocol: Union[str, int], packets: Dict[GameState, StatePack def __getitem__(self, key: Tuple[PacketDirection, int, int]) -> Packet: direction, state, packet_id = key - if direction == PacketDirection.CLIENTBOUND: + if direction is PacketDirection.CLIENTBOUND: return self.packets[state].client_bound[packet_id] return self.packets[state].server_bound[packet_id] From 5cae9aefaf5a7738e4c42ee954055b5e0dac027f Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Mon, 28 Feb 2022 11:19:48 +0100 Subject: [PATCH 05/43] Take __getitem__ argument as positional-only --- pymine_net/types/packet_map.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pymine_net/types/packet_map.py b/pymine_net/types/packet_map.py index 2d572f6..5076f77 100644 --- a/pymine_net/types/packet_map.py +++ b/pymine_net/types/packet_map.py @@ -55,8 +55,8 @@ def __init__(self, protocol: Union[str, int], packets: Dict[GameState, StatePack self.protocol = protocol self.packets = packets - def __getitem__(self, key: Tuple[PacketDirection, int, int]) -> Packet: - direction, state, packet_id = key + def __getitem__(self, __key: Tuple[PacketDirection, int, int]) -> Packet: + direction, state, packet_id = __key if direction is PacketDirection.CLIENTBOUND: return self.packets[state].client_bound[packet_id] From 32fdff2265387388ba6af52f4b3be1fbb4bc1148 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Mon, 28 Feb 2022 11:20:43 +0100 Subject: [PATCH 06/43] Use a more specific return type --- pymine_net/types/packet_map.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymine_net/types/packet_map.py b/pymine_net/types/packet_map.py index 5076f77..8700af6 100644 --- a/pymine_net/types/packet_map.py +++ b/pymine_net/types/packet_map.py @@ -55,7 +55,7 @@ def __init__(self, protocol: Union[str, int], packets: Dict[GameState, StatePack self.protocol = protocol self.packets = packets - def __getitem__(self, __key: Tuple[PacketDirection, int, int]) -> Packet: + def __getitem__(self, __key: Tuple[PacketDirection, int, int]) -> Union[ClientBoundPacket, ServerBoundPacket]: direction, state, packet_id = __key if direction is PacketDirection.CLIENTBOUND: From af35559168b605fa1c1faec69c68ad3865f0b5a3 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Mon, 28 Feb 2022 11:26:39 +0100 Subject: [PATCH 07/43] Specify __getitems__ overload --- pymine_net/types/packet_map.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pymine_net/types/packet_map.py b/pymine_net/types/packet_map.py index 8700af6..e37f64f 100644 --- a/pymine_net/types/packet_map.py +++ b/pymine_net/types/packet_map.py @@ -1,10 +1,10 @@ from __future__ import annotations -from typing import Dict, List, Tuple, Type, Union, TYPE_CHECKING +from typing import Dict, List, Literal, Tuple, Type, Union, TYPE_CHECKING, overload from pymine_net.enums import GameState, PacketDirection from pymine_net.errors import DuplicatePacketIdError -from pymine_net.types.packet import ClientBoundPacket, Packet, ServerBoundPacket +from pymine_net.types.packet import ClientBoundPacket, ServerBoundPacket if TYPE_CHECKING: from typing_extensions import Self @@ -55,6 +55,14 @@ def __init__(self, protocol: Union[str, int], packets: Dict[GameState, StatePack self.protocol = protocol self.packets = packets + @overload + def __getitem__(self, __key: Tuple[Literal[PacketDirection.CLIENTBOUND], int, int]) -> ClientBoundPacket: + ... + + @overload + def __getitem__(self, __key: Tuple[Literal[PacketDirection.SERVERBOUND], int, int]) -> ServerBoundPacket: + ... + def __getitem__(self, __key: Tuple[PacketDirection, int, int]) -> Union[ClientBoundPacket, ServerBoundPacket]: direction, state, packet_id = __key From cd843022bb502b09e53cee0674f3fc25bf329b8b Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Mon, 28 Feb 2022 11:42:43 +0100 Subject: [PATCH 08/43] Fix overload type hint --- pymine_net/types/registry.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pymine_net/types/registry.py b/pymine_net/types/registry.py index 90fe8af..2264195 100644 --- a/pymine_net/types/registry.py +++ b/pymine_net/types/registry.py @@ -1,6 +1,5 @@ from typing import Dict, Generic, Iterable, Mapping, Optional, TypeVar, Union, cast, overload -T = TypeVar("T") K = TypeVar("K") V = TypeVar("V") @@ -22,8 +21,8 @@ def __init__( @overload def __init__( self, - data: Mapping[K, T], - data_reversed: Optional[Mapping[T, K]] = None, + data: Mapping[K, V], + data_reversed: Optional[Mapping[V, K]] = None, ): ... From 586841fa4a506cf3b8fbd6d894d0e30125b30aa1 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Mon, 28 Feb 2022 12:16:49 +0100 Subject: [PATCH 09/43] Fix from_list fucntion In 7dcbfa9, I wrongly assumed that the function was taking instances, when in fact it was taking classes, this reverts that part of the commit. --- pymine_net/types/packet_map.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pymine_net/types/packet_map.py b/pymine_net/types/packet_map.py index e37f64f..9fca335 100644 --- a/pymine_net/types/packet_map.py +++ b/pymine_net/types/packet_map.py @@ -27,18 +27,18 @@ def __init__( def from_list( cls, state: GameState, - packets: List[Union[ServerBoundPacket, ClientBoundPacket]], + packets: List[Union[Type[ServerBoundPacket], Type[ClientBoundPacket]]], *, check_duplicates: bool = False, ) -> Self: server_bound = {} client_bound = {} for packet in packets: - if isinstance(packet, ServerBoundPacket): + if issubclass(packet, ServerBoundPacket): if check_duplicates and packet.id in server_bound: raise DuplicatePacketIdError("unknown", state, packet.id, PacketDirection.SERVERBOUND) server_bound[packet.id] = packet - elif isinstance(packet, ClientBoundPacket): + elif issubclass(packet, ClientBoundPacket): if check_duplicates and packet.id in client_bound: raise DuplicatePacketIdError("unknown", state, packet.id, PacketDirection.CLIENTBOUND) client_bound[packet.id] = packet From ff49d3cdad5cd816f7f6e0e7f576bed1c9943b60 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Mon, 28 Feb 2022 12:23:07 +0100 Subject: [PATCH 10/43] Fix return type for __getitem__ Originally, this return type was "Packet, which I've then change to a Union of 2 subtypes of Packet, however this type was actually supposed to be a Type[Packet], so this changes the return type to the type (class) of the union of these 2 subtypes --- pymine_net/types/packet_map.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pymine_net/types/packet_map.py b/pymine_net/types/packet_map.py index 9fca335..798894f 100644 --- a/pymine_net/types/packet_map.py +++ b/pymine_net/types/packet_map.py @@ -56,14 +56,14 @@ def __init__(self, protocol: Union[str, int], packets: Dict[GameState, StatePack self.packets = packets @overload - def __getitem__(self, __key: Tuple[Literal[PacketDirection.CLIENTBOUND], int, int]) -> ClientBoundPacket: + def __getitem__(self, __key: Tuple[Literal[PacketDirection.CLIENTBOUND], int, int]) -> Type[ClientBoundPacket]: ... @overload - def __getitem__(self, __key: Tuple[Literal[PacketDirection.SERVERBOUND], int, int]) -> ServerBoundPacket: + def __getitem__(self, __key: Tuple[Literal[PacketDirection.SERVERBOUND], int, int]) -> Type[ServerBoundPacket]: ... - def __getitem__(self, __key: Tuple[PacketDirection, int, int]) -> Union[ClientBoundPacket, ServerBoundPacket]: + def __getitem__(self, __key: Tuple[PacketDirection, int, int]) -> Union[Type[ClientBoundPacket], Type[ServerBoundPacket]]: direction, state, packet_id = __key if direction is PacketDirection.CLIENTBOUND: From 4afd065f9fee3c9d90e67a6da99f743f35663e49 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Mon, 28 Feb 2022 12:25:22 +0100 Subject: [PATCH 11/43] Improve error message --- pymine_net/types/packet_map.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymine_net/types/packet_map.py b/pymine_net/types/packet_map.py index 798894f..f171bb1 100644 --- a/pymine_net/types/packet_map.py +++ b/pymine_net/types/packet_map.py @@ -43,7 +43,7 @@ def from_list( raise DuplicatePacketIdError("unknown", state, packet.id, PacketDirection.CLIENTBOUND) client_bound[packet.id] = packet else: - raise TypeError(f"Expected ServerBoundPacket or ClientBoundPacket, got {type(packet)}") + raise TypeError(f"Expected ServerBoundPacket or ClientBoundPacket, got {packet} ({type(packet)})") return cls(state, server_bound, client_bound) From b913cb0ee3bc764c8730edd29efb0e3be05c943f Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Mon, 28 Feb 2022 12:26:31 +0100 Subject: [PATCH 12/43] Follow black --- pymine_net/types/packet_map.py | 24 ++++++++++++++++++------ pymine_net/types/registry.py | 2 +- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/pymine_net/types/packet_map.py b/pymine_net/types/packet_map.py index f171bb1..9f5caf0 100644 --- a/pymine_net/types/packet_map.py +++ b/pymine_net/types/packet_map.py @@ -36,14 +36,20 @@ def from_list( for packet in packets: if issubclass(packet, ServerBoundPacket): if check_duplicates and packet.id in server_bound: - raise DuplicatePacketIdError("unknown", state, packet.id, PacketDirection.SERVERBOUND) + raise DuplicatePacketIdError( + "unknown", state, packet.id, PacketDirection.SERVERBOUND + ) server_bound[packet.id] = packet elif issubclass(packet, ClientBoundPacket): if check_duplicates and packet.id in client_bound: - raise DuplicatePacketIdError("unknown", state, packet.id, PacketDirection.CLIENTBOUND) + raise DuplicatePacketIdError( + "unknown", state, packet.id, PacketDirection.CLIENTBOUND + ) client_bound[packet.id] = packet else: - raise TypeError(f"Expected ServerBoundPacket or ClientBoundPacket, got {packet} ({type(packet)})") + raise TypeError( + f"Expected ServerBoundPacket or ClientBoundPacket, got {packet} ({type(packet)})" + ) return cls(state, server_bound, client_bound) @@ -56,14 +62,20 @@ def __init__(self, protocol: Union[str, int], packets: Dict[GameState, StatePack self.packets = packets @overload - def __getitem__(self, __key: Tuple[Literal[PacketDirection.CLIENTBOUND], int, int]) -> Type[ClientBoundPacket]: + def __getitem__( + self, __key: Tuple[Literal[PacketDirection.CLIENTBOUND], int, int] + ) -> Type[ClientBoundPacket]: ... @overload - def __getitem__(self, __key: Tuple[Literal[PacketDirection.SERVERBOUND], int, int]) -> Type[ServerBoundPacket]: + def __getitem__( + self, __key: Tuple[Literal[PacketDirection.SERVERBOUND], int, int] + ) -> Type[ServerBoundPacket]: ... - def __getitem__(self, __key: Tuple[PacketDirection, int, int]) -> Union[Type[ClientBoundPacket], Type[ServerBoundPacket]]: + def __getitem__( + self, __key: Tuple[PacketDirection, int, int] + ) -> Union[Type[ClientBoundPacket], Type[ServerBoundPacket]]: direction, state, packet_id = __key if direction is PacketDirection.CLIENTBOUND: diff --git a/pymine_net/types/registry.py b/pymine_net/types/registry.py index 2264195..d5958cb 100644 --- a/pymine_net/types/registry.py +++ b/pymine_net/types/registry.py @@ -29,7 +29,7 @@ def __init__( def __init__( self, data: Union[Iterable[V], Mapping[K, V]], - data_reversed: Optional[Union[Iterable[V], Mapping[V, K]]] = None + data_reversed: Optional[Union[Iterable[V], Mapping[V, K]]] = None, ): if isinstance(data, Mapping): data = cast(Mapping[K, V], data) From 98cb92758668ca9d09e91253a8f81b648f8caba3 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Tue, 1 Mar 2022 13:09:03 +0100 Subject: [PATCH 13/43] Actually return Buffer from write_ingredient --- pymine_net/types/buffer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pymine_net/types/buffer.py b/pymine_net/types/buffer.py index f5bc6d0..253e733 100644 --- a/pymine_net/types/buffer.py +++ b/pymine_net/types/buffer.py @@ -319,6 +319,8 @@ def write_ingredient(self, value: dict) -> Buffer: for slot in value.values(): self.write_recipe_item(slot) + return self + def write_recipe(self, recipe_id: str, recipe: dict) -> Buffer: """Writes a recipe to the buffer.""" From cceb48b393f6acc1269d219d949a9fab4c4b6662 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Tue, 1 Mar 2022 13:10:56 +0100 Subject: [PATCH 14/43] Cast some values where necessary --- pymine_net/types/buffer.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pymine_net/types/buffer.py b/pymine_net/types/buffer.py index 253e733..de4630f 100644 --- a/pymine_net/types/buffer.py +++ b/pymine_net/types/buffer.py @@ -3,7 +3,7 @@ import json import struct import uuid -from typing import Callable, Dict, Optional, Tuple, Union +from typing import Callable, Dict, Optional, Tuple, Union, cast from pymine_net.enums import Direction, EntityModifier, Pose from pymine_net.types import nbt @@ -100,7 +100,7 @@ def read_varint(self, max_bits: int = 32) -> int: value = 0 for i in range(10): - byte = self.read("B") + byte = cast(int, self.read("B")) value |= (byte & 0x7F) << 7 * i if not byte & 0x80: @@ -216,7 +216,7 @@ def from_twos_complement(num, bits): return num - data = self.read("Q") + data = cast(int, self.read("Q")) return ( from_twos_complement(data >> 38, 26), @@ -272,7 +272,7 @@ def write_slot(self, item_id: int = None, count: int = 1, tag: nbt.TAG = None) - def read_rotation(self) -> Tuple[float, float, float]: """Reads a rotation from the buffer.""" - return self.read("fff") + return cast(Tuple[float, float, float], self.read("fff")) def write_rotation(self, x: float, y: float, z: float) -> Buffer: """Writes a rotation to the buffer.""" From df6a25e925b787aeb0d28ec37519ec161a07fc18 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Tue, 1 Mar 2022 13:12:11 +0100 Subject: [PATCH 15/43] Actually return Buffer from write_slot --- pymine_net/types/buffer.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pymine_net/types/buffer.py b/pymine_net/types/buffer.py index de4630f..9eac54f 100644 --- a/pymine_net/types/buffer.py +++ b/pymine_net/types/buffer.py @@ -265,9 +265,8 @@ def write_slot(self, item_id: int = None, count: int = 1, tag: nbt.TAG = None) - """Writes an inventory / container slot to the buffer.""" if item_id is None: - self.write("?", False) - else: - self.write("?", True).write_varint(item_id).write("b", count).write_nbt(tag) + return self.write("?", False) + return self.write("?", True).write_varint(item_id).write("b", count).write_nbt(tag) def read_rotation(self) -> Tuple[float, float, float]: """Reads a rotation from the buffer.""" From aa81341db4f00a4a2c768d94af511465c0fa89f8 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Tue, 1 Mar 2022 13:19:53 +0100 Subject: [PATCH 16/43] Use Self as return type --- pymine_net/types/buffer.py | 55 ++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/pymine_net/types/buffer.py b/pymine_net/types/buffer.py index 9eac54f..c2950d3 100644 --- a/pymine_net/types/buffer.py +++ b/pymine_net/types/buffer.py @@ -3,13 +3,16 @@ import json import struct import uuid -from typing import Callable, Dict, Optional, Tuple, Union, cast +from typing import Callable, Dict, Optional, Tuple, Union, TYPE_CHECKING, cast from pymine_net.enums import Direction, EntityModifier, Pose from pymine_net.types import nbt from pymine_net.types.chat import Chat from pymine_net.types.registry import Registry +if TYPE_CHECKING: + from typing_extensions import Self + __all__ = ("Buffer",) @@ -18,7 +21,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.pos = 0 - def write_bytes(self, data: Union[bytes, bytearray]) -> Buffer: + def write_bytes(self, data: Union[bytes, bytearray]) -> Self: """Writes bytes to the buffer.""" return self.extend(data) @@ -45,7 +48,7 @@ def reset(self) -> None: self.pos = 0 - def extend(self, data: Union[Buffer, bytes, bytearray]) -> Buffer: + def extend(self, data: Union[bytes, bytearray]) -> Self: super().extend(data) return self @@ -56,7 +59,7 @@ def read_byte(self) -> int: self.pos += 1 return byte - def write_byte(self, value: int) -> Buffer: + def write_byte(self, value: int) -> Self: """Writes a singular byte to the buffer.""" return self.extend(struct.pack(">b", value)) @@ -71,7 +74,7 @@ def read(self, fmt: str) -> Union[object, Tuple[object]]: return unpacked - def write(self, fmt: str, *value: object) -> Buffer: + def write(self, fmt: str, *value: object) -> Self: """Using the given format and value, packs the value and writes it to the buffer.""" self.write_bytes(struct.pack(">" + fmt, *value)) @@ -83,7 +86,7 @@ def read_optional(self, reader: Callable) -> Optional[object]: if self.read("?"): return reader() - def write_optional(self, writer: Callable, value: object = None) -> Buffer: + def write_optional(self, writer: Callable, value: object = None) -> Self: """Writes an optional value to the buffer.""" if value is None: @@ -119,7 +122,7 @@ def read_varint(self, max_bits: int = 32) -> int: return value - def write_varint(self, value: int, max_bits: int = 32) -> Buffer: + def write_varint(self, value: int, max_bits: int = 32) -> Self: """Writes a varint to the buffer.""" value_max = (1 << (max_bits - 1)) - 1 @@ -154,7 +157,7 @@ def read_optional_varint(self) -> Optional[int]: return value - 1 - def write_optional_varint(self, value: int = None) -> Buffer: + def write_optional_varint(self, value: int = None) -> Self: """Writes an optional (None if not present) varint to the buffer.""" return self.write_varint(0 if value is None else value + 1) @@ -164,7 +167,7 @@ def read_string(self) -> str: return self.read_bytes(self.read_varint(max_bits=16)).decode("utf-8") - def write_string(self, value: str) -> Buffer: + def write_string(self, value: str) -> Self: """Writes a string in UTF8 to the buffer.""" encoded = value.encode("utf-8") @@ -177,7 +180,7 @@ def read_json(self) -> object: return json.loads(self.read_string()) - def write_json(self, value: object) -> Buffer: + def write_json(self, value: object) -> Self: """Writes json data to the buffer.""" return self.write_string(json.dumps(value)) @@ -187,7 +190,7 @@ def read_nbt(self) -> nbt.TAG_Compound: return nbt.unpack(self[self.pos :]) - def write_nbt(self, value: nbt.TAG = None) -> Buffer: + def write_nbt(self, value: nbt.TAG = None) -> Self: """Writes an nbt tag to the buffer.""" if value is None: @@ -202,7 +205,7 @@ def read_uuid(self) -> uuid.UUID: return uuid.UUID(bytes=bytes(self.read_bytes(16))) - def write_uuid(self, value: uuid.UUID) -> Buffer: + def write_uuid(self, value: uuid.UUID) -> Self: """Writes a UUID to the buffer.""" return self.write_bytes(value.bytes) @@ -229,12 +232,12 @@ def read_chat(self) -> Chat: return Chat(self.read_json()) - def write_chat(self, value: Chat) -> Buffer: + def write_chat(self, value: Chat) -> Self: """Writes a chat message to the buffer.""" return self.write_json(value.data) - def write_position(self, x: int, y: int, z: int) -> Buffer: + def write_position(self, x: int, y: int, z: int) -> Self: """Writes a Minecraft position (x, y, z) to the buffer.""" def to_twos_complement(num, bits): @@ -261,7 +264,7 @@ def read_slot(self, registry: Registry) -> dict: "tag": self.read_nbt(), } - def write_slot(self, item_id: int = None, count: int = 1, tag: nbt.TAG = None) -> Buffer: + def write_slot(self, item_id: int = None, count: int = 1, tag: nbt.TAG = None) -> Self: """Writes an inventory / container slot to the buffer.""" if item_id is None: @@ -273,7 +276,7 @@ def read_rotation(self) -> Tuple[float, float, float]: return cast(Tuple[float, float, float], self.read("fff")) - def write_rotation(self, x: float, y: float, z: float) -> Buffer: + def write_rotation(self, x: float, y: float, z: float) -> Self: """Writes a rotation to the buffer.""" return self.write("fff", x, y, z) @@ -283,7 +286,7 @@ def read_direction(self) -> Direction: return Direction(self.read_varint()) - def write_direction(self, value: Direction) -> Buffer: + def write_direction(self, value: Direction) -> Self: """Writes a direction to the buffer.""" return self.write_varint(value.value) @@ -293,12 +296,12 @@ def read_pose(self) -> Pose: return Pose(self.read_varint()) - def write_pose(self, value: Pose) -> Buffer: + def write_pose(self, value: Pose) -> Self: """Writes a pose to the buffer.""" return self.write_varint(value.value) - def write_recipe_item(self, value: Union[dict, str]) -> Buffer: + def write_recipe_item(self, value: Union[dict, str]) -> Self: """Writes a recipe item / slot to the buffer.""" if isinstance(value, dict): @@ -310,7 +313,7 @@ def write_recipe_item(self, value: Union[dict, str]) -> Buffer: return self - def write_ingredient(self, value: dict) -> Buffer: + def write_ingredient(self, value: dict) -> Self: """Writes a part of a recipe to the buffer.""" self.write_varint(len(value)) @@ -320,7 +323,7 @@ def write_ingredient(self, value: dict) -> Buffer: return self - def write_recipe(self, recipe_id: str, recipe: dict) -> Buffer: + def write_recipe(self, recipe_id: str, recipe: dict) -> Self: """Writes a recipe to the buffer.""" recipe_type = recipe["type"] @@ -378,7 +381,7 @@ def read_villager(self) -> dict: "level": self.read_varint(), } - def write_villager(self, kind: int, profession: int, level: int) -> Buffer: + def write_villager(self, kind: int, profession: int, level: int) -> Self: return self.write_varint(kind).write_varint(profession).write_varint(level) def write_trade( @@ -393,7 +396,7 @@ def write_trade( price_multi: float, demand: int, in_item_2: dict = None, - ) -> Buffer: + ) -> Self: self.write_slot(**in_item_1).write_slot(**out_item) if in_item_2 is not None: @@ -427,7 +430,7 @@ def read_particle(self) -> dict: return particle - def write_particle(self, **value) -> Buffer: + def write_particle(self, **value) -> Self: particle_id = value["particle_id"] if particle_id == 3 or particle_id == 23: @@ -439,7 +442,7 @@ def write_particle(self, **value) -> Buffer: return self - def write_entity_metadata(self, value: Dict[Tuple[int, int], object]) -> Buffer: + def write_entity_metadata(self, value: Dict[Tuple[int, int], object]) -> Self: # index, type, value for (i, t), v in value.items(): self.write("B", i).write_varint(t) @@ -495,7 +498,7 @@ def read_modifier(self) -> Tuple[uuid.UUID, float, EntityModifier]: def write_modifier(self, uuid_: uuid.UUID, amount: float, operation: EntityModifier): return self.write_uuid(uuid_).write("f", amount).write("b", operation) - def write_node(self, node: dict) -> Buffer: + def write_node(self, node: dict) -> Self: node_flags = node["flags"] self.write_byte(node_flags).write_varint(len(node["children"])) From ded744955217c55fd2790af80e75de9787ec5cfe Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Tue, 1 Mar 2022 13:20:31 +0100 Subject: [PATCH 17/43] Use Optional for params defaulting to None --- pymine_net/types/buffer.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pymine_net/types/buffer.py b/pymine_net/types/buffer.py index c2950d3..215f6cd 100644 --- a/pymine_net/types/buffer.py +++ b/pymine_net/types/buffer.py @@ -26,7 +26,7 @@ def write_bytes(self, data: Union[bytes, bytearray]) -> Self: return self.extend(data) - def read_bytes(self, length: int = None) -> bytearray: + def read_bytes(self, length: Optional[int] = None) -> bytearray: """Reads bytes from the buffer, if length is None then all bytes are read.""" if length is None: @@ -86,7 +86,7 @@ def read_optional(self, reader: Callable) -> Optional[object]: if self.read("?"): return reader() - def write_optional(self, writer: Callable, value: object = None) -> Self: + def write_optional(self, writer: Callable, value: Optional[object] = None) -> Self: """Writes an optional value to the buffer.""" if value is None: @@ -157,7 +157,7 @@ def read_optional_varint(self) -> Optional[int]: return value - 1 - def write_optional_varint(self, value: int = None) -> Self: + def write_optional_varint(self, value: Optional[int] = None) -> Self: """Writes an optional (None if not present) varint to the buffer.""" return self.write_varint(0 if value is None else value + 1) @@ -190,7 +190,7 @@ def read_nbt(self) -> nbt.TAG_Compound: return nbt.unpack(self[self.pos :]) - def write_nbt(self, value: nbt.TAG = None) -> Self: + def write_nbt(self, value: Optional[nbt.TAG] = None) -> Self: """Writes an nbt tag to the buffer.""" if value is None: @@ -264,7 +264,7 @@ def read_slot(self, registry: Registry) -> dict: "tag": self.read_nbt(), } - def write_slot(self, item_id: int = None, count: int = 1, tag: nbt.TAG = None) -> Self: + def write_slot(self, item_id: Optional[int] = None, count: int = 1, tag: Optional[nbt.TAG] = None) -> Self: """Writes an inventory / container slot to the buffer.""" if item_id is None: @@ -395,7 +395,7 @@ def write_trade( special_price: int, price_multi: float, demand: int, - in_item_2: dict = None, + in_item_2: Optional[dict] = None, ) -> Self: self.write_slot(**in_item_1).write_slot(**out_item) From c8b106383111901c3bca0c580ee2107b1ad0769c Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Tue, 1 Mar 2022 13:20:54 +0100 Subject: [PATCH 18/43] Properly define __repr__ --- pymine_net/types/nbt.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pymine_net/types/nbt.py b/pymine_net/types/nbt.py index 21122cf..1f1e7af 100644 --- a/pymine_net/types/nbt.py +++ b/pymine_net/types/nbt.py @@ -108,10 +108,11 @@ def unpack(cls, buf) -> TAG: def pretty(self, indent: int = 0) -> str: return (" " * indent) + f'{self.__class__.__name__}("{self.name}"): {self.data}' - def __str__(self): + def __str__(self) -> str: return self.pretty() - __repr__ = __str__ + def __repr__(self) -> str: + return self.pretty() class TAG_End(TAG): From 43dc7b805f5c5f75df4a7d952dee60681d37457c Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Tue, 1 Mar 2022 14:52:08 +0100 Subject: [PATCH 19/43] Fix typo --- pymine_net/types/buffer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymine_net/types/buffer.py b/pymine_net/types/buffer.py index 215f6cd..e9d118a 100644 --- a/pymine_net/types/buffer.py +++ b/pymine_net/types/buffer.py @@ -343,7 +343,7 @@ def write_recipe(self, recipe_id: str, recipe: dict) -> Self: self.write_recipe_item(recipe["result"]) elif recipe_type == "minecraft:crafting_shaped": - self.write_varint(len(recipe["patern"][0])).write_varint( + self.write_varint(len(recipe["pattern"][0])).write_varint( len(recipe["pattern"]) ).write_string(recipe["group"]) From 10c39841f752e3ab31eadbc559d97597dd09229e Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Tue, 1 Mar 2022 16:40:14 +0100 Subject: [PATCH 20/43] Properly annotate read_optional --- pymine_net/types/buffer.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pymine_net/types/buffer.py b/pymine_net/types/buffer.py index e9d118a..a69104b 100644 --- a/pymine_net/types/buffer.py +++ b/pymine_net/types/buffer.py @@ -3,7 +3,7 @@ import json import struct import uuid -from typing import Callable, Dict, Optional, Tuple, Union, TYPE_CHECKING, cast +from typing import Callable, Dict, Optional, Tuple, Union, TYPE_CHECKING, TypeVar, cast from pymine_net.enums import Direction, EntityModifier, Pose from pymine_net.types import nbt @@ -13,6 +13,8 @@ if TYPE_CHECKING: from typing_extensions import Self +T = TypeVar("T") + __all__ = ("Buffer",) @@ -80,7 +82,7 @@ def write(self, fmt: str, *value: object) -> Self: self.write_bytes(struct.pack(">" + fmt, *value)) return self - def read_optional(self, reader: Callable) -> Optional[object]: + def read_optional(self, reader: Callable[[], T]) -> Optional[T]: """Reads an optional value from the buffer.""" if self.read("?"): From 46bfabde0b33aefcbf2c17c2c6701bf85c34fc83 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Tue, 1 Mar 2022 16:41:16 +0100 Subject: [PATCH 21/43] Use a dict switch instead of elif mess --- pymine_net/types/buffer.py | 74 +++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 41 deletions(-) diff --git a/pymine_net/types/buffer.py b/pymine_net/types/buffer.py index a69104b..ce58a1b 100644 --- a/pymine_net/types/buffer.py +++ b/pymine_net/types/buffer.py @@ -1,4 +1,5 @@ from __future__ import annotations +from functools import partial import json import struct @@ -445,50 +446,41 @@ def write_particle(self, **value) -> Self: return self def write_entity_metadata(self, value: Dict[Tuple[int, int], object]) -> Self: + def _f_10(v): + """This is basically write_optional_position. + + It's defined here because the function is too complex to be a lambda, + so instead we just refer to this definition in the switch dict.""" + self.write("?", v is not None) + if v is not None: + self.write_position(*v) + + sw = { + 0: partial(self.write, "b"), + 1: partial(self.write_varint), + 2: partial(self.write, "f"), + 3: self.write_string, + 4: self.write_chat, + 5: partial(self.write_optional, self.write_chat), + 6: lambda v: self.write_slot(**v), + 7: partial(self.write, "?"), + 8: lambda v: self.write_rotation(*v), + 9: lambda v: self.write_rotation(*v), + 10: _f_10, + 11: self.write_direction, + 12: partial(self.write_optional, self.write_uuid), + 13: self.write_block, + 14: self.write_nbt, + 15: lambda v: self.write_particle(**v), + 16: lambda v: self.write_villager(*v), + 17: self.write_optional_varint, + 18: self.write_pose, + } + # index, type, value for (i, t), v in value.items(): self.write("B", i).write_varint(t) - - if t == 0: - self.write("b", v) - elif t == 1: - self.write_varint(v) - elif t == 2: - self.write("f", v) - elif t == 3: - self.write_string(v) - elif t == 4: - self.write_chat(v) - elif t == 5: - self.write_optional(self.write_chat, v) - elif t == 6: - self.write_slot(**v) - elif t == 7: - self.write("?", v) - elif t == 8: - self.write_rotation(*v) - elif t == 9: - self.write_position(*v) - elif t == 10: - self.write("?", v is not None) - if v is not None: - self.write_position(*v) - elif t == 11: - self.write_direction(v) - elif t == 12: - self.write_optional(self.write_uuid, v) - elif t == 13: - self.write_block(v) - elif t == 14: - self.write_nbt(v) - elif t == 15: - self.write_particle(**v) - elif t == 16: - self.write_villager(*v) - elif t == 17: - self.write_optional_varint(v) - elif t == 18: - self.write_pose(v) + sw[t](v) self.write_bytes(b"\xFE") From de6879d1aef9dbe4bbaac8617b4376667377bd12 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Tue, 1 Mar 2022 16:41:52 +0100 Subject: [PATCH 22/43] Add not-implemented write_block for refs --- pymine_net/types/buffer.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pymine_net/types/buffer.py b/pymine_net/types/buffer.py index ce58a1b..1977978 100644 --- a/pymine_net/types/buffer.py +++ b/pymine_net/types/buffer.py @@ -516,3 +516,9 @@ def write_node(self, node: dict) -> Self: self.write_string(node["suggestions_type"]) return self + + def write_block(self, value: object): + # TODO: This method is not yet implemented, but it needs to be here + # so that pyright doesn't mind us referencing it in other places + # but it should be implemented as soon as possible + raise NotImplementedError() From d5ae0402b66a8c62c6230b9fa7d464a1c21c6dd7 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Tue, 1 Mar 2022 16:42:02 +0100 Subject: [PATCH 23/43] Improve docstring --- pymine_net/types/buffer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymine_net/types/buffer.py b/pymine_net/types/buffer.py index 1977978..d417ce8 100644 --- a/pymine_net/types/buffer.py +++ b/pymine_net/types/buffer.py @@ -63,7 +63,7 @@ def read_byte(self) -> int: return byte def write_byte(self, value: int) -> Self: - """Writes a singular byte to the buffer.""" + """Writes a singular byte passed as an integer to the buffer.""" return self.extend(struct.pack(">b", value)) From 251530d6f809e44abef57f22793b697ed10df150 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Tue, 1 Mar 2022 16:42:44 +0100 Subject: [PATCH 24/43] Cast read('f') output as float --- pymine_net/types/buffer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymine_net/types/buffer.py b/pymine_net/types/buffer.py index d417ce8..952b85e 100644 --- a/pymine_net/types/buffer.py +++ b/pymine_net/types/buffer.py @@ -487,7 +487,7 @@ def _f_10(v): return self def read_modifier(self) -> Tuple[uuid.UUID, float, EntityModifier]: - return (self.read_uuid(), self.read("f"), EntityModifier(self.read("b"))) + return (self.read_uuid(), cast(float, self.read("f")), EntityModifier(self.read("b"))) def write_modifier(self, uuid_: uuid.UUID, amount: float, operation: EntityModifier): return self.write_uuid(uuid_).write("f", amount).write("b", operation) From 27617f48a02bdd04110689627b95f03989d424b1 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Tue, 1 Mar 2022 16:43:01 +0100 Subject: [PATCH 25/43] Run black --- pymine_net/types/buffer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pymine_net/types/buffer.py b/pymine_net/types/buffer.py index 952b85e..23ced89 100644 --- a/pymine_net/types/buffer.py +++ b/pymine_net/types/buffer.py @@ -267,7 +267,9 @@ def read_slot(self, registry: Registry) -> dict: "tag": self.read_nbt(), } - def write_slot(self, item_id: Optional[int] = None, count: int = 1, tag: Optional[nbt.TAG] = None) -> Self: + def write_slot( + self, item_id: Optional[int] = None, count: int = 1, tag: Optional[nbt.TAG] = None + ) -> Self: """Writes an inventory / container slot to the buffer.""" if item_id is None: From c0c74044f3467d41dad9a5238a260912501577a0 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Tue, 1 Mar 2022 17:07:39 +0100 Subject: [PATCH 26/43] Use Optional for params defaulting to None --- pymine_net/packets/v_1_18_1/play/advancement.py | 2 +- pymine_net/packets/v_1_18_1/play/map.py | 8 ++++---- pymine_net/types/block_palette.py | 3 ++- pymine_net/types/nbt.py | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/pymine_net/packets/v_1_18_1/play/advancement.py b/pymine_net/packets/v_1_18_1/play/advancement.py index aaa0db8..0e95cbf 100644 --- a/pymine_net/packets/v_1_18_1/play/advancement.py +++ b/pymine_net/packets/v_1_18_1/play/advancement.py @@ -41,7 +41,7 @@ class PlaySelectAdvancementTab(ClientBoundPacket): id = 0x40 - def __init__(self, identifier: str = None): + def __init__(self, identifier: Optional[str] = None): super().__init__() self.identifier = identifier diff --git a/pymine_net/packets/v_1_18_1/play/map.py b/pymine_net/packets/v_1_18_1/play/map.py index 89d5565..87ec2bb 100644 --- a/pymine_net/packets/v_1_18_1/play/map.py +++ b/pymine_net/packets/v_1_18_1/play/map.py @@ -47,10 +47,10 @@ def __init__( tracking_pos: bool, icons: List[Tuple[int, int, int, int, bool, Optional[Chat]]], columns: int, - rows: int = None, - x: int = None, - z: int = None, - data: bytes = None, + rows: Optional[int] = None, + x: Optional[int] = None, + z: Optional[int] = None, + data: Optional[bytes] = None, ): super().__init__() diff --git a/pymine_net/types/block_palette.py b/pymine_net/types/block_palette.py index f934f84..887c078 100644 --- a/pymine_net/types/block_palette.py +++ b/pymine_net/types/block_palette.py @@ -1,4 +1,5 @@ from abc import abstractmethod +from typing import Optional from strict_abc import StrictABC @@ -11,7 +12,7 @@ def get_bits_per_block(self) -> int: pass @abstractmethod - def encode(self, block: str, props: dict = None) -> int: + def encode(self, block: str, props: Optional[dict] = None) -> int: pass @abstractmethod diff --git a/pymine_net/types/nbt.py b/pymine_net/types/nbt.py index 1f1e7af..b4d5b61 100644 --- a/pymine_net/types/nbt.py +++ b/pymine_net/types/nbt.py @@ -2,7 +2,7 @@ import gzip import struct -from typing import List +from typing import List, Optional from mutf8 import decode_modified_utf8, encode_modified_utf8 @@ -71,7 +71,7 @@ class TAG: id = None - def __init__(self, name: str = None): + def __init__(self, name: Optional[str] = None): self.id = self.__class__.id self.name = "" if name is None else name From 5ac2eb3fa654a3cc68442d2a36b021f86bf657e9 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Wed, 2 Mar 2022 18:46:01 +0100 Subject: [PATCH 27/43] Make a more specific type for json-like values --- pymine_net/types/buffer.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pymine_net/types/buffer.py b/pymine_net/types/buffer.py index 23ced89..d5e4d13 100644 --- a/pymine_net/types/buffer.py +++ b/pymine_net/types/buffer.py @@ -12,9 +12,10 @@ from pymine_net.types.registry import Registry if TYPE_CHECKING: - from typing_extensions import Self + from typing_extensions import Self, TypeAlias T = TypeVar("T") +JsonCompatible: TypeAlias = Union[dict, list, str, int, float, bool, None] __all__ = ("Buffer",) @@ -178,12 +179,12 @@ def write_string(self, value: str) -> Self: return self - def read_json(self) -> object: + def read_json(self) -> JsonCompatible: """Reads json data from the buffer.""" return json.loads(self.read_string()) - def write_json(self, value: object) -> Self: + def write_json(self, value: JsonCompatible) -> Self: """Writes json data to the buffer.""" return self.write_string(json.dumps(value)) From 71db8447fa9893451f08b14047d97047f00b9cad Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Wed, 2 Mar 2022 18:46:43 +0100 Subject: [PATCH 28/43] Add some TODOs to fix weird things --- pymine_net/types/buffer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pymine_net/types/buffer.py b/pymine_net/types/buffer.py index d5e4d13..d9d8535 100644 --- a/pymine_net/types/buffer.py +++ b/pymine_net/types/buffer.py @@ -313,7 +313,7 @@ def write_recipe_item(self, value: Union[dict, str]) -> Self: if isinstance(value, dict): self.write_slot(**value) elif isinstance(value, str): - self.write_slot(value) + self.write_slot(value) # TODO: This function takes int, but we're passing str? else: raise TypeError(f"Invalid type {type(value)}.") @@ -432,7 +432,7 @@ def read_particle(self) -> dict: particle["blue"] = self.read("f") particle["scale"] = self.read("f") elif particle_id == 32: - particle["item"] = self.read_slot() + particle["item"] = self.read_slot() # TODO: This function call is missing argument? return particle From cee02edc89ae5fdd7739366c00a0819f09b311f9 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Thu, 17 Mar 2022 14:05:08 +0100 Subject: [PATCH 29/43] Revert typing generic additions to Registry Turns out it's actually very hard, and perhaps impossible to properly type-hint the Registry class because python's typing doesn't have support for Higher Kinded TypeVars. The class is also very complex (even if it doesn't have that much code) and allows for way too many edge cases, which would probably result in a bunch of overloads and type ignores, making the code even more convoluted than it already is. Instead, this removes all of the generic behavior to the Registry class and improves some minor code quality issues. This isn't ideal, since it means it'd going to rely on typing.Any, but due to this difficulty and complexity, it's justified. --- pymine_net/types/registry.py | 60 ++++++++++++++---------------------- 1 file changed, 23 insertions(+), 37 deletions(-) diff --git a/pymine_net/types/registry.py b/pymine_net/types/registry.py index 474d392..5e78795 100644 --- a/pymine_net/types/registry.py +++ b/pymine_net/types/registry.py @@ -1,60 +1,46 @@ -from typing import Dict, Generic, Iterable, Mapping, Optional, TypeVar, Union, cast, overload - -K = TypeVar("K") -V = TypeVar("V") +from typing import Any, Mapping, Sequence, Union __all__ = ("Registry",) -class Registry(Generic[K, V]): +class Registry: """Stores various Minecraft data like block types, block states, particles, fluids, entities, and more.""" - data: Dict[K, V] - data_reversed: Dict[V, K] - - @overload def __init__( self, - data: Iterable[V], - data_reversed: Optional[Iterable[V]] = None, + data: Union[Mapping, Sequence], + data_reversed: Mapping = None, ): - ... + """ + Makes a doubly hashed map, providing O(1) lookups for both keys and values. - @overload - def __init__( - self, - data: Mapping[K, V], - data_reversed: Optional[Mapping[V, K]] = None, - ): - ... + If data_reversed is specified, we use it for the reverse map instead of autogenerating + one from data by simply swapping keys and values. + + If data is given as an iterable, we treat the positions as IDs (keys) and the elements as values. + """ + if data_reversed is not None and not isinstance(data_reversed, Mapping): + raise TypeError(f"data_reversed must be a Mapping, got {type(data_reversed)}.") - def __init__( - self, - data: Union[Iterable[V], Mapping[K, V]], - data_reversed: Optional[Union[Iterable[V], Mapping[V, K]]] = None, - ): if isinstance(data, Mapping): - data = cast(Mapping[K, V], data) self.data = dict(data) - if data_reversed is None: - self.data_reversed = {v: k for k, v in data.items()} - else: - data_reversed = cast(Mapping[V, K], data_reversed) - self.data_reversed = dict(data_reversed) - # When we get an iterable, we want to treat the positions as the - # IDs for the values and the elements as keys. - elif isinstance(data, (list, tuple)): + elif isinstance(data, Sequence): self.data = {v: i for i, v in enumerate(data)} - self.data_reversed = data else: - raise TypeError(f"Can't make registry from {type(data)}, must be Iterable/Mapping.") + raise TypeError(f"Can't make registry from {type(data)}, must be Sequence/Mapping.") + + # Generate reverse mapping if it wasn't passed directly + if data_reversed is None: + self.data_reversed = {v: k for k, v in self.data.items()} + else: + self.data_reversed = data_reversed - def encode(self, key: K) -> V: + def encode(self, key: Any) -> Any: """Key -> value, most likely an identifier to an integer.""" return self.data[key] - def decode(self, value: V) -> K: + def decode(self, value: Any) -> Any: """Value -> key, most likely a numeric id to a string identifier.""" return self.data_reversed[value] From 57ff2f001959657bb16f8ffa6f613b7aefc73d6c Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Thu, 17 Mar 2022 14:20:39 +0100 Subject: [PATCH 30/43] Split logic for taking sequences to a classmethod Accepting both a mapping and a sequence from init as data is weird, it just makes the code more messy and it's not a necessary thing that needs to be supported by the main constructor, it's an alternative way to pass data over, which means it should use an alternative constructor. This adds `from_sequence` classmethod constructor to address this. --- pymine_net/types/registry.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/pymine_net/types/registry.py b/pymine_net/types/registry.py index 5e78795..1b0f155 100644 --- a/pymine_net/types/registry.py +++ b/pymine_net/types/registry.py @@ -1,4 +1,7 @@ -from typing import Any, Mapping, Sequence, Union +from typing import Any, Mapping, Sequence, Optional, TYPE_CHECKING + +if TYPE_CHECKING: + from typing_extensions import Self __all__ = ("Registry",) @@ -8,24 +11,20 @@ class Registry: def __init__( self, - data: Union[Mapping, Sequence], - data_reversed: Mapping = None, + data: Mapping, + data_reversed: Optional[Mapping] = None, ): """ Makes a doubly hashed map, providing O(1) lookups for both keys and values. If data_reversed is specified, we use it for the reverse map instead of autogenerating one from data by simply swapping keys and values. - - If data is given as an iterable, we treat the positions as IDs (keys) and the elements as values. """ if data_reversed is not None and not isinstance(data_reversed, Mapping): raise TypeError(f"data_reversed must be a Mapping, got {type(data_reversed)}.") if isinstance(data, Mapping): self.data = dict(data) - elif isinstance(data, Sequence): - self.data = {v: i for i, v in enumerate(data)} else: raise TypeError(f"Can't make registry from {type(data)}, must be Sequence/Mapping.") @@ -35,6 +34,17 @@ def __init__( else: self.data_reversed = data_reversed + @classmethod + def from_sequence(cls, seq: Sequence, reversed_data: Optional[Mapping] = None) -> Self: + """ + Initialize the registry from a sequence, using it's positions (indices) + as the values (often useful with things like numeric block IDs, etc.) + and it's elements as the keys. + + If reversed_data is passed, we simply pass it over to __init__. + """ + return cls({v: i for i, v in enumerate(seq)}, reversed_data) + def encode(self, key: Any) -> Any: """Key -> value, most likely an identifier to an integer.""" From 8980e7e243574ab68004450e335e5c1195625e78 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Thu, 17 Mar 2022 14:32:09 +0100 Subject: [PATCH 31/43] Properly annotate list of tag classes --- pymine_net/types/nbt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pymine_net/types/nbt.py b/pymine_net/types/nbt.py index a085dfc..4e2a12f 100644 --- a/pymine_net/types/nbt.py +++ b/pymine_net/types/nbt.py @@ -2,7 +2,7 @@ import gzip import struct -from typing import List, Optional +from typing import Type, List, Optional from mutf8 import decode_modified_utf8, encode_modified_utf8 @@ -25,7 +25,7 @@ "unpack", ) -TYPES: List[TAG] = [] +TYPES: List[Type[TAG]] = [] def unpack(buf, root_is_full: bool = True) -> TAG_Compound: From dc568a433567658654ccc8159f824ffc7618546d Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Thu, 17 Mar 2022 14:37:49 +0100 Subject: [PATCH 32/43] Fix get function annotation --- pymine_net/types/player.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pymine_net/types/player.py b/pymine_net/types/player.py index a587259..2ea2cfb 100644 --- a/pymine_net/types/player.py +++ b/pymine_net/types/player.py @@ -2,13 +2,15 @@ import random import struct -from typing import Dict, List, Optional, Set +from typing import Dict, List, Optional, Set, TypeVar, Union from uuid import UUID import pymine_net.types.nbt as nbt from pymine_net.enums import ChatMode, GameMode, MainHand, SkinPart from pymine_net.types.vector import Rotation, Vector3 +T = TypeVar("T") + class PlayerProperty: __slots__ = ("name", "value", "signature") @@ -58,7 +60,7 @@ def __setitem__(self, key: str, value: nbt.TAG) -> None: self._data[key] = value - def get(self, key: str, default: object = None) -> Optional[nbt.TAG]: + def get(self, key: str, default: T = None) -> Union[Optional[nbt.TAG], T]: """Gets an NBT tag from the internal NBT compound tag.""" try: From c42d1a068b85defd37fc219edeaf498e3b2e3d28 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Thu, 17 Mar 2022 14:49:56 +0100 Subject: [PATCH 33/43] Don't make packet id optional --- pymine_net/types/packet.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pymine_net/types/packet.py b/pymine_net/types/packet.py index 16260dd..9415ae4 100644 --- a/pymine_net/types/packet.py +++ b/pymine_net/types/packet.py @@ -1,7 +1,7 @@ from __future__ import annotations from abc import abstractmethod -from typing import ClassVar, Optional +from typing import ClassVar from pymine_net.strict_abc import StrictABC, optionalabstractmethod from pymine_net.types.buffer import Buffer @@ -12,10 +12,10 @@ class Packet(StrictABC): """Base Packet class. - :cvar id: Packet identification number. Defaults to None. + :cvar id: Packet identification number. """ - id: ClassVar[Optional[int]] = None + id: ClassVar[int] class ServerBoundPacket(Packet): From 683ebe244a2932b85c74ddb56710deef4a572b5f Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Thu, 17 Mar 2022 14:59:25 +0100 Subject: [PATCH 34/43] Adhere to pyright's strictParameterNoneValue Since pyright 1.1.224, strictParameterNoneValue is now true by default, causing typing errors on things like `x: int = None` which instead requires explicit: `x: Optional[int] = None` to pass now. --- pymine_net/packets/v_1_18_1/login/login.py | 3 ++- pymine_net/packets/v_1_18_1/play/crafting.py | 4 ++-- pymine_net/packets/v_1_18_1/play/player.py | 6 +++--- pymine_net/types/player.py | 8 ++++---- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/pymine_net/packets/v_1_18_1/login/login.py b/pymine_net/packets/v_1_18_1/login/login.py index 36774b1..1473718 100644 --- a/pymine_net/packets/v_1_18_1/login/login.py +++ b/pymine_net/packets/v_1_18_1/login/login.py @@ -2,6 +2,7 @@ from __future__ import annotations +from typing import Optional from uuid import UUID from pymine_net.types.buffer import Buffer @@ -190,7 +191,7 @@ class LoginPluginResponse(ServerBoundPacket): id = 0x02 - def __init__(self, message_id: int, data: bytes = None): + def __init__(self, message_id: int, data: Optional[bytes] = None): self.message_id = message_id self.data = data diff --git a/pymine_net/packets/v_1_18_1/play/crafting.py b/pymine_net/packets/v_1_18_1/play/crafting.py index 50a7b43..d4d04a0 100644 --- a/pymine_net/packets/v_1_18_1/play/crafting.py +++ b/pymine_net/packets/v_1_18_1/play/crafting.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Dict, List +from typing import Dict, List, Optional from pymine_net.types.buffer import Buffer from pymine_net.types.packet import ClientBoundPacket, ServerBoundPacket @@ -177,7 +177,7 @@ def __init__( smoker_book_open: bool, smoker_book_filter_active: bool, recipe_ids_1: List[str], - recipe_ids_2: List[list] = None, + recipe_ids_2: Optional[List[list]] = None, ): super().__init__() diff --git a/pymine_net/packets/v_1_18_1/play/player.py b/pymine_net/packets/v_1_18_1/play/player.py index d5bbd5b..14ac8c8 100644 --- a/pymine_net/packets/v_1_18_1/play/player.py +++ b/pymine_net/packets/v_1_18_1/play/player.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import List +from typing import List, Optional from uuid import UUID import pymine_net.types.nbt as nbt @@ -804,8 +804,8 @@ def __init__( target_y: float, target_z: float, is_entity: bool, - entity_id: int = None, - entity_feet_or_eyes: int = None, + entity_id: Optional[int] = None, + entity_feet_or_eyes: Optional[int] = None, ): super().__init__() diff --git a/pymine_net/types/player.py b/pymine_net/types/player.py index 2ea2cfb..83c66ba 100644 --- a/pymine_net/types/player.py +++ b/pymine_net/types/player.py @@ -32,14 +32,14 @@ def __init__(self, entity_id: int, data: nbt.TAG_Compound): ] = data # typehinted as Dict[str, nbt.TAG] for ease of development # attributes like player settings not stored in Player._data - self.username: str = None + self.username: Optional[str] = None self.properties: List[PlayerProperty] = [] self.latency = -1 - self.display_name: str = None + self.display_name: Optional[str] = None # attributes from PlayClientSettings packet - self.locale: str = None - self.view_distance: int = None + self.locale: Optional[str] = None + self.view_distance: Optional[int] = None self.chat_mode: ChatMode = ChatMode.ENABLED self.chat_colors: bool = True self.displayed_skin_parts: Set[SkinPart] = set() From ae184e150829b6cb30628b30ab3416e94cd903d1 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Thu, 17 Mar 2022 15:41:07 +0100 Subject: [PATCH 35/43] Run isort --- pymine_net/types/buffer.py | 2 +- pymine_net/types/nbt.py | 2 +- pymine_net/types/packet_map.py | 2 +- pymine_net/types/registry.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pymine_net/types/buffer.py b/pymine_net/types/buffer.py index 794d6c1..92b0459 100644 --- a/pymine_net/types/buffer.py +++ b/pymine_net/types/buffer.py @@ -3,8 +3,8 @@ import json import struct import uuid -from typing import Callable, Dict, Optional, Tuple, Union, TYPE_CHECKING, TypeVar, cast from functools import partial +from typing import TYPE_CHECKING, Callable, Dict, Optional, Tuple, TypeVar, Union, cast from pymine_net.enums import Direction, EntityModifier, Pose from pymine_net.types import nbt diff --git a/pymine_net/types/nbt.py b/pymine_net/types/nbt.py index 4e2a12f..e3c374e 100644 --- a/pymine_net/types/nbt.py +++ b/pymine_net/types/nbt.py @@ -2,7 +2,7 @@ import gzip import struct -from typing import Type, List, Optional +from typing import List, Optional, Type from mutf8 import decode_modified_utf8, encode_modified_utf8 diff --git a/pymine_net/types/packet_map.py b/pymine_net/types/packet_map.py index 14274e1..83b9aab 100644 --- a/pymine_net/types/packet_map.py +++ b/pymine_net/types/packet_map.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Dict, List, Literal, Tuple, Type, Union, TYPE_CHECKING, overload +from typing import TYPE_CHECKING, Dict, List, Literal, Tuple, Type, Union, overload from pymine_net.enums import GameState, PacketDirection from pymine_net.errors import DuplicatePacketIdError diff --git a/pymine_net/types/registry.py b/pymine_net/types/registry.py index 1b0f155..cbfcfd2 100644 --- a/pymine_net/types/registry.py +++ b/pymine_net/types/registry.py @@ -1,4 +1,4 @@ -from typing import Any, Mapping, Sequence, Optional, TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Mapping, Optional, Sequence if TYPE_CHECKING: from typing_extensions import Self From 71679e8778d3012ae8fd9c7b08c9b600e4e88c42 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Mon, 21 Mar 2022 10:11:50 +0100 Subject: [PATCH 36/43] Allow tag names to be None --- pymine_net/types/nbt.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pymine_net/types/nbt.py b/pymine_net/types/nbt.py index e3c374e..988d2cb 100644 --- a/pymine_net/types/nbt.py +++ b/pymine_net/types/nbt.py @@ -154,7 +154,7 @@ class TAG_Byte(TAG): id = 1 - def __init__(self, name: str, data: int): + def __init__(self, name: Optional[str], data: int): super().__init__(name) self.data = data @@ -177,7 +177,7 @@ class TAG_Short(TAG): id = 2 - def __init__(self, name: str, data: int): + def __init__(self, name: Optional[str], data: int): super().__init__(name) self.data = data @@ -200,7 +200,7 @@ class TAG_Int(TAG): id = 3 - def __init__(self, name: str, data: int): + def __init__(self, name: Optional[str], data: int): super().__init__(name) self.data = data @@ -223,7 +223,7 @@ class TAG_Long(TAG): id = 4 - def __init__(self, name: str, data: int): + def __init__(self, name: Optional[str], data: int): super().__init__(name) self.data = data @@ -246,7 +246,7 @@ class TAG_Float(TAG): id = 5 - def __init__(self, name: str, data: float): + def __init__(self, name: Optional[str], data: float): super().__init__(name) self.data = data @@ -269,7 +269,7 @@ class TAG_Double(TAG): id = 6 - def __init__(self, name: str, data: float): + def __init__(self, name: Optional[str], data: float): super().__init__(name) self.data = data @@ -292,7 +292,7 @@ class TAG_Byte_Array(TAG, bytearray): id = 7 - def __init__(self, name: str, data: bytearray): + def __init__(self, name: Optional[str], data: bytearray): TAG.__init__(self, name) if isinstance(data, str): @@ -322,7 +322,7 @@ class TAG_String(TAG): id = 8 - def __init__(self, name: str, data: str): + def __init__(self, name: Optional[str], data: str): super().__init__(name) self.data = data @@ -349,7 +349,7 @@ class TAG_List(TAG, list): id = 9 - def __init__(self, name: str, data: List[TAG]): + def __init__(self, name: Optional[str], data: List[TAG]): TAG.__init__(self, name) list.__init__(self, data) @@ -387,7 +387,7 @@ class TAG_Compound(TAG, dict): id = 10 - def __init__(self, name: str, data: List[TAG]): + def __init__(self, name: Optional[str], data: List[TAG]): TAG.__init__(self, name) dict.__init__(self, [(t.name, t) for t in data]) @@ -439,7 +439,7 @@ class TAG_Int_Array(TAG, list): id = 11 - def __init__(self, name: str, data: list): + def __init__(self, name: Optional[str], data: list): TAG.__init__(self, name) list.__init__(self, data) @@ -468,7 +468,7 @@ class TAG_Long_Array(TAG, list): id = 12 - def __init__(self, name: str, data: List[int]): + def __init__(self, name: Optional[str], data: List[int]): TAG.__init__(self, name) list.__init__(self, data) From 01d5eb95b129d67ab23e5439544981ba9641afd7 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Mon, 21 Mar 2022 10:26:04 +0100 Subject: [PATCH 37/43] Cast types from Buffer.read --- pymine_net/packets/v_1_18_1/play/player.py | 42 ++++++++++++++-------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/pymine_net/packets/v_1_18_1/play/player.py b/pymine_net/packets/v_1_18_1/play/player.py index 14ac8c8..e85ed3d 100644 --- a/pymine_net/packets/v_1_18_1/play/player.py +++ b/pymine_net/packets/v_1_18_1/play/player.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import List, Optional +from typing import List, Optional, cast from uuid import UUID import pymine_net.types.nbt as nbt @@ -314,7 +314,12 @@ def __init__(self, x: float, feet_y: float, z: float, on_ground: bool): @classmethod def unpack(cls, buf: Buffer) -> PlayPlayerPositionServerBound: - return cls(buf.read("d"), buf.read("d"), buf.read("d"), buf.read("?")) + return cls( + cast(int, buf.read("d")), + cast(int, buf.read("d")), + cast(int, buf.read("d")), + cast(bool, buf.read("?")) + ) class PlayPlayerPositionAndRotationServerBound(ServerBoundPacket): @@ -352,12 +357,12 @@ def __init__( @classmethod def unpack(cls, buf: Buffer) -> PlayPlayerPositionAndRotationServerBound: return cls( - buf.read("d"), - buf.read("d"), - buf.read("d"), - buf.read("f"), - buf.read("f"), - buf.read("?"), + cast(int, buf.read("d")), + cast(int, buf.read("d")), + cast(int, buf.read("d")), + cast(float, buf.read("f")), + cast(float, buf.read("f")), + cast(bool, buf.read("?")), ) @@ -416,7 +421,11 @@ def __init__(self, yaw: float, pitch: float, on_ground: bool): @classmethod def unpack(cls, buf: Buffer) -> PlayPlayerRotation: - return cls(buf.read("f"), buf.read("f"), buf.read("?")) + return cls( + cast(float, buf.read("f")), + cast(float, buf.read("f")), + cast(bool, buf.read("?")) + ) class PlayPlayerMovement(ServerBoundPacket): @@ -436,7 +445,7 @@ def __init__(self, on_ground: bool): @classmethod def unpack(cls, buf: Buffer) -> PlayPlayerMovement: - return cls(buf.read("?")) + return cls(cast(bool, buf.read("?"))) class PlayTeleportConfirm(ServerBoundPacket): @@ -530,11 +539,11 @@ def unpack(cls, buf: Buffer) -> PlayClientSettings: buf.read_string(), buf.read_byte(), buf.read_varint(), - buf.read("?"), - buf.read("B"), + cast(bool, buf.read("?")), + cast(int, buf.read("B")), buf.read_varint(), - buf.read("?"), - buf.read("?"), + cast(bool, buf.read("?")), + cast(bool, buf.read("?")), ) @@ -558,7 +567,10 @@ def __init__(self, slot_id: int, slot: dict): @classmethod def unpack(cls, buf: Buffer) -> PlayCreativeInventoryAction: - return cls(buf.read("h"), buf.read_slot()) + return cls( + cast(int, buf.read("h")), + buf.read_slot(), # TODO: This is missing a Registry parameter? + ) class PlaySpectate(ServerBoundPacket): From 933ac3df782f3fd556dd3dc9cef709345aa76703 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Mon, 21 Mar 2022 10:26:28 +0100 Subject: [PATCH 38/43] Actually return buffer from PlayPlayerInfo.pack --- pymine_net/packets/v_1_18_1/play/player.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pymine_net/packets/v_1_18_1/play/player.py b/pymine_net/packets/v_1_18_1/play/player.py index e85ed3d..5b8554d 100644 --- a/pymine_net/packets/v_1_18_1/play/player.py +++ b/pymine_net/packets/v_1_18_1/play/player.py @@ -787,6 +787,8 @@ def pack(self) -> Buffer: for player in self.players: buf.write_uuid(player.uuid) + return buf + class PlayFacePlayer(ClientBoundPacket): """Used by the server to rotate the client player to face the given location or entity. (Server -> Client) From 9b3d0b36090d437e7671768c840c555a6fdbd0a9 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Mon, 21 Mar 2022 10:31:38 +0100 Subject: [PATCH 39/43] Actually initialize buffer before calling instance methods --- pymine_net/packets/v_1_18_1/play/boss.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymine_net/packets/v_1_18_1/play/boss.py b/pymine_net/packets/v_1_18_1/play/boss.py index e88ef05..a8bb85a 100644 --- a/pymine_net/packets/v_1_18_1/play/boss.py +++ b/pymine_net/packets/v_1_18_1/play/boss.py @@ -32,7 +32,7 @@ def __init__(self, uuid: UUID, action: int, **data: dict): self.data = data def pack(self) -> Buffer: - buf = Buffer.write_uuid(self.uuid).write_varint(self.action) + buf = Buffer().write_uuid(self.uuid).write_varint(self.action) if self.action == 0: buf.write_chat(self.data["title"]).write("f", self.data["health"]).write_varint( From 8bbbdab453f59c7e01b134f0fa4d6bc8ea4fb440 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Mon, 21 Mar 2022 10:35:05 +0100 Subject: [PATCH 40/43] Actually pass kwargs to fucntion that takes kwargs --- pymine_net/packets/v_1_18_1/play/particle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymine_net/packets/v_1_18_1/play/particle.py b/pymine_net/packets/v_1_18_1/play/particle.py index e5aa490..bcd3fce 100644 --- a/pymine_net/packets/v_1_18_1/play/particle.py +++ b/pymine_net/packets/v_1_18_1/play/particle.py @@ -73,5 +73,5 @@ def pack(self) -> Buffer: .write("f", self.offset_z) .write("f", self.particle_data) .write("i", self.particle_count) - .write_particle(self.data) + .write_particle(**self.data) ) From c1593282284c292091efec5b37be72fc12c852d6 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Mon, 21 Mar 2022 10:37:57 +0100 Subject: [PATCH 41/43] Use correct type (ServerBoundPacket, not ClientBoundPacket) --- pymine_net/net/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymine_net/net/server.py b/pymine_net/net/server.py index ef0b1b7..d9c8d07 100644 --- a/pymine_net/net/server.py +++ b/pymine_net/net/server.py @@ -46,7 +46,7 @@ def _decode_packet(self, buf: Buffer) -> ServerBoundPacket: # attempt to get packet class from given state and packet id try: - packet_class: Type[ClientBoundPacket] = self.packet_map[ + packet_class: Type[ServerBoundPacket] = self.packet_map[ PacketDirection.SERVERBOUND, self.state, packet_id ] except KeyError: From 4fa6de475c50a305fa76ef47af52bdef8e5d4c1b Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Mon, 21 Mar 2022 10:43:32 +0100 Subject: [PATCH 42/43] Type-ignore vars with async init set to None in constructor --- pymine_net/net/asyncio/client.py | 6 +++++- pymine_net/net/asyncio/server.py | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/pymine_net/net/asyncio/client.py b/pymine_net/net/asyncio/client.py index ef7f50f..2712384 100644 --- a/pymine_net/net/asyncio/client.py +++ b/pymine_net/net/asyncio/client.py @@ -15,7 +15,11 @@ class AsyncProtocolClient(AbstractProtocolClient): def __init__(self, host: str, port: int, protocol: Union[int, str], packet_map: PacketMap): super().__init__(host, port, protocol, packet_map) - self.stream: AsyncTCPStream = None + # We type-ignore this assignment since we don't expect it to stay as None + # it should be set in connect function which is expected to be ran after init + # this avoids further type-ignores or casts whenever it'd be used, to tell + # type checker that it won't actually be None. + self.stream: AsyncTCPStream = None # type: ignore async def connect(self) -> None: _, writer = await asyncio.open_connection(self.host, self.port) diff --git a/pymine_net/net/asyncio/server.py b/pymine_net/net/asyncio/server.py index e3882b6..dd236b3 100644 --- a/pymine_net/net/asyncio/server.py +++ b/pymine_net/net/asyncio/server.py @@ -30,7 +30,11 @@ def __init__(self, host: str, port: int, protocol: Union[int, str], packet_map: self.connected_clients: Dict[Tuple[str, int], AsyncProtocolServerClient] = {} - self.server: asyncio.AbstractServer = None + # We type-ignore this assignment since we don't expect it to stay as None + # it should be set in run function which is expected to be ran after init + # this avoids further type-ignores or casts whenever it'd be used, to tell + # type checker that it won't actually be None. + self.server: asyncio.AbstractServer = None # type: ignore async def run(self) -> None: self.server = await asyncio.start_server(self._client_connected_cb, self.host, self.port) From 7138cc46dfebb78c2a089c476644215a2e8d7e77 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Mon, 21 Mar 2022 10:49:03 +0100 Subject: [PATCH 43/43] Don't use len() on int variable --- pymine_net/packets/v_1_18_1/play/map.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymine_net/packets/v_1_18_1/play/map.py b/pymine_net/packets/v_1_18_1/play/map.py index 87ec2bb..a1b70e4 100644 --- a/pymine_net/packets/v_1_18_1/play/map.py +++ b/pymine_net/packets/v_1_18_1/play/map.py @@ -86,7 +86,7 @@ def pack(self) -> Buffer: buf.write("B", self.columns) - if len(self.columns) < 1: + if self.columns < 1: return buf return (