request.py 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. from typing import Any, Callable, Type
  2. import requests
  3. from requests.exceptions import (
  4. ChunkedEncodingError,
  5. ConnectionError,
  6. )
  7. from tenacity import (
  8. retry,
  9. retry_if_exception,
  10. retry_if_exception_type,
  11. stop_after_delay,
  12. wait_exponential,
  13. )
  14. from urllib3.exceptions import IncompleteRead
  15. from openhands.core.logger import openhands_logger as logger
  16. from openhands.utils.tenacity_stop import stop_if_should_exit
  17. def is_server_error(exception):
  18. return (
  19. isinstance(exception, requests.HTTPError)
  20. and exception.response.status_code >= 500
  21. )
  22. def is_404_error(exception):
  23. return (
  24. isinstance(exception, requests.HTTPError)
  25. and exception.response.status_code == 404
  26. )
  27. def is_429_error(exception):
  28. return (
  29. isinstance(exception, requests.HTTPError)
  30. and exception.response.status_code == 429
  31. )
  32. def is_503_error(exception):
  33. return (
  34. isinstance(exception, requests.HTTPError)
  35. and exception.response.status_code == 503
  36. )
  37. def is_502_error(exception):
  38. return (
  39. isinstance(exception, requests.HTTPError)
  40. and exception.response.status_code == 502
  41. )
  42. DEFAULT_RETRY_EXCEPTIONS = [
  43. ConnectionError,
  44. IncompleteRead,
  45. ChunkedEncodingError,
  46. ]
  47. def send_request_with_retry(
  48. session: requests.Session,
  49. method: str,
  50. url: str,
  51. timeout: int,
  52. retry_exceptions: list[Type[Exception]] | None = None,
  53. retry_fns: list[Callable[[Exception], bool]] | None = None,
  54. **kwargs: Any,
  55. ) -> requests.Response:
  56. exceptions_to_catch = retry_exceptions or DEFAULT_RETRY_EXCEPTIONS
  57. retry_condition = retry_if_exception_type(
  58. tuple(exceptions_to_catch)
  59. ) | retry_if_exception(is_502_error)
  60. if retry_fns is not None:
  61. for fn in retry_fns:
  62. retry_condition |= retry_if_exception(fn)
  63. # wait a few more seconds to get the timeout error from client side
  64. kwargs['timeout'] = timeout + 10
  65. @retry(
  66. stop=stop_after_delay(timeout) | stop_if_should_exit(),
  67. wait=wait_exponential(multiplier=1, min=4, max=20),
  68. retry=retry_condition,
  69. reraise=True,
  70. before_sleep=lambda retry_state: logger.debug(
  71. f'Retrying {method} request to {url} due to {retry_state.outcome.exception()}. Attempt {retry_state.attempt_number}'
  72. ),
  73. )
  74. def _send_request_with_retry():
  75. response = session.request(method, url, **kwargs)
  76. response.raise_for_status()
  77. return response
  78. return _send_request_with_retry()