Repository: hikari-py/hikari
Pull request #1108 (2022-03-27 19:33:42)

Improve pyright support
Ignored all pyright errors that go against the design patterns of hikari.

All of the ignored errors should be unfixable unless hikari underwent a large rewrite so there should be no problems ignoring them.



Patch:
diff --git a/changes/1108.bugfix.md b/changes/1108.bugfix.md
new file mode 100644
index 00000000..ed671938
--- /dev/null
+++ b/changes/1108.bugfix.md
@@ -0,0 +1,1 @@
+Improved pyright support.
diff --git a/changes/1120.bugfix.md b/changes/1120.bugfix.md
new file mode 100644
index 00000000..04734d5e
--- /dev/null
+++ b/changes/1120.bugfix.md
@@ -0,0 +1,1 @@
+Fix unicode decode error caused by `latin-1` encoding when sending the banner.
diff --git a/changes/1121.bugfix.md b/changes/1121.bugfix.md
new file mode 100644
index 00000000..67440ca3
--- /dev/null
+++ b/changes/1121.bugfix.md
@@ -0,0 +1,2 @@
+Don't error on an out-of-spec HTTP status code (e.g one of Cloudflare's custom status codes).
+`HTTPResponseError.status` may now be of type `http.HTTPStatus` or `int`.
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 71bd7717..cb14f8bf 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -29,7 +29,7 @@ sphobjinv==2.2.2
 #################
 
 mypy==0.942
-pyright==1.1.234
+pyright==1.1.235
 
 #######################
 # DEPENDENCY CHECKING #
diff --git a/examples/__init__.py b/examples/__init__.py
index 10e6d281..81d74ef3 100644
--- a/examples/__init__.py
+++ b/examples/__init__.py
@@ -7,4 +7,4 @@
 #
 # You should have received a copy of the CC0 Public Domain Dedication along with this software.
 # If not, see <https://creativecommons.org/publicdomain/zero/1.0/>.
