From b4be7159cf968309d86ef88e27c1bdc9efa6fc2f Mon Sep 17 00:00:00 2001 From: Jasmine Hou Date: Tue, 3 Dec 2024 16:13:13 -0500 Subject: [PATCH 01/10] get_links, get_link (id), create_link initial implementation --- canvasapi/lti_resource_link.py | 104 +++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 canvasapi/lti_resource_link.py diff --git a/canvasapi/lti_resource_link.py b/canvasapi/lti_resource_link.py new file mode 100644 index 00000000..d57e04da --- /dev/null +++ b/canvasapi/lti_resource_link.py @@ -0,0 +1,104 @@ +import os +from canvasapi import Canvas +from canvasapi.canvas_object import CanvasObject +from canvasapi.paginated_list import PaginatedList +from canvasapi.util import combine_kwargs, obj_or_id +from canvasapi.course import Course +from canvasapi.exceptions import RequiredFieldMissing + +class LTIResourceLink(CanvasObject): + def __init__(self, requester, attributes): + # Initialize an LTIResourceLink object. + super(LTIResourceLink, self).__init__(requester, attributes) + +class ExtendedCourse(Course): + 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 \ + `_ + + :rtype: :class:`canvasapi.paginated_list.PaginatedList` + """ + + return PaginatedList( + LTIResourceLink, + self._requester, + "GET", + f"courses/{self.id}/lti_resource_links", + 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 \ + `_ + + :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 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 \ + `_ + + :param course_id: The ID of the course. + :type course_id: `int` + :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("The 'url' paramter is required.") + + 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()) + +# local testing +API_URL = os.getenv('CANVAS_API_URL') +API_KEY = os.getenv('CANVAS_API_KEY') + +if not API_URL or not API_KEY: + print("Error: Please set the CANVAS_API_URL and CANVAS_API_KEY environment variables.") + exit(1) + +canvas = Canvas(API_URL, API_KEY) + +course_id = 10791957 +course = ExtendedCourse(canvas._Canvas__requester, {'id': course_id}) + +lti_resource_links = course.get_lti_resource_links() +for link in lti_resource_links: + print(link) \ No newline at end of file From 92ea372e0a0d85f698e7fd7f93f446eb43445da0 Mon Sep 17 00:00:00 2001 From: Jasmine Hou Date: Tue, 3 Dec 2024 16:33:47 -0500 Subject: [PATCH 02/10] add unittests for LTI resource links --- canvasapi/lti_resource_link.py | 19 ------------- tests/test_lti_resource_link.py | 48 +++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 19 deletions(-) create mode 100644 tests/test_lti_resource_link.py diff --git a/canvasapi/lti_resource_link.py b/canvasapi/lti_resource_link.py index d57e04da..9d708c32 100644 --- a/canvasapi/lti_resource_link.py +++ b/canvasapi/lti_resource_link.py @@ -1,5 +1,3 @@ -import os -from canvasapi import Canvas from canvasapi.canvas_object import CanvasObject from canvasapi.paginated_list import PaginatedList from canvasapi.util import combine_kwargs, obj_or_id @@ -85,20 +83,3 @@ def create_lti_resource_link(self, url, title=None, custom=None, **kwargs): _kwargs=combine_kwargs(**kwargs) ) return LTIResourceLink(self._requester, response.json()) - -# local testing -API_URL = os.getenv('CANVAS_API_URL') -API_KEY = os.getenv('CANVAS_API_KEY') - -if not API_URL or not API_KEY: - print("Error: Please set the CANVAS_API_URL and CANVAS_API_KEY environment variables.") - exit(1) - -canvas = Canvas(API_URL, API_KEY) - -course_id = 10791957 -course = ExtendedCourse(canvas._Canvas__requester, {'id': course_id}) - -lti_resource_links = course.get_lti_resource_links() -for link in lti_resource_links: - print(link) \ No newline at end of file diff --git a/tests/test_lti_resource_link.py b/tests/test_lti_resource_link.py new file mode 100644 index 00000000..afafd11b --- /dev/null +++ b/tests/test_lti_resource_link.py @@ -0,0 +1,48 @@ +import unittest + +import requests_mock + +from canvasapi import Canvas +from canvasapi.lti_resource_link import LTIResourceLink +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({"extended_course": ["get_by_id"]}, m) + self.extended_course = self.canvas.get_course(1) + + # create_lti_resource_link() + def test_create_lti_resource_link(self, m): + register_uris({"lti_resource_link": ["create_lti_resource_link"]}, m) + evnt = self.user.create_lti_resource_link( + name="Test LTI Resource Link", url="https://www.google.com" + ) + self.assertIsInstance(evnt, LTIResourceLink) + self.assertEqual(evnt.name, "Test LTI Resource Link") + self.assertEqual(evnt.url, "https://www.google.com") + + # 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.extended_course.get_lti_resource_links() + lti_resource_link_list = [link for link in lti_resource_links] + self.assertEqual(len(lti_resource_link_list), 2) + 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.extended_course.get_lti_resource_link(45) + self.assertIsInstance(lti_resource_link_by_id, LTIResourceLink) + self.assertEqual(lti_resource_link_by_id.name, "Test LTI Resource Link 3") + lti_resource_link_by_obj = self.extended_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.name, "Test LTI Resource Link 3") From 2de108393817da647b9906d23db313aff8f382d3 Mon Sep 17 00:00:00 2001 From: Jasmine Hou Date: Tue, 3 Dec 2024 17:11:22 -0500 Subject: [PATCH 03/10] move under "course" class, add fixtures --- canvasapi/course.py | 79 +++++++++++++++++++++++++++ canvasapi/lti_resource_link.py | 79 +-------------------------- tests/fixtures/lti_resource_link.json | 48 ++++++++++++++++ tests/test_course.py | 32 ++++++++++- tests/test_lti_resource_link.py | 48 ---------------- 5 files changed, 161 insertions(+), 125 deletions(-) create mode 100644 tests/fixtures/lti_resource_link.json delete mode 100644 tests/test_lti_resource_link.py diff --git a/canvasapi/course.py b/canvasapi/course.py index 523a2713..df23845f 100644 --- a/canvasapi/course.py +++ b/canvasapi/course.py @@ -2761,6 +2761,85 @@ def upload(self, file: FileOrPathLike, **kwargs): return Uploader( self._requester, "courses/{}/files".format(self.id), file, **kwargs ).start() + + + 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 \ + `_ + + :rtype: :class:`canvasapi.paginated_list.PaginatedList` + """ + from canvasapi.lti_resource_link import LTIResourceLink + + return PaginatedList( + LTIResourceLink, + self._requester, + "GET", + f"courses/{self.id}/lti_resource_links", + 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 \ + `_ + + :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` + """ + from canvasapi.lti_resource_link import 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 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 \ + `_ + + :param course_id: The ID of the course. + :type course_id: `int` + :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` + """ + from canvasapi.lti_resource_link import LTIResourceLink + + if not url: + raise RequiredFieldMissing("The 'url' paramter is required.") + + 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()) class CourseNickname(CanvasObject): diff --git a/canvasapi/lti_resource_link.py b/canvasapi/lti_resource_link.py index 9d708c32..9fc0e894 100644 --- a/canvasapi/lti_resource_link.py +++ b/canvasapi/lti_resource_link.py @@ -6,80 +6,7 @@ class LTIResourceLink(CanvasObject): def __init__(self, requester, attributes): - # Initialize an LTIResourceLink object. super(LTIResourceLink, self).__init__(requester, attributes) - -class ExtendedCourse(Course): - 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 \ - `_ - - :rtype: :class:`canvasapi.paginated_list.PaginatedList` - """ - - return PaginatedList( - LTIResourceLink, - self._requester, - "GET", - f"courses/{self.id}/lti_resource_links", - 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 \ - `_ - - :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 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 \ - `_ - - :param course_id: The ID of the course. - :type course_id: `int` - :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("The 'url' paramter is required.") - - 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 __str__(self): + return "{} ({})".format(self.url, self.title) \ No newline at end of file diff --git a/tests/fixtures/lti_resource_link.json b/tests/fixtures/lti_resource_link.json new file mode 100644 index 00000000..0e72a6f4 --- /dev/null +++ b/tests/fixtures/lti_resource_link.json @@ -0,0 +1,48 @@ +{ + "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" + }, + "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 + } +} diff --git a/tests/test_course.py b/tests/test_course.py index 2e2669dd..061908ed 100644 --- a/tests/test_course.py +++ b/tests/test_course.py @@ -21,6 +21,7 @@ from canvasapi.exceptions import RequiredFieldMissing, ResourceDoesNotExist from canvasapi.external_feed import ExternalFeed from canvasapi.external_tool import ExternalTool +from canvasapi.lti_resource_link import LTIResourceLink from canvasapi.feature import Feature, FeatureFlag from canvasapi.file import File from canvasapi.folder import Folder @@ -1889,7 +1890,36 @@ def test_resolve_path_null(self, m): self.assertEqual(len(root_folder_list), 1) 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) + evnt = self.course.create_lti_resource_link( + url="https://example.com/lti/launch/content_item/123", title="Test LTI Resource Link", + ) + self.assertIsInstance(evnt, LTIResourceLink) + self.assertEqual(evnt.title, "Test LTI Resource Link") + self.assertEqual(evnt.url, "https://example.com/lti/launch/content_item/123") + + # 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): diff --git a/tests/test_lti_resource_link.py b/tests/test_lti_resource_link.py deleted file mode 100644 index afafd11b..00000000 --- a/tests/test_lti_resource_link.py +++ /dev/null @@ -1,48 +0,0 @@ -import unittest - -import requests_mock - -from canvasapi import Canvas -from canvasapi.lti_resource_link import LTIResourceLink -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({"extended_course": ["get_by_id"]}, m) - self.extended_course = self.canvas.get_course(1) - - # create_lti_resource_link() - def test_create_lti_resource_link(self, m): - register_uris({"lti_resource_link": ["create_lti_resource_link"]}, m) - evnt = self.user.create_lti_resource_link( - name="Test LTI Resource Link", url="https://www.google.com" - ) - self.assertIsInstance(evnt, LTIResourceLink) - self.assertEqual(evnt.name, "Test LTI Resource Link") - self.assertEqual(evnt.url, "https://www.google.com") - - # 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.extended_course.get_lti_resource_links() - lti_resource_link_list = [link for link in lti_resource_links] - self.assertEqual(len(lti_resource_link_list), 2) - 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.extended_course.get_lti_resource_link(45) - self.assertIsInstance(lti_resource_link_by_id, LTIResourceLink) - self.assertEqual(lti_resource_link_by_id.name, "Test LTI Resource Link 3") - lti_resource_link_by_obj = self.extended_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.name, "Test LTI Resource Link 3") From 42876bd37e3772524f0a2b4a9177c1c668d25174 Mon Sep 17 00:00:00 2001 From: Jasmine Hou Date: Tue, 3 Dec 2024 17:16:54 -0500 Subject: [PATCH 04/10] styling fix --- canvasapi/course.py | 19 ++++++++++--------- canvasapi/lti_resource_link.py | 9 +++------ tests/test_course.py | 10 +++++++--- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/canvasapi/course.py b/canvasapi/course.py index df23845f..13467aa3 100644 --- a/canvasapi/course.py +++ b/canvasapi/course.py @@ -2761,7 +2761,6 @@ def upload(self, file: FileOrPathLike, **kwargs): return Uploader( self._requester, "courses/{}/files".format(self.id), file, **kwargs ).start() - def get_lti_resource_links(self, **kwargs): """ @@ -2773,15 +2772,15 @@ def get_lti_resource_links(self, **kwargs): :rtype: :class:`canvasapi.paginated_list.PaginatedList` """ from canvasapi.lti_resource_link import LTIResourceLink - + return PaginatedList( LTIResourceLink, self._requester, "GET", f"courses/{self.id}/lti_resource_links", - kwargs=combine_kwargs(**kwargs) + kwargs=combine_kwargs(**kwargs), ) - + def get_lti_resource_link(self, lti_resource_link, **kwargs): """ Return details about the specified resource link. @@ -2796,15 +2795,17 @@ def get_lti_resource_link(self, lti_resource_link, **kwargs): """ from canvasapi.lti_resource_link import LTIResourceLink - lti_resource_link_id = obj_or_id(lti_resource_link, "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) + _kwargs=combine_kwargs(**kwargs), ) return LTIResourceLink(self._requester, response.json()) - + def create_lti_resource_link(self, url, title=None, custom=None, **kwargs): """ Create a new LTI resource link. @@ -2827,7 +2828,7 @@ def create_lti_resource_link(self, url, title=None, custom=None, **kwargs): if not url: raise RequiredFieldMissing("The 'url' paramter is required.") - + kwargs["url"] = url if title: kwargs["title"] = title @@ -2837,7 +2838,7 @@ def create_lti_resource_link(self, url, title=None, custom=None, **kwargs): response = self._requester.request( "POST", f"courses/{self.id}/lti_resource_links", - _kwargs=combine_kwargs(**kwargs) + _kwargs=combine_kwargs(**kwargs), ) return LTIResourceLink(self._requester, response.json()) diff --git a/canvasapi/lti_resource_link.py b/canvasapi/lti_resource_link.py index 9fc0e894..4d544cd2 100644 --- a/canvasapi/lti_resource_link.py +++ b/canvasapi/lti_resource_link.py @@ -1,12 +1,9 @@ from canvasapi.canvas_object import CanvasObject -from canvasapi.paginated_list import PaginatedList -from canvasapi.util import combine_kwargs, obj_or_id -from canvasapi.course import Course -from canvasapi.exceptions import RequiredFieldMissing + class LTIResourceLink(CanvasObject): def __init__(self, requester, attributes): super(LTIResourceLink, self).__init__(requester, attributes) - + def __str__(self): - return "{} ({})".format(self.url, self.title) \ No newline at end of file + return "{} ({})".format(self.url, self.title) diff --git a/tests/test_course.py b/tests/test_course.py index 061908ed..b993c221 100644 --- a/tests/test_course.py +++ b/tests/test_course.py @@ -1890,12 +1890,13 @@ def test_resolve_path_null(self, m): self.assertEqual(len(root_folder_list), 1) 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) evnt = self.course.create_lti_resource_link( - url="https://example.com/lti/launch/content_item/123", title="Test LTI Resource Link", + url="https://example.com/lti/launch/content_item/123", + title="Test LTI Resource Link", ) self.assertIsInstance(evnt, LTIResourceLink) self.assertEqual(evnt.title, "Test LTI Resource Link") @@ -1917,10 +1918,13 @@ def test_get_lti_resource_link(self, 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) + 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): def setUp(self): From 3e3d5ced8ef91ba0335288bf772f230471ffac97 Mon Sep 17 00:00:00 2001 From: Jasmine Hou Date: Sat, 7 Dec 2024 15:05:32 -0500 Subject: [PATCH 05/10] fix import ordering --- tests/test_course.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_course.py b/tests/test_course.py index b993c221..1d07d2bd 100644 --- a/tests/test_course.py +++ b/tests/test_course.py @@ -21,7 +21,6 @@ from canvasapi.exceptions import RequiredFieldMissing, ResourceDoesNotExist from canvasapi.external_feed import ExternalFeed from canvasapi.external_tool import ExternalTool -from canvasapi.lti_resource_link import LTIResourceLink from canvasapi.feature import Feature, FeatureFlag from canvasapi.file import File from canvasapi.folder import Folder @@ -36,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 From 317a45fdd70a980e0addc246c8c467c72e2754c6 Mon Sep 17 00:00:00 2001 From: Jasmine Hou Date: Mon, 9 Dec 2024 15:17:13 -0500 Subject: [PATCH 06/10] attempt module import fix --- canvasapi/course.py | 4 +--- canvasapi/lti_resource_link.py | 3 --- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/canvasapi/course.py b/canvasapi/course.py index 13467aa3..a8009e88 100644 --- a/canvasapi/course.py +++ b/canvasapi/course.py @@ -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 @@ -2771,7 +2772,6 @@ def get_lti_resource_links(self, **kwargs): :rtype: :class:`canvasapi.paginated_list.PaginatedList` """ - from canvasapi.lti_resource_link import LTIResourceLink return PaginatedList( LTIResourceLink, @@ -2793,7 +2793,6 @@ def get_lti_resource_link(self, lti_resource_link, **kwargs): :rtype: :class:`canvasapi.lti_resource_link.LTIResourceLink` """ - from canvasapi.lti_resource_link import LTIResourceLink lti_resource_link_id = obj_or_id( lti_resource_link, "lti_resource_link", (LTIResourceLink,) @@ -2824,7 +2823,6 @@ def create_lti_resource_link(self, url, title=None, custom=None, **kwargs): :rtype: :class:`canvasapi.lti_resource_link.LTIResourceLink` """ - from canvasapi.lti_resource_link import LTIResourceLink if not url: raise RequiredFieldMissing("The 'url' paramter is required.") diff --git a/canvasapi/lti_resource_link.py b/canvasapi/lti_resource_link.py index 4d544cd2..d951569e 100644 --- a/canvasapi/lti_resource_link.py +++ b/canvasapi/lti_resource_link.py @@ -2,8 +2,5 @@ class LTIResourceLink(CanvasObject): - def __init__(self, requester, attributes): - super(LTIResourceLink, self).__init__(requester, attributes) - def __str__(self): return "{} ({})".format(self.url, self.title) From 76f67ba080b4b53c3ce69374629566588b649332 Mon Sep 17 00:00:00 2001 From: Jasmine Hou Date: Mon, 9 Dec 2024 15:25:23 -0500 Subject: [PATCH 07/10] alphabetize function names --- canvasapi/course.py | 154 ++++++++++++++++++++++---------------------- 1 file changed, 77 insertions(+), 77 deletions(-) diff --git a/canvasapi/course.py b/canvasapi/course.py index a8009e88..4ae2a657 100644 --- a/canvasapi/course.py +++ b/canvasapi/course.py @@ -439,6 +439,41 @@ 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 \ + `_ + + :param course_id: The ID of the course. + :type course_id: `int` + :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("The 'url' paramter is required.") + + 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. @@ -1646,6 +1681,48 @@ 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 \ + `_ + + :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 \ + `_ + + :rtype: :class:`canvasapi.paginated_list.PaginatedList` + """ + + 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. @@ -2763,83 +2840,6 @@ def upload(self, file: FileOrPathLike, **kwargs): self._requester, "courses/{}/files".format(self.id), file, **kwargs ).start() - 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 \ - `_ - - :rtype: :class:`canvasapi.paginated_list.PaginatedList` - """ - - return PaginatedList( - LTIResourceLink, - self._requester, - "GET", - f"courses/{self.id}/lti_resource_links", - 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 \ - `_ - - :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 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 \ - `_ - - :param course_id: The ID of the course. - :type course_id: `int` - :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("The 'url' paramter is required.") - - 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()) - class CourseNickname(CanvasObject): def __str__(self): From a8118ec48b2131cddf2e6dc491eb8638d37f7b16 Mon Sep 17 00:00:00 2001 From: Jasmine Hou Date: Mon, 9 Dec 2024 16:09:37 -0500 Subject: [PATCH 08/10] additional test coverage --- canvasapi/course.py | 2 +- tests/fixtures/lti_resource_link.json | 3 ++- tests/test_course.py | 9 ++++++++ tests/test_lti_resource_link.py | 31 +++++++++++++++++++++++++++ 4 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 tests/test_lti_resource_link.py diff --git a/canvasapi/course.py b/canvasapi/course.py index 4ae2a657..e7c9b920 100644 --- a/canvasapi/course.py +++ b/canvasapi/course.py @@ -459,7 +459,7 @@ def create_lti_resource_link(self, url, title=None, custom=None, **kwargs): """ if not url: - raise RequiredFieldMissing("The 'url' paramter is required.") + raise RequiredFieldMissing("url is required as a str.") kwargs["url"] = url if title: diff --git a/tests/fixtures/lti_resource_link.json b/tests/fixtures/lti_resource_link.json index 0e72a6f4..2de13515 100644 --- a/tests/fixtures/lti_resource_link.json +++ b/tests/fixtures/lti_resource_link.json @@ -22,7 +22,8 @@ "endpoint": "courses/1/lti_resource_links/45", "data": { "id": 45, - "title": "Test LTI Resource Link" + "title": "Test LTI Resource Link", + "url": "https://example.com/lti/launch/content_item/123" }, "status_code": 200 }, diff --git a/tests/test_course.py b/tests/test_course.py index 1d07d2bd..f21eb9fe 100644 --- a/tests/test_course.py +++ b/tests/test_course.py @@ -1894,14 +1894,22 @@ def test_resolve_path_null(self, m): # 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) @@ -1916,6 +1924,7 @@ 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( diff --git a/tests/test_lti_resource_link.py b/tests/test_lti_resource_link.py new file mode 100644 index 00000000..427a865f --- /dev/null +++ b/tests/test_lti_resource_link.py @@ -0,0 +1,31 @@ +import unittest + +import requests_mock + +from canvasapi import Canvas +from canvasapi.exceptions import RequiredFieldMissing +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) From dd38ca954d3b974c1bf8f3db221f71ada5c264b4 Mon Sep 17 00:00:00 2001 From: Jasmine Hou Date: Mon, 9 Dec 2024 16:17:33 -0500 Subject: [PATCH 09/10] bruh --- tests/test_lti_resource_link.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_lti_resource_link.py b/tests/test_lti_resource_link.py index 427a865f..f1b58901 100644 --- a/tests/test_lti_resource_link.py +++ b/tests/test_lti_resource_link.py @@ -3,7 +3,6 @@ import requests_mock from canvasapi import Canvas -from canvasapi.exceptions import RequiredFieldMissing from tests import settings from tests.util import register_uris From e00128621cd35fded509fc746c9b32eb6a4fd12b Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Mon, 9 Dec 2024 17:01:10 -0500 Subject: [PATCH 10/10] Update docs to include LTIResourceLink. Update authors and changelog --- AUTHORS.md | 1 + CHANGELOG.md | 4 ++++ canvasapi/course.py | 5 ++--- docs/class-reference.rst | 1 + docs/lti-resource-link-ref.rst | 6 ++++++ 5 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 docs/lti-resource-link-ref.rst diff --git a/AUTHORS.md b/AUTHORS.md index d2ac0bfa..67b1d405 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -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) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0a7326f..9a2b16d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/canvasapi/course.py b/canvasapi/course.py index e7c9b920..2b28f810 100644 --- a/canvasapi/course.py +++ b/canvasapi/course.py @@ -446,8 +446,6 @@ def create_lti_resource_link(self, url, title=None, custom=None, **kwargs): :calls: `POST /api/v1/courses/:course_id/lti_resource_links \ `_ - :param course_id: The ID of the course. - :type course_id: `int` :param url: The launch URL for the resource link. :type url: `str` :param title: The title of the resource link. @@ -1712,7 +1710,8 @@ def get_lti_resource_links(self, **kwargs): :calls: `GET /api/v1/courses/:course_id/lti_resource_links \ `_ - :rtype: :class:`canvasapi.paginated_list.PaginatedList` + :rtype: :class:`canvasapi.paginated_list.PaginatedList` of + :class:`canvasapi.lti_resource_link.LTIResourceLink` """ return PaginatedList( diff --git a/docs/class-reference.rst b/docs/class-reference.rst index 7420abff..e73ade2c 100644 --- a/docs/class-reference.rst +++ b/docs/class-reference.rst @@ -41,6 +41,7 @@ Class Reference jwt-ref login-ref license-ref + lti-resource-link-ref module-ref outcome-ref outcome-import-ref diff --git a/docs/lti-resource-link-ref.rst b/docs/lti-resource-link-ref.rst new file mode 100644 index 00000000..b14ff90e --- /dev/null +++ b/docs/lti-resource-link-ref.rst @@ -0,0 +1,6 @@ +=============== +LTIResourceLink +=============== + +.. autoclass:: canvasapi.lti_resource_link.LTIResourceLink + :members: