def request_csv_download( self, request_method, request_url, tmp_csv_file_name, tmp_directory, request_params=None, request_data=None, request_retry=None, request_retry_func=None, request_retry_excps=None, request_retry_http_status_codes=None, request_retry_excps_func=None, request_headers=None, request_auth=None, request_label=None, build_request_curl=True, allow_redirects=True, verify=True, skip_first_row=False, skip_last_row=False, read_first_row=False, csv_delimiter=',', csv_header=None, encoding_write=None, encoding_read=None, decode_unicode=False, ): """Download and Read CSV file. Args: request_method: request_method for the new :class:`Request` object. request_url: URL for the new :class:`Request` object. tmp_csv_file_name: Provide temporary name for downloaded CSV tmp_directory: Provide temporary directory to hold downloaded CSV request_params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`. request_data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`. request_retry: (optional) Retry configuration. request_headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`. request_auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth. allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed. verify: (optional) whether the SSL cert will be verified. A CA_BUNDLE path can also be provided. Defaults to ``True``. skip_first_row: (optional) Skip first row if it does not contain column headers. skip_last_row: (optional) Skip first row if it does not contain column values. read_first_row: (optional) Read first row separate from data returned. csv_delimiter: (optional) Delimiter character, default comma ','. csv_header: encoding_write: encoding_read: decode_unicode: Returns: Generator containing CSV data by rows in JSON dictionary format. """ _request_label = 'Request Download CSV File' request_label = f'{request_label}: {_request_label}' if request_label is not None else _request_label log.debug(f'{request_label}: Start', extra={ 'request_url': request_url, 'encoding_write': encoding_write, 'encoding_read': encoding_read, }) timer_start = dt.datetime.now() _attempts = 0 _tries = 60 _delay = 10 while _tries: _attempts += 1 log.info(f'{request_label}: Attempt: {_attempts}', extra={ 'request_url': request_url, }) response = self.mv_request.request( request_method=request_method, request_url=request_url, request_params=request_params, request_data=request_data, request_retry=request_retry, request_retry_func=request_retry_func, request_retry_excps=request_retry_excps, request_retry_http_status_codes=request_retry_http_status_codes, request_retry_excps_func=request_retry_excps_func, request_headers=request_headers, request_auth=request_auth, build_request_curl=build_request_curl, allow_redirects=allow_redirects, verify=verify, stream=True, request_label=request_label) if response is None: log.error(f'{request_label}: No response', extra={ 'request_url': request_url, }) raise TuneRequestModuleError( error_message=f'{request_label}: No response', error_code=TuneRequestErrorCodes.REQ_ERR_REQUEST, ) http_status_code = response.status_code timer_end = dt.datetime.now() timer_delta = timer_end - timer_start response_time_secs = timer_delta.seconds response_headers = None if hasattr(response, 'headers'): response_headers = \ json.loads( json.dumps( dict(response.headers) ) ) log.debug(f'{request_label}: Response Status', extra={ 'http_status_code': http_status_code, 'response_time_secs': response_time_secs, 'response_url': response.url, 'response_headers': safe_dict(response_headers), }) (tmp_csv_file_path, tmp_csv_file_size) = self.download_csv( response, tmp_directory, tmp_csv_file_name, request_label=request_label, encoding_write=encoding_write, decode_unicode=decode_unicode) if tmp_csv_file_path is not None: break _tries -= 1 if not _tries: log.error(f'{request_label}: Exhausted Retries', extra={ 'tries': _tries, 'request_url': request_url, }) raise TuneRequestModuleError( error_message=f'{request_label}: Exhausted Retries', error_code=TuneRequestErrorCodes.REQ_ERR_RETRY_EXHAUSTED) log.info(f'{request_label}: Performing Retry', extra={ 'tries': _tries, 'delay': _delay, 'request_url': request_url, }) time.sleep(_delay) log.info(f'{request_label}: Finished', extra={ 'file_path': tmp_csv_file_path, 'file_size': bytes_to_human(tmp_csv_file_size), 'encoding_read': encoding_read, }) log.debug( f'{request_label}: Usage', extra=env_usage(tmp_directory), ) with open(file=tmp_csv_file_path, mode='r', encoding=encoding_read) as csv_file_r: if read_first_row: csv_report_name = csv_file_r.readline() csv_report_name = re.sub('\"', '', csv_report_name) csv_report_name = re.sub('\n', '', csv_report_name) log.info( f'{request_label}: Report', extra={'csv_report_name': csv_report_name}, ) elif skip_first_row: next(csv_file_r) csv_file_header = next(csv_file_r) csv_header_actual = \ [h.strip() for h in csv_file_header.split(csv_delimiter)] csv_header_hr = [] index = 0 for column_name in csv_header_actual: csv_header_hr.append({'index': index, 'name': column_name}) index += 1 log.debug( f'{request_label}: Content Header', extra={'csv_header': csv_header_hr}, ) csv_fieldnames = csv_header if csv_header else csv_header_actual csv_dict_reader = csv.DictReader(csv_file_r, fieldnames=csv_fieldnames, delimiter=csv_delimiter) if skip_last_row: for row in csv_skip_last_row(csv_dict_reader): yield row else: for row in csv_dict_reader: yield row
def stream_csv(self, request_url, request_params, csv_delimiter=',', request_retry=None, request_headers=None, request_label=None, chunk_size=1024, decode_unicode=False, remove_bom_length=0): """Stream CSV and Yield JSON Args: request_url: request_params: csv_delimiter: request_retry: request_headers: chunk_size: decode_unicode: remove_bom_length: Returns: """ _request_label = 'Stream CSV' request_label = f'{request_label}: {_request_label}' if request_label is not None else _request_label log.info(f'{request_label}: Start', extra={'report_url': request_url}) response = self.mv_request.request( request_method='GET', request_url=request_url, request_params=request_params, request_retry=request_retry, request_headers=request_headers, stream=True, request_label=f'{request_label}: Request') log.info(f'{request_label}: Response', extra={ 'response_status_code': response.status_code, 'response_headers': response.headers, 'report_url': request_url }) request_curl = command_line_request_curl( request_method='GET', request_url=request_url, request_params=request_params, request_headers=request_headers, ) validate_response(response=response, request_curl=request_curl, request_label="Stream CSV") response_content_type = response.headers.get('Content-Type', None) response_transfer_encoding = response.headers.get( 'Transfer-Encoding', None) response_http_status_code = response.status_code log.debug(f'{request_label}: Status: Details', extra={ 'response_content_type': response_content_type, 'response_transfer_encoding': response_transfer_encoding, 'response_http_status_code': response_http_status_code }) log.debug(f'{request_label}: Usage', extra=env_usage()) line_count = 0 csv_keys_str = None csv_keys_list = None csv_keys_list_len = None pre_str_line = None for bytes_line in response.iter_lines(chunk_size=chunk_size, decode_unicode=decode_unicode): if bytes_line: # filter out keep-alive new chunks line_count += 1 str_line = bytes_line.decode("utf-8") if line_count == 1: if remove_bom_length > 0: str_line = str_line[remove_bom_length:] csv_keys_list = str_line.split(csv_delimiter) csv_keys_list = [ csv_key.strip() for csv_key in csv_keys_list ] csv_keys_list_len = len(csv_keys_list) continue if pre_str_line is not None: str_line = pre_str_line + str_line pre_str_line = None csv_values_str = str_line.replace('\n', ' ').replace('\r', ' ') csv_values_str_io = io.StringIO(csv_values_str) reader = csv.reader(csv_values_str_io, delimiter=csv_delimiter) csv_values_list = None for row in reader: csv_values_list = row csv_values_list_len = len(csv_values_list) if csv_values_list_len < csv_keys_list_len: pre_str_line = str_line continue if csv_keys_list_len != csv_values_list_len: log.error(f'{request_label}: Mismatch: CSV Key', extra={ 'line': line_count, 'csv_keys_list_len': csv_keys_list_len, 'csv_keys_str': csv_keys_str, 'csv_keys_list': csv_keys_list, 'csv_values_list_len': csv_values_list_len, 'csv_values_str': csv_values_str, 'csv_values_list': csv_values_list, }) raise TuneRequestModuleError( error_message= f"Mismatch: CSV Key '{csv_keys_str}': Values '{csv_values_str}'", error_code=TuneRequestErrorCodes. REQ_ERR_UNEXPECTED_VALUE) json_data_row = {} for idx, csv_key in enumerate(csv_keys_list): csv_value = csv_values_list[idx] json_data_row.update({csv_key: csv_value.strip('"')}) yield json_data_row
def request_json_download( self, request_method, request_url, tmp_json_file_name, tmp_directory, request_params=None, request_data=None, request_retry=None, request_retry_func=None, request_retry_excps=None, request_retry_excps_func=None, request_headers=None, request_auth=None, request_label=None, build_request_curl=False, allow_redirects=True, verify=True, encoding_write=None, encoding_read=None, ): """Download and Read JSON file. Args: request_method: request_method for the new :class:`Request` object. request_url: URL for the new :class:`Request` object. tmp_json_file_name: Provide temporary name for downloaded CSV tmp_directory: Provide temporary directory to hold downloaded CSV request_params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`. request_data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`. request_retry: (optional) Retry configuration. request_headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`. request_auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth. build_request_curl: (optional) Build a copy-n-paste curl for command line that provides same request as this call. allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed. verify: (optional) whether the SSL cert will be verified. A CA_BUNDLE path can also be provided. Defaults to ``True``. encoding_write: encoding_read: decode_unicode: Returns: Generator containing JSON data by rows in JSON dictionary format. """ _request_label = 'Request Download JSON File' request_label = f'{request_label}: {_request_label}' if request_label is not None else _request_label log.info(f'{request_label}: Start', extra={ 'request_url': request_url, 'encoding_write': encoding_write, 'encoding_read': encoding_read, }) timer_start = dt.datetime.now() _attempts = 0 _tries = 60 _delay = 10 while _tries: _attempts += 1 log.debug(f'{request_label}: Download', extra={ 'attempts': _attempts, 'request_url': request_url, }) response = self.mv_request.request( request_method=request_method, request_url=request_url, request_params=request_params, request_data=request_data, request_retry=request_retry, request_retry_func=request_retry_func, request_retry_excps=request_retry_excps, request_retry_excps_func=request_retry_excps_func, request_headers=request_headers, request_auth=request_auth, build_request_curl=build_request_curl, allow_redirects=allow_redirects, verify=verify, stream=True, request_label=request_label) if response is None: log.error(f'{request_label}: No response', extra={ 'request_url': request_url, }) raise TuneRequestModuleError( error_message=f'{request_label}: No response', error_code=TuneRequestErrorCodes.REQ_ERR_REQUEST) http_status_code = response.status_code timer_end = dt.datetime.now() timer_delta = timer_end - timer_start response_time_secs = timer_delta.seconds response_headers = None if hasattr(response, 'headers'): response_headers = \ json.loads( json.dumps( dict(response.headers) ) ) log.debug(f'{request_label}: Response Status', extra={ 'http_status_code': http_status_code, 'response_time_secs': response_time_secs, 'response_url': response.url, 'response_headers': safe_dict(response_headers), }) if not os.path.exists(tmp_directory): os.mkdir(tmp_directory) tmp_json_file_path = f'{tmp_directory}/{tmp_json_file_name}' if os.path.exists(tmp_json_file_path): log.debug( f'{request_label}: Removing', extra={'file_path': tmp_json_file_path}, ) os.remove(tmp_json_file_path) mode_write = 'wb' if encoding_write is None else 'w' log.debug(f'{request_label}: Finished', extra={ 'file_path': tmp_json_file_path, 'mode_write': mode_write, 'encoding_write': encoding_write, }) log.debug(f'{request_label}: Usage', extra=env_usage(tmp_directory)) chunk_total_sum = 0 with open(file=tmp_json_file_path, mode=mode_write, encoding=encoding_write) as json_raw_file_w: log.debug(f'{request_label}: Response Raw: Started', extra={ 'file_path': tmp_json_file_path, }) _tries -= 1 error_exception = None error_details = None chunk_size = 8192 try: raw_response = response.raw while True: chunk = raw_response.read(chunk_size, decode_content=True) if not chunk: break chunk_total_sum += chunk_size json_raw_file_w.write(chunk) json_raw_file_w.flush() os.fsync(json_raw_file_w.fileno()) log.debug(f'{request_label}: By Chunk: Completed', extra={ 'file_path': tmp_json_file_path, }) break except requests.exceptions.ChunkedEncodingError as chunked_encoding_ex: error_exception = base_class_name(chunked_encoding_ex) error_details = get_exception_message(chunked_encoding_ex) log.warning(f'{request_label}: Error: {error_exception}', extra={ 'error_details': error_details, 'chunk_total_sum': chunk_total_sum, }) if not _tries: log.error( f'{request_label}: Exhausted Retries: Error: {error_exception}' ) raise except http_client.IncompleteRead as incomplete_read_ex: error_exception = base_class_name(incomplete_read_ex) error_details = get_exception_message(incomplete_read_ex) log.warning(f'{request_label}: IncompleteRead', extra={ 'error_exception': error_exception, 'error_details': error_details, 'chunk_total_sum': chunk_total_sum, }) if not _tries: log.error( f'{request_label}: Exhausted Retries: Error: {error_exception}' ) raise except requests.exceptions.RequestException as request_ex: log.error(f'{request_label}: Request Exception', extra={ 'error_exception': base_class_name(request_ex), 'error_details': get_exception_message(request_ex), 'chunk_total_sum': chunk_total_sum, }) raise except Exception as ex: log.error(f'{request_label}: Unexpected Exception', extra={ 'error_exception': base_class_name(ex), 'error_details': get_exception_message(ex), 'chunk_total_sum': chunk_total_sum, }) raise if not _tries: log.error(f'{request_label}: Exhausted Retries', extra={ 'tries': _tries, 'request_url': request_url, }) raise TuneRequestModuleError( error_message= f'{request_label}: Exhausted Retries: {request_url}', error_request_curl=self.built_request_curl, error_code=TuneRequestErrorCodes. REQ_ERR_RETRY_EXHAUSTED) log.info(f'{request_label}: Performing Retry', extra={ 'tries': _tries, 'delay': _delay, 'request_url': request_url, }) time.sleep(_delay) tmp_json_file_size = os.path.getsize(tmp_json_file_path) bom_enc, bom_len, bom_header = detect_bom(tmp_json_file_path) log.info(f'{request_label}: By Chunk: Completed: Details', extra={ 'file_path': tmp_json_file_path, 'file_size': bytes_to_human(tmp_json_file_size), 'chunk_total_sum': chunk_total_sum, 'bom_encoding': bom_enc, }) if bom_enc == 'gzip': tmp_json_gz_file_path = f"{tmp_json_file_path}.gz" os.rename(src=tmp_json_file_path, dst=tmp_json_gz_file_path) with open(file=tmp_json_file_path, mode=mode_write, encoding=encoding_write) as json_file_w: log.debug(f'{request_label}: GZip: Started', extra={ 'file_path': tmp_json_file_path, }) with gzip.open(tmp_json_gz_file_path, 'r') as gzip_file_r: json_file_w.write(gzip_file_r.read()) response_extra = { 'file_path': tmp_json_file_path, 'file_size': bytes_to_human(tmp_json_file_size), } log.info(f'{request_label}: Read Downloaded', extra=response_extra) json_download = None with open(tmp_json_file_path, mode='r') as json_file_r: json_file_content = json_file_r.read() try: json_download = json.loads(json_file_content) except ValueError as json_decode_ex: pprint(json_file_content) response_extra.update({ 'json_file_content': json_file_content, 'json_file_content_len': len(json_file_content) }) handle_json_decode_error(response_decode_ex=json_decode_ex, response=response, response_extra=response_extra, request_label=request_label, request_curl=self.built_request_curl) except Exception as ex: pprint(json_file_content) response_extra.update({ 'json_file_content': json_file_content, 'json_file_content_len': len(json_file_content) }) log.error( f'{request_label}: Failed: Exception', extra=response_extra, ) handle_json_decode_error(response_decode_ex=ex, response=response, response_extra=response_extra, request_label=request_label, request_curl=self.built_request_curl) response_extra.update({'json_file_content_len': len(json_download)}) log.info(f'{request_label}: Finished', extra=response_extra) return json_download
def download_csv( self, response, tmp_directory, tmp_csv_file_name, request_label=None, encoding_write=None, decode_unicode=False, ): _request_label = 'Download CSV' request_label = f'{request_label}: {_request_label}' if request_label is not None else _request_label log.debug(f'{request_label}: Start') if not os.path.exists(tmp_directory): os.mkdir(tmp_directory) tmp_csv_file_path = \ f'{tmp_directory}/{tmp_csv_file_name}' if os.path.exists(tmp_csv_file_path): log.debug( f'{request_label}: Removing previous CSV', extra={'file_path': tmp_csv_file_path}, ) os.remove(tmp_csv_file_path) mode_write = 'wb' if encoding_write is None else 'w' log.debug(f'{request_label}: Details', extra={ 'file_path': tmp_csv_file_path, 'mode_write': mode_write, 'encoding_write': encoding_write, }) chunk_total_sum = 0 with open(file=tmp_csv_file_path, mode=mode_write, encoding=encoding_write) as csv_file_wb: log.debug(f'{request_label}: By Chunk: Started', extra={ 'file_path': tmp_csv_file_path, 'request_label': request_label }) error_exception = None error_details = None try: for chunk in response.iter_content( chunk_size=8192, decode_unicode=decode_unicode): if not chunk: break chunk_total_sum += 8192 csv_file_wb.write(chunk) csv_file_wb.flush() os.fsync(csv_file_wb.fileno()) log.debug(f'{request_label}: By Chunk: Completed', extra={ 'file_path': tmp_csv_file_path, }) except requests.exceptions.ChunkedEncodingError as chunked_encoding_ex: error_exception = base_class_name(chunked_encoding_ex) error_details = get_exception_message(chunked_encoding_ex) log.warning(f'{request_label}: ChunkedEncodingError', extra={ 'error_exception': error_exception, 'error_details': error_details, 'chunk_total_sum': bytes_to_human(chunk_total_sum), }) return (None, 0) except http_client.IncompleteRead as incomplete_read_ex: error_exception = base_class_name(incomplete_read_ex) error_details = get_exception_message(incomplete_read_ex) log.warning(f'{request_label}: IncompleteRead', extra={ 'error_exception': error_exception, 'error_details': error_details, 'chunk_total_sum': bytes_to_human(chunk_total_sum), }) return (None, 0) except requests.exceptions.RequestException as request_ex: log.error(f'{request_label}: Request Exception', extra={ 'error_exception': base_class_name(request_ex), 'error_details': get_exception_message(request_ex), 'chunk_total_sum': bytes_to_human(chunk_total_sum), }) raise except Exception as ex: log.error(f'{request_label}: Unexpected Exception', extra={ 'error_exception': base_class_name(ex), 'error_details': get_exception_message(ex), 'chunk_total_sum': bytes_to_human(chunk_total_sum), }) raise tmp_csv_file_size = os.path.getsize(tmp_csv_file_path) bom_enc, bom_len, bom_header = detect_bom(tmp_csv_file_path) log.info(f'{request_label}: By Chunk: Completed: Details', extra={ 'file_path': tmp_csv_file_path, 'file_size': bytes_to_human(tmp_csv_file_size), 'chunk_total_sum': bytes_to_human(chunk_total_sum), 'bom_encoding': bom_enc }) log.debug("Download CSV: Usage", extra=env_usage(tmp_directory)) tmp_csv_file_name_wo_ext = \ os.path.splitext( os.path.basename(tmp_csv_file_name) )[0] tmp_csv_file_path_wo_bom = \ f'{tmp_directory}/{tmp_csv_file_name_wo_ext}_wo_bom.csv' if os.path.exists(tmp_csv_file_path_wo_bom): os.remove(tmp_csv_file_path_wo_bom) bom_enc, bom_len = remove_bom(tmp_csv_file_path, tmp_csv_file_path_wo_bom) log.debug(f'{request_label}: Encoding', extra={ 'bom_enc': bom_enc, 'bom_len': bom_len }) if bom_len > 0: tmp_csv_file_path = tmp_csv_file_path_wo_bom return (tmp_csv_file_path, tmp_csv_file_size)
def request( self, request_method, request_url, request_params=None, request_data=None, request_json=None, request_retry=None, request_retry_excps=None, request_retry_http_status_codes=None, request_retry_func=None, request_retry_excps_func=None, request_headers=None, request_auth=None, cookie_payload=None, build_request_curl=True, allow_redirects=True, verify=True, stream=False, request_label=None ): """Request data from remote source with retries. Args: request_method: request_method for the new :class:`Request` object. request_url: URL for the new :class:`Request` object. request_params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`. request_data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`. request_json: (optional) json data to send in the body of the :class:`Request`. request_retry: (optional) Retry configuration. request_retry_func: (optional) Retry function, alternative to request_retry_excps. request_retry_excps: An exception or a tuple of exceptions to catch. request_headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`. request_auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth. allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed. verify: (optional) whether the SSL cert will be verified. A CA_BUNDLE path can also be provided. Defaults to ``True``. stream: (optional) if ``False``, the response content will be immediately downloaded. request_label: Returns: requests.Response: Data result if success or None if error. Raises: ServiceGatewayTimeoutError: Upon any timeout condition. Exception: Upon error within this request_method. Notes: * tries: the maximum number of attempts. default: 1. * delay: initial delay between attempts. default: 1. * max_delay: the maximum value of delay. default: None (no limit). * backoff: multiplier applied to delay between attempts. default: 1 (no backoff). * jitter: extra seconds added to delay between attempts. default: 0. """ if request_label is None: request_label = 'Request' self.logger.debug('{}: Start'.format(request_label)) timeout = None if not verify: requests.packages.urllib3.disable_warnings(InsecureRequestWarning) if request_method: request_method = request_method.upper() if request_label is None: request_label = 'Request' if not request_retry: request_retry = {} if 'timeout' not in request_retry: request_retry['timeout'] = self._REQUEST_CONFIG['timeout'] if 'tries' not in request_retry: request_retry['tries'] = self._REQUEST_CONFIG['tries'] if 'delay' not in request_retry: request_retry['delay'] = self._REQUEST_CONFIG['delay'] self._prep_request_retry(request_retry, request_retry_http_status_codes) if not self.tune_request: self.tune_request = TuneRequest( retry_tries=self.retry_tries, retry_backoff=self.retry_backoff, retry_codes=self.request_retry_http_status_codes ) key_user_agent = 'User-Agent' header_user_agent = {key_user_agent: __USER_AGENT__} if request_headers: if key_user_agent not in request_headers: request_headers.update(header_user_agent) else: request_headers = header_user_agent kwargs = { 'request_method': request_method, 'request_url': request_url, 'request_params': request_params, 'request_data': request_data, 'request_json': request_json, 'request_headers': request_headers, 'request_auth': request_auth, 'cookie_payload': cookie_payload, 'request_label': request_label, 'timeout': timeout, 'build_request_curl': build_request_curl, 'allow_redirects': allow_redirects, 'verify': verify, 'stream': stream } time_start_req = dt.datetime.now() if request_retry_func is None: request_retry_func = self.request_retry_func if request_retry_excps_func is None: request_retry_excps_func = self.request_retry_excps_func if request_retry_excps_func is None: request_retry_excps_func = mv_request_retry_excps_func if request_retry_http_status_codes is not None: self.request_retry_http_status_codes = request_retry_http_status_codes if request_retry_excps is not None: self.request_retry_excps = request_retry_excps extra_request = copy.copy(kwargs) if request_retry: extra_request.update({'request_retry': request_retry}) if request_label: extra_request.update({'request_label': request_label}) if request_retry: extra_request.update({'request_retry': request_retry}) if request_retry_func: extra_request.update({'request_retry_func': request_retry_func}) if request_retry_http_status_codes: extra_request.update({'request_retry_http_status_codes': request_retry_http_status_codes}) if request_retry_excps: extra_request.update({'request_retry_excps': request_retry_excps}) if request_retry_excps: extra_request.update({'request_retry_excps_func': request_retry_excps_func}) extra_request.update(env_usage()) self.logger.debug('{}: Start: Details'.format(request_label), extra=extra_request) try: self._prep_request_retry(request_retry, request_retry_http_status_codes) response = self._request_retry( call_func=self._request_data, fargs=None, fkwargs=kwargs, timeout=timeout, request_label=request_label, request_retry_func=request_retry_func, request_retry_excps_func=request_retry_excps_func ) except ( requests.exceptions.ConnectTimeout, requests.exceptions.ReadTimeout, requests.exceptions.Timeout, ) as ex_req_timeout: raise TuneRequestServiceError( error_message='{}: Exception: Timeout'.format(request_label), errors=ex_req_timeout, error_request_curl=self.built_request_curl, error_code=TuneRequestErrorCodes.GATEWAY_TIMEOUT ) except requests.exceptions.HTTPError as ex_req_http: raise TuneRequestModuleError( error_message='{}: Exception: HTTP Error'.format(request_label), errors=ex_req_http, error_request_curl=self.built_request_curl, error_code=TuneRequestErrorCodes.REQ_ERR_REQUEST_HTTP ) except ( requests.exceptions.ConnectionError, requests.exceptions.ProxyError, requests.exceptions.SSLError, ) as ex_req_connect: raise TuneRequestModuleError( error_message='{}: Exception: {}'.format(request_label, base_class_name(ex_req_connect)), errors=ex_req_connect, error_request_curl=self.built_request_curl, error_code=TuneRequestErrorCodes.REQ_ERR_REQUEST_CONNECT ) except (BrokenPipeError, ConnectionError,) as ex_ose_connect: raise TuneRequestModuleError( error_message='{}: Exception: OSE {}'.format(request_label, base_class_name(ex_ose_connect)), errors=ex_ose_connect, error_request_curl=self.built_request_curl, error_code=TuneRequestErrorCodes.REQ_ERR_CONNECT ) except requests.packages.urllib3.exceptions.ProtocolError as ex_req_urllib3_protocol: raise TuneRequestModuleError( error_message='{}: Exception: Urllib3: Protocol Error'.format(request_label), errors=ex_req_urllib3_protocol, error_request_curl=self.built_request_curl, error_code=TuneRequestErrorCodes.REQ_ERR_REQUEST_CONNECT ) except requests.packages.urllib3.exceptions.ReadTimeoutError as ex_req_urllib3_read_timeout: raise TuneRequestServiceError( error_message='{}: Exception: Urllib3: Read Timeout Error'.format(request_label), errors=ex_req_urllib3_read_timeout, error_request_curl=self.built_request_curl, error_code=TuneRequestErrorCodes.GATEWAY_TIMEOUT ) except requests.exceptions.TooManyRedirects as ex_req_redirects: raise TuneRequestModuleError( error_message='{}: Exception: Too Many Redirects'.format(request_label), errors=ex_req_redirects, error_request_curl=self.built_request_curl, error_code=TuneRequestErrorCodes.REQ_ERR_REQUEST_REDIRECTS ) except requests.exceptions.RetryError as ex_req_adapter_retry: # Expected structure of RetryError: # RetryError has list 'args' of len 1: RetryError.args[0] == MaxRetryError (from urlib3) # Structure of MaxRetryError: MaxRetryError.reason == ResponseError (from urlib3) if len(ex_req_adapter_retry.args) == 1 and hasattr(ex_req_adapter_retry.args[0], 'reason'): response_err = ex_req_adapter_retry.args[0].reason status_codes = [int(s) for s in response_err.args[0].split() if s.isdigit()] if len(status_codes) == 1: http_status_code = status_codes[0] http_status_type = http_status_code_to_type(http_status_code) error_kwargs = { 'errors': ex_req_adapter_retry, 'error_code': http_status_code, 'error_reason': response_err.args[0], 'error_message': "Request: Exception: HTTPAdapter: Retries exhausted on: '{}'" .format(request_url), 'error_request_curl': self.built_request_curl, } self.logger.error( '{}: Exception: HTTPAdapter: Max retry error'.format(request_label), extra=error_kwargs ) if http_status_type == HttpStatusType.CLIENT_ERROR: raise TuneRequestClientError(**error_kwargs) elif http_status_type == HttpStatusType.SERVER_ERROR: raise TuneRequestServiceError(**error_kwargs) # THIS BLOCK SHOULD NOT BE ACTUALLY ACCESSED. IF IT DOES LOOK INTO IT: self.logger.error( '{}: Unexpected RetryError occurred'.format(request_label), extra={'request_curl': self.built_request_curl} ) raise TuneRequestModuleError( error_message='{}: Exception: HTTPAdapter: Unexpected Retry Error'.format(request_label), errors=ex_req_adapter_retry, error_request_curl=self.built_request_curl, error_code=TuneRequestErrorCodes.REQ_ERR_RETRY_EXHAUSTED, ) except requests.exceptions.RequestException as ex_req_request: raise TuneRequestModuleError( error_message='{}: Exception: Request Error'.format(request_label), errors=ex_req_request, error_request_curl=self.built_request_curl, error_code=TuneRequestErrorCodes.REQ_ERR_REQUEST ) except TuneRequestBaseError: raise except Exception as ex: print_traceback(ex) raise TuneRequestModuleError( error_message='{}: Exception: Unexpected'.format(request_label), errors=ex, error_request_curl=self.built_request_curl, error_code=TuneRequestErrorCodes.REQ_ERR_SOFTWARE ) time_end_req = dt.datetime.now() diff_req = time_end_req - time_start_req request_time_msecs = int(diff_req.total_seconds() * 1000) self.logger.info( '{}: Finished'.format(request_label), extra={ 'request_time_msecs': request_time_msecs, } ) self.logger.debug('{}: Usage'.format(request_label), extra=env_usage()) return response