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/canvasapi/course.py b/canvasapi/course.py
index 9d0cacf7..d962607a 100644
--- a/canvasapi/course.py
+++ b/canvasapi/course.py
@@ -45,6 +45,7 @@
normalize_bool,
obj_or_id,
obj_or_str,
+ obj_or_id_or_sis_str,
)
@@ -730,7 +731,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/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/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/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
new file mode 100644
index 00000000..f8e41bab
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,16 @@
+[tool.poetry]
+name = "canvasapi"
+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"]
+build-backend = "poetry.core.masonry.api"
diff --git a/tests/test_course.py b/tests/test_course.py
index ec554c5a..dbbf93eb 100644
--- a/tests/test_course.py
+++ b/tests/test_course.py
@@ -229,6 +229,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)
diff --git a/tests/test_util.py b/tests/test_util.py
index cd85648b..652ccedd 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
@@ -470,6 +471,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"