Skip to content

Handler for HTTP 429 Too Many Requests #1657

Open
@D-Mielewczyk

Description

Problem trying to solve

My integration tests regarding a JIRA bot, are sending too many requests and I am getting this exception:

[2023-05-16T13:22:56.021Z] tests/test_integration_stella.py:34: 
[2023-05-16T13:22:56.021Z] _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
[2023-05-16T13:22:56.021Z] .tox/integrate/lib/python3.6/site-packages/jira/resources.py:741: in delete
[2023-05-16T13:22:56.021Z]     super().delete(params={"deleteSubtasks": deleteSubtasks})
[2023-05-16T13:22:56.022Z] .tox/integrate/lib/python3.6/site-packages/jira/resources.py:443: in delete
[2023-05-16T13:22:56.022Z]     return self._session.delete(url=self.self, params=params)
[2023-05-16T13:22:56.022Z] .tox/integrate/lib/python3.6/site-packages/jira/resilientsession.py:204: in delete
[2023-05-16T13:22:56.022Z]     return self.__verb("DELETE", str(url), **kwargs)
[2023-05-16T13:22:56.022Z] .tox/integrate/lib/python3.6/site-packages/jira/resilientsession.py:189: in __verb
[2023-05-16T13:22:56.022Z]     raise_on_error(response, verb=verb, **kwargs)
[2023-05-16T13:22:56.022Z] _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
[2023-05-16T13:22:56.022Z] 
[2023-05-16T13:22:56.022Z] r = <Response [429]>, verb = 'DELETE'
[2023-05-16T13:22:56.022Z] kwargs = {'headers': {'User-Agent': 'python-requests/2.27.1', 'Accept-Encoding': 'gzip, deflate', 'Accept': 'application/json,*... 'no-cache', 'Content-Type': 'application/json', 'X-Atlassian-Token': 'no-check'}, 'params': {'deleteSubtasks': False}}
[2023-05-16T13:22:56.022Z] request = None, error = 'Rate limit exceeded.'
[2023-05-16T13:22:56.022Z] response = {'message': 'Rate limit exceeded.'}
[2023-05-16T13:22:56.022Z] 
[2023-05-16T13:22:56.022Z]     def raise_on_error(r: Optional[Response], verb="???", **kwargs):
[2023-05-16T13:22:56.022Z]         """Handle errors from a Jira Request
[2023-05-16T13:22:56.022Z]     
[2023-05-16T13:22:56.022Z]         Args:
[2023-05-16T13:22:56.022Z]             r (Optional[Response]): Response from Jira request
[2023-05-16T13:22:56.022Z]             verb (Optional[str]): Request type, e.g. POST. Defaults to "???".
[2023-05-16T13:22:56.022Z]     
[2023-05-16T13:22:56.022Z]         Raises:
[2023-05-16T13:22:56.022Z]             JIRAError: If Response is None
[2023-05-16T13:22:56.022Z]             JIRAError: for unhandled 400 status codes.
[2023-05-16T13:22:56.022Z]             JIRAError: for unhandled 200 status codes.
[2023-05-16T13:22:56.022Z]         """
[2023-05-16T13:22:56.022Z]         request = kwargs.get("request", None)
[2023-05-16T13:22:56.022Z]         # headers = kwargs.get('headers', None)
[2023-05-16T13:22:56.022Z]     
[2023-05-16T13:22:56.022Z]         if r is None:
[2023-05-16T13:22:56.022Z]             raise JIRAError(None, **kwargs)
[2023-05-16T13:22:56.022Z]     
[2023-05-16T13:22:56.022Z]         if r.status_code >= 400:
[2023-05-16T13:22:56.022Z]             error = ""
[2023-05-16T13:22:56.022Z]             if r.status_code == 403 and "x-authentication-denied-reason" in r.headers:
[2023-05-16T13:22:56.022Z]                 error = r.headers["x-authentication-denied-reason"]
[2023-05-16T13:22:56.022Z]             elif r.text:
[2023-05-16T13:22:56.022Z]                 try:
[2023-05-16T13:22:56.022Z]                     response = json.loads(r.text)
[2023-05-16T13:22:56.022Z]                     if "message" in response:
[2023-05-16T13:22:56.022Z]                         # Jira 5.1 errors
[2023-05-16T13:22:56.022Z]                         error = response["message"]
[2023-05-16T13:22:56.022Z]                     elif "errorMessages" in response and len(response["errorMessages"]) > 0:
[2023-05-16T13:22:56.022Z]                         # Jira 5.0.x error messages sometimes come wrapped in this array
[2023-05-16T13:22:56.022Z]                         # Sometimes this is present but empty
[2023-05-16T13:22:56.022Z]                         errorMessages = response["errorMessages"]
[2023-05-16T13:22:56.022Z]                         if isinstance(errorMessages, (list, tuple)):
[2023-05-16T13:22:56.022Z]                             error = errorMessages[0]
[2023-05-16T13:22:56.022Z]                         else:
[2023-05-16T13:22:56.022Z]                             error = errorMessages
[2023-05-16T13:22:56.022Z]                     # Catching only 'errors' that are dict. See https://github.com/pycontribs/jira/issues/350
[2023-05-16T13:22:56.022Z]                     elif (
[2023-05-16T13:22:56.022Z]                         "errors" in response
[2023-05-16T13:22:56.022Z]                         and len(response["errors"]) > 0
[2023-05-16T13:22:56.022Z]                         and isinstance(response["errors"], dict)
[2023-05-16T13:22:56.022Z]                     ):
[2023-05-16T13:22:56.022Z]                         # Jira 6.x error messages are found in this array.
[2023-05-16T13:22:56.022Z]                         error_list = response["errors"].values()
[2023-05-16T13:22:56.022Z]                         error = ", ".join(error_list)
[2023-05-16T13:22:56.022Z]                     else:
[2023-05-16T13:22:56.022Z]                         error = r.text
[2023-05-16T13:22:56.022Z]                 except ValueError:
[2023-05-16T13:22:56.022Z]                     error = r.text
[2023-05-16T13:22:56.022Z]             raise JIRAError(
[2023-05-16T13:22:56.022Z]                 error,
[2023-05-16T13:22:56.022Z]                 status_code=r.status_code,
[2023-05-16T13:22:56.022Z]                 url=r.url,
[2023-05-16T13:22:56.022Z]                 request=request,
[2023-05-16T13:22:56.022Z]                 response=r,
[2023-05-16T13:22:56.022Z] >               **kwargs,
[2023-05-16T13:22:56.022Z]             )
[2023-05-16T13:22:56.022Z] E           jira.exceptions.JIRAError: JiraError HTTP 429 url: https://jira.com/rest/api/2/issue/16664769?deleteSubtasks=False
[2023-05-16T13:22:56.022Z] E           	text: Rate limit exceeded.
[2023-05-16T13:22:56.022Z] E           	
[2023-05-16T13:22:56.023Z] E           	response headers = {'X-AREQUESTID': '982x5431467x7', 'X-ANODEID': 'node3', 'Referrer-Policy': 'strict-origin-when-cross-origin', 'X-XSS-Protection': '1; mode=block', 'X-Content-Type-Options': 'nosniff', 'X-Frame-Options': 'SAMEORIGIN', 'Content-Security-Policy': 'sandbox', 'Strict-Transport-Security': 'max-age=31536000', 'Set-Cookie': 'JSESSIONID=node3~CA507D81DCCBDECECC9A42AC867864AD; Path=/; Secure; HttpOnly, atlassian.xsrf.token=BACO-OOY2-7ZTP-186A_3fd5f15ab3dbe9f7aacddd6e5b248651e9563f7e_lin; Path=/; Secure; SameSite=None', 'X-Seraph-LoginReason': 'OK', 'X-RateLimit-Limit': '60', 'X-RateLimit-Remaining': '0', 'X-RateLimit-FillRate': '60', 'X-RateLimit-Interval-Seconds': '60', 'Retry-After': '0', 'Content-Encoding': 'gzip', 'Vary': 'User-Agent', 'Content-Type': 'application/json;charset=UTF-8', 'Content-Length': '54', 'Date': 'Tue, 16 May 2023 13:22:52 GMT'}
[2023-05-16T13:22:56.023Z] E           	response text = {"message":"Rate limit exceeded."}
[2023-05-16T13:22:56.023Z] 
[2023-05-16T13:22:56.023Z] .tox/integrate/lib/python3.6/site-packages/jira/resilientsession.py:70: JIRAError

Possible solution(s)

I wish that this library could retry requests that were met with HTTP 429 response after a certain amount of time.

Alternatives

Currently, I am using retry module alongside with some ugly time.sleep() lines. If anyone has a better workaround I would be happy to see it.

Additional Context

No response

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions