Repository: hikari-py/hikari
Pull request #2427 (2025-06-19 19:13:01)

Switch ratelimiting logic to a sliding window
This started rollout on 2022-11-02 @ 14:30 PT, but was never announced anywhere. It allows us to greatly improve throughput over time


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index cca53833..4f91937d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -28,7 +28,7 @@ jobs:
         uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
 
       - name: Setup uv
-        uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
+        uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
         with:
           python-version: ${{ matrix.python-version }}
           activate-environment: true
@@ -72,7 +72,7 @@ jobs:
         uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
 
       - name: Setup uv
-        uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
+        uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
         with:
           python-version: "3.10"
           activate-environment: true
@@ -118,7 +118,7 @@ jobs:
         uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
 
       - name: Setup uv
-        uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
+        uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
         with:
           python-version: "3.10"
           activate-environment: true
@@ -179,7 +179,7 @@ jobs:
         uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
 
       - name: Setup uv
-        uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
+        uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
         with:
           python-version: 3.9
           activate-environment: true
@@ -198,7 +198,7 @@ jobs:
         uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
 
       - name: Setup uv
-        uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
+        uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
         with:
           # NOTE: This should be kept up to date with .readthedocs.yaml
           python-version: 3.11
diff --git a/.github/workflows/fragments-check.yml b/.github/workflows/fragments-check.yml
index 18e1ed3c..46fb5f34 100644
--- a/.github/workflows/fragments-check.yml
+++ b/.github/workflows/fragments-check.yml
@@ -20,7 +20,7 @@ jobs:
           fetch-depth: 0
 
       - name: Setup uv
-        uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
+        uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
         with:
           python-version: 3.9
           activate-environment: true
diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml
index 5a1d04d6..89dfeb08 100644
--- a/.github/workflows/prepare-release.yml
+++ b/.github/workflows/prepare-release.yml
@@ -33,7 +33,7 @@ jobs:
           git config --global user.email "90276125+hikari-bot[bot]@users.noreply.github.com"
 
       - name: Setup uv
-        uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
+        uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
         with:
           python-version: 3.9
           activate-environment: true
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 8e68cdbb..91bc437b 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -27,7 +27,7 @@ jobs:
           git config --global user.email "90276125+hikari-bot[bot]@users.noreply.github.com"
 
       - name: Setup uv
-        uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
+        uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
         with:
           python-version: 3.9
           activate-environment: true
diff --git a/changes/2427.optimization.md b/changes/2427.optimization.md
new file mode 100644
index 00000000..1810ac50
--- /dev/null
+++ b/changes/2427.optimization.md
@@ -0,0 +1,1 @@
+Switch ratelimiting logic to a sliding window implementation to better match Discord's side. This will allow maximum throughput that Discord would allow
diff --git a/hikari/impl/buckets.py b/hikari/impl/buckets.py
index 6280c0ad..52dd60d1 100644
--- a/hikari/impl/buckets.py
+++ b/hikari/impl/buckets.py
@@ -66,6 +66,18 @@
 major parameters. This is used for quick bucket indexing internally in this
 module.
 
+It is important to note that, as of 2022-11-02 @ 14:30 PT, all buckets use a
+sliding window/leaky bucket implementation, which means that we can perform
+requests quickly and no longer have stick to a strict window timing. Discord
+does not unfortunately expose the rate at which buckets refill, so we need
+to do some maths to accurately calculate it, which you can find in this
+implementation.
+
+This implementation is also built for concurrency, so extra care is taken to
+update only specific information about the buckets, and under specific
+circumstances, as to avoid Discord changing running buckets, which shouldn't
+happen, but better safe than sorry!
+
 One issue that occurs from this is that we cannot effectively hash a
 `CompiledRoute` that has not yet been hit, meaning that
 until we receive a response from this endpoint, we have no idea what our rate
@@ -128,9 +140,12 @@
     limiting occurs in the current window.
 * `X-RateLimit-Bucket`:
     a [`str`][] containing the initial bucket hash.
+* `X-RateLimit-Reset`:
+    a [`float`][] containing the epoch in seconds (with decimal millisecond
+    precision) at which the current rate limit bucket will fully reset.
 * `X-RateLimit-Reset-After`:
-    a [`float`][] containing the number of seconds when the current rate
-    limit bucket will reset with decimal millisecond precision.
+    a [`float`][] containing the number of seconds (with decimal millisecond
+    precision) after which the current rate limit bucket will fully reset.
 
 Each of the above values should be passed to the
 [`hikari.impl.buckets.RESTBucketManager.update_rate_limits`][] method to
@@ -195,6 +210,7 @@
 
 import asyncio
 import logging
+import math
 import typing
 
 from hikari import errors
@@ -219,8 +235,10 @@ class RESTBucket(rate_limits.WindowedBurstRateLimiter):
     Component to represent an active rate limit bucket on a specific HTTP route
     with a specific major parameter combo.
 
-    This is somewhat similar to the [`hikari.impl.rate_limits.WindowedBurstRateLimiter`][] in how it
-    works.
+    This is somewhat similar to [`hikari.impl.rate_limits.WindowedBurstRateLimiter`][] in how it
+    works, except it uses a sliding window instead, which means that we will gain back the
+    ability to make a request based on a recovery rate, rather than being constraint to a full
+    window.
 
     This algorithm will use fixed-period time windows that have a given limit
     (capacity). Each time a task requests processing time, it will drip another
@@ -235,7 +253,28 @@ class RESTBucket(rate_limits.WindowedBurstRateLimiter):
     which allows dynamically changing the enforced rate limits at any time.
     """
 
-    __slots__: typing.Sequence[str] = ("_compiled_route", "_global_ratelimit", "_lock", "_max_rate_limit")
+    __slots__: typing.Sequence[str] = (
+        "_compiled_route",
+        "_global_ratelimit",
+        "_lock",
+        "_max_rate_limit",
+        "_out_of_sync",
+    )
+
+    name: str
+    # <<inherited docstring from WindowedBurstRateLimiter>>.
+
+    increase_at: float
+    # <<inherited docstring from WindowedBurstRateLimiter>>.
+
+    remaining: int
+    # <<inherited docstring from WindowedBurstRateLimiter>>.
+
+    period: float
+    # <<inherited docstring from WindowedBurstRateLimiter>>.
+
+    limit: int
+    # <<inherited docstring from WindowedBurstRateLimiter>>.
 
     def __init__(
         self,
@@ -245,10 +284,12 @@ def __init__(
         max_rate_limit: float,
     ) -> None:
         super().__init__(name, 1, 1)
+        self.increase_at = time.time() + self.period
         self._compiled_route = compiled_route
         self._max_rate_limit = max_rate_limit
         self._global_ratelimit = global_ratelimit
         self._lock = asyncio.Lock()
+        self._out_of_sync = False
 
     async def __aenter__(self) -> None:
         await self.acquire()
@@ -265,7 +306,8 @@ def is_unknown(self) -> bool:
 
     def release(self) -> None:
         """Release the lock on the bucket."""
-        self._lock.release()
+        if self._lock.locked():
+            self._lock.release()
 
     @typing_extensions.override
     async def acquire(self) -> None:
@@ -281,23 +323,21 @@ async def acquire(self) -> None:
         hikari.errors.RateLimitTooLongError
             If the rate limit is longer than `max_rate_limit`.
         """
-        await self._lock.acquire()
-
         if self.is_unknown:
-            return
+            await self._lock.acquire()
 
-        now = time.monotonic()
-        retry_after = self.reset_at - now
+            if self.is_unknown:
+                return
 
-        if self.is_rate_limited(now) and retry_after > self._max_rate_limit:
-            # Release lock before we error
-            self._lock.release()
+        now = time.time()
+
+        if self.remaining == 0 and self.increase_at - now > self._max_rate_limit:
             raise errors.RateLimitTooLongError(
                 route=self._compiled_route,
                 is_global=False,
-                retry_after=retry_after,
+                retry_after=self.get_time_until_increase(time.time()),
                 max_retry_after=self._max_rate_limit,
-                reset_at=self.reset_at,
+                reset_at=self.increase_at,
                 limit=self.limit,
                 period=self.period,
             )
@@ -307,7 +347,6 @@ async def acquire(self) -> None:
         global_ratelimit = self._global_ratelimit
         if global_ratelimit.reset_at and (global_ratelimit.reset_at - now) > self._max_rate_limit:
             # Release lock before we error
