示例#1
0
def test_empty():
    assert safe_int("") == 0
    assert safe_int('') == 0
    assert safe_int('', 7) == 7
    assert safe_float("") == 0.0
    assert safe_float('') == 0.0
    assert safe_float('', default=7.7) == 7.7
    assert safe_dict("") == {}
    assert safe_dict('') == {}
    assert safe_dict('', {'Max': 'Ohad'}) == {'Max': 'Ohad'}
示例#2
0
def test_None():
    assert safe_int(None) == 0
    assert safe_int(None, 7) == 7
    assert safe_float(None) == 0.0
    assert safe_float(None, default=7.7) == 7.7
    assert safe_dict(None) == {}
    assert safe_dict(None, {'Jeff': 'Tanner'}) == {'Jeff': 'Tanner'}
    assert safe_str(None) == ''
    assert safe_str(None, "stas") == "stas"
    assert safe_cast(None, str) is None
    assert safe_cast(None, str, default="TuliTuliTuli") == "TuliTuliTuli"
示例#3
0
def test_safe_dict():
    # test basic dict:
    assert isinstance(safe_dict({'key': 'value'}), dict)
    # test fail:
    with pytest.raises(
            TypeError,
            message='Expecting TypeError because passing not iterable value.'):
        assert safe_dict(5)
    with pytest.raises(
            ValueError,
            message='Expecting ValueError because str not castable to dict.'):
        assert safe_dict('Hello Jeff')
