def test_api_call_with_retries_with_all_errors(self):
        """api_call_with_retries returns None when no valid Response is found after the maximum number of attempts."""
        full_url: str = '/'.join(
            [self.api_handler.base_url, self.get_scores_url])
        num_attempts: int = 4
        resp_mocks: list[MagicMock] = [
            MagicMock(spec=Response,
                      status_code=504,
                      text=json.dumps({'message': 'Gateway Timeout'}),
                      url=full_url) for i in range(num_attempts + 1)
        ]

        with patch.object(ApiUtil, 'api_call', autospec=True) as mock_api_call:
            mock_api_call.side_effect = resp_mocks

            response: Union[MagicMock, None] = api_call_with_retries(
                self.api_handler,
                self.get_scores_url,
                CANVAS_SCOPE,
                'GET',
                self.canvas_params,
                max_req_attempts=num_attempts)

        self.assertEqual(mock_api_call.call_count, 4)
        mock_api_call.assert_called_with(self.api_handler, self.get_scores_url,
                                         CANVAS_SCOPE, 'GET',
                                         self.canvas_params)
        self.assertEqual(response, None)
Пример #2
0
    def get_sub_dicts_for_exam(self,
                               page_size: int = 50) -> list[dict[str, Any]]:
        """
        Gets the graded submissions for the exam using paging.

        :param page_size: How many results from Canvas to include per page
        :type page_size: int, optional (default is 50)
        :return: List of submission dictionaries from Canvas returned based on the URL and parameters
        :rtype: List of dictionaries with string keys
        """
        get_subs_url: str = f'{CANVAS_URL_BEGIN}/courses/{self.exam.course_id}/students/submissions'

        canvas_params: dict[str, Any] = {
            'student_ids[]': 'all',
            'assignment_ids[]': str(self.exam.assignment_id),
            'per_page': page_size,
            'include[]': 'user',
            'graded_since': self.sub_time_filter.strftime(ISO8601_FORMAT)
        }

        more_pages: bool = True
        page_num: int = 1
        sub_dicts: list[dict[str, Any]] = []
        next_params: dict[str, Any] = canvas_params
        LOGGER.debug(f'Params for first request: {next_params}')

        while more_pages:
            LOGGER.debug(f'Page number {page_num}')
            response: Union[Response, None] = api_call_with_retries(
                self.api_handler, get_subs_url, CANVAS_SCOPE, 'GET',
                next_params, MAX_REQ_ATTEMPTS)
            if response is None:
                LOGGER.info(
                    'api_call_with_retries failed to get a response; no more data will be collected'
                )
                more_pages = False
            else:
                sub_dicts += json.loads(response.text)
                page_info: Union[None, dict[
                    str, Any]] = self.api_handler.get_next_page(response)
                if not page_info:
                    more_pages = False
                else:
                    LOGGER.debug(f'Params for next page: {page_info}')
                    next_params = page_info
                    page_num += 1

        sub_dicts_with_scores: list[dict[str, Any]] = list(
            filter((lambda x: x['score'] is not None), sub_dicts))
        filter_diff: int = len(sub_dicts) - len(sub_dicts_with_scores)
        if filter_diff > 0:
            LOGGER.info(
                f'Discarded {filter_diff} Canvas submission(s) with no score(s)'
            )

        LOGGER.info(
            f'Gathered {len(sub_dicts_with_scores)} submission(s) from Canvas')
        LOGGER.debug(sub_dicts_with_scores)
        return sub_dicts_with_scores
    def test_api_call_with_retries_when_no_errors(self):
        """api_call_with_retries returns Response object when a valid Response is found."""

        with patch.object(ApiUtil, 'api_call', autospec=True) as mock_api_call:
            mock_api_call.return_value = MagicMock(
                spec=Response,
                status_code=200,
                text=json.dumps(self.canvas_potions_val_subs),
                url='/'.join([self.api_handler.base_url, self.get_scores_url]))

            response = api_call_with_retries(self.api_handler,
                                             self.get_scores_url, CANVAS_SCOPE,
                                             'GET', self.canvas_params)

        self.assertEqual(mock_api_call.call_count, 1)
        mock_api_call.assert_called_with(self.api_handler, self.get_scores_url,
                                         CANVAS_SCOPE, 'GET',
                                         self.canvas_params)
        self.assertTrue(response.ok)
        self.assertEqual(json.loads(response.text),
                         self.canvas_potions_val_subs)