| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent fe5e608 commit 3235c48
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,7 +2,6 @@ | |||
| 2 | 2 | ||
| 3 | 3 | import os | |
| 4 | 4 | import re | |
| 5 | - import time | ||
| 6 | 5 | from typing import ( | |
| 7 | 6 | Any, | |
| 8 | 7 | BinaryIO, | |
@@ -718,7 +717,12 @@ def http_request( | |||
| 718 | 717 | send_data = self._backend.prepare_send_data(files, post_data, raw) | |
| 719 | 718 | opts["headers"]["Content-type"] = send_data.content_type | |
| 720 | 719 | ||
| 721 | - cur_retries = 0 | ||
| 720 | + retry = utils.Retry( | ||
| 721 | + max_retries=max_retries, | ||
| 722 | + obey_rate_limit=obey_rate_limit, | ||
| 723 | + retry_transient_errors=retry_transient_errors, | ||
| 724 | + ) | ||
| 725 | + | ||
| 722 | 726 | while True: | |
| 723 | 727 | try: | |
| 724 | 728 | result = self._backend.http_request( | |
@@ -733,46 +737,17 @@ def http_request( | |||
| 733 | 737 | **opts, | |
| 734 | 738 | ) | |
| 735 | 739 | except (requests.ConnectionError, requests.exceptions.ChunkedEncodingError): | |
| 736 | - if retry_transient_errors and ( | ||
| 737 | - max_retries == -1 or cur_retries < max_retries | ||
| 738 | - ): | ||
| 739 | - wait_time = 2**cur_retries * 0.1 | ||
| 740 | - cur_retries += 1 | ||
| 741 | - time.sleep(wait_time) | ||
| 740 | + if retry.handle_retry(): | ||
| 742 | 741 | continue | |
| 743 | - | ||
| 744 | 742 | raise | |
| 745 | 743 | ||
| 746 | 744 | self._check_redirects(result.response) | |
| 747 | 745 | ||
| 748 | 746 | if 200 <= result.status_code < 300: | |
| 749 | 747 | return result.response | |
| 750 | 748 | ||
| 751 | - def should_retry() -> bool: | ||
| 752 | - if result.status_code == 429 and obey_rate_limit: | ||
| 753 | - return True | ||
| 754 | - | ||
| 755 | - if not retry_transient_errors: | ||
| 756 | - return False | ||
| 757 | - if result.status_code in gitlab.const.RETRYABLE_TRANSIENT_ERROR_CODES: | ||
| 758 | - return True | ||
| 759 | - if result.status_code == 409 and "Resource lock" in result.reason: | ||
| 760 | - return True | ||
| 761 | - | ||
| 762 | - return False | ||
| 763 | - | ||
| 764 | - if should_retry(): | ||
| 765 | - # Response headers documentation: | ||
| 766 | - # https://docs.gitlab.com/ee/user/admin_area/settings/user_and_ip_rate_limits.html#response-headers | ||
| 767 | - if max_retries == -1 or cur_retries < max_retries: | ||
| 768 | - wait_time = 2**cur_retries * 0.1 | ||
| 769 | - if "Retry-After" in result.headers: | ||
| 770 | - wait_time = int(result.headers["Retry-After"]) | ||
| 771 | - elif "RateLimit-Reset" in result.headers: | ||
| 772 | - wait_time = int(result.headers["RateLimit-Reset"]) - time.time() | ||
| 773 | - cur_retries += 1 | ||
| 774 | - time.sleep(wait_time) | ||
| 775 | - continue | ||
| 749 | + if retry.handle_retry_on_status(result): | ||
| 750 | + continue | ||
| 776 | 751 | ||
| 777 | 752 | error_message = result.content | |
| 778 | 753 | try: | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,6 +2,7 @@ | |||
| 2 | 2 | import email.message | |
| 3 | 3 | import logging | |
| 4 | 4 | import pathlib | |
| 5 | + import time | ||
| 5 | 6 | import traceback | |
| 6 | 7 | import urllib.parse | |
| 7 | 8 | import warnings | |
@@ -10,6 +11,7 @@ | |||
| 10 | 11 | import requests | |
| 11 | 12 | ||
| 12 | 13 | from gitlab import const, types | |
| 14 | + from gitlab._backends import requests_backend | ||
| 13 | 15 | ||
| 14 | 16 | ||
| 15 | 17 | class _StdoutStream: | |
@@ -85,6 +87,64 @@ def response_content( | |||
| 85 | 87 | return None | |
| 86 | 88 | ||
| 87 | 89 | ||
| 90 | + class Retry: | ||
| 91 | + def __init__( | ||
| 92 | + self, | ||
| 93 | + max_retries: int, | ||
| 94 | + obey_rate_limit: Optional[bool] = True, | ||
| 95 | + retry_transient_errors: Optional[bool] = False, | ||
| 96 | + ) -> None: | ||
| 97 | + self.cur_retries = 0 | ||
| 98 | + self.max_retries = max_retries | ||
| 99 | + self.obey_rate_limit = obey_rate_limit | ||
| 100 | + self.retry_transient_errors = retry_transient_errors | ||
| 101 | + | ||
| 102 | + def _retryable_status_code( | ||
| 103 | + self, | ||
| 104 | + result: requests_backend.RequestsResponse, | ||
| 105 | + ) -> bool: | ||
| 106 | + if result.status_code == 429 and self.obey_rate_limit: | ||
| 107 | + return True | ||
| 108 | + | ||
| 109 | + if not self.retry_transient_errors: | ||
| 110 | + return False | ||
| 111 | + if result.status_code in const.RETRYABLE_TRANSIENT_ERROR_CODES: | ||
| 112 | + return True | ||
| 113 | + if result.status_code == 409 and "Resource lock" in result.reason: | ||
| 114 | + return True | ||
| 115 | + | ||
| 116 | + return False | ||
| 117 | + | ||
| 118 | + def handle_retry_on_status(self, result: requests_backend.RequestsResponse) -> bool: | ||
| 119 | + if not self._retryable_status_code(result): | ||
| 120 | + return False | ||
| 121 | + | ||
| 122 | + # Response headers documentation: | ||
| 123 | + # https://docs.gitlab.com/ee/user/admin_area/settings/user_and_ip_rate_limits.html#response-headers | ||
| 124 | + if self.max_retries == -1 or self.cur_retries < self.max_retries: | ||
| 125 | + wait_time = 2**self.cur_retries * 0.1 | ||
| 126 | + if "Retry-After" in result.headers: | ||
| 127 | + wait_time = int(result.headers["Retry-After"]) | ||
| 128 | + elif "RateLimit-Reset" in result.headers: | ||
| 129 | + wait_time = int(result.headers["RateLimit-Reset"]) - time.time() | ||
| 130 | + self.cur_retries += 1 | ||
| 131 | + time.sleep(wait_time) | ||
| 132 | + return True | ||
| 133 | + | ||
| 134 | + return False | ||
| 135 | + | ||
| 136 | + def handle_retry(self) -> bool: | ||
| 137 | + if self.retry_transient_errors and ( | ||
| 138 | + self.max_retries == -1 or self.cur_retries < self.max_retries | ||
| 139 | + ): | ||
| 140 | + wait_time = 2**self.cur_retries * 0.1 | ||
| 141 | + self.cur_retries += 1 | ||
| 142 | + time.sleep(wait_time) | ||
| 143 | + return True | ||
| 144 | + | ||
| 145 | + return False | ||
| 146 | + | ||
| 147 | + | ||
| 88 | 148 | def _transform_types( | |
| 89 | 149 | data: Dict[str, Any], | |
| 90 | 150 | custom_types: Dict[str, Any], | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,58 @@ | |||
| 1 | + import time | ||
| 2 | + from unittest import mock | ||
| 3 | + | ||
| 4 | + import pytest | ||
| 5 | + import requests | ||
| 6 | + | ||
| 7 | + from gitlab import utils | ||
| 8 | + from gitlab._backends import requests_backend | ||
| 9 | + | ||
| 10 | + | ||
| 11 | + def test_handle_retry_on_status_ignores_unknown_status_code(): | ||
| 12 | + retry = utils.Retry(max_retries=1, retry_transient_errors=True) | ||
| 13 | + response = requests.Response() | ||
| 14 | + response.status_code = 418 | ||
| 15 | + backend_response = requests_backend.RequestsResponse(response) | ||
| 16 | + | ||
| 17 | + assert retry.handle_retry_on_status(backend_response) is False | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + def test_handle_retry_on_status_accepts_retry_after_header( | ||
| 21 | + monkeypatch: pytest.MonkeyPatch, | ||
| 22 | + ): | ||
| 23 | + mock_sleep = mock.Mock() | ||
| 24 | + monkeypatch.setattr(time, "sleep", mock_sleep) | ||
| 25 | + | ||
| 26 | + retry = utils.Retry(max_retries=1) | ||
| 27 | + response = requests.Response() | ||
| 28 | + response.status_code = 429 | ||
| 29 | + response.headers["Retry-After"] = "1" | ||
| 30 | + backend_response = requests_backend.RequestsResponse(response) | ||
| 31 | + | ||
| 32 | + assert retry.handle_retry_on_status(backend_response) is True | ||
| 33 | + assert isinstance(mock_sleep.call_args[0][0], int) | ||
| 34 | + | ||
| 35 | + | ||
| 36 | + def test_handle_retry_on_status_accepts_ratelimit_reset_header( | ||
| 37 | + monkeypatch: pytest.MonkeyPatch, | ||
| 38 | + ): | ||
| 39 | + mock_sleep = mock.Mock() | ||
| 40 | + monkeypatch.setattr(time, "sleep", mock_sleep) | ||
| 41 | + | ||
| 42 | + retry = utils.Retry(max_retries=1) | ||
| 43 | + response = requests.Response() | ||
| 44 | + response.status_code = 429 | ||
| 45 | + response.headers["RateLimit-Reset"] = str(int(time.time() + 1)) | ||
| 46 | + backend_response = requests_backend.RequestsResponse(response) | ||
| 47 | + | ||
| 48 | + assert retry.handle_retry_on_status(backend_response) is True | ||
| 49 | + assert isinstance(mock_sleep.call_args[0][0], float) | ||
| 50 | + | ||
| 51 | + | ||
| 52 | + def test_handle_retry_on_status_returns_false_when_max_retries_reached(): | ||
| 53 | + retry = utils.Retry(max_retries=0) | ||
| 54 | + response = requests.Response() | ||
| 55 | + response.status_code = 429 | ||
| 56 | + backend_response = requests_backend.RequestsResponse(response) | ||
| 57 | + | ||
| 58 | + assert retry.handle_retry_on_status(backend_response) is False | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments