Repository: ucfopen/canvasapi

Issue:
Query paramaters and sis ids
Hi, i am new to this api but I like the implementations of it a lot.

A lot of the canvas api accepts query parameters,
Lets take for example [listing users in a course](https://canvas.instructure.com/doc/api/live#!/courses.json/list_users_in_course_users_get_5) where almost every parameter type is query

I would like to be able to add a query parameters for example
/api/v1/courses/{course_id}/users?enrollment_type=student
Is this possible now or how would you suggest the implementation? 

Also, along with that it would also be handy to not use user_id but sis_user_id
and for course_id; sis_course_id. 
However, sis_ids are not a query string. But instead come with a "sis_course_id:" in front of the sis id number itself.
example:
```/api/v1/courses/sis_course_id:{{sis_course_id}}```

Is that possible?, suggest implementation?

Currently I seem to be able to make a custom query like this
This example fetches only students in a course and I have tested it to be working.
```
	canvas = Canvas(API_URL, API_KEY)

	# First, retrieve the Course object
	course = canvas.get_course(COURSE_ID)

	
	'''You can make your own custom request like this'''
	from canvasapi.paginated_list import PaginatedList
	from canvasapi.user import User

	st_users = PaginatedList(User,
					course._requester,
					'GET',
					'courses/{}/users?enrollment_type=student'.format(COURSE_ID)
					,{})
	for st in st_users:
		print(st.id,st.name)
```

Discussion:

Patch:
diff --git a/canvasapi/canvas.py b/canvasapi/canvas.py
index c295820..64c74a4 100644
--- a/canvasapi/canvas.py
+++ b/canvasapi/canvas.py
@@ -8,6 +8,7 @@ from canvasapi.folder import Folder
 from canvasapi.group import Group, GroupCategory
 from canvasapi.paginated_list import PaginatedList
 from canvasapi.requester import Requester
+from canvasapi.section import Section
 from canvasapi.user import User
 from canvasapi.util import combine_kwargs
 
@@ -42,7 +43,7 @@ class Canvas(object):
         )
         return Account(self.__requester, response.json())
 
-    def get_account(self, account_id):
+    def get_account(self, account_id, use_sis_id=False, **kwargs):
         """
         Retrieve information on an individual account.
 
@@ -50,12 +51,22 @@ class Canvas(object):
         <https://canvas.instructure.com/doc/api/accounts.html#method.accounts.show>`_
 
         :param account_id: The ID of the account to retrieve.
-        :type account_id: int
+        :type account_id: int or str
+        :param use_sis_id: Whether or not account_id is an sis ID.
+            Defaults to `False`.
+        :type use_sis_id: bool
+
         :rtype: :class:`canvasapi.account.Account`
         """
+        if use_sis_id:
+            uri_str = 'accounts/sis_account_id:{}'
+        else:
+            uri_str = 'accounts/{}'
+
         response = self.__requester.request(
             'GET',
-            'accounts/%s' % (account_id)
+            uri_str.format(account_id),
+            **combine_kwargs(**kwargs)
         )
         return Account(self.__requester, response.json())
 
@@ -102,7 +113,7 @@ class Canvas(object):
             'course_accounts',
         )
 
-    def get_course(self, course_id, **kwargs):
+    def get_course(self, course_id, use_sis_id=False, **kwargs):
         """
         Retrieve a course by its ID.
 
@@ -110,12 +121,20 @@ class Canvas(object):
         <https://canvas.instructure.com/doc/api/courses.html#method.courses.show>`_
 
         :param course_id: The ID of the course to retrieve.
-        :type course_id: int
+        :type course_id: int or str
+        :param use_sis_id: Whether or not course_id is an sis ID.
+            Defaults to `False`.
+        :type use_sis_id: bool
         :rtype: :class:`canvasapi.course.Course`
         """
+        if use_sis_id:
+            uri_str = 'courses/sis_course_id:{}'
+        else:
+            uri_str = 'courses/{}'
+
         response = self.__requester.request(
             'GET',
-            'courses/%s' % (course_id),
+            uri_str.format(course_id),
             **combine_kwargs(**kwargs)
         )
         return Course(self.__requester, response.json())
