_shared.py 1.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566
  1. from __future__ import annotations
  2. from email.utils import parsedate_to_datetime
  3. from enum import Enum
  4. from http import HTTPStatus
  5. class RetryMode(Enum):
  6. """Controls how request content is retained for retries."""
  7. BUFFERED = "buffered"
  8. """Streaming requests are fully buffered in memory for retries on connection errors and server responses."""
  9. UNBUFFERED = "unbuffered"
  10. """Streaming requests are not buffered in memory and can only be retried on connection errors."""
  11. _IDEMPOTENT_METHODS = ("GET", "HEAD", "PUT", "DELETE")
  12. def parse_retry_after(header: str | None) -> float | None:
  13. if header is None:
  14. return None
  15. # of seconds, e.g., Retry-After: 120
  16. try:
  17. ret = int(header)
  18. if ret < 0:
  19. return None
  20. return float(ret)
  21. except ValueError:
  22. pass
  23. # Date, e.g., Retry-After: Wed, 21 Oct 2015 07:28:00 GMT
  24. try:
  25. dt = parsedate_to_datetime(header)
  26. except Exception:
  27. return None
  28. delta = (dt - dt.now(dt.tzinfo)).total_seconds()
  29. if delta < 0:
  30. return None
  31. return delta
  32. def default_should_retry_request(method: str) -> RetryMode:
  33. if method in _IDEMPOTENT_METHODS:
  34. return RetryMode.BUFFERED
  35. return RetryMode.UNBUFFERED
  36. def normalize_retry_mode(*, value: bool | RetryMode) -> RetryMode | None:
  37. if isinstance(value, RetryMode):
  38. return value
  39. return RetryMode.BUFFERED if value else None
  40. def default_should_retry_response(method: str, status: int | Exception) -> bool:
  41. if isinstance(status, ConnectionError):
  42. return True
  43. if method not in _IDEMPOTENT_METHODS:
  44. return False
  45. if isinstance(status, Exception):
  46. return True
  47. if status == HTTPStatus.TOO_MANY_REQUESTS:
  48. return True
  49. return status >= 500 and status != HTTPStatus.NOT_IMPLEMENTED