示例#4
0
def test_safe_dict():
    # test basic dict:
    assert isinstance(safe_dict({'key': 'value'}), dict)
    # test fail:
    with pytest.raises(TypeError, message='Expecting TypeError because passing not iterable value.') as excinfo:
        assert safe_dict(5)
    assert 'Error: \'int\' object is not iterable' in str(excinfo.value)
    assert 'Value: 5' in str(excinfo.value)
    assert 'Cast: int to dict' in str(excinfo.value)

    with pytest.raises(ValueError, message='Expecting ValueError because str not castable to dict.') as excinfo:
        assert safe_dict('Hello Jeff')
    assert 'Error: Dictionary update sequence element #0 has length 1; 2 is required' \
           in str(excinfo.value)
    assert 'Value: Hello Jeff' in str(excinfo.value)
    assert 'Cast: str to dict' in str(excinfo.value)
    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 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 _check_v3_job_status_on_queue(self, export_job, request_retry=None, request_label="TMC v3 Job Status On Queue"):
        """Status of Export Report.

        Args:
            export_job:
            request_retry:

        Returns:

        """
        request_label = "v3 Logs Advertisers Check Export Status"

        request_url = \
            self.tune_mat_request_path(
                mat_api_version="v3",
                controller=self.controller,
                action="exports/{}".format(
                    export_job
                )
            )

        self.logger.info((
            "TMC v3 Logs Advertisers Base: Check Export Status: "
            "Logs '{}': "
            "Action: 'exports status', "
            "Status of Export Report for "
            "Job Handle: '{}'"
        ).format(self.logs_advertisers_type, export_job))

        tries = -1  # default: -1 (indefinite)
        delay = 10
        jitter = 0
        max_delay = 60

        if request_retry:
            if 'delay' in request_retry:
                delay = request_retry['delay']
            if 'jitter' in request_retry:
                jitter = request_retry['jitter']
            if 'max_delay' in request_retry:
                max_delay = request_retry['max_delay']

        request_params = {"session_token": self.session_token}

        self.logger.debug("TMC v3 Logs Advertisers Base: Check Export Status", extra={'request_url': request_url})

        self.logger.debug(
            "TMC v3 Logs Advertisers Base: Check Export Status: Request Retry",
            extra={'tries': tries,
                   'delay': delay,
                   'jitter': jitter,
                   'max_delay': max_delay}
        )

        self.logger.debug(
            "TMC v3 Logs Advertisers Base: Check Export Status: Request",
            extra={'request_params': safe_dict(request_params)}
        )

        report_url = None
        _attempts = 1
        export_percent_complete = 0

        export_status_action = 'exports status'

        self.logger.warning(
            "TMC v3 Logs Advertisers Base: Check Export Status",
            extra={'job': export_job,
                   'attempt': _attempts,
                   'action': export_status_action}
        )

        time.sleep(10)

        _tries, _delay = tries, delay
        while True:
            try:
                response = self.mv_request.request(
                    request_method="GET",
                    request_url=request_url,
                    request_params=request_params,
                    request_retry=None,
                    request_retry_http_status_codes=None,
                    request_retry_func=self.tune_v3_request_retry_func,
                    request_retry_excps_func=None,
                    request_label=request_label
                )

            except TuneRequestBaseError as tmc_req_ex:
                self.logger.error(
                    "TMC v3 Logs Advertisers Base: Check Export Status: Failed",
                    extra=tmc_req_ex.to_dict(),
                )
                raise

            except TuneReportingError as tmc_rep_ex:
                self.logger.error(
                    "TMC v3 Logs Advertisers Base: Check Export Status: Failed",
                    extra=tmc_rep_ex.to_dict(),
                )
                raise

            except Exception as ex:
                print_traceback(ex)

                self.logger.error(
                    "TMC v3 Logs Advertisers Base: Check Export Status: Failed",
                    extra={'error': get_exception_message(ex)}
                )
                raise

            http_status_successful = is_http_status_type(
                http_status_code=response.status_code, http_status_type=HttpStatusType.SUCCESSFUL
            )

            if not http_status_successful:
                raise TuneReportingError(
                    error_message="Failed to get export status on queue: {}".format(response.status_code),
                    error_code=TuneReportingErrorCodes.REP_ERR_REQUEST
                )

            json_response = response.json()

            export_percent_complete = 0
            if "percent_complete" in json_response:
                export_percent_complete = \
                    safe_int(json_response["percent_complete"])

            self.logger.info(
                "TMC v3 Logs Advertisers Base: Check Job Export Status",
                extra={
                    'job': export_job,
                    'response_status_code': json_response["status"],
                    'export_percent_complete': safe_int(export_percent_complete)
                }
            )

            if (export_percent_complete == 100 and json_response["status"] == "complete" and json_response["url"]):
                report_url = json_response["url"]

                self.logger.info(
                    "TMC v3 Logs Advertisers Base: Check Job Export Status: Completed",
                    extra={
                        'job': export_job,
                        'report_url': report_url,
                        'request_label': request_label,
                        'export_percent_complete': safe_int(export_percent_complete)
                    }
                )

                break

            if tries >= 0:
                _tries -= 1
                if _tries == 0:
                    self.logger.error(("TMC v3 Logs Advertisers Base: "
                                       "Check Job Export Status: Exhausted Retries"),
                                      extra={
                                          'attempt': _attempts,
                                          'tries': _tries,
                                          'action': export_status_action,
                                          'request_label': request_label,
                                          'export_percent_complete': export_percent_complete
                                      })

                    raise TuneReportingError(
                        error_message=(
                            "TMC v3 Logs Advertisers Base: "
                            "Check Job Export Status: "
                            "Exhausted Retries: "
                            "Percent Completed: {}"
                        ).format(export_percent_complete),
                        error_code=TuneReportingErrorCodes.REP_ERR_JOB_STOPPED
                    )

            _attempts += 1

            self.logger.warning(
                "TMC v3 Logs Advertisers Base: Check Export Status",
                extra={'attempt': _attempts,
                       'job': export_job,
                       'delay': _delay,
                       'action': 'exports status'}
            )
            time.sleep(_delay)

            _delay += jitter
            _delay = min(_delay, max_delay)

        if export_percent_complete == 100 and not report_url:
            raise TuneReportingError(
                error_message=(
                    "TMC v3 Logs Advertisers Base: Check Job Export Status: "
                    "Download report URL: Undefined"
                )
            )

        self.logger.info(
            "TMC v3 Logs Advertisers Base: Check Job Export Status: Finished",
            extra={
                'attempt': _attempts,
                'action': export_status_action,
                'report_url': report_url,
                'request_label': request_label,
                'export_percent_complete': export_percent_complete,
                'job': export_job
            }
        )

        return report_url
    def _request_data(
        self,
        request_method,
        request_url,
        request_params=None,
        request_data=None,
        request_json=None,
        request_headers=None,
        request_auth=None,
        cookie_payload=None,
        request_label=None,
        timeout=60,
        build_request_curl=True,
        allow_redirects=True,
        verify=True,
        stream=False
    ):
        """Request Data from requests.

        Args:
            request_method: request_method for the new :class:`Request` object.
            logger: logging instance
            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_headers: (optional) Dictionary of HTTP Headers to send
                with the :class:`Request`.
            request_auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth.
            timeout: (optional) How long to wait for the server to send data
                before giving up.
            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.

        Returns:
            requests.Response

        """

        if request_label is None:
            request_label = 'Request Data'

        if not request_method:
            raise TuneRequestValueError(error_message="Parameter 'request_method' not defined")
        if not request_url:
            raise TuneRequestValueError(error_message="Parameter 'request_url' not defined")

        self.built_request_curl = None

        self.logger.debug(
            '{}: Session: Details'.format(request_label),
            extra={'cookie_payload': self.tune_request.session.cookies.get_dict(),
                   'request_label': request_label}
        )

        response = None
        headers = None

        if request_headers:
            headers = request_headers

        request_method = request_method.upper()

        if request_data and isinstance(request_data, str):
            if len(request_data) <= 20:
                request_data_extra = request_data
            else:
                request_data_extra = request_data[:20] + ' ...'
        else:
            request_data_extra = safe_str(request_data)

        request_extra = {
            'request_method': request_method,
            'request_url': request_url,
            'timeout': timeout,
            'request_params': safe_dict(request_params),
            'request_data': request_data_extra,
            'request_headers': safe_dict(headers),
            'request_label': request_label
        }

        self.logger.debug('{}: Details'.format(request_label), extra=request_extra)

        self.built_request_curl = None

        kwargs = {}
        if headers:
            kwargs.update({'headers': headers})

        if request_auth:
            kwargs.update({'auth': request_auth})

        if timeout and isinstance(timeout, int):
            kwargs.update({'timeout': timeout})

        if allow_redirects:
            kwargs.update({'allow_redirects': allow_redirects})

        if stream:
            kwargs.update({'stream': stream})

        if cookie_payload:
            kwargs.update({'cookies': cookie_payload})

        kwargs.update({'verify': verify})

        try:
            if build_request_curl:
                # In case no authentication information has been provided,
                # use session's cookies information, if exists
                if not request_auth and self.session.cookies and len(self.session.cookies) > 0:
                    request_auth = self.session.cookies

                self.built_request_curl = command_line_request_curl(
                    request_method=request_method,
                    request_url=request_url,
                    request_headers=headers,
                    request_params=request_params,
                    request_data=request_data,
                    request_json=request_json,
                    request_auth=request_auth,
                    request_timeout=timeout,
                    request_allow_redirects=allow_redirects
                )

                self.logger.debug(
                    '{}: Curl'.format(request_label),
                    extra={
                        'request_method': request_method,
                        'request_label': request_label,
                        'request_curl': self.built_request_curl
                    }
                )

            if hasattr(response, 'url'):
                self.logger.debug('{}: {}'.format(request_label, request_method), extra={'response_url': response.url})

            if request_params:
                kwargs.update({'params': request_params})

            if request_data:
                kwargs.update({'data': request_data})

            if request_json:
                kwargs.update({'json': request_json})

            if headers:
                kwargs.update({'headers': headers})

            kwargs.update({'request_method': request_method, 'request_url': request_url})

            response = self.tune_request.request(**kwargs)

        except Exception as ex:
            self.logger.error(
                '{}: Request Base: Error'.format(request_label),
                extra={
                    'request_label': request_label,
                    'error_exception': base_class_name(ex),
                    'error_details': get_exception_message(ex)
                }
            )
            raise

        if response is None:
            self.logger.error(
                '{}: Response: Failed'.format(request_label),
                extra={'request_curl': self.built_request_curl},
            )
            raise TuneRequestModuleError(
                error_message='{}: Response: Failed'.format(request_label),
                error_code=TuneRequestErrorCodes.REQ_ERR_UNEXPECTED_VALUE,
                error_request_curl=self.built_request_curl
            )

        http_status_code = response.status_code
        response_headers = json.loads(json.dumps(dict(response.headers)))

        http_status_type = \
            http_status_code_to_type(http_status_code)
        http_status_desc = \
            http_status_code_to_desc(http_status_code)

        response_extra = {
            'http_status_code': http_status_code,
            'http_status_type': http_status_type,
            'http_status_desc': http_status_desc,
            'response_headers': safe_dict(response_headers),
        }

        self.logger.debug(
            '{}: Response: Details'.format(request_label),
            extra=response_extra,
        )

        http_status_successful = is_http_status_type(
            http_status_code=http_status_code, http_status_type=HttpStatusType.SUCCESSFUL
        )

        http_status_redirection = is_http_status_type(
            http_status_code=http_status_code, http_status_type=HttpStatusType.REDIRECTION
        )

        if http_status_successful or http_status_redirection:
            if hasattr(response, 'url') and \
                    response.url and \
                    len(response.url) > 0:
                response_extra.update({'response_url': response.url})

            self.logger.debug(
                '{}: Cookie Payload'.format(request_label),
                extra={'cookie_payload': self.tune_request.session.cookies.get_dict(),
                       'request_label': request_label}
            )

            assert response
            return response
        else:
            response_extra.update({'error_request_curl': self.built_request_curl})
            self.logger.error('{}: Response: Failed'.format(request_label), extra=response_extra)

            json_response_error = \
                build_response_error_details(
                    response=response,
                    request_label=request_label,
                    request_url=request_url
                )

            extra_error = copy.deepcopy(json_response_error)

            if self.logger_level == logging.INFO:
                error_response_details = \
                    extra_error.get('response_details', None)

                if error_response_details and \
                        isinstance(error_response_details, str) and \
                        len(error_response_details) > 100:
                    extra_error['response_details'] = error_response_details[:100] + ' ...'

            if self.built_request_curl and \
                    'error_request_curl' not in extra_error:
                extra_error.update({'error_request_curl': self.built_request_curl})

            self.logger.error('{}: Error: Response: Details'.format(request_label), extra=extra_error)

            kwargs = {
                'error_status': json_response_error.get("response_status", None),
                'error_reason': json_response_error.get("response_reason", None),
                'error_details': json_response_error.get("response_details", None),
                'error_request_curl': self.built_request_curl
            }

            if http_status_code in [
                HttpStatusCode.BAD_REQUEST,
                HttpStatusCode.UNAUTHORIZED,
                HttpStatusCode.FORBIDDEN,
                HttpStatusCode.NOT_FOUND,
                HttpStatusCode.METHOD_NOT_ALLOWED,
                HttpStatusCode.NOT_ACCEPTABLE,
                HttpStatusCode.REQUEST_TIMEOUT,
                HttpStatusCode.CONFLICT,
                HttpStatusCode.GONE,
                HttpStatusCode.UNPROCESSABLE_ENTITY,
                HttpStatusCode.TOO_MANY_REQUESTS,
            ]:
                kwargs.update({'error_code': http_status_code})
                raise TuneRequestClientError(**kwargs)

            if http_status_code in [
                HttpStatusCode.INTERNAL_SERVER_ERROR,
                HttpStatusCode.NOT_IMPLEMENTED,
                HttpStatusCode.BAD_GATEWAY,
                HttpStatusCode.SERVICE_UNAVAILABLE,
                HttpStatusCode.NETWORK_AUTHENTICATION_REQUIRED,
            ]:
                kwargs.update({'error_code': http_status_code})
                raise TuneRequestServiceError(**kwargs)

            kwargs.update({'error_code': json_response_error['response_status_code']})

            extra_unhandled = copy.deepcopy(kwargs)
            extra_unhandled.update({'http_status_code': http_status_code})
            self.logger.error('{}: Error: Unhandled'.format(request_label), extra=extra_unhandled)

            raise TuneRequestModuleError(**kwargs)
示例#9
0
    def _check_v2_job_status_on_queue(
        self,
        auth_type,
        auth_value,
        export_status_controller,
        export_status_action,
        export_job_id,
        request_retry=None,
    ):
        """Check Job Export Status

        Args:
            export_status_controller:
            export_status_action:
            export_job_id:
            request_retry:

        Returns:

        """
        request_label = "TMC v2 Advertiser Stats: Check Export Status"

        v2_export_status_request_url = \
            self.tune_mat_request_path(
                mat_api_version="v2",
                controller=export_status_controller,
                action=export_status_action
            )

        request_params = {auth_type: auth_value, "job_id": export_job_id}

        self.logger.info(
            ("TMC v2 Advertiser Stats: Check Job Status"),
            extra={
                'action': export_status_action,
                'job_id': export_job_id,
                'request_url': v2_export_status_request_url,
                'request_params': safe_dict(request_params)
            })

        tries = 60  # -1 (indefinite)
        delay = 10
        jitter = 10
        max_delay = 60

        if request_retry is not None:
            if 'delay' in request_retry:
                delay = request_retry['delay']
            if 'jitter' in request_retry:
                jitter = request_retry['jitter']
            if 'max_delay' in request_retry:
                max_delay = request_retry['max_delay']

            if 'tries' in request_retry:
                tries = request_retry['tries']
            else:
                request_retry.update({'tries': 60})
        else:
            request_retry = {'tries': 60, 'delay': 10, 'timeout': 60}

        self.logger.debug(msg=("TMC v2 Advertiser Stats: Check Job Status: "
                               "Request Retry"),
                          extra=request_retry)

        report_url = None
        _attempts = 1
        export_percent_complete = 0

        time.sleep(10)

        _tries, _delay = tries, delay
        while True:
            try:
                response = self.mv_request.request(
                    request_method="GET",
                    request_url=v2_export_status_request_url,
                    request_params=request_params,
                    request_label=request_label,
                    request_retry_func=self.tune_v2_request_retry_func)

            except TuneRequestBaseError as tmc_req_ex:
                self.logger.error(
                    "TMC v2 Advertiser Stats: Check Job Status: Failed",
                    extra=tmc_req_ex.to_dict(),
                )
                raise

            except TuneReportingError as tmc_rep_ex:
                self.logger.error(
                    "TMC v2 Advertiser Stats: Check Job Status: Failed",
                    extra=tmc_rep_ex.to_dict(),
                )
                raise

            except Exception as ex:
                print_traceback(ex)

                self.logger.error(
                    "TMC v2 Advertiser Stats: Check Job Status: {}".format(
                        get_exception_message(ex)))
                raise

            http_status_successful = is_http_status_type(
                http_status_code=response.status_code,
                http_status_type=HttpStatusType.SUCCESSFUL)

            if not http_status_successful:
                raise TuneReportingError(
                    error_message=(
                        "Failed to get export status on queue: {}").format(
                            response.status_code),
                    error_code=TuneReportingErrorCodes.REP_ERR_REQUEST)

            if hasattr(response, 'url'):
                self.logger.info(
                    "TMC v2 Advertiser Stats: Reporting API: Status URL",
                    extra={'response_url': response.url})

            json_response = response.json()

            if not json_response:
                request_status_successful = False

            elif 'status_code' not in json_response:
                request_status_successful = False

            else:
                status_code = json_response['status_code']

                request_status_successful = is_http_status_type(
                    http_status_code=status_code,
                    http_status_type=HttpStatusType.SUCCESSFUL)

                errors = None
                if 'errors' in json_response:
                    errors = json_response['errors']

            if not request_status_successful:
                error_message = (
                    "TMC v2 Advertiser Stats: Check Job Status: GET '{}', Failed: {}, {}"
                ).format(v2_export_status_request_url, status_code, errors)

                if (status_code ==
                        TuneReportingError.EX_SRV_ERR_500_INTERNAL_SERVER):
                    self.logger.error(error_message)

                elif (status_code ==
                      TuneReportingError.EX_SRV_ERR_503_SERVICE_UNAVAILABLE):
                    self.logger.error(error_message)

                elif (status_code ==
                      TuneReportingError.EX_SRV_ERR_504_SERVICE_TIMEOUT):
                    self.logger.error(error_message)
                    continue

                elif (status_code ==
                      TuneReportingError.EX_CLT_ERR_408_REQUEST_TIMEOUT):
                    self.logger.error(
                        "GET '{}' request timeout, Retrying: {}".format(
                            v2_export_status_request_url, status_code))
                    continue

                else:
                    raise TuneReportingError(error_message=error_message,
                                             error_code=status_code)

                if tries >= 0 and _tries <= 1:
                    if (status_code == HttpStatusCode.GATEWAY_TIMEOUT):
                        raise TuneReportingError(
                            error_message=error_message,
                            error_code=TuneReportingErrorCodes.GATEWAY_TIMEOUT)
                    elif (status_code == HttpStatusCode.REQUEST_TIMEOUT):
                        raise TuneReportingError(
                            error_message=error_message,
                            error_code=TuneReportingErrorCodes.REQUEST_TIMEOUT)
                    else:
                        raise TuneReportingError(error_message=error_message,
                                                 error_code=status_code)
                else:
                    self.logger.warning(error_message)

            export_percent_complete = 0
            if 'data' in json_response and json_response['data']:
                json_data = json_response['data']

                if "percent_complete" in json_data:
                    export_percent_complete = \
                        safe_int(json_data["percent_complete"])

                self.logger.info(msg=("TMC v2 Advertiser Stats: "
                                      "Check Job Export Status: "
                                      "Response Success"),
                                 extra={
                                     'job_id':
                                     export_job_id,
                                     'export_status':
                                     json_data["status"],
                                     'export_percent_complete':
                                     safe_int(export_percent_complete),
                                     'attempt':
                                     _attempts
                                 })

                if (export_status_action ==
                        TuneV2AdvertiserStatsStatusAction.STATUS):
                    if (export_percent_complete == 100
                            and json_data["status"] == "complete"
                            and json_data["url"]):
                        report_url = json_data["url"]

                        self.logger.debug(
                            ("TMC v2 Advertiser Stats: "
                             "Check Job Export Status: Completed"),
                            extra={
                                'job_id': export_job_id,
                                'action': export_status_action,
                                'report_url': report_url,
                                'request_label': request_label
                            })

                        break

                elif (export_status_action ==
                      TuneV2AdvertiserStatsStatusAction.DOWNLOAD):
                    if (export_percent_complete == 100
                            and json_data["status"] == "complete"
                            and json_data["data"]["url"]):
                        report_url = json_data["data"]["url"]

                        self.logger.debug(
                            ("TMC v2 Advertiser Stats: "
                             "Check Job Export Status: Completed"),
                            extra={
                                'job_id': export_job_id,
                                'action': export_status_action,
                                'report_url': report_url,
                                'request_label': request_label
                            })

                        break
            else:
                self.logger.debug("TMC v2 Advertiser Stats: "
                                  "Check Job Export Status: "
                                  "No Data Available")

            if tries >= 0:
                _tries -= 1
                if _tries == 0:
                    self.logger.error(
                        ("TMC v2 Advertiser Stats: "
                         "Check Job Export Status: Exhausted Retries"),
                        extra={
                            'attempt':
                            _attempts,
                            'tries':
                            _tries,
                            'action':
                            export_status_action,
                            'request_label':
                            request_label,
                            'export_percent_complete':
                            safe_int(export_percent_complete),
                            'job_id':
                            export_job_id
                        })

                    raise TuneReportingError(
                        error_message=("TMC v2 Advertiser Stats: "
                                       "Check Job Export Status: "
                                       "Exhausted Retries: "
                                       "Percent Completed: {}").format(
                                           safe_int(export_percent_complete)),
                        error_code=TuneReportingErrorCodes.
                        REP_ERR_RETRY_EXHAUSTED)

            _attempts += 1

            self.logger.info("TMC v2 Advertiser Stats: Check Job Status",
                             extra={
                                 'attempt': _attempts,
                                 'job_id': export_job_id,
                                 'delay': _delay,
                                 'action': export_status_action
                             })

            time.sleep(_delay)

            _delay += jitter
            _delay = min(_delay, max_delay)

        if export_percent_complete == 100 and not report_url:
            raise TuneReportingError(
                error_message=(
                    "TMC v2 Advertiser Stats: Check Job Export Status: "
                    "Download report URL: Undefined"),
                error_code=TuneReportingErrorCodes.REP_ERR_UNEXPECTED_VALUE)

        self.logger.info(
            "TMC v2 Advertiser Stats: Check Job Export Status: Finished",
            extra={
                'attempt': _attempts,
                'action': export_status_action,
                'report_url': report_url,
                'request_label': request_label,
                'export_percent_complete': export_percent_complete,
                'job_id': export_job_id
            })

        return report_url
