From 2423171ab513603c7757d71f7d5142075eecf0e7 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Thu, 29 Dec 2022 19:09:29 +0100 Subject: [PATCH 01/15] Add slotscheck package --- changes/14.internal.md | 1 + poetry.lock | 19 ++++++++++++++++++- pyproject.toml | 7 +++++++ 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 changes/14.internal.md diff --git a/changes/14.internal.md b/changes/14.internal.md new file mode 100644 index 00000000..c28dfa1a --- /dev/null +++ b/changes/14.internal.md @@ -0,0 +1 @@ +Add slotscheck, ensuring `__slots__` are defined properly everywhere. diff --git a/poetry.lock b/poetry.lock index 06dcba94..d0fbd51a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1328,6 +1328,23 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +[[package]] +name = "slotscheck" +version = "0.16.1" +description = "Ensure your __slots__ are working properly." +category = "dev" +optional = false +python-versions = ">=3.7,<4" +files = [ + {file = "slotscheck-0.16.1-py3-none-any.whl", hash = "sha256:9d930279cae0a72369a2227b52318e267aba8bd6b68713e250357133ae104678"}, + {file = "slotscheck-0.16.1.tar.gz", hash = "sha256:2592c74456af0bf3f08abde8bb4c5ff2562f093e61ec12d12a97a9218f99ef2a"}, +] + +[package.dependencies] +click = ">=8.0,<9.0" +tomli = ">=0.2.6,<3.0.0" +typing-extensions = {version = ">=4.1,<5", markers = "python_version < \"3.10\""} + [[package]] name = "smmap" version = "5.0.0" @@ -1487,4 +1504,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4" -content-hash = "2c17706acd15b04ba552a427bff732d35417eb38befdec71ced6b412946a3e01" +content-hash = "08eb540e752f964f01c8ddc623246396f8222ff516617d5f752ddb9df4275513" diff --git a/pyproject.toml b/pyproject.toml index 62dd40d8..bccf8031 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,6 +62,7 @@ pep8-naming = "^0.12.1" black = "^22.3.0" isort = "^5.10.1" pyright = "^1.1.239" +slotscheck = "^0.16.1" [tool.poetry.group.release.dependencies] towncrier = "^22.12.0" @@ -133,6 +134,11 @@ type = [ { name = "Internal Changes", directory = "internal", showcontent = true }, ] +[tool.slotscheck] +strict-imports = true +require-superclass = true +require-subclass = true + [tool.taskipy.tasks] precommit = "pre-commit install" lint = "pre-commit run --all-files" @@ -140,6 +146,7 @@ black = "black ." isort = "isort ." pyright = "pyright ." flake8 = "flake8 ." +slotscheck = "slotscheck -m mcproto" test = "pytest -v --failed-first" retest = "pytest -v --last-failed" test-nocov = "pytest -v --no-cov --failed-first" From d3bb94de7e7082ffedfc0bff235cfe741a0ce344 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Thu, 29 Dec 2022 19:09:39 +0100 Subject: [PATCH 02/15] Add slotscheck to pre-commit --- .pre-commit-config.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b01945b0..e8968854 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -44,6 +44,17 @@ repos: language: system types: [python] + - repo: local + hooks: + - id: slotscheck + name: Slotscheck + description: "Slotscheck: Ensure your __slots__ are working properly" + entry: poetry run slotscheck -v + language: python + require_serial: true + types: [python] + exclude: "^(?!mcproto/)" + - repo: local hooks: - id: pyright From fc8b3eb7d41a399ae9ddf1643c6bb3ca89377286 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Thu, 29 Dec 2022 19:10:37 +0100 Subject: [PATCH 03/15] Run slotscheck separately in validation workflow --- .github/workflows/validation.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/validation.yml b/.github/workflows/validation.yml index cd0bce05..4225d34f 100644 --- a/.github/workflows/validation.yml +++ b/.github/workflows/validation.yml @@ -27,7 +27,7 @@ jobs: install_args: "--without release" - name: Run pre-commit hooks - run: SKIP=black,isort,flake8,pyright pre-commit run --all-files + run: SKIP=black,isort,flake8,slotscheck,pyright pre-commit run --all-files - name: Run black formatter check run: black --check --diff . @@ -38,5 +38,8 @@ jobs: - name: Run flake8 linter run: flake8 . + - name: Run slotscheck + run: slotscheck -m mcproto + - name: Run pyright type checker run: pyright . From 9c36fbce0d7e18c6c098838e1f930c7aa67b3cc1 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Thu, 29 Dec 2022 19:14:00 +0100 Subject: [PATCH 04/15] Make connection classes slotted --- changes/14.feature.md | 2 ++ mcproto/connection.py | 12 ++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 changes/14.feature.md diff --git a/changes/14.feature.md b/changes/14.feature.md new file mode 100644 index 00000000..c854e3e9 --- /dev/null +++ b/changes/14.feature.md @@ -0,0 +1,2 @@ +Add `__slots__` to most classes in the project + - All connection classes are now slotted diff --git a/mcproto/connection.py b/mcproto/connection.py index 3e3c2144..ea096199 100644 --- a/mcproto/connection.py +++ b/mcproto/connection.py @@ -31,6 +31,8 @@ class SyncConnection(BaseSyncReader, BaseSyncWriter, ABC): + __slots__ = ("closed",) + def __init__(self): self.closed = False @@ -59,6 +61,8 @@ def __exit__(self, *a, **kw) -> None: class AsyncConnection(BaseAsyncReader, BaseAsyncWriter, ABC): + __slots__ = ("closed",) + def __init__(self): self.closed = False @@ -87,6 +91,8 @@ async def __aexit__(self, *a, **kw) -> None: class TCPSyncConnection(SyncConnection, Generic[T_SOCK]): + __slots__ = ("socket",) + def __init__(self, socket: T_SOCK): super().__init__() self.socket = socket @@ -130,6 +136,8 @@ def _close(self) -> None: class TCPAsyncConnection(AsyncConnection, Generic[T_STREAMREADER, T_STREAMWRITER]): + __slots__ = ("reader", "writer", "timeout") + def __init__(self, reader: T_STREAMREADER, writer: T_STREAMWRITER, timeout: float): super().__init__() self.reader = reader @@ -180,6 +188,8 @@ def socket(self) -> socket.socket: class UDPSyncConnection(SyncConnection, Generic[T_SOCK]): + __slots__ = ("socket", "address") + BUFFER_SIZE = 65535 def __init__(self, socket: T_SOCK, address: tuple[str, int]): @@ -215,6 +225,8 @@ def _close(self) -> None: class UDPAsyncConnection(AsyncConnection, Generic[T_DATAGRAM_CLIENT]): + __slots__ = ("stream", "timeout") + def __init__(self, stream: T_DATAGRAM_CLIENT, timeout: float): super().__init__() self.stream = stream From 363de4ca2df1e5b8832d4f1a5e117cc99de35346 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Thu, 29 Dec 2022 19:15:58 +0100 Subject: [PATCH 05/15] Make ABC classes use __slots__ --- changes/14.feature.md | 1 + mcproto/utils/abc.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/changes/14.feature.md b/changes/14.feature.md index c854e3e9..3f0ff26f 100644 --- a/changes/14.feature.md +++ b/changes/14.feature.md @@ -1,2 +1,3 @@ Add `__slots__` to most classes in the project - All connection classes are now slotted + - Classes in `mcproto.utils.abc` are now slotted diff --git a/mcproto/utils/abc.py b/mcproto/utils/abc.py index f592888d..f2821004 100644 --- a/mcproto/utils/abc.py +++ b/mcproto/utils/abc.py @@ -32,6 +32,8 @@ class RequiredParamsABCMixin: and if _REQUIRED_CLASS_VARS_NO_MRO isn't set, no such check will be performed. """ + __slots__ = () + _REQUIRRED_CLASS_VARS: ClassVar[Sequence[str]] _REQUIRED_CLASS_VARS_NO_MRO: ClassVar[Sequence[str]] @@ -64,6 +66,8 @@ def __new__(cls: type[Self], *a, **kw) -> Self: class Serializable(ABC): """Base class for any type that should be (de)serializable into/from given buffer data.""" + __slots__ = () + @abstractmethod def serialize(self) -> Buffer: """Represent the object as a transmittable sequence of bytes.""" From cd6f718d2725b325feaf2d89f438de3f0860c525 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Thu, 29 Dec 2022 19:26:42 +0100 Subject: [PATCH 06/15] Ignore tests in slotscheck from config file --- .pre-commit-config.yaml | 1 - pyproject.toml | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e8968854..c49a29b8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -53,7 +53,6 @@ repos: language: python require_serial: true types: [python] - exclude: "^(?!mcproto/)" - repo: local hooks: diff --git a/pyproject.toml b/pyproject.toml index bccf8031..cf2ac6f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -138,6 +138,11 @@ type = [ strict-imports = true require-superclass = true require-subclass = true +exclude-modules = ''' +( + ^test # ignore any tests +) +''' [tool.taskipy.tasks] precommit = "pre-commit install" From 12bfbd16d4c222c6c2dda6756c74be356210c107 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Thu, 29 Dec 2022 19:44:13 +0100 Subject: [PATCH 07/15] Add __slots__ to SemanticVersion --- changes/14.feature.md | 1 + mcproto/utils/version.py | 2 ++ pyproject.toml | 1 + 3 files changed, 4 insertions(+) diff --git a/changes/14.feature.md b/changes/14.feature.md index 3f0ff26f..ba589258 100644 --- a/changes/14.feature.md +++ b/changes/14.feature.md @@ -1,3 +1,4 @@ Add `__slots__` to most classes in the project - All connection classes are now slotted - Classes in `mcproto.utils.abc` are now slotted + - `mcproto.utils.version.SemanticVersion` class is now slotted diff --git a/mcproto/utils/version.py b/mcproto/utils/version.py index 11b61d67..9cf412bb 100644 --- a/mcproto/utils/version.py +++ b/mcproto/utils/version.py @@ -29,6 +29,8 @@ class SemanticVersion: Relies on Semantic Versioning specification (see ). """ + __slots__ = ("version", "prerelease", "build_metadata") + version: tuple[int, int, int] prerelease: Optional[tuple[str, ...]] = None build_metadata: Optional[tuple[str, ...]] = None diff --git a/pyproject.toml b/pyproject.toml index cf2ac6f4..eb8ca5d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -141,6 +141,7 @@ require-subclass = true exclude-modules = ''' ( ^test # ignore any tests + |^mcproto\.utils\.version # Dataclasses cause false-positives; See: https://github.com/ariebovenberg/slotscheck/issues/129 ) ''' From 8b56bbc53f02fa5d44d1699ae7d3b4a40b6c7163 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Thu, 29 Dec 2022 19:57:51 +0100 Subject: [PATCH 08/15] Ignore DecoratorFunction protocol class in slotscheck --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index eb8ca5d5..0215877c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -142,6 +142,7 @@ exclude-modules = ''' ( ^test # ignore any tests |^mcproto\.utils\.version # Dataclasses cause false-positives; See: https://github.com/ariebovenberg/slotscheck/issues/129 + |^mcproto\.utils\.deprecation # Protocol classes don't need __slots__; See: https://github.com/ariebovenberg/slotscheck/issues/130 ) ''' From c0b84e24e138590d94c2e5125f23dd601945e13e Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Thu, 29 Dec 2022 20:00:02 +0100 Subject: [PATCH 09/15] Make ServerBoundPacket and ClientBoundPacket classes slotted --- changes/14.bugfix.md | 2 ++ mcproto/packets/abc.py | 4 ++++ 2 files changed, 6 insertions(+) create mode 100644 changes/14.bugfix.md diff --git a/changes/14.bugfix.md b/changes/14.bugfix.md new file mode 100644 index 00000000..1e1b9520 --- /dev/null +++ b/changes/14.bugfix.md @@ -0,0 +1,2 @@ +Add missing `__slots__` to `ServerBoundPacket` and `ClientBoundPacket` subclasses, which inherited from slotted +`Packet`, but didn't themselves define `__slots__`, causing `__dict__` to be needlessly created. diff --git a/mcproto/packets/abc.py b/mcproto/packets/abc.py index 2665e2dd..120134fe 100644 --- a/mcproto/packets/abc.py +++ b/mcproto/packets/abc.py @@ -42,6 +42,10 @@ class Packet(Serializable, RequiredParamsABCMixin): class ServerBoundPacket(Packet): """Packet bound to a server (Client -> Server).""" + __slots__ = () + class ClientBoundPacket(Packet): """Packet bound to a client (Server -> Client).""" + + __slots__ = () From 05998480051ba4cdd6e94c35fc0115550c347f7d Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Thu, 29 Dec 2022 20:19:33 +0100 Subject: [PATCH 10/15] Remove __slots__ from SemanticVersion dataclass (not supported by python) --- changes/14.feature.md | 1 - mcproto/utils/version.py | 2 -- pyproject.toml | 2 +- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/changes/14.feature.md b/changes/14.feature.md index ba589258..3f0ff26f 100644 --- a/changes/14.feature.md +++ b/changes/14.feature.md @@ -1,4 +1,3 @@ Add `__slots__` to most classes in the project - All connection classes are now slotted - Classes in `mcproto.utils.abc` are now slotted - - `mcproto.utils.version.SemanticVersion` class is now slotted diff --git a/mcproto/utils/version.py b/mcproto/utils/version.py index 9cf412bb..11b61d67 100644 --- a/mcproto/utils/version.py +++ b/mcproto/utils/version.py @@ -29,8 +29,6 @@ class SemanticVersion: Relies on Semantic Versioning specification (see ). """ - __slots__ = ("version", "prerelease", "build_metadata") - version: tuple[int, int, int] prerelease: Optional[tuple[str, ...]] = None build_metadata: Optional[tuple[str, ...]] = None diff --git a/pyproject.toml b/pyproject.toml index 0215877c..cfedc5e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -141,7 +141,7 @@ require-subclass = true exclude-modules = ''' ( ^test # ignore any tests - |^mcproto\.utils\.version # Dataclasses cause false-positives; See: https://github.com/ariebovenberg/slotscheck/issues/129 + |^mcproto\.utils\.version # Dataclasses below python 3.10 don't support __slots__ due to default value fields being treated as classvars. |^mcproto\.utils\.deprecation # Protocol classes don't need __slots__; See: https://github.com/ariebovenberg/slotscheck/issues/130 ) ''' From ab24fd9c1a7662c645da6d67ff17d0ae57a8bb2e Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Fri, 30 Dec 2022 00:14:26 +0100 Subject: [PATCH 11/15] Update slotscheck --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index d0fbd51a..bea229b8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1330,14 +1330,14 @@ files = [ [[package]] name = "slotscheck" -version = "0.16.1" +version = "0.16.2" description = "Ensure your __slots__ are working properly." category = "dev" optional = false python-versions = ">=3.7,<4" files = [ - {file = "slotscheck-0.16.1-py3-none-any.whl", hash = "sha256:9d930279cae0a72369a2227b52318e267aba8bd6b68713e250357133ae104678"}, - {file = "slotscheck-0.16.1.tar.gz", hash = "sha256:2592c74456af0bf3f08abde8bb4c5ff2562f093e61ec12d12a97a9218f99ef2a"}, + {file = "slotscheck-0.16.2-py3-none-any.whl", hash = "sha256:e1e06183e381048dd83ce96e97a262464c8df26ccba987a89b4f79c78e002760"}, + {file = "slotscheck-0.16.2.tar.gz", hash = "sha256:92d33a6dc3125830b77460471c15c9b99224c8c6368813ea128dca299f4e55fa"}, ] [package.dependencies] From a3367a017ab498c5ee46aeb3070cd198f4a541ef Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Fri, 30 Dec 2022 00:25:55 +0100 Subject: [PATCH 12/15] Use typing-extensions directly on runtime --- mcproto/connection.py | 21 ++++++++----------- mcproto/packets/interactions.py | 4 ++-- mcproto/packets/map.py | 7 +++---- mcproto/packets/v757/handshaking/handshake.py | 8 +++---- mcproto/packets/v757/status/ping.py | 8 +++---- mcproto/packets/v757/status/status.py | 7 +++---- mcproto/protocol/base_io.py | 13 ++++++------ mcproto/utils/abc.py | 7 +++---- mcproto/utils/deprecation.py | 14 +++++-------- mcproto/utils/version.py | 5 ++--- mcproto/utils/version_map.py | 9 ++++---- poetry.lock | 4 ++-- pyproject.toml | 1 + tests/helpers.py | 8 +++---- 14 files changed, 49 insertions(+), 67 deletions(-) diff --git a/mcproto/connection.py b/mcproto/connection.py index ea096199..58dfa463 100644 --- a/mcproto/connection.py +++ b/mcproto/connection.py @@ -3,23 +3,13 @@ import asyncio import socket from abc import ABC, abstractmethod -from typing import Generic, Optional, TYPE_CHECKING, TypeVar +from typing import Generic, Optional, TypeVar import asyncio_dgram +from typing_extensions import ParamSpec, Self from mcproto.protocol.base_io import BaseAsyncReader, BaseAsyncWriter, BaseSyncReader, BaseSyncWriter -if TYPE_CHECKING: - from typing_extensions import ParamSpec, Self - - P = ParamSpec("P") - -R = TypeVar("R") -T_SOCK = TypeVar("T_SOCK", bound=socket.socket) -T_STREAMREADER = TypeVar("T_STREAMREADER", bound=asyncio.StreamReader) -T_STREAMWRITER = TypeVar("T_STREAMWRITER", bound=asyncio.StreamWriter) -T_DATAGRAM_CLIENT = TypeVar("T_DATAGRAM_CLIENT", bound=asyncio_dgram.aio.DatagramClient) - __all__ = [ "AsyncConnection", "SyncConnection", @@ -29,6 +19,13 @@ "UDPSyncConnection", ] +P = ParamSpec("P") +R = TypeVar("R") +T_SOCK = TypeVar("T_SOCK", bound=socket.socket) +T_STREAMREADER = TypeVar("T_STREAMREADER", bound=asyncio.StreamReader) +T_STREAMWRITER = TypeVar("T_STREAMWRITER", bound=asyncio.StreamWriter) +T_DATAGRAM_CLIENT = TypeVar("T_DATAGRAM_CLIENT", bound=asyncio_dgram.aio.DatagramClient) + class SyncConnection(BaseSyncReader, BaseSyncWriter, ABC): __slots__ = ("closed",) diff --git a/mcproto/packets/interactions.py b/mcproto/packets/interactions.py index 13e9e97b..17783cb5 100644 --- a/mcproto/packets/interactions.py +++ b/mcproto/packets/interactions.py @@ -9,10 +9,10 @@ from mcproto.packets.map import PacketMap from mcproto.protocol.base_io import BaseAsyncReader, BaseAsyncWriter, BaseSyncReader, BaseSyncWriter -T_Packet = TypeVar("T_Packet", bound=Packet) - __all__ = ["async_read_packet", "async_write_packet", "sync_read_packet", "sync_write_packet", "PACKET_MAP"] +T_Packet = TypeVar("T_Packet", bound=Packet) + # PACKET FORMAT: # | Field name | Field type | Notes | # |-------------|---------------|---------------------------------------| diff --git a/mcproto/packets/map.py b/mcproto/packets/map.py index 71296435..e8932f35 100644 --- a/mcproto/packets/map.py +++ b/mcproto/packets/map.py @@ -1,14 +1,13 @@ from __future__ import annotations from collections.abc import Mapping -from typing import Any, ClassVar, Literal, TYPE_CHECKING, overload +from typing import Any, ClassVar, Literal, overload + +from typing_extensions import TypeGuard from mcproto.packets.abc import ClientBoundPacket, GameState, Packet, PacketDirection, ServerBoundPacket from mcproto.utils.version_map import VersionMap, WalkableModuleData -if TYPE_CHECKING: - from typing_extensions import TypeGuard - __all__ = ["PacketMap"] diff --git a/mcproto/packets/v757/handshaking/handshake.py b/mcproto/packets/v757/handshaking/handshake.py index 65cdb822..0ad6f0d9 100644 --- a/mcproto/packets/v757/handshaking/handshake.py +++ b/mcproto/packets/v757/handshaking/handshake.py @@ -1,16 +1,14 @@ from __future__ import annotations from enum import IntEnum -from typing import ClassVar, TYPE_CHECKING, Union +from typing import ClassVar, Union + +from typing_extensions import Self from mcproto.buffer import Buffer from mcproto.packets.abc import GameState, ServerBoundPacket from mcproto.protocol.base_io import StructFormat -if TYPE_CHECKING: - from typing_extensions import Self - - __all__ = [ "NextState", "Handshake", diff --git a/mcproto/packets/v757/status/ping.py b/mcproto/packets/v757/status/ping.py index 9fec7f3d..5094b18a 100644 --- a/mcproto/packets/v757/status/ping.py +++ b/mcproto/packets/v757/status/ping.py @@ -1,15 +1,13 @@ from __future__ import annotations -from typing import ClassVar, TYPE_CHECKING +from typing import ClassVar + +from typing_extensions import Self from mcproto.buffer import Buffer from mcproto.packets.abc import ClientBoundPacket, GameState, ServerBoundPacket from mcproto.protocol.base_io import StructFormat -if TYPE_CHECKING: - from typing_extensions import Self - - __all__ = ["PingPong"] diff --git a/mcproto/packets/v757/status/status.py b/mcproto/packets/v757/status/status.py index fa54aa91..ae28baef 100644 --- a/mcproto/packets/v757/status/status.py +++ b/mcproto/packets/v757/status/status.py @@ -1,14 +1,13 @@ from __future__ import annotations import json -from typing import Any, ClassVar, TYPE_CHECKING +from typing import Any, ClassVar + +from typing_extensions import Self from mcproto.buffer import Buffer from mcproto.packets.abc import ClientBoundPacket, GameState, ServerBoundPacket -if TYPE_CHECKING: - from typing_extensions import Self - __all__ = ["StatusRequest", "StatusResponse"] diff --git a/mcproto/protocol/base_io.py b/mcproto/protocol/base_io.py index ae775f8b..b7630f3d 100644 --- a/mcproto/protocol/base_io.py +++ b/mcproto/protocol/base_io.py @@ -5,15 +5,11 @@ from collections.abc import Awaitable, Callable from enum import Enum from itertools import count -from typing import Literal, Optional, TYPE_CHECKING, TypeVar, Union, overload +from typing import Literal, Optional, TypeVar, Union, overload -from mcproto.protocol.utils import from_twos_complement, to_twos_complement - -if TYPE_CHECKING: - from typing_extensions import TypeAlias +from typing_extensions import TypeAlias -T = TypeVar("T") -R = TypeVar("R") +from mcproto.protocol.utils import from_twos_complement, to_twos_complement __all__ = [ "BaseAsyncReader", @@ -25,6 +21,9 @@ "FLOAT_FORMATS_TYPE", ] +T = TypeVar("T") +R = TypeVar("R") + # region: Format types diff --git a/mcproto/utils/abc.py b/mcproto/utils/abc.py index f2821004..03803a25 100644 --- a/mcproto/utils/abc.py +++ b/mcproto/utils/abc.py @@ -2,12 +2,11 @@ from abc import ABC, abstractmethod from collections.abc import Sequence -from typing import ClassVar, TYPE_CHECKING +from typing import ClassVar -from mcproto.buffer import Buffer +from typing_extensions import Self -if TYPE_CHECKING: - from typing_extensions import Self +from mcproto.buffer import Buffer __all__ = ["RequiredParamsABCMixin", "Serializable"] diff --git a/mcproto/utils/deprecation.py b/mcproto/utils/deprecation.py index e973631f..9f56dba8 100644 --- a/mcproto/utils/deprecation.py +++ b/mcproto/utils/deprecation.py @@ -4,20 +4,16 @@ import warnings from collections.abc import Callable from functools import wraps -from typing import Optional, TYPE_CHECKING, TypeVar, Union +from typing import Optional, TypeVar, Union -from mcproto.utils.version import SemanticVersion +from typing_extensions import ParamSpec, Protocol -if TYPE_CHECKING: - from typing_extensions import ParamSpec, Protocol +from mcproto.utils.version import SemanticVersion - P = ParamSpec("P") -else: - Protocol = object +__all__ = ["deprecated", "deprecation_warn"] R = TypeVar("R") - -__all__ = ["deprecated", "deprecation_warn"] +P = ParamSpec("P") def deprecation_warn( diff --git a/mcproto/utils/version.py b/mcproto/utils/version.py index 11b61d67..850608f5 100644 --- a/mcproto/utils/version.py +++ b/mcproto/utils/version.py @@ -3,10 +3,9 @@ import re from dataclasses import dataclass from itertools import zip_longest -from typing import Any, Optional, TYPE_CHECKING +from typing import Any, Optional -if TYPE_CHECKING: - from typing_extensions import Self +from typing_extensions import Self __all__ = ["SemanticVersion"] diff --git a/mcproto/utils/version_map.py b/mcproto/utils/version_map.py index d0075195..a6d14f3f 100644 --- a/mcproto/utils/version_map.py +++ b/mcproto/utils/version_map.py @@ -6,18 +6,17 @@ from abc import ABC, abstractmethod from collections.abc import Hashable, Iterator, Sequence from types import ModuleType -from typing import Any, ClassVar, Generic, NamedTuple, NoReturn, TYPE_CHECKING, TypeVar +from typing import Any, ClassVar, Generic, NamedTuple, NoReturn, TypeVar + +from typing_extensions import TypeGuard from mcproto.utils.abc import RequiredParamsABCMixin -if TYPE_CHECKING: - from typing_extensions import TypeGuard +__all__ = ["VersionMap", "WalkableModuleData"] K = TypeVar("K", bound=Hashable) V = TypeVar("V") -__all__ = ["VersionMap", "WalkableModuleData"] - class WalkableModuleData(NamedTuple): module: ModuleType diff --git a/poetry.lock b/poetry.lock index bea229b8..6bd4ea8f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1456,7 +1456,7 @@ dev = ["furo", "packaging", "sphinx (>=5)", "twisted"] name = "typing-extensions" version = "4.4.0" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1504,4 +1504,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4" -content-hash = "08eb540e752f964f01c8ddc623246396f8222ff516617d5f752ddb9df4275513" +content-hash = "1242b1dc76ae09243775b0c6703fca3f8d0b30bc7f7ae623d0f05773ff61b0f2" diff --git a/pyproject.toml b/pyproject.toml index cfedc5e4..74e932f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ packages = [{ include = "mcproto" }] [tool.poetry.dependencies] python = ">=3.8.1,<4" asyncio-dgram = "^2.1.2" +typing-extensions = "^4.4.0" [tool.poetry.group.dev.dependencies] pre-commit = "^2.18.1" diff --git a/tests/helpers.py b/tests/helpers.py index 7718b6cf..97014f37 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -4,14 +4,12 @@ import inspect import unittest.mock from collections.abc import Callable, Coroutine -from typing import Any, Generic, TYPE_CHECKING, TypeVar +from typing import Any, Generic, TypeVar -if TYPE_CHECKING: - from typing_extensions import ParamSpec - - P = ParamSpec("P") +from typing_extensions import ParamSpec T = TypeVar("T") +P = ParamSpec("P") T_Mock = TypeVar("T_Mock", bound=unittest.mock.Mock) From bc369662c5ca1792136b971019eaeb69d6fbc78e Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Fri, 30 Dec 2022 00:27:18 +0100 Subject: [PATCH 13/15] Remove resolved slotscheck exclude on Protocol class --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 74e932f1..0adfba82 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -143,7 +143,6 @@ exclude-modules = ''' ( ^test # ignore any tests |^mcproto\.utils\.version # Dataclasses below python 3.10 don't support __slots__ due to default value fields being treated as classvars. - |^mcproto\.utils\.deprecation # Protocol classes don't need __slots__; See: https://github.com/ariebovenberg/slotscheck/issues/130 ) ''' From 87a164890850207f04f9192a67d40e5ae40d9157 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Fri, 30 Dec 2022 00:41:42 +0100 Subject: [PATCH 14/15] Split change fragment for #14 into 2 files --- changes/{14.internal.md => 14.internal.1.md} | 0 changes/14.internal.2.md | 1 + 2 files changed, 1 insertion(+) rename changes/{14.internal.md => 14.internal.1.md} (100%) create mode 100644 changes/14.internal.2.md diff --git a/changes/14.internal.md b/changes/14.internal.1.md similarity index 100% rename from changes/14.internal.md rename to changes/14.internal.1.md diff --git a/changes/14.internal.2.md b/changes/14.internal.2.md new file mode 100644 index 00000000..e844f65d --- /dev/null +++ b/changes/14.internal.2.md @@ -0,0 +1 @@ +Make `typing-extensions` a runtime dependency and use it directly, don't rely on `if typing.TYPE_CHECKING` blocks. From 7d1535fe7943207f5966ad55fe8738d175ebe2f6 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Fri, 30 Dec 2022 00:57:01 +0100 Subject: [PATCH 15/15] Explain how to handle PRs making multiple distinct changes of same category --- changes/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/changes/README.md b/changes/README.md index c4db2265..4186fe4c 100644 --- a/changes/README.md +++ b/changes/README.md @@ -32,6 +32,14 @@ the file with your `$EDITOR`. If necessary, multiple fragment files can be created per pull-request, with different change types, if the PR covers multiple areas (for example a PR that both introduces a feature, and changes the documentation). +Additionally, if a single PR is addressing multiple unrelated topics in the same category, and needs to make multiple +distinct changelog entries, you can do so by adding an optional counter value to the fragment file name, which needs to +be an integer, for example: `25.internal.1.md` and `25.internal.2.md`. This counter value will not be shown in the +final changelog and is merely here for separation of the individual fragments. That said, if you end up making multiple +distinct changelog fragments like this is a good sign that your PR is probably too big, and you should split it up into +multiple PRs. Making huge PRs that address several unrelated topics at once are generally a bad practice, and should be +avoided. + To preview the latest changelog, run `towncrier build --draft --version [version number]`. (For version number, you can pretty much enter anything as this is just for a draft version. For true builds, this would be the next version number, so for example, if the current version is 1.0.2, next one will be one either 1.0.3, or 1.1.0, or 2.0.0. But for drafts,