-            self._lock.release()
             raise errors.RateLimitTooLongError(
                 route=self._compiled_route,
                 is_global=True,
@@ -320,34 +359,94 @@ async def acquire(self) -> None:
 
         await global_ratelimit.acquire()
 
-    def update_rate_limit(self, remaining: int, limit: int, reset_at: float) -> None:
-        """Update the rate limit information.
+    @typing_extensions.override
+    def move_window(self, now: float) -> None:
+        # If we are out of sync, we shouldn't slide the window along, as we will be off due to
+        # network latency.
+        # The second part of this 'if' is to account for some cases where there can be a race
+        # condition and we receive rate limit updates out of order, and we cannot update `_out_of_sync`
+        if not self._out_of_sync or (now - self.increase_at > self.period):
+            gain = math.floor((now - self.increase_at) / self.period) + 1
+            now_remaining = self.remaining + gain
+
+            self.remaining = min(self.limit, now_remaining)
+
+            if self.remaining == self.limit:
+                self.increase_at = now + self.period
+                self._out_of_sync = True
+            else:
+                self.increase_at = self.increase_at + gain * self.period
 
-        !!! note
-            The `reset_at` epoch is expected to be a [`time.monotonic`][]
-            monotonic epoch, rather than a [`time.time`][] date-based epoch.
+    def update_rate_limit(self, remaining: int, limit: int, reset_at: float, reset_after: float) -> None:
+        """Update the rate limit information.
 
         Parameters
         ----------
         remaining
-            The calls remaining in this time window.
+            The `X-RateLimit-Remaining` header cast to an [`int`][].
         limit
-            The total calls allowed in this time window.
+            The `X-RateLimit-Limit` header cast to an [`int`][].
         reset_at
-            The epoch at which to reset the limit.
+            The `X-RateLimit-Reset` header cast to a [`float`][].
+        reset_after
+            The `X-RateLimit-Reset-After` header cast to a [`float`][].
         """
-        self.remaining: int = remaining
-        self.limit: int = limit
-        self.reset_at: float = reset_at
-        self.period: float = max(0.0, self.reset_at - time.monotonic())
+        if remaining == limit:
+            # This should never happen, but just in case
+            return
+
+        slide_period = reset_after / (limit - remaining)
+        next_slide_at = (reset_at - reset_after) + slide_period
+        if next_slide_at < self.increase_at:
+            # Old ratelimit information (from last window), ignore
+            return
+
+        if self.limit != limit:
+            if self.limit > limit:
+                _LOGGER.warning(
+                    "bucket '%s' decreased its limit (%d -> %d). "
+                    "It is possible that you will see a small increase in 429s",
+                    self.name,
+                    self.limit,
+                    limit,
+                )
+            self.limit = limit
+
+        # We want to update the slide period only, and only if:
+        #   1. The bucket is out of sync (ie, we reset the full window)
+        #   2. We receive the first usage of the bucket, which will always have the most accurate slide period
+        #   3. The slide periods differ too much. This is helpful if we diverged too much from the real one
+        #      due to network latency, of if the bucket randomly changed
+        #      Note: 0.5 and 0.7 are chosen arbitrarily after some testing
+        if self._out_of_sync or remaining == limit - 1 or not math.isclose(self.period, slide_period, abs_tol=0.5):
+            if not math.isclose(self.period, slide_period, abs_tol=0.7):
+                _LOGGER.warning(
+                    "bucket '%s' greatly increased its slide period (%s -> %s). "
+                    "It is possible that you will see a small increase in 429s",
+                    self.name,
+                    self.period,
+                    slide_period,
+                )
+
+            self._out_of_sync = False
+            self.period = slide_period
+            self.increase_at = next_slide_at
 
-    def resolve(self, real_bucket_hash: str) -> None:
-        """Resolve an unknown bucket.
+    def resolve(self, real_bucket_hash: str, remaining: int, limit: int, reset_at: float, reset_after: float) -> None:
+        """Set the ratelimit information for this bucket.
 
         Parameters
         ----------
         real_bucket_hash
             The real bucket hash for this bucket.
+        remaining
+            The `X-RateLimit-Remaining` header cast to an [`int`][].
+        limit
+            The `X-RateLimit-Limit` header cast to an [`int`][].
+        reset_at
+            The `X-RateLimit-Reset` header cast to a [`float`][].
+        reset_after
+            The `X-RateLimit-Reset-After` header cast to a [`float`][].
 
         Raises
         ------
@@ -358,7 +457,17 @@ def resolve(self, real_bucket_hash: str) -> None:
             msg = "Cannot resolve known bucket"
             raise RuntimeError(msg)
 
-        self.name: str = real_bucket_hash
+        if remaining == limit:
+            # This should never happen, but just in case
+            return
+
+        slide_period = reset_after / (limit - remaining)
+        self.name = real_bucket_hash
+        self.remaining = remaining
+        self.limit = limit
+        self.period = slide_period
+        self.increase_at = (reset_at - reset_after) + self.period
+        self._out_of_sync = False
 
 
 def _create_authentication_hash(authentication: str | None) -> str:
@@ -366,7 +475,7 @@ def _create_authentication_hash(authentication: str | None) -> str:
 
 
 def _create_unknown_hash(route: routes.CompiledRoute, authentication_hash: str) -> str:
-    return f"{UNKNOWN_HASH}{routes.HASH_SEPARATOR}{authentication_hash}{routes.HASH_SEPARATOR}{hash(route)!s}"
+    return f"{UNKNOWN_HASH}{routes.HASH_SEPARATOR}{authentication_hash}{routes.HASH_SEPARATOR}{hash(route.route)!s}"
 
 
 class RESTBucketManager:
@@ -388,11 +497,11 @@ class RESTBucketManager:
         "_global_ratelimit",
         "_max_rate_limit",
         "_real_hashes_to_buckets",
-        "_routes_to_hashes",
+        "_route_hash_to_bucket_hash",
     )
 
     def __init__(self, max_rate_limit: float) -> None:
-        self._routes_to_hashes: dict[routes.Route, str] = {}
+        self._route_hash_to_bucket_hash: dict[int, str] = {}
         self._real_hashes_to_buckets: dict[str, RESTBucket] = {}
         self._gc_task: asyncio.Task[None] | None = None
         self._max_rate_limit = max_rate_limit
@@ -445,7 +554,7 @@ async def close(self) -> None:
 
         self._global_ratelimit.close()
         self._real_hashes_to_buckets.clear()
-        self._routes_to_hashes.clear()
+        self._route_hash_to_bucket_hash.clear()
 
         self._gc_task.cancel()
 
@@ -469,7 +578,7 @@ async def _gc(self, poll_period: float, expire_after: float) -> None:
     def _purge_stale_buckets(self, expire_after: float) -> None:
         buckets_to_purge: list[str] = []
 
-        now = time.monotonic()
+        now = time.time()
 
         # We have three main states that a bucket can be in:
         # 1. active - the bucket is active and is not at risk of deallocation
@@ -481,12 +590,12 @@ def _purge_stale_buckets(self, expire_after: float) -> None:
         bucket_pairs = self._real_hashes_to_buckets.items()
 
         for full_hash, bucket in bucket_pairs:
-            if bucket.is_empty and bucket.reset_at + expire_after < now:
+            if bucket.is_empty and bucket.increase_at + expire_after < now:
                 # If it is still running a throttle and is in memory, it will remain in memory
                 # but we will not know about it.
                 buckets_to_purge.append(full_hash)
 
-            if bucket.reset_at >= now:
+            if now > bucket.increase_at:
                 active += 1
 
         dead = len(buckets_to_purge)
@@ -530,7 +639,7 @@ def acquire_bucket(
 
         authentication_hash = _create_authentication_hash(authentication)
 
-        if bucket_hash := self._routes_to_hashes.get(compiled_route.route):
+        if bucket_hash := self._route_hash_to_bucket_hash.get(hash(compiled_route.route)):
             real_bucket_hash = compiled_route.create_real_bucket_hash(bucket_hash, authentication_hash)
         else:
             real_bucket_hash = _create_unknown_hash(compiled_route, authentication_hash)
@@ -551,6 +660,7 @@ def update_rate_limits(
         bucket_header: str,
         remaining_header: int,
         limit_header: int,
+        reset_at: float,
         reset_after: float,
     ) -> None:
         """Update the rate limits for a bucket using info from a response.
@@ -567,6 +677,8 @@ def update_rate_limits(
             The `X-RateLimit-Remaining` header cast to an [`int`][].
         limit_header
             The `X-RateLimit-Limit` header cast to an [`int`][].
+        reset_at
+            The `X-RateLimit-Reset` header cast to a [`float`][].
         reset_after
             The `X-RateLimit-Reset-After` header cast to a [`float`][].
         """
@@ -574,7 +686,7 @@ def update_rate_limits(
             msg = "Cannot interact with an inactive bucket manager"
             raise errors.ComponentStateConflictError(msg)
 
-        self._routes_to_hashes[compiled_route.route] = bucket_header
+        self._route_hash_to_bucket_hash[hash(compiled_route.route)] = bucket_header
         authentication_hash = _create_authentication_hash(authentication)
         real_bucket_hash = compiled_route.create_real_bucket_hash(bucket_header, authentication_hash)
 
@@ -591,7 +703,6 @@ def update_rate_limits(
             unknown_bucket_hash = _create_unknown_hash(compiled_route, authentication_hash)
 
             if bucket := self._real_hashes_to_buckets.pop(unknown_bucket_hash, None):
-                bucket.resolve(real_bucket_hash)
                 _LOGGER.debug(
                     "remapping %s with existing bucket %s [reset-after:%ss, limit:%s, remaining:%s]",
                     compiled_route,
@@ -600,6 +711,7 @@ def update_rate_limits(
                     limit_header,
                     remaining_header,
                 )
+
             else:
                 _LOGGER.debug(
                     "remapping %s with new bucket %s [reset-after:%ss, limit:%s, remaining:%s]",
@@ -609,13 +721,13 @@ def update_rate_limits(
                     limit_header,
                     remaining_header,
                 )
+                bucket = RESTBucket(UNKNOWN_HASH, compiled_route, self._global_ratelimit, self._max_rate_limit)
 
-                bucket = RESTBucket(real_bucket_hash, compiled_route, self._global_ratelimit, self._max_rate_limit)
+            bucket.resolve(real_bucket_hash, remaining_header, limit_header, reset_at, reset_after)
 
             self._real_hashes_to_buckets[real_bucket_hash] = bucket
 
-        reset_at_monotonic = time.monotonic() + reset_after
-        bucket.update_rate_limit(remaining_header, limit_header, reset_at_monotonic)
+        bucket.update_rate_limit(remaining_header, limit_header, reset_at, reset_after)
 
     def throttle(self, retry_after: float) -> None:
         """Throttle the global ratelimit for the buckets.
diff --git a/hikari/impl/event_manager.py b/hikari/impl/event_manager.py
index 2e3d64df..0fe6e42b 100644
--- a/hikari/impl/event_manager.py
+++ b/hikari/impl/event_manager.py
@@ -72,7 +72,7 @@
 
 def _fixed_size_nonce() -> str:
     # This generates nonces of length 28 for use in member chunking.
-    head = time.monotonic_ns().to_bytes(8, "big")
+    head = time.time_ns().to_bytes(8, "big")
     tail = random.getrandbits(92).to_bytes(12, "big")
     return base64.b64encode(head + tail).decode("ascii")
 
diff --git a/hikari/impl/gateway_bot.py b/hikari/impl/gateway_bot.py
index 1419d85e..25e14399 100644
--- a/hikari/impl/gateway_bot.py
+++ b/hikari/impl/gateway_bot.py
@@ -973,7 +973,7 @@ async def start(
 
         _validate_activity(activity)
 
-        start_time = time.monotonic()
+        start_time = time.time()
         self._closed_event = asyncio.Event()
         self._closing_event = asyncio.Event()
 
@@ -1060,7 +1060,7 @@ async def start(
 
         await self._event_manager.dispatch(self._event_factory.deserialize_started_event(), return_tasks=True)
 
-        _LOGGER.info("started successfully in approx %.2f seconds", time.monotonic() - start_time)
+        _LOGGER.info("started successfully in approx %.2f seconds", time.time() - start_time)
 
     def stream(
         self, event_type: type[base_events.EventT], /, timeout: float | None, limit: int | None = None
@@ -1350,9 +1350,9 @@ async def _start_one_shard(
             url=url,
         )
         try:
-            start = time.monotonic()
+            start = time.time()
             await new_shard.start()
-            end = time.monotonic()
+            end = time.time()
 
         except Exception:
             if new_shard.is_alive:
diff --git a/hikari/impl/rate_limits.py b/hikari/impl/rate_limits.py
index fec71026..6b678384 100644
--- a/hikari/impl/rate_limits.py
+++ b/hikari/impl/rate_limits.py
@@ -161,7 +161,7 @@ class ManualRateLimiter(BurstRateLimiter):
     # <<inherited docstring from BurstRateLimiter>>.
 
     reset_at: float | None
-    """The monotonic [`time.monotonic`][] timestamp at which the ratelimit gets lifted."""
+    """The monotonic [`time.time`][] timestamp at which the ratelimit gets lifted."""
 
     def __init__(self) -> None:
         super().__init__("global")
@@ -235,7 +235,7 @@ async def unlock_later(self, retry_after: float) -> None:
         """
         _LOGGER.warning("you are being globally rate limited for %ss", retry_after)
 
-        self.reset_at = time.monotonic() + retry_after
+        self.reset_at = time.time() + retry_after
         await asyncio.sleep(retry_after)
         self.reset_at = None
 
@@ -250,7 +250,7 @@ def get_time_until_reset(self, now: float) -> float:
         Parameters
         ----------
         now
-            The monotonic [`time.monotonic`][] timestamp.
+            The [`time.time`][] timestamp.
 
         Returns
         -------
@@ -293,12 +293,12 @@ class WindowedBurstRateLimiter(BurstRateLimiter):
     that a unit has been placed into the bucket.
     """
 
-    __slots__: typing.Sequence[str] = ("limit", "period", "remaining", "reset_at")
+    __slots__: typing.Sequence[str] = ("increase_at", "limit", "period", "remaining")
 
     throttle_task: asyncio.Task[typing.Any] | None
     # <<inherited docstring from BurstRateLimiter>>.
 
-    reset_at: float
+    increase_at: float
     """The [`time.monotonic`][] that the limit window ends at."""
 
     remaining: int
@@ -314,7 +314,7 @@ class WindowedBurstRateLimiter(BurstRateLimiter):
 
     def __init__(self, name: str, period: float, limit: int) -> None:
         super().__init__(name)
-        self.reset_at = 0.0
+        self.increase_at = 0.0
         self.remaining = 0
         self.limit = limit
         self.period = period
@@ -330,7 +330,7 @@ async def acquire(self) -> None:
         # if it hasn't started. Likewise, if the throttle task is still running, we should
         # delegate releasing the future to the throttler task so that we still process
         # first-come-first-serve
-        if self.throttle_task is not None or self.is_rate_limited(time.monotonic()):
+        if self.throttle_task is not None or self.is_rate_limited(time.time()):
             loop = asyncio.get_running_loop()
             future = loop.create_future()
 
@@ -340,9 +340,9 @@ async def acquire(self) -> None:
 
             await future
         else:
-            self.drip()
+            self.remaining -= 1
 
-    def get_time_until_reset(self, now: float) -> float:
+    def get_time_until_increase(self, now: float) -> float:
         """Determine how long until the current rate limit is reset.
 
         !!! warning
@@ -365,7 +365,7 @@ def get_time_until_reset(self, now: float) -> float:
         """
         if not self.is_rate_limited(now):
             return 0.0
-        return self.reset_at - now
+        return self.increase_at - now
 
     def is_rate_limited(self, now: float) -> bool:
         """Determine if we are under a rate limit at the given time.
@@ -387,16 +387,19 @@ def is_rate_limited(self, now: float) -> bool:
         bool
             Whether the bucket is ratelimited.
         """
-        if self.reset_at <= now:
-            self.remaining = self.limit
-            self.reset_at = now + self.period
-            return False
+        if now >= self.increase_at:
+            self.move_window(now)
 
         return self.remaining <= 0
 
-    def drip(self) -> None:
-        """Decrement the remaining counter."""
-        self.remaining -= 1
+    def move_window(self, now: float) -> None:
+        """Move the window along.
+
+        !!! note
+            You should usually not need to invoke this directly!
+        """
+        self.remaining = self.limit
+        self.increase_at = now + self.period
 
     async def throttle(self) -> None:
         """Perform the throttling rate limiter logic.
@@ -416,14 +419,14 @@ async def throttle(self) -> None:
             is occurring by checking if it is not [`None`][].
         """
         while self.queue:
-            sleep_for = self.get_time_until_reset(time.monotonic())
+            sleep_for = self.get_time_until_increase(time.time())
 
             if sleep_for > 0:
                 _LOGGER.debug("you are being rate limited on bucket %s, backing off for %ss", self.name, sleep_for)
                 await asyncio.sleep(sleep_for)
 
             while self.remaining > 0 and self.queue:
-                self.drip()
+                self.remaining -= 1
                 self.queue.pop(0).set_result(None)
 
         self.throttle_task = None
diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py
index 187873e9..d047e64d 100644
--- a/hikari/impl/rest.py
+++ b/hikari/impl/rest.py
@@ -114,6 +114,7 @@
 _X_RATELIMIT_BUCKET_HEADER: typing.Final[str] = sys.intern("X-RateLimit-Bucket")
 _X_RATELIMIT_LIMIT_HEADER: typing.Final[str] = sys.intern("X-RateLimit-Limit")
 _X_RATELIMIT_REMAINING_HEADER: typing.Final[str] = sys.intern("X-RateLimit-Remaining")
+_X_RATELIMIT_RESET_HEADER: typing.Final[str] = sys.intern("X-RateLimit-Reset")
 _X_RATELIMIT_RESET_AFTER_HEADER: typing.Final[str] = sys.intern("X-RateLimit-Reset-After")
 _X_RATELIMIT_SCOPE_HEADER: typing.Final[str] = sys.intern("X-RateLimit-Scope")
 _RETRY_ERROR_CODES: typing.Final[frozenset[int]] = frozenset((500, 502, 503, 504))
@@ -180,7 +181,7 @@ def client_id(self) -> snowflakes.Snowflake:
 
     @property
     def _is_expired(self) -> bool:
-        return time.monotonic() >= self._expire_at
+        return time.time() >= self._expire_at
 
     @property
     def scopes(self) -> typing.Sequence[applications.OAuth2Scope | str]:
@@ -218,7 +219,7 @@ async def acquire(self, client: rest_api.RESTClient) -> str:
                 raise
 
             # Expires in is lowered a bit in-order to lower the chance of a dead token being used.
-            self._expire_at = time.monotonic() + math.floor(response.expires_in.total_seconds() * 0.99)
+            self._expire_at = time.time() + math.floor(response.expires_in.total_seconds() * 0.99)
             self._token = f"{response.token_type} {response.access_token}"
             return self._token
 
@@ -812,7 +813,7 @@ async def _request(  # noqa: C901, PLR0912, PLR0915
                         url,
                         _stringify_http_message(headers, self._dumps(json)) if json else None,
                     )
-                    start = time.monotonic()
+                    start = time.time()
 
                 # Make the request.
                 response = await self._client_session.request(
@@ -828,7 +829,7 @@ async def _request(  # noqa: C901, PLR0912, PLR0915
                 )
 
                 if trace_logging_enabled:
-                    time_taken = (time.monotonic() - start) * 1_000  # pyright: ignore[reportUnboundVariable]
+                    time_taken = (time.time() - start) * 1_000  # pyright: ignore[reportUnboundVariable]
                     _LOGGER.log(
                         ux.TRACE,
                         "%s %s %s in %sms\n%s",
@@ -921,12 +922,13 @@ async def _parse_ratelimits(
         # If returns a `float`, the time to wait before retrying the request. If `None`, the request
         # does not need to be retried.
         resp_headers = response.headers
-        limit = int(resp_headers.get(_X_RATELIMIT_LIMIT_HEADER, "1"))
-        remaining = int(resp_headers.get(_X_RATELIMIT_REMAINING_HEADER, "1"))
         bucket = resp_headers.get(_X_RATELIMIT_BUCKET_HEADER)
-        reset_after = float(resp_headers.get(_X_RATELIMIT_RESET_AFTER_HEADER, "0"))
+        remaining = int(resp_headers.get(_X_RATELIMIT_REMAINING_HEADER, "1"))
 
         if bucket:
+            limit = int(resp_headers.get(_X_RATELIMIT_LIMIT_HEADER, "1"))
+            reset_at = float(resp_headers.get(_X_RATELIMIT_RESET_HEADER, "0"))
+            reset_after = float(resp_headers.get(_X_RATELIMIT_RESET_AFTER_HEADER, "0"))
             if not compiled_route.route.has_ratelimits:
                 # This should theoretically never see the light of day, but it scares me that Discord might
                 # pull a funny one and this may go unnoticed, so better safe to have it!
@@ -944,6 +946,7 @@ async def _parse_ratelimits(
                 bucket_header=bucket,
                 remaining_header=remaining,
                 limit_header=limit,
+                reset_at=reset_at,
                 reset_after=reset_after,
             )
 
@@ -971,7 +974,9 @@ async def _parse_ratelimits(
         # isn't some weird edge case here somewhere in Discord's implementation.
         # We can safely retry if this happens as acquiring the bucket will handle
         # this.
-        if remaining <= 0:
+        scope = resp_headers.get(_X_RATELIMIT_SCOPE_HEADER, "route")
+
+        if scope == "user" and remaining <= 0:
             _LOGGER.warning(
                 "rate limited on bucket %s, maybe you are running more than one bot on this token? Retrying request...",
                 bucket,
@@ -1014,7 +1019,7 @@ async def _parse_ratelimits(
         _LOGGER.error(
             "rate limited on a %s sub bucket on bucket %s (reason: '%s'). You should consider lowering the number "
             "of requests you make to '%s'. Backing off and retrying request...",
-            resp_headers.get(_X_RATELIMIT_SCOPE_HEADER, "route"),
+            scope,
             bucket,
             reason,
             compiled_route.route,
@@ -1026,7 +1031,7 @@ async def _parse_ratelimits(
                 is_global=False,
                 retry_after=body_retry_after,
                 max_retry_after=self._bucket_manager.max_rate_limit,
-                reset_at=time.monotonic() + body_retry_after,
+                reset_at=time.time() + body_retry_after,
                 limit=None,
                 period=None,
             )
diff --git a/hikari/impl/shard.py b/hikari/impl/shard.py
index e9bdf637..c308e63c 100644
--- a/hikari/impl/shard.py
+++ b/hikari/impl/shard.py
@@ -716,11 +716,11 @@ async def update_voice_state(
     async def _send_heartbeat(self) -> None:
         self._logger.log(ux.TRACE, "sending HEARTBEAT [s:%s]", self._seq)
         await self._send_json({_OP: _HEARTBEAT, _D: self._seq}, priority=True)
-        self._last_heartbeat_sent = time.monotonic()
+        self._last_heartbeat_sent = time.time()
 
     async def _heartbeat(self, heartbeat_interval: float) -> None:
         # Prevent immediately zombie-ing.
-        self._last_heartbeat_ack_received = time.monotonic()
+        self._last_heartbeat_ack_received = time.time()
         self._logger.debug("starting heartbeat with interval %ss", heartbeat_interval)
 
         while True:
@@ -729,7 +729,7 @@ async def _heartbeat(self, heartbeat_interval: float) -> None:
                 self._logger.error(
                     "connection has not received a HEARTBEAT_ACK for approx %.1fs and is being disconnected; "
                     "will attempt to reconnect",
-                    time.monotonic() - self._last_heartbeat_ack_received,
+                    time.time() - self._last_heartbeat_ack_received,
                 )
                 return
 
@@ -783,7 +783,7 @@ async def _poll_events(self) -> None:
                     self._logger.debug("ignoring unknown event %s:\n    %r", name, data)
 
             elif op == _HEARTBEAT_ACK:
-                now = time.monotonic()
+                now = time.time()
                 self._last_heartbeat_ack_received = now
                 self._heartbeat_latency = now - self._last_heartbeat_sent
                 self._logger.log(ux.TRACE, "received HEARTBEAT ACK in %.1fms", self._heartbeat_latency * 1_000)
@@ -922,13 +922,13 @@ async def _keep_alive(self) -> None:  # noqa: PLR0912, PLR0915
         while True:
             self._handshake_event.clear()
 
-            if time.monotonic() - last_started_at < _BACKOFF_WINDOW:
+            if time.time() - last_started_at < _BACKOFF_WINDOW:
                 backoff_time = next(backoff)
                 self._logger.info("backing off reconnecting for %.2fs", backoff_time)
                 await asyncio.sleep(backoff_time)
 
             try:
-                last_started_at = time.monotonic()
+                last_started_at = time.time()
                 lifetime_tasks = await self._connect()
 
                 if not self._handshake_event.is_set():
diff --git a/hikari/internal/routes.py b/hikari/internal/routes.py
index 90f07a10..127990da 100644
--- a/hikari/internal/routes.py
+++ b/hikari/internal/routes.py
@@ -116,7 +116,7 @@ def __str__(self) -> str:
 
 
 @attrs_extensions.with_copy
-@attrs.define(unsafe_hash=True, init=False, weakref_slot=False)
+@attrs.define(hash=False, eq=False, weakref_slot=False)
 @typing.final
 class Route:
     """A template used to create compiled routes for specific parameters.
@@ -138,10 +138,13 @@ class Route:
     path_template: str = attrs.field()
     """The template string used for the path."""
 
-    major_params: frozenset[str] | None = attrs.field(hash=False, eq=False, repr=False)
+    major_params: frozenset[str] | None = attrs.field(repr=False, init=False, default=None)
     """The optional major parameter name combination for this endpoint."""
 
-    has_ratelimits: bool = attrs.field(hash=False, eq=False, repr=False)
+    ratelimit_hash: int | None = attrs.field(repr=False, default=None)
+    """The rate limit hash for this endpoint."""
+
+    has_ratelimits: bool = attrs.field(repr=False, default=True)
     """Whether this route is affected by ratelimits.
 
     This should be left as [`True`][] (the default) for most routes. This
@@ -149,13 +152,8 @@ class Route:
     be a bit more efficient with them.
     """
 
-    def __init__(self, method: str, path_template: str, *, has_ratelimits: bool = True) -> None:
-        self.method = method
-        self.path_template = path_template
-        self.has_ratelimits = has_ratelimits
-
-        self.major_params = None
-        match = PARAM_REGEX.findall(path_template)
+    def __attrs_post_init__(self) -> None:
+        match = PARAM_REGEX.findall(self.path_template)
         for major_param_combo in MAJOR_PARAM_COMBOS:
             if major_param_combo.issubset(match):
                 self.major_params = major_param_combo
@@ -186,6 +184,10 @@ def compile(self, **kwargs: data_binding.Stringish) -> CompiledRoute:
             major_param_hash=MAJOR_PARAM_COMBOS[self.major_params](data) if self.major_params else "-",
         )
 
+    @typing_extensions.override
+    def __hash__(self) -> int:
+        return self.ratelimit_hash or hash((self.method, self.path_template))
+
     @typing_extensions.override
     def __str__(self) -> str:
         return self.method + " " + self.path_template
@@ -318,6 +320,10 @@ def compile_to_file(
 DELETE: typing.Final[str] = "DELETE"
 PUT: typing.Final[str] = "PUT"
 
+# PUT/DELETE /reactions/ are a special case because you pass arguments as part of the route,
+# so we need to join them
+_JOINED_REACTIONS_HASH = hash("PUT/DELETE REACTIONS")
+
 # Channels
 GET_CHANNEL: typing.Final[Route] = Route(GET, "/channels/{channel}")
 PATCH_CHANNEL: typing.Final[Route] = Route(PATCH, "/channels/{channel}")
@@ -378,10 +384,14 @@ def compile_to_file(
 
 # Reactions
 GET_REACTIONS: typing.Final[Route] = Route(GET, "/channels/{channel}/messages/{message}/reactions/{emoji}")
-DELETE_ALL_REACTIONS: typing.Final[Route] = Route(DELETE, "/channels/{channel}/messages/{message}/reactions")
-DELETE_REACTION_EMOJI: typing.Final[Route] = Route(DELETE, "/channels/{channel}/messages/{message}/reactions/{emoji}")
+DELETE_ALL_REACTIONS: typing.Final[Route] = Route(
+    DELETE, "/channels/{channel}/messages/{message}/reactions", ratelimit_hash=_JOINED_REACTIONS_HASH
+)
+DELETE_REACTION_EMOJI: typing.Final[Route] = Route(
+    DELETE, "/channels/{channel}/messages/{message}/reactions/{emoji}", ratelimit_hash=_JOINED_REACTIONS_HASH
+)
 DELETE_REACTION_USER: typing.Final[Route] = Route(
-    DELETE, "/channels/{channel}/messages/{message}/reactions/{emoji}/{user}"
+    DELETE, "/channels/{channel}/messages/{message}/reactions/{emoji}/{user}", ratelimit_hash=_JOINED_REACTIONS_HASH
 )
 
 # Guilds
@@ -530,8 +540,12 @@ def compile_to_file(
 GET_MY_USER: typing.Final[Route] = Route(GET, "/users/@me")
 PATCH_MY_USER: typing.Final[Route] = Route(PATCH, "/users/@me")
 
-PUT_MY_REACTION: typing.Final[Route] = Route(PUT, "/channels/{channel}/messages/{message}/reactions/{emoji}/@me")
-DELETE_MY_REACTION: typing.Final[Route] = Route(DELETE, "/channels/{channel}/messages/{message}/reactions/{emoji}/@me")
+PUT_MY_REACTION: typing.Final[Route] = Route(
+    PUT, "/channels/{channel}/messages/{message}/reactions/{emoji}/@me", ratelimit_hash=_JOINED_REACTIONS_HASH
+)
+DELETE_MY_REACTION: typing.Final[Route] = Route(
+    DELETE, "/channels/{channel}/messages/{message}/reactions/{emoji}/@me", ratelimit_hash=_JOINED_REACTIONS_HASH
+)
 
 # Voice
 GET_VOICE_REGIONS: typing.Final[Route] = Route(GET, "/voice/regions")
diff --git a/hikari/internal/time.py b/hikari/internal/time.py
index 3686b0fc..f26dbe58 100644
--- a/hikari/internal/time.py
+++ b/hikari/internal/time.py
@@ -28,8 +28,8 @@
     "datetime_to_discord_epoch",
     "discord_epoch_to_datetime",
     "local_datetime",
-    "monotonic",
-    "monotonic_ns",
+    "time",
+    "time_ns",
     "timespan_to_int",
     "unix_epoch_to_datetime",
     "utc_datetime",
@@ -37,7 +37,7 @@
 )
 
 import datetime
-import time
+import time as time_
 import typing
 import uuid as uuid_
 
@@ -196,29 +196,24 @@ def utc_datetime() -> datetime.datetime:
     return datetime.datetime.now(tz=datetime.timezone.utc)
 
 
-# time.monotonic_ns is no slower than time.monotonic, but is more accurate.
-# Also, fun fact that monotonic_ns appears to be 1µs faster on average than
-# monotonic on ARM64 architectures, but on x86, monotonic is around 1ns faster
-# than monotonic_ns. Just thought that was kind of interesting to note down.
-# (RPi 3B versus i7 6700)
 if typing.TYPE_CHECKING:
 
-    def monotonic() -> float:
-        """Performance counter for benchmarking."""  # noqa: D401 - Imperative mood
+    def time() -> float:
+        """Epoch time in seconds (since 00:00:00 UTC on January 1, 1970)."""
         raise NotImplementedError
 
-    def monotonic_ns() -> int:
-        """Performance counter for benchmarking as nanoseconds."""  # noqa: D401 - Imperative mood
+    def time_ns() -> int:
+        """Epoch time in nanoseconds (since 00:00:00 UTC on January 1, 1970)."""
         raise NotImplementedError
 
 else:
-    monotonic = time.perf_counter
-    """Performance counter for benchmarking."""
+    time = time_.time
+    """Epoch time in seconds (since 00:00:00 UTC on January 1, 1970)."""
 
-    monotonic_ns = time.perf_counter_ns
-    """Performance counter for benchmarking as nanoseconds."""
+    time_ns = time_.time_ns
+    """Epoch time in nanoseconds (since 00:00:00 UTC on January 1, 1970)."""
 
 
 def uuid() -> str:
     """Generate a unique UUID (1ns precision)."""
-    return uuid_.uuid1(None, monotonic_ns()).hex
+    return uuid_.uuid1(None, time_ns()).hex
diff --git a/uv.lock b/uv.lock
index 1bc539e1..ec3765a2 100644
--- a/uv.lock
+++ b/uv.lock
@@ -233,15 +233,15 @@ wheels = [
 
 [[package]]
 name = "backrefs"
-version = "5.8"
+version = "5.9"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/6c/46/caba1eb32fa5784428ab401a5487f73db4104590ecd939ed9daaf18b47e0/backrefs-5.8.tar.gz", hash = "sha256:2cab642a205ce966af3dd4b38ee36009b31fa9502a35fd61d59ccc116e40a6bd", size = 6773994, upload-time = "2025-02-25T18:15:32.003Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/eb/a7/312f673df6a79003279e1f55619abbe7daebbb87c17c976ddc0345c04c7b/backrefs-5.9.tar.gz", hash = "sha256:808548cb708d66b82ee231f962cb36faaf4f2baab032f2fbb783e9c2fdddaa59", size = 5765857, upload-time = "2025-06-22T19:34:13.97Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/bf/cb/d019ab87fe70e0fe3946196d50d6a4428623dc0c38a6669c8cae0320fbf3/backrefs-5.8-py310-none-any.whl", hash = "sha256:c67f6638a34a5b8730812f5101376f9d41dc38c43f1fdc35cb54700f6ed4465d", size = 380337, upload-time = "2025-02-25T16:53:14.607Z" },
-    { url = "https://files.pythonhosted.org/packages/a9/86/abd17f50ee21b2248075cb6924c6e7f9d23b4925ca64ec660e869c2633f1/backrefs-5.8-py311-none-any.whl", hash = "sha256:2e1c15e4af0e12e45c8701bd5da0902d326b2e200cafcd25e49d9f06d44bb61b", size = 392142, upload-time = "2025-02-25T16:53:17.266Z" },
-    { url = "https://files.pythonhosted.org/packages/b3/04/7b415bd75c8ab3268cc138c76fa648c19495fcc7d155508a0e62f3f82308/backrefs-5.8-py312-none-any.whl", hash = "sha256:bbef7169a33811080d67cdf1538c8289f76f0942ff971222a16034da88a73486", size = 398021, upload-time = "2025-02-25T16:53:26.378Z" },
-    { url = "https://files.pythonhosted.org/packages/04/b8/60dcfb90eb03a06e883a92abbc2ab95c71f0d8c9dd0af76ab1d5ce0b1402/backrefs-5.8-py313-none-any.whl", hash = "sha256:e3a63b073867dbefd0536425f43db618578528e3896fb77be7141328642a1585", size = 399915, upload-time = "2025-02-25T16:53:28.167Z" },
-    { url = "https://files.pythonhosted.org/packages/0c/37/fb6973edeb700f6e3d6ff222400602ab1830446c25c7b4676d8de93e65b8/backrefs-5.8-py39-none-any.whl", hash = "sha256:a66851e4533fb5b371aa0628e1fee1af05135616b86140c9d787a2ffdf4b8fdc", size = 380336, upload-time = "2025-02-25T16:53:29.858Z" },
+    { url = "https://files.pythonhosted.org/packages/19/4d/798dc1f30468134906575156c089c492cf79b5a5fd373f07fe26c4d046bf/backrefs-5.9-py310-none-any.whl", hash = "sha256:db8e8ba0e9de81fcd635f440deab5ae5f2591b54ac1ebe0550a2ca063488cd9f", size = 380267, upload-time = "2025-06-22T19:34:05.252Z" },
+    { url = "https://files.pythonhosted.org/packages/55/07/f0b3375bf0d06014e9787797e6b7cc02b38ac9ff9726ccfe834d94e9991e/backrefs-5.9-py311-none-any.whl", hash = "sha256:6907635edebbe9b2dc3de3a2befff44d74f30a4562adbb8b36f21252ea19c5cf", size = 392072, upload-time = "2025-06-22T19:34:06.743Z" },
+    { url = "https://files.pythonhosted.org/packages/9d/12/4f345407259dd60a0997107758ba3f221cf89a9b5a0f8ed5b961aef97253/backrefs-5.9-py312-none-any.whl", hash = "sha256:7fdf9771f63e6028d7fee7e0c497c81abda597ea45d6b8f89e8ad76994f5befa", size = 397947, upload-time = "2025-06-22T19:34:08.172Z" },
+    { url = "https://files.pythonhosted.org/packages/10/bf/fa31834dc27a7f05e5290eae47c82690edc3a7b37d58f7fb35a1bdbf355b/backrefs-5.9-py313-none-any.whl", hash = "sha256:cc37b19fa219e93ff825ed1fed8879e47b4d89aa7a1884860e2db64ccd7c676b", size = 399843, upload-time = "2025-06-22T19:34:09.68Z" },
+    { url = "https://files.pythonhosted.org/packages/41/ff/392bff89415399a979be4a65357a41d92729ae8580a66073d8ec8d810f98/backrefs-5.9-py39-none-any.whl", hash = "sha256:f48ee18f6252b8f5777a22a00a09a85de0ca931658f1dd96d4406a34f3748c60", size = 380265, upload-time = "2025-06-22T19:34:12.405Z" },
 ]
 
 [[package]]
@@ -1292,14 +1292,14 @@ wheels = [
 
 [[package]]
 name = "jaraco-functools"
-version = "4.1.0"
+version = "4.2.1"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "more-itertools" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/ab/23/9894b3df5d0a6eb44611c36aec777823fc2e07740dabbd0b810e19594013/jaraco_functools-4.1.0.tar.gz", hash = "sha256:70f7e0e2ae076498e212562325e805204fc092d7b4c17e0e86c959e249701a9d", size = 19159, upload-time = "2024-09-27T19:47:09.122Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/49/1c/831faaaa0f090b711c355c6d8b2abf277c72133aab472b6932b03322294c/jaraco_functools-4.2.1.tar.gz", hash = "sha256:be634abfccabce56fa3053f8c7ebe37b682683a4ee7793670ced17bab0087353", size = 19661, upload-time = "2025-06-21T19:22:03.201Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/9f/4f/24b319316142c44283d7540e76c7b5a6dbd5db623abd86bb7b3491c21018/jaraco.functools-4.1.0-py3-none-any.whl", hash = "sha256:ad159f13428bc4acbf5541ad6dec511f91573b90fba04df61dafa2a1231cf649", size = 10187, upload-time = "2024-09-27T19:47:07.14Z" },
+    { url = "https://files.pythonhosted.org/packages/f3/fd/179a20f832824514df39a90bb0e5372b314fea99f217f5ab942b10a8a4e8/jaraco_functools-4.2.1-py3-none-any.whl", hash = "sha256:590486285803805f4b1f99c60ca9e94ed348d4added84b74c7a12885561e524e", size = 10349, upload-time = "2025-06-21T19:22:02.039Z" },
 ]
 
 [[package]]
@@ -1349,14 +1349,14 @@ wheels = [
 
 [[package]]
 name = "markdown"
-version = "3.8"
+version = "3.8.2"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/2f/15/222b423b0b88689c266d9eac4e61396fe2cc53464459d6a37618ac863b24/markdown-3.8.tar.gz", hash = "sha256:7df81e63f0df5c4b24b7d156eb81e4690595239b7d70937d0409f1b0de319c6f", size = 360906, upload-time = "2025-04-11T14:42:50.928Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/d7/c2/4ab49206c17f75cb08d6311171f2d65798988db4360c4d1485bd0eedd67c/markdown-3.8.2.tar.gz", hash = "sha256:247b9a70dd12e27f67431ce62523e675b866d254f900c4fe75ce3dda62237c45", size = 362071, upload-time = "2025-06-19T17:12:44.483Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/51/3f/afe76f8e2246ffbc867440cbcf90525264df0e658f8a5ca1f872b3f6192a/markdown-3.8-py3-none-any.whl", hash = "sha256:794a929b79c5af141ef5ab0f2f642d0f7b1872981250230e72682346f7cc90dc", size = 106210, upload-time = "2025-04-11T14:42:49.178Z" },
+    { url = "https://files.pythonhosted.org/packages/96/2b/34cc11786bc00d0f04d0f5fdc3a2b1ae0b6239eef72d3d345805f9ad92a1/markdown-3.8.2-py3-none-any.whl", hash = "sha256:5c83764dbd4e00bdd94d85a19b8d55ccca20fe35b2e678a1422b380324dd5f24", size = 106827, upload-time = "2025-06-19T17:12:42.994Z" },
 ]
 
 [[package]]
@@ -1659,116 +1659,122 @@ wheels = [
 
 [[package]]
 name = "multidict"
-version = "6.4.4"
+version = "6.5.0"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "typing-extensions", marker = "python_full_version < '3.11'" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/91/2f/a3470242707058fe856fe59241eee5635d79087100b7042a867368863a27/multidict-6.4.4.tar.gz", hash = "sha256:69ee9e6ba214b5245031b76233dd95408a0fd57fdb019ddcc1ead4790932a8e8", size = 90183, upload-time = "2025-05-19T14:16:37.381Z" }
-wheels = [
-    { url = "https://files.pythonhosted.org/packages/1f/92/0926a5baafa164b5d0ade3cd7932be39310375d7e25c9d7ceca05cb26a45/multidict-6.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8adee3ac041145ffe4488ea73fa0a622b464cc25340d98be76924d0cda8545ff", size = 66052, upload-time = "2025-05-19T14:13:49.944Z" },
-    { url = "https://files.pythonhosted.org/packages/b2/54/8a857ae4f8f643ec444d91f419fdd49cc7a90a2ca0e42d86482b604b63bd/multidict-6.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b61e98c3e2a861035aaccd207da585bdcacef65fe01d7a0d07478efac005e028", size = 38867, upload-time = "2025-05-19T14:13:51.92Z" },
-    { url = "https://files.pythonhosted.org/packages/9e/5f/63add9069f945c19bc8b217ea6b0f8a1ad9382eab374bb44fae4354b3baf/multidict-6.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:75493f28dbadecdbb59130e74fe935288813301a8554dc32f0c631b6bdcdf8b0", size = 38138, upload-time = "2025-05-19T14:13:53.778Z" },
-    { url = "https://files.pythonhosted.org/packages/97/8b/fbd9c0fc13966efdb4a47f5bcffff67a4f2a3189fbeead5766eaa4250b20/multidict-6.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffc3c6a37e048b5395ee235e4a2a0d639c2349dffa32d9367a42fc20d399772", size = 220433, upload-time = "2025-05-19T14:13:55.346Z" },
-    { url = "https://files.pythonhosted.org/packages/a9/c4/5132b2d75b3ea2daedb14d10f91028f09f74f5b4d373b242c1b8eec47571/multidict-6.4.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:87cb72263946b301570b0f63855569a24ee8758aaae2cd182aae7d95fbc92ca7", size = 218059, upload-time = "2025-05-19T14:13:56.993Z" },
-    { url = "https://files.pythonhosted.org/packages/1a/70/f1e818c7a29b908e2d7b4fafb1d7939a41c64868e79de2982eea0a13193f/multidict-6.4.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bbf7bd39822fd07e3609b6b4467af4c404dd2b88ee314837ad1830a7f4a8299", size = 231120, upload-time = "2025-05-19T14:13:58.333Z" },
-    { url = "https://files.pythonhosted.org/packages/b4/7e/95a194d85f27d5ef9cbe48dff9ded722fc6d12fedf641ec6e1e680890be7/multidict-6.4.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1f7cbd4f1f44ddf5fd86a8675b7679176eae770f2fc88115d6dddb6cefb59bc", size = 227457, upload-time = "2025-05-19T14:13:59.663Z" },
-    { url = "https://files.pythonhosted.org/packages/25/2b/590ad220968d1babb42f265debe7be5c5c616df6c5688c995a06d8a9b025/multidict-6.4.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb5ac9e5bfce0e6282e7f59ff7b7b9a74aa8e5c60d38186a4637f5aa764046ad", size = 219111, upload-time = "2025-05-19T14:14:01.019Z" },
-    { url = "https://files.pythonhosted.org/packages/e0/f0/b07682b995d3fb5313f339b59d7de02db19ba0c02d1f77c27bdf8212d17c/multidict-6.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4efc31dfef8c4eeb95b6b17d799eedad88c4902daba39ce637e23a17ea078915", size = 213012, upload-time = "2025-05-19T14:14:02.396Z" },
-    { url = "https://files.pythonhosted.org/packages/24/56/c77b5f36feef2ec92f1119756e468ac9c3eebc35aa8a4c9e51df664cbbc9/multidict-6.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9fcad2945b1b91c29ef2b4050f590bfcb68d8ac8e0995a74e659aa57e8d78e01", size = 225408, upload-time = "2025-05-19T14:14:04.826Z" },
-    { url = "https://files.pythonhosted.org/packages/cc/b3/e8189b82af9b198b47bc637766208fc917189eea91d674bad417e657bbdf/multidict-6.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:d877447e7368c7320832acb7159557e49b21ea10ffeb135c1077dbbc0816b598", size = 214396, upload-time = "2025-05-19T14:14:06.187Z" },
-    { url = "https://files.pythonhosted.org/packages/20/e0/200d14c84e35ae13ee99fd65dc106e1a1acb87a301f15e906fc7d5b30c17/multidict-6.4.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:33a12ebac9f380714c298cbfd3e5b9c0c4e89c75fe612ae496512ee51028915f", size = 222237, upload-time = "2025-05-19T14:14:07.778Z" },
-    { url = "https://files.pythonhosted.org/packages/13/f3/bb3df40045ca8262694a3245298732ff431dc781414a89a6a364ebac6840/multidict-6.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0f14ea68d29b43a9bf37953881b1e3eb75b2739e896ba4a6aa4ad4c5b9ffa145", size = 231425, upload-time = "2025-05-19T14:14:09.516Z" },
-    { url = "https://files.pythonhosted.org/packages/85/3b/538563dc18514384dac169bcba938753ad9ab4d4c8d49b55d6ae49fb2579/multidict-6.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0327ad2c747a6600e4797d115d3c38a220fdb28e54983abe8964fd17e95ae83c", size = 226251, upload-time = "2025-05-19T14:14:10.82Z" },
-    { url = "https://files.pythonhosted.org/packages/56/79/77e1a65513f09142358f1beb1d4cbc06898590b34a7de2e47023e3c5a3a2/multidict-6.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d1a20707492db9719a05fc62ee215fd2c29b22b47c1b1ba347f9abc831e26683", size = 220363, upload-time = "2025-05-19T14:14:12.638Z" },
-    { url = "https://files.pythonhosted.org/packages/16/57/67b0516c3e348f8daaa79c369b3de4359a19918320ab82e2e586a1c624ef/multidict-6.4.4-cp310-cp310-win32.whl", hash = "sha256:d83f18315b9fca5db2452d1881ef20f79593c4aa824095b62cb280019ef7aa3d", size = 35175, upload-time = "2025-05-19T14:14:14.805Z" },
-    { url = "https://files.pythonhosted.org/packages/86/5a/4ed8fec642d113fa653777cda30ef67aa5c8a38303c091e24c521278a6c6/multidict-6.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:9c17341ee04545fd962ae07330cb5a39977294c883485c8d74634669b1f7fe04", size = 38678, upload-time = "2025-05-19T14:14:16.949Z" },
-    { url = "https://files.pythonhosted.org/packages/19/1b/4c6e638195851524a63972c5773c7737bea7e47b1ba402186a37773acee2/multidict-6.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4f5f29794ac0e73d2a06ac03fd18870adc0135a9d384f4a306a951188ed02f95", size = 65515, upload-time = "2025-05-19T14:14:19.767Z" },
-    { url = "https://files.pythonhosted.org/packages/25/d5/10e6bca9a44b8af3c7f920743e5fc0c2bcf8c11bf7a295d4cfe00b08fb46/multidict-6.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c04157266344158ebd57b7120d9b0b35812285d26d0e78193e17ef57bfe2979a", size = 38609, upload-time = "2025-05-19T14:14:21.538Z" },
-    { url = "https://files.pythonhosted.org/packages/26/b4/91fead447ccff56247edc7f0535fbf140733ae25187a33621771ee598a18/multidict-6.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bb61ffd3ab8310d93427e460f565322c44ef12769f51f77277b4abad7b6f7223", size = 37871, upload-time = "2025-05-19T14:14:22.666Z" },
-    { url = "https://files.pythonhosted.org/packages/3b/37/cbc977cae59277e99d15bbda84cc53b5e0c4929ffd91d958347200a42ad0/multidict-6.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e0ba18a9afd495f17c351d08ebbc4284e9c9f7971d715f196b79636a4d0de44", size = 226661, upload-time = "2025-05-19T14:14:24.124Z" },
-    { url = "https://files.pythonhosted.org/packages/15/cd/7e0b57fbd4dc2fc105169c4ecce5be1a63970f23bb4ec8c721b67e11953d/multidict-6.4.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9faf1b1dcaadf9f900d23a0e6d6c8eadd6a95795a0e57fcca73acce0eb912065", size = 223422, upload-time = "2025-05-19T14:14:25.437Z" },
-    { url = "https://files.pythonhosted.org/packages/f1/01/1de268da121bac9f93242e30cd3286f6a819e5f0b8896511162d6ed4bf8d/multidict-6.4.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a4d1cb1327c6082c4fce4e2a438483390964c02213bc6b8d782cf782c9b1471f", size = 235447, upload-time = "2025-05-19T14:14:26.793Z" },
-    { url = "https://files.pythonhosted.org/packages/d2/8c/8b9a5e4aaaf4f2de14e86181a3a3d7b105077f668b6a06f043ec794f684c/multidict-6.4.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:941f1bec2f5dbd51feeb40aea654c2747f811ab01bdd3422a48a4e4576b7d76a", size = 231455, upload-time = "2025-05-19T14:14:28.149Z" },
-    { url = "https://files.pythonhosted.org/packages/35/db/e1817dcbaa10b319c412769cf999b1016890849245d38905b73e9c286862/multidict-6.4.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5f8a146184da7ea12910a4cec51ef85e44f6268467fb489c3caf0cd512f29c2", size = 223666, upload-time = "2025-05-19T14:14:29.584Z" },
-    { url = "https://files.pythonhosted.org/packages/4a/e1/66e8579290ade8a00e0126b3d9a93029033ffd84f0e697d457ed1814d0fc/multidict-6.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:232b7237e57ec3c09be97206bfb83a0aa1c5d7d377faa019c68a210fa35831f1", size = 217392, upload-time = "2025-05-19T14:14:30.961Z" },
-    { url = "https://files.pythonhosted.org/packages/7b/6f/f8639326069c24a48c7747c2a5485d37847e142a3f741ff3340c88060a9a/multidict-6.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:55ae0721c1513e5e3210bca4fc98456b980b0c2c016679d3d723119b6b202c42", size = 228969, upload-time = "2025-05-19T14:14:32.672Z" },
-    { url = "https://files.pythonhosted.org/packages/d2/c3/3d58182f76b960eeade51c89fcdce450f93379340457a328e132e2f8f9ed/multidict-6.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:51d662c072579f63137919d7bb8fc250655ce79f00c82ecf11cab678f335062e", size = 217433, upload-time = "2025-05-19T14:14:34.016Z" },
-    { url = "https://files.pythonhosted.org/packages/e1/4b/f31a562906f3bd375f3d0e83ce314e4a660c01b16c2923e8229b53fba5d7/multidict-6.4.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0e05c39962baa0bb19a6b210e9b1422c35c093b651d64246b6c2e1a7e242d9fd", size = 225418, upload-time = "2025-05-19T14:14:35.376Z" },
-    { url = "https://files.pythonhosted.org/packages/99/89/78bb95c89c496d64b5798434a3deee21996114d4d2c28dd65850bf3a691e/multidict-6.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5b1cc3ab8c31d9ebf0faa6e3540fb91257590da330ffe6d2393d4208e638925", size = 235042, upload-time = "2025-05-19T14:14:36.723Z" },
-    { url = "https://files.pythonhosted.org/packages/74/91/8780a6e5885a8770442a8f80db86a0887c4becca0e5a2282ba2cae702bc4/multidict-6.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:93ec84488a384cd7b8a29c2c7f467137d8a73f6fe38bb810ecf29d1ade011a7c", size = 230280, upload-time = "2025-05-19T14:14:38.194Z" },
-    { url = "https://files.pythonhosted.org/packages/68/c1/fcf69cabd542eb6f4b892469e033567ee6991d361d77abdc55e3a0f48349/multidict-6.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b308402608493638763abc95f9dc0030bbd6ac6aff784512e8ac3da73a88af08", size = 223322, upload-time = "2025-05-19T14:14:40.015Z" },
-    { url = "https://files.pythonhosted.org/packages/b8/85/5b80bf4b83d8141bd763e1d99142a9cdfd0db83f0739b4797172a4508014/multidict-6.4.4-cp311-cp311-win32.whl", hash = "sha256:343892a27d1a04d6ae455ecece12904d242d299ada01633d94c4f431d68a8c49", size = 35070, upload-time = "2025-05-19T14:14:41.904Z" },
-    { url = "https://files.pythonhosted.org/packages/09/66/0bed198ffd590ab86e001f7fa46b740d58cf8ff98c2f254e4a36bf8861ad/multidict-6.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:73484a94f55359780c0f458bbd3c39cb9cf9c182552177d2136e828269dee529", size = 38667, upload-time = "2025-05-19T14:14:43.534Z" },
-    { url = "https://files.pythonhosted.org/packages/d2/b5/5675377da23d60875fe7dae6be841787755878e315e2f517235f22f59e18/multidict-6.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dc388f75a1c00000824bf28b7633e40854f4127ede80512b44c3cfeeea1839a2", size = 64293, upload-time = "2025-05-19T14:14:44.724Z" },
-    { url = "https://files.pythonhosted.org/packages/34/a7/be384a482754bb8c95d2bbe91717bf7ccce6dc38c18569997a11f95aa554/multidict-6.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:98af87593a666f739d9dba5d0ae86e01b0e1a9cfcd2e30d2d361fbbbd1a9162d", size = 38096, upload-time = "2025-05-19T14:14:45.95Z" },
-    { url = "https://files.pythonhosted.org/packages/66/6d/d59854bb4352306145bdfd1704d210731c1bb2c890bfee31fb7bbc1c4c7f/multidict-6.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aff4cafea2d120327d55eadd6b7f1136a8e5a0ecf6fb3b6863e8aca32cd8e50a", size = 37214, upload-time = "2025-05-19T14:14:47.158Z" },
-    { url = "https://files.pythonhosted.org/packages/99/e0/c29d9d462d7cfc5fc8f9bf24f9c6843b40e953c0b55e04eba2ad2cf54fba/multidict-6.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:169c4ba7858176b797fe551d6e99040c531c775d2d57b31bcf4de6d7a669847f", size = 224686, upload-time = "2025-05-19T14:14:48.366Z" },
-    { url = "https://files.pythonhosted.org/packages/dc/4a/da99398d7fd8210d9de068f9a1b5f96dfaf67d51e3f2521f17cba4ee1012/multidict-6.4.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b9eb4c59c54421a32b3273d4239865cb14ead53a606db066d7130ac80cc8ec93", size = 231061, upload-time = "2025-05-19T14:14:49.952Z" },
-    { url = "https://files.pythonhosted.org/packages/21/f5/ac11add39a0f447ac89353e6ca46666847051103649831c08a2800a14455/multidict-6.4.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7cf3bd54c56aa16fdb40028d545eaa8d051402b61533c21e84046e05513d5780", size = 232412, upload-time = "2025-05-19T14:14:51.812Z" },
-    { url = "https://files.pythonhosted.org/packages/d9/11/4b551e2110cded705a3c13a1d4b6a11f73891eb5a1c449f1b2b6259e58a6/multidict-6.4.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f682c42003c7264134bfe886376299db4cc0c6cd06a3295b41b347044bcb5482", size = 231563, upload-time = "2025-05-19T14:14:53.262Z" },
-    { url = "https://files.pythonhosted.org/packages/4c/02/751530c19e78fe73b24c3da66618eda0aa0d7f6e7aa512e46483de6be210/multidict-6.4.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920f9cf2abdf6e493c519492d892c362007f113c94da4c239ae88429835bad1", size = 223811, upload-time = "2025-05-19T14:14:55.232Z" },
-    { url = "https://files.pythonhosted.org/packages/c7/cb/2be8a214643056289e51ca356026c7b2ce7225373e7a1f8c8715efee8988/multidict-6.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:530d86827a2df6504526106b4c104ba19044594f8722d3e87714e847c74a0275", size = 216524, upload-time = "2025-05-19T14:14:57.226Z" },
-    { url = "https://files.pythonhosted.org/packages/19/f3/6d5011ec375c09081f5250af58de85f172bfcaafebff286d8089243c4bd4/multidict-6.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ecde56ea2439b96ed8a8d826b50c57364612ddac0438c39e473fafad7ae1c23b", size = 229012, upload-time = "2025-05-19T14:14:58.597Z" },
-    { url = "https://files.pythonhosted.org/packages/67/9c/ca510785df5cf0eaf5b2a8132d7d04c1ce058dcf2c16233e596ce37a7f8e/multidict-6.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:dc8c9736d8574b560634775ac0def6bdc1661fc63fa27ffdfc7264c565bcb4f2", size = 226765, upload-time = "2025-05-19T14:15:00.048Z" },
-    { url = "https://files.pythonhosted.org/packages/36/c8/ca86019994e92a0f11e642bda31265854e6ea7b235642f0477e8c2e25c1f/multidict-6.4.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7f3d3b3c34867579ea47cbd6c1f2ce23fbfd20a273b6f9e3177e256584f1eacc", size = 222888, upload-time = "2025-05-19T14:15:01.568Z" },
-    { url = "https://files.pythonhosted.org/packages/c6/67/bc25a8e8bd522935379066950ec4e2277f9b236162a73548a2576d4b9587/multidict-6.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:87a728af265e08f96b6318ebe3c0f68b9335131f461efab2fc64cc84a44aa6ed", size = 234041, upload-time = "2025-05-19T14:15:03.759Z" },
-    { url = "https://files.pythonhosted.org/packages/f1/a0/70c4c2d12857fccbe607b334b7ee28b6b5326c322ca8f73ee54e70d76484/multidict-6.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9f193eeda1857f8e8d3079a4abd258f42ef4a4bc87388452ed1e1c4d2b0c8740", size = 231046, upload-time = "2025-05-19T14:15:05.698Z" },
-    { url = "https://files.pythonhosted.org/packages/c1/0f/52954601d02d39742aab01d6b92f53c1dd38b2392248154c50797b4df7f1/multidict-6.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be06e73c06415199200e9a2324a11252a3d62030319919cde5e6950ffeccf72e", size = 227106, upload-time = "2025-05-19T14:15:07.124Z" },
-    { url = "https://files.pythonhosted.org/packages/af/24/679d83ec4379402d28721790dce818e5d6b9f94ce1323a556fb17fa9996c/multidict-6.4.4-cp312-cp312-win32.whl", hash = "sha256:622f26ea6a7e19b7c48dd9228071f571b2fbbd57a8cd71c061e848f281550e6b", size = 35351, upload-time = "2025-05-19T14:15:08.556Z" },
-    { url = "https://files.pythonhosted.org/packages/52/ef/40d98bc5f986f61565f9b345f102409534e29da86a6454eb6b7c00225a13/multidict-6.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:5e2bcda30d5009996ff439e02a9f2b5c3d64a20151d34898c000a6281faa3781", size = 38791, upload-time = "2025-05-19T14:15:09.825Z" },
-    { url = "https://files.pythonhosted.org/packages/df/2a/e166d2ffbf4b10131b2d5b0e458f7cee7d986661caceae0de8753042d4b2/multidict-6.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:82ffabefc8d84c2742ad19c37f02cde5ec2a1ee172d19944d380f920a340e4b9", size = 64123, upload-time = "2025-05-19T14:15:11.044Z" },
-    { url = "https://files.pythonhosted.org/packages/8c/96/e200e379ae5b6f95cbae472e0199ea98913f03d8c9a709f42612a432932c/multidict-6.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6a2f58a66fe2c22615ad26156354005391e26a2f3721c3621504cd87c1ea87bf", size = 38049, upload-time = "2025-05-19T14:15:12.902Z" },
-    { url = "https://files.pythonhosted.org/packages/75/fb/47afd17b83f6a8c7fa863c6d23ac5ba6a0e6145ed8a6bcc8da20b2b2c1d2/multidict-6.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5883d6ee0fd9d8a48e9174df47540b7545909841ac82354c7ae4cbe9952603bd", size = 37078, upload-time = "2025-05-19T14:15:14.282Z" },
-    { url = "https://files.pythonhosted.org/packages/fa/70/1af3143000eddfb19fd5ca5e78393985ed988ac493bb859800fe0914041f/multidict-6.4.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9abcf56a9511653fa1d052bfc55fbe53dbee8f34e68bd6a5a038731b0ca42d15", size = 224097, upload-time = "2025-05-19T14:15:15.566Z" },
-    { url = "https://files.pythonhosted.org/packages/b1/39/d570c62b53d4fba844e0378ffbcd02ac25ca423d3235047013ba2f6f60f8/multidict-6.4.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6ed5ae5605d4ad5a049fad2a28bb7193400700ce2f4ae484ab702d1e3749c3f9", size = 230768, upload-time = "2025-05-19T14:15:17.308Z" },
-    { url = "https://files.pythonhosted.org/packages/fd/f8/ed88f2c4d06f752b015933055eb291d9bc184936903752c66f68fb3c95a7/multidict-6.4.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbfcb60396f9bcfa63e017a180c3105b8c123a63e9d1428a36544e7d37ca9e20", size = 231331, upload-time = "2025-05-19T14:15:18.73Z" },
-    { url = "https://files.pythonhosted.org/packages/9c/6f/8e07cffa32f483ab887b0d56bbd8747ac2c1acd00dc0af6fcf265f4a121e/multidict-6.4.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0f1987787f5f1e2076b59692352ab29a955b09ccc433c1f6b8e8e18666f608b", size = 230169, upload-time = "2025-05-19T14:15:20.179Z" },
-    { url = "https://files.pythonhosted.org/packages/e6/2b/5dcf173be15e42f330110875a2668ddfc208afc4229097312212dc9c1236/multidict-6.4.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d0121ccce8c812047d8d43d691a1ad7641f72c4f730474878a5aeae1b8ead8c", size = 222947, upload-time = "2025-05-19T14:15:21.714Z" },
-    { url = "https://files.pythonhosted.org/packages/39/75/4ddcbcebe5ebcd6faa770b629260d15840a5fc07ce8ad295a32e14993726/multidict-6.4.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83ec4967114295b8afd120a8eec579920c882831a3e4c3331d591a8e5bfbbc0f", size = 215761, upload-time = "2025-05-19T14:15:23.242Z" },
-    { url = "https://files.pythonhosted.org/packages/6a/c9/55e998ae45ff15c5608e384206aa71a11e1b7f48b64d166db400b14a3433/multidict-6.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:995f985e2e268deaf17867801b859a282e0448633f1310e3704b30616d269d69", size = 227605, upload-time = "2025-05-19T14:15:24.763Z" },
-    { url = "https://files.pythonhosted.org/packages/04/49/c2404eac74497503c77071bd2e6f88c7e94092b8a07601536b8dbe99be50/multidict-6.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d832c608f94b9f92a0ec8b7e949be7792a642b6e535fcf32f3e28fab69eeb046", size = 226144, upload-time = "2025-05-19T14:15:26.249Z" },
-    { url = "https://files.pythonhosted.org/packages/62/c5/0cd0c3c6f18864c40846aa2252cd69d308699cb163e1c0d989ca301684da/multidict-6.4.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d21c1212171cf7da703c5b0b7a0e85be23b720818aef502ad187d627316d5645", size = 221100, upload-time = "2025-05-19T14:15:28.303Z" },
-    { url = "https://files.pythonhosted.org/packages/71/7b/f2f3887bea71739a046d601ef10e689528d4f911d84da873b6be9194ffea/multidict-6.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:cbebaa076aaecad3d4bb4c008ecc73b09274c952cf6a1b78ccfd689e51f5a5b0", size = 232731, upload-time = "2025-05-19T14:15:30.263Z" },
-    { url = "https://files.pythonhosted.org/packages/e5/b3/d9de808349df97fa75ec1372758701b5800ebad3c46ae377ad63058fbcc6/multidict-6.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:c93a6fb06cc8e5d3628b2b5fda215a5db01e8f08fc15fadd65662d9b857acbe4", size = 229637, upload-time = "2025-05-19T14:15:33.337Z" },
-    { url = "https://files.pythonhosted.org/packages/5e/57/13207c16b615eb4f1745b44806a96026ef8e1b694008a58226c2d8f5f0a5/multidict-6.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8cd8f81f1310182362fb0c7898145ea9c9b08a71081c5963b40ee3e3cac589b1", size = 225594, upload-time = "2025-05-19T14:15:34.832Z" },
-    { url = "https://files.pythonhosted.org/packages/3a/e4/d23bec2f70221604f5565000632c305fc8f25ba953e8ce2d8a18842b9841/multidict-6.4.4-cp313-cp313-win32.whl", hash = "sha256:3e9f1cd61a0ab857154205fb0b1f3d3ace88d27ebd1409ab7af5096e409614cd", size = 35359, upload-time = "2025-05-19T14:15:36.246Z" },
-    { url = "https://files.pythonhosted.org/packages/a7/7a/cfe1a47632be861b627f46f642c1d031704cc1c0f5c0efbde2ad44aa34bd/multidict-6.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:8ffb40b74400e4455785c2fa37eba434269149ec525fc8329858c862e4b35373", size = 38903, upload-time = "2025-05-19T14:15:37.507Z" },
-    { url = "https://files.pythonhosted.org/packages/68/7b/15c259b0ab49938a0a1c8f3188572802704a779ddb294edc1b2a72252e7c/multidict-6.4.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6a602151dbf177be2450ef38966f4be3467d41a86c6a845070d12e17c858a156", size = 68895, upload-time = "2025-05-19T14:15:38.856Z" },
-    { url = "https://files.pythonhosted.org/packages/f1/7d/168b5b822bccd88142e0a3ce985858fea612404edd228698f5af691020c9/multidict-6.4.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0d2b9712211b860d123815a80b859075d86a4d54787e247d7fbee9db6832cf1c", size = 40183, upload-time = "2025-05-19T14:15:40.197Z" },
-    { url = "https://files.pythonhosted.org/packages/e0/b7/d4b8d98eb850ef28a4922ba508c31d90715fd9b9da3801a30cea2967130b/multidict-6.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d2fa86af59f8fc1972e121ade052145f6da22758f6996a197d69bb52f8204e7e", size = 39592, upload-time = "2025-05-19T14:15:41.508Z" },
-    { url = "https://files.pythonhosted.org/packages/18/28/a554678898a19583548e742080cf55d169733baf57efc48c2f0273a08583/multidict-6.4.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50855d03e9e4d66eab6947ba688ffb714616f985838077bc4b490e769e48da51", size = 226071, upload-time = "2025-05-19T14:15:42.877Z" },
-    { url = "https://files.pythonhosted.org/packages/ee/dc/7ba6c789d05c310e294f85329efac1bf5b450338d2542498db1491a264df/multidict-6.4.4-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5bce06b83be23225be1905dcdb6b789064fae92499fbc458f59a8c0e68718601", size = 222597, upload-time = "2025-05-19T14:15:44.412Z" },
-    { url = "https://files.pythonhosted.org/packages/24/4f/34eadbbf401b03768dba439be0fb94b0d187facae9142821a3d5599ccb3b/multidict-6.4.4-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66ed0731f8e5dfd8369a883b6e564aca085fb9289aacabd9decd70568b9a30de", size = 228253, upload-time = "2025-05-19T14:15:46.474Z" },
-    { url = "https://files.pythonhosted.org/packages/c0/e6/493225a3cdb0d8d80d43a94503fc313536a07dae54a3f030d279e629a2bc/multidict-6.4.4-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:329ae97fc2f56f44d91bc47fe0972b1f52d21c4b7a2ac97040da02577e2daca2", size = 226146, upload-time = "2025-05-19T14:15:48.003Z" },
-    { url = "https://files.pythonhosted.org/packages/2f/70/e411a7254dc3bff6f7e6e004303b1b0591358e9f0b7c08639941e0de8bd6/multidict-6.4.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c27e5dcf520923d6474d98b96749e6805f7677e93aaaf62656005b8643f907ab", size = 220585, upload-time = "2025-05-19T14:15:49.546Z" },
-    { url = "https://files.pythonhosted.org/packages/08/8f/beb3ae7406a619100d2b1fb0022c3bb55a8225ab53c5663648ba50dfcd56/multidict-6.4.4-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:058cc59b9e9b143cc56715e59e22941a5d868c322242278d28123a5d09cdf6b0", size = 212080, upload-time = "2025-05-19T14:15:51.151Z" },
-    { url = "https://files.pythonhosted.org/packages/9c/ec/355124e9d3d01cf8edb072fd14947220f357e1c5bc79c88dff89297e9342/multidict-6.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:69133376bc9a03f8c47343d33f91f74a99c339e8b58cea90433d8e24bb298031", size = 226558, upload-time = "2025-05-19T14:15:52.665Z" },
-    { url = "https://files.pythonhosted.org/packages/fd/22/d2b95cbebbc2ada3be3812ea9287dcc9712d7f1a012fad041770afddb2ad/multidict-6.4.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:d6b15c55721b1b115c5ba178c77104123745b1417527ad9641a4c5e2047450f0", size = 212168, upload-time = "2025-05-19T14:15:55.279Z" },
-    { url = "https://files.pythonhosted.org/packages/4d/c5/62bfc0b2f9ce88326dbe7179f9824a939c6c7775b23b95de777267b9725c/multidict-6.4.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a887b77f51d3d41e6e1a63cf3bc7ddf24de5939d9ff69441387dfefa58ac2e26", size = 217970, upload-time = "2025-05-19T14:15:56.806Z" },
-    { url = "https://files.pythonhosted.org/packages/79/74/977cea1aadc43ff1c75d23bd5bc4768a8fac98c14e5878d6ee8d6bab743c/multidict-6.4.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:632a3bf8f1787f7ef7d3c2f68a7bde5be2f702906f8b5842ad6da9d974d0aab3", size = 226980, upload-time = "2025-05-19T14:15:58.313Z" },
-    { url = "https://files.pythonhosted.org/packages/48/fc/cc4a1a2049df2eb84006607dc428ff237af38e0fcecfdb8a29ca47b1566c/multidict-6.4.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a145c550900deb7540973c5cdb183b0d24bed6b80bf7bddf33ed8f569082535e", size = 220641, upload-time = "2025-05-19T14:15:59.866Z" },
-    { url = "https://files.pythonhosted.org/packages/3b/6a/a7444d113ab918701988d4abdde373dbdfd2def7bd647207e2bf645c7eac/multidict-6.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cc5d83c6619ca5c9672cb78b39ed8542f1975a803dee2cda114ff73cbb076edd", size = 221728, upload-time = "2025-05-19T14:16:01.535Z" },
-    { url = "https://files.pythonhosted.org/packages/2b/b0/fdf4c73ad1c55e0f4dbbf2aa59dd37037334091f9a4961646d2b7ac91a86/multidict-6.4.4-cp313-cp313t-win32.whl", hash = "sha256:3312f63261b9df49be9d57aaa6abf53a6ad96d93b24f9cc16cf979956355ce6e", size = 41913, upload-time = "2025-05-19T14:16:03.199Z" },
-    { url = "https://files.pythonhosted.org/packages/8e/92/27989ecca97e542c0d01d05a98a5ae12198a243a9ee12563a0313291511f/multidict-6.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:ba852168d814b2c73333073e1c7116d9395bea69575a01b0b3c89d2d5a87c8fb", size = 46112, upload-time = "2025-05-19T14:16:04.909Z" },
-    { url = "https://files.pythonhosted.org/packages/18/5c/92607a79e7fd0361c90b3c5d79bbd186e3968e8a4832dbefcd7808f1c823/multidict-6.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:603f39bd1cf85705c6c1ba59644b480dfe495e6ee2b877908de93322705ad7cf", size = 66007, upload-time = "2025-05-19T14:16:06.25Z" },
-    { url = "https://files.pythonhosted.org/packages/32/1e/212a154926a9290d8ae432e761d1c98ed95fccce84b1b938eaf1bf17378e/multidict-6.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fc60f91c02e11dfbe3ff4e1219c085695c339af72d1641800fe6075b91850c8f", size = 38824, upload-time = "2025-05-19T14:16:07.61Z" },
-    { url = "https://files.pythonhosted.org/packages/8b/64/5ca6fb5dbc7d5aa352cd2d013c86ae44133c3f4f6b83a80dacd42ee5c568/multidict-6.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:496bcf01c76a70a31c3d746fd39383aad8d685ce6331e4c709e9af4ced5fa221", size = 38117, upload-time = "2025-05-19T14:16:08.966Z" },
-    { url = "https://files.pythonhosted.org/packages/aa/20/3aee7910260e7b6f0045b6f48b97ebf041de0cab513c12f87cf6e4e514d3/multidict-6.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4219390fb5bf8e548e77b428bb36a21d9382960db5321b74d9d9987148074d6b", size = 218106, upload-time = "2025-05-19T14:16:10.962Z" },
-    { url = "https://files.pythonhosted.org/packages/a9/79/15f5a65b8de8ae8f3c5da1591a322620675e4fec8d39995b04101d2b2e2c/multidict-6.4.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef4e9096ff86dfdcbd4a78253090ba13b1d183daa11b973e842465d94ae1772", size = 213817, upload-time = "2025-05-19T14:16:12.486Z" },
-    { url = "https://files.pythonhosted.org/packages/ab/a7/90de36db90ce2936fbb1639ca51508965861a8ad5dc2947531d18f3363b9/multidict-6.4.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49a29d7133b1fc214e818bbe025a77cc6025ed9a4f407d2850373ddde07fd04a", size = 228133, upload-time = "2025-05-19T14:16:14.48Z" },
-    { url = "https://files.pythonhosted.org/packages/df/25/5fcd66fda3c8b7d6d6f658a871017791c46824e965dfa20a4c46d4167ad4/multidict-6.4.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e32053d6d3a8b0dfe49fde05b496731a0e6099a4df92154641c00aa76786aef5", size = 224271, upload-time = "2025-05-19T14:16:16.314Z" },
-    { url = "https://files.pythonhosted.org/packages/fd/9a/1011812091fd99b2dddd9d2dbde4b7d69bbf8070e0291fe49c3bb40c2d55/multidict-6.4.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cc403092a49509e8ef2d2fd636a8ecefc4698cc57bbe894606b14579bc2a955", size = 216448, upload-time = "2025-05-19T14:16:18.263Z" },
-    { url = "https://files.pythonhosted.org/packages/cf/cc/916e066b7e2686999f95dde87f588be26fa1c2f05e70d9fd472fe2289c0b/multidict-6.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5363f9b2a7f3910e5c87d8b1855c478c05a2dc559ac57308117424dfaad6805c", size = 210080, upload-time = "2025-05-19T14:16:20.326Z" },
-    { url = "https://files.pythonhosted.org/packages/f8/ff/15034b18f2e4179cd559aa13bc3b376a95c22e1fd7c3b88884e078ad5466/multidict-6.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2e543a40e4946cf70a88a3be87837a3ae0aebd9058ba49e91cacb0b2cd631e2b", size = 221926, upload-time = "2025-05-19T14:16:22.227Z" },
-    { url = "https://files.pythonhosted.org/packages/17/43/4243298a6b0b869a83b6331f3fcc12a2a0544c0995292ee96badf0fec6aa/multidict-6.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:60d849912350da557fe7de20aa8cf394aada6980d0052cc829eeda4a0db1c1db", size = 211318, upload-time = "2025-05-19T14:16:23.914Z" },
-    { url = "https://files.pythonhosted.org/packages/fe/80/bc43c87d60138e401c7d1818a47e5a0f748904c9f3be99012cdab5e31446/multidict-6.4.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:19d08b4f22eae45bb018b9f06e2838c1e4b853c67628ef8ae126d99de0da6395", size = 217611, upload-time = "2025-05-19T14:16:25.647Z" },
-    { url = "https://files.pythonhosted.org/packages/1e/5d/2ec94209254e48910911ac2404d71b37f06fd97ec83948a92d0c87a11d3c/multidict-6.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d693307856d1ef08041e8b6ff01d5b4618715007d288490ce2c7e29013c12b9a", size = 227893, upload-time = "2025-05-19T14:16:27.721Z" },
-    { url = "https://files.pythonhosted.org/packages/71/83/89344adc0cf08fd89d82d43de1a17a2635b03a57dfa680f6cdf2a24d481f/multidict-6.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fad6daaed41021934917f4fb03ca2db8d8a4d79bf89b17ebe77228eb6710c003", size = 221956, upload-time = "2025-05-19T14:16:29.307Z" },
-    { url = "https://files.pythonhosted.org/packages/f0/ea/81382bb59cd3a1047d1c2ea9339d2107fc918a63491bbb9399eb1aceda91/multidict-6.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c10d17371bff801af0daf8b073c30b6cf14215784dc08cd5c43ab5b7b8029bbc", size = 216850, upload-time = "2025-05-19T14:16:30.913Z" },
-    { url = "https://files.pythonhosted.org/packages/0f/90/c848d62de66c2958932ce155adae418cbf79d96cfaf992e5255819f8f1d9/multidict-6.4.4-cp39-cp39-win32.whl", hash = "sha256:7e23f2f841fcb3ebd4724a40032d32e0892fbba4143e43d2a9e7695c5e50e6bd", size = 35235, upload-time = "2025-05-19T14:16:32.85Z" },
-    { url = "https://files.pythonhosted.org/packages/d4/19/dd625207c92889c1ae7b89fcbde760d99853265cfe7ffb0826393151acd1/multidict-6.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:4d7b50b673ffb4ff4366e7ab43cf1f0aef4bd3608735c5fbdf0bdb6f690da411", size = 38821, upload-time = "2025-05-19T14:16:34.288Z" },
-    { url = "https://files.pythonhosted.org/packages/84/5d/e17845bb0fa76334477d5de38654d27946d5b5d3695443987a094a71b440/multidict-6.4.4-py3-none-any.whl", hash = "sha256:bd4557071b561a8b3b6075c3ce93cf9bfb6182cb241805c3d66ced3b75eff4ac", size = 10481, upload-time = "2025-05-19T14:16:36.024Z" },
+sdist = { url = "https://files.pythonhosted.org/packages/46/b5/59f27b4ce9951a4bce56b88ba5ff5159486797ab18863f2b4c1c5e8465bd/multidict-6.5.0.tar.gz", hash = "sha256:942bd8002492ba819426a8d7aefde3189c1b87099cdf18aaaefefcf7f3f7b6d2", size = 98512, upload-time = "2025-06-17T14:15:56.556Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/8b/88/f8354ef1cb1121234c3461ff3d11eac5f4fe115f00552d3376306275c9ab/multidict-6.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2e118a202904623b1d2606d1c8614e14c9444b59d64454b0c355044058066469", size = 73858, upload-time = "2025-06-17T14:13:21.451Z" },
+    { url = "https://files.pythonhosted.org/packages/49/04/634b49c7abe71bd1c61affaeaa0c2a46b6be8d599a07b495259615dbdfe0/multidict-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a42995bdcaff4e22cb1280ae7752c3ed3fbb398090c6991a2797a4a0e5ed16a9", size = 43186, upload-time = "2025-06-17T14:13:23.615Z" },
+    { url = "https://files.pythonhosted.org/packages/3b/ff/091ff4830ec8f96378578bfffa7f324a9dd16f60274cec861ae65ba10be3/multidict-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2261b538145723ca776e55208640fffd7ee78184d223f37c2b40b9edfe0e818a", size = 43031, upload-time = "2025-06-17T14:13:24.725Z" },
+    { url = "https://files.pythonhosted.org/packages/10/c1/1b4137845f8b8dbc2332af54e2d7761c6a29c2c33c8d47a0c8c70676bac1/multidict-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e5b19f8cd67235fab3e195ca389490415d9fef5a315b1fa6f332925dc924262", size = 233588, upload-time = "2025-06-17T14:13:26.181Z" },
+    { url = "https://files.pythonhosted.org/packages/c3/77/cbe9a1f58c6d4f822663788e414637f256a872bc352cedbaf7717b62db58/multidict-6.5.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:177b081e4dec67c3320b16b3aa0babc178bbf758553085669382c7ec711e1ec8", size = 222714, upload-time = "2025-06-17T14:13:27.482Z" },
+    { url = "https://files.pythonhosted.org/packages/6c/37/39e1142c2916973818515adc13bbdb68d3d8126935e3855200e059a79bab/multidict-6.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d30a2cc106a7d116b52ee046207614db42380b62e6b1dd2a50eba47c5ca5eb1", size = 242741, upload-time = "2025-06-17T14:13:28.92Z" },
+    { url = "https://files.pythonhosted.org/packages/a3/aa/60c3ef0c87ccad3445bf01926a1b8235ee24c3dde483faef1079cc91706d/multidict-6.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a72933bc308d7a64de37f0d51795dbeaceebdfb75454f89035cdfc6a74cfd129", size = 235008, upload-time = "2025-06-17T14:13:30.587Z" },
+    { url = "https://files.pythonhosted.org/packages/bf/5e/f7e0fd5f5b8a7b9a75b0f5642ca6b6dde90116266920d8cf63b513f3908b/multidict-6.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96d109e663d032280ef8ef62b50924b2e887d5ddf19e301844a6cb7e91a172a6", size = 226627, upload-time = "2025-06-17T14:13:31.831Z" },
+    { url = "https://files.pythonhosted.org/packages/b7/74/1bc0a3c6a9105051f68a6991fe235d7358836e81058728c24d5bbdd017cb/multidict-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b555329c9894332401f03b9a87016f0b707b6fccd4706793ec43b4a639e75869", size = 228232, upload-time = "2025-06-17T14:13:33.402Z" },
+    { url = "https://files.pythonhosted.org/packages/99/e7/37118291cdc31f4cc680d54047cdea9b520e9a724a643919f71f8c2a2aeb/multidict-6.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6994bad9d471ef2156f2b6850b51e20ee409c6b9deebc0e57be096be9faffdce", size = 246616, upload-time = "2025-06-17T14:13:34.964Z" },
+    { url = "https://files.pythonhosted.org/packages/ff/89/e2c08d6bdb21a1a55be4285510d058ace5f5acabe6b57900432e863d4c70/multidict-6.5.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:b15f817276c96cde9060569023808eec966bd8da56a97e6aa8116f34ddab6534", size = 235007, upload-time = "2025-06-17T14:13:36.428Z" },
+    { url = "https://files.pythonhosted.org/packages/89/1e/e39a98e8e1477ec7a871b3c17265658fbe6d617048059ae7fa5011b224f3/multidict-6.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b4bf507c991db535a935b2127cf057a58dbc688c9f309c72080795c63e796f58", size = 244824, upload-time = "2025-06-17T14:13:37.982Z" },
+    { url = "https://files.pythonhosted.org/packages/a3/ba/63e11edd45c31e708c5a1904aa7ac4de01e13135a04cfe96bc71eb359b85/multidict-6.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:60c3f8f13d443426c55f88cf3172547bbc600a86d57fd565458b9259239a6737", size = 257229, upload-time = "2025-06-17T14:13:39.554Z" },
+    { url = "https://files.pythonhosted.org/packages/0f/00/bdcceb6af424936adfc8b92a79d3a95863585f380071393934f10a63f9e3/multidict-6.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a10227168a24420c158747fc201d4279aa9af1671f287371597e2b4f2ff21879", size = 247118, upload-time = "2025-06-17T14:13:40.795Z" },
+    { url = "https://files.pythonhosted.org/packages/b6/a0/4aa79e991909cca36ca821a9ba5e8e81e4cd5b887c81f89ded994e0f49df/multidict-6.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e3b1425fe54ccfde66b8cfb25d02be34d5dfd2261a71561ffd887ef4088b4b69", size = 243948, upload-time = "2025-06-17T14:13:42.477Z" },
+    { url = "https://files.pythonhosted.org/packages/21/8b/e45e19ce43afb31ff6b0fd5d5816b4fcc1fcc2f37e8a82aefae06c40c7a6/multidict-6.5.0-cp310-cp310-win32.whl", hash = "sha256:b4e47ef51237841d1087e1e1548071a6ef22e27ed0400c272174fa585277c4b4", size = 40433, upload-time = "2025-06-17T14:13:43.972Z" },
+    { url = "https://files.pythonhosted.org/packages/d2/6e/96e0ba4601343d9344e69503fca072ace19c35f7d4ca3d68401e59acdc8f/multidict-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:63b3b24fadc7067282c88fae5b2f366d5b3a7c15c021c2838de8c65a50eeefb4", size = 44423, upload-time = "2025-06-17T14:13:44.991Z" },
+    { url = "https://files.pythonhosted.org/packages/eb/4a/9befa919d7a390f13a5511a69282b7437782071160c566de6e0ebf712c9f/multidict-6.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:8b2d61afbafc679b7eaf08e9de4fa5d38bd5dc7a9c0a577c9f9588fb49f02dbb", size = 41481, upload-time = "2025-06-17T14:13:49.389Z" },
+    { url = "https://files.pythonhosted.org/packages/75/ba/484f8e96ee58ec4fef42650eb9dbbedb24f9bc155780888398a4725d2270/multidict-6.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8b4bf6bb15a05796a07a248084e3e46e032860c899c7a9b981030e61368dba95", size = 73283, upload-time = "2025-06-17T14:13:50.406Z" },
+    { url = "https://files.pythonhosted.org/packages/71/48/01d62ea6199d76934c87746695b3ed16aeedfdd564e8d89184577037baac/multidict-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46bb05d50219655c42a4b8fcda9c7ee658a09adbb719c48e65a20284e36328ea", size = 42937, upload-time = "2025-06-17T14:13:51.45Z" },
+    { url = "https://files.pythonhosted.org/packages/da/cf/bb462d920f26d9e2e0aff8a78aeb06af1225b826e9a5468870c57591910a/multidict-6.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:54f524d73f4d54e87e03c98f6af601af4777e4668a52b1bd2ae0a4d6fc7b392b", size = 42748, upload-time = "2025-06-17T14:13:52.505Z" },
+    { url = "https://files.pythonhosted.org/packages/cd/b1/d5c11ea0fdad68d3ed45f0e2527de6496d2fac8afe6b8ca6d407c20ad00f/multidict-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:529b03600466480ecc502000d62e54f185a884ed4570dee90d9a273ee80e37b5", size = 236448, upload-time = "2025-06-17T14:13:53.562Z" },
+    { url = "https://files.pythonhosted.org/packages/fc/69/c3ceb264994f5b338c812911a8d660084f37779daef298fc30bd817f75c7/multidict-6.5.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:69ad681ad7c93a41ee7005cc83a144b5b34a3838bcf7261e2b5356057b0f78de", size = 228695, upload-time = "2025-06-17T14:13:54.775Z" },
+    { url = "https://files.pythonhosted.org/packages/81/3d/c23dcc0d34a35ad29974184db2878021d28fe170ecb9192be6bfee73f1f2/multidict-6.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fe9fada8bc0839466b09fa3f6894f003137942984843ec0c3848846329a36ae", size = 247434, upload-time = "2025-06-17T14:13:56.039Z" },
+    { url = "https://files.pythonhosted.org/packages/06/b3/06cf7a049129ff52525a859277abb5648e61d7afae7fb7ed02e3806be34e/multidict-6.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f94c6ea6405fcf81baef1e459b209a78cda5442e61b5b7a57ede39d99b5204a0", size = 239431, upload-time = "2025-06-17T14:13:57.33Z" },
+    { url = "https://files.pythonhosted.org/packages/8a/72/b2fe2fafa23af0c6123aebe23b4cd23fdad01dfe7009bb85624e4636d0dd/multidict-6.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84ca75ad8a39ed75f079a8931435a5b51ee4c45d9b32e1740f99969a5d1cc2ee", size = 231542, upload-time = "2025-06-17T14:13:58.597Z" },
+    { url = "https://files.pythonhosted.org/packages/a1/c9/a52ca0a342a02411a31b6af197a6428a5137d805293f10946eeab614ec06/multidict-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be4c08f3a2a6cc42b414496017928d95898964fed84b1b2dace0c9ee763061f9", size = 233069, upload-time = "2025-06-17T14:13:59.834Z" },
+    { url = "https://files.pythonhosted.org/packages/9b/55/a3328a3929b8e131e2678d5e65f552b0a6874fab62123e31f5a5625650b0/multidict-6.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:046a7540cfbb4d5dc846a1fd9843f3ba980c6523f2e0c5b8622b4a5c94138ae6", size = 250596, upload-time = "2025-06-17T14:14:01.178Z" },
+    { url = "https://files.pythonhosted.org/packages/6c/b8/aa3905a38a8287013aeb0a54c73f79ccd8b32d2f1d53e5934643a36502c2/multidict-6.5.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:64306121171d988af77d74be0d8c73ee1a69cf6f96aea7fa6030c88f32a152dd", size = 237858, upload-time = "2025-06-17T14:14:03.232Z" },
+    { url = "https://files.pythonhosted.org/packages/d3/eb/f11d5af028014f402e5dd01ece74533964fa4e7bfae4af4824506fa8c398/multidict-6.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b4ac1dd5eb0ecf6f7351d5a9137f30a83f7182209c5d37f61614dfdce5714853", size = 249175, upload-time = "2025-06-17T14:14:04.561Z" },
+    { url = "https://files.pythonhosted.org/packages/ac/57/d451905a62e5ef489cb4f92e8190d34ac5329427512afd7f893121da4e96/multidict-6.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bab4a8337235365f4111a7011a1f028826ca683834ebd12de4b85e2844359c36", size = 259532, upload-time = "2025-06-17T14:14:05.798Z" },
+    { url = "https://files.pythonhosted.org/packages/d3/90/ff82b5ac5cabe3c79c50cf62a62f3837905aa717e67b6b4b7872804f23c8/multidict-6.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a05b5604c5a75df14a63eeeca598d11b2c3745b9008539b70826ea044063a572", size = 250554, upload-time = "2025-06-17T14:14:07.382Z" },
+    { url = "https://files.pythonhosted.org/packages/d5/5a/0cabc50d4bc16e61d8b0a8a74499a1409fa7b4ef32970b7662a423781fc7/multidict-6.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:67c4a640952371c9ca65b6a710598be246ef3be5ca83ed38c16a7660d3980877", size = 248159, upload-time = "2025-06-17T14:14:08.65Z" },
+    { url = "https://files.pythonhosted.org/packages/c0/1d/adeabae0771544f140d9f42ab2c46eaf54e793325999c36106078b7f6600/multidict-6.5.0-cp311-cp311-win32.whl", hash = "sha256:fdeae096ca36c12d8aca2640b8407a9d94e961372c68435bef14e31cce726138", size = 40357, upload-time = "2025-06-17T14:14:09.91Z" },
+    { url = "https://files.pythonhosted.org/packages/e1/fe/bbd85ae65c96de5c9910c332ee1f4b7be0bf0fb21563895167bcb6502a1f/multidict-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:e2977ef8b7ce27723ee8c610d1bd1765da4f3fbe5a64f9bf1fd3b4770e31fbc0", size = 44432, upload-time = "2025-06-17T14:14:11.013Z" },
+    { url = "https://files.pythonhosted.org/packages/96/af/f9052d9c4e65195b210da9f7afdea06d3b7592b3221cc0ef1b407f762faa/multidict-6.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:82d0cf0ea49bae43d9e8c3851e21954eff716259ff42da401b668744d1760bcb", size = 41408, upload-time = "2025-06-17T14:14:12.112Z" },
+    { url = "https://files.pythonhosted.org/packages/0a/fa/18f4950e00924f7e84c8195f4fc303295e14df23f713d64e778b8fa8b903/multidict-6.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1bb986c8ea9d49947bc325c51eced1ada6d8d9b4c5b15fd3fcdc3c93edef5a74", size = 73474, upload-time = "2025-06-17T14:14:13.528Z" },
+    { url = "https://files.pythonhosted.org/packages/6c/66/0392a2a8948bccff57e4793c9dde3e5c088f01e8b7f8867ee58a2f187fc5/multidict-6.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:03c0923da300120830fc467e23805d63bbb4e98b94032bd863bc7797ea5fa653", size = 43741, upload-time = "2025-06-17T14:14:15.188Z" },
+    { url = "https://files.pythonhosted.org/packages/98/3e/f48487c91b2a070566cfbab876d7e1ebe7deb0a8002e4e896a97998ae066/multidict-6.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4c78d5ec00fdd35c91680ab5cf58368faad4bd1a8721f87127326270248de9bc", size = 42143, upload-time = "2025-06-17T14:14:16.612Z" },
+    { url = "https://files.pythonhosted.org/packages/3f/49/439c6cc1cd00365cf561bdd3579cc3fa1a0d38effb3a59b8d9562839197f/multidict-6.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aadc3cb78be90a887f8f6b73945b840da44b4a483d1c9750459ae69687940c97", size = 239303, upload-time = "2025-06-17T14:14:17.707Z" },
+    { url = "https://files.pythonhosted.org/packages/c4/24/491786269e90081cb536e4d7429508725bc92ece176d1204a4449de7c41c/multidict-6.5.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5b02e1ca495d71e07e652e4cef91adae3bf7ae4493507a263f56e617de65dafc", size = 236913, upload-time = "2025-06-17T14:14:18.981Z" },
+    { url = "https://files.pythonhosted.org/packages/e8/76/bbe2558b820ebeca8a317ab034541790e8160ca4b1e450415383ac69b339/multidict-6.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7fe92a62326eef351668eec4e2dfc494927764a0840a1895cff16707fceffcd3", size = 250752, upload-time = "2025-06-17T14:14:20.297Z" },
+    { url = "https://files.pythonhosted.org/packages/3e/e3/3977f2c1123f553ceff9f53cd4de04be2c1912333c6fabbcd51531655476/multidict-6.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7673ee4f63879ecd526488deb1989041abcb101b2d30a9165e1e90c489f3f7fb", size = 243937, upload-time = "2025-06-17T14:14:21.935Z" },
+    { url = "https://files.pythonhosted.org/packages/b6/b8/7a6e9c13c79709cdd2f22ee849f058e6da76892d141a67acc0e6c30d845c/multidict-6.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa097ae2a29f573de7e2d86620cbdda5676d27772d4ed2669cfa9961a0d73955", size = 237419, upload-time = "2025-06-17T14:14:23.215Z" },
+    { url = "https://files.pythonhosted.org/packages/84/9d/8557f5e88da71bc7e7a8ace1ada4c28197f3bfdc2dd6e51d3b88f2e16e8e/multidict-6.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:300da0fa4f8457d9c4bd579695496116563409e676ac79b5e4dca18e49d1c308", size = 237222, upload-time = "2025-06-17T14:14:24.516Z" },
+    { url = "https://files.pythonhosted.org/packages/a3/3b/8f023ad60e7969cb6bc0683738d0e1618f5ff5723d6d2d7818dc6df6ad3d/multidict-6.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9a19bd108c35877b57393243d392d024cfbfdefe759fd137abb98f6fc910b64c", size = 247861, upload-time = "2025-06-17T14:14:25.839Z" },
+    { url = "https://files.pythonhosted.org/packages/af/1c/9cf5a099ce7e3189906cf5daa72c44ee962dcb4c1983659f3a6f8a7446ab/multidict-6.5.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f32a1777465a35c35ddbbd7fc1293077938a69402fcc59e40b2846d04a120dd", size = 243917, upload-time = "2025-06-17T14:14:27.164Z" },
+    { url = "https://files.pythonhosted.org/packages/6c/bb/88ee66ebeef56868044bac58feb1cc25658bff27b20e3cfc464edc181287/multidict-6.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9cc1e10c14ce8112d1e6d8971fe3cdbe13e314f68bea0e727429249d4a6ce164", size = 249214, upload-time = "2025-06-17T14:14:28.795Z" },
+    { url = "https://files.pythonhosted.org/packages/3e/ec/a90e88cc4a1309f33088ab1cdd5c0487718f49dfb82c5ffc845bb17c1973/multidict-6.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e95c5e07a06594bdc288117ca90e89156aee8cb2d7c330b920d9c3dd19c05414", size = 258682, upload-time = "2025-06-17T14:14:30.066Z" },
+    { url = "https://files.pythonhosted.org/packages/d2/d8/16dd69a6811920a31f4e06114ebe67b1cd922c8b05c9c82b050706d0b6fe/multidict-6.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:40ff26f58323795f5cd2855e2718a1720a1123fb90df4553426f0efd76135462", size = 254254, upload-time = "2025-06-17T14:14:31.323Z" },
+    { url = "https://files.pythonhosted.org/packages/ac/a8/90193a5f5ca1bdbf92633d69a25a2ef9bcac7b412b8d48c84d01a2732518/multidict-6.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:76803a29fd71869a8b59c2118c9dcfb3b8f9c8723e2cce6baeb20705459505cf", size = 247741, upload-time = "2025-06-17T14:14:32.717Z" },
+    { url = "https://files.pythonhosted.org/packages/cd/43/29c7a747153c05b41d1f67455426af39ed88d6de3f21c232b8f2724bde13/multidict-6.5.0-cp312-cp312-win32.whl", hash = "sha256:df7ecbc65a53a2ce1b3a0c82e6ad1a43dcfe7c6137733f9176a92516b9f5b851", size = 41049, upload-time = "2025-06-17T14:14:33.941Z" },
+    { url = "https://files.pythonhosted.org/packages/1e/e8/8f3fc32b7e901f3a2719764d64aeaf6ae77b4ba961f1c3a3cf3867766636/multidict-6.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ec1c3fbbb0b655a6540bce408f48b9a7474fd94ed657dcd2e890671fefa7743", size = 44700, upload-time = "2025-06-17T14:14:35.016Z" },
+    { url = "https://files.pythonhosted.org/packages/24/e4/e250806adc98d524d41e69c8d4a42bc3513464adb88cb96224df12928617/multidict-6.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:2d24a00d34808b22c1f15902899b9d82d0faeca9f56281641c791d8605eacd35", size = 41703, upload-time = "2025-06-17T14:14:36.168Z" },
+    { url = "https://files.pythonhosted.org/packages/1a/c9/092c4e9402b6d16de761cff88cb842a5c8cc50ccecaf9c4481ba53264b9e/multidict-6.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:53d92df1752df67a928fa7f884aa51edae6f1cf00eeb38cbcf318cf841c17456", size = 73486, upload-time = "2025-06-17T14:14:37.238Z" },
+    { url = "https://files.pythonhosted.org/packages/08/f9/6f7ddb8213f5fdf4db48d1d640b78e8aef89b63a5de8a2313286db709250/multidict-6.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:680210de2c38eef17ce46b8df8bf2c1ece489261a14a6e43c997d49843a27c99", size = 43745, upload-time = "2025-06-17T14:14:38.32Z" },
+    { url = "https://files.pythonhosted.org/packages/f3/a7/b9be0163bfeee3bb08a77a1705e24eb7e651d594ea554107fac8a1ca6a4d/multidict-6.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e279259bcb936732bfa1a8eec82b5d2352b3df69d2fa90d25808cfc403cee90a", size = 42135, upload-time = "2025-06-17T14:14:39.897Z" },
+    { url = "https://files.pythonhosted.org/packages/8e/30/93c8203f943a417bda3c573a34d5db0cf733afdfffb0ca78545c7716dbd8/multidict-6.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1c185fc1069781e3fc8b622c4331fb3b433979850392daa5efbb97f7f9959bb", size = 238585, upload-time = "2025-06-17T14:14:41.332Z" },
+    { url = "https://files.pythonhosted.org/packages/9d/fe/2582b56a1807604774f566eeef183b0d6b148f4b89d1612cd077567b2e1e/multidict-6.5.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6bb5f65ff91daf19ce97f48f63585e51595539a8a523258b34f7cef2ec7e0617", size = 236174, upload-time = "2025-06-17T14:14:42.602Z" },
+    { url = "https://files.pythonhosted.org/packages/9b/c4/d8b66d42d385bd4f974cbd1eaa8b265e6b8d297249009f312081d5ded5c7/multidict-6.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8646b4259450c59b9286db280dd57745897897284f6308edbdf437166d93855", size = 250145, upload-time = "2025-06-17T14:14:43.944Z" },
+    { url = "https://files.pythonhosted.org/packages/bc/64/62feda5093ee852426aae3df86fab079f8bf1cdbe403e1078c94672ad3ec/multidict-6.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d245973d4ecc04eea0a8e5ebec7882cf515480036e1b48e65dffcfbdf86d00be", size = 243470, upload-time = "2025-06-17T14:14:45.343Z" },
+    { url = "https://files.pythonhosted.org/packages/67/dc/9f6fa6e854625cf289c0e9f4464b40212a01f76b2f3edfe89b6779b4fb93/multidict-6.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a133e7ddc9bc7fb053733d0ff697ce78c7bf39b5aec4ac12857b6116324c8d75", size = 236968, upload-time = "2025-06-17T14:14:46.609Z" },
+    { url = "https://files.pythonhosted.org/packages/46/ae/4b81c6e3745faee81a156f3f87402315bdccf04236f75c03e37be19c94ff/multidict-6.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80d696fa38d738fcebfd53eec4d2e3aeb86a67679fd5e53c325756682f152826", size = 236575, upload-time = "2025-06-17T14:14:47.929Z" },
+    { url = "https://files.pythonhosted.org/packages/8a/fa/4089d7642ea344226e1bfab60dd588761d4791754f8072e911836a39bedf/multidict-6.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:20d30c9410ac3908abbaa52ee5967a754c62142043cf2ba091e39681bd51d21a", size = 247632, upload-time = "2025-06-17T14:14:49.525Z" },
+    { url = "https://files.pythonhosted.org/packages/16/ee/a353dac797de0f28fb7f078cc181c5f2eefe8dd16aa11a7100cbdc234037/multidict-6.5.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6c65068cc026f217e815fa519d8e959a7188e94ec163ffa029c94ca3ef9d4a73", size = 243520, upload-time = "2025-06-17T14:14:50.83Z" },
+    { url = "https://files.pythonhosted.org/packages/50/ec/560deb3d2d95822d6eb1bcb1f1cb728f8f0197ec25be7c936d5d6a5d133c/multidict-6.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e355ac668a8c3e49c2ca8daa4c92f0ad5b705d26da3d5af6f7d971e46c096da7", size = 248551, upload-time = "2025-06-17T14:14:52.229Z" },
+    { url = "https://files.pythonhosted.org/packages/10/85/ddf277e67c78205f6695f2a7639be459bca9cc353b962fd8085a492a262f/multidict-6.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:08db204213d0375a91a381cae0677ab95dd8c67a465eb370549daf6dbbf8ba10", size = 258362, upload-time = "2025-06-17T14:14:53.934Z" },
+    { url = "https://files.pythonhosted.org/packages/02/fc/d64ee1df9b87c5210f2d4c419cab07f28589c81b4e5711eda05a122d0614/multidict-6.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ffa58e3e215af8f6536dc837a990e456129857bb6fd546b3991be470abd9597a", size = 253862, upload-time = "2025-06-17T14:14:55.323Z" },
+    { url = "https://files.pythonhosted.org/packages/c9/7c/a2743c00d9e25f4826d3a77cc13d4746398872cf21c843eef96bb9945665/multidict-6.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3e86eb90015c6f21658dbd257bb8e6aa18bdb365b92dd1fba27ec04e58cdc31b", size = 247391, upload-time = "2025-06-17T14:14:57.293Z" },
+    { url = "https://files.pythonhosted.org/packages/9b/03/7773518db74c442904dbd349074f1e7f2a854cee4d9529fc59e623d3949e/multidict-6.5.0-cp313-cp313-win32.whl", hash = "sha256:f34a90fbd9959d0f857323bd3c52b3e6011ed48f78d7d7b9e04980b8a41da3af", size = 41115, upload-time = "2025-06-17T14:14:59.33Z" },
+    { url = "https://files.pythonhosted.org/packages/eb/9a/6fc51b1dc11a7baa944bc101a92167d8b0f5929d376a8c65168fc0d35917/multidict-6.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:fcb2aa79ac6aef8d5b709bbfc2fdb1d75210ba43038d70fbb595b35af470ce06", size = 44768, upload-time = "2025-06-17T14:15:00.427Z" },
+    { url = "https://files.pythonhosted.org/packages/82/2d/0d010be24b663b3c16e3d3307bbba2de5ae8eec496f6027d5c0515b371a8/multidict-6.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:6dcee5e7e92060b4bb9bb6f01efcbb78c13d0e17d9bc6eec71660dd71dc7b0c2", size = 41770, upload-time = "2025-06-17T14:15:01.854Z" },
+    { url = "https://files.pythonhosted.org/packages/aa/d1/a71711a5f32f84b7b036e82182e3250b949a0ce70d51a2c6a4079e665449/multidict-6.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:cbbc88abea2388fde41dd574159dec2cda005cb61aa84950828610cb5010f21a", size = 80450, upload-time = "2025-06-17T14:15:02.968Z" },
+    { url = "https://files.pythonhosted.org/packages/0f/a2/953a9eede63a98fcec2c1a2c1a0d88de120056219931013b871884f51b43/multidict-6.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:70b599f70ae6536e5976364d3c3cf36f40334708bd6cebdd1e2438395d5e7676", size = 46971, upload-time = "2025-06-17T14:15:04.149Z" },
+    { url = "https://files.pythonhosted.org/packages/44/61/60250212953459edda2c729e1d85130912f23c67bd4f585546fe4bdb1578/multidict-6.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:828bab777aa8d29d59700018178061854e3a47727e0611cb9bec579d3882de3b", size = 45548, upload-time = "2025-06-17T14:15:05.666Z" },
+    { url = "https://files.pythonhosted.org/packages/11/b6/e78ee82e96c495bc2582b303f68bed176b481c8d81a441fec07404fce2ca/multidict-6.5.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9695fc1462f17b131c111cf0856a22ff154b0480f86f539d24b2778571ff94d", size = 238545, upload-time = "2025-06-17T14:15:06.88Z" },
+    { url = "https://files.pythonhosted.org/packages/5a/0f/6132ca06670c8d7b374c3a4fd1ba896fc37fbb66b0de903f61db7d1020ec/multidict-6.5.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b5ac6ebaf5d9814b15f399337ebc6d3a7f4ce9331edd404e76c49a01620b68d", size = 229931, upload-time = "2025-06-17T14:15:08.24Z" },
+    { url = "https://files.pythonhosted.org/packages/c0/63/d9957c506e6df6b3e7a194f0eea62955c12875e454b978f18262a65d017b/multidict-6.5.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84a51e3baa77ded07be4766a9e41d977987b97e49884d4c94f6d30ab6acaee14", size = 248181, upload-time = "2025-06-17T14:15:09.907Z" },
+    { url = "https://files.pythonhosted.org/packages/43/3f/7d5490579640db5999a948e2c41d4a0efd91a75989bda3e0a03a79c92be2/multidict-6.5.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8de67f79314d24179e9b1869ed15e88d6ba5452a73fc9891ac142e0ee018b5d6", size = 241846, upload-time = "2025-06-17T14:15:11.596Z" },
+    { url = "https://files.pythonhosted.org/packages/e1/f7/252b1ce949ece52bba4c0de7aa2e3a3d5964e800bce71fb778c2e6c66f7c/multidict-6.5.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17f78a52c214481d30550ec18208e287dfc4736f0c0148208334b105fd9e0887", size = 232893, upload-time = "2025-06-17T14:15:12.946Z" },
+    { url = "https://files.pythonhosted.org/packages/45/7e/0070bfd48c16afc26e056f2acce49e853c0d604a69c7124bc0bbdb1bcc0a/multidict-6.5.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2966d0099cb2e2039f9b0e73e7fd5eb9c85805681aa2a7f867f9d95b35356921", size = 228567, upload-time = "2025-06-17T14:15:14.267Z" },
+    { url = "https://files.pythonhosted.org/packages/2a/31/90551c75322113ebf5fd9c5422e8641d6952f6edaf6b6c07fdc49b1bebdd/multidict-6.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:86fb42ed5ed1971c642cc52acc82491af97567534a8e381a8d50c02169c4e684", size = 246188, upload-time = "2025-06-17T14:15:15.985Z" },
+    { url = "https://files.pythonhosted.org/packages/cc/e2/aa4b02a55e7767ff292871023817fe4db83668d514dab7ccbce25eaf7659/multidict-6.5.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:4e990cbcb6382f9eae4ec720bcac6a1351509e6fc4a5bb70e4984b27973934e6", size = 235178, upload-time = "2025-06-17T14:15:17.395Z" },
+    { url = "https://files.pythonhosted.org/packages/7d/5c/f67e726717c4b138b166be1700e2b56e06fbbcb84643d15f9a9d7335ff41/multidict-6.5.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d99a59d64bb1f7f2117bec837d9e534c5aeb5dcedf4c2b16b9753ed28fdc20a3", size = 243422, upload-time = "2025-06-17T14:15:18.939Z" },
+    { url = "https://files.pythonhosted.org/packages/e5/1c/15fa318285e26a50aa3fa979bbcffb90f9b4d5ec58882d0590eda067d0da/multidict-6.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:e8ef15cc97c9890212e1caf90f0d63f6560e1e101cf83aeaf63a57556689fb34", size = 254898, upload-time = "2025-06-17T14:15:20.31Z" },
+    { url = "https://files.pythonhosted.org/packages/ad/3d/d6c6d1c2e9b61ca80313912d30bb90d4179335405e421ef0a164eac2c0f9/multidict-6.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:b8a09aec921b34bd8b9f842f0bcfd76c6a8c033dc5773511e15f2d517e7e1068", size = 247129, upload-time = "2025-06-17T14:15:21.665Z" },
+    { url = "https://files.pythonhosted.org/packages/29/15/1568258cf0090bfa78d44be66247cfdb16e27dfd935c8136a1e8632d3057/multidict-6.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ff07b504c23b67f2044533244c230808a1258b3493aaf3ea2a0785f70b7be461", size = 243841, upload-time = "2025-06-17T14:15:23.38Z" },
+    { url = "https://files.pythonhosted.org/packages/65/57/64af5dbcfd61427056e840c8e520b502879d480f9632fbe210929fd87393/multidict-6.5.0-cp313-cp313t-win32.whl", hash = "sha256:9232a117341e7e979d210e41c04e18f1dc3a1d251268df6c818f5334301274e1", size = 46761, upload-time = "2025-06-17T14:15:24.733Z" },
+    { url = "https://files.pythonhosted.org/packages/26/a8/cac7f7d61e188ff44f28e46cb98f9cc21762e671c96e031f06c84a60556e/multidict-6.5.0-cp313-cp313t-win_amd64.whl", hash = "sha256:44cb5c53fb2d4cbcee70a768d796052b75d89b827643788a75ea68189f0980a1", size = 52112, upload-time = "2025-06-17T14:15:25.906Z" },
+    { url = "https://files.pythonhosted.org/packages/51/9f/076533feb1b5488d22936da98b9c217205cfbf9f56f7174e8c5c86d86fe6/multidict-6.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:51d33fafa82640c0217391d4ce895d32b7e84a832b8aee0dcc1b04d8981ec7f4", size = 44358, upload-time = "2025-06-17T14:15:27.117Z" },
+    { url = "https://files.pythonhosted.org/packages/68/0b/b024da30f18241e03a400aebdc3ca1bcbdc0561f9d48019cbe66549aea3e/multidict-6.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c0078358470da8dc90c37456f4a9cde9f86200949a048d53682b9cd21e5bbf2b", size = 73804, upload-time = "2025-06-17T14:15:28.305Z" },
+    { url = "https://files.pythonhosted.org/packages/a3/8f/5e69092bb8a75b95dd27ed4d21220641ede7e127d8a0228cd5e1d5f2150e/multidict-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5cc7968b7d1bf8b973c307d38aa3a2f2c783f149bcac855944804252f1df5105", size = 43161, upload-time = "2025-06-17T14:15:29.47Z" },
+    { url = "https://files.pythonhosted.org/packages/e1/d9/51968d296800285343055d482b65001bda4fa4950aad5575afe17906f16f/multidict-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ad73a60e11aa92f1f2c9330efdeaac4531b719fc568eb8d312fd4112f34cc18", size = 42996, upload-time = "2025-06-17T14:15:30.622Z" },
+    { url = "https://files.pythonhosted.org/packages/38/1c/19ce336cf8af2b7c530ea890496603eb9bbf0da4e3a8e0fcc3669ad30c21/multidict-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3233f21abdcd180b2624eb6988a1e1287210e99bca986d8320afca5005d85844", size = 231051, upload-time = "2025-06-17T14:15:32.296Z" },
+    { url = "https://files.pythonhosted.org/packages/73/9b/2cf6eff5b30ff8a67ca231a741053c8cc8269fd860cac2c0e16b376de89d/multidict-6.5.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:bee5c0b79fca78fd2ab644ca4dc831ecf793eb6830b9f542ee5ed2c91bc35a0e", size = 219511, upload-time = "2025-06-17T14:15:33.602Z" },
+    { url = "https://files.pythonhosted.org/packages/bf/ac/43c89a11d710ce6e5c824ece7b570fd79839e3d25a6a7d3b2526a77b290c/multidict-6.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e053a4d690f4352ce46583080fefade9a903ce0fa9d820db1be80bdb9304fa2f", size = 240287, upload-time = "2025-06-17T14:15:34.915Z" },
+    { url = "https://files.pythonhosted.org/packages/16/94/1896d424324618f2e2adbf9acb049aeef8da3f31c109e37ffda63b58d1b5/multidict-6.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:42bdee30424c1f4dcda96e07ac60e2a4ede8a89f8ae2f48b5e4ccc060f294c52", size = 232748, upload-time = "2025-06-17T14:15:36.576Z" },
+    { url = "https://files.pythonhosted.org/packages/e1/43/2f852c12622bda304a2e0c4419250de3cd0345776ae2e699416cbdc15c9f/multidict-6.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58b2ded1a7982cf7b8322b0645713a0086b2b3cf5bb9f7c01edfc1a9f98d20dc", size = 224910, upload-time = "2025-06-17T14:15:37.941Z" },
+    { url = "https://files.pythonhosted.org/packages/31/68/9c32a0305a11aec71a85f354d739011221507bce977a3be8d9fa248763e7/multidict-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f805b8b951d1fadc5bc18c3c93e509608ac5a883045ee33bc22e28806847c20", size = 225773, upload-time = "2025-06-17T14:15:39.645Z" },
+    { url = "https://files.pythonhosted.org/packages/bc/81/488054827b644e615f59211fc26fd64b28a1366143e4985326802f18773b/multidict-6.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2540395b63723da748f850568357a39cd8d8d4403ca9439f9fcdad6dd423c780", size = 244097, upload-time = "2025-06-17T14:15:41.164Z" },
+    { url = "https://files.pythonhosted.org/packages/9f/71/b9d96548da768dd7284c1f21187129a48906f526d5ed4f71bb050476d91f/multidict-6.5.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:c96aedff25f4e47b6697ba048b2c278f7caa6df82c7c3f02e077bcc8d47b4b76", size = 232831, upload-time = "2025-06-17T14:15:42.897Z" },
+    { url = "https://files.pythonhosted.org/packages/fc/45/0c57c9bf9be7808252269f0d3964c1495413bcee36a7a7e836fdb778a578/multidict-6.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e80de5ad995de210fd02a65c2350649b8321d09bd2e44717eaefb0f5814503e8", size = 242201, upload-time = "2025-06-17T14:15:44.286Z" },
+    { url = "https://files.pythonhosted.org/packages/8b/d4/2441e56b32f7d25c917557641b35a89e0142a7412bc57182c80330975b8d/multidict-6.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:6cb9bcedd9391b313e5ec2fb3aa07c03e050550e7b9e4646c076d5c24ba01532", size = 254479, upload-time = "2025-06-17T14:15:45.718Z" },
+    { url = "https://files.pythonhosted.org/packages/0d/93/acbc2fed235c7a7b2b21fe8c6ac1b612f7fee79dbddd9c73d42b1a65599c/multidict-6.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:a7d130ed7a112e25ab47309962ecafae07d073316f9d158bc7b3936b52b80121", size = 244179, upload-time = "2025-06-17T14:15:47.174Z" },
+    { url = "https://files.pythonhosted.org/packages/aa/b2/07ce91400ee2b296de2d6d55f1d948d88d148182b35a3edcc480ddb0f99a/multidict-6.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:95750a9a9741cd1855d1b6cb4c6031ae01c01ad38d280217b64bfae986d39d56", size = 241173, upload-time = "2025-06-17T14:15:48.566Z" },
+    { url = "https://files.pythonhosted.org/packages/a0/09/61c0b044065a1d2e1329b0e4f0f2afa992d3bb319129b63dd63c54c2cc15/multidict-6.5.0-cp39-cp39-win32.whl", hash = "sha256:7f78caf409914f108f4212b53a9033abfdc2cbab0647e9ac3a25bb0f21ab43d2", size = 40467, upload-time = "2025-06-17T14:15:50.285Z" },
+    { url = "https://files.pythonhosted.org/packages/7f/43/48c2837046222ea6800824d576f110d7622c4048b3dd252ef62c51a0969b/multidict-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:220c74009507e847a3a6fc5375875f2a2e05bd9ce28cf607be0e8c94600f4472", size = 44449, upload-time = "2025-06-17T14:15:51.84Z" },
+    { url = "https://files.pythonhosted.org/packages/d2/4e/b61b006e75c6e071fac1bd0f32696ad1b052772493c4e9d0121ba604b215/multidict-6.5.0-cp39-cp39-win_arm64.whl", hash = "sha256:d98f4ac9c1ede7e9d04076e2e6d967e15df0079a6381b297270f6bcab661195e", size = 41477, upload-time = "2025-06-17T14:15:53.964Z" },
+    { url = "https://files.pythonhosted.org/packages/44/d8/45e8fc9892a7386d074941429e033adb4640e59ff0780d96a8cf46fe788e/multidict-6.5.0-py3-none-any.whl", hash = "sha256:5634b35f225977605385f56153bd95a7133faffc0ffe12ad26e10517537e8dfc", size = 12181, upload-time = "2025-06-17T14:15:55.156Z" },
 ]
 
 [[package]]
@@ -2437,24 +2443,24 @@ wheels = [
 
 [[package]]
 name = "pygments"
-version = "2.19.1"
+version = "2.19.2"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" },
+    { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
 ]
 
 [[package]]
 name = "pymdown-extensions"
-version = "10.15"
+version = "10.16"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "markdown" },
     { name = "pyyaml" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/08/92/a7296491dbf5585b3a987f3f3fc87af0e632121ff3e490c14b5f2d2b4eb5/pymdown_extensions-10.15.tar.gz", hash = "sha256:0e5994e32155f4b03504f939e501b981d306daf7ec2aa1cd2eb6bd300784f8f7", size = 852320, upload-time = "2025-04-27T23:48:29.183Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/1a/0a/c06b542ac108bfc73200677309cd9188a3a01b127a63f20cadc18d873d88/pymdown_extensions-10.16.tar.gz", hash = "sha256:71dac4fca63fabeffd3eb9038b756161a33ec6e8d230853d3cecf562155ab3de", size = 853197, upload-time = "2025-06-21T17:56:36.974Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/a7/d1/c54e608505776ce4e7966d03358ae635cfd51dff1da6ee421c090dbc797b/pymdown_extensions-10.15-py3-none-any.whl", hash = "sha256:46e99bb272612b0de3b7e7caf6da8dd5f4ca5212c0b273feb9304e236c484e5f", size = 265845, upload-time = "2025-04-27T23:48:27.359Z" },
+    { url = "https://files.pythonhosted.org/packages/98/d4/10bb14004d3c792811e05e21b5e5dcae805aacb739bd12a0540967b99592/pymdown_extensions-10.16-py3-none-any.whl", hash = "sha256:f5dd064a4db588cb2d95229fc4ee63a1b16cc8b4d0e6145c0899ed8723da1df2", size = 266143, upload-time = "2025-06-21T17:56:35.356Z" },
 ]
 
 [[package]]
@@ -2868,15 +2874,15 @@ wheels = [
 
 [[package]]
 name = "typeguard"
-version = "4.4.3"
+version = "4.4.4"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
     { name = "typing-extensions" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/34/53/f701077a29ddf65ed4556119961ef517d767c07f15f6cdf0717ad985426b/typeguard-4.4.3.tar.gz", hash = "sha256:be72b9c85f322c20459b29060c5c099cd733d5886c4ee14297795e62b0c0d59b", size = 75072, upload-time = "2025-06-04T21:47:07.733Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/c7/68/71c1a15b5f65f40e91b65da23b8224dad41349894535a97f63a52e462196/typeguard-4.4.4.tar.gz", hash = "sha256:3a7fd2dffb705d4d0efaed4306a704c89b9dee850b688f060a8b1615a79e5f74", size = 75203, upload-time = "2025-06-18T09:56:07.624Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/5c/18/662e2a14fcdbbc9e7842ad801a7f9292fcd6cf7df43af94e59ac9c0da9af/typeguard-4.4.3-py3-none-any.whl", hash = "sha256:7d8b4a3d280257fd1aa29023f22de64e29334bda0b172ff1040f05682223795e", size = 34855, upload-time = "2025-06-04T21:47:03.683Z" },
+    { url = "https://files.pythonhosted.org/packages/1b/a9/e3aee762739c1d7528da1c3e06d518503f8b6c439c35549b53735ba52ead/typeguard-4.4.4-py3-none-any.whl", hash = "sha256:b5f562281b6bfa1f5492470464730ef001646128b180769880468bd84b68b09e", size = 34874, upload-time = "2025-06-18T09:56:05.999Z" },
 ]
 
 [[package]]


Test patch:
diff --git a/tests/hikari/impl/test_buckets.py b/tests/hikari/impl/test_buckets.py
index bca92087..6f366d74 100644
--- a/tests/hikari/impl/test_buckets.py
+++ b/tests/hikari/impl/test_buckets.py
@@ -22,6 +22,8 @@
 
 import asyncio
 import contextlib
+import math
+
 import time
 
 import mock
@@ -43,109 +45,188 @@ def template(self):
     def compiled_route(self, template):
         return routes.CompiledRoute("/foo/bar", template, "1a2b3c")
 
+    @pytest.mark.parametrize(
+        ("name", "expected"), [("spaghetti", False), ("UNKNoWN hash", False), ("UNKNOWN", True), ("UNKNOWN hash", True)]
+    )
+    def test_is_unknown(self, name, compiled_route, expected):
+        bucket = buckets.RESTBucket(name, compiled_route, mock.Mock(), float("inf"))
+
+        assert bucket.is_unknown is expected
+
     @pytest.mark.asyncio
-    async def test_async_context_manager(self, compiled_route):
-        with mock.patch.object(buckets.RESTBucket, "acquire", new=mock.AsyncMock()) as acquire:
-            with mock.patch.object(buckets.RESTBucket, "release") as release:
-                async with buckets.RESTBucket("spaghetti", compiled_route, object(), float("inf")):
-                    acquire.assert_awaited_once_with()
-                    release.assert_not_called()
+    async def test_usage_when_unknown(self, compiled_route):
+        bucket = buckets.RESTBucket(buckets.UNKNOWN_HASH, compiled_route, mock.Mock(), float("inf"))
+        bucket._lock = mock.Mock(acquire=mock.AsyncMock(), locked=mock.Mock(return_value=False))
 
-            release.assert_called_once_with()
+        async with bucket:
+            bucket._lock.acquire.assert_awaited_once_with()
+            bucket._lock.release.assert_not_called()
 
-    @pytest.mark.parametrize("name", ["spaghetti", buckets.UNKNOWN_HASH])
-    def test_is_unknown(self, name, compiled_route):
-        with buckets.RESTBucket(name, compiled_route, object(), float("inf")) as rl:
-            assert rl.is_unknown is (name == buckets.UNKNOWN_HASH)
+            bucket._lock.locked.return_value = True
 
-    def test_release(self, compiled_route):
-        with buckets.RESTBucket(__name__, compiled_route, object(), float("inf")) as rl:
-            rl._lock = mock.Mock()
+        bucket._lock.acquire.assert_awaited_once_with()
+        bucket._lock.release.assert_called_once_with()
 
-            rl.release()
+    @pytest.mark.asyncio
+    async def test_usage_when_resolved(self, compiled_route):
+        global_ratelimit = mock.Mock(acquire=mock.AsyncMock(), reset_at=None)
+        bucket = buckets.RESTBucket("resolved bucket", compiled_route, global_ratelimit, float("inf"))
+        bucket.remaining = 10
+        bucket._lock = mock.Mock(acquire=mock.AsyncMock(), locked=mock.Mock(return_value=False))
 
-            rl._lock.release.assert_called_once_with()
+        async with bucket:
+            bucket._lock.acquire.assert_not_called()
+            bucket._lock.release.assert_not_called()
 
-    def test_update_rate_limit(self, compiled_route):
-        with buckets.RESTBucket(__name__, compiled_route, object(), float("inf")) as rl:
-            rl.remaining = 1
-            rl.limit = 2
-            rl.reset_at = 3
-            rl.period = 2
+        bucket._lock.acquire.assert_not_called()
+        bucket._lock.release.assert_not_called()
 
-            with mock.patch.object(hikari_date, "monotonic", return_value=4.20):
-                rl.update_rate_limit(9, 18, 27)
+    def test_update_rate_limit_when_no_issues(self, compiled_route):
+        bucket = buckets.RESTBucket("updating ratelimit test", compiled_route, mock.Mock(), float("inf"))
+        now = time.time()
 
-            assert rl.remaining == 9
-            assert rl.limit == 18
-            assert rl.reset_at == 27
-            assert rl.period == 27 - 4.20
+        bucket.remaining = 1
+        bucket.limit = 2
+        bucket.increase_at = now
+        bucket.period = 2
 
-    @pytest.mark.asyncio
-    async def test_acquire_when_unknown_bucket(self, compiled_route):
-        with buckets.RESTBucket(buckets.UNKNOWN_HASH, compiled_route, object(), float("inf")) as rl:
-            rl._lock = mock.AsyncMock()
-            with mock.patch.object(rate_limits.WindowedBurstRateLimiter, "acquire") as super_acquire:
-                assert await rl.acquire() is None
+        bucket.update_rate_limit(0, 2, now, 4)
+
+        assert bucket.remaining == 1
+        assert bucket.limit == 2
+        assert bucket.increase_at == now
+        assert bucket.period == 2
+
+    @pytest.mark.parametrize("limit", (10, 5))
+    def test_update_rate_limit_when_limit_changed(self, compiled_route, limit):
+        bucket = buckets.RESTBucket("updating ratelimit test", compiled_route, mock.Mock(), float("inf"))
+        now = time.time()
 
-            rl._lock.acquire.assert_awaited_once_with()
-            super_acquire.assert_not_called()
+        bucket.remaining = 1
+        bucket.limit = 2
+        bucket.increase_at = now
+        bucket.period = 2
+
+        bucket.update_rate_limit(0, limit, now + 20, 4)
+
+        assert bucket.limit == limit
+
+    def test_update_rate_limit_when_period_far_apart(self, compiled_route):
+        bucket = buckets.RESTBucket("updating ratelimit test", compiled_route, mock.Mock(), float("inf"))
+        now = 12123123
+
+        bucket.remaining = 1
+        bucket.limit = 3
+        bucket.increase_at = now
+        bucket.period = 2
+        bucket._out_of_sync = True
+
+        bucket.update_rate_limit(1, 3, 123123124, 4.5)
+
+        assert bucket.remaining == 1
+        assert bucket.limit == 3
+        assert bucket.increase_at == 123123121.75
+        assert bucket.period == 2.25
+        assert bucket._out_of_sync is False
 
     @pytest.mark.asyncio
     async def test_acquire_when_too_long_ratelimit(self, compiled_route):
-        stack = contextlib.ExitStack()
-        rl = stack.enter_context(buckets.RESTBucket("spaghetti", compiled_route, object(), 60))
-        rl._lock = mock.Mock(acquire=mock.AsyncMock())
-        rl.reset_at = time.perf_counter() + 999999999999999999999999999
-        stack.enter_context(mock.patch.object(buckets.RESTBucket, "is_rate_limited", return_value=True))
-        stack.enter_context(pytest.raises(errors.RateLimitTooLongError))
+        bucket = buckets.RESTBucket("spaghetti", compiled_route, mock.Mock(), 60)
+        bucket.increase_at = time.time() + 999999999999999999999999999
+        bucket._lock = mock.Mock(acquire=mock.AsyncMock())
 
-        with stack:
-            await rl.acquire()
+        with (
+            mock.patch.object(buckets.RESTBucket, "is_rate_limited", return_value=True),
+            pytest.raises(errors.RateLimitTooLongError),
+        ):
+            await bucket.acquire()
 
-        rl._lock.acquire.assert_awaited_once_with()
-        rl._lock.release.assert_called_once_with()
+        bucket._lock.acquire.assert_not_called()
+        bucket._lock.release.assert_not_called()
 
     @pytest.mark.asyncio
     async def test_acquire_when_too_long_global_ratelimit(self, compiled_route):
-        global_ratelimit = mock.Mock(reset_at=time.perf_counter() + 999999999999999999999999999)
+        global_ratelimit = mock.Mock(reset_at=time.time() + 999999999999999999999999999)
+
+        bucket = buckets.RESTBucket("spaghetti", compiled_route, global_ratelimit, 1)
+        bucket._lock = mock.Mock(acquire=mock.AsyncMock())
+
+        with (
+            mock.patch.object(rate_limits.WindowedBurstRateLimiter, "acquire") as super_acquire,
+            pytest.raises(errors.RateLimitTooLongError),
+        ):
+            await bucket.acquire()
+
+        bucket._lock.acquire.assert_not_called()
+        bucket._lock.release.assert_not_called()
+        super_acquire.assert_called_once_with()
+        global_ratelimit.acquire.assert_not_called()
 
-        with buckets.RESTBucket("spaghetti", compiled_route, global_ratelimit, 1) as rl:
-            rl._lock = mock.Mock(acquire=mock.AsyncMock())
-            with mock.patch.object(rate_limits.WindowedBurstRateLimiter, "acquire") as super_acquire:
-                with pytest.raises(errors.RateLimitTooLongError):
-                    await rl.acquire()
+    @pytest.mark.asyncio
+    async def test_acquire_when_unknown_bucket(self, compiled_route):
+        global_ratelimit = mock.Mock(acquire=mock.AsyncMock(), reset_at=None)
+
+        bucket = buckets.RESTBucket("UNKNOWN", compiled_route, global_ratelimit, float("inf"))
+        bucket._lock = mock.Mock(acquire=mock.AsyncMock())
+
+        with mock.patch.object(rate_limits.WindowedBurstRateLimiter, "acquire") as super_acquire:
+            await bucket.acquire()
+
+        bucket._lock.acquire.assert_awaited_once_with()
+        super_acquire.assert_not_called()
+        global_ratelimit.acquire.assert_not_called()
+
+    @pytest.mark.asyncio
+    async def test_acquire_when_resolved_while_waiting(self, compiled_route):
+        async def resolve_bucket():
+            nonlocal lock_acquire_called
+
+            bucket.resolve("some real hash", 1, 2, 3, 4)
+            lock_acquire_called += 1
 
-            rl._lock.acquire.assert_awaited_once_with()
-            super_acquire.assert_awaited_once_with()
-            rl._lock.release.assert_called_once_with()
-            global_ratelimit.acquire.assert_not_called()
+        global_ratelimit = mock.Mock(acquire=mock.AsyncMock(), reset_at=None)
+        bucket = buckets.RESTBucket(buckets.UNKNOWN_HASH, compiled_route, global_ratelimit, float("inf"))
+        bucket._lock = mock.Mock(acquire=resolve_bucket)
+        lock_acquire_called = 0
+
+        with mock.patch.object(rate_limits.WindowedBurstRateLimiter, "acquire") as super_acquire:
+            await bucket.acquire()
+
+        super_acquire.assert_awaited_once_with()
+        assert lock_acquire_called == 1
+        global_ratelimit.acquire.assert_awaited_once_with()
 
     @pytest.mark.asyncio
-    async def test_acquire(self, compiled_route):
+    async def test_acquire_when_resolved_bucket(self, compiled_route):
         global_ratelimit = mock.Mock(acquire=mock.AsyncMock(), reset_at=None)
+        bucket = buckets.RESTBucket("spaghetti", compiled_route, global_ratelimit, float("inf"))
+        bucket._lock = mock.Mock()
 
-        with buckets.RESTBucket("spaghetti", compiled_route, global_ratelimit, float("inf")) as rl:
-            rl._lock = mock.AsyncMock()
-            with mock.patch.object(rate_limits.WindowedBurstRateLimiter, "acquire") as super_acquire:
-                await rl.acquire()
+        with mock.patch.object(rate_limits.WindowedBurstRateLimiter, "acquire") as super_acquire:
+            await bucket.acquire()
 
-            super_acquire.assert_awaited_once_with()
-            rl._lock.acquire.assert_awaited_once_with()
-            global_ratelimit.acquire.assert_awaited_once_with()
+        super_acquire.assert_awaited_once_with()
+        bucket._lock.acquire.assert_not_called()
+        global_ratelimit.acquire.assert_awaited_once_with()
 
     def test_resolve_when_not_unknown(self, compiled_route):
-        with buckets.RESTBucket("spaghetti", compiled_route, object(), float("inf")) as rl:
-            with pytest.raises(RuntimeError, match=r"Cannot resolve known bucket"):
-                rl.resolve("test")
+        bucket = buckets.RESTBucket("spaghetti", compiled_route, mock.Mock(), float("inf"))
 
-            assert rl.name == "spaghetti"
+        with pytest.raises(RuntimeError, match=r"Cannot resolve known bucket"):
+            bucket.resolve("test", 1, 2, 3, 4)
+
+        assert bucket.name == "spaghetti"
 
     def test_resolve(self, compiled_route):
-        with buckets.RESTBucket(buckets.UNKNOWN_HASH, compiled_route, object(), float("inf")) as rl:
-            rl.resolve("test")
+        bucket = buckets.RESTBucket(buckets.UNKNOWN_HASH, compiled_route, mock.Mock(), float("inf"))
+
+        bucket.resolve("test", 1, 3, 123123124, 4)
 
-            assert rl.name == "test"
+        assert bucket.name == "test"
+        assert bucket.remaining == 1
+        assert bucket.limit == 3
+        assert bucket.period == 2
+        assert bucket.increase_at == 123123122
 
 
 class TestRESTBucketManager:
@@ -234,7 +315,7 @@ async def test_purge_stale_buckets_any_buckets_that_are_empty_but_still_rate_lim
         bucket = mock.Mock()
         bucket.is_empty = True
         bucket.is_unknown = False
-        bucket.reset_at = time.perf_counter() + 999999999999999999999999999
+        bucket.increase_at = time.time() + 999999999999999999999999999
 
         bucket_manager._real_hashes_to_buckets["foobar"] = bucket
 
@@ -250,7 +331,7 @@ async def test_purge_stale_buckets_any_buckets_that_are_empty_but_not_rate_limit
         bucket = mock.Mock()
         bucket.is_empty = True
         bucket.is_unknown = False
-        bucket.reset_at = time.perf_counter()
+        bucket.increase_at = time.time()
 
         bucket_manager._real_hashes_to_buckets["foobar"] = bucket
 
@@ -266,7 +347,7 @@ async def test_purge_stale_buckets_any_buckets_that_are_empty_but_not_rate_limit
         bucket = mock.Mock()
         bucket.is_empty = True
         bucket.is_unknown = False
-        bucket.reset_at = time.perf_counter() - 999999999999999999999999999
+        bucket.increase_at = time.time() - 999999999999999999999999999
 
         bucket_manager._real_hashes_to_buckets["foobar"] = bucket
 
@@ -280,7 +361,7 @@ async def test_purge_stale_buckets_any_buckets_that_are_not_empty_are_kept_alive
         bucket = mock.Mock()
         bucket.is_empty = False
         bucket.is_unknown = True
-        bucket.reset_at = time.perf_counter()
+        bucket.increase_at = time.time()
 
         bucket_manager._real_hashes_to_buckets["foobar"] = bucket
 
@@ -312,16 +393,18 @@ async def test_acquire_route_when_not_in_routes_to_real_hashes_doesnt_cache_rout
 
         bucket_manager.acquire_bucket(route, "auth")
 
-        assert bucket_manager._routes_to_hashes.get(route.route) is None
+        assert bucket_manager._route_hash_to_bucket_hash.get(hash(route.route)) is None
 
     @pytest.mark.asyncio
     async def test_acquire_route_when_route_cached_already_obtains_hash_from_route_and_bucket_from_hash(
         self, bucket_manager
     ):
+        mock_route_hash = 123123123
         route = mock.Mock()
+        route.route = mock.Mock(__hash__=lambda _: mock_route_hash)
         route.create_real_bucket_hash = mock.Mock(return_value="eat pant;1234")
-        bucket = mock.Mock(reset_at=time.perf_counter() + 999999999999999999999999999)
-        bucket_manager._routes_to_hashes[route.route] = "eat pant"
+        bucket = mock.Mock(reset_at=time.time() + 999999999999999999999999999)
+        bucket_manager._route_hash_to_bucket_hash[mock_route_hash] = "eat pant"
         bucket_manager._real_hashes_to_buckets["eat pant;1234"] = bucket
 
         assert bucket_manager.acquire_bucket(route, "auth") is bucket
@@ -330,7 +413,7 @@ async def test_acquire_route_when_route_cached_already_obtains_hash_from_route_a
     async def test_acquire_route_returns_context_manager(self, bucket_manager):
         route = mock.Mock()
 
-        bucket = mock.Mock(reset_at=time.perf_counter() + 999999999999999999999999999)
+        bucket = mock.Mock(reset_at=time.time() + 999999999999999999999999999)
         with mock.patch.object(buckets, "RESTBucket", return_value=bucket):
             route.create_real_bucket_hash = mock.Mock(
                 wraps=lambda initial_hash, auth: initial_hash + ";" + auth + ";bobs"
@@ -340,53 +423,58 @@ async def test_acquire_route_returns_context_manager(self, bucket_manager):
 
     @pytest.mark.asyncio
     async def test_acquire_unknown_route_returns_context_manager_for_new_bucket(self, bucket_manager):
+        mock_route_hash = 123123123
         route = mock.Mock()
+        route.route = mock.Mock(__hash__=lambda _: mock_route_hash)
         route.create_real_bucket_hash = mock.Mock(return_value="eat pant;bobs")
-        bucket = mock.Mock(reset_at=time.perf_counter() + 999999999999999999999999999)
-        bucket_manager._routes_to_hashes[route.route] = "eat pant"
+        bucket = mock.Mock(reset_at=time.time() + 999999999999999999999999999)
+        bucket_manager._route_hash_to_bucket_hash[mock_route_hash] = "eat pant"
         bucket_manager._real_hashes_to_buckets["eat pant;bobs"] = bucket
 
         assert bucket_manager.acquire_bucket(route, "auth") is bucket
 
     @pytest.mark.asyncio
     async def test_update_rate_limits_if_wrong_bucket_hash_reroutes_route(self, bucket_manager):
+        mock_route_hash = 123123123
         route = mock.Mock()
+        route.route = mock.Mock(__hash__=lambda _: mock_route_hash)
         route.create_real_bucket_hash = mock.Mock(wraps=lambda initial_hash, auth: initial_hash + ";" + auth + ";bobs")
-        bucket_manager._routes_to_hashes[route.route] = "123"
+        bucket_manager._route_hash_to_bucket_hash[mock_route_hash] = "123"
 
-        with mock.patch.object(buckets, "_create_authentication_hash", return_value="auth_hash"):
-            with mock.patch.object(hikari_date, "monotonic", return_value=27):
-                with mock.patch.object(buckets, "RESTBucket") as bucket:
-                    bucket_manager.update_rate_limits(route, "auth", "blep", 22, 23, 3.56)
+        with (
+            mock.patch.object(buckets, "_create_authentication_hash", return_value="auth_hash"),
+            mock.patch.object(buckets, "RESTBucket") as bucket,
+        ):
+            bucket_manager.update_rate_limits(route, "auth", "blep", 22, 23, 123123.56, 3.56)
 
-        assert bucket_manager._routes_to_hashes[route.route] == "blep"
+        assert bucket_manager._route_hash_to_bucket_hash[mock_route_hash] == "blep"
         assert bucket_manager._real_hashes_to_buckets["blep;auth_hash;bobs"] is bucket.return_value
-        bucket.return_value.update_rate_limit.assert_called_once_with(22, 23, 27 + 3.56)
+        bucket.return_value.update_rate_limit.assert_called_once_with(22, 23, 123123.56, 3.56)
 
     @pytest.mark.asyncio
     async def test_update_rate_limits_if_unknown_bucket_hash_reroutes_route(self, bucket_manager):
+        mock_route_hash = 123123123
         route = mock.Mock()
+        route.route = mock.Mock(__hash__=lambda _: mock_route_hash)
         route.create_real_bucket_hash = mock.Mock(wraps=lambda initial_hash, auth: initial_hash + ";" + auth + ";bobs")
-        bucket_manager._routes_to_hashes[route.route] = "123"
+        bucket_manager._route_hash_to_bucket_hash[mock_route_hash] = "123"
         bucket = mock.Mock()
         bucket_manager._real_hashes_to_buckets["UNKNOWN;auth_hash;bobs"] = bucket
 
-        stack = contextlib.ExitStack()
-        create_authentication_hash = stack.enter_context(
-            mock.patch.object(buckets, "_create_authentication_hash", return_value="auth_hash")
-        )
-        create_unknown_hash = stack.enter_context(
-            mock.patch.object(buckets, "_create_unknown_hash", return_value="UNKNOWN;auth_hash;bobs")
-        )
-        stack.enter_context(mock.patch.object(hikari_date, "monotonic", return_value=27))
-
-        with stack:
-            bucket_manager.update_rate_limits(route, "auth", "blep", 22, 23, 3.56)
+        with (
+            mock.patch.object(
+                buckets, "_create_authentication_hash", return_value="auth_hash"
+            ) as create_authentication_hash,
+            mock.patch.object(
+                buckets, "_create_unknown_hash", return_value="UNKNOWN;auth_hash;bobs"
+            ) as create_unknown_hash,
+        ):
+            bucket_manager.update_rate_limits(route, "auth", "blep", 22, 23, 123123.53, 3.56)
 
-        assert bucket_manager._routes_to_hashes[route.route] == "blep"
+        assert bucket_manager._route_hash_to_bucket_hash[mock_route_hash] == "blep"
         assert bucket_manager._real_hashes_to_buckets["blep;auth_hash;bobs"] is bucket
-        bucket.resolve.assert_called_once_with("blep;auth_hash;bobs")
-        bucket.update_rate_limit.assert_called_once_with(22, 23, 27 + 3.56)
+        bucket.resolve.assert_called_once_with("blep;auth_hash;bobs", 22, 23, 123123.53, 3.56)
+        bucket.update_rate_limit.assert_called_once_with(22, 23, 123123.53, 3.56)
         create_unknown_hash.assert_called_once_with(route, "auth_hash")
         create_authentication_hash.assert_called_once_with("auth")
 
@@ -394,30 +482,29 @@ async def test_update_rate_limits_if_unknown_bucket_hash_reroutes_route(self, bu
     async def test_update_rate_limits_if_right_bucket_hash_does_nothing_to_hash(self, bucket_manager):
         route = mock.Mock()
         route.create_real_bucket_hash = mock.Mock(wraps=lambda initial_hash, auth: initial_hash + ";" + auth + ";bobs")
-        bucket_manager._routes_to_hashes[route.route] = "123"
-        bucket = mock.Mock(reset_at=time.perf_counter() + 999999999999999999999999999)
+        bucket_manager._route_hash_to_bucket_hash[route.route] = "123"
+        bucket = mock.Mock(reset_at=time.time() + 999999999999999999999999999)
         bucket_manager._real_hashes_to_buckets["123;auth_hash;bobs"] = bucket
 
         with mock.patch.object(buckets, "_create_authentication_hash", return_value="auth_hash"):
-            with mock.patch.object(hikari_date, "monotonic", return_value=27):
-                bucket_manager.update_rate_limits(route, "auth", "123", 22, 23, 7.65)
+            bucket_manager.update_rate_limits(route, "auth", "123", 22, 23, 123123.53, 7.65)
 
-        assert bucket_manager._routes_to_hashes[route.route] == "123"
+        assert bucket_manager._route_hash_to_bucket_hash[route.route] == "123"
         assert bucket_manager._real_hashes_to_buckets["123;auth_hash;bobs"] is bucket
-        bucket.update_rate_limit.assert_called_once_with(22, 23, 27 + 7.65)
+        bucket.update_rate_limit.assert_called_once_with(22, 23, 123123.53, 7.65)
 
     @pytest.mark.asyncio
     async def test_update_rate_limits_updates_params(self, bucket_manager):
         route = mock.Mock()
         route.create_real_bucket_hash = mock.Mock(wraps=lambda initial_hash, auth: initial_hash + ";" + auth + ";bobs")
-        bucket_manager._routes_to_hashes[route.route] = "123"
-        bucket = mock.Mock(reset_at=time.perf_counter() + 999999999999999999999999999)
+        bucket_manager._route_hash_to_bucket_hash[route.route] = "123"
+        bucket = mock.Mock(reset_at=time.time() + 999999999999999999999999999)
         bucket_manager._real_hashes_to_buckets["123;auth_hash;bobs"] = bucket
 
         with mock.patch.object(buckets, "_create_authentication_hash", return_value="auth_hash"):
-            with mock.patch.object(hikari_date, "monotonic", return_value=27):
-                bucket_manager.update_rate_limits(route, "auth", "123", 22, 23, 5.32)
-                bucket.update_rate_limit.assert_called_once_with(22, 23, 27 + 5.32)
+            bucket_manager.update_rate_limits(route, "auth", "123", 22, 23, 123123123.53, 5.32)
+
+        bucket.update_rate_limit.assert_called_once_with(22, 23, 123123123.53, 5.32)
 
     @pytest.mark.parametrize(("gc_task", "is_alive"), [(None, False), ("some", True)])
     def test_is_alive(self, bucket_manager, gc_task, is_alive):
diff --git a/tests/hikari/impl/test_event_manager.py b/tests/hikari/impl/test_event_manager.py
index 0aeac397..ede42046 100644
--- a/tests/hikari/impl/test_event_manager.py
+++ b/tests/hikari/impl/test_event_manager.py
@@ -41,27 +41,11 @@
 
 
 def test_fixed_size_nonce():
-    stack = contextlib.ExitStack()
-    monotonic = stack.enter_context(mock.patch.object(time, "monotonic_ns"))
-    monotonic.return_value.to_bytes = mock.Mock(return_value="foo")
-
-    randbits = stack.enter_context(mock.patch.object(random, "getrandbits"))
-    randbits.return_value.to_bytes = mock.Mock(return_value="bar")
-
-    encode = stack.enter_context(mock.patch.object(base64, "b64encode"))
-    encode.return_value.decode = mock.Mock(return_value="nonce")
-
-    with stack:
-        assert event_manager._fixed_size_nonce() == "nonce"
-
-    monotonic.assert_called_once_with()
-    monotonic.return_value.to_bytes.assert_called_once_with(8, "big")
-
-    randbits.assert_called_once_with(92)
-    randbits.return_value.to_bytes.assert_called_once_with(12, "big")
-
-    encode.assert_called_once_with("foobar")
-    encode.return_value.decode.assert_called_once_with("ascii")
+    with (
+        mock.patch.object(time, "time_ns", return_value=1750363933884138616),
+        mock.patch.object(random, "getrandbits", return_value=3816720724214628891853616180),
+    ):
+        assert event_manager._fixed_size_nonce() == "GEqKuVrRxHgMVR4NDFaqlnpIdDQ="
 
 
 @pytest.fixture
diff --git a/tests/hikari/impl/test_interaction_server.py b/tests/hikari/impl/test_interaction_server.py
index f7348d0e..b5c816f5 100644
--- a/tests/hikari/impl/test_interaction_server.py
+++ b/tests/hikari/impl/test_interaction_server.py
@@ -760,7 +760,7 @@ async def mock_generator_listener(event):
         assert g_complete is False
         assert len(mock_interaction_server._running_generator_listeners) != 0
         # Give some time for the task to complete
-        await asyncio.sleep(hikari_test_helpers.REASONABLE_QUICK_RESPONSE_TIME)
+        await asyncio.sleep(0.01)
 
         assert g_complete is True
         assert len(mock_interaction_server._running_generator_listeners) == 0
diff --git a/tests/hikari/impl/test_rate_limits.py b/tests/hikari/impl/test_rate_limits.py
index 848241ce..b315b3c6 100644
--- a/tests/hikari/impl/test_rate_limits.py
+++ b/tests/hikari/impl/test_rate_limits.py
@@ -187,11 +187,11 @@ async def test_throttle_sleeps_before_popping_queue(self):
 
         async def mock_sleep(_):
             nonlocal slept_at
-            slept_at = time.perf_counter()
+            slept_at = time.time()
 
         class MockList(list):
             def pop(self, _=-1):
-                popped_at.append(time.perf_counter())
+                popped_at.append(time.time())
                 return event_loop.create_future()
 
         with hikari_test_helpers.mock_class_namespace(rate_limits.ManualRateLimiter, slots_=False)() as limiter:
@@ -229,21 +229,21 @@ def ratelimiter(self):
     async def test_drip_if_not_throttled_and_not_ratelimited(self, ratelimiter):
         event_loop = asyncio.get_running_loop()
 
-        ratelimiter.drip = mock.Mock()
+        ratelimiter.remaining = 10
         ratelimiter.throttle_task = None
         ratelimiter.is_rate_limited = mock.Mock(return_value=False)
         event_loop.create_future = mock.Mock()
 
         await ratelimiter.acquire()
 
-        ratelimiter.drip.assert_called_once_with()
+        assert ratelimiter.remaining == 9
         event_loop.create_future.assert_not_called()
 
     @pytest.mark.asyncio
     async def test_no_drip_if_throttle_task_is_not_None(self, ratelimiter):
         event_loop = asyncio.get_running_loop()
 
-        ratelimiter.drip = mock.Mock()
+        ratelimiter.remaining = 10
         ratelimiter.throttle_task = asyncio.get_running_loop().create_future()
         ratelimiter.is_rate_limited = mock.Mock(return_value=False)
         future = MockFuture()
@@ -251,13 +251,13 @@ async def test_no_drip_if_throttle_task_is_not_None(self, ratelimiter):
 
         await ratelimiter.acquire()
 
-        ratelimiter.drip.assert_not_called()
+        assert ratelimiter.remaining == 10
 
     @pytest.mark.asyncio
     async def test_no_drip_if_rate_limited(self, ratelimiter):
         event_loop = asyncio.get_running_loop()
 
-        ratelimiter.drip = mock.Mock()
+        ratelimiter.remaining = 10
         ratelimiter.throttle_task = False
         ratelimiter.is_rate_limited = mock.Mock(return_value=True)
         future = MockFuture()
@@ -265,13 +265,12 @@ async def test_no_drip_if_rate_limited(self, ratelimiter):
 
         await ratelimiter.acquire()
 
-        ratelimiter.drip.assert_not_called()
+        assert ratelimiter.remaining == 10
 
     @pytest.mark.asyncio
     async def test_task_scheduled_if_rate_limited_and_throttle_task_is_None(self, ratelimiter):
         event_loop = asyncio.get_running_loop()
 
-        ratelimiter.drip = mock.Mock()
         ratelimiter.throttle_task = None
         ratelimiter.throttle = mock.AsyncMock()
         ratelimiter.is_rate_limited = mock.Mock(return_value=True)
@@ -287,7 +286,6 @@ async def test_task_scheduled_if_rate_limited_and_throttle_task_is_None(self, ra
     async def test_task_not_scheduled_if_rate_limited_and_throttle_task_not_None(self, ratelimiter):
         event_loop = asyncio.get_running_loop()
 
-        ratelimiter.drip = mock.Mock()
         ratelimiter.throttle_task = event_loop.create_future()
         old_task = ratelimiter.throttle_task
         ratelimiter.is_rate_limited = mock.Mock(return_value=True)
@@ -301,7 +299,6 @@ async def test_task_not_scheduled_if_rate_limited_and_throttle_task_not_None(sel
     async def test_future_is_added_to_queue_if_throttle_task_is_not_None(self, ratelimiter):
         event_loop = asyncio.get_running_loop()
 
-        ratelimiter.drip = mock.Mock()
         ratelimiter.throttle_task = asyncio.get_running_loop().create_future()
         ratelimiter.is_rate_limited = mock.Mock(return_value=False)
         future = MockFuture()
@@ -316,7 +313,6 @@ async def test_future_is_added_to_queue_if_throttle_task_is_not_None(self, ratel
     async def test_future_is_added_to_queue_if_rate_limited(self, ratelimiter):
         event_loop = asyncio.get_running_loop()
 
-        ratelimiter.drip = mock.Mock()
         ratelimiter.throttle_task = None
         ratelimiter.is_rate_limited = mock.Mock(return_value=True)
         future = MockFuture()
@@ -352,7 +348,7 @@ async def test_throttle_when_limited_sleeps_then_bursts_repeatedly(self):
         futures = [event_loop.create_future() for _ in range(20)]
         reset_time_iter = iter(range(int(len(futures) / window)))
 
-        def mock_get_time_until_reset(_self, _):
+        def mock_get_time_until_increase(_self, _):
             nonlocal loop_count
 
             for i, future in enumerate(futures):
@@ -371,14 +367,14 @@ def mock_get_time_until_reset(_self, _):
         rl = stack.enter_context(rate_limits.WindowedBurstRateLimiter(__name__, 0, window))
         stack.enter_context(
             mock.patch.object(
-                rate_limits.WindowedBurstRateLimiter, "get_time_until_reset", new=mock_get_time_until_reset
+                rate_limits.WindowedBurstRateLimiter, "get_time_until_increase", new=mock_get_time_until_increase
             )
         )
         stack.enter_context(mock.patch.object(asyncio, "sleep"))
 
         with stack:
             rl.queue = list(futures)
-            rl.reset_at = time.perf_counter()
+            rl.increase_at = time.time()
             await rl.throttle()
             # die if we take too long...
             await asyncio.wait(futures, timeout=3)
@@ -398,37 +394,37 @@ async def test_throttle_resets_throttle_task(self):
             await rl.throttle()
         assert rl.throttle_task is None
 
-    def test_get_time_until_reset_if_not_rate_limited(self):
+    def test_get_time_until_increase_if_not_rate_limited(self):
         with hikari_test_helpers.mock_class_namespace(rate_limits.WindowedBurstRateLimiter, slots_=False)(
             __name__, 0.01, 1
         ) as rl:
             rl.is_rate_limited = mock.Mock(return_value=False)
-            assert rl.get_time_until_reset(420) == 0.0
+            assert rl.get_time_until_increase(420) == 0.0
 
-    def test_get_time_until_reset_if_rate_limited(self):
+    def test_get_time_until_increase_if_rate_limited(self):
         with hikari_test_helpers.mock_class_namespace(rate_limits.WindowedBurstRateLimiter, slots_=False)(
             __name__, 0.01, 1
         ) as rl:
             rl.is_rate_limited = mock.Mock(return_value=True)
-            rl.reset_at = 420.4
-            assert rl.get_time_until_reset(69.8) == 420.4 - 69.8
+            rl.increase_at = 420.4
+            assert rl.get_time_until_increase(69.8) == 420.4 - 69.8
 
-    def test_is_rate_limited_when_rate_limit_expired_resets_self(self):
-        with rate_limits.WindowedBurstRateLimiter(__name__, 403, 27) as rl:
+    def test_is_rate_limited_when_rate_limit_expired(self):
+        with rate_limits.WindowedBurstRateLimiter(__name__, 27, 27) as rl:
             now = 180
-            rl.reset_at = 80
-            rl.remaining = 4
+            rl.increase_at = 80
+            rl.remaining = 3
 
             assert not rl.is_rate_limited(now)
 
-            assert rl.reset_at == now + 403
+            assert rl.increase_at == 207
             assert rl.remaining == 27
 
     @pytest.mark.parametrize("remaining", [-1, 0, 1])
-    def test_is_rate_limited_when_rate_limit_not_expired_only_returns_False(self, remaining):
+    def test_is_rate_limited_when_rate_limit_not_expired(self, remaining):
         with rate_limits.WindowedBurstRateLimiter(__name__, 403, 27) as rl:
             now = 420
-            rl.reset_at = now + 69
+            rl.increase_at = now + 69
             rl.remaining = remaining
             assert rl.is_rate_limited(now) is (remaining <= 0)
 
diff --git a/tests/hikari/impl/test_rest.py b/tests/hikari/impl/test_rest.py
index 2fcae2c0..3013c63b 100644
--- a/tests/hikari/impl/test_rest.py
+++ b/tests/hikari/impl/test_rest.py
@@ -171,7 +171,7 @@ async def test_acquire_handles_out_of_date_token(self, mock_token):
             mock.Mock(authorize_client_credentials_token=mock.AsyncMock(return_value=mock_old_token))
         )
 
-        with mock.patch.object(time, "monotonic", return_value=99999999999):
+        with mock.patch.object(time, "time", return_value=99999999999):
             new_token = await strategy.acquire(mock_rest)
 
         mock_rest.authorize_client_credentials_token.assert_awaited_once_with(
@@ -240,7 +240,7 @@ async def __aexit__(self, exc_type, exc_val, exc_tb):
         strategy = rest.ClientCredentialsStrategy(client=65123, client_secret="12354")
         strategy._lock = MockLock(strategy)
         strategy._token = None
-        strategy._expire_at = time.monotonic() + 500
+        strategy._expire_at = time.time() + 500
 
         result = await strategy.acquire(mock_rest)
 
@@ -2219,6 +2219,7 @@ class StubResponse:
                 rest._X_RATELIMIT_LIMIT_HEADER: "123456789",
                 rest._X_RATELIMIT_REMAINING_HEADER: "987654321",
                 rest._X_RATELIMIT_RESET_AFTER_HEADER: "12.2",
+                rest._X_RATELIMIT_RESET_HEADER: "12123123.2",
             }
 
         response = StubResponse()
@@ -2233,6 +2234,7 @@ class StubResponse:
             remaining_header=987654321,
             limit_header=123456789,
             reset_after=12.2,
+            reset_at=12123123.2,
         )
 
     async def test__parse_ratelimits_when_not_ratelimited(self, rest_client):
@@ -2295,7 +2297,7 @@ async def test__parse_ratelimits_when_remaining_header_under_or_equal_to_0(self,
         class StubResponse:
             status = http.HTTPStatus.TOO_MANY_REQUESTS
             content_type = rest._APPLICATION_JSON
-            headers = {rest._X_RATELIMIT_REMAINING_HEADER: "0"}
+            headers = {rest._X_RATELIMIT_SCOPE_HEADER: "user", rest._X_RATELIMIT_REMAINING_HEADER: "0"}
             real_url = "https://some.url"
 
             async def json(self):
diff --git a/tests/hikari/impl/test_shard.py b/tests/hikari/impl/test_shard.py
index 507c9e72..41c9f792 100644
--- a/tests/hikari/impl/test_shard.py
+++ b/tests/hikari/impl/test_shard.py
@@ -896,7 +896,7 @@ class ExitException(Exception): ...
 
         stack = contextlib.ExitStack()
         sleep = stack.enter_context(mock.patch.object(asyncio, "sleep", side_effect=[None, ExitException]))
-        stack.enter_context(mock.patch.object(time, "monotonic", return_value=10))
+        stack.enter_context(mock.patch.object(time, "time", return_value=10))
         send_heartbeat = stack.enter_context(mock.patch.object(shard.GatewayShardImpl, "_send_heartbeat"))
         stack.enter_context(pytest.raises(ExitException))
 
@@ -913,7 +913,7 @@ async def test__heartbeat_when_zombie(self, client):
         client._last_heartbeat_sent = 10
         client._logger = mock.Mock()
 
-        with mock.patch.object(time, "monotonic", return_value=5):
+        with mock.patch.object(time, "time", return_value=5):
             with mock.patch.object(asyncio, "sleep") as sleep:
                 await client._heartbeat(20)
 
@@ -1117,7 +1117,7 @@ async def test__send_heartbeat(self, client):
         client._seq = 10
 
         with mock.patch.object(shard.GatewayShardImpl, "_send_json") as send_json:
-            with mock.patch.object(time, "monotonic", return_value=200):
+            with mock.patch.object(time, "time", return_value=200):
                 await client._send_heartbeat()
 
         send_json.assert_awaited_once_with({"op": 1, "d": 10}, priority=True)
@@ -1194,7 +1194,7 @@ async def test__poll_events_on_heartbeat_ack(self, client):
         client._last_heartbeat_sent = 1.5
         client._handshake_event = mock.Mock()
 
-        with mock.patch.object(time, "monotonic", return_value=3):
+        with mock.patch.object(time, "time", return_value=3):
             with pytest.raises(RuntimeError):
                 await client._poll_events()