-
Notifications
You must be signed in to change notification settings - Fork 24
[CIVIS-11753] FIX retries: recursion error and wait strategy #525
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
fbe7d65
d73feb9
ca8baf3
f985f3a
e142749
0c24763
2df86ff
832ccba
6a7c6d8
bb82032
7ab3b1b
3515397
f9ca80c
4b2f4e1
1267387
a7c89e0
cc3f0b7
ac5e82a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import os | ||
|
|
||
|
|
||
| def get_api_key(api_key): | ||
| """Pass-through if `api_key` is not None otherwise tries the CIVIS_API_KEY | ||
| environment variable. | ||
| """ | ||
| if api_key is not None: # always prefer user given one | ||
| return api_key | ||
| api_key = os.environ.get("CIVIS_API_KEY", None) | ||
| if api_key is None: | ||
| raise EnvironmentError( | ||
| "No Civis API key found. Please store in " | ||
| "CIVIS_API_KEY environment variable" | ||
| ) | ||
| return api_key | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,4 @@ | ||
| import logging | ||
| import os | ||
|
|
||
| import tenacity | ||
| from tenacity.wait import wait_base | ||
|
|
@@ -19,36 +18,32 @@ | |
| wait=tenacity.wait_random_exponential(multiplier=2, max=60), | ||
| stop=(tenacity.stop_after_delay(600) | tenacity.stop_after_attempt(10)), | ||
| retry_error_callback=lambda retry_state: retry_state.outcome.result(), | ||
| reraise=True, | ||
| ) | ||
| """ | ||
|
|
||
| # Explicitly set the available globals and locals | ||
| # to mitigate risk of unwanted code execution | ||
| DEFAULT_RETRYING = eval( # nosec | ||
| DEFAULT_RETRYING_STR, | ||
| {"tenacity": tenacity, "__builtins__": {}}, # globals | ||
| {}, # locals | ||
| ) | ||
|
|
||
|
|
||
| def get_api_key(api_key): | ||
| """Pass-through if `api_key` is not None otherwise tries the CIVIS_API_KEY | ||
| environment variable. | ||
| """ | ||
| if api_key is not None: # always prefer user given one | ||
| return api_key | ||
| api_key = os.environ.get("CIVIS_API_KEY", None) | ||
| if api_key is None: | ||
| raise EnvironmentError( | ||
| "No Civis API key found. Please store in " | ||
| "CIVIS_API_KEY environment variable" | ||
| ) | ||
| return api_key | ||
| def get_default_retrying(): | ||
| """Return a new instance of the default tenacity.Retrying.""" | ||
| # Explicitly set the available globals and locals | ||
| # to mitigate risk of unwanted code execution | ||
| return eval( # nosec | ||
| DEFAULT_RETRYING_STR, | ||
| {"tenacity": tenacity, "__builtins__": {}}, # globals | ||
| {}, # locals | ||
| ) | ||
|
|
||
|
|
||
| def retry_request(method, prepared_req, session, retrying=None): | ||
| retry_conditions = None | ||
| retrying = retrying if retrying else DEFAULT_RETRYING | ||
|
|
||
| # New tenacity.Retrying instance needed, whether it's a copy of the user-provided | ||
| # one or it's one based on civis-python's default settings. | ||
| retrying = retrying.copy() if retrying else get_default_retrying() | ||
|
|
||
| # If retries are exhausted, | ||
| # raise the last exception encountered, not tenacity's RetryError. | ||
| retrying.reraise = True | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The docstring for |
||
|
|
||
| def _make_request(req, sess): | ||
| """send the prepared session request""" | ||
|
|
@@ -66,33 +61,31 @@ def _make_request(req, sess): | |
|
|
||
| if retry_conditions: | ||
| retrying.retry = retry_conditions | ||
| retrying.wait = wait_for_retry_after_header(fallback=retrying.wait) | ||
| retrying.wait = wait_at_least_retry_after_header(base=retrying.wait) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The "fallback" idea doesn't seem right. I've modified code to "wait at least rety-after seconds, compared to the specified wait strategy" instead. |
||
| response = retrying(_make_request, prepared_req, session) | ||
| return response | ||
|
|
||
| response = _make_request(prepared_req, session) | ||
| return response | ||
|
|
||
|
|
||
| class wait_for_retry_after_header(wait_base): | ||
| """Wait strategy that first looks for Retry-After header. If not | ||
| present it uses the fallback strategy as the wait param""" | ||
| class wait_at_least_retry_after_header(wait_base): | ||
| """Wait strategy for at least `Retry-After` seconds (if present from header)""" | ||
|
|
||
| def __init__(self, fallback): | ||
| self.fallback = fallback | ||
| def __init__(self, base): | ||
| self.base = base | ||
|
|
||
| def __call__(self, retry_state): | ||
| # retry_state is an instance of tenacity.RetryCallState. | ||
| # The .outcome property contains the result/exception | ||
| # that came from the underlying function. | ||
| result_headers = retry_state.outcome._result.headers | ||
| retry_after = result_headers.get("Retry-After") or result_headers.get( | ||
| "retry-after" | ||
| ) | ||
| headers = retry_state.outcome._result.headers | ||
|
|
||
| try: | ||
| log.info("Retrying after {} seconds".format(retry_after)) | ||
| return int(retry_after) | ||
| retry_after = float( | ||
| headers.get("Retry-After") or headers.get("retry-after") or "0.0" | ||
| ) | ||
jacksonlee-civis marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| except (TypeError, ValueError): | ||
| pass | ||
| return self.fallback(retry_state) | ||
| retry_after = 0.0 | ||
| # Wait at least retry_after seconds (compared to the user-specified wait). | ||
| return max(retry_after, self.base(retry_state)) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't new code. Moved this function here from
_utils.py(now renamed_retries.pyfor clarity and not having a cluttered "utilities" module around).