Skip to content

requester.request json keyword doesn't allow for actual json inputs #668

@dsavransky

Description

@dsavransky

Describe the bug

I'm working with the new quiz items API (https://canvas.instructure.com/doc/api/new_quiz_items.html) and have run into an issue where it appears that the structure of canvasapi's request class is incompatible with this endpoint.

Specifically, the scoring_data dictionary for the multiple choice with varying point values by answer item type (https://canvas.instructure.com/doc/api/new_quiz_items.html#choice) requires an internal list of dictionaries, e.g.:

"scoring_data": {
    "value": "f1feae62-566d-4d85-9afb-182d757030c9",
    "values": [
        {
            "value": "f1feae62-566d-4d85-9afb-182d757030c9",
            "points": 2
        },
        {
            "value": "22d2bd84-5d5a-4640-8acf-484d799a735a",
            "points": 1
        },
        {
            "value": "74a22711-85a6-4b46-8fb3-ea22947e869a",
            "points": 0
        },
        {
            "value": "3ae2c63d-b990-40f0-aa70-e6bb854d0de2",
            "points": 0
        },
        {
            "value": "8193a348-d336-4b00-b60e-dece48739ce4",
            "points": 0
        }
    ]
}

If you run this through combine_kwargs you'll get something like:

[('item[scoring_data][value]', 'f1feae62-566d-4d85-9afb-182d757030c9'),
 ('item[scoring_data][values][][value]',
  'f1feae62-566d-4d85-9afb-182d757030c9'),
 ('item[scoring_data][values][][points]', 2),
 ('item[scoring_data][values][][value]',
  '22d2bd84-5d5a-4640-8acf-484d799a735a'),
 ('item[scoring_data][values][][points]', 1),
 ('item[scoring_data][values][][value]',
  '74a22711-85a6-4b46-8fb3-ea22947e869a'),
 ('item[scoring_data][values][][points]', 0),
 ('item[scoring_data][values][][value]',
  '3ae2c63d-b990-40f0-aa70-e6bb854d0de2'),
 ('item[scoring_data][values][][points]', 0),
 ('item[scoring_data][values][][value]',
  '8193a348-d336-4b00-b60e-dece48739ce4'),
 ('item[scoring_data][values][][points]', 0)]

Trying to then PUT this data up to Canvas will generate the error:
UnprocessableEntity: {"errors":{"scoring_data":["Invalid Scoring Data: The property '#/values/0/points' of type string did not match the following type: number"]},"message":"Unprocessable Entity"}

This seems to be a case where it is much simpler to just send a dictionary directly, and I see that the request method has a json keyword, but it looks like setting this to True, doesn't actually allow you to send a data dictionary as _kwargs is still expected to be a list of tuples. Is there any way to make this work with the canvasapi requester, or is this a case where just manually assembling the request is the best way to go?

To Reproduce

Minimal worked example:

#course is a canvasapi course object

nq = course.create_new_quiz(
    quiz={
        "title": "Quiz Example",
        "assignment_group_id": xxxxxx,
        "points_possible": 10,
        "grading_type": "points",
        "instructions": "something"})

item = {
    "position": 1,
    "points_possible": 3.0,
    "entry_type": "Item",
    "status": "immutable",
    "entry": {
        "title": "Q1",
        "item_body": "<p>Answer the question</p>",
        "calculator_type": "none",
        "interaction_data": {
            "choices": [
                {
                    "id": "e5519152-c925-4abd-a0ec-3081511ca636",
                    "position": 1,
                    "item_body": "<p>0</p>",
                },
                {
                    "id": "3f55f6d8-7115-4d5b-bb70-07e15bb76112",
                    "position": 2,
                    "item_body": "<p>1</p>",
                },
                {
                    "id": "7d243ebc-cff9-4b97-b047-b96d0b8e2aed",
                    "position": 3,
                    "item_body": "<p>2</p>",
                },
                {
                    "id": "ff4cc47b-1846-4e7f-8482-b471685f8865",
                    "position": 4,
                    "item_body": "<p>3</p>",
                },
            ]
        },
        "properties": {
            "shuffle_rules": {"choices": {"to_lock": [], "shuffled": False}},
            "vary_points_by_answer": True,
        },
        "scoring_data": {
            "value": "ff4cc47b-1846-4e7f-8482-b471685f8865",
            "values": [
                {"value": "e5519152-c925-4abd-a0ec-3081511ca636", "points": 0},
                {"value": "3f55f6d8-7115-4d5b-bb70-07e15bb76112", "points": 1},
                {"value": "7d243ebc-cff9-4b97-b047-b96d0b8e2aed", "points": 2},
                {"value": "ff4cc47b-1846-4e7f-8482-b471685f8865", "points": 3},
            ],
        },
        "answer_feedback": {"e5519152-c925-4abd-a0ec-3081511ca636": ""},
        "scoring_algorithm": "VaryPointsByAnswer",
        "interaction_type_slug": "choice",
        "feedback": {},
    },
}

endpoint = "courses/{}/quizzes/{}/items".format(course.id, nq.id)

# this leads to Canvas error:
r =nq._requester.request(
            "POST",
            endpoint,
            _url="new_quizzes",
            _kwargs=combine_kwargs(**{"item":item}),
        )

#setting json=True leads to different error: BadRequest: {"error":"Expected `item` object"}

Expected behavior

Setting up the request manually works as expected:

full_url = "{}{}".format(
    course._requester.new_quizzes_url,
    "courses/{}/quizzes/{}/items".format(course.id, nq.id),
)
headers = {"Authorization": "Bearer {}".format(course._requester.access_token)}
r = requests.post(full_url, json={"item": tmp}, headers=headers)

Environment information

  • Python 3.11.9
  • CanvasAPI version 3.3.0

Additional context

Thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions