Coverage for app/venv/lib/python3.14/site-packages/weblate/api/middleware.py: 89%
27 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 07:15 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 07:15 +0000
1# Copyright © Michal Čihař <michal@weblate.org>
2#
3# SPDX-License-Identifier: GPL-3.0-or-later
5from __future__ import annotations
7import contextlib
8from typing import TYPE_CHECKING
10from django.conf import settings
11from django.http.request import UnreadablePostError
13if TYPE_CHECKING: 13 ↛ 14line 13 didn't jump to line 14 because the condition on line 13 was never true
14 from weblate.auth.models import AuthenticatedHttpRequest
17RATELIMIT_LIMIT_HEADER = "X-RateLimit-Limit"
18RATELIMIT_REMAINING_HEADER = "X-RateLimit-Remaining"
19RATELIMIT_RESET_HEADER = "X-RateLimit-Reset"
22class ThrottlingMiddleware:
23 def __init__(self, get_response=None) -> None:
24 self.get_response = get_response
26 def __call__(self, request: AuthenticatedHttpRequest):
27 response = self.get_response(request)
29 # API payload workaround
30 if request.method != "GET" and request.path_info.startswith(
31 f"{settings.URL_PREFIX}/api"
32 ):
33 # Make sure full request is read. Django REST Framework lazily loads
34 # request body when needed, but keeping the payload in the stream the client
35 # ends up with an error:
36 #
37 # - HTTP/1.1: transfer closed with outstanding read data remaining
38 # - HTTP/2: stream was not closed cleanly
39 with contextlib.suppress(UnreadablePostError):
40 request.read()
42 # Actual throttling
43 throttling = request.META.get("throttling_state", None)
44 if throttling is not None:
45 response[RATELIMIT_LIMIT_HEADER] = throttling.num_requests
46 response[RATELIMIT_REMAINING_HEADER] = throttling.num_requests - len(
47 throttling.history
48 )
49 if throttling.history: 49 ↛ 54line 49 didn't jump to line 54 because the condition on line 49 was always true
50 remaining_duration = throttling.duration - (
51 throttling.now - throttling.history[-1]
52 )
53 else:
54 remaining_duration = throttling.duration
55 response[RATELIMIT_RESET_HEADER] = int(remaining_duration)
56 return response