Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/google/resumable_media/requests/_request_helpers.py: 46%

48 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-03-26 06:07 +0000

1# Copyright 2017 Google Inc. 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14 

15"""Shared utilities used by both downloads and uploads. 

16 

17This utilities are explicitly catered to ``requests``-like transports. 

18""" 

19 

20import requests.exceptions 

21import urllib3.exceptions # type: ignore 

22 

23import time 

24 

25from google.resumable_media import common 

26from google.resumable_media import _helpers 

27 

28_DEFAULT_RETRY_STRATEGY = common.RetryStrategy() 

29_SINGLE_GET_CHUNK_SIZE = 8192 

30# The number of seconds to wait to establish a connection 

31# (connect() call on socket). Avoid setting this to a multiple of 3 to not 

32# Align with TCP Retransmission timing. (typically 2.5-3s) 

33_DEFAULT_CONNECT_TIMEOUT = 61 

34# The number of seconds to wait between bytes sent from the server. 

35_DEFAULT_READ_TIMEOUT = 60 

36 

37_CONNECTION_ERROR_CLASSES = ( 

38 requests.exceptions.ConnectionError, 

39 requests.exceptions.ChunkedEncodingError, 

40 requests.exceptions.Timeout, 

41 urllib3.exceptions.ProtocolError, 

42 ConnectionError, # Python 3.x only, superclass of ConnectionResetError. 

43) 

44 

45 

46class RequestsMixin(object): 

47 """Mix-in class implementing ``requests``-specific behavior. 

48 

49 These are methods that are more general purpose, with implementations 

50 specific to the types defined in ``requests``. 

51 """ 

52 

53 @staticmethod 

54 def _get_status_code(response): 

55 """Access the status code from an HTTP response. 

56 

57 Args: 

58 response (~requests.Response): The HTTP response object. 

59 

60 Returns: 

61 int: The status code. 

62 """ 

63 return response.status_code 

64 

65 @staticmethod 

66 def _get_headers(response): 

67 """Access the headers from an HTTP response. 

68 

69 Args: 

70 response (~requests.Response): The HTTP response object. 

71 

72 Returns: 

73 ~requests.structures.CaseInsensitiveDict: The header mapping (keys 

74 are case-insensitive). 

75 """ 

76 return response.headers 

77 

78 @staticmethod 

79 def _get_body(response): 

80 """Access the response body from an HTTP response. 

81 

82 Args: 

83 response (~requests.Response): The HTTP response object. 

84 

85 Returns: 

86 bytes: The body of the ``response``. 

87 """ 

88 return response.content 

89 

90 

91class RawRequestsMixin(RequestsMixin): 

92 @staticmethod 

93 def _get_body(response): 

94 """Access the response body from an HTTP response. 

95 

96 Args: 

97 response (~requests.Response): The HTTP response object. 

98 

99 Returns: 

100 bytes: The body of the ``response``. 

101 """ 

102 if response._content is False: 

103 response._content = b"".join( 

104 response.raw.stream(_SINGLE_GET_CHUNK_SIZE, decode_content=False) 

105 ) 

106 response._content_consumed = True 

107 return response._content 

108 

109 

110def wait_and_retry(func, get_status_code, retry_strategy): 

111 """Attempts to retry a call to ``func`` until success. 

112 

113 Expects ``func`` to return an HTTP response and uses ``get_status_code`` 

114 to check if the response is retry-able. 

115 

116 ``func`` is expected to raise a failure status code as a 

117 common.InvalidResponse, at which point this method will check the code 

118 against the common.RETRIABLE list of retriable status codes. 

119 

120 Will retry until :meth:`~.RetryStrategy.retry_allowed` (on the current 

121 ``retry_strategy``) returns :data:`False`. Uses 

122 :func:`_helpers.calculate_retry_wait` to double the wait time (with jitter) 

123 after each attempt. 

124 

125 Args: 

126 func (Callable): A callable that takes no arguments and produces 

127 an HTTP response which will be checked as retry-able. 

128 get_status_code (Callable[Any, int]): Helper to get a status code 

129 from a response. 

130 retry_strategy (~google.resumable_media.common.RetryStrategy): The 

131 strategy to use if the request fails and must be retried. 

132 

133 Returns: 

134 object: The return value of ``func``. 

135 """ 

136 total_sleep = 0.0 

137 num_retries = 0 

138 # base_wait will be multiplied by the multiplier on the first retry. 

139 base_wait = float(retry_strategy.initial_delay) / retry_strategy.multiplier 

140 

141 # Set the retriable_exception_type if possible. We expect requests to be 

142 # present here and the transport to be using requests.exceptions errors, 

143 # but due to loose coupling with the transport layer we can't guarantee it. 

144 

145 while True: # return on success or when retries exhausted. 

146 error = None 

147 try: 

148 response = func() 

149 except _CONNECTION_ERROR_CLASSES as e: 

150 error = e # Fall through to retry, if there are retries left. 

151 except common.InvalidResponse as e: 

152 # An InvalidResponse is only retriable if its status code matches. 

153 # The `process_response()` method on a Download or Upload method 

154 # will convert the status code into an exception. 

155 if get_status_code(e.response) in common.RETRYABLE: 

156 error = e # Fall through to retry, if there are retries left. 

157 else: 

158 raise # If the status code is not retriable, raise w/o retry. 

159 else: 

160 return response 

161 

162 base_wait, wait_time = _helpers.calculate_retry_wait( 

163 base_wait, retry_strategy.max_sleep, retry_strategy.multiplier 

164 ) 

165 num_retries += 1 

166 total_sleep += wait_time 

167 

168 # Check if (another) retry is allowed. If retries are exhausted and 

169 # no acceptable response was received, raise the retriable error. 

170 if not retry_strategy.retry_allowed(total_sleep, num_retries): 

171 raise error 

172 

173 time.sleep(wait_time)