From 4fd0ec229befef29775511ecce0fd0d3b3e90da0 Mon Sep 17 00:00:00 2001 From: Soham Panda Date: Thu, 12 Mar 2026 21:45:03 -0500 Subject: [PATCH] api: Add auto_retry_rate_limits option to handle 429 errors Python's API Library does not handle HTTP 429 (RATE_LIMIT_HIT) errors automatically. If a client exceeds the message limit, Python will throw an error and force users to manually implement a method to pause the thread and try again. To resolve this, the __init__ constructor of the Client class has been given a new addition of an opt-in parameter initialized to False to keep existing behavior. This catches the 429 status code in do_api_query(). It ensures that the client safely parses the retry-after header, pauses the sleep thread using the time.sleep() function, and finally tries the message request again. Fixes: #773 --- zulip/zulip/__init__.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/zulip/zulip/__init__.py b/zulip/zulip/__init__.py index 82190a51d..e3bb2d669 100644 --- a/zulip/zulip/__init__.py +++ b/zulip/zulip/__init__.py @@ -393,6 +393,7 @@ def __init__( config_file: Optional[str] = None, verbose: bool = False, retry_on_errors: bool = True, + auto_retry_rate_limits: bool = False, site: Optional[str] = None, client: Optional[str] = None, cert_bundle: Optional[str] = None, @@ -476,6 +477,7 @@ def __init__( self.api_key = api_key self.email = email self.verbose = verbose + self.auto_retry_rate_limits = auto_retry_rate_limits if site is not None: if site.startswith("localhost"): site = "http://" + site @@ -655,6 +657,20 @@ def end_error_retry(succeeded: bool) -> None: self.has_connected = True + # On Rate Limit (429) errors, retry after the delay the server asked us to wait + if res.status_code == 429 and self.auto_retry_rate_limits: + try: + # Zulip will return the wait time in the either the (JSON) body or header + wait_time = float(res.headers.get("Retry-After", 1.0)) + except Exception: + wait_time = 1.0 + if self.verbose: + print( + f"Rate limit hit! Sleeping for {wait_time} seconds before retrying..." + ) + time.sleep(wait_time) + continue + # On 50x errors, try again after a short sleep if str(res.status_code).startswith("5") and error_retry( f" (server {res.status_code})"