@@ -251,19 +270,30 @@ class Canvas(object):
         )
         return CourseNickname(self.__requester, response.json())
 
-    def get_section(self, section_id):
+    def get_section(self, section_id, use_sis_id=False, **kwargs):
         """
         Get details about a specific section.
 
         :calls: `GET /api/v1/sections/:id \
         <https://canvas.instructure.com/doc/api/sections.html#method.sections.show>`_
 
+        :param section_id: The ID of the section to get.
+        :type section_id: int or str
+        :param use_sis_id: Whether or not section_id is an sis ID.
+            Defaults to `False`.
+        :type use_sis_id: bool
+
         :rtype: :class:`canvasapi.section.Section`
         """
-        from canvasapi.section import Section
+        if use_sis_id:
+            uri_str = 'sections/sis_section_id:{}'
+        else:
+            uri_str = 'sections/{}'
+
         response = self.__requester.request(
             'GET',
-            'sections/%s' % (section_id)
+            uri_str.format(section_id),
+            **combine_kwargs(**kwargs)
         )
         return Section(self.__requester, response.json())
 
@@ -340,7 +370,7 @@ class Canvas(object):
         )
         return Group(self.__requester, response.json())
 
-    def get_group(self, group_id, **kwargs):
+    def get_group(self, group_id, use_sis_id=False, **kwargs):
         """
         Return the data for a single group. If the caller does not
         have permission to view the group a 401 will be returned.
@@ -348,11 +378,22 @@ class Canvas(object):
         :calls: `GET /api/v1/groups/:group_id \
         <https://canvas.instructure.com/doc/api/groups.html#method.groups.show>`_
 
+        :param group_id: The ID of the group to get.
+        :type group_id: int or str
+        :param use_sis_id: Whether or not group_id is an sis ID.
+            Defaults to `False`.
+        :type use_sis_id: bool
+
         :rtype: :class:`canvasapi.group.Group`
         """
+        if use_sis_id:
+            uri_str = 'groups/sis_group_id:{}'
+        else:
+            uri_str = 'groups/{}'
+
         response = self.__requester.request(
             'GET',
-            'groups/%s' % (group_id),
+            uri_str.format(group_id),
             **combine_kwargs(**kwargs)
         )
         return Group(self.__requester, response.json())


Test patch:
diff --git a/tests/fixtures/account.json b/tests/fixtures/account.json
index f8e0456..de829c0 100644
--- a/tests/fixtures/account.json
+++ b/tests/fixtures/account.json
@@ -146,6 +146,21 @@
 		},
 		"status_code": 200
 	},
+	"get_by_sis_id": {
+		"method": "GET",
+		"endpoint": "accounts/sis_account_id:test-sis-id",
+		"data": {
+			"id": 10,
+			"sis_account_id": "test-sis-id",
+			"name": "Account From SIS",
+			"parent_account_id": null,
+			"root_account_id": null,
+			"default_storage_quota_mb": 500,
+			"default_user_storage_quota_mb": 50,
+			"default_group_storage_quota_mb": 50,
+			"default_time_zone": "America/New_York"
+		}
+	},
 	"get_courses": {
 		"method": "GET",
 		"endpoint": "accounts/1/courses",
diff --git a/tests/fixtures/course.json b/tests/fixtures/course.json
index 4a879bc..7ee017d 100644
--- a/tests/fixtures/course.json
+++ b/tests/fixtures/course.json
@@ -159,6 +159,22 @@
 		},
 		"status_code": 200
 	},
+	"get_by_sis_id": {
+		"method": "GET",
+		"endpoint": "courses/sis_course_id:test-sis-id",
+		"data": {
+			"id": 1,
+			"sis_course_id": "test-sis-id",
+			"course_code": "SIS101",
+			"name": "SIS Course",
+			"workflow_state": "available",
+			"account_id": 1,
+			"root_account_id": 1,
+			"enrollment_term_id": 1,
+			"grading_standard_id": 1
+		},
+		"status_code": 200
+	},
 	"get_external_tools": {
 		"method": "GET",
 		"endpoint": "courses/1/external_tools",
diff --git a/tests/fixtures/group.json b/tests/fixtures/group.json
index 0481a4c..6e542af 100644
--- a/tests/fixtures/group.json
+++ b/tests/fixtures/group.json
@@ -19,6 +19,17 @@
 		},
 		"status_code": 200
 	},
+	"get_by_sis_id": {
+		"method": "GET",
+		"endpoint": "groups/sis_group_id:test-sis-id",
+		"data": {
+			"id": 10,
+			"sis_group_id": "test-sis-id",
+			"name": "SIS Group",
+			"description": "best SIS group ever"
+		},
+		"status_code": 200
+	},
 	"pages_get_page": {
 		"method": "GET",
 		"endpoint": "groups/1/pages/my-url",
diff --git a/tests/fixtures/section.json b/tests/fixtures/section.json
index 6d06bcd..19c860b 100644
--- a/tests/fixtures/section.json
+++ b/tests/fixtures/section.json
@@ -9,6 +9,17 @@
 		},
 		"status_code": 200
 	},
+	"get_by_sis_id": {
+		"method": "GET",
+		"endpoint": "sections/sis_section_id:test-sis-id",
+		"data":{
+			"id": 10,
+			"sis_section_id": "test-sis-id",
+			"course_id": 1,
+			"name": "SIS Section"
+		},
+		"status_code": 200
+	},
 	"list_enrollments": {
 		"method": "GET",
 		"endpoint": "sections/1/enrollments",
diff --git a/tests/test_canvas.py b/tests/test_canvas.py
index 17cc875..e2acf32 100644
--- a/tests/test_canvas.py
+++ b/tests/test_canvas.py
@@ -51,6 +51,14 @@ class TestCanvas(unittest.TestCase):
 
         self.assertIsInstance(account, Account)
 
+    def test_get_account_sis_id(self, m):
+        register_uris({'account': ['get_by_sis_id']}, m)
+
+        account = self.canvas.get_account('test-sis-id', use_sis_id=True)
+
+        self.assertIsInstance(account, Account)
+        self.assertEqual(account.name, 'Account From SIS')
+
     def test_get_account_fail(self, m):
         register_uris({'generic': ['not_found']}, m)
 
@@ -82,6 +90,14 @@ class TestCanvas(unittest.TestCase):
         self.assertIsInstance(course, Course)
         self.assertTrue(hasattr(course, 'name'))
 
+    def test_get_course_sis_id(self, m):
+        register_uris({'course': ['get_by_sis_id']}, m)
+
+        course = self.canvas.get_course('test-sis-id', use_sis_id=True)
+
+        self.assertIsInstance(course, Course)
+        self.assertEqual(course.name, 'SIS Course')
+
     def test_get_course_with_start_date(self, m):
         register_uris({'course': ['start_at_date']}, m)
 
@@ -225,6 +241,14 @@ class TestCanvas(unittest.TestCase):
 
         self.assertIsInstance(info, Section)
 
+    def test_get_section_sis_id(self, m):
+        register_uris({'section': ['get_by_sis_id']}, m)
+
+        section = self.canvas.get_section('test-sis-id', use_sis_id=True)
+
+        self.assertIsInstance(section, Section)
+        self.assertEqual(section.name, 'SIS Section')
+
     # create_group()
     def test_create_group(self, m):
         register_uris({'group': ['create']}, m)
@@ -245,6 +269,14 @@ class TestCanvas(unittest.TestCase):
         self.assertTrue(hasattr(group, 'name'))
         self.assertTrue(hasattr(group, 'description'))
 
+    def test_get_group_sis_id(self, m):
+        register_uris({'group': ['get_by_sis_id']}, m)
+
+        group = self.canvas.get_group('test-sis-id', use_sis_id=True)
+
+        self.assertIsInstance(group, Group)
+        self.assertEqual(group.name, 'SIS Group')
+
     # get_group_category()
     def test_get_group_category(self, m):
         register_uris({'group': ['get_category_by_id']}, m)