示例#10
0
    def _process_export_stream_v2(
        self,
        auth_type_use,
        str_date_start,
        str_date_end,
        export_controller,
        export_action,
        export_status_controller,
        export_status_action,
        request_params,
        request_retry,
        request_label="TMC v2 Advertiser Stats Export Stream",
    ):
        """Process Export Job by Steaming

        Args:
            str_date_start:
            str_date_end:
            export_controller:
            export_action:
            export_status_controller:
            export_status_action:
            request_params:
            request_retry:

        Returns:

        """
        auth_value = None
        if auth_type_use == TuneV2AuthenticationTypes.API_KEY:
            auth_value = self.api_key
        elif auth_type_use == TuneV2AuthenticationTypes.SESSION_TOKEN:
            auth_value = self.session_token

        str_date_start += " 00:00:00"
        str_date_end += " 23:59:59"

        request_params["start_date"] = \
            str_date_start
        request_params["end_date"] = \
            str_date_end

        self.logger.debug(
            "TMC v2 Advertiser Stats: Export Stream V2: Export Job to Queue",
            extra={
                'controller': export_controller,
                'action': export_action,
                'request_params': safe_dict(request_params),
                'request_retry': safe_dict(request_retry)
            })

        export_job_id = self._export_v2_job_to_queue(
            export_controller,
            export_action,
            request_params,
            request_retry,
        )

        self.logger.debug(
            "TMC v2 Advertiser Stats: Export Stream V2: Check Job status on Queue",
            extra={
                'controller': export_status_controller,
                'action': export_status_action,
                'job_id': export_job_id
            })

        export_report_url = self._check_v2_job_status_on_queue(
            auth_type_use,
            auth_value,
            export_status_controller,
            export_status_action,
            export_job_id,
            request_retry=request_retry)

        if not export_report_url:
            raise TuneReportingError(
                error_message="Export URL not defined",
                error_code=TuneReportingErrorCodes.REP_ERR_UNEXPECTED_VALUE,
            )

        self.logger.info(("TMC v2 Advertiser Stats: "
                          "Export Stream V2: Request Completed Job"),
                         extra={
                             'job_id': export_job_id,
                             'report_url': export_report_url
                         })

        response = self.mv_request.request(request_method="GET",
                                           request_url=export_report_url,
                                           stream=True,
                                           request_label=request_label)

        self.logger.info(
            "TMC v2 Advertiser Stats: Export Stream V2: Response Completed Job",
            extra={
                'response_status_code': response.status_code,
                'response_headers': response.headers,
                'job_id': export_job_id,
                'report_url': export_report_url
            })

        return response
    def _request_data(
        self,
        request_method,
        request_url,
        request_params=None,
        request_data=None,
        request_json=None,
        request_headers=None,
        request_auth=None,
        request_cert=None,
        cookie_payload=None,
        request_label=None,
        timeout=60,
        build_request_curl=True,
        allow_redirects=True,
        verify=True,
        stream=False
    ):
        """Request Data from requests.

        Args:
            request_method: request_method for the new :class:`Request` object.
            logger: logging instance
            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_headers: (optional) Dictionary of HTTP Headers to send
                with the :class:`Request`.
            request_auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth.
            request_cert: (optional) Cert tuple to enable Client side certificates.
            timeout: (optional) How long to wait for the server to send data
                before giving up.
            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.

        Returns:
            requests.Response

        """

        if request_label is None:
            request_label = 'Request Data'

        if not request_method:
            raise RequestsFortifiedValueError(error_message="Parameter 'request_method' not defined")
        if not request_url:
            raise RequestsFortifiedValueError(error_message="Parameter 'request_url' not defined")

        self.built_request_curl = None

        self.logger.debug(
            "{0}: Session: Details".format(request_label),
            extra={
                'cookie_payload': self.requests_session_client.session.cookies.get_dict(),
                'request_label': request_label
            }
        )

        response = None
        headers = None

        if request_headers:
            headers = request_headers

        request_method = request_method.upper()

        if request_data and isinstance(request_data, str):
            if len(request_data) <= 20:
                request_data_extra = request_data
            else:
                request_data_extra = request_data[:20] + ' ...'
        else:
            request_data_extra = safe_str(request_data)

        request_extra = {
            'request_method': request_method,
            'request_url': request_url,
            'timeout': timeout,
            'request_params': safe_dict(request_params),
            'request_data': request_data_extra,
            'request_headers': safe_dict(headers),
            'request_label': request_label
        }

        self.logger.debug("{0}: Details".format(request_label), extra=request_extra)
        self.built_request_curl = None

        kwargs = {}
        if headers:
            kwargs.update({'headers': headers})

        if request_auth:
            kwargs.update({'auth': request_auth})

        if request_cert:
            kwargs.update({'cert': request_cert})

        if timeout and isinstance(timeout, int):
            kwargs.update({'timeout': timeout})

        if allow_redirects:
            kwargs.update({'allow_redirects': allow_redirects})

        if stream:
            kwargs.update({'stream': stream})

        if cookie_payload:
            kwargs.update({'cookies': cookie_payload})

        kwargs.update({'verify': verify})

        try:
            if build_request_curl:
                # In case no authentication information has been provided,
                # use session's cookies information, if exists
                if not request_auth and self.session.cookies and len(self.session.cookies) > 0:
                    request_auth = self.session.cookies

                self.built_request_curl = command_line_request_curl(
                    request_method=request_method,
                    request_url=request_url,
                    request_headers=headers,
                    request_params=request_params,
                    request_data=request_data,
                    request_json=request_json,
                    request_auth=request_auth,
                    request_timeout=timeout,
                    request_allow_redirects=allow_redirects
                )

                self.logger.note(
                    "{0}: Curl".format(request_label),
                    extra={
                        'request_method': request_method,
                        'request_label': request_label,
                        'request_curl': self.built_request_curl
                    }
                )

            if hasattr(response, 'url'):
                self.logger.debug(
                    "{0}: {1}".format(request_label, request_method),
                    extra={'response_url': response.url}
                )

            if request_params:
                kwargs.update({'params': request_params})

            if request_data:
                kwargs.update({'data': request_data})

            if request_json:
                kwargs.update({'json': request_json})

            if headers:
                kwargs.update({'headers': headers})

            kwargs.update({'request_method': request_method, 'request_url': request_url})

            response = self.requests_session_client.request(**kwargs)

        except Exception as ex:
            self.logger.error(
                "{0}: Request Base: Error".format(request_label),
                extra={
                    'request_label': request_label,
                    'error_exception': base_class_name(ex),
                    'error_details': get_exception_message(ex)
                }
            )
            raise

        if response is None:
            self.logger.error(
                "{0}: Response: Failed".format(request_label),
                extra={
                    'request_curl': self.built_request_curl
                },
            )

            raise RequestsFortifiedModuleError(
                error_message="{0}: Response: Failed".format(request_label),
                error_code=RequestsFortifiedErrorCodes.REQ_ERR_UNEXPECTED_VALUE,
                error_request_curl=self.built_request_curl
            )

        http_status_code = response.status_code
        response_headers = json.loads(json.dumps(dict(response.headers)))

        http_status_type = \
            get_http_status_type(http_status_code)
        http_status_desc = \
            get_http_status_desc(http_status_code)

        response_extra = {
            'http_status_code': http_status_code,
            'http_status_type': http_status_type,
            'http_status_desc': http_status_desc,
            'response_headers': safe_dict(response_headers),
        }

        self.logger.debug(
            "{0}: Response: Details".format(request_label),
            extra=response_extra,
        )

        http_status_successful = is_http_status_type(
            http_status_code=http_status_code, http_status_type=HttpStatusType.SUCCESSFUL
        )

        http_status_redirection = is_http_status_type(
            http_status_code=http_status_code, http_status_type=HttpStatusType.REDIRECTION
        )

        if http_status_successful or http_status_redirection:
            if hasattr(response, 'url') and \
                    response.url and \
                    len(response.url) > 0:
                response_extra.update({'response_url': response.url})

            self.logger.debug(
                "{0}: Cookie Payload".format(request_label),
                extra={
                    'cookie_payload': self.requests_session_client.session.cookies.get_dict(),
                    'request_label': request_label
                }
            )

            assert response
            return response
        else:
            response_extra.update({'error_request_curl': self.built_request_curl})
            self.logger.error("{0}: Response: Failed".format(request_label), extra=response_extra)

            json_response_error = \
                build_response_error_details(
                    response=response,
                    request_label=request_label,
                    request_url=request_url
                )

            extra_error = copy.deepcopy(json_response_error)

            if self.logger_level == logging.INFO:
                error_response_details = \
                    extra_error.get('response_details', None)

                if error_response_details and \
                        isinstance(error_response_details, str) and \
                        len(error_response_details) > 100:
                    extra_error['response_details'] = error_response_details[:100] + ' ...'

            if self.built_request_curl and \
                    'error_request_curl' not in extra_error:
                extra_error.update({'error_request_curl': self.built_request_curl})

            self.logger.error("{0}: Error: Response: Details".format(request_label), extra=extra_error)

            kwargs = {
                'error_status': json_response_error.get("response_status", None),
                'error_reason': json_response_error.get("response_reason", None),
                'error_details': json_response_error.get("response_details", None),
                'error_request_curl': self.built_request_curl
            }

            if http_status_code in [
                HttpStatusCode.BAD_REQUEST,
                HttpStatusCode.UNAUTHORIZED,
                HttpStatusCode.FORBIDDEN,
                HttpStatusCode.NOT_FOUND,
                HttpStatusCode.METHOD_NOT_ALLOWED,
                HttpStatusCode.NOT_ACCEPTABLE,
                HttpStatusCode.REQUEST_TIMEOUT,
                HttpStatusCode.CONFLICT,
                HttpStatusCode.GONE,
                HttpStatusCode.UNPROCESSABLE_ENTITY,
                HttpStatusCode.TOO_MANY_REQUESTS,
            ]:
                kwargs.update({'error_code': http_status_code})
                raise RequestsFortifiedClientError(**kwargs)

            if http_status_code in [
                HttpStatusCode.INTERNAL_SERVER_ERROR,
                HttpStatusCode.NOT_IMPLEMENTED,
                HttpStatusCode.BAD_GATEWAY,
                HttpStatusCode.SERVICE_UNAVAILABLE,
                HttpStatusCode.NETWORK_AUTHENTICATION_REQUIRED,
            ]:
                kwargs.update({'error_code': http_status_code})
                raise RequestsFortifiedServiceError(**kwargs)

            kwargs.update({'error_code': json_response_error['response_status_code']})

            extra_unhandled = copy.deepcopy(kwargs)
            extra_unhandled.update({'http_status_code': http_status_code})
            self.logger.error("{0}: Error: Unhandled".format(request_label), extra=extra_unhandled)

            raise RequestsFortifiedModuleError(**kwargs)
    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 = "{0}: {1}".format(request_label, _request_label)  if request_label is not None else _request_label

        log.debug(
            "{0}: Start".format(request_label),
            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(
                "{0}: Attempt: {1}".format(request_label, _attempts),
                extra={
                    'request_url': request_url,
                }
            )

            response = self.requests_client.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(
                    "{0}: No response".format(request_label),
                    extra={
                        'request_url': request_url,
                    }
                )

                raise RequestsFortifiedModuleError(
                    error_message="{0}: No response".format(request_label),
                    error_code=RequestsFortifiedErrorCodes.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(
                "{0}: Response Status".format(request_label),
                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(
                    "{0}: Exhausted Retries".format(request_label),
                    extra={
                        'tries': _tries,
                        'request_url': request_url,
                    }
                )

                raise RequestsFortifiedModuleError(
                    error_message="{0}: Exhausted Retries".format(request_label),
                    error_code=RequestsFortifiedErrorCodes.REQ_ERR_RETRY_EXHAUSTED
                )

            log.info(
                "{0}: Performing Retry".format(request_label),
                extra={
                    'tries': _tries,
                    'delay': _delay,
                    'request_url': request_url,
                }
            )

            time.sleep(_delay)

        log.info(
            "{0}: Finished".format(request_label),
            extra={
                'file_path': tmp_csv_file_path,
                'file_size': bytes_to_human(tmp_csv_file_size),
                'encoding_read': encoding_read,
            }
        )

        log.debug(
            "{0}: Usage".format(request_label),
            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(
                    "{0}: Report".format(request_label),
                    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(
                "{0}: Content Header".format(request_label),
                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 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 = "{0}: {1}".format(request_label, _request_label)  if request_label is not None else _request_label

        log.info(
            "{0}: Start".format(request_label),
            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(
                "{0}: Download".format(request_label),
                extra={
                    'attempts': _attempts,
                    'request_url': request_url,
                }
            )

            response = self.requests_client.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(
                    "{0}: No response".format(request_label),
                    extra={
                        'request_url': request_url,
                    }
                )

                raise RequestsFortifiedModuleError(
                    error_message="{0}: No response".format(request_label),
                    error_code=RequestsFortifiedErrorCodes.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(
                "{0}: Response Status".format(request_label),
                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 = "{0}/{1}".format(tmp_directory, tmp_json_file_name)

            if os.path.exists(tmp_json_file_path):
                log.debug(
                    "{0}: Removing".format(request_label),
                    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(
                "{0}: Finished".format(request_label),
                extra={
                    'file_path': tmp_json_file_path,
                    'mode_write': mode_write,
                    'encoding_write': encoding_write,
                }
            )

            log.debug(
                "{0}: Usage".format(request_label),
                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(
                    "{0}: Response Raw: Started".format(request_label),
                    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(
                        "{0}: By Chunk: Completed".format(request_label),
                        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(
                        "{0}: Error: {1}".format(request_label, error_exception),
                        extra={
                            'error_details': error_details,
                            'chunk_total_sum': chunk_total_sum,
                        }
                    )

                    if not _tries:
                        log.error(
                            "{0}: Exhausted Retries: Error: {1}".format(request_label, 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(
                        "{0}: IncompleteRead".format(request_label),
                        extra={
                            'error_exception': error_exception,
                            'error_details': error_details,
                            'chunk_total_sum': chunk_total_sum,
                        }
                    )

                    if not _tries:
                        log.error(
                            "{0}: Exhausted Retries: Error: {1}".format(request_label, error_exception),
                        )
                        raise

                except requests.exceptions.RequestException as request_ex:
                    log.error(
                        "{0}: Request Exception".format(request_label),
                        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(
                        "{0}: Unexpected Exception".format(request_label),
                        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(
                        "{0}: Exhausted Retries".format(request_label),
                        extra={
                            'tries': _tries,
                            'request_url': request_url,
                        }
                    )

                    raise RequestsFortifiedModuleError(
                        error_message="{0}: Exhausted Retries: {1}".format(request_label, request_url),
                        error_request_curl=self.built_request_curl,
                        error_code=RequestsFortifiedErrorCodes.REQ_ERR_RETRY_EXHAUSTED
                    )

                log.info(
                    "{0}: Performing Retry".format(request_label),
                    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(
            "{0}: By Chunk: Completed: Details".format(request_label),
            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 = "%s.gz" % tmp_json_file_path

            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(
                    "{0}: GZip: Started".format(request_label),
                    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(
            "{0}: Read Downloaded".format(request_label),
            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(
                    "{0}: Failed: Exception".format(request_label),
                    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(
            "{0}: Finished".format(request_label),
            extra=response_extra
        )

        return json_download
示例#14
0
print(safe_cost(.12))
print(safe_cost(.123))
print(safe_cost(.1234))
print(safe_cost(.12345))
print(safe_cost(4))
print(safe_cost(4.1))
print(safe_cost(4.12))
print(safe_cost(4.123))
print(safe_cost(4.1234))
print(safe_cost(4.12345))

print("\n")
print('==================================')
print('safe_dict()')
print('==================================')
print(safe_dict({'key': 'value'}))

print("\n")
print('==================================')
print('safe_fraction()')
print('==================================')
print(safe_fraction("1/2"))

print("\n")
print('==================================')
print('ValueError')
print('==================================')
try:
    safe_int("apple")
except ValueError as ex:
    print(str(ex))
    def _map_request_params(self,
                            auth_type_use,
                            start_date,
                            end_date,
                            request_params=None):
        """Build Request Paramaters

        Args:
            start_date:
            end_date:
            request_params:

        Returns:

        """
        auth_value = None
        if auth_type_use == TuneV2AuthenticationTypes.API_KEY:
            auth_value = self.api_key
        elif auth_type_use == TuneV2AuthenticationTypes.SESSION_TOKEN:
            auth_value = self.session_token

        dict_request_params = {
            auth_type_use:
            auth_value,
            "source":
            "multiverse",
            "response_timezone":
            self.timezone,
            "timestamp":
            "datehour",
            "group": ("advertiser_id,"
                      "country_id,"
                      "currency_code,"
                      "is_reengagement,"
                      "platform,"
                      "publisher_id,"
                      "publisher_sub_ad_id,"
                      "publisher_sub_adgroup_id,"
                      "publisher_sub_campaign_id,"
                      "publisher_sub_publisher_id,"
                      "publisher_sub_site_id,"
                      "purchase_validation_status,"
                      "site_id"),
            "fields": ("ad_clicks,"
                       "ad_clicks_unique,"
                       "ad_impressions,"
                       "ad_impressions_unique,"
                       "ad_network_id,"
                       "advertiser_id,"
                       "conversions,"
                       "country.code,"
                       "country.name,"
                       "currency_code,"
                       "date_hour,"
                       "events,"
                       "installs,"
                       "is_reengagement,"
                       "payouts,"
                       "publisher.name,"
                       "publisher_id,"
                       "publisher_sub_ad.ref,"
                       "publisher_sub_adgroup.ref,"
                       "publisher_sub_campaign.ref,"
                       "publisher_sub_publisher.ref,"
                       "publisher_sub_site.ref,"
                       "site.mobile_app_type,"
                       "site.package_name,"
                       "site.store_app_id,"
                       "site_id"),
            "filter":
            "({})".format(self._FILTER_NOT_DEBUG_NOR_TEST_DATA),
            "start_date":
            start_date,
            "end_date":
            end_date,
            "debug":
            0
        }

        if request_params:
            self.logger.debug(
                "TuneV2AdvertiserStatsActuals: Request",
                extra={'request_params': safe_dict(request_params)})

            if "fields" in request_params and \
                    request_params["fields"]:
                dict_request_params["fields"] = \
                    request_params["fields"]

            if "group" in request_params and \
                    request_params["group"]:
                dict_request_params["group"] = \
                    request_params["group"]

            if "timestamp" in request_params and \
                    request_params["timestamp"]:
                dict_request_params["timestamp"] = \
                    request_params["timestamp"]

            if "filter" in request_params and \
                    request_params["filter"]:
                dict_request_params["filter"] = "({} AND {})".format(
                    request_params["filter"],
                    self._FILTER_NOT_DEBUG_NOR_TEST_DATA)

            if "format" in request_params:
                dict_request_params["format"] = \
                    request_params["format"]

            if "offset" in request_params:
                dict_request_params["offset"] = \
                    int(request_params["offset"])

            if "page" in request_params:
                dict_request_params["page"] = \
                    int(request_params["page"])

            if "limit" in request_params:
                dict_request_params["limit"] = \
                    int(request_params["limit"])

            if "debug" in request_params:
                dict_request_params["debug"] = \
                    int(request_params["debug"])

            response_timezone = None
            if "timezone" in request_params:
                response_timezone = request_params["timezone"]
            if "response_timezone" in request_params:
                response_timezone = request_params["response_timezone"]

            if response_timezone:
                if not validate_tz_name(response_timezone):
                    return TuneReportingError(
                        error_message="Invalid Timezone: {}".format(
                            response_timezone))
                self.timezone = response_timezone
                dict_request_params["response_timezone"] = \
                    self.timezone

        self.logger.debug(("TuneV2AdvertiserStatsActuals: "
                           "Timezone: {}").format(self.timezone))

        return dict_request_params