-"""Allows mypy to run here."""
+"""Allows type-checkers to run here."""
diff --git a/hikari/api/rest.py b/hikari/api/rest.py
index 9068d967..55dcc855 100644
--- a/hikari/api/rest.py
+++ b/hikari/api/rest.py
@@ -1199,9 +1199,6 @@ async def create_message(
             `role_mentions` or `user_mentions` or if both `attachment` and
             `attachments`, `component` and `components` or `embed` and `embeds`
             are specified.
-        builtins.TypeError
-            If `attachments`, `components` or `embeds` is passed but is not a
-            sequence.
         hikari.errors.BadRequestError
             This may be raised in several discrete situations, such as messages
             being empty with no attachments or embeds; messages with more than
@@ -1446,9 +1443,6 @@ async def edit_message(
         builtins.ValueError
             If both `attachment` and `attachments`, `component` and `components`
             or `embed` and `embeds` are specified.
-        builtins.TypeError
-            If `attachments`, `components` or `embeds` is passed but is not a
-            sequence.
         hikari.errors.BadRequestError
             This may be raised in several discrete situations, such as messages
             being empty with no embeds; messages with more than 2000 characters
@@ -2358,8 +2352,6 @@ async def execute_webhook(
             If more than 100 unique objects/entities are passed for
             `role_mentions` or `user_mentions` or if both `attachment` and
             `attachments` or `embed` and `embeds` are specified.
-        builtins.TypeError
-            If `attachments`, or `embeds` is passed but is not a sequence.
         hikari.errors.BadRequestError
             This may be raised in several discrete situations, such as messages
             being empty with no attachments or embeds; messages with more than
@@ -2579,9 +2571,6 @@ async def edit_webhook_message(
         builtins.ValueError
             If both `attachment` and `attachments`, `component` and `components`
             or `embed` and `embeds` are specified.
-        builtins.TypeError
-            If `attachments`, `components` or `embeds` is passed but is not a
-            sequence.
         hikari.errors.BadRequestError
             This may be raised in several discrete situations, such as messages
             being empty with no attachments or embeds; messages with more than
@@ -7715,9 +7704,6 @@ async def edit_interaction_response(
         builtins.ValueError
             If both `attachment` and `attachments`, `component` and `components`
             or `embed` and `embeds` are specified.
-        builtins.TypeError
-            If `attachments`, `components` or `embeds` is passed but is not a
-            sequence.
         hikari.errors.BadRequestError
             This may be raised in several discrete situations, such as messages
             being empty with no attachments or embeds; messages with more than
diff --git a/hikari/colors.py b/hikari/colors.py
index cbdb9317..7808b681 100644
--- a/hikari/colors.py
+++ b/hikari/colors.py
@@ -517,12 +517,12 @@ def of(cls, value: Colorish, /) -> Color:
             if len(value) != 3:
                 raise ValueError(f"Color must be an RGB triplet if set to a {type(value).__name__} type")
 
-            if any(isinstance(c, float) for c in value):
-                r, g, b = value
+            r, g, b = value
+
+            if isinstance(r, float) and isinstance(g, float) and isinstance(b, float):
                 return cls.from_rgb_float(r, g, b)
 
-            if all(isinstance(c, int) for c in value):
-                r, g, b = value
+            if isinstance(r, int) and isinstance(g, int) and isinstance(b, int):
                 return cls.from_rgb(r, g, b)
 
         if isinstance(value, str):
diff --git a/hikari/embeds.py b/hikari/embeds.py
index 24321c52..442b61e1 100644
--- a/hikari/embeds.py
+++ b/hikari/embeds.py
@@ -278,11 +278,11 @@ def is_inline(self, value: bool) -> None:
         self._inline = value
 
 
-def _ensure_embed_resource(resource: files.Resourceish, cls: typing.Type[_T]) -> _T:
+def _ensure_embed_resource(resource: files.Resourceish) -> files.Resource[files.AsyncReader]:
     if isinstance(resource, EmbedResource):
-        return cls(resource=resource.resource)
+        return resource.resource
 
-    return cls(resource=files.ensure_resource(resource))
+    return files.ensure_resource(resource)
 
 
 class Embed:
@@ -742,7 +742,7 @@ def set_author(
         if name is None and url is None and icon is None:
             self._author = None
         else:
-            real_icon = _ensure_embed_resource(icon, EmbedResourceWithProxy) if icon is not None else None
+            real_icon = EmbedResourceWithProxy(resource=_ensure_embed_resource(icon)) if icon is not None else None
             self._author = EmbedAuthor(name=name, url=url, icon=real_icon)
         return self
 
@@ -791,7 +791,7 @@ def set_footer(self, text: typing.Optional[str], *, icon: typing.Optional[files.
 
             self._footer = None
         else:
-            real_icon = _ensure_embed_resource(icon, EmbedResourceWithProxy) if icon is not None else None
+            real_icon = EmbedResourceWithProxy(resource=_ensure_embed_resource(icon)) if icon is not None else None
             self._footer = EmbedFooter(icon=real_icon, text=text)
         return self
 
@@ -829,7 +829,7 @@ def set_image(self, image: typing.Optional[files.Resourceish] = None, /) -> Embe
             This embed. Allows for call chaining.
         """
         if image is not None:
-            self._image = _ensure_embed_resource(image, EmbedImage)
+            self._image = EmbedImage(resource=_ensure_embed_resource(image))
         else:
             self._image = None
 
@@ -868,7 +868,7 @@ def set_thumbnail(self, image: typing.Optional[files.Resourceish] = None, /) ->
             This embed. Allows for call chaining.
         """
         if image is not None:
-            self._thumbnail = _ensure_embed_resource(image, EmbedImage)
+            self._thumbnail = EmbedImage(resource=_ensure_embed_resource(image))
         else:
             self._thumbnail = None
 
diff --git a/hikari/errors.py b/hikari/errors.py
index 4273d489..470bb553 100644
--- a/hikari/errors.py
+++ b/hikari/errors.py
@@ -231,8 +231,12 @@ class HTTPResponseError(HTTPError):
     url: str = attr.field()
     """The URL that produced this error message."""
 
-    status: http.HTTPStatus = attr.field()
-    """The HTTP status code for the response."""
+    status: typing.Union[http.HTTPStatus, int] = attr.field()
+    """The HTTP status code for the response.
+
+    This will be `int` if it's outside the range of status codes in the HTTP
+    specification (e.g. one of Cloudflare's non-standard status codes).
+    """
 
     headers: data_binding.Headers = attr.field()
     """The headers received in the error response."""
@@ -247,8 +251,12 @@ class HTTPResponseError(HTTPError):
     """The error code."""
 
     def __str__(self) -> str:
-        name = self.status.name.replace("_", " ").title()
-        name_value = f"{name} {self.status.value}"
+        if isinstance(self.status, http.HTTPStatus):
+            name = self.status.name.replace("_", " ").title()
+            name_value = f"{name} {self.status.value}"
+
+        else:
+            name_value = f"Unknown Status {self.status}"
 
         if self.code:
             code_str = f" ({self.code})"
diff --git a/hikari/events/typing_events.py b/hikari/events/typing_events.py
index 40756df7..6d02278c 100644
--- a/hikari/events/typing_events.py
+++ b/hikari/events/typing_events.py
@@ -196,12 +196,12 @@ def user_id(self) -> snowflakes.Snowflake:
         # <<inherited docstring from TypingEvent>>.
         return self.member.id
 
-    async def fetch_channel(self) -> typing.Union[channels.TextableGuildChannel]:
+    async def fetch_channel(self) -> channels.TextableGuildChannel:
         """Perform an API call to fetch an up-to-date image of this channel.
 
         Returns
         -------
-        typing.Union[hikari.channels.TextableGuildChannel]
+        hikari.channels.TextableGuildChannel
             The channel.
         """
         channel = await super().fetch_channel()
diff --git a/hikari/impl/buckets.py b/hikari/impl/buckets.py
index bc5848d8..1733c5dd 100644
--- a/hikari/impl/buckets.py
+++ b/hikari/impl/buckets.py
@@ -496,7 +496,7 @@ def do_gc_pass(self, expire_after: float) -> None:
             `RESTBucketManager.start` and `RESTBucketManager.close` to control
             this instead.
         """
-        buckets_to_purge = []
+        buckets_to_purge: typing.List[str] = []
 
         now = time.monotonic()
 
diff --git a/hikari/impl/config.py b/hikari/impl/config.py
index 9fc03597..581a878c 100644
--- a/hikari/impl/config.py
+++ b/hikari/impl/config.py
@@ -246,7 +246,7 @@ class HTTPTimeoutSettings:
     @request_socket_connect.validator
     @request_socket_read.validator
     @total.validator
-    def _(self, attrib: attr.Attribute[typing.Optional[float]], value: typing.Optional[float]) -> None:
+    def _(self, attrib: attr.Attribute[typing.Optional[float]], value: typing.Any) -> None:
         # This error won't occur until some time in the future where it will be annoying to
         # try and determine the root cause, so validate it NOW.
         if value is not None and (not isinstance(value, (float, int)) or value <= 0):
@@ -317,7 +317,7 @@ class HTTPSettings(config.HTTPSettings):
     """
 
     @max_redirects.validator
-    def _(self, _: attr.Attribute[typing.Optional[int]], value: typing.Optional[int]) -> None:
+    def _(self, _: attr.Attribute[typing.Optional[int]], value: typing.Any) -> None:
         # This error won't occur until some time in the future where it will be annoying to
         # try and determine the root cause, so validate it NOW.
         if value is not None and (not isinstance(value, int) or value <= 0):
diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py
index 5d31777a..27ceaa4d 100644
--- a/hikari/impl/entity_factory.py
+++ b/hikari/impl/entity_factory.py
@@ -636,7 +636,7 @@ def deserialize_implicit_token(self, query: data_binding.Query) -> application_m
     def _deserialize_audit_log_change_roles(
         self, payload: data_binding.JSONArray
     ) -> typing.Mapping[snowflakes.Snowflake, guild_models.PartialRole]:
-        roles = {}
+        roles: typing.Dict[snowflakes.Snowflake, guild_models.PartialRole] = {}
         for role_payload in payload:
             role = guild_models.PartialRole(
                 app=self._app, id=snowflakes.Snowflake(role_payload["id"]), name=role_payload["name"]
@@ -710,7 +710,7 @@ def deserialize_audit_log(self, payload: data_binding.JSONObject) -> audit_log_m
         for entry_payload in payload["audit_log_entries"]:
             entry_id = snowflakes.Snowflake(entry_payload["id"])
 
-            changes = []
+            changes: typing.List[audit_log_models.AuditLogChange] = []
             if (change_payloads := entry_payload.get("changes")) is not None:
                 for change_payload in change_payloads:
                     key: typing.Union[audit_log_models.AuditLogChangeKey, str] = audit_log_models.AuditLogChangeKey(
@@ -723,7 +723,9 @@ def deserialize_audit_log(self, payload: data_binding.JSONObject) -> audit_log_m
                         new_value = value_converter(new_value) if new_value is not None else None
                         old_value = value_converter(old_value) if old_value is not None else None
 
-                    elif not isinstance(key, audit_log_models.AuditLogChangeKey):
+                    elif not isinstance(
+                        key, audit_log_models.AuditLogChangeKey
+                    ):  # pyright: ignore [reportUnnecessaryIsInstance]
                         _LOGGER.debug("Unknown audit log change key found %r", key)
 
                     changes.append(audit_log_models.AuditLogChange(key=key, new_value=new_value, old_value=old_value))
@@ -1375,10 +1377,9 @@ def deserialize_member(
             time.iso8601_datetime_string_to_datetime(raw_premium_since) if raw_premium_since is not None else None
         )
 
+        communication_disabled_until: typing.Optional[datetime.datetime] = None
         if raw_communication_disabled_until := payload.get("communication_disabled_until"):
             communication_disabled_until = time.iso8601_datetime_string_to_datetime(raw_communication_disabled_until)
-        else:
-            communication_disabled_until = None
 
         return guild_models.Member(
             user=user,
@@ -2622,7 +2623,7 @@ def deserialize_member_presence(  # noqa: CFQ001 - Max function length
         *,
         guild_id: undefined.UndefinedOr[snowflakes.Snowflake] = undefined.UNDEFINED,
     ) -> presence_models.MemberPresence:
-        activities = []
+        activities: typing.List[presence_models.RichActivity] = []
         for activity_payload in payload["activities"]:
             timestamps: typing.Optional[presence_models.ActivityTimestamps] = None
             if "timestamps" in activity_payload:
diff --git a/hikari/impl/event_manager_base.py b/hikari/impl/event_manager_base.py
index c4a98354..e855d314 100644
--- a/hikari/impl/event_manager_base.py
+++ b/hikari/impl/event_manager_base.py
@@ -63,7 +63,7 @@
         typing.List[event_manager_.CallbackT[base_events.EventT]],
     ]
     _WaiterT = typing.Tuple[
-        typing.Optional[event_manager_.PredicateT[base_events.EventT]], asyncio.Future[base_events.EventT]
+        typing.Optional[event_manager_.PredicateT[base_events.EventT]], "asyncio.Future[base_events.EventT]"
     ]
     _WaiterMapT = typing.Dict[typing.Type[base_events.EventT], typing.Set[_WaiterT[base_events.EventT]]]
 
@@ -103,8 +103,8 @@ def __events_bitmask__(self) -> int:
 
 def _generate_weak_listener(
     reference: weakref.WeakMethod[typing.Any],
-) -> typing.Callable[[base_events.EventT], typing.Coroutine[typing.Any, typing.Any, None]]:
-    async def call_weak_method(event: base_events.EventT) -> None:
+) -> typing.Callable[[base_events.Event], typing.Coroutine[typing.Any, typing.Any, None]]:
+    async def call_weak_method(event: base_events.Event) -> None:
         method = reference()
         if method is None:
             raise TypeError(
@@ -555,10 +555,7 @@ def decorator(
 
         return decorator
 
-    def dispatch(self, event: base_events.EventT) -> asyncio.Future[typing.Any]:
-        if not isinstance(event, base_events.Event):
-            raise TypeError(f"Events must be subclasses of {base_events.Event.__name__}, not {type(event).__name__}")
-
+    def dispatch(self, event: base_events.Event) -> asyncio.Future[typing.Any]:
         tasks: typing.List[typing.Coroutine[None, typing.Any, None]] = []
 
         for cls in event.dispatches():
@@ -613,6 +610,7 @@ async def wait_for(
 
         future: asyncio.Future[base_events.EventT] = asyncio.get_running_loop().create_future()
 
+        waiter_set: typing.MutableSet[_WaiterT[base_events.Event]]
         try:
             waiter_set = self._waiters[event_type]
         except KeyError:
diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py
index a9e7c6c1..926a3895 100644
--- a/hikari/impl/rest.py
+++ b/hikari/impl/rest.py
@@ -260,9 +260,6 @@ def proxy_settings(self) -> config_impl.ProxySettings:
         return self._rest().proxy_settings
 
 
-_NONE_OR_UNDEFINED: typing.Final[typing.Tuple[None, undefined.UndefinedType]] = (None, undefined.UNDEFINED)
-
-
 class RESTApp(traits.ExecutorAware):
     """The base for a HTTP-only Discord application.
 
@@ -762,7 +759,7 @@ async def _request(
                     )
 
                     if trace_logging_enabled:
-                        time_taken = (time.monotonic() - start) * 1_000
+                        time_taken = (time.monotonic() - start) * 1_000  # pyright: ignore[reportUnboundVariable]
                         _LOGGER.log(
                             ux.TRACE,
                             "%s %s %s in %sms\n%s",
@@ -1246,24 +1243,6 @@ def _build_message_payload(  # noqa: C901- Function too complex
         if not undefined.any_undefined(embed, embeds):
             raise ValueError("You may only specify one of 'embed' or 'embeds', not both")
 
-        if attachments is not undefined.UNDEFINED and not isinstance(attachments, typing.Collection):
-            raise TypeError(
-                "You passed a non-collection to 'attachments', but this expects a collection. Maybe you meant to "
-                "use 'attachment' (singular) instead?"
-            )
-
-        if components not in _NONE_OR_UNDEFINED and not isinstance(components, typing.Collection):
-            raise TypeError(
-                "You passed a non-collection to 'components', but this expects a collection. Maybe you meant to "
-                "use 'component' (singular) instead?"
-            )
-
-        if embeds not in _NONE_OR_UNDEFINED and not isinstance(embeds, typing.Collection):
-            raise TypeError(
-                "You passed a non-collection to 'embeds', but this expects a collection. Maybe you meant to "
-                "use 'embed' (singular) instead?"
-            )
-
         if undefined.all_undefined(embed, embeds) and isinstance(content, embeds_.Embed):
             # Syntactic sugar, common mistake to accidentally send an embed
             # as the content, so let's detect this and fix it for the user.
diff --git a/hikari/internal/attr_extensions.py b/hikari/internal/attr_extensions.py
index 82fa4743..faf114d6 100644
--- a/hikari/internal/attr_extensions.py
+++ b/hikari/internal/attr_extensions.py
@@ -60,7 +60,7 @@ def invalidate_deep_copy_cache() -> None:
 
 
 def get_fields_definition(
-    cls: typing.Type[ModelT],
+    cls: type,
 ) -> typing.Tuple[
     typing.Sequence[typing.Tuple[attr.Attribute[typing.Any], str]], typing.Sequence[attr.Attribute[typing.Any]]
 ]:
@@ -76,8 +76,8 @@ def get_fields_definition(
     typing.Sequence[typing.Tuple[builtins.str, builtins.str]]
         A sequence of tuples of string attribute names to string key-word names.
     """
-    init_results = []
-    non_init_results = []
+    init_results: typing.List[typing.Tuple[attr.Attribute[typing.Any], str]] = []
+    non_init_results: typing.List[attr.Attribute[typing.Any]] = []
 
     for field in attr.fields(cls):
         if field.init:
diff --git a/hikari/internal/collections.py b/hikari/internal/collections.py
index db61827b..3c5509e0 100644
--- a/hikari/internal/collections.py
+++ b/hikari/internal/collections.py
@@ -134,28 +134,6 @@ def __setitem__(self, key: KeyT, value: ValueT) -> None:
         self._data[key] = value
 
 
-class _FrozenDict(typing.MutableMapping[KeyT, ValueT]):
-    __slots__: typing.Sequence[str] = ("_source",)
-
-    def __init__(self, source: typing.Dict[KeyT, typing.Tuple[float, ValueT]], /) -> None:
-        self._source = source
-
-    def __getitem__(self, key: KeyT) -> ValueT:
-        return self._source[key][1]
-
-    def __iter__(self) -> typing.Iterator[KeyT]:
-        return iter(self._source)
-
-    def __len__(self) -> int:
-        return len(self._source)
-
-    def __delitem__(self, key: KeyT) -> None:
-        del self._source[key]
-
-    def __setitem__(self, key: KeyT, value: ValueT) -> None:
-        self._source[key] = (0.0, value)
-
-
 class LimitedCapacityCacheMap(ExtendedMutableMapping[KeyT, ValueT]):
     """Implementation of a capacity-limited most-recently-inserted mapping.
 
@@ -357,8 +335,6 @@ def get_index_or_slice(
 
     Raises
     ------
-    TypeError
-        If `index_or_slice` isn't a `builtins.slice` or `builtins.int`.
     IndexError
         If `index_or_slice` is an int and is outside the range of the mapping's
         contents.
@@ -366,10 +342,7 @@ def get_index_or_slice(
     if isinstance(index_or_slice, slice):
         return tuple(itertools.islice(mapping.values(), index_or_slice.start, index_or_slice.stop, index_or_slice.step))
 
-    if isinstance(index_or_slice, int):
-        try:
-            return next(itertools.islice(mapping.values(), index_or_slice, None))
-        except StopIteration:
-            raise IndexError(index_or_slice) from None
-
-    raise TypeError(f"sequence indices must be integers or slices, not {type(index_or_slice).__name__}")
+    try:
+        return next(itertools.islice(mapping.values(), index_or_slice, None))
+    except StopIteration:
+        raise IndexError(index_or_slice) from None
diff --git a/hikari/internal/net.py b/hikari/internal/net.py
index 3024f513..4e0f0cd8 100644
--- a/hikari/internal/net.py
+++ b/hikari/internal/net.py
@@ -62,7 +62,10 @@ async def generate_error_response(response: aiohttp.ClientResponse) -> errors.HT
     if response.status == http.HTTPStatus.NOT_FOUND:
         return errors.NotFoundError(*args)
 
-    status = http.HTTPStatus(response.status)
+    try:
+        status: typing.Union[http.HTTPStatus, int] = http.HTTPStatus(response.status)
+    except ValueError:
+        status = response.status
 
     if 400 <= status < 500:
         return errors.ClientHTTPResponseError(real_url, status, response.headers, raw_body)
diff --git a/hikari/internal/reflect.py b/hikari/internal/reflect.py
index d28af6c3..ec1524b1 100644
--- a/hikari/internal/reflect.py
+++ b/hikari/internal/reflect.py
@@ -62,7 +62,7 @@ def resolve_signature(func: typing.Callable[..., typing.Any]) -> inspect.Signatu
 
     signature = inspect.signature(func)
     resolved_typehints = typing.get_type_hints(func)
-    params = []
+    params: typing.List[inspect.Parameter] = []
 
     none_type = type(None)
     for name, param in signature.parameters.items():
diff --git a/hikari/internal/ux.py b/hikari/internal/ux.py
index 04217fc9..d38c744e 100644
--- a/hikari/internal/ux.py
+++ b/hikari/internal/ux.py
@@ -33,7 +33,6 @@
 import re
 import string
 import sys
-import time
 import typing
 import warnings
 
@@ -223,10 +222,8 @@ def print_banner(
         for code in colorlog.escape_codes.escape_codes:
             args[code] = ""
 
-    sys.stdout.write(string.Template(raw_banner).safe_substitute(args))
-    # Give the stream some time to flush
-    sys.stdout.flush()
-    time.sleep(0.125)
+    with open(sys.stdout.fileno(), "w", encoding="utf-8", closefd=False) as stdout:
+        stdout.write(string.Template(raw_banner).safe_substitute(args))
 
 
 def supports_color(allow_color: bool, force_color: bool) -> bool:
diff --git a/hikari/iterators.py b/hikari/iterators.py
index 0aeeabe6..8b83de56 100644
--- a/hikari/iterators.py
+++ b/hikari/iterators.py
@@ -115,7 +115,7 @@ def __call__(self, item: ValueT) -> bool:
     def __invert__(self) -> typing.Callable[[ValueT], bool]:
         return lambda item: not self(item)
 
-    def __or__(self, other: All[ValueT]) -> All[ValueT]:
+    def __or__(self, other: typing.Any) -> All[ValueT]:
         if not isinstance(other, All):
             raise TypeError(f"unsupported operand type(s) for |: {type(self).__name__!r} and {type(other).__name__!r}")
 
diff --git a/hikari/snowflakes.py b/hikari/snowflakes.py
index b0f06576..77d8ac0b 100644
--- a/hikari/snowflakes.py
+++ b/hikari/snowflakes.py
@@ -93,7 +93,7 @@ def min(cls) -> Snowflake:
             return cls.___MIN___
 
         except AttributeError:
-            cls.___MIN___ = Snowflake(0)
+            cls.___MIN___ = Snowflake(0)  # pyright: ignore[reportConstantRedefinition]
             return cls.___MIN___
 
     @classmethod
@@ -103,7 +103,7 @@ def max(cls) -> Snowflake:
             return cls.___MAX___
 
         except AttributeError:
-            cls.___MAX___ = Snowflake((1 << 63) - 1)
+            cls.___MAX___ = Snowflake((1 << 63) - 1)  # pyright: ignore[reportConstantRedefinition]
             return cls.___MAX___
 
     @classmethod
diff --git a/pipelines/mypy.nox.py b/pipelines/mypy.nox.py
index bb5a5395..65c0cfde 100644
--- a/pipelines/mypy.nox.py
+++ b/pipelines/mypy.nox.py
@@ -34,7 +34,7 @@
 
 @nox.session(reuse_venv=True)
 def mypy(session: nox.Session) -> None:
-    """Perform static type analysis on Python source code."""
+    """Perform static type analysis on Python source code using mypy."""
     session.install(
         "-r",
         "requirements.txt",
diff --git a/pipelines/pyright.nox.py b/pipelines/pyright.nox.py
index 60eecdbf..4e2d6d01 100644
--- a/pipelines/pyright.nox.py
+++ b/pipelines/pyright.nox.py
@@ -25,6 +25,27 @@
 from pipelines import nox
 
 
+@nox.session()
+def pyright(session: nox.Session) -> None:
+    """Perform static type analysis on Python source code using pyright.
+
+    At the time of writing this, this pipeline will not run successfully,
+    as hikari does not have 100% compatibility with pyright just yet. This
+    exists to make it easier to test and eventually reach that 100% compatibility.
+    """
+    session.install(
+        "-r",
+        "requirements.txt",
+        "-r",
+        "speedup-requirements.txt",
+        "-r",
+        "server-requirements.txt",
+        "-r",
+        "dev-requirements.txt",
+    )
+    session.run("python", "-m", "pyright")
+
+
 @nox.session()
 def verify_types(session: nox.Session) -> None:
     """Verify the "type completeness" of types exported by the library using Pyright."""
diff --git a/pyproject.toml b/pyproject.toml
index dcbab9a8..29e5efc0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -59,10 +59,24 @@ exclude_lines = [
 ]
 
 [tool.pyright]
-include = ["examples", "hikari"]
+include = ["hikari", "examples"]
+exclude = ["examples/simple_dashboard.py", "**/__init__.py", "hikari/internal/enums.py", "hikari/internal/fast_protocol.py"]
 pythonVersion = "3.8"
 typeCheckingMode = "strict"
 
+reportUnnecessaryTypeIgnoreComment = "error"
+reportMissingTypeStubs = "none"
+reportImportCycles = "none" # Doesn't account for TYPE_CHECKING
+reportIncompatibleMethodOverride = "none"  # This relies on ordering for keyword-only arguments
+reportOverlappingOverload = "none" # Type-Vars in last overloads may interfere
+reportIncompatibleVariableOverride = "none" # Cannot overwrite abstract properties using attrs
+
+# Attrs validators will always be unknown
+# https://github.com/python-attrs/attrs/issues/795
+reportUnknownMemberType = "warning"
+reportUntypedFunctionDecorator = "warning"
+reportOptionalMemberAccess = "warning"
+
 [tool.pytest.ini_options]
 asyncio_mode = "strict"
 xfail_strict = true


Test patch:
diff --git a/tests/hikari/impl/test_rest.py b/tests/hikari/impl/test_rest.py
index f821e524..9ad20649 100644
--- a/tests/hikari/impl/test_rest.py
+++ b/tests/hikari/impl/test_rest.py
@@ -23,7 +23,6 @@
 import contextlib
 import datetime
 import http
-import re
 import warnings
 
 import mock
@@ -1334,19 +1333,6 @@ def test__build_message_payload_when_both_single_and_plural_args_passed(
         ):
             rest_client._build_message_payload(**{singular_arg: object(), plural_arg: object()})
 
-    @pytest.mark.parametrize(
-        ("singular_arg", "plural_arg"),
-        [("attachment", "attachments"), ("component", "components"), ("embed", "embeds")],
-    )
-    def test__build_message_payload_when_non_collection_passed_to_plural(self, rest_client, singular_arg, plural_arg):
-        expected_error_message = (
-            f"You passed a non-collection to '{plural_arg}', but this expects a collection. Maybe you meant to use "
-            f"'{singular_arg}' (singular) instead?"
-        )
-
-        with pytest.raises(TypeError, match=re.escape(expected_error_message)):
-            rest_client._build_message_payload(**{plural_arg: object()})
-
     def test_interaction_deferred_builder(self, rest_client):
         result = rest_client.interaction_deferred_builder(5)
 
diff --git a/tests/hikari/internal/test_collections.py b/tests/hikari/internal/test_collections.py
index a6b98026..4b4cdac1 100644
--- a/tests/hikari/internal/test_collections.py
+++ b/tests/hikari/internal/test_collections.py
@@ -80,35 +80,6 @@ def test___setitem__(self):
         assert mock_map == {"hmm": "forearm", "cat": "bag", "ok": "bye", "bye": 4}
 
 
-class TestFrozenDict:
-    def test___init__(self):
-        mock_map = collections._FrozenDict({"foo": (0.432, "bar"), "blam": (0.111, "okok")})
-        assert mock_map == {"foo": "bar", "blam": "okok"}
-
-    def test___getitem__(self):
-        mock_map = collections._FrozenDict({"blam": (0.432, "bar"), "obar": (0.111, "okok")})
-        assert mock_map["obar"] == "okok"
-
-    def test___iter__(self):
-        mock_map = collections._FrozenDict({"bye": (0.33, "bye"), "111": (0.2, "222"), "45949": (0.5, "020202")})
-        assert list(mock_map) == ["bye", "111", "45949"]
-
-    def test___len__(self):
-        mock_map = collections._FrozenDict({"wsw": (0.3, "3"), "fdsa": (0.55, "ewqwe"), "45949": (0.23, "fsasd")})
-        assert len(mock_map) == 3
-
-    def test___delitem__(self):
-        mock_map = collections._FrozenDict({"rororo": (0.55, "bye bye"), "raw": (0.999, "ywywyw")})
-        del mock_map["raw"]
-        assert mock_map == {"rororo": "bye bye"}
-
-    def test___setitem__(self):
-        mock_map = collections._FrozenDict({"rororo": (0.55, "bye 3231"), "2121": (0.999, "4321")})
-        mock_map["foo bar"] = 42
-
-        assert mock_map == {"rororo": "bye 3231", "2121": "4321", "foo bar": 42}
-
-
 class TestLimitedCapacityCacheMap:
     def test___init___with_source(self):
         raw_map = {"voo": "doo", "blam": "blast", "foo": "bye"}
@@ -380,8 +351,3 @@ def test_get_index_or_slice_with_index_outside_range():
 def test_get_index_or_slice_with_slice():
     test_map = {"o": "b", "b": "o", "a": "m", "arara": "blam", "oof": "no", "rika": "may"}
     assert collections.get_index_or_slice(test_map, slice(1, 5, 2)) == ("o", "blam")
-
-
-def test_get_index_or_slice_with_invalid_type():
-    with pytest.raises(TypeError):
-        collections.get_index_or_slice({}, object())
diff --git a/tests/hikari/internal/test_net.py b/tests/hikari/internal/test_net.py
index 4d7312fb..62891653 100644
--- a/tests/hikari/internal/test_net.py
+++ b/tests/hikari/internal/test_net.py
@@ -70,6 +70,52 @@ async def json(self):
     assert returned is error()
 
 
+@pytest.mark.parametrize(
+    ("status_", "expected_error"),
+    [
+        # The following internal server non-conforming status codes are used by cloudflare.
+        # Source I made it up...
+        # jk https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
+        (520, "InternalServerError"),
+        (521, "InternalServerError"),
+        (522, "InternalServerError"),
+        (523, "InternalServerError"),
+        (524, "InternalServerError"),
+        (525, "InternalServerError"),
+        (526, "InternalServerError"),
+        (527, "InternalServerError"),
+        (530, "InternalServerError"),
+        # These non-conforming bad requests status codes are sent by NGINX.
+        # Same source as cloudflare status codes.
+        (494, "ClientHTTPResponseError"),
+        (495, "ClientHTTPResponseError"),
+        (496, "ClientHTTPResponseError"),
+        (497, "ClientHTTPResponseError"),
+        # This non-conforming status code is made up.
+        (694, "HTTPResponseError"),
+    ],
+)
+@pytest.mark.asyncio()
+async def test_generate_error_response_with_non_conforming_status_code(status_, expected_error):
+    class StubResponse:
+        real_url = "https://some.url"
+        status = status_
+        headers = {}
+
+        async def read(self):
+            return "some raw body"
+
+        async def json(self):
+            return {"message": "raw message", "code": 123}
+
+    with mock.patch.object(errors, expected_error) as error:
+        returned = await net.generate_error_response(StubResponse())
+
+    error.assert_called_once_with("https://some.url", status_, {}, "some raw body")
+
+    assert returned is error()
+
+
 @pytest.mark.parametrize(
     ("status_", "expected_error"),
     [
diff --git a/tests/hikari/internal/test_ux.py b/tests/hikari/internal/test_ux.py
index d5c6b96f..fbbcb563 100644
--- a/tests/hikari/internal/test_ux.py
+++ b/tests/hikari/internal/test_ux.py
@@ -19,6 +19,7 @@
 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 # SOFTWARE.
+import builtins
 import contextlib
 import importlib
 import logging
@@ -180,9 +181,10 @@ def test_when_supports_color(self, mock_args):
         supports_color = stack.enter_context(mock.patch.object(ux, "supports_color", return_value=True))
         read_text = stack.enter_context(mock.patch.object(importlib.resources, "read_text"))
         template = stack.enter_context(mock.patch.object(string, "Template"))
-        write = stack.enter_context(mock.patch.object(sys.stdout, "write"))
+        builtins_open = stack.enter_context(mock.patch.object(builtins, "open"))
         abspath = stack.enter_context(mock.patch.object(os.path, "abspath", return_value="some path"))
         dirname = stack.enter_context(mock.patch.object(os.path, "dirname"))
+        fileno = stack.enter_context(mock.patch.object(sys.stdout, "fileno"))
 
         with stack:
             ux.print_banner("hikari", True, False)
@@ -207,7 +209,8 @@ def test_when_supports_color(self, mock_args):
 
         template.assert_called_once_with(read_text())
         template().safe_substitute.assert_called_once_with(args)
-        write.assert_called_once_with(template().safe_substitute())
+        builtins_open.assert_called_once_with(fileno.return_value, "w", encoding="utf-8", closefd=False)
+        builtins_open.return_value.__enter__.return_value.write.assert_called_once_with(template().safe_substitute())
         dirname.assert_called_once_with("~/hikari")
         abspath.assert_called_once_with(dirname())
         supports_color.assert_called_once_with(True, False)
@@ -221,9 +224,10 @@ def test_when_doesnt_supports_color(self, mock_args):
         supports_color = stack.enter_context(mock.patch.object(ux, "supports_color", return_value=False))
         read_text = stack.enter_context(mock.patch.object(importlib.resources, "read_text"))
         template = stack.enter_context(mock.patch.object(string, "Template"))
-        write = stack.enter_context(mock.patch.object(sys.stdout, "write"))
         abspath = stack.enter_context(mock.patch.object(os.path, "abspath", return_value="some path"))
         dirname = stack.enter_context(mock.patch.object(os.path, "dirname"))
+        builtins_open = stack.enter_context(mock.patch.object(builtins, "open"))
+        fileno = stack.enter_context(mock.patch.object(sys.stdout, "fileno"))
 
         with stack:
             ux.print_banner("hikari", True, False)
@@ -248,10 +252,11 @@ def test_when_doesnt_supports_color(self, mock_args):
 
         template.assert_called_once_with(read_text())
         template().safe_substitute.assert_called_once_with(args)
-        write.assert_called_once_with(template().safe_substitute())
         dirname.assert_called_once_with("~/hikari")
         abspath.assert_called_once_with(dirname())
         supports_color.assert_called_once_with(True, False)
+        builtins_open.assert_called_once_with(fileno.return_value, "w", encoding="utf-8", closefd=False)
+        builtins_open.return_value.__enter__.return_value.write.assert_called_once_with(template().safe_substitute())
 
     def test_use_extra_args(self, mock_args):
         stack = contextlib.ExitStack()
@@ -259,8 +264,9 @@ def test_use_extra_args(self, mock_args):
         stack.enter_context(mock.patch.object(time, "sleep"))
         read_text = stack.enter_context(mock.patch.object(importlib.resources, "read_text"))
         template = stack.enter_context(mock.patch.object(string, "Template"))
-        write = stack.enter_context(mock.patch.object(sys.stdout, "write"))
+        builtins_open = stack.enter_context(mock.patch.object(builtins, "open"))
         stack.enter_context(mock.patch.object(os.path, "abspath", return_value="some path"))
+        fileno = stack.enter_context(mock.patch.object(sys.stdout, "fileno"))
 
         extra_args = {
             "extra_argument_1": "one",
@@ -289,7 +295,8 @@ def test_use_extra_args(self, mock_args):
 
         template.assert_called_once_with(read_text())
         template().safe_substitute.assert_called_once_with(args)
-        write.assert_called_once_with(template().safe_substitute())
+        builtins_open.assert_called_once_with(fileno.return_value, "w", encoding="utf-8", closefd=False)
+        builtins_open.return_value.__enter__.return_value.write.assert_called_once_with(template().safe_substitute())
 
     def test_overwrite_args_raises_error(self, mock_args):
         stack = contextlib.ExitStack()
diff --git a/tests/hikari/test_audit_logs.py b/tests/hikari/test_audit_logs.py
index b76c44e7..b14ae126 100644
--- a/tests/hikari/test_audit_logs.py
+++ b/tests/hikari/test_audit_logs.py
@@ -158,15 +158,6 @@ def test_get_item_with_slice(self):
         )
         assert audit_log[1:5:2] == (entry_1, entry_2)
 
-    def test_get_item_with_ivalid_type(self):
-        with pytest.raises(TypeError):
-            audit_logs.AuditLog(
-                entries=[object(), object()],
-                integrations={},
-                users={},
-                webhooks={},
-            )["OK"]
-
     def test_len(self):
         audit_log = audit_logs.AuditLog(
             entries={
diff --git a/tests/hikari/test_colors.py b/tests/hikari/test_colors.py
index 2a99976c..1f178fb5 100644
--- a/tests/hikari/test_colors.py
+++ b/tests/hikari/test_colors.py
@@ -276,7 +276,7 @@ def test_Color_to_bytes(self):
         [
             (colors.Color(0xFF051A), colors.Color(0xFF051A)),
             (0xFF051A, colors.Color(0xFF051A)),
-            ((1, 0.5, 0), colors.Color(0xFF7F00)),
+            ((1.0, 0.5, 0.0), colors.Color(0xFF7F00)),
             ([0xFF, 0x5, 0x1A], colors.Color(0xFF051A)),
             ("#1a2b3c", colors.Color(0x1A2B3C)),
             ("#123", colors.Color(0x112233)),
@@ -305,9 +305,9 @@ def test_Color_of_happy_path(self, input, expected_result):
             (NotImplemented, r"Could not transform NotImplemented into a Color object"),
             ((1, 1, 1, 1), r"Color must be an RGB triplet if set to a tuple type"),
             ((1, "a", 1), r"Could not transform \(1, 'a', 1\) into a Color object"),
-            ((1.1, 1, 1), r"Expected red channel to be in the inclusive range of 0.0 and 1.0"),
-            ((1, 1.1, 1), r"Expected green channel to be in the inclusive range of 0.0 and 1.0"),
-            ((1, 1, 1.1), r"Expected blue channel to be in the inclusive range of 0.0 and 1.0"),
+            ((1.1, 1.0, 1.0), r"Expected red channel to be in the inclusive range of 0.0 and 1.0"),
+            ((1.0, 1.1, 1.0), r"Expected green channel to be in the inclusive range of 0.0 and 1.0"),
+            ((1.0, 1.0, 1.1), r"Expected blue channel to be in the inclusive range of 0.0 and 1.0"),
             ((), r"Color must be an RGB triplet if set to a tuple type"),
             ({}, r"Could not transform \{\} into a Color object"),
             ([], r"Color must be an RGB triplet if set to a list type"),
diff --git a/tests/hikari/test_errors.py b/tests/hikari/test_errors.py
index 4d6d9558..9b71a109 100644
--- a/tests/hikari/test_errors.py
+++ b/tests/hikari/test_errors.py
@@ -87,6 +87,10 @@ def error(self):
     def test_str(self, error):
         assert str(error) == "Bad Request 400: (12345) 'message' for https://some.url"
 
+    def test_str_when_int_status_code(self, error):
+        error.status = 699
+        assert str(error) == "Unknown Status 699: (12345) 'message' for https://some.url"
+
     def test_str_when_message_is_None(self, error):
         error.message = None
         assert str(error) == "Bad Request 400: (12345) 'raw body' for https://some.url"