def assert_response_has_json(response: requests.Response) -> None:
    """
    Проверка, что ответ (объект requests.Response) содержит JSON (аттрибут .json())

    :param response: Ответ (объект requests.Response)
    """
    LOG.info('Asserting response has json ...')
    try:
        assert response_has_json(response)
    except AssertionError as exception:
        LOG.warning('Assertion error: Response has no JSON')
        raise ResponseHasNoJsonError(
            'У ответа отсутствует JSON') from exception
    LOG.info('Assertion success')
def assert_response_status_code(response: requests.Response,
                                status_code: int = 200) -> None:
    """
    Проверка, что у ответа (объект requests.Response) правильный статус

    :param response: Ответ (объект requests.Response)
    :param status_code: Ожидаемый статус ответа
    """
    LOG.info(f'Asserting response status-code (expecting: {status_code}) ...')
    try:
        assert response.status_code == status_code
    except AssertionError as exception:
        LOG.warning(
            f'Assertion error: Wrong response status-code: {response.status_code}'
        )
        raise BadResponseStatusCodeError(
            f'Неверный код статуса ответа: "{response.status_code}". Ожидалось: "{status_code}".'
        ) from exception
    LOG.info('Assertion success')
def validate(data: dict, schema: Schema) -> None:
    """
    Функция для валидации схемы ответа
    :param data: JSON из ответа
    :param schema: Схема, по которой требуется валидировать ответ
    """
    LOG.info(f'Trying to validate response with schema "{schema.name}"')
    try:
        schema.validate(data)
    except SchemaError as exception:
        LOG.warning('JSON failed validation: {}'.format(
            str(exception).replace('\n', ' ')))
        raise JsonSchemaValidationError(
            'Ошибка валидации схемы JSON ответа') from exception
    except Exception as exception:
        LOG.error('Error while validating JSON')
        LOG.exception(exception)
        raise
    LOG.info('Validation success')
def get_receipt_new_status(
    receipt_uuid: str,
    balancer_api: Balancer,
    group_code: str,
    timeout: int = 30,
    request_frequency: Union[int, float] = 0.5,
    base_status: Literal['fail', 'done', 'wait', 'timeout'] = 'wait'
) -> Tuple[str, requests.Response]:
    """
    Функция для получения нового статуса чека

    :param receipt_uuid: UUID чека, полученный после отправки запроса на регистрацию чека
    :param balancer_api: Объект Balancer
    :param group_code: Код группы
    :param timeout: Сколько секунд ждать изменения статуса
    :param request_frequency: Частота запросов в секундах
    :param base_status: Статус, в котором сейчас находится запрос на регистрацию чека (fail|done|wait|timeout)
    :return: (<новый статус>, <requests.Response>)
    :rtype: Tuple[str, requests.Response]
    """
    tries = int(timeout // request_frequency)
    for i in range(tries):
        LOG.info(f'Checking receipt new status (Try {i + 1}/{tries}) ...')
        try:
            response = balancer_api.get_receipt_state(receipt_uuid, group_code)
            new_receipt_status = response.json()['status']
        except Exception as exception:
            LOG.error('Error has occurred while checking receipt new status')
            LOG.exception(exception)
            raise
        if new_receipt_status != base_status:
            LOG.info(f'New receipt status: {new_receipt_status}')
            return new_receipt_status, response
        time.sleep(request_frequency)
    LOG.warning(f'Receipt status has not changed in {timeout} seconds')
    raise ReceiptStateNotChangedError(
        f'Статус чека не изменился за {timeout} секунд.')