Repository: hikari-py/hikari
Pull request #2163 (2025-02-21 13:30:15)

Bump ruff from 0.8.4 to 0.9.7
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.8.4 to 0.9.7.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/astral-sh/ruff/releases">ruff's releases</a>.</em></p>
<blockquote>
<h2>0.9.7</h2>
<h2>Release Notes</h2>
<h3>Preview features</h3>
<ul>
<li>Consider <code>__new__</code> methods as special function type for enforcing class method or static method rules (<a href="https://redirect.github.com/astral-sh/ruff/pull/13305">#13305</a>)</li>
<li>[<code>airflow</code>] Improve the internal logic to differentiate deprecated symbols (<code>AIR303</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16013">#16013</a>)</li>
<li>[<code>refurb</code>] Manual timezone monkeypatching (<code>FURB162</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16113">#16113</a>)</li>
<li>[<code>ruff</code>] Implicit class variable in dataclass (<code>RUF045</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/14349">#14349</a>)</li>
<li>[<code>ruff</code>] Skip singleton starred expressions for <code>incorrectly-parenthesized-tuple-in-subscript</code> (<code>RUF031</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16083">#16083</a>)</li>
<li>[<code>refurb</code>] Check for subclasses includes subscript expressions (<code>FURB189</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16155">#16155</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[<code>flake8-comprehensions</code>]: Handle trailing comma in <code>C403</code> fix (<a href="https://redirect.github.com/astral-sh/ruff/pull/16110">#16110</a>)</li>
<li>[<code>flake8-debugger</code>] Also flag <code>sys.breakpointhook</code> and <code>sys.__breakpointhook__</code> (<code>T100</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16191">#16191</a>)</li>
<li>[<code>pydocstyle</code>] Handle arguments with the same names as sections (<code>D417</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16011">#16011</a>)</li>
<li>[<code>pylint</code>] Correct ordering of arguments in fix for <code>if-stmt-min-max</code> (<code>PLR1730</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16080">#16080</a>)</li>
<li>[<code>pylint</code>] Do not offer fix for raw strings (<code>PLE251</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16132">#16132</a>)</li>
<li>[<code>pyupgrade</code>] Do not upgrade functional <code>TypedDicts</code> with private field names to the class-based syntax (<code>UP013</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16219">#16219</a>)</li>
<li>[<code>pyupgrade</code>] Handle micro version numbers correctly (<code>UP036</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16091">#16091</a>)</li>
<li>[<code>pyupgrade</code>] Unwrap unary expressions correctly (<code>UP018</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/15919">#15919</a>)</li>
<li>[<code>ruff</code>] Skip <code>RUF001</code> diagnostics when visiting string type definitions (<a href="https://redirect.github.com/astral-sh/ruff/pull/16122">#16122</a>)</li>
<li>[<code>flake8-pyi</code>] Avoid flagging <code>custom-typevar-for-self</code> on metaclass methods (<code>PYI019</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16141">#16141</a>)</li>
<li>[<code>pycodestyle</code>] Exempt <code>site.addsitedir(...)</code> calls (<code>E402</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16251">#16251</a>)</li>
</ul>
<h3>Formatter</h3>
<ul>
<li>Fix unstable formatting of trailing end-of-line comments of parenthesized attribute values (<a href="https://redirect.github.com/astral-sh/ruff/pull/16187">#16187</a>)</li>
</ul>
<h3>Server</h3>
<ul>
<li>Fix handling of requests received after shutdown message (<a href="https://redirect.github.com/astral-sh/ruff/pull/16262">#16262</a>)</li>
<li>Ignore <code>source.organizeImports.ruff</code> and <code>source.fixAll.ruff</code> code actions for a notebook cell (<a href="https://redirect.github.com/astral-sh/ruff/pull/16154">#16154</a>)</li>
<li>Include document specific debug info for <code>ruff.printDebugInformation</code> (<a href="https://redirect.github.com/astral-sh/ruff/pull/16215">#16215</a>)</li>
<li>Update server to return the debug info as string with <code>ruff.printDebugInformation</code> (<a href="https://redirect.github.com/astral-sh/ruff/pull/16214">#16214</a>)</li>
</ul>
<h3>CLI</h3>
<ul>
<li>Warn on invalid <code>noqa</code> even when there are no diagnostics (<a href="https://redirect.github.com/astral-sh/ruff/pull/16178">#16178</a>)</li>
<li>Better error messages while loading configuration <code>extend</code>s (<a href="https://redirect.github.com/astral-sh/ruff/pull/15658">#15658</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>[<code>refurb</code>] Correctly handle lengths of literal strings in <code>slice-to-remove-prefix-or-suffix</code> (<code>FURB188</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16237">#16237</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Add FAQ entry for <code>source.*</code> code actions in Notebook (<a href="https://redirect.github.com/astral-sh/ruff/pull/16212">#16212</a>)</li>
<li>Add <code>SECURITY.md</code> (<a href="https://redirect.github.com/astral-sh/ruff/pull/16224">#16224</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md">ruff's changelog</a>.</em></p>
<blockquote>
<h2>0.9.7</h2>
<h3>Preview features</h3>
<ul>
<li>Consider <code>__new__</code> methods as special function type for enforcing class method or static method rules (<a href="https://redirect.github.com/astral-sh/ruff/pull/13305">#13305</a>)</li>
<li>[<code>airflow</code>] Improve the internal logic to differentiate deprecated symbols (<code>AIR303</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16013">#16013</a>)</li>
<li>[<code>refurb</code>] Manual timezone monkeypatching (<code>FURB162</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16113">#16113</a>)</li>
<li>[<code>ruff</code>] Implicit class variable in dataclass (<code>RUF045</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/14349">#14349</a>)</li>
<li>[<code>ruff</code>] Skip singleton starred expressions for <code>incorrectly-parenthesized-tuple-in-subscript</code> (<code>RUF031</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16083">#16083</a>)</li>
<li>[<code>refurb</code>] Check for subclasses includes subscript expressions (<code>FURB189</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16155">#16155</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[<code>flake8-comprehensions</code>]: Handle trailing comma in <code>C403</code> fix (<a href="https://redirect.github.com/astral-sh/ruff/pull/16110">#16110</a>)</li>
<li>[<code>flake8-debugger</code>] Also flag <code>sys.breakpointhook</code> and <code>sys.__breakpointhook__</code> (<code>T100</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16191">#16191</a>)</li>
<li>[<code>pydocstyle</code>] Handle arguments with the same names as sections (<code>D417</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16011">#16011</a>)</li>
<li>[<code>pylint</code>] Correct ordering of arguments in fix for <code>if-stmt-min-max</code> (<code>PLR1730</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16080">#16080</a>)</li>
<li>[<code>pylint</code>] Do not offer fix for raw strings (<code>PLE251</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16132">#16132</a>)</li>
<li>[<code>pyupgrade</code>] Do not upgrade functional <code>TypedDicts</code> with private field names to the class-based syntax (<code>UP013</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16219">#16219</a>)</li>
<li>[<code>pyupgrade</code>] Handle micro version numbers correctly (<code>UP036</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16091">#16091</a>)</li>
<li>[<code>pyupgrade</code>] Unwrap unary expressions correctly (<code>UP018</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/15919">#15919</a>)</li>
<li>[<code>ruff</code>] Skip <code>RUF001</code> diagnostics when visiting string type definitions (<a href="https://redirect.github.com/astral-sh/ruff/pull/16122">#16122</a>)</li>
<li>[<code>flake8-pyi</code>] Avoid flagging <code>custom-typevar-for-self</code> on metaclass methods (<code>PYI019</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16141">#16141</a>)</li>
<li>[<code>pycodestyle</code>] Exempt <code>site.addsitedir(...)</code> calls (<code>E402</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16251">#16251</a>)</li>
</ul>
<h3>Formatter</h3>
<ul>
<li>Fix unstable formatting of trailing end-of-line comments of parenthesized attribute values (<a href="https://redirect.github.com/astral-sh/ruff/pull/16187">#16187</a>)</li>
</ul>
<h3>Server</h3>
<ul>
<li>Fix handling of requests received after shutdown message (<a href="https://redirect.github.com/astral-sh/ruff/pull/16262">#16262</a>)</li>
<li>Ignore <code>source.organizeImports.ruff</code> and <code>source.fixAll.ruff</code> code actions for a notebook cell (<a href="https://redirect.github.com/astral-sh/ruff/pull/16154">#16154</a>)</li>
<li>Include document specific debug info for <code>ruff.printDebugInformation</code> (<a href="https://redirect.github.com/astral-sh/ruff/pull/16215">#16215</a>)</li>
<li>Update server to return the debug info as string with <code>ruff.printDebugInformation</code> (<a href="https://redirect.github.com/astral-sh/ruff/pull/16214">#16214</a>)</li>
</ul>
<h3>CLI</h3>
<ul>
<li>Warn on invalid <code>noqa</code> even when there are no diagnostics (<a href="https://redirect.github.com/astral-sh/ruff/pull/16178">#16178</a>)</li>
<li>Better error messages while loading configuration <code>extend</code>s (<a href="https://redirect.github.com/astral-sh/ruff/pull/15658">#15658</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>[<code>refurb</code>] Correctly handle lengths of literal strings in <code>slice-to-remove-prefix-or-suffix</code> (<code>FURB188</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16237">#16237</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Add FAQ entry for <code>source.*</code> code actions in Notebook (<a href="https://redirect.github.com/astral-sh/ruff/pull/16212">#16212</a>)</li>
<li>Add <code>SECURITY.md</code> (<a href="https://redirect.github.com/astral-sh/ruff/pull/16224">#16224</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/astral-sh/ruff/commit/54fccb3ee23a2b20b287e631f32f18ee5b64b62b"><code>54fccb3</code></a> Bump version to 0.9.7 (<a href="https://redirect.github.com/astral-sh/ruff/issues/16271">#16271</a>)</li>
<li><a href="https://github.com/astral-sh/ruff/commit/8198668fc34283eeff9be6dae337a7b38e3d498b"><code>8198668</code></a> [red-knot] MDTest: Use custom class names instead of builtins (<a href="https://redirect.github.com/astral-sh/ruff/issues/16269">#16269</a>)</li>
<li><a href="https://github.com/astral-sh/ruff/commit/fc6b03c8daf203fd323a74ab9eb290a61013ae5b"><code>fc6b03c</code></a> Handle requests received after shutdown message (<a href="https://redirect.github.com/astral-sh/ruff/issues/16262">#16262</a>)</li>
<li><a href="https://github.com/astral-sh/ruff/commit/fb09d63e5507d73e764bdaadfacc8a34a139b78d"><code>fb09d63</code></a> [red-knot] Prefix <code>Type::call</code> and <code>dunder_call</code> with <code>try</code> (<a href="https://redirect.github.com/astral-sh/ruff/issues/16261">#16261</a>)</li>
<li><a href="https://github.com/astral-sh/ruff/commit/16d0625dfb7c62afdf773e38b62eb4ccf71e01bc"><code>16d0625</code></a> Improve internal docs for various string-node APIs (<a href="https://redirect.github.com/astral-sh/ruff/issues/16256">#16256</a>)</li>
<li><a href="https://github.com/astral-sh/ruff/commit/25920fe489346f8e0a117ab7781455aa30388c79"><code>25920fe</code></a> Rename <code>ExprStringLiteral::as_unconcatenated_string()</code> to `ExprStringLiteral:...</li>
<li><a href="https://github.com/astral-sh/ruff/commit/97d0659ce3e3977245ca202770078a9df60849dd"><code>97d0659</code></a> Pass <code>ParserOptions</code> to the parser (<a href="https://redirect.github.com/astral-sh/ruff/issues/16220">#16220</a>)</li>
<li><a href="https://github.com/astral-sh/ruff/commit/cfc6941d5c4b90f74329a5db6c590ce93683a6c0"><code>cfc6941</code></a> [red-knot] Resolve references in eager nested scopes eagerly (<a href="https://redirect.github.com/astral-sh/ruff/issues/16079">#16079</a>)</li>
<li><a href="https://github.com/astral-sh/ruff/commit/f50849aeef51a381af6c27df8595ac0e1ef5a891"><code>f50849a</code></a> Add <code>text_len()</code> methods to more <code>*Prefix</code> enums in <code>ruff_python_ast</code> (<a href="https://redirect.github.com/astral-sh/ruff/issues/16254">#16254</a>)</li>
<li><a href="https://github.com/astral-sh/ruff/commit/55ea09401a6045441ff2c55a769a53a20faaf3d5"><code>55ea094</code></a> [red-knot] Allow any <code>Ranged</code> argument for <code>report_lint</code> and `report_diagnost...</li>
<li>Additional commits viewable in <a href="https://github.com/astral-sh/ruff/compare/0.8.4...0.9.7">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=ruff&package-manager=pip&previous-version=0.8.4&new-version=0.9.7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/changes/2149.bugfix.md b/changes/2149.bugfix.md
new file mode 100644
index 00000000..987511b7
--- /dev/null
+++ b/changes/2149.bugfix.md
@@ -0,0 +1,1 @@
+Fixed Entitlement `starts_at` and `ends_at` erroring when set to `None`.
diff --git a/dev-requirements.txt b/dev-requirements.txt
index cbfbbfc0..4cb19302 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -1,1 +1,1 @@
-nox[uv]==2024.10.9
+nox[uv]==2025.2.9
diff --git a/dev-requirements/coverage.txt b/dev-requirements/coverage.txt
index e668f7d1..5f600886 100644
--- a/dev-requirements/coverage.txt
+++ b/dev-requirements/coverage.txt
@@ -1,1 +1,1 @@
-coverage[toml]==7.6.10
+coverage[toml]==7.6.12
diff --git a/dev-requirements/pyright.txt b/dev-requirements/pyright.txt
index 66f07fd2..7b1b0698 100644
--- a/dev-requirements/pyright.txt
+++ b/dev-requirements/pyright.txt
@@ -1,1 +1,1 @@
-pyright==1.1.392.post0
+pyright==1.1.394
diff --git a/dev-requirements/release.txt b/dev-requirements/release.txt
index 1d86da8e..b441f943 100644
--- a/dev-requirements/release.txt
+++ b/dev-requirements/release.txt
@@ -1,3 +1,3 @@
-twine==6.0.1
+twine==6.1.0
 
 -r build.txt
diff --git a/dev-requirements/ruff.txt b/dev-requirements/ruff.txt
index cd4e5668..b3f06b14 100644
--- a/dev-requirements/ruff.txt
+++ b/dev-requirements/ruff.txt
@@ -1,1 +1,1 @@
-ruff==0.8.4
+ruff==0.9.7
diff --git a/hikari/channels.py b/hikari/channels.py
index 10175f7b..72d5af2d 100644
--- a/hikari/channels.py
+++ b/hikari/channels.py
@@ -1193,9 +1193,9 @@ async def edit_overwrite(
             If an internal error occurs on Discord while handling the request.
         """
         if target_type is undefined.UNDEFINED:
-            assert not isinstance(
-                target, int
-            ), "Cannot determine the type of the target to update. Try specifying 'target_type' manually."
+            assert not isinstance(target, int), (
+                "Cannot determine the type of the target to update. Try specifying 'target_type' manually."
+            )
             return await self.app.rest.edit_permission_overwrite(self.id, target, allow=allow, deny=deny, reason=reason)
 
         return await self.app.rest.edit_permission_overwrite(
diff --git a/hikari/events/message_events.py b/hikari/events/message_events.py
index a44bb562..438f41ca 100644
--- a/hikari/events/message_events.py
+++ b/hikari/events/message_events.py
@@ -191,9 +191,9 @@ def get_channel(self) -> typing.Optional[channels.TextableGuildChannel]:
             return None
 
         channel = self.app.cache.get_guild_channel(self.channel_id)
-        assert channel is None or isinstance(
-            channel, channels.TextableGuildChannel
-        ), f"Cached channel ID is not a TextableGuildChannel, but a {type(channel).__name__}!"
+        assert channel is None or isinstance(channel, channels.TextableGuildChannel), (
+            f"Cached channel ID is not a TextableGuildChannel, but a {type(channel).__name__}!"
+        )
         return channel
 
     def get_guild(self) -> typing.Optional[guilds.GatewayGuild]:
@@ -429,9 +429,9 @@ def get_channel(self) -> typing.Optional[channels.TextableGuildChannel]:
             return None
 
         channel = self.app.cache.get_guild_channel(self.channel_id)
-        assert channel is None or isinstance(
-            channel, channels.TextableGuildChannel
-        ), f"Cached channel ID is not a TextableGuildChannel, but a {type(channel).__name__}!"
+        assert channel is None or isinstance(channel, channels.TextableGuildChannel), (
+            f"Cached channel ID is not a TextableGuildChannel, but a {type(channel).__name__}!"
+        )
         return channel
 
     def get_guild(self) -> typing.Optional[guilds.GatewayGuild]:
@@ -544,9 +544,9 @@ def get_channel(self) -> typing.Optional[channels.TextableGuildChannel]:
             return None
 
         channel = self.app.cache.get_guild_channel(self.channel_id)
-        assert channel is None or isinstance(
-            channel, channels.TextableGuildChannel
-        ), f"Cached channel ID is not a TextableGuildChannel, but a {type(channel).__name__}!"
+        assert channel is None or isinstance(channel, channels.TextableGuildChannel), (
+            f"Cached channel ID is not a TextableGuildChannel, but a {type(channel).__name__}!"
+        )
         return channel
 
     def get_guild(self) -> typing.Optional[guilds.GatewayGuild]:
@@ -640,9 +640,9 @@ def get_channel(self) -> typing.Optional[channels.TextableGuildChannel]:
             return None
 
         channel = self.app.cache.get_guild_channel(self.channel_id)
-        assert channel is None or isinstance(
-            channel, channels.TextableGuildChannel
-        ), f"Cached channel ID is not a TextableGuildChannel, but a {type(channel).__name__}!"
+        assert channel is None or isinstance(channel, channels.TextableGuildChannel), (
+            f"Cached channel ID is not a TextableGuildChannel, but a {type(channel).__name__}!"
+        )
         return channel
 
     def get_guild(self) -> typing.Optional[guilds.GatewayGuild]:
diff --git a/hikari/events/typing_events.py b/hikari/events/typing_events.py
index 09345e3a..76bdba07 100644
--- a/hikari/events/typing_events.py
+++ b/hikari/events/typing_events.py
@@ -167,9 +167,9 @@ async def fetch_channel(self) -> channels.TextableGuildChannel:
             The channel.
         """
         channel = await super().fetch_channel()
-        assert isinstance(
-            channel, channels.TextableGuildChannel
-        ), f"expected TextableGuildChannel from API, got {channel}"
+        assert isinstance(channel, channels.TextableGuildChannel), (
+            f"expected TextableGuildChannel from API, got {channel}"
+        )
         return channel
 
     async def fetch_guild(self) -> guilds.Guild:
@@ -214,9 +214,9 @@ def get_channel(self) -> typing.Optional[channels.TextableGuildChannel]:
             return None
 
         channel = self.app.cache.get_guild_channel(self.channel_id)
-        assert channel is None or isinstance(
-            channel, channels.TextableGuildChannel
-        ), f"expected TextableGuildChannel from cache, got {channel}"
+        assert channel is None or isinstance(channel, channels.TextableGuildChannel), (
+            f"expected TextableGuildChannel from cache, got {channel}"
+        )
         return channel
 
     def get_guild(self) -> typing.Optional[guilds.GatewayGuild]:
diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py
index bb364022..a81fe8d4 100644
--- a/hikari/impl/entity_factory.py
+++ b/hikari/impl/entity_factory.py
@@ -3755,7 +3755,7 @@ def deserialize_webhook(self, payload: data_binding.JSONObject) -> webhook_model
     ##################
 
     def deserialize_entitlement(self, payload: data_binding.JSONObject) -> monetization_models.Entitlement:
-        starts_at = time.iso8601_datetime_string_to_datetime(payload["starts_at"]) if "starts_at" in payload else None
+        starts_at = time.iso8601_datetime_string_to_datetime(payload["starts_at"]) if payload.get("starts_at") else None
 
         return monetization_models.Entitlement(
             id=snowflakes.Snowflake(payload["id"]),
@@ -3766,7 +3766,7 @@ def deserialize_entitlement(self, payload: data_binding.JSONObject) -> monetizat
             user_id=snowflakes.Snowflake(payload["user_id"]) if "user_id" in payload else None,
             is_deleted=payload["deleted"],
             starts_at=starts_at,
-            ends_at=time.iso8601_datetime_string_to_datetime(payload["ends_at"]) if "ends_at" in payload else None,
+            ends_at=time.iso8601_datetime_string_to_datetime(payload["ends_at"]) if payload.get("ends_at") else None,
             subscription_id=snowflakes.Snowflake(payload["subscription_id"]) if "subscription_id" in payload else None,
         )
 
diff --git a/hikari/impl/event_factory.py b/hikari/impl/event_factory.py
index 515015d0..6fc68f73 100644
--- a/hikari/impl/event_factory.py
+++ b/hikari/impl/event_factory.py
@@ -96,9 +96,9 @@ def deserialize_guild_channel_create_event(
         self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject
     ) -> channel_events.GuildChannelCreateEvent:
         channel = self._app.entity_factory.deserialize_channel(payload)
-        assert isinstance(
-            channel, channel_models.PermissibleGuildChannel
-        ), "CHANNEL_CREATE events for threads and DMS are undocumented behaviour"
+        assert isinstance(channel, channel_models.PermissibleGuildChannel), (
+            "CHANNEL_CREATE events for threads and DMS are undocumented behaviour"
+        )
         return channel_events.GuildChannelCreateEvent(shard=shard, channel=channel)
 
     def deserialize_guild_channel_update_event(
@@ -109,18 +109,18 @@ def deserialize_guild_channel_update_event(
         old_channel: typing.Optional[channel_models.PermissibleGuildChannel] = None,
     ) -> channel_events.GuildChannelUpdateEvent:
         channel = self._app.entity_factory.deserialize_channel(payload)
-        assert isinstance(
-            channel, channel_models.PermissibleGuildChannel
-        ), "CHANNEL_UPDATE events for threads and DMS are undocumented behaviour"
+        assert isinstance(channel, channel_models.PermissibleGuildChannel), (
+            "CHANNEL_UPDATE events for threads and DMS are undocumented behaviour"
+        )
         return channel_events.GuildChannelUpdateEvent(shard=shard, channel=channel, old_channel=old_channel)
 
     def deserialize_guild_channel_delete_event(
         self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject
     ) -> channel_events.GuildChannelDeleteEvent:
         channel = self._app.entity_factory.deserialize_channel(payload)
-        assert isinstance(
-            channel, channel_models.PermissibleGuildChannel
-        ), "CHANNEL_DELETE events for threads and DMS are undocumented behaviour"
+        assert isinstance(channel, channel_models.PermissibleGuildChannel), (
+            "CHANNEL_DELETE events for threads and DMS are undocumented behaviour"
+        )
         return channel_events.GuildChannelDeleteEvent(shard=shard, channel=channel)
 
     def deserialize_channel_pins_update_event(
diff --git a/pipelines/nox.py b/pipelines/nox.py
index 891d12af..445e99c0 100644
--- a/pipelines/nox.py
+++ b/pipelines/nox.py
@@ -32,19 +32,9 @@
 
 from pipelines import config as _pipelines_config
 
-try:
-    import uv
-
-    del uv
-
-    venv_backend = "uv"
-except ModuleNotFoundError:
-    venv_backend = "venv"
-
-
 # Default sessions should be defined here
 _options.sessions = ["reformat-code", "codespell", "pytest", "flake8", "slotscheck", "mypy", "verify-types"]
-_options.default_venv_backend = venv_backend
+_options.default_venv_backend = "uv|venv"
 
 _NoxCallbackSig = _typing.Callable[[Session], None]
 
diff --git a/requirements.txt b/requirements.txt
index 2bfa8bf4..7d436865 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
 aiohttp~=3.11
-attrs~=24.3
+attrs~=25.1
 colorlog~=6.9
 multidict~=6.1


Test patch:
diff --git a/tests/hikari/impl/test_entity_factory.py b/tests/hikari/impl/test_entity_factory.py
index d89ba045..67d116be 100644
--- a/tests/hikari/impl/test_entity_factory.py
+++ b/tests/hikari/impl/test_entity_factory.py
@@ -7169,6 +7169,21 @@ def entitlement_payload(self):
             "subscription_id": "1019653835926409216",
         }
 
+    @pytest.fixture
+    def entitlement_payload_starts_ends_null(self):
+        return {
+            "id": "696969696969696",
+            "sku_id": "420420420420420",
+            "application_id": "123123123123123",
+            "type": 8,
+            "deleted": False,
+            "starts_at": None,
+            "ends_at": None,
+            "guild_id": "1015034326372454400",
+            "user_id": "115590097100865541",
+            "subscription_id": "1019653835926409216",
+        }
+
     @pytest.fixture
     def sku_payload(self):
         return {
@@ -7195,6 +7210,21 @@ def test_deserialize_entitlement(self, entity_factory_impl, entitlement_payload)
         assert entitlement.subscription_id == 1019653835926409216
         assert isinstance(entitlement, monetization_models.Entitlement)
 
+    def test_deserialize_entitlement_starts_ends_null(self, entity_factory_impl, entitlement_payload_starts_ends_null):
+        entitlement = entity_factory_impl.deserialize_entitlement(entitlement_payload_starts_ends_null)
+
+        assert entitlement.id == 696969696969696
+        assert entitlement.sku_id == 420420420420420
+        assert entitlement.application_id == 123123123123123
+        assert entitlement.type is monetization_models.EntitlementType.APPLICATION_SUBSCRIPTION
+        assert entitlement.is_deleted is False
+        assert entitlement.starts_at is None
+        assert entitlement.ends_at is None
+        assert entitlement.guild_id == 1015034326372454400
+        assert entitlement.user_id == 115590097100865541
+        assert entitlement.subscription_id == 1019653835926409216
+        assert isinstance(entitlement, monetization_models.Entitlement)
+
     def test_deserialize_sku(self, entity_factory_impl, sku_payload):
         sku = entity_factory_impl.deserialize_sku(sku_payload)