Repository: hikari-py/hikari
Pull request #735 (2021-08-20 13:46:26)

Add codespell pipeline to CI
### Checklist
<!-- Make sure to tick all the following boxes by putting an `x` in between (like this `[x]`) -->
- [x] I have run `nox` and all the pipelines have passed.
- [ ] I have made unittests according to the code I have added/modified/deleted.


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1ef35240..2ccd9da6 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -107,7 +107,7 @@ jobs:
 
       - name: Linting
         run: |
-          nox -s safety mypy flake8
+          nox -s safety mypy flake8 codespell
 
   twemoji:
     runs-on: ubuntu-20.04
@@ -155,18 +155,28 @@ jobs:
           retention-days: 2
           if-no-files-found: error
 
-  # This job is only used to mark the CI as being done
-  #
   # Allows us to add this as a required check in Github branch rules, as all the
   # other jobs are subject to change
   ci-done:
     needs: [upload-coverage, linting, twemoji, pages]
+    if: always()
 
     runs-on: ubuntu-20.04
 
     steps:
-      - name: Completed
-        run: echo "CI Completed"
+      - name: Mark status based on past job status
+        env:
+          # All new need jobs need to be added here with the prefix "RESULT_"
+          RESULT_UPLOAD_COVERAGE: ${{ needs.upload-coverage.result }}
+          RESULT_LINTING: ${{ needs.linting.result }}
+          RESULT_TWEMOJI: ${{ needs.twemoji.result }}
+          RESULT_PAGES: ${{ needs.pages.result }}
+        run: |
+          if [ "$(env | grep 'RESULT_')" = "$(env | grep "RESULT_" | grep '=success')" ]; then
+            exit 0
+          else
+            exit 1
+          fi
 
   deploy:
     if: github.event_name == 'release'
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ba23d781..af2d75e3 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -50,7 +50,7 @@ This includes:
   - `test`
     - Run tests and installation of the package on different OS's and python versions.
   - `linting`
-    - Linting (`flake8`), type checking (`mypy`), and safety (`safety`).
+    - Linting (`flake8`), type checking (`mypy`), safety (`safety`) and spelling (`codespell`).
   - `twemoji`
     - Force test all discord emojis.
   - `pages`
