From 34f657fee4ea45bb8f8c493a608514160b1c4c28 Mon Sep 17 00:00:00 2001 From: Thibault Ducret Date: Tue, 29 Sep 2026 00:22:55 +0200 Subject: [PATCH] Improve rate limit backoff strategy --- allocine/api.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/allocine/api.py b/allocine/api.py index 37a875b..6301b12 100644 --- a/allocine/api.py +++ b/allocine/api.py @@ -10,6 +10,10 @@ THEATERS_BASE_URL = "https://www.allocine.fr/salle/cinema" +class _RateLimitError(Exception): + pass + + class AllocineApi: """Client to process requests with the Allocine APIs.""" @@ -103,7 +107,7 @@ def _request( try: response = self._fetch(url, expected_status, *args, not_found_ok=not_found_ok, **kwargs) - except (ValueError, httpx2.HTTPError): + except (_RateLimitError, ValueError, httpx2.HTTPError): if cached is not CACHE_MISS and cached.can_serve_on_error(): return cached.to_response() raise @@ -113,6 +117,14 @@ def _request( self.cache.set(url, response, params) return response + @backoff.on_exception( + backoff.expo, + _RateLimitError, + factor=5, + max_value=120, + max_tries=7, + jitter=None, + ) @backoff.on_exception(backoff.expo, ValueError, max_tries=5, max_time=30) def _fetch( self, @@ -123,6 +135,8 @@ def _fetch( **kwargs, ) -> httpx2.Response: ret = self.session.get(url, *args, **kwargs) + if ret.status_code == 429: + raise _RateLimitError(f"{url!r}: rate limit exceeded") if ret.status_code not in {expected_status, 304} and not (not_found_ok and ret.status_code == 404): raise ValueError("{!r} : expected status {}, received {}".format(url, expected_status, ret.status_code)) return ret