From 02739d9f5636d3bc354286c9cb1b192724fcb1ff Mon Sep 17 00:00:00 2001 From: schlopp Date: Mon, 29 Apr 2024 01:50:39 +0200 Subject: [PATCH 1/8] Make MaxLenDict generic and typehint APICache.messages more accurately --- novus/api/_cache.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/novus/api/_cache.py b/novus/api/_cache.py index 26cca597..fb35661e 100644 --- a/novus/api/_cache.py +++ b/novus/api/_cache.py @@ -19,7 +19,7 @@ import logging from collections import OrderedDict -from typing import TYPE_CHECKING, Any, overload +from typing import TYPE_CHECKING, Any, TypeVar, overload from ..models.channel import Channel from ..models.guild import Guild @@ -38,13 +38,17 @@ log = logging.getLogger("novus.api.cache") -class MaxLenDict(OrderedDict): +K = TypeVar("K") +V = TypeVar("V") + + +class MaxLenDict(OrderedDict[K, V]): def __init__(self, *, max_size: int): self.max_size = max_size super().__init__() - def __setitem__(self, __key: Any, __value: Any) -> None: + def __setitem__(self, __key: K, __value: V) -> None: super().__setitem__(__key, __value) while len(self) > self.max_size: self.popitem(last=False) @@ -65,7 +69,7 @@ def __init__(self, parent: HTTPConnection): self.emojis: dict[int, Emoji] = {} self.stickers: dict[int, Sticker] = {} self.events: dict[int, ScheduledEvent] = {} - self.messages: dict[int, Message] = MaxLenDict(max_size=1_000) + self.messages: MaxLenDict[int, Message] = MaxLenDict(max_size=1_000) def __repr__(self) -> str: return f"<{self.__class__.__name__} " + ( From 39521000fc35d4e3c2a12bfd751e61ac9671f3f3 Mon Sep 17 00:00:00 2001 From: schlopp Date: Mon, 29 Apr 2024 02:09:16 +0200 Subject: [PATCH 2/8] Add a list of extreme pyright rules to ignore (mostly about unknown types) --- pyproject.toml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 2e5f470e..1af43e58 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,6 +5,19 @@ build-backend = "setuptools.build_meta" [tool.setuptools] packages.find.where = [ ".", "./novus/ext", ] +[tool.pyright] +reportPrivateUsage = false +reportImportCycles = false +reportUnknownMemberType = false +reportUnknownParameterType = false +reportUnknownArgumentType = false +reportUnknownVariableType = false +reportMissingParameterType = false +reportMissingTypeArgument = false +reportMissingTypeStubs = false +reportUnknownLambdaType = false +reportIncompatibleVariableOverride = false + [project] name = "novus" version = "1.0.0" From fd92a857c8a7ca396bb3271576755c8d6e911b74 Mon Sep 17 00:00:00 2001 From: schlopp Date: Mon, 29 Apr 2024 02:20:45 +0200 Subject: [PATCH 3/8] Avoid headers value type being inferred as str | None without changing logic --- novus/api/_http.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/novus/api/_http.py b/novus/api/_http.py index b41db348..e86ab5ac 100644 --- a/novus/api/_http.py +++ b/novus/api/_http.py @@ -209,11 +209,10 @@ def request_params( # Set headers headers = { - "Authorization": self._prefixed_token, "User-Agent": self._user_agent, } - if self._token is None: - del headers["Authorization"] + if self._prefixed_token is not None: + headers["Authorization"] = self._prefixed_token if reason: headers['X-Audit-Log-Reason'] = reason From a1d1ae01e653ee351a8f1e28e368781c4d8ff4b8 Mon Sep 17 00:00:00 2001 From: schlopp Date: Mon, 29 Apr 2024 02:22:05 +0200 Subject: [PATCH 4/8] Remove redunant `is None` check (limit is always an integer) --- novus/api/audit_log.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/novus/api/audit_log.py b/novus/api/audit_log.py index 1ba07eda..8d890853 100644 --- a/novus/api/audit_log.py +++ b/novus/api/audit_log.py @@ -50,6 +50,8 @@ async def get_guild_audit_log( """ params: dict[str, Any] = {} + params['limit'] = limit + if user_id is not None: params['user_id'] = user_id if action_type is not None: @@ -58,8 +60,7 @@ async def get_guild_audit_log( params['before'] = before if after is not None: params['after'] = after - if limit is not None: - params['limit'] = limit + route = Route( "GET", "/guilds/{guild_id}/audit-logs", From 10f67f6af1514a6ea585b939604e72c30284d740 Mon Sep 17 00:00:00 2001 From: schlopp Date: Mon, 29 Apr 2024 02:26:14 +0200 Subject: [PATCH 5/8] ignore reportUnnecessaryIsInstance whenever exceptions get raised --- novus/api/channel.py | 10 +++++----- novus/models/file.py | 2 +- novus/models/message.py | 2 +- novus/utils/localization.py | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/novus/api/channel.py b/novus/api/channel.py index f4f7ea15..a34fad69 100644 --- a/novus/api/channel.py +++ b/novus/api/channel.py @@ -268,7 +268,7 @@ async def create_reaction( emoji_str: str if isinstance(emoji, str): emoji_str = quote_plus(emoji) - elif isinstance(emoji, PartialEmoji): + elif isinstance(emoji, PartialEmoji): # pyright: ignore[reportUnnecessaryIsInstance] emoji_str = f"{emoji.name}:{emoji.id}" else: raise ValueError @@ -293,7 +293,7 @@ async def delete_own_reaction( emoji_str: str if isinstance(emoji, str): emoji_str = quote_plus(emoji) - elif isinstance(emoji, PartialEmoji): + elif isinstance(emoji, PartialEmoji): # pyright: ignore[reportUnnecessaryIsInstance] emoji_str = f"{emoji.name}:{emoji.id}" else: raise ValueError @@ -319,7 +319,7 @@ async def delete_user_reaction( emoji_str: str if isinstance(emoji, str): emoji_str = quote_plus(emoji) - elif isinstance(emoji, PartialEmoji): + elif isinstance(emoji, PartialEmoji): # pyright: ignore[reportUnnecessaryIsInstance] emoji_str = f"{emoji.name}:{emoji.id}" else: raise ValueError @@ -345,7 +345,7 @@ async def get_reactions( emoji_str: str if isinstance(emoji, str): emoji_str = quote_plus(emoji) - elif isinstance(emoji, PartialEmoji): + elif isinstance(emoji, PartialEmoji): # pyright: ignore[reportUnnecessaryIsInstance] emoji_str = f"{emoji.name}:{emoji.id}" else: raise ValueError @@ -389,7 +389,7 @@ async def delete_all_reactions_for_emoji( emoji_str: str if isinstance(emoji, str): emoji_str = quote_plus(emoji) - elif isinstance(emoji, PartialEmoji): + elif isinstance(emoji, PartialEmoji): # pyright: ignore[reportUnnecessaryIsInstance] emoji_str = f"{emoji.name}:{emoji.id}" else: raise ValueError diff --git a/novus/models/file.py b/novus/models/file.py index 9922a152..44c92cdd 100644 --- a/novus/models/file.py +++ b/novus/models/file.py @@ -79,7 +79,7 @@ def __init__( elif isinstance(data, (str, pathlib.Path)): with open(data, 'rb') as a: self.data = a.read() - elif isinstance(data, io.IOBase): + elif isinstance(data, io.IOBase): # pyright: ignore[reportUnnecessaryIsInstance] pointer = data.tell() self.data = data.read() data.seek(pointer) diff --git a/novus/models/message.py b/novus/models/message.py index 18c0b1e0..f23834e1 100644 --- a/novus/models/message.py +++ b/novus/models/message.py @@ -909,7 +909,7 @@ def only(cls, target: Role | User | GuildMember) -> Self: if isinstance(target, (User, GuildMember,)): return cls(users=[target]) - elif isinstance(target, Role): + elif isinstance(target, Role): # pyright: ignore[reportUnnecessaryIsInstance] return cls(roles=[target]) else: raise TypeError("Only role and user types are permitted.") diff --git a/novus/utils/localization.py b/novus/utils/localization.py index 5a2765d0..034e0e38 100644 --- a/novus/utils/localization.py +++ b/novus/utils/localization.py @@ -39,7 +39,7 @@ def flatten_localization(d: LocType) -> Localization: return Localization() elif isinstance(d, Localization): return d - elif isinstance(d, dict): + elif isinstance(d, dict): # pyright: ignore[reportUnnecessaryIsInstance] return Localization(d) else: raise TypeError() From c0b146d78d048021eb405d420eda3ff1889bf948 Mon Sep 17 00:00:00 2001 From: schlopp Date: Mon, 29 Apr 2024 02:29:01 +0200 Subject: [PATCH 6/8] ignore reportConstantRedefinition due to finicky behaviour when typehinting before definition --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 1af43e58..91ffb5d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ reportMissingTypeArgument = false reportMissingTypeStubs = false reportUnknownLambdaType = false reportIncompatibleVariableOverride = false +reportConstantRedefinition = false [project] name = "novus" From 782be8cb88f91197cd68ec4605999545fe251327 Mon Sep 17 00:00:00 2001 From: schlopp Date: Mon, 29 Apr 2024 02:45:12 +0200 Subject: [PATCH 7/8] Avoid dispatch handler data types from being typed too specifically --- novus/api/gateway/dispatch.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/novus/api/gateway/dispatch.py b/novus/api/gateway/dispatch.py index 81decd50..89d9dfcb 100644 --- a/novus/api/gateway/dispatch.py +++ b/novus/api/gateway/dispatch.py @@ -21,7 +21,7 @@ import json import logging from copy import copy -from typing import TYPE_CHECKING, Any, Awaitable, Callable +from typing import TYPE_CHECKING, Any, Awaitable, Callable, cast from ...models import ( # Emoji,; Object,; Sticker,; ThreadMember, AuditLogEntry, @@ -168,10 +168,13 @@ async def handle_dispatch(self, event_name: str, data: dict) -> None: elif event_name == "RESUMED": return None - coro: Callable[..., Awaitable[None]] | None + coro: Callable[[Any], Awaitable[None]] | None if self.parent.gateway.guild_ids_only: if event_name == "GUILD_CREATE": - coro = self._handle_guild_create_id_only + # Cast to avoid coro from being typed too specifically + # This is a compromise to avoid 50 different @overload's for every single + # dispatch handler's individual data type + coro = cast(Callable[[Any], Awaitable[None]], self._handle_guild_create_id_only) elif event_name == "GUILD_DELETE": coro = self.EVENT_HANDLER["GUILD_DELETE"] else: From 0053daadd5622988b2b202e41b72b1ae9d8815ec Mon Sep 17 00:00:00 2001 From: schlopp Date: Mon, 29 Apr 2024 02:48:25 +0200 Subject: [PATCH 8/8] allow GatewayGuild as valid data for Guild model --- novus/models/guild.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/novus/models/guild.py b/novus/models/guild.py index 9700a206..964fba62 100644 --- a/novus/models/guild.py +++ b/novus/models/guild.py @@ -1842,7 +1842,7 @@ class Guild(Hashable, BaseGuild): approximate_member_count: int | None welcome_screen: WelcomeScreen | None - def __init__(self, *, state: HTTPConnection, data: payloads.Guild): + def __init__(self, *, state: HTTPConnection, data: payloads.Guild | payloads.GatewayGuild): self.state = state self.id = try_snowflake(data['id']) self._emojis: dict[int, Emoji] = {}