def _spreadsheet2gradebook_multi(
            self,
            csv_reader,
            email_field,
            non_assignment_fields,
            approve_grades=False,
            use_max_points_column=False,
            max_points_column=None,
            normalize_column=None
    ):
        """Transfer grades from spreadsheet to array.

        Helper function that transfer grades from spreadsheet using
        ``multi_grade()`` (multiple students at a time). We do this by
        creating a large array containing all grades to transfer, then
        make one call to the Gradebook API.

        Args:
            csv_reader (csv.DictReader): list of rows in CSV file
            email_field (str): The name of the email field
            non_assignment_fields (list): list of column names in CSV file
                which should not be treated as assignment names
            approve_grades (bool): Should grades be auto approved?
            use_max_points_column (bool): If true, read the max points
                and normalize values from the CSV and use the max points value
                in place of the default if normalized is False.
            max_points_column (str): The name of the max_pts column. All
                rows contain the same number, the max points for
                the assignment.
            normalize_column (str): The name of the normalize column which
                indicates whether to use the max points value.

        Raises:
            PyLmodFailedAssignmentCreation: Failed to create assignment
            requests.RequestException: Exception connection error
            ValueError: Unable to decode response content

        Returns:
            tuple: tuple of dictionary containing response ``status``
            and ``message``, and duration of operation

        """
        # pylint: disable=too-many-locals
        if use_max_points_column:
            if max_points_column is None:
                raise ValueError(
                    "max_points_column must be set "
                    "if use_max_points_column is set"
                )
            if normalize_column is None:
                raise ValueError(
                    "normalize_column must be set "
                    "if use_max_points_column is set"
                )

        assignments = self.get_assignments()
        students = self.get_students()
        assignment2id = {}
        grade_array = []
        for row in csv_reader:
            email = row[email_field]
            sid, _ = self.get_student_by_email(email, students)
            if sid is None:
                log.warning(
                    'Error in spreadsheet2gradebook: cannot find '
                    'student id for email="%s"\n', email
                )
                continue
            for field in row.keys():
                if field in non_assignment_fields:
                    continue
                if field not in assignment2id:
                    assignment_id, _ = self.get_assignment_by_name(
                        field, assignments=assignments
                    )
                    # If no assignment found, try creating it.
                    if assignment_id is None:
                        name = field
                        shortname = field[0:3] + field[-2:]
                        log.info('calling create_assignment from multi')

                        # If the max_pts and normalize columns are present,
                        # and use_max_points_column is True,
                        # replace the default value for max points.
                        max_points = DEFAULT_MAX_POINTS
                        if use_max_points_column:
                            # This value means it was already normalized, and
                            # we should use the default max points
                            # instead of the one in the CSV.
                            normalize_value = True
                            normalize_value_str = row.get(normalize_column)
                            if normalize_value_str is not None:
                                try:
                                    normalize_value = bool(
                                        int(normalize_value_str)
                                    )
                                except ValueError as ex:
                                    # Value is already normalized
                                    log.warning(
                                        'Bool conversion error '
                                        ' in normalize column for '
                                        'value: %s, exception: %s',
                                        normalize_value_str,
                                        ex
                                    )

                            if not normalize_value:
                                max_points_str = row.get(max_points_column)
                                if max_points_str is not None:
                                    try:
                                        max_points = float(max_points_str)
                                    except ValueError as ex:
                                        log.warning(
                                            'Floating point conversion error '
                                            'in max points column for '
                                            'value: %s, exception: %s',
                                            max_points_str,
                                            ex
                                        )

                        response = self.create_assignment(
                            name, shortname, 1.0, max_points, '12-15-2013'
                        )
                        if (
                                not response.get('data', '') or
                                'assignmentId' not in response.get('data')
                        ):
                            failure_message = (
                                "Error! Failed to create assignment {0}"
                                ", got {1}".format(
                                    name, response
                                )
                            )
                            log.critical(failure_message)
                            raise PyLmodFailedAssignmentCreation(
                                failure_message
                            )
                        assignment_id = response['data']['assignmentId']
                    log.info("Assignment %s has Id=%s", field, assignment_id)
                    assignment2id[field] = assignment_id

                assignment_id = assignment2id[field]
                successful = True
                try:
                    # Try to convert to numeric, but grade the
                    # rest anyway if any particular grade isn't a
                    # number
                    gradeval = float(row[field]) * 1.0
                    log.debug(
                        'Received grade value %s(converted to %s) for '
                        'student %s on assignment %s',
                        row[field],
                        gradeval,
                        sid,
                        assignment_id,
                    )
                except ValueError as err:
                    log.exception(
                        "Failed in converting grade for student %s"
                        ", row=%s, err=%s", sid, row, err
                    )
                    successful = False
                if successful:
                    grade_array.append({
                        "studentId": sid,
                        "assignmentId": assignment_id,
                        "numericGradeValue": gradeval,
                        "mode": 2,
                        "isGradeApproved": approve_grades
                    })
        # Everything is setup to post, do the post and track the time
        # it takes.
        log.info(
            'Data read from file, doing multiGrades API '
            'call (%d grades)', len(grade_array)
        )
        tstart = time.time()
        response = self.multi_grade(grade_array)
        duration = time.time() - tstart
        log.info(
            'multiGrades API call done (%d bytes returned) '
            'dt=%6.2f seconds.', len(json.dumps(response)), duration
        )
        return response, duration
Transfer grades from spreadsheet to array.

        Helper function that transfer grades from spreadsheet using
        ``multi_grade()`` (multiple students at a time). We do this by
        creating a large array containing all grades to transfer, then
        make one call to the Gradebook API.

        Args:
            csv_reader (csv.DictReader): list of rows in CSV file
            email_field (str): The name of the email field
            non_assignment_fields (list): list of column names in CSV file
                which should not be treated as assignment names
            approve_grades (bool): Should grades be auto approved?
            use_max_points_column (bool): If true, read the max points
                and normalize values from the CSV and use the max points value
                in place of the default if normalized is False.
            max_points_column (str): The name of the max_pts column. All
                rows contain the same number, the max points for
                the assignment.
            normalize_column (str): The name of the normalize column which
                indicates whether to use the max points value.

        Raises:
            PyLmodFailedAssignmentCreation: Failed to create assignment
            requests.RequestException: Exception connection error
            ValueError: Unable to decode response content

        Returns:
            tuple: tuple of dictionary containing response ``status``
            and ``message``, and duration of operation