Skip to content

Issue/662 Add API coverage for LTI resource links #673

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Dec 9, 2024
Merged
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions AUTHORS.md
Original file line number Diff line number Diff line change
@@ -53,6 +53,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)
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -2,6 +2,10 @@

## [Unreleased]

### New Endpoint Coverage

- LTI Resource Links (Thanks, [@jsmnhou](https://github.com/jsmnhou))

### Backstage

- Updated deploy Action to use more modern processes.
77 changes: 77 additions & 0 deletions canvasapi/course.py
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@
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.outcome_import import OutcomeImport
@@ -438,6 +439,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.
@@ -1645,6 +1679,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.
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)
1 change: 1 addition & 0 deletions docs/class-reference.rst
Original file line number Diff line number Diff line change
@@ -41,6 +41,7 @@ Class Reference
jwt-ref
login-ref
license-ref
lti-resource-link-ref
module-ref
outcome-ref
outcome-import-ref
6 changes: 6 additions & 0 deletions docs/lti-resource-link-ref.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
===============
LTIResourceLink
===============

.. autoclass:: canvasapi.lti_resource_link.LTIResourceLink
:members:
49 changes: 49 additions & 0 deletions tests/fixtures/lti_resource_link.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"create_lti_resource_link": {
"method": "POST",
"endpoint": "courses/1/lti_resource_links",
"data": {
"id": 45,
"context_id": 1,
"context_type": "Course",
"context_external_tool_id": 1,
"resource_type": "assignment",
"canvas_launch_url": "https://example.instructure.com/courses/1/external_tools/retrieve?resource_link_lookup_uuid=ae43ba23-d238-49bc-ab55-ba7f79f77896",
"resource_link_uuid": "ae43ba23-d238-49bc-ab55-ba7f79f77896",
"lookup_uuid": "c522554a-d4be-49ef-b163-9c87fdc6ad6f",
"title": "Test LTI Resource Link",
"url": "https://example.com/lti/launch/content_item/123"
},
"status_code": 200
},

"get_lti_resource_link": {
"method": "GET",
"endpoint": "courses/1/lti_resource_links/45",
"data": {
"id": 45,
"title": "Test LTI Resource Link",
"url": "https://example.com/lti/launch/content_item/123"
},
"status_code": 200
},
"list_lti_resource_links": {
"method": "GET",
"endpoint": "courses/1/lti_resource_links",
"data": [
{
"id": 45,
"title": "Test LTI Resource Link"
},
{
"id": 56,
"title": "Test LTI Resource Link 2"
},
{
"id": 67,
"title": "Test LTI Resource Link 3"
}
],
"status_code": 200
}
}
43 changes: 43 additions & 0 deletions tests/test_course.py
Original file line number Diff line number Diff line change
@@ -35,6 +35,7 @@
from canvasapi.grading_standard import GradingStandard
from canvasapi.group import Group, GroupCategory
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.outcome import OutcomeGroup, OutcomeLink, OutcomeResult
@@ -1890,6 +1891,48 @@ def test_resolve_path_null(self, m):
self.assertIsInstance(root_folder_list[0], Folder)
self.assertEqual("course_files", root_folder_list[0].name)

# create_lti_resource_link()
def test_create_lti_resource_link(self, m):
register_uris({"lti_resource_link": ["create_lti_resource_link"]}, m)
custom_dict = {"hello": "world"}

evnt = self.course.create_lti_resource_link(
url="https://example.com/lti/launch/content_item/123",
title="Test LTI Resource Link",
custom=custom_dict,
)
self.assertIsInstance(evnt, LTIResourceLink)

self.assertEqual(evnt.title, "Test LTI Resource Link")
self.assertEqual(evnt.url, "https://example.com/lti/launch/content_item/123")

def test_create_lti_resource_link_fail(self, m):
with self.assertRaises(RequiredFieldMissing):
self.course.create_lti_resource_link({})

# get_lti_resource_links()
def test_get_lti_resource_links(self, m):
register_uris({"lti_resource_link": ["list_lti_resource_links"]}, m)

lti_resource_links = self.course.get_lti_resource_links()
lti_resource_link_list = [link for link in lti_resource_links]
self.assertEqual(len(lti_resource_link_list), 3)
self.assertIsInstance(lti_resource_link_list[0], LTIResourceLink)

# get_lti_resource_link()
def test_get_lti_resource_link(self, m):
register_uris({"lti_resource_link": ["get_lti_resource_link"]}, m)

lti_resource_link_by_id = self.course.get_lti_resource_link(45)

self.assertIsInstance(lti_resource_link_by_id, LTIResourceLink)
self.assertEqual(lti_resource_link_by_id.title, "Test LTI Resource Link")
lti_resource_link_by_obj = self.course.get_lti_resource_link(
lti_resource_link_by_id
)
self.assertIsInstance(lti_resource_link_by_obj, LTIResourceLink)
self.assertEqual(lti_resource_link_by_obj.title, "Test LTI Resource Link")


@requests_mock.Mocker()
class TestCourseNickname(unittest.TestCase):
30 changes: 30 additions & 0 deletions tests/test_lti_resource_link.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import unittest

import requests_mock

from canvasapi import Canvas
from tests import settings
from tests.util import register_uris


@requests_mock.Mocker()
class TestLTIResourceLink(unittest.TestCase):
def setUp(self):
self.canvas = Canvas(settings.BASE_URL, settings.API_KEY)

with requests_mock.Mocker() as m:
register_uris(
{
"course": ["get_by_id"],
"lti_resource_link": ["get_lti_resource_link"],
},
m,
)

self.course = self.canvas.get_course(1)
self.resource_link = self.course.get_lti_resource_link(45)

# __str__()
def test__str__(self, m):
string = str(self.resource_link)
self.assertIsInstance(string, str)