Skip to content

Commit b173567

Browse files
authored
Merge branch 'ucfopen:develop' into issue/440-allow-sis-ids
2 parents beec165 + 1bb9920 commit b173567

30 files changed

+847
-53
lines changed

.github/CONTRIBUTING.md

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,26 @@ Below you'll find guidelines for contributing that will keep our codebase clean
77
## Table of Contents
88

99
* [Contributing to CanvasAPI](#contributing-to-canvasapi)
10-
* [Table of Contents](#table-of-contents)
11-
* [How can I contribute?](#how-can-i-contribute)
12-
* [Bug reports](#bug-reports)
13-
* [Resolving issues](#resolving-issues)
14-
* [Making your first contribution](#making-your-first-contribution)
15-
* [Setting up the environment](#setting-up-the-environment)
16-
* [Writing tests](#writing-tests)
17-
* [API coverage tests](#api-coverage-tests)
18-
* [Engine tests](#engine-tests)
19-
* [Running tests / coverage reports](#running-tests--coverage-reports)
20-
* [Making a pull request](#making-a-pull-request)
21-
* [Code style guidelines](#code-style-guidelines)
22-
* [Running code style checks](#running-code-style-checks)
23-
* [Foolish consistency](#foolish-consistency)
24-
* [Method docstrings](#method-docstrings)
25-
* [Descriptions](#descriptions)
26-
* [Links to related API endpoints](#links-to-related-api-endpoints)
27-
* [Parameters](#parameters)
28-
* [Returns](#returns)
29-
* [Docstring Examples](#docstring-examples)
10+
* [Table of Contents](#table-of-contents)
11+
* [How can I contribute?](#how-can-i-contribute)
12+
* [Bug reports](#bug-reports)
13+
* [Resolving issues](#resolving-issues)
14+
* [Making your first contribution](#making-your-first-contribution)
15+
* [Setting up the environment](#setting-up-the-environment)
16+
* [Writing tests](#writing-tests)
17+
* [API coverage tests](#api-coverage-tests)
18+
* [Engine tests](#engine-tests)
19+
* [Running tests / coverage reports](#running-tests--coverage-reports)
20+
* [Making a pull request](#making-a-pull-request)
21+
* [Code style guidelines](#code-style-guidelines)
22+
* [Running code style checks](#running-code-style-checks)
23+
* [Foolish consistency](#foolish-consistency)
24+
* [Method docstrings](#method-docstrings)
25+
* [Descriptions](#descriptions)
26+
* [Links to related API endpoints](#links-to-related-api-endpoints)
27+
* [Parameters](#parameters)
28+
* [Returns](#returns)
29+
* [Docstring Examples](#docstring-examples)
3030

3131
## How can I contribute?
3232

.github/workflows/run-tests.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ jobs:
1111
runs-on: ubuntu-latest
1212
strategy:
1313
matrix:
14-
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
14+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
1515

1616
steps:
17-
- uses: actions/checkout@v2
17+
- uses: actions/checkout@v4
1818
- name: Set up Python ${{ matrix.python-version }}
19-
uses: actions/setup-python@v2
19+
uses: actions/setup-python@v5
2020
with:
2121
python-version: ${{ matrix.python-version }}
2222
- name: Install dependencies

AUTHORS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
- Adrian Goetz [@a-goetz](https://github.com/a-goetz)
1818
- Aileen Pongnon [@aileenpongnon](https://github.com/aileenpongnon)
1919
- Alyssa Davis [@allygator](https://github.com/allygator)
20+
- Alex Gabriel Nunez-Carrasquillo [@alportoricensis](https://github.com/alportoricensis)
2021
- [@amorqiu](https://github.com/amorqiu)
2122
- Andrew Gardener [@andrew-gardener](https://github.com/andrew-gardener)
2223
- Anthony Rodriguez [@AnthonyRodriguez726](https://github.com/AnthonyRodriguez726)
@@ -42,6 +43,7 @@
4243
- Deundre Williams [@deundrewilliams](https://github.com/deundrewilliams)
4344
- Devin Singh [@devints47](https://github.com/devints47)
4445
- Dmitry Savransky [@dsavransky](https://github.com/dsavransky)
46+
- Elias [@HandcartCactus](https://github.com/HandcartCactus)
4547
- Elise Heron [@thedarkestknight](https://github.com/thedarkestknight)
4648
- Elli Howard [@qwertynerd97](https://github.com/qwertynerd97)
4749
- Erik Tews [@eriktews](https://github.com/eriktews)
@@ -53,6 +55,7 @@
5355
- Ian Altgilbers [@altgilbers](https://github.com/altgilbers)
5456
- Ian Turgeon [@iturgeon](https://github.com/iturgeon)
5557
- [@jackrsteiner](https://github.com/jackrsteiner)
58+
- Jasmine Hou [@jsmnhou](https://github.com/jsmnhou)
5659
- John Raible [@rebelaide](https://github.com/rebelaide)
5760
- Joon Ro [@joonro](https://github.com/joonro)
5861
- Jonah Majumder [@jonahmajumder](https://github.com/jonahmajumder)

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,23 @@
22

33
## [Unreleased]
44

5+
### New Endpoint Coverage
6+
7+
- LTI Resource Links (Thanks, [@jsmnhou](https://github.com/jsmnhou))
8+
- Smart Search API [BETA] (Thanks, [@alportoricensis](https://github.com/alportoricensis))
9+
- New Quizzes Accommodations
10+
11+
### General
12+
13+
- Added support for Python 3.12 and 3.13
14+
- Dropped support for Python 3.7 and 3.8
15+
516
### Backstage
617

718
- Updated deploy Action to use more modern processes.
19+
- Updated `PaginatedList` to be type-aware, showing which class is included in the response. (Thanks [@HandcartCactus](https://github.com/HandcartCactus))
20+
- Updated Sphinx
21+
- Reworked how `Requester` handles JSON-only POST requests (currently, only New Quizzes Accommodations and GraphQL)
822

923
## [3.3.0] - 2023-08-27
1024

canvasapi/canvas.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1276,11 +1276,13 @@ def graphql(self, query, variables=None, **kwargs):
12761276
"POST",
12771277
"graphql",
12781278
headers={"Content-Type": "application/json"},
1279-
_kwargs=combine_kwargs(**kwargs)
1280-
+ [("query", query), ("variables", variables)],
1279+
_kwargs=combine_kwargs(**kwargs),
12811280
# Needs to call special endpoint without api/v1
12821281
_url="graphql",
1283-
json=True,
1282+
json={
1283+
"query": query,
1284+
"variables": variables if variables else {},
1285+
},
12841286
)
12851287

12861288
return response.json()

canvasapi/course.py

Lines changed: 134 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,16 @@
2323
from canvasapi.grading_period import GradingPeriod
2424
from canvasapi.grading_standard import GradingStandard
2525
from canvasapi.license import License
26+
from canvasapi.lti_resource_link import LTIResourceLink
2627
from canvasapi.module import Module
27-
from canvasapi.new_quiz import NewQuiz
28+
from canvasapi.new_quiz import AccommodationResponse, NewQuiz
2829
from canvasapi.outcome_import import OutcomeImport
2930
from canvasapi.page import Page
3031
from canvasapi.paginated_list import PaginatedList
3132
from canvasapi.progress import Progress
3233
from canvasapi.quiz import QuizExtension
3334
from canvasapi.rubric import Rubric, RubricAssociation
35+
from canvasapi.searchresult import SearchResult
3436
from canvasapi.submission import GroupedSubmission, Submission
3537
from canvasapi.tab import Tab
3638
from canvasapi.todo import Todo
@@ -439,6 +441,39 @@ def create_late_policy(self, **kwargs):
439441

440442
return LatePolicy(self._requester, late_policy_json["late_policy"])
441443

444+
def create_lti_resource_link(self, url, title=None, custom=None, **kwargs):
445+
"""
446+
Create a new LTI resource link.
447+
448+
:calls: `POST /api/v1/courses/:course_id/lti_resource_links \
449+
<https://canvas.instructure.com/doc/api/lti_resource_links.html#method.lti/resource_links.create>`_
450+
451+
:param url: The launch URL for the resource link.
452+
:type url: `str`
453+
:param title: The title of the resource link.
454+
:type title: `str`, optional
455+
:param custom: Custom parameters to send to the tool.
456+
:type custom: `dict`, optional
457+
458+
:rtype: :class:`canvasapi.lti_resource_link.LTIResourceLink`
459+
"""
460+
461+
if not url:
462+
raise RequiredFieldMissing("url is required as a str.")
463+
464+
kwargs["url"] = url
465+
if title:
466+
kwargs["title"] = title
467+
if custom:
468+
kwargs["custom"] = custom
469+
470+
response = self._requester.request(
471+
"POST",
472+
f"courses/{self.id}/lti_resource_links",
473+
_kwargs=combine_kwargs(**kwargs),
474+
)
475+
return LTIResourceLink(self._requester, response.json())
476+
442477
def create_module(self, module, **kwargs):
443478
"""
444479
Create a new module.
@@ -1646,6 +1681,49 @@ def get_licenses(self, **kwargs):
16461681
_kwargs=combine_kwargs(**kwargs),
16471682
)
16481683

1684+
def get_lti_resource_link(self, lti_resource_link, **kwargs):
1685+
"""
1686+
Return details about the specified resource link.
1687+
1688+
:calls: `GET /api/v1/courses/:course_id/lti_resource_links/:id \
1689+
<https://canvas.instructure.com/doc/api/lti_resource_links.html#method.lti/resource_links.show>`_
1690+
1691+
:param lti_resource_link: The object or ID of the LTI resource link.
1692+
:type lti_resource_link: :class:`canvasapi.lti_resource_link.LTIResourceLink` or int
1693+
1694+
:rtype: :class:`canvasapi.lti_resource_link.LTIResourceLink`
1695+
"""
1696+
1697+
lti_resource_link_id = obj_or_id(
1698+
lti_resource_link, "lti_resource_link", (LTIResourceLink,)
1699+
)
1700+
1701+
response = self._requester.request(
1702+
"GET",
1703+
f"courses/{self.id}/lti_resource_links/{lti_resource_link_id}",
1704+
_kwargs=combine_kwargs(**kwargs),
1705+
)
1706+
return LTIResourceLink(self._requester, response.json())
1707+
1708+
def get_lti_resource_links(self, **kwargs):
1709+
"""
1710+
Returns all LTI resource links for this course as a PaginatedList.
1711+
1712+
:calls: `GET /api/v1/courses/:course_id/lti_resource_links \
1713+
<https://canvas.instructure.com/doc/api/lti_resource_links.html#method.lti/resource_links.index>`_
1714+
1715+
:rtype: :class:`canvasapi.paginated_list.PaginatedList` of
1716+
:class:`canvasapi.lti_resource_link.LTIResourceLink`
1717+
"""
1718+
1719+
return PaginatedList(
1720+
LTIResourceLink,
1721+
self._requester,
1722+
"GET",
1723+
f"courses/{self.id}/lti_resource_links",
1724+
kwargs=combine_kwargs(**kwargs),
1725+
)
1726+
16491727
def get_migration_systems(self, **kwargs):
16501728
"""
16511729
Return a list of migration systems.
@@ -1769,7 +1847,7 @@ def get_new_quiz(self, assignment, **kwargs):
17691847

17701848
def get_new_quizzes(self, **kwargs):
17711849
"""
1772-
Get a list of new quizzes.
1850+
Get a list of new quizzes in this course.
17731851
17741852
:calls: `GET /api/quiz/v1/courses/:course_id/quizzes \
17751853
<https://canvas.instructure.com/doc/api/new_quizzes.html#method.new_quizzes/quizzes_api.index>`_
@@ -1784,6 +1862,7 @@ def get_new_quizzes(self, **kwargs):
17841862
self._requester,
17851863
"GET",
17861864
endpoint,
1865+
{"course_id": self.id},
17871866
_url_override="new_quizzes",
17881867
_kwargs=combine_kwargs(**kwargs),
17891868
)
@@ -2577,6 +2656,33 @@ def resolve_path(self, full_path=None, **kwargs):
25772656
_kwargs=combine_kwargs(**kwargs),
25782657
)
25792658

2659+
def set_new_quizzes_accommodations(self, accommodations, **kwargs):
2660+
"""
2661+
Apply accommodations to New Quizzes at the **course level** for
2662+
students enrolled in this course.
2663+
2664+
:calls: `POST /api/quiz/v1/courses/:course_id/accommodations \
2665+
<https://developerdocs.instructure.com/services/canvas/resources/new_quizzes_accommodations#method.new_quizzes-accommodation_api.course_level_accommodations>`_
2666+
2667+
:param accommodations: A list of dictionaries containing accommodation details
2668+
for each user. Each dictionary must contain `user_id` and can optionally include
2669+
`extra_time`, `apply_to_in_progress_quiz_sessions`, and/or `reduce_choices_enabled`.
2670+
:type accommodations: list of dict
2671+
2672+
:returns: AccommodationResponse object containing the status of the accommodation request.
2673+
:rtype: :class:`canvasapi.new_quiz.AccommodationResponse`
2674+
"""
2675+
endpoint = "courses/{}/accommodations".format(self.id)
2676+
2677+
response = self._requester.request(
2678+
"POST",
2679+
endpoint,
2680+
_url="new_quizzes",
2681+
_kwargs=combine_kwargs(**kwargs),
2682+
json=accommodations,
2683+
)
2684+
return AccommodationResponse(self._requester, response.json())
2685+
25802686
def set_quiz_extensions(self, quiz_extensions, **kwargs):
25812687
"""
25822688
Set extensions for student all quiz submissions in a course.
@@ -2668,6 +2774,32 @@ def show_front_page(self, **kwargs):
26682774

26692775
return Page(self._requester, page_json)
26702776

2777+
def smartsearch(self, q, **kwargs):
2778+
"""
2779+
AI-powered course content search.
2780+
2781+
:calls: `GET /api/v1/courses/:course_id/smartsearch \
2782+
<https://canvas.instructure.com/doc/api/smart_search.html#method.smart_search.search>`_
2783+
2784+
:param q: The search query string.
2785+
:type q: str
2786+
:param kwargs: Optional query parameters (e.g., filter, per_page).
2787+
:type kwargs: dict
2788+
:rtype: :class:`canvasapi.paginated_list.PaginatedList` of
2789+
:class:`canvasapi.searchresult.SearchResult`
2790+
"""
2791+
kwargs["q"] = q
2792+
2793+
return PaginatedList(
2794+
SearchResult,
2795+
self._requester,
2796+
"GET",
2797+
f"courses/{self.id}/smartsearch",
2798+
{"course_id": self.id},
2799+
_root="results",
2800+
_kwargs=combine_kwargs(**kwargs),
2801+
)
2802+
26712803
def submissions_bulk_update(self, **kwargs):
26722804
"""
26732805
Update the grading and comments on multiple student's assignment

canvasapi/lti_resource_link.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from canvasapi.canvas_object import CanvasObject
2+
3+
4+
class LTIResourceLink(CanvasObject):
5+
def __str__(self):
6+
return "{} ({})".format(self.url, self.title)

canvasapi/new_quiz.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,34 @@ def delete(self, **kwargs):
2929

3030
return NewQuiz(self._requester, response_json)
3131

32+
def set_accommodations(self, accommodations, **kwargs):
33+
"""
34+
Apply accommodations at the quiz level for students in a specific assignment.
35+
36+
:calls: `POST /api/quiz/v1/courses/:course_id/quizzes/:assignment_id/accommodations \
37+
<https://developerdocs.instructure.com/services/canvas/resources/new_quizzes_accommodations#method.new_quizzes-accommodation_api.quiz_level_accommodations>`_
38+
39+
:param accommodations: A list of dictionaries containing accommodation details
40+
for each user. Each dictionary must contain `user_id` and can optionally include
41+
`extra_time`, `extra_attempts`, and/or `reduce_choices_enabled`.
42+
:type accommodations: list of dict
43+
44+
:returns: AccommodationResponse object containing the status of the accommodation request.
45+
:rtype: :class:`canvasapi.new_quiz.AccommodationResponse`
46+
"""
47+
endpoint = "courses/{}/quizzes/{}/accommodations".format(
48+
self.course_id, self.id
49+
)
50+
51+
response = self._requester.request(
52+
"POST",
53+
endpoint,
54+
_url="new_quizzes",
55+
_kwargs=combine_kwargs(**kwargs),
56+
json=accommodations,
57+
)
58+
return AccommodationResponse(self._requester, response.json())
59+
3260
def update(self, **kwargs):
3361
"""
3462
Update a single New Quiz for the course.
@@ -51,3 +79,8 @@ def update(self, **kwargs):
5179
response_json.update({"course_id": self.course_id})
5280

5381
return NewQuiz(self._requester, response_json)
82+
83+
84+
class AccommodationResponse(CanvasObject):
85+
def __str__(self):
86+
return f"{self.message}"

0 commit comments

Comments
 (0)