From 3b7ba1e714ffdd834cf062e1e1b6d9e090ac7c1a Mon Sep 17 00:00:00 2001 From: Nico de Groot Date: Mon, 1 Nov 2021 12:52:18 +0100 Subject: [PATCH 1/5] #440: added util.py:obj_or_id_or_sis_str, use it in course.py:enroll_user to support 'sis_login_id:' syntax --- canvasapi/course.py | 3 +- canvasapi/util.py | 44 +++++++++++++++++++++++++++++ tests/test_util.py | 67 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 1 deletion(-) diff --git a/canvasapi/course.py b/canvasapi/course.py index fcb97f05..15544783 100644 --- a/canvasapi/course.py +++ b/canvasapi/course.py @@ -37,6 +37,7 @@ normalize_bool, obj_or_id, obj_or_str, + obj_or_id_or_sis_str ) @@ -659,7 +660,7 @@ def enroll_user(self, user, enrollment_type=None, **kwargs): from canvasapi.enrollment import Enrollment from canvasapi.user import User - kwargs["enrollment[user_id]"] = obj_or_id(user, "user", (User,)) + kwargs["enrollment[user_id]"] = obj_or_id_or_sis_str(user, "user", (User,)) if enrollment_type: warnings.warn( diff --git a/canvasapi/util.py b/canvasapi/util.py index 21c2a02b..51184575 100644 --- a/canvasapi/util.py +++ b/canvasapi/util.py @@ -166,6 +166,50 @@ def obj_or_str(obj, attr, object_types): raise TypeError("Parameter {} must be of type {}.".format(obj, obj_type_list)) +def obj_or_id_or_sis_str(parameter, param_name, object_types): + """ + Accepts either an int (or long or str representation of an integer) or + a 'sis_*_id:[some id]' format string or an object. If it is an int or + a format string, return it. + If it is an object and the object is of correct type, return the object's id. Otherwise, + throw an exception. + + :param parameter: int, str, long, or object + :param param_name: str + :param object_types: tuple + :rtype: int + """ + from canvasapi.user import User + + try: + return int(parameter) + except (ValueError, TypeError): + # Special case where 'self' is a valid ID of a User object + if User in object_types and parameter == "self": + return parameter + + if ( + isinstance(parameter, str) + and parameter.startswith("sis_") + and "_id:" in parameter + ): + # not foolproof + return parameter + + for obj_type in object_types: + if isinstance(parameter, obj_type): + try: + return int(parameter.id) + except Exception: + break + + obj_type_list = ",".join([obj_type.__name__ for obj_type in object_types]) + message = "Parameter {} must be of type {} or int or a sis_*_id: format string".format( + param_name, obj_type_list + ) + raise TypeError(message) + + def get_institution_url(base_url): """ Clean up a given base URL. diff --git a/tests/test_util.py b/tests/test_util.py index 863d7e10..ae041ca6 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -16,6 +16,7 @@ normalize_bool, obj_or_id, obj_or_str, + obj_or_id_or_sis_str, ) from tests import settings from tests.util import cleanup_file, register_uris @@ -471,6 +472,72 @@ def test_obj_or_str_invalid_obj_type(self, m): with self.assertRaises(TypeError): obj_or_str("user", "name", (User,)) + # obj_or_id_or_sis_str : + # same set of tests as used in the obj_or_id tests, + # added _sis_str_{valid,invalid} tests + def test_obj_or_id_or_sis_str_int(self, m): + user_id = obj_or_id_or_sis_str(1, "user_id", (User,)) + + self.assertIsInstance(user_id, int) + self.assertEqual(user_id, 1) + + def test_obj_or_id_or_sis_str_str_valid(self, m): + user_id = obj_or_id_or_sis_str("1", "user_id", (User,)) + + self.assertIsInstance(user_id, int) + self.assertEqual(user_id, 1) + + def test_obj_or_id_or_sis_str_str_invalid(self, m): + with self.assertRaises(TypeError): + obj_or_id_or_sis_str("1a", "user_id", (User,)) + + def test_obj_or_id_or_sis_str_sis_str_valid(self, m): + user_id = obj_or_id_or_sis_str("sis_login_id:a_login_id", "user_id", (User,)) + + self.assertEqual(user_id, "sis_login_id:a_login_id") + + def test_obj_or_id_or_sis_str_sis_str_invalid(self, m): + with self.assertRaises(TypeError): + obj_or_id_or_sis_str("sys_login_id:a_login_id", "user_id", (User,)) + + def test_obj_or_id_or_sis_str_obj(self, m): + register_uris({"user": ["get_by_id"]}, m) + + user = self.canvas.get_user(1) + + user_id = obj_or_id_or_sis_str(user, "user_id", (User,)) + + self.assertIsInstance(user_id, int) + self.assertEqual(user_id, 1) + + def test_obj_or_id_or_sis_str_obj_no_id(self, m): + register_uris({"user": ["course_nickname"]}, m) + + nick = self.canvas.get_course_nickname(1) + + with self.assertRaises(TypeError): + obj_or_id_or_sis_str(nick, "nickname_id", (CourseNickname,)) + + def test_obj_or_id_or_sis_str_multiple_objs(self, m): + register_uris({"user": ["get_by_id"]}, m) + + user = self.canvas.get_user(1) + + user_id = obj_or_id_or_sis_str(user, "user_id", (CourseNickname, User)) + + self.assertIsInstance(user_id, int) + self.assertEqual(user_id, 1) + + def test_obj_or_id_or_sis_str_user_self(self, m): + user_id = obj_or_id_or_sis_str("self", "user_id", (User,)) + + self.assertIsInstance(user_id, str) + self.assertEqual(user_id, "self") + + def test_obj_or_id_or_sis_str_nonuser_self(self, m): + with self.assertRaises(TypeError): + obj_or_id_or_sis_str("self", "user_id", (CourseNickname,)) + # get_institution_url() def test_get_institution_url(self, m): correct_url = "https://my.canvas.edu" From f788a2d8e13e46ea76c70178cad47a35b4ec8027 Mon Sep 17 00:00:00 2001 From: Nico de Groot Date: Wed, 3 Nov 2021 10:16:40 +0100 Subject: [PATCH 2/5] #440: apply black and flake8 to course.py --- canvasapi/course.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/canvasapi/course.py b/canvasapi/course.py index 15544783..43592ed6 100644 --- a/canvasapi/course.py +++ b/canvasapi/course.py @@ -37,7 +37,7 @@ normalize_bool, obj_or_id, obj_or_str, - obj_or_id_or_sis_str + obj_or_id_or_sis_str, ) From 58ea8366ee21a1ac1a5f6e0cae451498cb334207 Mon Sep 17 00:00:00 2001 From: Nico de Groot Date: Mon, 18 Nov 2024 15:37:48 +0100 Subject: [PATCH 3/5] section.enroll_user accepts format like 'sis_login_id:xxx' too, updated to latest version 3.3, tests added --- canvasapi/section.py | 4 ++-- pyproject.toml | 14 ++++++++++++++ tests/test_course.py | 10 ++++++++++ tests/test_section.py | 9 +++++++++ 4 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 pyproject.toml diff --git a/canvasapi/section.py b/canvasapi/section.py index 8046b648..e07b848e 100644 --- a/canvasapi/section.py +++ b/canvasapi/section.py @@ -4,7 +4,7 @@ from canvasapi.progress import Progress from canvasapi.submission import GroupedSubmission, Submission from canvasapi.user import User -from canvasapi.util import combine_kwargs, normalize_bool, obj_or_id +from canvasapi.util import combine_kwargs, normalize_bool, obj_or_id, obj_or_id_or_sis_str class Section(CanvasObject): @@ -94,7 +94,7 @@ def enroll_user(self, user, **kwargs): :rtype: :class:`canvasapi.enrollment.Enrollment` """ - kwargs["enrollment[user_id]"] = obj_or_id(user, "user", (User,)) + kwargs["enrollment[user_id]"] = obj_or_id_or_sis_str(user, "user", (User,)) response = self._requester.request( "POST", diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..54379824 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,14 @@ +[tool.poetry] +name = "canvasapi" +version = "0.1.0" +description = "" +authors = ["Nico de Groot "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.12" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/tests/test_course.py b/tests/test_course.py index 2e2669dd..25c01892 100644 --- a/tests/test_course.py +++ b/tests/test_course.py @@ -227,6 +227,16 @@ def test_enroll_user(self, m): self.assertTrue(hasattr(enrollment_by_obj, "type")) self.assertEqual(enrollment_by_obj.type, enrollment_type) + # by sis_str: str could be {'login', + enrollment_by_sis_str = self.course.enroll_user( + "sis_login_id:u123456", enrollment={"type": enrollment_type} + ) + + self.assertIsInstance(enrollment_by_sis_str, Enrollment) + self.assertTrue(hasattr(enrollment_by_sis_str, "type")) + self.assertEqual(enrollment_by_sis_str.type, enrollment_type) + + def test_enroll_user_legacy(self, m): warnings.simplefilter("always", DeprecationWarning) diff --git a/tests/test_section.py b/tests/test_section.py index cc088d18..9420a1d5 100644 --- a/tests/test_section.py +++ b/tests/test_section.py @@ -150,3 +150,12 @@ def test_enroll_user(self, m): self.assertIsInstance(enrollment_by_obj, Enrollment) self.assertTrue(hasattr(enrollment_by_obj, "type")) self.assertEqual(enrollment_by_obj.type, enrollment_type) + + # by user pecified as sis_login_id + enrollment_by_sis_str = self.section.enroll_user( + "sis_login_id:u123456" , enrollment={"type": enrollment_type} + ) + + self.assertIsInstance(enrollment_by_sis_str, Enrollment) + self.assertTrue(hasattr(enrollment_by_sis_str, "type")) + self.assertEqual(enrollment_by_sis_str.type, enrollment_type) From b2254d7e4492d02dab0e6b1f3a74c565f52070b8 Mon Sep 17 00:00:00 2001 From: Nico de Groot Date: Tue, 19 Nov 2024 22:34:46 +0100 Subject: [PATCH 4/5] pyproject.toml python version relaxed --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 54379824..dbf2b4a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Nico de Groot "] readme = "README.md" [tool.poetry.dependencies] -python = "^3.12" +python = ">=3.9,<4.0.0" [build-system] From beec165035e34fdfef2af9cce3d6ca1143c20c23 Mon Sep 17 00:00:00 2001 From: Nico de Groot Date: Fri, 22 Nov 2024 13:48:57 +0100 Subject: [PATCH 5/5] pyproject.toml added poetry-core, changed version to match original canvasapi --- .idea/.gitignore | 8 ++ .idea/canvasapi.iml | 15 +++ .idea/dictionaries/ncdegroot.xml | 3 + .idea/encodings.xml | 6 + .idea/inspectionProfiles/Project_Default.xml | 112 ++++++++++++++++++ .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 75 ++++++++++++ .idea/modules.xml | 8 ++ .idea/ruff.xml | 6 + .idea/vcs.xml | 6 + poetry.lock | 17 +++ pyproject.toml | 6 +- 12 files changed, 266 insertions(+), 2 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/canvasapi.iml create mode 100644 .idea/dictionaries/ncdegroot.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/ruff.xml create mode 100644 .idea/vcs.xml create mode 100644 poetry.lock diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..13566b81 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/canvasapi.iml b/.idea/canvasapi.iml new file mode 100644 index 00000000..de5999c7 --- /dev/null +++ b/.idea/canvasapi.iml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/dictionaries/ncdegroot.xml b/.idea/dictionaries/ncdegroot.xml new file mode 100644 index 00000000..01f513ad --- /dev/null +++ b/.idea/dictionaries/ncdegroot.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 00000000..97626ba4 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 00000000..5486f22c --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,112 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 00000000..105ce2da --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..0f460a16 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..28e4970d --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/ruff.xml b/.idea/ruff.xml new file mode 100644 index 00000000..ed9f86d9 --- /dev/null +++ b/.idea/ruff.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 00000000..4ac1d813 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,17 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "poetry-core" +version = "1.9.1" +description = "Poetry PEP 517 Build Backend" +optional = false +python-versions = "<4.0,>=3.8" +files = [ + {file = "poetry_core-1.9.1-py3-none-any.whl", hash = "sha256:6f45dd3598e0de8d9b0367360253d4c5d4d0110c8f5c71120a14f0e0f116c1a0"}, + {file = "poetry_core-1.9.1.tar.gz", hash = "sha256:7a2d49214bf58b4f17f99d6891d947a9836c9899a67a5069f52d7b67217f61b8"}, +] + +[metadata] +lock-version = "2.0" +python-versions = ">=3.9,<4.0.0" +content-hash = "07ac5b6df21285b34ef14be0c8ad2467701926876a25fce627798d9d5be51f4a" diff --git a/pyproject.toml b/pyproject.toml index dbf2b4a9..f8e41bab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,15 @@ [tool.poetry] name = "canvasapi" -version = "0.1.0" -description = "" +version = "3.3.0" +description = "Fork to support sis_login_id" authors = ["Nico de Groot "] readme = "README.md" [tool.poetry.dependencies] python = ">=3.9,<4.0.0" +[tool.poetry.group.dev.dependencies] +poetry-core = "*" [build-system] requires = ["poetry-core"]