diff --git a/README.md b/README.md
index 8a6daf3b..0f686ee5 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 [![Hikari PyPI version badge](https://img.shields.io/pypi/v/hikari)](https://pypi.org/project/hikari)
 [![Github Actions Status badge](https://github.com/hikari-py/hikari/actions/workflows/ci.yml/badge.svg?branch=master&event=push)](https://github.com/hikari-py/hikari/actions)
-[![Supported Python verisons badge](https://img.shields.io/pypi/pyversions/hikari)](https://pypi.org/project/hikari)
+[![Supported Python versions badge](https://img.shields.io/pypi/pyversions/hikari)](https://pypi.org/project/hikari)
 [![Maintainability badge](https://api.codeclimate.com/v1/badges/f95070b25136a69b0589/maintainability)](https://codeclimate.com/github/hikari-py/hikari/maintainability)
 [![Test Coverage badge](https://api.codeclimate.com/v1/badges/f95070b25136a69b0589/test_coverage)](https://codeclimate.com/github/hikari-py/hikari/test_coverage)
 [![Open Pull Requests badge](https://img.shields.io/github/issues-pr/hikari-py/hikari)](https://github.com/hikari-py/hikari/pulls)
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 34832733..2951f36c 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -37,8 +37,14 @@ mypy==0.910
 safety~=1.10.3
 
 #############
-# FORMATING #
+# FORMATTING #
 #############
 
 black==21.7b0
 isort==5.9.3
+
+##################
+# SPELL CHECKING #
+##################
+
+codespell==2.1.0
diff --git a/hikari/api/cache.py b/hikari/api/cache.py
index fc037641..19e6c333 100644
--- a/hikari/api/cache.py
+++ b/hikari/api/cache.py
@@ -97,7 +97,7 @@ def get_dm_channel_id(
         Parameters
         ----------
         user : hikari.snowflakes.SnowflakeishOr[hikari.users.PartialUser]
-            Objet or ID of the user to get the DM channel ID for.
+            Object or ID of the user to get the DM channel ID for.
 
         Returns
         -------
diff --git a/hikari/api/interaction_server.py b/hikari/api/interaction_server.py
index a0714145..bc04be1c 100644
--- a/hikari/api/interaction_server.py
+++ b/hikari/api/interaction_server.py
@@ -23,7 +23,7 @@
 """Provides an interface for Interaction REST server API implementations to follow."""
 from __future__ import annotations
 
-__all__: typing.Sequence[str] = ["ListenerT", "Response", "InteractionServer"]
+__all__: typing.List[str] = ["ListenerT", "Response", "InteractionServer"]
 
 import abc
 import typing
diff --git a/hikari/api/rest.py b/hikari/api/rest.py
index 7ff76160..368807b9 100644
--- a/hikari/api/rest.py
+++ b/hikari/api/rest.py
@@ -401,7 +401,7 @@ async def edit_my_voice_state(
         Other Parameters
         ----------------
         suppress : hikari.undefined.UndefinedOr[builtins.bool]
-            If specificed, whether the user should be allowed to become a speaker
+            If specified, whether the user should be allowed to become a speaker
             in the target stage channel with `builtin.True` suppressing them from
             becoming one.
         request_to_speak : typing.Union[hikari.undefined.UndefinedType, builtins.bool, datetime.datetime]
@@ -1197,7 +1197,7 @@ async def create_message(
             being empty with no attachments or embeds; messages with more than
             2000 characters in them, embeds that exceed one of the many embed
             limits; too many attachments; attachments that are too large;
-            invalid image URLs in embeds; if `reply` is ot found or not in the
+            invalid image URLs in embeds; if `reply` is not found or not in the
             same channel as `channel`.
         hikari.errors.UnauthorizedError
             If you are unauthorized to make the request (invalid/missing token).
@@ -1896,7 +1896,7 @@ async def create_webhook(
             The channel where the webhook will be created. This may be
             the object or the ID of an existing channel.
         name : str
-            The name for the webhook. This cannnot be `clyde`.
+            The name for the webhook. This cannot be `clyde`.
 
         Other Parameters
         ----------------
@@ -1914,7 +1914,7 @@ async def create_webhook(
         Raises
         ------
         hikari.errors.BadRequestError
-            If `name` doesnt follow the restrictions enforced by discord.
+            If `name` doesn't follow the restrictions enforced by discord.
         hikari.errors.ForbiddenError
             If you are missing the `MANAGE_WEBHOOKS` permission.
         hikari.errors.UnauthorizedError
@@ -3258,7 +3258,7 @@ async def add_user_to_guild(
             If you are not part of the guild you want to add the user to,
             if you are missing permissions to do one of the things you specified,
             if you are using an access token for another user, if the token is
-            bound to annother bot or if the access token doesnt have the
+            bound to another bot or if the access token doesn't have the
             `hikari.applications.OAuth2Scope.GUILDS_JOIN` scope enabled.
         hikari.errors.UnauthorizedError
             If you are unauthorized to make the request (invalid/missing token).
@@ -4010,7 +4010,7 @@ async def create_guild_text_channel(
         nsfw : hikari.undefined.UndefinedOr[builtins.bool]
             If provided, whether to mark the channel as NSFW.
         rate_limit_per_user : hikari.undefined.UndefinedOr[hikari.internal.time.Intervalish]
-            If provided, the ammount of seconds a user has to wait
+            If provided, the amount of seconds a user has to wait
             before being able to send another message in the channel.
             Maximum 21600 seconds.
         permission_overwrites : hikari.undefined.UndefinedOr[typing.Sequence[hikari.channels.PermissionOverwrite]]
@@ -4088,7 +4088,7 @@ async def create_guild_news_channel(
         nsfw : hikari.undefined.UndefinedOr[builtins.bool]
             If provided, whether to mark the channel as NSFW.
         rate_limit_per_user : hikari.undefined.UndefinedOr[hikari.internal.time.Intervalish]
-            If provided, the ammount of seconds a user has to wait
+            If provided, the amount of seconds a user has to wait
             before being able to send another message in the channel.
             Maximum 21600 seconds.
         permission_overwrites : hikari.undefined.UndefinedOr[typing.Sequence[hikari.channels.PermissionOverwrite]]
diff --git a/hikari/api/special_endpoints.py b/hikari/api/special_endpoints.py
index 3d91d192..493ddc23 100644
--- a/hikari/api/special_endpoints.py
+++ b/hikari/api/special_endpoints.py
@@ -402,7 +402,7 @@ def add_text_channel(
         nsfw : hikari.undefined.UndefinedOr[builtins.bool]
             If provided, whether to mark the channel as NSFW.
         rate_limit_per_user : hikari.undefined.UndefinedOr[builtins.int]
-            If provided, the ammount of seconds a user has to wait
+            If provided, the amount of seconds a user has to wait
             before being able to send another message in the channel.
             Maximum 21600 seconds.
         permission_overwrites : hikari.undefined.UndefinedOr[typing.Sequence[hikari.channels.PermissionOverwrite]]
@@ -649,7 +649,7 @@ def embeds(self) -> typing.Sequence[embeds_.Embed]:
         Returns
         -------
         typing.Sequence[hikari.embeds.Embed]
-            A sequence of up to 10 ot the embeds included in this response.
+            A sequence of up to 10 of the embeds included in this response.
         """
 
     # Settable fields
diff --git a/hikari/colors.py b/hikari/colors.py
index 42823018..cf3c1a71 100644
--- a/hikari/colors.py
+++ b/hikari/colors.py
@@ -233,7 +233,7 @@ def is_web_safe(self) -> bool:  # noqa: D401
     def from_rgb(cls, red: int, green: int, blue: int, /) -> Color:
         """Convert the given RGB to a `Color` object.
 
-        Each channel must be withing the range [0, 255] (0x0, 0xFF).
+        Each channel must be within the range [0, 255] (0x0, 0xFF).
 
         Parameters
         ----------
@@ -436,7 +436,7 @@ def from_bytes(
         bytes_ : typing.Iterable[builtins.int]
             A iterable of int byte values.
         byteorder : builtins.str
-            The endianess of the value represented by the bytes.
+            The endianness of the value represented by the bytes.
             Can be `"big"` endian or `"little"` endian.
         signed : builtins.bool
             Whether the value is signed or unsigned.
@@ -540,7 +540,7 @@ def to_bytes(self, length: int, byteorder: str, *, signed: bool = True) -> bytes
         length : builtins.int
             The number of bytes to produce. Should be around `3`, but not less.
         byteorder : builtins.str
-            The endianess of the value represented by the bytes.
+            The endianness of the value represented by the bytes.
             Can be `"big"` endian or `"little"` endian.
         signed : builtins.bool
             Whether the value is signed or unsigned.
diff --git a/hikari/errors.py b/hikari/errors.py
index feee3ddf..e5f68ae5 100644
--- a/hikari/errors.py
+++ b/hikari/errors.py
@@ -202,7 +202,7 @@ class GatewayServerClosedConnectionError(GatewayError):
 
     If `builtins.True`, it will try to reconnect after this is raised rather
     than it being propagated to the caller. If `builtins.False`, this will
-    be raised, thus stopping the applicaiton unless handled explicitly by the
+    be raised, thus stopping the application unless handled explicitly by the
     user.
 
     Returns
@@ -270,7 +270,25 @@ class RESTErrorCode(int, enums.Enum):
     """Unknown ban provided."""
 
     UNKNOWN_GUILD_TEMPLATE = 10_057
-    """Unknown guild template."""
+    """Unknown guild template provided."""
+
+    UNKNOWN_STICKER = 10_060
+    """Unknown sticker provided."""
+
+    UNKNOWN_INTERACTION = 10_062
+    """Unknown interaction provided."""
+
+    UNKNOWN_APPLICATION_COMMAND = 10_063
+    """Unknown application command provided."""
+
+    UNKNOWN_APPLICATION_COMMAND_PERMISSIONS = 10_066
+    """Unknown application command permissions provided."""
+
+    UNKNOWN_STAGE_INSTANCE = 10_067
+    """Unknown stage instance provided."""
+
+    UNKNOWN_GUILD_WELCOME_SCREEN = 10_069
+    """Unknown guild welcome screen provided."""
 
     EXPLICIT_CONTENT_BLOCKED = 20_009
     """Explicit content cannot be sent to the desired recipient(s)."""
@@ -281,8 +299,14 @@ class RESTErrorCode(int, enums.Enum):
     WRITE_LIMIT_HIT = 20_028
     """The global write limit on a channel has been hit."""
 
-    DISALLOWED_WORDS_IN_PUBLIC_STAGES = 20_031
-    """The stage channel contains disallowed words for public stages."""
+    DISALLOWED_WORDS_FOR_PUBLIC_STAGES = 20_031
+    """The guild contains disallowed words for public stages.
+
+    This may include guild name, guild description or channel names.
+    """
+
+    GUILD_PREMIUM_LEVEL_TOO_LOW = 20_035
+    """The guilds premium level is too low."""
 
     MAXIMUM_GUILDS = 30_001
     """Maximum number of guilds reached (100)."""
@@ -314,15 +338,24 @@ class RESTErrorCode(int, enums.Enum):
     MAXIMUM_ANIMATED_EMOJIS = 30_018
     """Maximum number of animated emojis reached."""
 
-    MAXIMUM_NUMBER_OF_SERVER_MEMBERS_REACHED = 30_019
-    """Maximum number of server members reached."""
+    MAXIMUM_NUMBER_OF_GUILD_MEMBERS_REACHED = 30_019
+    """Maximum number of guild members reached."""
 
     GUILD_ALREADY_HAS_TEMPLATE = 30_031
     """Guild already has a template."""
 
+    MAXIMUM_NUMBER_OF_THREAD_PARTICIPANTS_REACHED = 30_033
+    """Maximum number of thread participants reached."""
+
     MAXIMUM_BANS_FOR_NON_GUILD_MEMBERS = 30_035
     """Maximum number of bans for non-guild members reached."""
 
+    MAXIMUM_NUMBER_OF_STICKERS_REACHED = 30_037
+    """Maximum number of stickers reached."""
+
+    MAXIMUM_PRUNE_REQUESTS_REACHED = 30_040
+    """Maximum number of prune requests has been reached. Try again later."""
+
     REQUEST_TOO_LARGE = 40_005
     """Request too large. Try sending something smaller in size."""
 
@@ -401,6 +434,9 @@ class RESTErrorCode(int, enums.Enum):
     SYSTEM_OVERLOADED = 130_000
     """API resource is currently overloaded. Try again a little later."""
 
+    STAGE_ALREADY_OPEN = 150_006
+    """The stage channel is already open."""
+
 
 @attr.define(auto_exc=True, repr=False, weakref_slot=False)
 class HTTPResponseError(HTTPError):
@@ -409,7 +445,7 @@ class HTTPResponseError(HTTPError):
     url: str = attr.field()
     """The URL that produced this error message."""
 
-    status: typing.Union[int, http.HTTPStatus] = attr.field()
+    status: http.HTTPStatus = attr.field()
     """The HTTP status code for the response."""
 
     headers: data_binding.Headers = attr.field()
@@ -425,11 +461,13 @@ class HTTPResponseError(HTTPError):
     """The error code."""
 
     def __str__(self) -> str:
-        if isinstance(self.status, http.HTTPStatus):
-            name = self.status.name.replace("_", " ").title()
-            name_value = f"{name} {self.status.value}"
+        name = self.status.name.replace("_", " ").title()
+        name_value = f"{name} {self.status.value}"
+
+        if isinstance(self.code, RESTErrorCode) and self.code != RESTErrorCode.GENERAL_ERROR:
+            code_str = f" ({self.code.name})"
         else:
-            name_value = str(self.status).title()
+            code_str = ""
 
         if self.message:
             body = self.message
@@ -441,7 +479,7 @@ def __str__(self) -> str:
 
         chomped = len(body) > 200
 
-        return f"{name_value}: '{body[:200]}{'...' if chomped else ''}' for {self.url}"
+        return f"{name_value}:{code_str} '{body[:200]}{'...' if chomped else ''}' for {self.url}"
 
 
 @attr.define(auto_exc=True, repr=False, weakref_slot=False)
diff --git a/hikari/events/guild_events.py b/hikari/events/guild_events.py
index c97877eb..9d194395 100644
--- a/hikari/events/guild_events.py
+++ b/hikari/events/guild_events.py
@@ -218,7 +218,7 @@ class GuildAvailableEvent(GuildVisibilityEvent):
     This will be `builtins.None` if no chunks were requested.
 
     !!! note
-        This is a syntetic field.
+        This is a synthetic field.
 
     Returns
     -------
diff --git a/hikari/events/interaction_events.py b/hikari/events/interaction_events.py
index 2780c74e..11d169a3 100644
--- a/hikari/events/interaction_events.py
+++ b/hikari/events/interaction_events.py
@@ -23,7 +23,7 @@
 """Events fired for interaction related changes."""
 from __future__ import annotations
 
-__all__: typing.Sequence[str] = [
+__all__: typing.List[str] = [
     "CommandEvent",
     "CommandCreateEvent",
     "CommandUpdateEvent",
diff --git a/hikari/files.py b/hikari/files.py
index 6a065aba..81c1e04d 100644
--- a/hikari/files.py
+++ b/hikari/files.py
@@ -24,7 +24,7 @@
 
 from __future__ import annotations
 
-__all__ = [
+__all__: typing.List[str] = [
     "ensure_path",
     "ensure_resource",
     "unwrap_bytes",
diff --git a/hikari/guilds.py b/hikari/guilds.py
index 4e5ef439..fd76c184 100644
--- a/hikari/guilds.py
+++ b/hikari/guilds.py
@@ -1723,7 +1723,7 @@ async def create_text_channel(
         nsfw : hikari.undefined.UndefinedOr[builtins.bool]
             If provided, whether to mark the channel as NSFW.
         rate_limit_per_user : hikari.undefined.UndefinedOr[hikari.internal.time.Intervalish]
-            If provided, the ammount of seconds a user has to wait
+            If provided, the amount of seconds a user has to wait
             before being able to send another message in the channel.
             Maximum 21600 seconds.
         permission_overwrites : hikari.undefined.UndefinedOr[typing.Sequence[hikari.channels.PermissionOverwrite]]
@@ -1807,7 +1807,7 @@ async def create_news_channel(
         nsfw : hikari.undefined.UndefinedOr[builtins.bool]
             If provided, whether to mark the channel as NSFW.
         rate_limit_per_user : hikari.undefined.UndefinedOr[hikari.internal.time.Intervalish]
-            If provided, the ammount of seconds a user has to wait
+            If provided, the amount of seconds a user has to wait
             before being able to send another message in the channel.
             Maximum 21600 seconds.
         permission_overwrites : hikari.undefined.UndefinedOr[typing.Sequence[hikari.channels.PermissionOverwrite]]
@@ -2932,7 +2932,7 @@ async def fetch_public_updates_channel(self) -> typing.Optional[channels_.GuildT
         Returns
         -------
         typing.Optional[hikari.channels.GuildTextChannel]
-            The channel where discord sents relevent updates to moderators and admins.
+            The channel where discord sents relevant updates to moderators and admins.
 
         Raises
         ------
@@ -2991,8 +2991,11 @@ class RESTGuild(Guild):
     This will be `builtins.None` when creating a guild.
     """
 
-    max_presences: int = attr.field(eq=False, hash=False, repr=False)
-    """The maximum number of presences for the guild."""
+    max_presences: typing.Optional[int] = attr.field(eq=False, hash=False, repr=False)
+    """The maximum number of presences for the guild.
+
+    If `builtins.None`, then there is no limit.
+    """
 
     max_members: int = attr.field(eq=False, hash=False, repr=False)
     """The maximum number of members allowed in this guild."""
diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py
index 1487bb3c..80e6ae74 100644
--- a/hikari/impl/entity_factory.py
+++ b/hikari/impl/entity_factory.py
@@ -61,9 +61,8 @@
 from hikari.internal import data_binding
 from hikari.internal import time
 
-_DEFAULT_MAX_PRESENCES: typing.Final[int] = 25000
-_LOGGER: typing.Final[logging.Logger] = logging.getLogger("hikari.entity_factory")
 _ValueT = typing.TypeVar("_ValueT")
+_LOGGER: typing.Final[logging.Logger] = logging.getLogger("hikari.entity_factory")
 
 
 def _with_int_cast(cast: typing.Callable[[int], _ValueT]) -> typing.Callable[[typing.Any], _ValueT]:
@@ -1372,10 +1371,7 @@ def deserialize_rest_guild(self, payload: data_binding.JSONObject) -> guild_mode
         max_members = int(payload["max_members"])
 
         raw_max_presences = payload["max_presences"]
-        if raw_max_presences is None:
-            max_presences = _DEFAULT_MAX_PRESENCES
-        else:
-            max_presences = int(raw_max_presences)
+        max_presences = int(raw_max_presences) if raw_max_presences is not None else None
 
         roles = {
             snowflakes.Snowflake(role["id"]): self.deserialize_role(role, guild_id=guild_fields.id)
diff --git a/hikari/impl/interaction_server.py b/hikari/impl/interaction_server.py
index f9b16d02..5f2fcc4e 100644
--- a/hikari/impl/interaction_server.py
+++ b/hikari/impl/interaction_server.py
@@ -23,7 +23,7 @@
 """Standard implementation of a REST based interactions server."""
 from __future__ import annotations
 
-__all__: typing.Sequence[str] = ["InteractionServer"]
+__all__: typing.List[str] = ["InteractionServer"]
 
 import asyncio
 import logging
diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py
index 5fd9d6d9..8ddcc613 100644
--- a/hikari/impl/rest.py
+++ b/hikari/impl/rest.py
@@ -1197,7 +1197,7 @@ async def _create_message(
             )
 
         if undefined.all_undefined(embed, embeds) and isinstance(content, embeds_.Embed):
-            # Syntatic sugar, common mistake to accidentally send an embed
+            # Syntactic sugar, common mistake to accidentally send an embed
             # as the content, so lets detect this and fix it for the user.
             embed = content
             content = undefined.UNDEFINED
@@ -1205,7 +1205,7 @@ async def _create_message(
         elif undefined.all_undefined(attachment, attachments) and isinstance(
             content, (files.Resource, files.RAWISH_TYPES, os.PathLike)
         ):
-            # Syntatic sugar, common mistake to accidentally send an attachment
+            # Syntactic sugar, common mistake to accidentally send an attachment
             # as the content, so lets detect this and fix it for the user. This
             # will still then work with normal implicit embed attachments as
             # we work this out later.
@@ -1354,14 +1354,14 @@ async def _edit_message(  # noqa: C901 - Function too complex
             )
 
         if embed is undefined.UNDEFINED and isinstance(content, embeds_.Embed):
-            # Syntatic sugar, common mistake to accidentally send an embed
+            # Syntactic sugar, common mistake to accidentally send an embed
             # as the content, so lets detect this and fix it for the user.
             embed = content
             content = undefined.UNDEFINED
         elif undefined.all_undefined(attachment, attachments) and isinstance(
             content, (files.Resource, files.RAWISH_TYPES, os.PathLike)
         ):
-            # Syntatic sugar, common mistake to accidentally send an attachment
+            # Syntactic sugar, common mistake to accidentally send an attachment
             # as the content, so lets detect this and fix it for the user. This
             # will still then work with normal implicit embed attachments as
             # we work this out later.
@@ -3085,7 +3085,7 @@ async def create_interaction_response(
                 "use 'embed' (singular) instead?"
             )
         if undefined.all_undefined(embed, embeds) and isinstance(content, embeds_.Embed):
-            # Syntatic sugar, common mistake to accidentally send an embed
+            # Syntactic sugar, common mistake to accidentally send an embed
             # as the content, so lets detect this and fix it for the user.
             embed = content
             content = undefined.UNDEFINED
diff --git a/hikari/impl/rest_bot.py b/hikari/impl/rest_bot.py
index d3f5b53a..24d82c45 100644
--- a/hikari/impl/rest_bot.py
+++ b/hikari/impl/rest_bot.py
@@ -23,7 +23,7 @@
 """Standard implementations of a Interaction based REST-only bot."""
 from __future__ import annotations
 
-__all__: typing.Sequence[str] = ["RESTBot"]
+__all__: typing.List[str] = ["RESTBot"]
 
 import asyncio
 import logging
diff --git a/hikari/impl/shard.py b/hikari/impl/shard.py
index adabf2fb..3f6ff6ec 100644
--- a/hikari/impl/shard.py
+++ b/hikari/impl/shard.py
@@ -339,7 +339,7 @@ class GatewayShardImpl(shard.GatewayShard):
 
     Other Parameters
     ----------------
-    compression : typing.Optional[buitlins.str]
+    compression : typing.Optional[builtins.str]
         Compression format to use for the shard. Only supported values are
         `"transport_zlib_stream"` or `builtins.None` to disable it.
     initial_activity : typing.Optional[hikari.presences.Activity]
@@ -992,7 +992,7 @@ def _serialize_and_store_presence_payload(
         payload.put("since", idle_since, conversion=self._serialize_datetime)
         payload.put("afk", afk)
         payload.put("game", activity, conversion=self._serialize_activity)
-        # Sending "offline" to the gateway wont do anything, we will have to
+        # Sending "offline" to the gateway won't do anything, we will have to
         # send "invisible" instead for this to work.
         if status is presences.Status.OFFLINE:
             payload.put("status", "invisible")
diff --git a/hikari/internal/aio.py b/hikari/internal/aio.py
index 20bbdd63..b7731331 100644
--- a/hikari/internal/aio.py
+++ b/hikari/internal/aio.py
@@ -205,7 +205,7 @@ def get_or_make_loop() -> asyncio.AbstractEventLoop:
     # get_event_loop will error under oddly specific cases such as if set_event_loop has been called before even
     # if it was just called with None or if it's called on a thread which isn't the main Thread.
     try:
-        loop = asyncio.get_event_loop()
+        loop = asyncio.get_event_loop_policy().get_event_loop()
 
         # Closed loops cannot be re-used.
         if not loop.is_closed():
diff --git a/hikari/internal/cache.py b/hikari/internal/cache.py
index 04ed7c12..e0f7fbae 100644
--- a/hikari/internal/cache.py
+++ b/hikari/internal/cache.py
@@ -194,7 +194,7 @@ class GuildRecord:
     """
 
     is_available: typing.Optional[bool] = attr.field(default=None)
-    """Whether the chached guild is available or not.
+    """Whether the cached guild is available or not.
 
     This will be `builtins.None` when no `GuildRecord.guild` is also
     `builtins.None` else `builtins.bool`.
@@ -598,7 +598,7 @@ class MemberPresenceData(BaseData[presences.MemberPresence]):
 
     @classmethod
     def build_from_entity(cls, presence: presences.MemberPresence, /) -> MemberPresenceData:
-        # role_ids and activities are special cases as may be mutable sequences, therefor we ant to ensure they're
+        # role_ids and activities are special cases as may be mutable sequences, therefore we want to ensure they're
         # stored in immutable sequences (tuples). Plus activities need to be converted to Data objects.
         return cls(
             user_id=presence.user_id,
diff --git a/hikari/internal/ed25519.py b/hikari/internal/ed25519.py
index 0fc97f66..9119a09d 100644
--- a/hikari/internal/ed25519.py
+++ b/hikari/internal/ed25519.py
@@ -24,7 +24,7 @@
 
 from __future__ import annotations
 
-__all__: typing.Sequence[str] = ["VerifyBuilderT", "VerifierT", "build_ed25519_verifier"]
+__all__: typing.List[str] = ["VerifyBuilderT", "VerifierT", "build_ed25519_verifier"]
 
 import typing
 
diff --git a/hikari/internal/enums.py b/hikari/internal/enums.py
index 95971ff1..b9a07efd 100644
--- a/hikari/internal/enums.py
+++ b/hikari/internal/enums.py
@@ -104,7 +104,7 @@ def __call__(cls, value: typing.Any) -> typing.Any:
         try:
             return cls._value_to_member_map_[value]
         except KeyError:
-            # If we cant find the value, just return what got casted in
+            # If we can't find the value, just return what got casted in
             return value
 
     def __getitem__(cls, name: str) -> typing.Any:
@@ -342,8 +342,8 @@ def __call__(cls, value: int = 0) -> typing.Any:
                 # Try to get a cached value.
                 return temp_members[value]
             except KeyError:
-                # If we cant find the value, just return what got casted in by generating a pseudomember
-                # and caching it. We cant use weakref because int is not weak referenceable, annoyingly.
+                # If we can't find the value, just return what got casted in by generating a pseudomember
+                # and caching it. We can't use weakref because int is not weak referenceable, annoyingly.
                 pseudomember = cls.__new__(cls, value)
                 pseudomember._name_ = None
                 pseudomember._value_ = value
@@ -393,7 +393,7 @@ def __new__(
             "_name_to_member_map_": (name_to_member := {}),
             "_value_to_member_map_": (value_to_member := {}),
             "_powers_of_2_to_member_map_": (powers_of_2_map := {}),
-            # We cant weakref, as we inherit from int. Turns out that is significantly
+            # We can't weakref, as we inherit from int. Turns out that is significantly
             # slower anyway, so it isn't important for now. We just manually limit
             # the cache size.
             # This also randomly ends up with a 0 value in it at the start
diff --git a/hikari/internal/mentions.py b/hikari/internal/mentions.py
index 825cb803..10e3216f 100644
--- a/hikari/internal/mentions.py
+++ b/hikari/internal/mentions.py
@@ -23,7 +23,7 @@
 """Utility functions used for managing mentions on Discord."""
 from __future__ import annotations
 
-__all__: typing.Sequence[str] = ["generate_allowed_mentions"]
+__all__: typing.List[str] = ["generate_allowed_mentions"]
 
 import typing
 
diff --git a/hikari/internal/spel.py b/hikari/internal/spel.py
index def42929..32935938 100644
--- a/hikari/internal/spel.py
+++ b/hikari/internal/spel.py
@@ -65,7 +65,7 @@ class AttrGetter(typing.Generic[InputValueT, ReturnValueT]):
     """An attribute getter that can resolve nested attributes and methods.
 
     This follows the SPEL definition for how to define expressions. Expressions
-    may be preceeded with an optional `.` to aid in readability.
+    may be preceded with an optional `.` to aid in readability.
     """
 
     __slots__: typing.Sequence[str] = ("pipeline", "invert_all")
diff --git a/hikari/internal/time.py b/hikari/internal/time.py
index 20e85c60..d0513dd7 100644
--- a/hikari/internal/time.py
+++ b/hikari/internal/time.py
@@ -87,13 +87,12 @@ def slow_iso8601_datetime_string_to_datetime(datetime_str: str) -> datetime.date
     return datetime.datetime.fromisoformat(datetime_str)
 
 
+fast_iso8601_datetime_string_to_datetime: typing.Optional[typing.Callable[[str], datetime.datetime]]
 try:
     # CISO8601 is around 600x faster than modules like dateutil, which is
-    # going to be noticable on big bots where you are parsing hundreds of
+    # going to be noticeable on big bots where you are parsing hundreds of
     # thousands of "joined_at" fields on users on startup.
-    #
-    # ciso8601 doesn't have typing available, so ignore it.
-    import ciso8601  # type: ignore[import]
+    import ciso8601
 
     # Discord appears to actually use RFC-3339, which isn't a true ISO-8601 implementation,
     # but somewhat of a subset with some edge cases.
diff --git a/hikari/internal/ux.py b/hikari/internal/ux.py
index 1dc6c768..b33b05cb 100644
--- a/hikari/internal/ux.py
+++ b/hikari/internal/ux.py
@@ -36,7 +36,7 @@
 import typing
 import warnings
 
-import colorlog  # type: ignore[import]
+import colorlog
 
 from hikari import _about as about
 from hikari.internal import net
@@ -191,9 +191,9 @@ def print_banner(package: typing.Optional[str], allow_color: bool, force_color:
     }
 
     if supports_color(allow_color, force_color):
-        args.update(colorlog.escape_codes)
+        args.update(colorlog.escape_codes.escape_codes)
     else:
-        for code in colorlog.escape_codes:
+        for code in colorlog.escape_codes.escape_codes:
             args[code] = ""
 
     sys.stdout.write(string.Template(raw_banner).safe_substitute(args))
diff --git a/hikari/invites.py b/hikari/invites.py
index 4536fe98..cc5a9ebd 100644
--- a/hikari/invites.py
+++ b/hikari/invites.py
@@ -182,7 +182,7 @@ def make_splash_url(self, *, ext: str = "png", size: int = 4096) -> typing.Optio
         )
 
     @property
-    def banner(self) -> typing.Optional[files.URL]:
+    def banner_url(self) -> typing.Optional[files.URL]:
         """Banner URL for the guild, if set."""
         return self.make_banner_url()
 
diff --git a/hikari/iterators.py b/hikari/iterators.py
index d286fba2..5633ab40 100644
--- a/hikari/iterators.py
+++ b/hikari/iterators.py
@@ -273,7 +273,7 @@ def filter(
         that this iterator would return when it is requested.
 
         All conditions must evaluate to `builtins.True` for the item to be
-        returned. If this is not met, then the item is discared and ignored,
+        returned. If this is not met, then the item is discarded and ignored,
         the next matching item will be returned instead, if there is one.
 
         Parameters
diff --git a/hikari/webhooks.py b/hikari/webhooks.py
index b4987c55..c9e5ccc1 100644
--- a/hikari/webhooks.py
+++ b/hikari/webhooks.py
@@ -543,19 +543,20 @@ def mention(self) -> str:
         return f"<@{self.id}>"
 
     @property
-    def avatar(self) -> files_.URL:
-        """URL for this webhook's custom avatar or default avatar.
+    def avatar_url(self) -> typing.Optional[files_.URL]:
+        """URL for this webhook's avatar, if set.
 
-        If the webhook has a custom avatar, a URL to this is returned. Otherwise
-        a URL to the default avatar is provided instead.
+        May be `builtins.None` if no avatar is set. In this case, you should use
+        `default_avatar_url` instead.
         """
-        return self.make_avatar_url() or self.default_avatar
+        return self.make_avatar_url()
 
     @property
-    def default_avatar(self) -> files_.URL:
-        """URL for this webhook's default avatar.
+    def default_avatar_url(self) -> files_.URL:
+        """Avatar URL for the user, if they have one set.
 
-        This is used if no avatar is set.
+        May be `builtins.None` if no custom avatar is set. In this case, you
+        should use `default_avatar_url` instead.
         """
         return routes.CDN_DEFAULT_USER_AVATAR.compile_to_file(
             urls.CDN_URL,
diff --git a/pipelines/codespell.nox.py b/pipelines/codespell.nox.py
new file mode 100644
index 00000000..4eaaf8db
--- /dev/null
+++ b/pipelines/codespell.nox.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020 Nekokatt
+# Copyright (c) 2021 davfsa
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# 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.
+from pipelines import config
+from pipelines import nox
+
+
+@nox.session(reuse_venv=True)
+def codespell(session: nox.Session) -> None:
+    """Run codespell to check for spelling mistakes."""
+    session.install("-r", "dev-requirements.txt")
+    session.run("codespell", config.MAIN_PACKAGE, config.TEST_PACKAGE, config.EXAMPLE_SCRIPTS)
diff --git a/pipelines/format.nox.py b/pipelines/format.nox.py
index ec70992f..7db35b5f 100644
--- a/pipelines/format.nox.py
+++ b/pipelines/format.nox.py
@@ -96,6 +96,7 @@ def reformat_code(session: nox.Session) -> None:
 
     session.run("isort", *REFORMATING_PATHS)
     session.run("black", *REFORMATING_PATHS)
+    session.run("codespell", "-w", config.MAIN_PACKAGE, config.TEST_PACKAGE, config.EXAMPLE_SCRIPTS)
 
 
 def remove_trailing_whitespaces() -> None:
diff --git a/pipelines/mypy.nox.py b/pipelines/mypy.nox.py
index f2c1cedb..0502b3d8 100644
--- a/pipelines/mypy.nox.py
+++ b/pipelines/mypy.nox.py
@@ -35,7 +35,7 @@
 @nox.session(reuse_venv=True)
 def mypy(session: nox.Session) -> None:
     """Perform static type analysis on Python source code."""
-    session.install("-r", "requirements.txt", "-r", "dev-requirements.txt")
+    session.install("-r", "requirements.txt", "-r", "speedup-requirements.txt", "-r", "dev-requirements.txt")
 
     _generate_stubs(session)
 
@@ -46,7 +46,7 @@ def mypy(session: nox.Session) -> None:
 @nox.session(reuse_venv=True)
 def generate_stubs(session: nox.Session) -> None:
     """Generate the stubs for the package."""
-    session.install("-r", "requirements.txt", "-r", "dev-requirements.txt")
+    session.install("-r", "dev-requirements.txt")
     _generate_stubs(session)
 
 
diff --git a/requirements.txt b/requirements.txt
index f6f67465..ef3960f9 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,5 @@
 aiohttp==3.7.4.post0
 attrs==21.2.0
-colorlog==5.0.1
+colorlog==6.4.1
 multidict==5.1.0
 pure25519==0.0.1


Test patch:
diff --git a/tests/hikari/impl/test_entity_factory.py b/tests/hikari/impl/test_entity_factory.py
index 262e48b5..b209eb30 100644
--- a/tests/hikari/impl/test_entity_factory.py
+++ b/tests/hikari/impl/test_entity_factory.py
@@ -2411,7 +2411,7 @@ def test_deserialize_rest_guild_with_null_fields(self, entity_factory_impl):
         assert guild.widget_channel_id is None
         assert guild.system_channel_id is None
         assert guild.rules_channel_id is None
-        assert guild.max_presences is entity_factory._DEFAULT_MAX_PRESENCES
+        assert guild.max_presences is None
         assert guild.vanity_url_code is None
         assert guild.description is None
         assert guild.banner_hash is None
diff --git a/tests/hikari/impl/test_rest_bot.py b/tests/hikari/impl/test_rest_bot.py
index 91f67cdb..26078818 100644
--- a/tests/hikari/impl/test_rest_bot.py
+++ b/tests/hikari/impl/test_rest_bot.py
@@ -297,7 +297,7 @@ def test_run(self, mock_rest_bot):
             ssl_context=mock_context,
         )
         mock_rest_bot.join.assert_awaited_once()
-        assert asyncio.get_event_loop().is_closed() is False
+        assert asyncio.get_event_loop_policy().get_event_loop().is_closed() is False
 
     def test_run_when_asyncio_debug(self, mock_rest_bot):
         mock_rest_bot.start = mock.Mock()
diff --git a/tests/hikari/impl/test_shard.py b/tests/hikari/impl/test_shard.py
index 7329d5e6..164c12ea 100644
--- a/tests/hikari/impl/test_shard.py
+++ b/tests/hikari/impl/test_shard.py
@@ -1085,7 +1085,7 @@ def test__serialize_activity_when_activity_is_None(self, client):
 
     def test__serialize_activity_when_activity_is_not_None(self, client):
         activity = mock.Mock(type="0", url="https://some.url")
-        activity.name = "some name"  # This has to be set seperate because if not, its set as the mock's name
+        activity.name = "some name"  # This has to be set separate because if not, its set as the mock's name
         assert client._serialize_activity(activity) == {"name": "some name", "type": 0, "url": "https://some.url"}
 
     @pytest.mark.parametrize("idle_since", [datetime.datetime.now(), None])
diff --git a/tests/hikari/internal/test_aio.py b/tests/hikari/internal/test_aio.py
index aad61eb1..8cf1eca4 100644
--- a/tests/hikari/internal/test_aio.py
+++ b/tests/hikari/internal/test_aio.py
@@ -398,7 +398,7 @@ def test_get_or_make_loop_handles_runtime_error():
 
         new_event_loop.assert_called_once_with()
 
-    assert asyncio.get_event_loop() is mock_loop
+    assert asyncio.get_event_loop_policy().get_event_loop() is mock_loop
 
 
 def test_get_or_make_loop_handles_closed_loop():
@@ -410,4 +410,4 @@ def test_get_or_make_loop_handles_closed_loop():
 
         new_event_loop.assert_called_once_with()
 
-    assert asyncio.get_event_loop() is mock_loop
+    assert asyncio.get_event_loop_policy().get_event_loop() is mock_loop
diff --git a/tests/hikari/internal/test_ux.py b/tests/hikari/internal/test_ux.py
index 7c76df4b..f8e52ccb 100644
--- a/tests/hikari/internal/test_ux.py
+++ b/tests/hikari/internal/test_ux.py
@@ -153,7 +153,9 @@ def mock_args(self):
 
     def test_when_supports_color(self, mock_args):
         stack = contextlib.ExitStack()
-        stack.enter_context(mock.patch.object(colorlog, "escape_codes", new={"red": 0, "green": 1, "blue": 2}))
+        stack.enter_context(
+            mock.patch.object(colorlog.escape_codes, "escape_codes", new={"red": 0, "green": 1, "blue": 2})
+        )
         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"))
@@ -191,7 +193,9 @@ def test_when_supports_color(self, mock_args):
 
     def test_when_doesnt_supports_color(self, mock_args):
         stack = contextlib.ExitStack()
-        stack.enter_context(mock.patch.object(colorlog, "escape_codes", new={"red": 0, "green": 1, "blue": 2}))
+        stack.enter_context(
+            mock.patch.object(colorlog.escape_codes, "escape_codes", new={"red": 0, "green": 1, "blue": 2})
+        )
         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"))
diff --git a/tests/hikari/test_errors.py b/tests/hikari/test_errors.py
index 0c067efe..875dfcee 100644
--- a/tests/hikari/test_errors.py
+++ b/tests/hikari/test_errors.py
@@ -75,19 +75,29 @@ def test_str(self, error):
 class TestHTTPResponseError:
     @pytest.fixture()
     def error(self):
-        return errors.HTTPResponseError("https://some.url", http.HTTPStatus.BAD_REQUEST, {}, "raw body")
+        return errors.HTTPResponseError(
+            "https://some.url",
+            http.HTTPStatus.BAD_REQUEST,
+            {},
+            "raw body",
+            "message",
+            errors.RESTErrorCode.UNKNOWN_GUILD,
+        )
 
     def test_str(self, error):
-        assert str(error) == "Bad Request 400: 'raw body' for https://some.url"
+        assert str(error) == "Bad Request 400: (UNKNOWN_GUILD) 'message' for https://some.url"
+
+    def test_str_when_message_is_None(self, error):
+        error.message = None
+        assert str(error) == "Bad Request 400: (UNKNOWN_GUILD) 'raw body' for https://some.url"
 
-    def test_str_when_status_is_not_HTTPStatus(self, error):
-        error.status = "SOME STATUS"
-        assert str(error) == "Some Status: 'raw body' for https://some.url"
+    def test_str_when_code_is_GENERAL_ERROR(self, error):
+        error.code = errors.RESTErrorCode.GENERAL_ERROR
+        assert str(error) == "Bad Request 400: 'message' for https://some.url"
 
-    def test_str_when_message_is_not_None(self, error):
-        error.status = "SOME STATUS"
-        error.message = "Some message"
-        assert str(error) == "Some Status: 'Some message' for https://some.url"
+    def test_str_when_code_is_int(self, error):
+        error.code = 100
+        assert str(error) == "Bad Request 400: 'message' for https://some.url"
 
 
 class TestBadRequestError:
diff --git a/tests/hikari/test_undefined.py b/tests/hikari/test_undefined.py
index 6f3eb9e3..bf77c2dc 100644
--- a/tests/hikari/test_undefined.py
+++ b/tests/hikari/test_undefined.py
@@ -57,8 +57,8 @@ def test_deepcopy(self):
         assert copy.deepcopy(undefined.UNDEFINED) is undefined.UNDEFINED
 
     def test_can_pickle(self):
-        ser = pickle.dumps(undefined.UNDEFINED)
-        deser = pickle.loads(ser)  # noqa: S301 pickle loads is unsafe with untrusted data
+        data = pickle.dumps(undefined.UNDEFINED)
+        deser = pickle.loads(data)  # noqa: S301 pickle loads is unsafe with untrusted data
         assert deser is undefined.UNDEFINED
 
     def test__getstate__(self):
diff --git a/tests/hikari/test_webhooks.py b/tests/hikari/test_webhooks.py
index 3feeda0f..926a270c 100644
--- a/tests/hikari/test_webhooks.py
+++ b/tests/hikari/test_webhooks.py
@@ -180,7 +180,7 @@ def webhook(self):
             id=987654321,
             type=webhooks.WebhookType.CHANNEL_FOLLOWER,
             name="not a webhook",
-            avatar_hash="hoook",
+            avatar_hash="hook",
             application_id=None,
         )
 
@@ -194,21 +194,16 @@ def test_str_when_name_is_None(self, webhook):
     def test_mention_property(self, webhook):
         assert webhook.mention == "<@987654321>"
 
-    def test_avatar_property(self, webhook):
-        assert webhook.avatar == webhook.make_avatar_url()
+    def test_avatar_url_property(self, webhook):
+        assert webhook.avatar_url == webhook.make_avatar_url()
 
-    def test_avatar_property_when_default(self, webhook):
-        webhook.avatar_hash = None
-
-        assert webhook.avatar == webhook.default_avatar
-
-    def test_default_avatar(self, webhook):
-        assert webhook.default_avatar.url == "https://cdn.discordapp.com/embed/avatars/0.png"
+    def test_default_avatar_url(self, webhook):
+        assert webhook.default_avatar_url.url == "https://cdn.discordapp.com/embed/avatars/0.png"
 
     def test_make_avatar_url(self, webhook):
         result = webhook.make_avatar_url(ext="jpeg", size=2048)
 
-        assert result.url == "https://cdn.discordapp.com/avatars/987654321/hoook.jpeg?size=2048"
+        assert result.url == "https://cdn.discordapp.com/avatars/987654321/hook.jpeg?size=2048"
 
     def test_make_avatar_url_when_no_avatar(self, webhook):
         webhook.avatar_hash = None