Files
edx-platform/xmodule/capa/xqueue_submission.py
gabrielC1409 db27ab6166 feat: Send xqueue submissions to edx-submission
fix: Restructuring to send course_id and score to edx submission

fix: Refactoring of sending information to sent_to_submission

fix: Elimination of unnecessary functions

fix: Added usage comment to ProblemBlock in XqueueInterface constructor

fix: update doc ADR

fix: setting for Quality Others test (ubuntu-24.04, 3.11, 20)

fix: Deprecation for django-trans-escape-filter-parse-error

test: Add @pytest.mark.django_db decorator to test functions

test: Fix for pylint being disabled

fix: updated changes for pylint disable

fix: update error from docs ADR-0005

update: xmodule/docs/decisions/0005-send-data-to-edx-submission.rst

Co-authored-by: Sarina Canelake <sarina@axim.org>

update: xmodule/docs/decisions/0005-send-data-to-edx-submission.rst

Co-authored-by: Sarina Canelake <sarina@axim.org>

fix: Adjusted correction

fix: update date for docs ADR

Revert "fix: update date for docs ADR"

This reverts commit 0b4229c51c4937f95cb407872645dd448df45418.

fix: replace call created_submission to create_external_grader_detail

fix: update test xqueue_submission

fix: add docstring in test_xqueue_submission

fix: update date doc ADR

fix: update version edx-submission 3.8.6

fix: add @pytest.mark.xfail

fix: add 20 chances in test_capa_block:

fix: increase retry attempts for seed generation in ProblemBlockTest

fix: change version to edx-submission lib

fix: new version edx-submission in testings

fix: replace parameter file to files

fix: update variable grader_file_name and points_possible

fix: Adjustment in the is_flag_active function to always take the last record edited in the waffle

fix: wrap large line of code

fix: update function is_flag_active

fix: code style adjustment

fix: changes for 60 retry

feat: use CourseWaffleFlag to determine xqueue callback path

fix: Code style adjustment

fix: remove deprecated xqueue callback route and simplify callback type logic

fix: Deleting a comment in the ADR document

fix: add log in self.block is None

fix: Code style adjustment in log
2025-04-15 11:59:32 -04:00

114 lines
4.0 KiB
Python

"""
This module provides an interface for submitting student responses
to an external grading system through XQueue.
"""
import json
import logging
from xmodule.capa.errors import (
GetSubmissionParamsError,
JSONParsingError,
MissingKeyError,
ValidationError,
TypeErrorSubmission,
RuntimeErrorSubmission
)
log = logging.getLogger(__name__)
class XQueueInterfaceSubmission:
"""Interface to the external grading system."""
def __init__(self, block):
self.block = block
def _parse_json(self, data, name):
"""
Helper function to safely parse data that may or may not be a JSON string.
This is necessary because some callers may already provide parsed Python dicts
(e.g., during internal calls or test cases), while other sources may send raw JSON strings.
This helper ensures consistent behavior regardless of input format.
Args:
data: The input to parse, either a JSON string or a Python dict.
name: Name of the field (used for error reporting).
Returns:
Parsed Python object or original data if already parsed.
Raises:
JSONParsingError: If `data` is a string and cannot be parsed as JSON.
"""
try:
return json.loads(data) if isinstance(data, str) else data
except json.JSONDecodeError as e:
raise JSONParsingError(name, str(e)) from e
def get_submission_params(self, header, payload):
"""
Extracts student submission data from the given header and payload.
"""
header = self._parse_json(header, "header")
payload = self._parse_json(payload, "payload")
queue_name = header.get('queue_name', 'default')
if not self.block:
raise GetSubmissionParamsError()
course_id = str(self.block.scope_ids.usage_id.context_key)
item_type = self.block.scope_ids.block_type
points_possible = self.block.max_score()
item_id = str(self.block.scope_ids.usage_id)
try:
grader_payload = self._parse_json(payload["grader_payload"], "grader_payload")
grader_file_name = grader_payload.get("grader", '')
except KeyError as e:
raise MissingKeyError("grader_payload") from e
student_info = self._parse_json(payload["student_info"], "student_info")
student_id = student_info.get("anonymous_student_id")
if not student_id:
raise ValidationError("The field 'anonymous_student_id' is missing from student_info.")
student_answer = payload.get("student_response")
if student_answer is None:
raise ValidationError("The field 'student_response' does not exist.")
student_dict = {
'item_id': item_id,
'item_type': item_type,
'course_id': course_id,
'student_id': student_id
}
return student_dict, student_answer, queue_name, grader_file_name, points_possible
def send_to_submission(self, header, body, files_to_upload=None):
"""
Submits the extracted student data to the edx-submissions system.
"""
try:
from submissions.api import create_external_grader_detail
student_item, answer, queue_name, grader_file_name, points_possible = (
self.get_submission_params(header, body)
)
return create_external_grader_detail(
student_item,
answer,
queue_name=queue_name,
grader_file_name=grader_file_name,
points_possible=points_possible,
files=files_to_upload
)
except (JSONParsingError, MissingKeyError, ValidationError) as e:
log.error("%s", e)
return {"error": str(e)}
except TypeError as e:
log.error("%s", e)
raise TypeErrorSubmission(str(e)) from e
except RuntimeError as e:
log.error("%s", e)
raise RuntimeErrorSubmission(str(e)) from e