Skip to content

Commit f30082c

Browse files
authored
Merge branch 'ucfopen:develop' into develop
2 parents e6127f0 + 45ba5ac commit f30082c

16 files changed

+307
-22
lines changed

.github/workflows/deploy.yml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@ jobs:
99
deploy:
1010
runs-on: ubuntu-latest
1111
steps:
12-
- uses: actions/checkout@v1
12+
- uses: actions/checkout@v4
1313

1414
- name: Set up Python
15-
uses: actions/setup-python@v2
15+
uses: actions/setup-python@v5
16+
17+
- name: Install build dependency
18+
run: pip install build
1619

1720
- name: Create source distribution
18-
run: python setup.py sdist
21+
run: python -m build
1922

2023
- name: Publish distribution to PyPI
2124
uses: pypa/gh-action-pypi-publish@release/v1

.github/workflows/run-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ jobs:
4040
- name: Check for missing kwargs
4141
run: python scripts/find_missing_kwargs.py
4242
- name: Upload coverage to Codecov
43-
uses: codecov/codecov-action@v3
43+
uses: codecov/codecov-action@v4
4444
with:
4545
fail_ci_if_error: true
4646
token: ${{ secrets.CODECOV_TOKEN }}

AUTHORS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
- Deundre Williams [@deundrewilliams](https://github.com/deundrewilliams)
4343
- Devin Singh [@devints47](https://github.com/devints47)
4444
- Dmitry Savransky [@dsavransky](https://github.com/dsavransky)
45+
- Elias [@HandcartCactus](https://github.com/HandcartCactus)
4546
- Elise Heron [@thedarkestknight](https://github.com/thedarkestknight)
4647
- Elli Howard [@qwertynerd97](https://github.com/qwertynerd97)
4748
- Erik Tews [@eriktews](https://github.com/eriktews)
@@ -53,6 +54,7 @@
5354
- Ian Altgilbers [@altgilbers](https://github.com/altgilbers)
5455
- Ian Turgeon [@iturgeon](https://github.com/iturgeon)
5556
- [@jackrsteiner](https://github.com/jackrsteiner)
57+
- Jasmine Hou [@jsmnhou](https://github.com/jsmnhou)
5658
- John Raible [@rebelaide](https://github.com/rebelaide)
5759
- Joon Ro [@joonro](https://github.com/joonro)
5860
- Jonah Majumder [@jonahmajumder](https://github.com/jonahmajumder)

CHANGELOG.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,30 @@
22

33
## [Unreleased]
44

5+
### New Endpoint Coverage
6+
7+
- LTI Resource Links (Thanks, [@jsmnhou](https://github.com/jsmnhou))
8+
9+
### Backstage
10+
11+
- Updated deploy Action to use more modern processes.
12+
- Updated `PaginatedList` to be type-aware, showing which class is included in the response. (Thanks [@HandcartCactus](https://github.com/HandcartCactus))
13+
14+
## [3.3.0] - 2023-08-27
15+
16+
### General
17+
18+
- Added documentation for PaginatedList
19+
- Rework requester URLs to accommodate graphql and new quizzes endpoints (Thanks, [@bennettscience](https://github.com/bennettscience))
20+
21+
### Bugfixes
22+
23+
- Fixed PaginatedList not respecting new quizzes endpoints (Thanks, [@jonespm](https://github.com/jonespm))
24+
25+
### Backstage
26+
27+
- Updated codecov action
28+
529
## [3.2.0] - 2023-05-25
630

731
### New Endpoint Coverage
@@ -621,7 +645,8 @@ Huge thanks to [@liblit](https://github.com/liblit) for lots of issues, suggesti
621645
- Fixed some incorrectly defined parameters
622646
- Fixed an issue where tests would fail due to an improperly configured requires block
623647

624-
[Unreleased]: https://github.com/ucfopen/canvasapi/compare/v3.2.0...develop
648+
[Unreleased]: https://github.com/ucfopen/canvasapi/compare/v3.3.0...develop
649+
[3.3.0]: https://github.com/ucfopen/canvasapi/compare/v3.2.0...v3.3.0
625650
[3.2.0]: https://github.com/ucfopen/canvasapi/compare/v3.1.0...v3.2.0
626651
[3.1.0]: https://github.com/ucfopen/canvasapi/compare/v3.0.0...v3.1.0
627652
[3.0.0]: https://github.com/ucfopen/canvasapi/compare/v2.2.0...v3.0.0

canvasapi/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44

55
__all__ = ["Canvas"]
66

7-
__version__ = "3.2.0"
7+
__version__ = "3.3.0"

canvasapi/canvas.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1279,7 +1279,7 @@ def graphql(self, query, variables=None, **kwargs):
12791279
_kwargs=combine_kwargs(**kwargs)
12801280
+ [("query", query), ("variables", variables)],
12811281
# Needs to call special endpoint without api/v1
1282-
_url=self.__requester.original_url + "/api/graphql",
1282+
_url="graphql",
12831283
json=True,
12841284
)
12851285

canvasapi/course.py

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
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
2728
from canvasapi.new_quiz import NewQuiz
2829
from canvasapi.outcome_import import OutcomeImport
@@ -438,6 +439,39 @@ def create_late_policy(self, **kwargs):
438439

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

442+
def create_lti_resource_link(self, url, title=None, custom=None, **kwargs):
443+
"""
444+
Create a new LTI resource link.
445+
446+
:calls: `POST /api/v1/courses/:course_id/lti_resource_links \
447+
<https://canvas.instructure.com/doc/api/lti_resource_links.html#method.lti/resource_links.create>`_
448+
449+
:param url: The launch URL for the resource link.
450+
:type url: `str`
451+
:param title: The title of the resource link.
452+
:type title: `str`, optional
453+
:param custom: Custom parameters to send to the tool.
454+
:type custom: `dict`, optional
455+
456+
:rtype: :class:`canvasapi.lti_resource_link.LTIResourceLink`
457+
"""
458+
459+
if not url:
460+
raise RequiredFieldMissing("url is required as a str.")
461+
462+
kwargs["url"] = url
463+
if title:
464+
kwargs["title"] = title
465+
if custom:
466+
kwargs["custom"] = custom
467+
468+
response = self._requester.request(
469+
"POST",
470+
f"courses/{self.id}/lti_resource_links",
471+
_kwargs=combine_kwargs(**kwargs),
472+
)
473+
return LTIResourceLink(self._requester, response.json())
474+
441475
def create_module(self, module, **kwargs):
442476
"""
443477
Create a new module.
@@ -480,7 +514,7 @@ def create_new_quiz(self, **kwargs):
480514
response = self._requester.request(
481515
"POST",
482516
endpoint,
483-
_url=self._requester.original_url + "/api/quiz/v1/" + endpoint,
517+
_url="new_quizzes",
484518
_kwargs=combine_kwargs(**kwargs),
485519
)
486520
response_json = response.json()
@@ -1645,6 +1679,49 @@ def get_licenses(self, **kwargs):
16451679
_kwargs=combine_kwargs(**kwargs),
16461680
)
16471681

1682+
def get_lti_resource_link(self, lti_resource_link, **kwargs):
1683+
"""
1684+
Return details about the specified resource link.
1685+
1686+
:calls: `GET /api/v1/courses/:course_id/lti_resource_links/:id \
1687+
<https://canvas.instructure.com/doc/api/lti_resource_links.html#method.lti/resource_links.show>`_
1688+
1689+
:param lti_resource_link: The object or ID of the LTI resource link.
1690+
:type lti_resource_link: :class:`canvasapi.lti_resource_link.LTIResourceLink` or int
1691+
1692+
:rtype: :class:`canvasapi.lti_resource_link.LTIResourceLink`
1693+
"""
1694+
1695+
lti_resource_link_id = obj_or_id(
1696+
lti_resource_link, "lti_resource_link", (LTIResourceLink,)
1697+
)
1698+
1699+
response = self._requester.request(
1700+
"GET",
1701+
f"courses/{self.id}/lti_resource_links/{lti_resource_link_id}",
1702+
_kwargs=combine_kwargs(**kwargs),
1703+
)
1704+
return LTIResourceLink(self._requester, response.json())
1705+
1706+
def get_lti_resource_links(self, **kwargs):
1707+
"""
1708+
Returns all LTI resource links for this course as a PaginatedList.
1709+
1710+
:calls: `GET /api/v1/courses/:course_id/lti_resource_links \
1711+
<https://canvas.instructure.com/doc/api/lti_resource_links.html#method.lti/resource_links.index>`_
1712+
1713+
:rtype: :class:`canvasapi.paginated_list.PaginatedList` of
1714+
:class:`canvasapi.lti_resource_link.LTIResourceLink`
1715+
"""
1716+
1717+
return PaginatedList(
1718+
LTIResourceLink,
1719+
self._requester,
1720+
"GET",
1721+
f"courses/{self.id}/lti_resource_links",
1722+
kwargs=combine_kwargs(**kwargs),
1723+
)
1724+
16481725
def get_migration_systems(self, **kwargs):
16491726
"""
16501727
Return a list of migration systems.
@@ -1758,7 +1835,7 @@ def get_new_quiz(self, assignment, **kwargs):
17581835
response = self._requester.request(
17591836
"GET",
17601837
endpoint,
1761-
_url=self._requester.original_url + "/api/quiz/v1/" + endpoint,
1838+
_url="new_quizzes",
17621839
_kwargs=combine_kwargs(**kwargs),
17631840
)
17641841
response_json = response.json()
@@ -1783,7 +1860,7 @@ def get_new_quizzes(self, **kwargs):
17831860
self._requester,
17841861
"GET",
17851862
endpoint,
1786-
_url_override=self._requester.original_url + "/api/quiz/v1/" + endpoint,
1863+
_url_override="new_quizzes",
17871864
_kwargs=combine_kwargs(**kwargs),
17881865
)
17891866

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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def delete(self, **kwargs):
2121
response = self._requester.request(
2222
"DELETE",
2323
endpoint,
24-
_url=self._requester.original_url + "/api/quiz/v1/" + endpoint,
24+
_url="new_quizzes",
2525
_kwargs=combine_kwargs(**kwargs),
2626
)
2727
response_json = response.json()
@@ -44,7 +44,7 @@ def update(self, **kwargs):
4444
response = self._requester.request(
4545
"PATCH",
4646
endpoint,
47-
_url=self._requester.original_url + "/api/quiz/v1/" + endpoint,
47+
_url="new_quizzes",
4848
_kwargs=combine_kwargs(**kwargs),
4949
)
5050
response_json = response.json()

canvasapi/paginated_list.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
from __future__ import annotations
2+
13
import re
4+
from typing import Iterable, Iterator, Type, TypeVar
5+
6+
T = TypeVar("T")
27

38

4-
class PaginatedList(object):
9+
class PaginatedList(Iterable[T]):
510
"""
611
Abstracts `pagination of Canvas API \
712
<https://canvas.instructure.com/doc/api/file.pagination.html>`_.
@@ -19,15 +24,33 @@ def __getitem__(self, index):
1924

2025
def __init__(
2126
self,
22-
content_class,
27+
content_class: Type[T],
2328
requester,
2429
request_method,
2530
first_url,
2631
extra_attribs=None,
2732
_root=None,
2833
_url_override=None,
29-
**kwargs
34+
**kwargs,
3035
):
36+
"""
37+
:param content_class: The expected type to return in the list.
38+
:type content_class: class
39+
:param requester: The requester to pass HTTP requests through.
40+
:type requester: :class:`canvasapi.requester.Requester`
41+
:param request_method: HTTP request method
42+
:type request_method: str
43+
:param first_url: Canvas endpoint for the initial request
44+
:type first_url: str
45+
:param extra_attribs: Extra data to include in the request
46+
:type extra_attribs: dict
47+
:param _root: Specify a nested property from Canvas to use for the resulting list.
48+
:type _root: str
49+
:param _url_override: "new_quizzes" or "graphql" for specific Canvas endpoints.
50+
Other URLs may be specified for third-party requests.
51+
:type _url_override: str
52+
:rtype: :class:`canvasapi.paginated_list.PaginatedList` of type content_class
53+
"""
3154
self._elements = list()
3255

3356
self._requester = requester
@@ -42,7 +65,7 @@ def __init__(
4265
self._root = _root
4366
self._url_override = _url_override
4467

45-
def __iter__(self):
68+
def __iter__(self) -> Iterator[T]:
4669
for element in self._elements:
4770
yield element
4871
while self._has_next():
@@ -78,7 +101,10 @@ def _get_next_page(self):
78101
else:
79102
next_link = None
80103

81-
regex = r"{}(.*)".format(re.escape(self._requester.base_url))
104+
regex = r"(?:{}|{})(.*)".format(
105+
re.escape(self._requester.base_url),
106+
re.escape(self._requester.new_quizzes_url),
107+
)
82108

83109
self._next_url = (
84110
re.search(regex, next_link["url"]).group(1) if next_link else None

0 commit comments

Comments
 (0)