Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
3c06c36
Update PyPI deploy
Thetwam Aug 27, 2024
d8714ca
Update deploy.yml
Thetwam Aug 27, 2024
b4a7300
Update changelog - deploy GH action
Thetwam Aug 27, 2024
5e4f260
Fix errors in changelog
Thetwam Aug 29, 2024
b4be715
get_links, get_link (id), create_link initial implementation
jsmnhou Dec 3, 2024
92ea372
add unittests for LTI resource links
jsmnhou Dec 3, 2024
2de1083
move under "course" class, add fixtures
jsmnhou Dec 3, 2024
42876bd
styling fix
jsmnhou Dec 3, 2024
3e3d5ce
fix import ordering
jsmnhou Dec 7, 2024
317a45f
attempt module import fix
jsmnhou Dec 9, 2024
76f67ba
alphabetize function names
jsmnhou Dec 9, 2024
a8118ec
additional test coverage
jsmnhou Dec 9, 2024
dd38ca9
bruh
jsmnhou Dec 9, 2024
e001286
Update docs to include LTIResourceLink. Update authors and changelog
Thetwam Dec 9, 2024
e9154d2
Merge pull request #673 from jsmnhou/issue/662-api-lti-resource-links
Thetwam Dec 9, 2024
45ba5ac
Merge pull request #677 from handcartcactus/issue/676-improve-develop…
HandcartCactus Dec 27, 2024
33461de
Added beta support for Canvas SmartSearch API
alportoricensis Apr 25, 2025
906665c
Update sphinx. Fix doc build warnings
Thetwam Jun 27, 2025
f9e5073
Set up GH Actions to run against current versions of Python
Thetwam Jun 27, 2025
531638b
Update Black
Thetwam Jun 27, 2025
50d91de
Add kwargs to SearchResults.resolve(). Rework content_type lookup to …
Thetwam Jun 27, 2025
49ed5d5
Merge branch 'develop' of github.com:ucfopen/canvasapi into smartsearch
Thetwam Jun 27, 2025
feb6db1
Update docs, add contrib to changelog. add alportoricensis to authors.
Thetwam Jun 27, 2025
efbca76
Have resolver work directly on type dict. Rework test structure to ma…
Thetwam Jun 30, 2025
aa40fd8
Remove requirement for user to provide Course object. Since Course.sm…
Thetwam Jun 30, 2025
83c4b5a
minor comment tweak
Thetwam Jun 30, 2025
a2b22fd
Merge pull request #690 from alportoricensis/develop
Thetwam Jun 30, 2025
cafac38
Merge pull request #680 from jonespm/issue_675
jonespm Jul 1, 2025
9aeb6e6
Add missing QuizStatistic ref page to docs
Thetwam Aug 4, 2025
1bb9920
New Quizzes Accommodations (#699)
Thetwam Aug 11, 2025
5104eb6
Update for 3.4.0
Thetwam Nov 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 20 additions & 20 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,26 @@ Below you'll find guidelines for contributing that will keep our codebase clean
## Table of Contents

* [Contributing to CanvasAPI](#contributing-to-canvasapi)
* [Table of Contents](#table-of-contents)
* [How can I contribute?](#how-can-i-contribute)
* [Bug reports](#bug-reports)
* [Resolving issues](#resolving-issues)
* [Making your first contribution](#making-your-first-contribution)
* [Setting up the environment](#setting-up-the-environment)
* [Writing tests](#writing-tests)
* [API coverage tests](#api-coverage-tests)
* [Engine tests](#engine-tests)
* [Running tests / coverage reports](#running-tests--coverage-reports)
* [Making a pull request](#making-a-pull-request)
* [Code style guidelines](#code-style-guidelines)
* [Running code style checks](#running-code-style-checks)
* [Foolish consistency](#foolish-consistency)
* [Method docstrings](#method-docstrings)
* [Descriptions](#descriptions)
* [Links to related API endpoints](#links-to-related-api-endpoints)
* [Parameters](#parameters)
* [Returns](#returns)
* [Docstring Examples](#docstring-examples)
* [Table of Contents](#table-of-contents)
* [How can I contribute?](#how-can-i-contribute)
* [Bug reports](#bug-reports)
* [Resolving issues](#resolving-issues)
* [Making your first contribution](#making-your-first-contribution)
* [Setting up the environment](#setting-up-the-environment)
* [Writing tests](#writing-tests)
* [API coverage tests](#api-coverage-tests)
* [Engine tests](#engine-tests)
* [Running tests / coverage reports](#running-tests--coverage-reports)
* [Making a pull request](#making-a-pull-request)
* [Code style guidelines](#code-style-guidelines)
* [Running code style checks](#running-code-style-checks)
* [Foolish consistency](#foolish-consistency)
* [Method docstrings](#method-docstrings)
* [Descriptions](#descriptions)
* [Links to related API endpoints](#links-to-related-api-endpoints)
* [Parameters](#parameters)
* [Returns](#returns)
* [Docstring Examples](#docstring-examples)

## How can I contribute?

Expand Down
9 changes: 6 additions & 3 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v5

- name: Install build dependency
run: pip install build

- name: Create source distribution
run: python setup.py sdist
run: python -m build

- name: Publish distribution to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
Expand Down
3 changes: 3 additions & 0 deletions AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- Adrian Goetz [@a-goetz](https://github.com/a-goetz)
- Aileen Pongnon [@aileenpongnon](https://github.com/aileenpongnon)
- Alyssa Davis [@allygator](https://github.com/allygator)
- Alex Gabriel Nunez-Carrasquillo [@alportoricensis](https://github.com/alportoricensis)
- [@amorqiu](https://github.com/amorqiu)
- Andrew Gardener [@andrew-gardener](https://github.com/andrew-gardener)
- Anthony Rodriguez [@AnthonyRodriguez726](https://github.com/AnthonyRodriguez726)
Expand All @@ -42,6 +43,7 @@
- Deundre Williams [@deundrewilliams](https://github.com/deundrewilliams)
- Devin Singh [@devints47](https://github.com/devints47)
- Dmitry Savransky [@dsavransky](https://github.com/dsavransky)
- Elias [@HandcartCactus](https://github.com/HandcartCactus)
- Elise Heron [@thedarkestknight](https://github.com/thedarkestknight)
- Elli Howard [@qwertynerd97](https://github.com/qwertynerd97)
- Erik Tews [@eriktews](https://github.com/eriktews)
Expand All @@ -53,6 +55,7 @@
- Ian Altgilbers [@altgilbers](https://github.com/altgilbers)
- Ian Turgeon [@iturgeon](https://github.com/iturgeon)
- [@jackrsteiner](https://github.com/jackrsteiner)
- Jasmine Hou [@jsmnhou](https://github.com/jsmnhou)
- John Raible [@rebelaide](https://github.com/rebelaide)
- Joon Ro [@joonro](https://github.com/joonro)
- Jonah Majumder [@jonahmajumder](https://github.com/jonahmajumder)
Expand Down
27 changes: 24 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,36 @@

## [Unreleased]

## [3.4.0] - 2025-11-10

### New Endpoint Coverage

- LTI Resource Links (Thanks, [@jsmnhou](https://github.com/jsmnhou))
- Smart Search API [BETA] (Thanks, [@alportoricensis](https://github.com/alportoricensis))
- New Quizzes Accommodations

### General

- Added support for Python 3.12 and 3.13
- Dropped support for Python 3.7 and 3.8

### Backstage

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

## [3.3.0] - 2023-08-27

### General

- Added documentation for PaginatedList
- Rework requester URLs to accomodate graphql and new quizzes endpoints (Thanks, [@bennettscience](https://github.com/bennettscience))
- Rework requester URLs to accommodate graphql and new quizzes endpoints (Thanks, [@bennettscience](https://github.com/bennettscience))

### Bugfixes

- Fixed PaginatedList not respecting new quizzes endpoints (Thanks, [@matthewf-ucsd](https://github.com/matthewf-ucsd))
- Fixed PaginatedList not respecting new quizzes endpoints (Thanks, [@jonespm](https://github.com/jonespm))

### Backstage

Expand Down Expand Up @@ -636,7 +656,8 @@ Huge thanks to [@liblit](https://github.com/liblit) for lots of issues, suggesti
- Fixed some incorrectly defined parameters
- Fixed an issue where tests would fail due to an improperly configured requires block

[Unreleased]: https://github.com/ucfopen/canvasapi/compare/v3.3.0...develop
[Unreleased]: https://github.com/ucfopen/canvasapi/compare/v3.4.0...develop
[3.4.0]: https://github.com/ucfopen/canvasapi/compare/v3.3.0...v3.4.0
[3.3.0]: https://github.com/ucfopen/canvasapi/compare/v3.2.0...v3.3.0
[3.2.0]: https://github.com/ucfopen/canvasapi/compare/v3.1.0...v3.2.0
[3.1.0]: https://github.com/ucfopen/canvasapi/compare/v3.0.0...v3.1.0
Expand Down
2 changes: 1 addition & 1 deletion canvasapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@

__all__ = ["Canvas"]

__version__ = "3.3.0"
__version__ = "3.4.0"
8 changes: 5 additions & 3 deletions canvasapi/canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -1276,11 +1276,13 @@ def graphql(self, query, variables=None, **kwargs):
"POST",
"graphql",
headers={"Content-Type": "application/json"},
_kwargs=combine_kwargs(**kwargs)
+ [("query", query), ("variables", variables)],
_kwargs=combine_kwargs(**kwargs),
# Needs to call special endpoint without api/v1
_url="graphql",
json=True,
json={
"query": query,
"variables": variables if variables else {},
},
)

return response.json()
Expand Down
136 changes: 134 additions & 2 deletions canvasapi/course.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,16 @@
from canvasapi.grading_period import GradingPeriod
from canvasapi.grading_standard import GradingStandard
from canvasapi.license import License
from canvasapi.lti_resource_link import LTIResourceLink
from canvasapi.module import Module
from canvasapi.new_quiz import NewQuiz
from canvasapi.new_quiz import AccommodationResponse, NewQuiz
from canvasapi.outcome_import import OutcomeImport
from canvasapi.page import Page
from canvasapi.paginated_list import PaginatedList
from canvasapi.progress import Progress
from canvasapi.quiz import QuizExtension
from canvasapi.rubric import Rubric, RubricAssociation
from canvasapi.searchresult import SearchResult
from canvasapi.submission import GroupedSubmission, Submission
from canvasapi.tab import Tab
from canvasapi.todo import Todo
Expand Down Expand Up @@ -438,6 +440,39 @@ def create_late_policy(self, **kwargs):

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

def create_lti_resource_link(self, url, title=None, custom=None, **kwargs):
"""
Create a new LTI resource link.

:calls: `POST /api/v1/courses/:course_id/lti_resource_links \
<https://canvas.instructure.com/doc/api/lti_resource_links.html#method.lti/resource_links.create>`_

:param url: The launch URL for the resource link.
:type url: `str`
:param title: The title of the resource link.
:type title: `str`, optional
:param custom: Custom parameters to send to the tool.
:type custom: `dict`, optional

:rtype: :class:`canvasapi.lti_resource_link.LTIResourceLink`
"""

if not url:
raise RequiredFieldMissing("url is required as a str.")

kwargs["url"] = url
if title:
kwargs["title"] = title
if custom:
kwargs["custom"] = custom

response = self._requester.request(
"POST",
f"courses/{self.id}/lti_resource_links",
_kwargs=combine_kwargs(**kwargs),
)
return LTIResourceLink(self._requester, response.json())

def create_module(self, module, **kwargs):
"""
Create a new module.
Expand Down Expand Up @@ -1645,6 +1680,49 @@ def get_licenses(self, **kwargs):
_kwargs=combine_kwargs(**kwargs),
)

def get_lti_resource_link(self, lti_resource_link, **kwargs):
"""
Return details about the specified resource link.

:calls: `GET /api/v1/courses/:course_id/lti_resource_links/:id \
<https://canvas.instructure.com/doc/api/lti_resource_links.html#method.lti/resource_links.show>`_

:param lti_resource_link: The object or ID of the LTI resource link.
:type lti_resource_link: :class:`canvasapi.lti_resource_link.LTIResourceLink` or int

:rtype: :class:`canvasapi.lti_resource_link.LTIResourceLink`
"""

lti_resource_link_id = obj_or_id(
lti_resource_link, "lti_resource_link", (LTIResourceLink,)
)

response = self._requester.request(
"GET",
f"courses/{self.id}/lti_resource_links/{lti_resource_link_id}",
_kwargs=combine_kwargs(**kwargs),
)
return LTIResourceLink(self._requester, response.json())

def get_lti_resource_links(self, **kwargs):
"""
Returns all LTI resource links for this course as a PaginatedList.

:calls: `GET /api/v1/courses/:course_id/lti_resource_links \
<https://canvas.instructure.com/doc/api/lti_resource_links.html#method.lti/resource_links.index>`_

:rtype: :class:`canvasapi.paginated_list.PaginatedList` of
:class:`canvasapi.lti_resource_link.LTIResourceLink`
"""

return PaginatedList(
LTIResourceLink,
self._requester,
"GET",
f"courses/{self.id}/lti_resource_links",
kwargs=combine_kwargs(**kwargs),
)

def get_migration_systems(self, **kwargs):
"""
Return a list of migration systems.
Expand Down Expand Up @@ -1768,7 +1846,7 @@ def get_new_quiz(self, assignment, **kwargs):

def get_new_quizzes(self, **kwargs):
"""
Get a list of new quizzes.
Get a list of new quizzes in this course.

:calls: `GET /api/quiz/v1/courses/:course_id/quizzes \
<https://canvas.instructure.com/doc/api/new_quizzes.html#method.new_quizzes/quizzes_api.index>`_
Expand All @@ -1783,6 +1861,7 @@ def get_new_quizzes(self, **kwargs):
self._requester,
"GET",
endpoint,
{"course_id": self.id},
_url_override="new_quizzes",
_kwargs=combine_kwargs(**kwargs),
)
Expand Down Expand Up @@ -2576,6 +2655,33 @@ def resolve_path(self, full_path=None, **kwargs):
_kwargs=combine_kwargs(**kwargs),
)

def set_new_quizzes_accommodations(self, accommodations, **kwargs):
"""
Apply accommodations to New Quizzes at the **course level** for
students enrolled in this course.

:calls: `POST /api/quiz/v1/courses/:course_id/accommodations \
<https://developerdocs.instructure.com/services/canvas/resources/new_quizzes_accommodations#method.new_quizzes-accommodation_api.course_level_accommodations>`_

:param accommodations: A list of dictionaries containing accommodation details
for each user. Each dictionary must contain `user_id` and can optionally include
`extra_time`, `apply_to_in_progress_quiz_sessions`, and/or `reduce_choices_enabled`.
:type accommodations: list of dict

:returns: AccommodationResponse object containing the status of the accommodation request.
:rtype: :class:`canvasapi.new_quiz.AccommodationResponse`
"""
endpoint = "courses/{}/accommodations".format(self.id)

response = self._requester.request(
"POST",
endpoint,
_url="new_quizzes",
_kwargs=combine_kwargs(**kwargs),
json=accommodations,
)
return AccommodationResponse(self._requester, response.json())

def set_quiz_extensions(self, quiz_extensions, **kwargs):
"""
Set extensions for student all quiz submissions in a course.
Expand Down Expand Up @@ -2667,6 +2773,32 @@ def show_front_page(self, **kwargs):

return Page(self._requester, page_json)

def smartsearch(self, q, **kwargs):
"""
AI-powered course content search.

:calls: `GET /api/v1/courses/:course_id/smartsearch \
<https://canvas.instructure.com/doc/api/smart_search.html#method.smart_search.search>`_

:param q: The search query string.
:type q: str
:param kwargs: Optional query parameters (e.g., filter, per_page).
:type kwargs: dict
:rtype: :class:`canvasapi.paginated_list.PaginatedList` of
:class:`canvasapi.searchresult.SearchResult`
"""
kwargs["q"] = q

return PaginatedList(
SearchResult,
self._requester,
"GET",
f"courses/{self.id}/smartsearch",
{"course_id": self.id},
_root="results",
_kwargs=combine_kwargs(**kwargs),
)

def submissions_bulk_update(self, **kwargs):
"""
Update the grading and comments on multiple student's assignment
Expand Down
6 changes: 6 additions & 0 deletions canvasapi/lti_resource_link.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from canvasapi.canvas_object import CanvasObject


class LTIResourceLink(CanvasObject):
def __str__(self):
return "{} ({})".format(self.url, self.title)
Loading