jira icon indicating copy to clipboard operation
jira copied to clipboard

KeyError: X-RateLimit-FillRate

Open adhamselman opened this issue 2 years ago • 2 comments

Bug summary

https://github.com/pycontribs/jira/blob/main/jira/resilientsession.py#L287 As far as i can tell, JIRA cloud does not set this response header when rate limiting. The only rate limiting headers i can see are: X-Ratelimit-Limit. X-Ratelimit-Remaining, X-Ratelimit-Reset and Retry-After.

Is there an existing issue for this?

  • [X] I have searched the existing issues

Jira Instance type

Jira Cloud (Hosted by Atlassian)

Jira instance version

No response

jira-python version

3.3.2

Python Interpreter version

3.9

Which operating systems have you used?

  • [ ] Linux
  • [ ] macOS
  • [X] Windows

Reproduction steps

# 1. Given a Jira client instance
jira: JIRA
# 2. When I call the function with argument x
jira.the_function(x)
# 3.
...

Stack trace

KeyError: X-RateLimit-FillRate

Expected behaviour

No KeyError and request is retried after 'retry-after' amount.

Additional Context

No response

adhamselman avatar Aug 16 '22 18:08 adhamselman

Thanks for creating this issue. Is there any timeline for when a fix will be out for this?

wesleyhamburger-okta avatar Sep 07 '22 12:09 wesleyhamburger-okta

Also, perhaps its worth noting that this was introduced in 3.3.0 not 3.3.2 -

https://github.com/pycontribs/jira/compare/3.2.0...3.3.0#diff-0605333df4b31ca7f97b2591e3bcba9c71ea201f4a88e383316e7f6c1c478e06R286

wesleyhamburger-okta avatar Sep 07 '22 15:09 wesleyhamburger-okta

Encountering this problem as well. Here's a little more for a stack trace.

  File "...\mycode", line 123, in my_function
    self.jira.my_function(
  File "...\AppData\Roaming\Python\Python39\site-packages\jira-3.4.1-py3.9.egg\jira\client.py", line 119, in wrapper
    result = func(*arg_list, **kwargs)
  File "...\AppData\Roaming\Python\Python39\site-packages\jira-3.4.1-py3.9.egg\jira\client.py", line 2497, in create_issue_link
    return self._session.post(url, data=json.dumps(data))
  File "...\appdata\local\programs\python\python39\lib\site-packages\requests\sessions.py", line 590, in post
    return self.request('POST', url, data=data, json=json, **kwargs)
  File "...\AppData\Roaming\Python\Python39\site-packages\jira-3.4.1-py3.9.egg\jira\resilientsession.py", line 213, in request
    if is_allowed_to_retry() and self.__recoverable(
  File "...\AppData\Roaming\Python\Python39\site-packages\jira-3.4.1-py3.9.egg\jira\resilientsession.py", line 286, in __recoverable
    number_of_tokens_issued_per_interval = response.headers[
  File "...\appdata\local\programs\python\python39\lib\site-packages\requests\structures.py", line 54, in __getitem__
    return self._store[key.lower()][1]
KeyError: 'x-ratelimit-fillrate'

BenKettlewell avatar Dec 17 '22 09:12 BenKettlewell

In my case the only headers to go with 429 error are:

{
    "content-length": "117",
    "cache-control": "no-cache",
    "content-type": "text/html"
}

The rest of the timeout logic works fine if I remove headers access. The only thing is this headers KeyError.

rehsals avatar Jan 18 '23 09:01 rehsals

In case it's helpful, I got this error and rich gave me this traceback, which includes the headers:

│ C:\Users\edp\AppData\Local\pypoetry\Cache\virtualenvs\trac-to-jira-ZN2GFehq-py3.10\lib\site-pack │
│ ages\requests\sessions.py:635 in post                                                            │
│                                                                                                  │
│   632 │   │   :rtype: requests.Response                                                          │
│   633 │   │   """                                                                                │
│   634 │   │                                                                                      │
│ ❱ 635 │   │   return self.request("POST", url, data=data, json=json, **kwargs)                   │
│   636 │                                                                                          │
│   637 │   def put(self, url, data=None, **kwargs):                                               │
│   638 │   │   r"""Sends a PUT request. Returns :class:`Response` object.                         │
│                                                                                                  │
│ ╭─────────────────────────────────────────── locals ───────────────────────────────────────────╮ │
│ │   data = '{"body": "xxx        │ │
│ │          xxx                                                                         │ │
│ │   json = None                                                                                │ │
│ │ kwargs = {}                                                                                  │ │
│ │   self = <jira.resilientsession.ResilientSession object at 0x000001CFADEEA1D0>               │ │
│ │    url = 'https://mydomain.atlassian.net/rest/api/2/issue/PM-809/comment'                     │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
│                                                                                                  │
│ C:\Users\edp\AppData\Local\pypoetry\Cache\virtualenvs\trac-to-jira-ZN2GFehq-py3.10\lib\site-pack │
│ ages\jira\resilientsession.py:213 in request                                                     │
│                                                                                                  │
│   210 │   │   │   # Decide if we should keep retrying                                            │
│   211 │   │   │   response_or_exception = response if response is not None else exception        │
│   212 │   │   │   retry_number += 1                                                              │
│ ❱ 213 │   │   │   if is_allowed_to_retry() and self.__recoverable(                               │
│   214 │   │   │   │   response_or_exception, url, method.upper(), retry_number                   │
│   215 │   │   │   ):                                                                             │
│   216 │   │   │   │   _prepare_retry_class.prepare(processed_kwargs)  # type: ignore[arg-type]   │
│                                                                                                  │
│ ╭─────────────────────────────────────────── locals ───────────────────────────────────────────╮ │
│ │             __class__ = <class 'jira.resilientsession.ResilientSession'>                     │ │
│ │  _prepare_retry_class = <jira.resilientsession.PassthroughRetryPrepare object at             │ │
│ │                         0x000001CFAC1F6380>                                                  │ │
│ │             exception = None                                                                 │ │
│ │   is_allowed_to_retry = <function ResilientSession.request.<locals>.is_allowed_to_retry at   │ │
│ │                         0x000001CFAF832290>                                                  │ │
│ │                kwargs = {                                                                    │ │
│ │                         │   'data': '{"body": "xxx,                                       │ │
│ │                         │   'json': None                                                     │ │
│ │                         }                                                                    │ │
│ │                method = 'POST'                                                               │ │
│ │      processed_kwargs = {                                                                    │ │
│ │                         │   'data': '{"body": "{0,                                       │ │
│ │                         │   'json': None,                                                    │ │
│ │                         │   'headers': {'User-Agent': 'python-requests/2.28.1',              │ │
│ │                         'Accept-Encoding': 'gzip, deflate', 'Accept':                        │ │
│ │                         'application/json,*.*;q=0.9', 'Connection': 'keep-alive',            │ │
│ │                         'Cache-Control': 'no-cache', 'Content-Type': 'application/json',     │ │
│ │                         'X-Atlassian-Token': 'no-check'}                                     │ │
│ │                         }                                                                    │ │
│ │              response = <Response [429]>                                                     │ │
│ │ response_or_exception = <Response [429]>                                                     │ │
│ │          retry_number = 1                                                                    │ │
│ │                  self = <jira.resilientsession.ResilientSession object at                    │ │
│ │                         0x000001CFADEEA1D0>                                                  │ │
│ │                   url = 'https://mydomain.atlassian.net/rest/api/2/issue/PM-809/comment'      │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
C:\Users\edp\AppData\Local\pypoetry\Cache\virtualenvs\trac-to-jira-ZN2GFehq-py3.10\lib\site-pack │
│ ages\jira\resilientsession.py:286 in __recoverable                                               │
│                                                                                                  │
│   283 │   │   if isinstance(response, Response):                                                 │
│   284 │   │   │   if response.status_code in [429]:                                              │
│   285 │   │   │   │   is_recoverable = True                                                      │
│ ❱ 286 │   │   │   │   number_of_tokens_issued_per_interval = response.headers[                   │
│   287 │   │   │   │   │   "X-RateLimit-FillRate"                                                 │
│   288 │   │   │   │   ]                                                                          │
│   289 │   │   │   │   token_issuing_rate_interval_seconds = response.headers[                    │
│                                                                                                  │
│ ╭──────────────────────────────────────── locals ────────────────────────────────────────╮       │
│ │        counter = 1                                                                     │       │
│ │ is_recoverable = True                                                                  │       │
│ │            msg = '<Response [429]>'                                                    │       │
│ │ request_method = 'POST'                                                                │       │
│ │       response = <Response [429]>                                                      │       │
│ │           self = <jira.resilientsession.ResilientSession object at 0x000001CFADEEA1D0> │       │
│ │            url = 'https://mydomain.atlassian.net/rest/api/2/issue/PM-809/comment'       │       │
│ ╰────────────────────────────────────────────────────────────────────────────────────────╯       │
│                                                                                                  │
│ C:\Users\edp\AppData\Local\pypoetry\Cache\virtualenvs\trac-to-jira-ZN2GFehq-py3.10\lib\site-pack │
│ ages\requests\structures.py:52 in __getitem__                                                    │
│                                                                                                  │
│    49 │   │   self._store[key.lower()] = (key, value)                                            │
│    50 │                                                                                          │
│    51 │   def __getitem__(self, key):                                                            │
│ ❱  52 │   │   return self._store[key.lower()][1]                                                 │
│    53 │                                                                                          │
│    54 │   def __delitem__(self, key):                                                            │
│    55 │   │   del self._store[key.lower()]                                                       │
│                                                                                                  │
│ ╭─────────────────────────────────────────── locals ───────────────────────────────────────────╮ │
│ │  key = 'X-RateLimit-FillRate'                                                                │ │
│ │ self = {'Date': 'Fri, 03 Mar 2023 17:16:58 GMT', 'Content-Type':                             │ │
│ │        'application/json;charset=UTF-8', 'Server': 'AtlassianEdge', 'Timing-Allow-Origin':   │ │
│ │        '*', 'X-Arequestid': '72d13a2f5f541fb91d7f7c41eca8c502', 'X-Aaccountid':              │ │
│ │        '633371672eaaa5dcfa14bdf8', 'Cache-Control': 'no-cache, no-store, no-transform',      │ │
│ │        'Expect-Ct':                                                                          │ │
│ │        'report-uri="https://web-security-reports.services.atlassian.com/expect-ct-report/at… │ │
│ │        max-age=86400', 'X-Content-Type-Options': 'nosniff', 'X-Xss-Protection': '1;          │ │
│ │        mode=block', 'Atl-Traceid': '87b7ede713a91672', 'Report-To': '{"endpoints": [{"url":  │ │
│ │        "https://dz8aopenkvv6s.cloudfront.net"}], "group": "endpoint-1",                      │ │
│ │        "include_subdomains": true, "max_age": 600}', 'Nel': '{"failure_fraction": 0.001,     │ │
│ │        "include_subdomains": true, "max_age": 600, "report_to": "endpoint-1"}',              │ │
│ │        'Strict-Transport-Security': 'max-age=63072000; includeSubDomains; preload',          │ │
│ │        'Transfer-Encoding': 'chunked'}                                                       │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
KeyError: 'x-ratelimit-fillrate'

edporteous avatar Mar 03 '23 18:03 edporteous

Hopefully in a release later this weekend

adehad avatar Mar 11 '23 12:03 adehad