Repository: ucfopen/canvasapi
Pull request #410 (2020-08-14 14:34:28)

Release v2.0.0
🎉 


Patch:
diff --git a/.gitignore b/.gitignore
index 3cfe0c5..f6b23d7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,4 @@
 .vscode/settings.json
 setup.sh
 .python-version
+.*.swp
diff --git a/.readthedocs.yml b/.readthedocs.yml
new file mode 100644
index 0000000..151e7cc
--- /dev/null
+++ b/.readthedocs.yml
@@ -0,0 +1,16 @@
+# .readthedocs.yml
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+
+# Required
+version: 2
+
+# Build documentation in the docs/ directory with Sphinx
+sphinx:
+  configuration: docs/conf.py
+
+python:
+  version: 3.7
+  install:
+    - requirements: dev_requirements.txt
+    
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 5517eab..24aba50 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -15,10 +15,11 @@ install:
   - gem install mdl
 script:
   - coverage run -m unittest discover
+  - black --check canvasapi tests
   - flake8 canvasapi tests
   - mdl . .github
-  - ./scripts/run_black.sh
   - python scripts/alphabetic.py
+  - python scripts/find_missing_kwargs.py
 after_success:
   - coveralls
 notifications:
diff --git a/AUTHORS.md b/AUTHORS.md
index 8baa6be..78430e8 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -11,12 +11,16 @@ Patches and Suggestions
 -----------------------
 
 - Adrian Goetz [@a-goetz](https://github.com/a-goetz)
+- Aileen Pongnon [@aileenpongnon](https://github.com/aileenpongnon)
 - Alyssa Davis [@allygator](https://github.com/allygator)
+- [@amorqiu](https://github.com/amorqiu)
 - Andrew Gardener [@andrew-gardener](https://github.com/andrew-gardener)
 - Anthony Rodriguez [@AnthonyRodriguez726](https://github.com/AnthonyRodriguez726)
 - Ben Liblit [@liblit](https://github.com/liblit)
 - Bill Wrbican [@wjw27](https://github.com/wjw27)
 - Bradford Lynch [@bradfordlynch](https://github.com/bradfordlynch)
+- Brian Bennett [@bennettscience](https://github.com/bennettscience)
+- Bruce Spang [@brucespang](https://github.com/brucespang)
 - Catherine Abbruzzese [@cat0698](https://github.com/cat0698))
 - Cameron Cuff [@ctcuff](https://github.com/ctcuff)
 - Dalton Durst [@UniversalSuperBox](https://github.com/UniversalSuperBox)
@@ -25,11 +29,13 @@ Patches and Suggestions
 - David Warden [@dfwarden](https://github.com/dfwarden)
 - Davis Goff [@Goff-Davis](https://github.com/Goff-Davis)
 - Devin Singh [@devints47](https://github.com/devints47)
+- Dmitry Savransky [@dsavransky](https://github.com/dsavransky)
 - Elise Heron [@thedarkestknight](https://github.com/thedarkestknight)
 - Elli Howard [@qwertynerd97](https://github.com/qwertynerd97)
 - Erik Tews [@eriktews](https://github.com/eriktews)
 - Ethan Finlay [@atarisafari](https://github.com/atarisafari)
 - Gabriela Dijkhoffz [@gdijkhoffz](https://github.com/gdijkhoffz)
+- Harlan Colclough [@hcolclou](https://github.com/hcolclou)
 - Henry Acevedo [@Colombiangmr](https://github.com/Colombiangmr)
 - Ian Altgilbers [@altgilbers](https://github.com/altgilbers)
 - Ian Turgeon [@iturgeon](https://github.com/iturgeon)
@@ -38,11 +44,15 @@ Patches and Suggestions
 - Jonathan Guilbe [@JonGuilbe](https://github.com/JonGuilbe)
 - John Raible [@rebelaide](https://github.com/rebelaide)
 - Jose Silveti [@jrsilveti](https://github.com/jrsilveti)
+- Keegan Berry [@keeeeeegan](https://github.com/keeeeeegan)
+- Kenny Perez [@kennygperez](https://github.com/kennygperez)
 - [@kensler](https://github.com/kensler)
 - Lawrence Oks [@ljoks](https://github.com/ljoks)
+- Leonard Camacho [@lcamacho](https://github.com/lcamacho)
 - Mark Lalor [@MarkLalor](https://github.com/MarkLalor)
 - Markus [@elec3647](https://github.com/elec3647)
 - Matthew Fedder [@matthewf-ucsd](https://github.com/matthewf-ucsd)
+- Matthew Jones [@jonespm](https://github.com/jonespm)
 - Michael Phelps [@nottheswimmer](https://github.com/nottheswimmer)
 - Mike Nahmias [@Mike-Nahmias](https://github.com/Mike-Nahmias)
 - Nathan Dabu [@nathaned](https://github.com/nathaned)
@@ -54,11 +64,14 @@ Patches and Suggestions
 - Sigurður Baldursson [@sigurdurb](https://github.com/sigurdurb)
 - Spencer Rogers [@spencer1248](https://github.com/spencer1248)
 - Stephen Woosley [@stephenwoosley](https://github.com/stephenwoosley)
+- Steven Williams [@onomou](https://github.com/onomou)
 - Toan Vu [@vutoan1245](https://github.com/vutoan1245)
 - [@Tobiaqs](https://github.com/Tobiaqs)
 - Tuan Pham [@tuanvpham](https://github.com/tuanvpham)
 - Tyler Wallace [@wallacetyler](https://github.com/wallacetyler)
 - [@us91](https://github.com/us91)
+- Vishvak Seenichamy [@Vishvak365](https://github.com/Vishvak365)
 - Weining Li [@weining-li](https://github.com/weining-li)
 - William Funk [@WilliamRADFunk](https://github.com/WilliamRADFunk)
 - William Wesley Monroe [@purpleHey](https://github.com/purpleHey)
+- [@wjw27](https://github.com/wjw27)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 18321cc..e5e1b2d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,29 @@
 
 ## [Unreleased]
 
+## [2.0.0] - 2020-08-14
+
+### General
+
+- Added support for arbitrary keyword arguments across the entire library
+
+### New Endpoint Coverage
+
+- Custom Gradebook Columns (Thanks,[@aileenpongnon](https://github.com/aileenpongnon))
+- Files
+    - Resolve Path (Thanks,[@dsavransky](https://github.com/dsavransky))
+
+### Bugfixes
+
+- Fixed an issue where `Quiz.get_quiz_group` incorrectly set `course_id` to the quiz ID.  (Thanks,[@hcolclou](https://github.com/hcolclou))
+- Fixed an issue where `Course.create_external_tool` didn't accept `client_id` (LTI 1.3 support).
+- Fixed an issue where `Module.create_module_item` didn't (Thanks,[@aileenpongnon](https://github.com/aileenpongnon) and [@onomou](https://github.com/onomou))
+- Fixed an issue where `Page.revert_to_revision` would incorrectly always set `group_id` to the page ID. Now correctly sets `group_id` or `course_id` appropriately.
+
+### Breaking Changes
+
+- `Course.create_external_tool` no longer supports positional arguments for its required parameters. Use keyword arguments instead.
+
 ## [1.0.0] - 2020-07-09
 
 ### General
@@ -464,7 +487,8 @@ Huge thanks to [@liblit](https://github.com/liblit) for lots of issues, suggesti
 - Fixed some incorrectly defined parameters
 - Fixed an issue where tests would fail due to an improperly configured requires block
 
-[Unreleased]: https://github.com/ucfopen/canvasapi/compare/v1.0.0...develop
+[Unreleased]: https://github.com/ucfopen/canvasapi/compare/v2.0.0...develop
+[2.0.0]: https://github.com/ucfopen/canvasapi/compare/v1.0.0...v2.0.0
 [1.0.0]: https://github.com/ucfopen/canvasapi/compare/v0.16.1...v1.0.0
 [0.16.1]: https://github.com/ucfopen/canvasapi/compare/v0.16.0...v0.16.1
 [0.16.0]: https://github.com/ucfopen/canvasapi/compare/v0.15.0...v0.16.0
diff --git a/README.md b/README.md
index d9e0e24..40245a0 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 [![CanvasAPI on PyPI](https://img.shields.io/pypi/v/canvasapi.svg)](https://pypi.python.org/pypi/canvasapi)
 [![License](https://img.shields.io/pypi/l/canvasapi.svg)](https://pypi.python.org/pypi/canvasapi)
 [![Python Versions](https://img.shields.io/pypi/pyversions/canvasapi.svg)](https://pypi.python.org/pypi/canvasapi)
-[![Documentation Status](https://readthedocs.org/projects/canvasapi/badge/?version=latest)](http://canvasapi.readthedocs.io/en/latest/?badge=latest)
+[![Documentation Status](https://readthedocs.org/projects/canvasapi/badge/?version=stable)](http://canvasapi.readthedocs.io/en/stable/?badge=stable)
 [![Build Status](https://travis-ci.org/ucfopen/canvasapi.svg?branch=master)](https://travis-ci.org/ucfopen/canvasapi)
 [![Coverage Status](https://coveralls.io/repos/github/ucfopen/canvasapi/badge.svg?branch=master)](https://coveralls.io/github/ucfopen/canvasapi?branch=master)
 [![Join UCF Open Slack Discussions](https://ucf-open-slackin.herokuapp.com/badge.svg)](https://ucf-open-slackin.herokuapp.com/)
diff --git a/canvasapi/__init__.py b/canvasapi/__init__.py
index 08b1370..c5d95fc 100644
--- a/canvasapi/__init__.py
+++ b/canvasapi/__init__.py
@@ -4,4 +4,4 @@ from canvasapi.canvas import Canvas
 
 __all__ = ["Canvas"]
 
-__version__ = "1.0.0"
+__version__ = "2.0.0"
diff --git a/canvasapi/account.py b/canvasapi/account.py
index f17de92..1428dc7 100644
--- a/canvasapi/account.py
+++ b/canvasapi/account.py
@@ -108,7 +108,7 @@ class Account(CanvasObject):
 
         return GradingStandard(self._requester, response.json())
 
-    def close_notification_for_user(self, user, notification):
+    def close_notification_for_user(self, user, notification, **kwargs):
         """
         If the user no long wants to see a notification, it can be
         excused with this call.
@@ -133,6 +133,7 @@ class Account(CanvasObject):
             "accounts/{}/users/{}/account_notifications/{}".format(
                 self.id, user_id, notif_id
             ),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return AccountNotification(self._requester, response.json())
 
@@ -520,7 +521,7 @@ class Account(CanvasObject):
         )
         return Role(self._requester, response.json())
 
-    def delete(self):
+    def delete(self, **kwargs):
         """
         Delete the current account
 
@@ -539,11 +540,12 @@ class Account(CanvasObject):
         response = self._requester.request(
             "DELETE",
             "accounts/{}/sub_accounts/{}".format(self.parent_account_id, self.id),
+            _kwargs=combine_kwargs(**kwargs),
         )
 
         return response.json().get("workflow_state") == "deleted"
 
-    def delete_grading_period(self, grading_period):
+    def delete_grading_period(self, grading_period, **kwargs):
         """
         Delete a grading period for an account.
 
@@ -564,11 +566,12 @@ class Account(CanvasObject):
         response = self._requester.request(
             "DELETE",
             "accounts/{}/grading_periods/{}".format(self.id, grading_period_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
 
         return response.json().get("delete")
 
-    def delete_user(self, user):
+    def delete_user(self, user, **kwargs):
         """
         Delete a user record from a Canvas root account.
 
@@ -593,7 +596,9 @@ class Account(CanvasObject):
         user_id = obj_or_id(user, "user", (User,))
 
         response = self._requester.request(
-            "DELETE", "accounts/{}/users/{}".format(self.id, user_id)
+            "DELETE",
+            "accounts/{}/users/{}".format(self.id, user_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return User(self._requester, response.json())
 
@@ -615,7 +620,7 @@ class Account(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def get_all_outcome_links_in_context(self):
+    def get_all_outcome_links_in_context(self, **kwargs):
         """
         Get all outcome links for context - BETA
 
@@ -633,6 +638,7 @@ class Account(CanvasObject):
             self._requester,
             "GET",
             "accounts/{}/outcome_group_links".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
         )
 
     def get_authentication_events(self, **kwargs):
@@ -777,7 +783,7 @@ class Account(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def get_department_level_grade_data_completed(self):
+    def get_department_level_grade_data_completed(self, **kwargs):
         """
         Return the distribution of all concluded grades in the default term
 
@@ -788,11 +794,13 @@ class Account(CanvasObject):
         """
 
         response = self._requester.request(
-            "GET", "accounts/{}/analytics/completed/grades".format(self.id)
+            "GET",
+            "accounts/{}/analytics/completed/grades".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return response.json()
 
-    def get_department_level_grade_data_current(self):
+    def get_department_level_grade_data_current(self, **kwargs):
         """
         Return the distribution of all available grades in the default term
 
@@ -803,11 +811,13 @@ class Account(CanvasObject):
         """
 
         response = self._requester.request(
-            "GET", "accounts/{}/analytics/current/grades".format(self.id)
+            "GET",
+            "accounts/{}/analytics/current/grades".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return response.json()
 
-    def get_department_level_grade_data_with_given_term(self, term_id):
+    def get_department_level_grade_data_with_given_term(self, term_id, **kwargs):
         """
         Return the distribution of all available or concluded grades with the given term
 
@@ -821,11 +831,13 @@ class Account(CanvasObject):
         """
 
         response = self._requester.request(
-            "GET", "accounts/{}/analytics/terms/{}/grades".format(self.id, term_id)
+            "GET",
+            "accounts/{}/analytics/terms/{}/grades".format(self.id, term_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return response.json()
 
-    def get_department_level_participation_data_completed(self):
+    def get_department_level_participation_data_completed(self, **kwargs):
         """
         Return page view hits all concluded courses in the default term
 
@@ -836,11 +848,13 @@ class Account(CanvasObject):
         """
 
         response = self._requester.request(
-            "GET", "accounts/{}/analytics/completed/activity".format(self.id)
+            "GET",
+            "accounts/{}/analytics/completed/activity".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return response.json()
 
-    def get_department_level_participation_data_current(self):
+    def get_department_level_participation_data_current(self, **kwargs):
         """
         Return page view hits all available courses in the default term
 
@@ -851,11 +865,15 @@ class Account(CanvasObject):
         """
 
         response = self._requester.request(
-            "GET", "accounts/{}/analytics/current/activity".format(self.id)
+            "GET",
+            "accounts/{}/analytics/current/activity".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return response.json()
 
-    def get_department_level_participation_data_with_given_term(self, term_id):
+    def get_department_level_participation_data_with_given_term(
+        self, term_id, **kwargs
+    ):
         """
         Return page view hits all available or concluded courses in the given term
 
@@ -869,11 +887,13 @@ class Account(CanvasObject):
         """
 
         response = self._requester.request(
-            "GET", "accounts/{}/analytics/terms/{}/activity".format(self.id, term_id)
+            "GET",
+            "accounts/{}/analytics/terms/{}/activity".format(self.id, term_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return response.json()
 
-    def get_department_level_statistics_completed(self):
+    def get_department_level_statistics_completed(self, **kwargs):
         """
         Return all available numeric statistics about the department in the default term
 
@@ -884,11 +904,13 @@ class Account(CanvasObject):
         """
 
         response = self._requester.request(
-            "GET", "accounts/{}/analytics/completed/statistics".format(self.id)
+            "GET",
+            "accounts/{}/analytics/completed/statistics".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return response.json()
 
-    def get_department_level_statistics_current(self):
+    def get_department_level_statistics_current(self, **kwargs):
         """
         Return all available numeric statistics about the department in the default term
 
@@ -899,11 +921,13 @@ class Account(CanvasObject):
         """
 
         response = self._requester.request(
-            "GET", "accounts/{}/analytics/current/statistics".format(self.id)
+            "GET",
+            "accounts/{}/analytics/current/statistics".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return response.json()
 
-    def get_department_level_statistics_with_given_term(self, term_id):
+    def get_department_level_statistics_with_given_term(self, term_id, **kwargs):
         """
         Return numeric statistics about the department with the given term
 
@@ -917,7 +941,9 @@ class Account(CanvasObject):
         """
 
         response = self._requester.request(
-            "GET", "accounts/{}/analytics/terms/{}/statistics".format(self.id, term_id)
+            "GET",
+            "accounts/{}/analytics/terms/{}/statistics".format(self.id, term_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return response.json()
 
@@ -1006,7 +1032,7 @@ class Account(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def get_external_tool(self, tool):
+    def get_external_tool(self, tool, **kwargs):
         """
         :calls: `GET /api/v1/accounts/:account_id/external_tools/:external_tool_id \
         <https://canvas.instructure.com/doc/api/external_tools.html#method.external_tools.show>`_
@@ -1021,7 +1047,9 @@ class Account(CanvasObject):
         tool_id = obj_or_id(tool, "tool", (ExternalTool,))
 
         response = self._requester.request(
-            "GET", "accounts/{}/external_tools/{}".format(self.id, tool_id)
+            "GET",
+            "accounts/{}/external_tools/{}".format(self.id, tool_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         tool_json = response.json()
         tool_json.update({"account_id": self.id})
@@ -1190,7 +1218,7 @@ class Account(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def get_index_of_reports(self, report_type):
+    def get_index_of_reports(self, report_type, **kwargs):
         """
         Retrieve all reports that have been run for the account of a specific type.
 
@@ -1208,6 +1236,7 @@ class Account(CanvasObject):
             "GET",
             "accounts/{}/reports/{}".format(self.id, report_type),
             {"account_id": self.id},
+            _kwargs=combine_kwargs(**kwargs),
         )
 
     def get_migration_systems(self, **kwargs):
@@ -1231,7 +1260,7 @@ class Account(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def get_outcome_group(self, group):
+    def get_outcome_group(self, group, **kwargs):
         """
         Returns the details of the Outcome Group with the given id.
 
@@ -1248,12 +1277,14 @@ class Account(CanvasObject):
 
         outcome_group_id = obj_or_id(group, "outcome group", (OutcomeGroup,))
         response = self._requester.request(
-            "GET", "accounts/{}/outcome_groups/{}".format(self.id, outcome_group_id)
+            "GET",
+            "accounts/{}/outcome_groups/{}".format(self.id, outcome_group_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
 
         return OutcomeGroup(self._requester, response.json())
 
-    def get_outcome_groups_in_context(self):
+    def get_outcome_groups_in_context(self, **kwargs):
         """
         Get all outcome groups for context - BETA
 
@@ -1271,6 +1302,7 @@ class Account(CanvasObject):
             self._requester,
             "GET",
             "accounts/{}/outcome_groups".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
         )
 
     def get_outcome_import_status(self, outcome_import, **kwargs):
@@ -1331,7 +1363,7 @@ class Account(CanvasObject):
 
         return AccountReport(self._requester, response_json)
 
-    def get_reports(self):
+    def get_reports(self, **kwargs):
         """
         Return a list of reports for the current context.
 
@@ -1347,9 +1379,10 @@ class Account(CanvasObject):
             "GET",
             "accounts/{}/reports".format(self.id),
             {"account_id": self.id},
+            _kwargs=combine_kwargs(**kwargs),
         )
 
-    def get_role(self, role):
+    def get_role(self, role, **kwargs):
         """
         Retrieve a role by ID.
 
@@ -1364,7 +1397,9 @@ class Account(CanvasObject):
         role_id = obj_or_id(role, "role", (Role,))
 
         response = self._requester.request(
-            "GET", "accounts/{}/roles/{}".format(self.id, role_id)
+            "GET",
+            "accounts/{}/roles/{}".format(self.id, role_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return Role(self._requester, response.json())
 
@@ -1387,7 +1422,7 @@ class Account(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def get_root_outcome_group(self):
+    def get_root_outcome_group(self, **kwargs):
         """
         Redirect to root outcome group for context
 
@@ -1400,7 +1435,9 @@ class Account(CanvasObject):
         from canvasapi.outcome import OutcomeGroup
 
         response = self._requester.request(
-            "GET", "accounts/{}/root_outcome_group".format(self.id)
+            "GET",
+            "accounts/{}/root_outcome_group".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return OutcomeGroup(self._requester, response.json())
 
@@ -1546,7 +1583,7 @@ class Account(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def get_subaccounts(self, recursive=False):
+    def get_subaccounts(self, recursive=False, **kwargs):
         """
         List accounts that are sub-accounts of the given account.
 
@@ -1559,12 +1596,13 @@ class Account(CanvasObject):
         :rtype: :class:`canvasapi.paginated_list.PaginatedList` of
             :class:`canvasapi.account.Account`
         """
+        kwargs["recursive"] = recursive
         return PaginatedList(
             Account,
             self._requester,
             "GET",
             "accounts/{}/sub_accounts".format(self.id),
-            recursive=recursive,
+            _kwargs=combine_kwargs(**kwargs),
         )
 
     def get_user_logins(self, **kwargs):
@@ -1587,7 +1625,7 @@ class Account(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def get_user_notifications(self, user):
+    def get_user_notifications(self, user, **kwargs):
         """
         Return a list of all global notifications in the account for
         this user. Any notifications that have been closed by the user
@@ -1611,6 +1649,7 @@ class Account(CanvasObject):
             self._requester,
             "GET",
             "accounts/{}/users/{}/account_notifications".format(self.id, user_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
 
     def get_users(self, **kwargs):
diff --git a/canvasapi/bookmark.py b/canvasapi/bookmark.py
index 3294d3e..52160f2 100644
--- a/canvasapi/bookmark.py
+++ b/canvasapi/bookmark.py
@@ -6,7 +6,7 @@ class Bookmark(CanvasObject):
     def __str__(self):
         return "{} ({})".format(self.name, self.id)
 
-    def delete(self):
+    def delete(self, **kwargs):
         """
         Delete this bookmark.
 
@@ -16,7 +16,9 @@ class Bookmark(CanvasObject):
         :rtype: :class:`canvasapi.bookmark.Bookmark`
         """
         response = self._requester.request(
-            "DELETE", "users/self/bookmarks/{}".format(self.id)
+            "DELETE",
+            "users/self/bookmarks/{}".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return Bookmark(self._requester, response.json())
 
diff --git a/canvasapi/canvas.py b/canvasapi/canvas.py
index 5c8deab..2b668b0 100644
--- a/canvasapi/canvas.py
+++ b/canvasapi/canvas.py
@@ -60,7 +60,7 @@ class Canvas(object):
 
         self.__requester = Requester(base_url, access_token)
 
-    def clear_course_nicknames(self):
+    def clear_course_nicknames(self, **kwargs):
         """
         Remove all stored course nicknames.
 
@@ -72,10 +72,12 @@ class Canvas(object):
         :rtype: bool
         """
 
-        response = self.__requester.request("DELETE", "users/self/course_nicknames")
+        response = self.__requester.request(
+            "DELETE", "users/self/course_nicknames", _kwargs=combine_kwargs(**kwargs),
+        )
         return response.json().get("message") == "OK"
 
-    def conversations_batch_update(self, conversation_ids, event):
+    def conversations_batch_update(self, conversation_ids, event, **kwargs):
         """
 
         :calls: `PUT /api/v1/conversations \
@@ -99,34 +101,30 @@ class Canvas(object):
             "destroy",
         ]
 
-        try:
-            if event not in ALLOWED_EVENTS:
-                raise ValueError(
-                    "{} is not a valid action. Please use one of the following: {}".format(
-                        event, ",".join(ALLOWED_EVENTS)
-                    )
+        if event not in ALLOWED_EVENTS:
+            raise ValueError(
+                "{} is not a valid action. Please use one of the following: {}".format(
+                    event, ",".join(ALLOWED_EVENTS)
                 )
+            )
 
-            if len(conversation_ids) > 500:
-                raise ValueError(
-                    "You have requested {} updates, which exceeds the limit of 500".format(
-                        len(conversation_ids)
-                    )
+        if len(conversation_ids) > 500:
+            raise ValueError(
+                "You have requested {} updates, which exceeds the limit of 500".format(
+                    len(conversation_ids)
                 )
-
-            response = self.__requester.request(
-                "PUT",
-                "conversations",
-                event=event,
-                **{"conversation_ids[]": conversation_ids}
             )
-            return_progress = Progress(self.__requester, response.json())
-            return return_progress
 
-        except ValueError as e:
-            return e
+        kwargs["conversation_ids"] = conversation_ids
+        kwargs["event"] = event
 
-    def conversations_get_running_batches(self):
+        response = self.__requester.request(
+            "PUT", "conversations", _kwargs=combine_kwargs(**kwargs),
+        )
+        return_progress = Progress(self.__requester, response.json())
+        return return_progress
+
+    def conversations_get_running_batches(self, **kwargs):
         """
         Returns any currently running conversation batches for the current user.
         Conversation batches are created when a bulk private message is sent
@@ -139,11 +137,13 @@ class Canvas(object):
         :rtype: `dict`
         """
 
-        response = self.__requester.request("GET", "conversations/batches")
+        response = self.__requester.request(
+            "GET", "conversations/batches", _kwargs=combine_kwargs(**kwargs)
+        )
 
         return response.json()
 
-    def conversations_mark_all_as_read(self):
+    def conversations_mark_all_as_read(self, **kwargs):
         """
         Mark all conversations as read.
 
@@ -152,10 +152,12 @@ class Canvas(object):
 
         :rtype: `bool`
         """
-        response = self.__requester.request("POST", "conversations/mark_all_as_read")
+        response = self.__requester.request(
+            "POST", "conversations/mark_all_as_read", _kwargs=combine_kwargs(**kwargs)
+        )
         return response.json() == {}
 
-    def conversations_unread_count(self):
+    def conversations_unread_count(self, **kwargs):
         """
         Get the number of unread conversations for the current user
 
@@ -165,7 +167,9 @@ class Canvas(object):
         :returns: simple object with unread_count, example: {'unread_count': '7'}
         :rtype: `dict`
         """
-        response = self.__requester.request("GET", "conversations/unread_count")
+        response = self.__requester.request(
+            "GET", "conversations/unread_count", _kwargs=combine_kwargs(**kwargs)
+        )
 
         return response.json()
 
@@ -413,7 +417,7 @@ class Canvas(object):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def get_activity_stream_summary(self):
+    def get_activity_stream_summary(self, **kwargs):
         """
         Return a summary of the current user's global activity stream.
 
@@ -422,7 +426,11 @@ class Canvas(object):
 
         :rtype: dict
         """
-        response = self.__requester.request("GET", "users/self/activity_stream/summary")
+        response = self.__requester.request(
+            "GET",
+            "users/self/activity_stream/summary",
+            _kwargs=combine_kwargs(**kwargs),
+        )
         return response.json()
 
     def get_announcements(self, **kwargs):
@@ -445,7 +453,7 @@ class Canvas(object):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def get_appointment_group(self, appointment_group):
+    def get_appointment_group(self, appointment_group, **kwargs):
         """
         Return single Appointment Group by id
 
@@ -464,7 +472,9 @@ class Canvas(object):
         )
 
         response = self.__requester.request(
-            "GET", "appointment_groups/{}".format(appointment_group_id)
+            "GET",
+            "appointment_groups/{}".format(appointment_group_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return AppointmentGroup(self.__requester, response.json())
 
@@ -488,7 +498,7 @@ class Canvas(object):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def get_brand_variables(self):
+    def get_brand_variables(self, **kwargs):
         """
         Get account brand variables
 
@@ -498,10 +508,12 @@ class Canvas(object):
         :returns: JSON with brand variables for the account.
         :rtype: dict
         """
-        response = self.__requester.request("GET", "brand_variables")
+        response = self.__requester.request(
+            "GET", "brand_variables", _kwargs=combine_kwargs(**kwargs)
+        )
         return response.json()
 
-    def get_calendar_event(self, calendar_event):
+    def get_calendar_event(self, calendar_event, **kwargs):
         """
         Return single Calendar Event by id
 
@@ -520,7 +532,9 @@ class Canvas(object):
         )
 
         response = self.__requester.request(
-            "GET", "calendar_events/{}".format(calendar_event_id)
+            "GET",
+            "calendar_events/{}".format(calendar_event_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return CalendarEvent(self.__requester, response.json())
 
@@ -640,7 +654,7 @@ class Canvas(object):
         )
         return Course(self.__requester, response.json())
 
-    def get_course_accounts(self):
+    def get_course_accounts(self, **kwargs):
         """
         List accounts that the current user can view through their
         admin course enrollments (Teacher, TA or designer enrollments).
@@ -654,9 +668,15 @@ class Canvas(object):
         :rtype: :class:`canvasapi.paginated_list.PaginatedList` of
             :class:`canvasapi.account.Account`
         """
-        return PaginatedList(Account, self.__requester, "GET", "course_accounts")
+        return PaginatedList(
+            Account,
+            self.__requester,
+            "GET",
+            "course_accounts",
+            _kwargs=combine_kwargs(**kwargs),
+        )
 
-    def get_course_nickname(self, course):
+    def get_course_nickname(self, course, **kwargs):
         """
         Return the nickname for the given course.
 
@@ -673,11 +693,13 @@ class Canvas(object):
         course_id = obj_or_id(course, "course", (Course,))
 
         response = self.__requester.request(
-            "GET", "users/self/course_nicknames/{}".format(course_id)
+            "GET",
+            "users/self/course_nicknames/{}".format(course_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return CourseNickname(self.__requester, response.json())
 
-    def get_course_nicknames(self):
+    def get_course_nicknames(self, **kwargs):
         """
         Return all course nicknames set by the current account.
 
@@ -690,7 +712,11 @@ class Canvas(object):
         from canvasapi.course import CourseNickname
 
         return PaginatedList(
-            CourseNickname, self.__requester, "GET", "users/self/course_nicknames"
+            CourseNickname,
+            self.__requester,
+            "GET",
+            "users/self/course_nicknames",
+            _kwargs=combine_kwargs(**kwargs),
         )
 
     def get_courses(self, **kwargs):
@@ -749,7 +775,7 @@ class Canvas(object):
         )
         return File(self.__requester, response.json())
 
-    def get_folder(self, folder):
+    def get_folder(self, folder, **kwargs):
         """
         Return the details for a folder
 
@@ -763,7 +789,9 @@ class Canvas(object):
         """
         folder_id = obj_or_id(folder, "folder", (Folder,))
 
-        response = self.__requester.request("GET", "folders/{}".format(folder_id))
+        response = self.__requester.request(
+            "GET", "folders/{}".format(folder_id), _kwargs=combine_kwargs(**kwargs)
+        )
         return Folder(self.__requester, response.json())
 
     def get_group(self, group, use_sis_id=False, **kwargs):
@@ -796,7 +824,7 @@ class Canvas(object):
         )
         return Group(self.__requester, response.json())
 
-    def get_group_category(self, category):
+    def get_group_category(self, category, **kwargs):
         """
         Get a single group category.
 
@@ -811,7 +839,9 @@ class Canvas(object):
         category_id = obj_or_id(category, "category", (GroupCategory,))
 
         response = self.__requester.request(
-            "GET", "group_categories/{}".format(category_id)
+            "GET",
+            "group_categories/{}".format(category_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return GroupCategory(self.__requester, response.json())
 
@@ -842,7 +872,7 @@ class Canvas(object):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def get_outcome(self, outcome):
+    def get_outcome(self, outcome, **kwargs):
         """
         Returns the details of the outcome with the given id.
 
@@ -858,10 +888,12 @@ class Canvas(object):
         from canvasapi.outcome import Outcome
 
         outcome_id = obj_or_id(outcome, "outcome", (Outcome,))
-        response = self.__requester.request("GET", "outcomes/{}".format(outcome_id))
+        response = self.__requester.request(
+            "GET", "outcomes/{}".format(outcome_id), _kwargs=combine_kwargs(**kwargs)
+        )
         return Outcome(self.__requester, response.json())
 
-    def get_outcome_group(self, group):
+    def get_outcome_group(self, group, **kwargs):
         """
         Returns the details of the Outcome Group with the given id.
 
@@ -879,7 +911,9 @@ class Canvas(object):
         outcome_group_id = obj_or_id(group, "group", (OutcomeGroup,))
 
         response = self.__requester.request(
-            "GET", "global/outcome_groups/{}".format(outcome_group_id)
+            "GET",
+            "global/outcome_groups/{}".format(outcome_group_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
 
         return OutcomeGroup(self.__requester, response.json())
@@ -1049,7 +1083,7 @@ class Canvas(object):
         )
         return Progress(self.__requester, response.json())
 
-    def get_root_outcome_group(self):
+    def get_root_outcome_group(self, **kwargs):
         """
         Redirect to root outcome group for context
 
@@ -1061,7 +1095,9 @@ class Canvas(object):
         """
         from canvasapi.outcome import OutcomeGroup
 
-        response = self.__requester.request("GET", "global/root_outcome_group")
+        response = self.__requester.request(
+            "GET", "global/root_outcome_group", _kwargs=combine_kwargs(**kwargs)
+        )
         return OutcomeGroup(self.__requester, response.json())
 
     def get_section(self, section, use_sis_id=False, **kwargs):
@@ -1091,7 +1127,7 @@ class Canvas(object):
         )
         return Section(self.__requester, response.json())
 
-    def get_todo_items(self):
+    def get_todo_items(self, **kwargs):
         """
         Return the current user's list of todo items, as seen on the user dashboard.
 
@@ -1100,10 +1136,12 @@ class Canvas(object):
 
         :rtype: dict
         """
-        response = self.__requester.request("GET", "users/self/todo")
+        response = self.__requester.request(
+            "GET", "users/self/todo", _kwargs=combine_kwargs(**kwargs)
+        )
         return response.json()
 
-    def get_upcoming_events(self):
+    def get_upcoming_events(self, **kwargs):
         """
         Return the current user's upcoming events, i.e. the same things shown
         in the dashboard 'Coming Up' sidebar.
@@ -1113,7 +1151,9 @@ class Canvas(object):
 
         :rtype: dict
         """
-        response = self.__requester.request("GET", "users/self/upcoming_events")
+        response = self.__requester.request(
+            "GET", "users/self/upcoming_events", _kwargs=combine_kwargs(**kwargs)
+        )
         return response.json()
 
     def get_user(self, user, id_type=None, **kwargs):
@@ -1284,7 +1324,7 @@ class Canvas(object):
         )
         return response.json()
 
-    def set_course_nickname(self, course, nickname):
+    def set_course_nickname(self, course, nickname, **kwargs):
         """
         Set a nickname for the given course. This will replace the
         course's name in the output of subsequent API calls, as
@@ -1304,7 +1344,11 @@ class Canvas(object):
 
         course_id = obj_or_id(course, "course", (Course,))
 
+        kwargs["nickname"] = nickname
+
         response = self.__requester.request(
-            "PUT", "users/self/course_nicknames/{}".format(course_id), nickname=nickname
+            "PUT",
+            "users/self/course_nicknames/{}".format(course_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return CourseNickname(self.__requester, response.json())
diff --git a/canvasapi/communication_channel.py b/canvasapi/communication_channel.py
index 623cff6..9197537 100644
--- a/canvasapi/communication_channel.py
+++ b/canvasapi/communication_channel.py
@@ -7,7 +7,7 @@ class CommunicationChannel(CanvasObject):
     def __str__(self):
         return "{} ({})".format(self.address, self.id)
 
-    def delete(self):
+    def delete(self, **kwargs):
         """
         Delete the current communication_channel
 
@@ -19,12 +19,14 @@ class CommunicationChannel(CanvasObject):
         """
 
         response = self._requester.request(
-            "DELETE", "users/{}/communication_channels/{}".format(self.user_id, self.id)
+            "DELETE",
+            "users/{}/communication_channels/{}".format(self.user_id, self.id),
+            _kwargs=combine_kwargs(**kwargs),
         )
 
         return response.json().get("workflow_state") == "deleted"
 
-    def get_preference(self, notification):
+    def get_preference(self, notification, **kwargs):
         """
         Fetch the preference for the given notification for the given
         communication channel.
@@ -43,6 +45,7 @@ class CommunicationChannel(CanvasObject):
             "users/{}/communication_channels/{}/notification_preferences/{}".format(
                 self.user_id, self.id, notification
             ),
+            _kwargs=combine_kwargs(**kwargs),
         )
         data = response.json()["notification_preferences"][0]
         return NotificationPreference(self._requester, data)
diff --git a/canvasapi/course.py b/canvasapi/course.py
index d834187..4a5f2f3 100644
--- a/canvasapi/course.py
+++ b/canvasapi/course.py
@@ -3,6 +3,7 @@ from canvasapi.blueprint import BlueprintSubscription
 from canvasapi.canvas_object import CanvasObject
 from canvasapi.collaboration import Collaboration
 from canvasapi.course_epub_export import CourseEpubExport
+from canvasapi.custom_gradebook_columns import CustomGradebookColumn
 from canvasapi.discussion_topic import DiscussionTopic
 from canvasapi.gradebook_history import (
     Day,
@@ -50,7 +51,7 @@ class Course(CanvasObject):
         :param title: The title for the Grading Standard
         :type title: str
         :param grading_scheme: A list of dictionaries containing keys for "name" and "value"
-        :type grading_scheme: list[dict]
+        :type grading_scheme: list of dict
         :rtype: :class:`canvasapi.grading_standards.GradingStandard`
         """
         if not isinstance(grading_scheme_entry, list) or len(grading_scheme_entry) <= 0:
@@ -73,7 +74,29 @@ class Course(CanvasObject):
         )
         return GradingStandard(self._requester, response.json())
 
-    def conclude(self):
+    def column_data_bulk_update(self, column_data, **kwargs):
+        """
+        Set the content of custom columns.
+
+        :calls: `PUT /api/v1/courses/:course_id/custom_gradebook_column_data \
+        <https://canvas.instructure.com/doc/api/custom_gradebook_columns.html#method.custom_gradebook_column_data_api.bulk_update>`_
+
+        :param column_data: Content to put into the column
+        :type column_data: list
+        :rtype: :class:`canvasapi.progress.Progress`
+        """
+
+        kwargs["column_data"] = column_data
+
+        response = self._requester.request(
+            "PUT",
+            "courses/{}/custom_gradebook_column_data".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
+        )
+
+        return Progress(self._requester, response.json())
+
+    def conclude(self, **kwargs):
         """
         Mark this course as concluded.
 
@@ -83,8 +106,10 @@ class Course(CanvasObject):
         :returns: True if the course was concluded, False otherwise.
         :rtype: bool
         """
+        kwargs["event"] = "conclude"
+
         response = self._requester.request(
-            "DELETE", "courses/{}".format(self.id), event="conclude"
+            "DELETE", "courses/{}".format(self.id), _kwargs=combine_kwargs(**kwargs),
         )
 
         return response.json().get("conclude")
@@ -216,6 +241,33 @@ class Course(CanvasObject):
 
         return Section(self._requester, response.json())
 
+    def create_custom_column(self, column, **kwargs):
+        """
+        Create a custom gradebook column.
+
+        :calls: `POST /api/v1/courses/:course_id/custom_gradebook_columns \
+        <https://canvas.instructure.com/doc/api/custom_gradebook_columns.html#method.custom_gradebook_columns_api.create>`_
+
+        :param column: A dictionary representing the Custom Gradebook Column to create
+        :type column: dict
+
+        :rtype: :class:`canvasapi.custom_gradebook_columns.CustomGradebookColumn`
+        """
+        if isinstance(column, dict) and "title" in column:
+            kwargs["column"] = column
+        else:
+            raise RequiredFieldMissing("Dictionary with key 'title' is required.")
+
+        response = self._requester.request(
+            "POST",
+            "courses/{}/custom_gradebook_columns".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
+        )
+        column_json = response.json()
+        column_json.update({"course_id": self.id})
+
+        return CustomGradebookColumn(self._requester, column_json)
+
     def create_discussion_topic(self, **kwargs):
         """
         Creates a new discussion topic for the course or group.
@@ -245,7 +297,6 @@ class Course(CanvasObject):
 
         :rtype: :class:`canvasapi.course_epub_export.CourseEpubExport`
         """
-
         response = self._requester.request(
             "POST",
             "courses/{}/epub_exports/".format(self.id),
@@ -275,9 +326,7 @@ class Course(CanvasObject):
         )
         return ExternalFeed(self._requester, response.json())
 
-    def create_external_tool(
-        self, name, privacy_level, consumer_key, shared_secret, **kwargs
-    ):
+    def create_external_tool(self, **kwargs):
         """
         Create an external tool in the current course.
 
@@ -286,24 +335,21 @@ class Course(CanvasObject):
 
         :param name: The name of the tool
         :type name: str
-        :param privacy_level: What information to send to the external
-            tool. Options are "anonymous", "name_only", "public"
-        :type privacy_level: str
-        :param consumer_key: The consumer key for the external tool
-        :type consumer_key: str
-        :param shared_secret: The shared secret with the external tool
-        :type shared_secret: str
+
         :rtype: :class:`canvasapi.external_tool.ExternalTool`
         """
         from canvasapi.external_tool import ExternalTool
 
+        required_params = ("name", "privacy_level", "consumer_key", "shared_secret")
+        if "client_id" not in kwargs and not all(x in kwargs for x in required_params):
+            raise RequiredFieldMissing(
+                "Must pass either `client_id` parameter or "
+                "`name`, `privacy_level`, `consumer_key`, and `shared_secret` parameters."
+            )
+
         response = self._requester.request(
             "POST",
             "courses/{}/external_tools".format(self.id),
-            name=name,
-            privacy_level=privacy_level,
-            consumer_key=consumer_key,
-            shared_secret=shared_secret,
             _kwargs=combine_kwargs(**kwargs),
         )
         response_json = response.json()
@@ -512,7 +558,7 @@ class Course(CanvasObject):
 
         return RubricAssociation(self._requester, quiz_json)
 
-    def delete(self):
+    def delete(self, **kwargs):
         """
         Permanently delete this course.
 
@@ -522,12 +568,14 @@ class Course(CanvasObject):
         :returns: True if the course was deleted, False otherwise.
         :rtype: bool
         """
+        kwargs["event"] = "delete"
+
         response = self._requester.request(
-            "DELETE", "courses/{}".format(self.id), event="delete"
+            "DELETE", "courses/{}".format(self.id), _kwargs=combine_kwargs(**kwargs),
         )
         return response.json().get("delete")
 
-    def delete_external_feed(self, feed):
+    def delete_external_feed(self, feed, **kwargs):
         """
         Deletes the external feed.
 
@@ -544,7 +592,9 @@ class Course(CanvasObject):
         feed_id = obj_or_id(feed, "feed", (ExternalFeed,))
 
         response = self._requester.request(
-            "DELETE", "courses/{}/external_feeds/{}".format(self.id, feed_id)
+            "DELETE",
+            "courses/{}/external_feeds/{}".format(self.id, feed_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return ExternalFeed(self._requester, response.json())
 
@@ -636,7 +686,7 @@ class Course(CanvasObject):
         )
         return ContentExport(self._requester, response.json())
 
-    def get_all_outcome_links_in_context(self):
+    def get_all_outcome_links_in_context(self, **kwargs):
         """
         Get all outcome links for context - BETA
 
@@ -654,6 +704,7 @@ class Course(CanvasObject):
             self._requester,
             "GET",
             "courses/{}/outcome_group_links".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
         )
 
     def get_assignment(self, assignment, **kwargs):
@@ -956,7 +1007,7 @@ class Course(CanvasObject):
 
         return response.json()
 
-    def get_course_level_participation_data(self):
+    def get_course_level_participation_data(self, **kwargs):
         """
         Return page view hits and participation numbers grouped by day through the course's history
 
@@ -967,7 +1018,9 @@ class Course(CanvasObject):
         """
 
         response = self._requester.request(
-            "GET", "courses/{}/analytics/activity".format(self.id)
+            "GET",
+            "courses/{}/analytics/activity".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
         )
 
         return response.json()
@@ -990,7 +1043,26 @@ class Course(CanvasObject):
 
         return response.json()
 
-    def get_discussion_topic(self, topic):
+    def get_custom_columns(self, **kwargs):
+        """
+        List of all the custom gradebook columns for a course.
+
+        :calls: `GET /api/v1/courses/:course_id/custom_gradebook_columns \
+        <https://canvas.instructure.com/doc/api/custom_gradebook_columns.html#method.custom_gradebook_columns_api.index>`_
+
+        :rtype: :class:`canvasapi.paginated_list.PaginatedList` of
+            :class:`canvasapi.custom_gradebook_columns.CustomGradebookColumn`
+        """
+        return PaginatedList(
+            CustomGradebookColumn,
+            self._requester,
+            "GET",
+            "courses/{}/custom_gradebook_columns".format(self.id),
+            {"course_id": self.id},
+            _kwargs=combine_kwargs(**kwargs),
+        )
+
+    def get_discussion_topic(self, topic, **kwargs):
         """
         Return data on an individual discussion topic.
 
@@ -1005,7 +1077,9 @@ class Course(CanvasObject):
         topic_id = obj_or_id(topic, "topic", (DiscussionTopic,))
 
         response = self._requester.request(
-            "GET", "courses/{}/discussion_topics/{}".format(self.id, topic_id)
+            "GET",
+            "courses/{}/discussion_topics/{}".format(self.id, topic_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
 
         response_json = response.json()
@@ -1114,7 +1188,7 @@ class Course(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def get_external_tool(self, tool):
+    def get_external_tool(self, tool, **kwargs):
         """
         :calls: `GET /api/v1/courses/:course_id/external_tools/:external_tool_id \
         <https://canvas.instructure.com/doc/api/external_tools.html#method.external_tools.show>`_
@@ -1129,7 +1203,9 @@ class Course(CanvasObject):
         tool_id = obj_or_id(tool, "tool", (ExternalTool,))
 
         response = self._requester.request(
-            "GET", "courses/{}/external_tools/{}".format(self.id, tool_id)
+            "GET",
+            "courses/{}/external_tools/{}".format(self.id, tool_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         tool_json = response.json()
         tool_json.update({"course_id": self.id})
@@ -1238,7 +1314,7 @@ class Course(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def get_folder(self, folder):
+    def get_folder(self, folder, **kwargs):
         """
         Returns the details for a course folder
 
@@ -1253,7 +1329,9 @@ class Course(CanvasObject):
         folder_id = obj_or_id(folder, "folder", (Folder,))
 
         response = self._requester.request(
-            "GET", "courses/{}/folders/{}".format(self.id, folder_id)
+            "GET",
+            "courses/{}/folders/{}".format(self.id, folder_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return Folder(self._requester, response.json())
 
@@ -1276,7 +1354,7 @@ class Course(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def get_full_discussion_topic(self, topic):
+    def get_full_discussion_topic(self, topic, **kwargs):
         """
         Return a cached structure of the discussion topic.
 
@@ -1291,7 +1369,9 @@ class Course(CanvasObject):
         topic_id = obj_or_id(topic, "topic", (DiscussionTopic,))
 
         response = self._requester.request(
-            "GET", "courses/{}/discussion_topics/{}/view".format(self.id, topic_id)
+            "GET",
+            "courses/{}/discussion_topics/{}/view".format(self.id, topic_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return response.json()
 
@@ -1574,7 +1654,7 @@ class Course(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def get_outcome_group(self, group):
+    def get_outcome_group(self, group, **kwargs):
         """
         Returns the details of the Outcome Group with the given id.
 
@@ -1597,7 +1677,7 @@ class Course(CanvasObject):
 
         return OutcomeGroup(self._requester, response.json())
 
-    def get_outcome_groups_in_context(self):
+    def get_outcome_groups_in_context(self, **kwargs):
         """
         Get all outcome groups for context - BETA
 
@@ -1615,6 +1695,7 @@ class Course(CanvasObject):
             self._requester,
             "GET",
             "courses/{}/outcome_groups".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
         )
 
     def get_outcome_import_status(self, outcome_import, **kwargs):
@@ -1685,7 +1766,7 @@ class Course(CanvasObject):
 
         return response.json()
 
-    def get_page(self, url):
+    def get_page(self, url, **kwargs):
         """
         Retrieve the contents of a wiki page.
 
@@ -1699,7 +1780,9 @@ class Course(CanvasObject):
         """
 
         response = self._requester.request(
-            "GET", "courses/{}/pages/{}".format(self.id, url)
+            "GET",
+            "courses/{}/pages/{}".format(self.id, url),
+            _kwargs=combine_kwargs(**kwargs),
         )
         page_json = response.json()
         page_json.update({"course_id": self.id})
@@ -1725,7 +1808,7 @@ class Course(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def get_quiz(self, quiz):
+    def get_quiz(self, quiz, **kwargs):
         """
         Return the quiz with the given id.
 
@@ -1742,7 +1825,9 @@ class Course(CanvasObject):
         quiz_id = obj_or_id(quiz, "quiz", (Quiz,))
 
         response = self._requester.request(
-            "GET", "courses/{}/quizzes/{}".format(self.id, quiz_id)
+            "GET",
+            "courses/{}/quizzes/{}".format(self.id, quiz_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         quiz_json = response.json()
         quiz_json.update({"course_id": self.id})
@@ -1793,7 +1878,7 @@ class Course(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def get_recent_students(self):
+    def get_recent_students(self, **kwargs):
         """
         Return a list of students in the course ordered by how recently they
         have logged in.
@@ -1807,10 +1892,14 @@ class Course(CanvasObject):
         from canvasapi.user import User
 
         return PaginatedList(
-            User, self._requester, "GET", "courses/{}/recent_students".format(self.id)
+            User,
+            self._requester,
+            "GET",
+            "courses/{}/recent_students".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
         )
 
-    def get_root_outcome_group(self):
+    def get_root_outcome_group(self, **kwargs):
         """
         Redirect to root outcome group for context
 
@@ -1823,7 +1912,9 @@ class Course(CanvasObject):
         from canvasapi.outcome import OutcomeGroup
 
         response = self._requester.request(
-            "GET", "courses/{}/root_outcome_group".format(self.id)
+            "GET",
+            "courses/{}/root_outcome_group".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return OutcomeGroup(self._requester, response.json())
 
@@ -1907,7 +1998,7 @@ class Course(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def get_settings(self):
+    def get_settings(self, **kwargs):
         """
         Returns this course's settings.
 
@@ -1916,7 +2007,11 @@ class Course(CanvasObject):
 
         :rtype: dict
         """
-        response = self._requester.request("GET", "courses/{}/settings".format(self.id))
+        response = self._requester.request(
+            "GET",
+            "courses/{}/settings".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
+        )
         return response.json()
 
     def get_single_grading_standard(self, grading_standard_id, **kwargs):
@@ -2037,7 +2132,7 @@ class Course(CanvasObject):
         response = self._requester.request("GET", uri, _kwargs=combine_kwargs(**kwargs))
         return User(self._requester, response.json())
 
-    def get_user_in_a_course_level_assignment_data(self, user):
+    def get_user_in_a_course_level_assignment_data(self, user, **kwargs):
         """
         Return a list of assignments for the course sorted by due date
 
@@ -2054,12 +2149,14 @@ class Course(CanvasObject):
         user_id = obj_or_id(user, "user", (User,))
 
         response = self._requester.request(
-            "GET", "courses/{}/analytics/users/{}/assignments".format(self.id, user_id)
+            "GET",
+            "courses/{}/analytics/users/{}/assignments".format(self.id, user_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
 
         return response.json()
 
-    def get_user_in_a_course_level_messaging_data(self, user):
+    def get_user_in_a_course_level_messaging_data(self, user, **kwargs):
         """
         Return messaging hits grouped by day through the entire history of the course
 
@@ -2078,11 +2175,12 @@ class Course(CanvasObject):
         response = self._requester.request(
             "GET",
             "courses/{}/analytics/users/{}/communication".format(self.id, user_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
 
         return response.json()
 
-    def get_user_in_a_course_level_participation_data(self, user):
+    def get_user_in_a_course_level_participation_data(self, user, **kwargs):
         """
         Return page view hits grouped by hour and participation details through course's history
 
@@ -2099,7 +2197,9 @@ class Course(CanvasObject):
         user_id = obj_or_id(user, "user", (User,))
 
         response = self._requester.request(
-            "GET", "courses/{}/analytics/users/{}/activity".format(self.id, user_id)
+            "GET",
+            "courses/{}/analytics/users/{}/activity".format(self.id, user_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
 
         return response.json()
@@ -2176,7 +2276,7 @@ class Course(CanvasObject):
             kwargs=combine_kwargs(**kwargs),
         )
 
-    def preview_html(self, html):
+    def preview_html(self, html, **kwargs):
         """
         Preview HTML content processed for this course.
 
@@ -2187,8 +2287,12 @@ class Course(CanvasObject):
         :type html: str
         :rtype: str
         """
+        kwargs["html"] = html
+
         response = self._requester.request(
-            "POST", "courses/{}/preview_html".format(self.id), html=html
+            "POST",
+            "courses/{}/preview_html".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return response.json().get("html", "")
 
@@ -2210,7 +2314,7 @@ class Course(CanvasObject):
 
         return response.json()
 
-    def reorder_pinned_topics(self, order):
+    def reorder_pinned_topics(self, order, **kwargs):
         """
         Puts the pinned discussion topics in the specified order.
         All pinned topics should be included.
@@ -2233,13 +2337,17 @@ class Course(CanvasObject):
         if not isinstance(order, str) or "," not in order:
             raise ValueError("Param `order` must be a list, tuple, or string.")
 
+        kwargs["order"] = order
+
         response = self._requester.request(
-            "POST", "courses/{}/discussion_topics/reorder".format(self.id), order=order
+            "POST",
+            "courses/{}/discussion_topics/reorder".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
         )
 
         return response.json().get("reorder")
 
-    def reset(self):
+    def reset(self, **kwargs):
         """
         Delete the current course and create a new equivalent course
         with no content, but all sections and users moved over.
@@ -2250,31 +2358,44 @@ class Course(CanvasObject):
         :rtype: :class:`canvasapi.course.Course`
         """
         response = self._requester.request(
-            "POST", "courses/{}/reset_content".format(self.id)
+            "POST",
+            "courses/{}/reset_content".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return Course(self._requester, response.json())
 
-    def resolve_path(self, full_path, **kwargs):
+    def resolve_path(self, full_path=None, **kwargs):
         """
         Returns the paginated list of all of the folders in the given
-        path starting at the course root folder.
+        path starting at the course root folder. Returns root folder if called
+        with no arguments.
 
         :calls: `GET /api/v1/courses/:course_id/folders/by_path/*full_path \
         <https://canvas.instructure.com/doc/api/files.html#method.folders.resolve_path>`_
 
-        :param full_path: Full path to resolve, relative to course root
+        :param full_path: Full path to resolve, relative to course root.
         :type full_path: string
 
         :rtype: :class:`canvasapi.paginated_list.PaginatedList` of
             :class:`canvasapi.folder.Folder`
         """
-        return PaginatedList(
-            Folder,
-            self._requester,
-            "GET",
-            "courses/{0}/folders/by_path/{1}".format(self.id, full_path),
-            _kwargs=combine_kwargs(**kwargs),
-        )
+
+        if full_path:
+            return PaginatedList(
+                Folder,
+                self._requester,
+                "GET",
+                "courses/{0}/folders/by_path/{1}".format(self.id, full_path),
+                _kwargs=combine_kwargs(**kwargs),
+            )
+        else:
+            return PaginatedList(
+                Folder,
+                self._requester,
+                "GET",
+                "courses/{0}/folders/by_path".format(self.id),
+                _kwargs=combine_kwargs(**kwargs),
+            )
 
     def set_quiz_extensions(self, quiz_extensions, **kwargs):
         """
@@ -2348,7 +2469,7 @@ class Course(CanvasObject):
 
         return UsageRights(self._requester, response.json())
 
-    def show_front_page(self):
+    def show_front_page(self, **kwargs):
         """
         Retrieve the content of the front page.
 
@@ -2358,7 +2479,9 @@ class Course(CanvasObject):
         :rtype: :class:`canvasapi.course.Course`
         """
         response = self._requester.request(
-            "GET", "courses/{}/front_page".format(self.id)
+            "GET",
+            "courses/{}/front_page".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         page_json = response.json()
         page_json.update({"course_id": self.id})
@@ -2465,7 +2588,7 @@ class CourseNickname(CanvasObject):
     def __str__(self):
         return "{} ({})".format(self.nickname, self.course_id)
 
-    def remove(self):
+    def remove(self, **kwargs):
         """
         Remove the nickname for the given course. Subsequent course API
         calls will return the actual name for the course.
@@ -2476,7 +2599,9 @@ class CourseNickname(CanvasObject):
         :rtype: :class:`canvasapi.course.CourseNickname`
         """
         response = self._requester.request(
-            "DELETE", "users/self/course_nicknames/{}".format(self.course_id)
+            "DELETE",
+            "users/self/course_nicknames/{}".format(self.course_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return CourseNickname(self._requester, response.json())
 
diff --git a/canvasapi/current_user.py b/canvasapi/current_user.py
index 2ea52ea..1ed53a0 100644
--- a/canvasapi/current_user.py
+++ b/canvasapi/current_user.py
@@ -101,7 +101,7 @@ class CurrentUser(User):
 
         return Bookmark(self._requester, response.json())
 
-    def get_bookmark(self, bookmark):
+    def get_bookmark(self, bookmark, **kwargs):
         """
         Return single Bookmark by id
 
@@ -118,7 +118,9 @@ class CurrentUser(User):
         bookmark_id = obj_or_id(bookmark, "bookmark", (Bookmark,))
 
         response = self._requester.request(
-            "GET", "users/self/bookmarks/{}".format(bookmark_id)
+            "GET",
+            "users/self/bookmarks/{}".format(bookmark_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return Bookmark(self._requester, response.json())
 
diff --git a/canvasapi/custom_gradebook_columns.py b/canvasapi/custom_gradebook_columns.py
new file mode 100644
index 0000000..4791173
--- /dev/null
+++ b/canvasapi/custom_gradebook_columns.py
@@ -0,0 +1,132 @@
+from canvasapi.canvas_object import CanvasObject
+from canvasapi.paginated_list import PaginatedList
+from canvasapi.util import (
+    combine_kwargs,
+    is_multivalued,
+)
+
+
+class CustomGradebookColumn(CanvasObject):
+    def __str__(self):
+        return "{} ({})".format(self.title, self.id)
+
+    def delete(self, **kwargs):
+        """
+        Permanently delete a custom column.
+
+        :calls: `DELETE /api/v1/courses/:course_id/custom_gradebook_columns/:id \
+            <https://canvas.instructure.com/doc/api/custom_gradebook_columns.html#method.custom_gradebook_columns_api.destroy>`_
+        :rtype: :class:`canvasapi.custom_gradebook_columns.CustomGradebookColumn`
+        """
+        response = self._requester.request(
+            "DELETE",
+            "courses/{}/custom_gradebook_columns/{}".format(self.course_id, self.id),
+            _kwargs=combine_kwargs(**kwargs),
+        )
+
+        return CustomGradebookColumn(self._requester, response.json())
+
+    def get_column_entries(self, **kwargs):
+        """
+        Returns a list of ColumnData objects.
+
+        :calls: `GET /api/v1/courses/:course_id/custom_gradebook_columns/:id/data \
+            <https://canvas.instructure.com/doc/api/custom_gradebook_columns.html#method.custom_gradebook_column_data_api.index>`_
+
+        :rtype: :class:`canvasapi.paginated_list.PaginatedList` of
+            :class:`canvasapi.custom_gradebook_columns.ColumnData`
+        """
+        return PaginatedList(
+            ColumnData,
+            self._requester,
+            "GET",
+            "courses/{}/custom_gradebook_columns/{}/data".format(
+                self.course_id, self.id
+            ),
+            {"course_id": self.course_id, "gradebook_column_id": self.id},
+            _kwargs=combine_kwargs(**kwargs),
+        )
+
+    def reorder_custom_columns(self, order, **kwargs):
+        """
+        Put the given columns in a specific order based on given parameter.
+
+        :calls: `POST /api/v1/courses/:course_id/custom_gradebook_columns/reorder \
+            <https://canvas.instructure.com/doc/api/custom_gradebook_columns.html#method.custom_gradebook_columns_api.reorder>`_
+
+        :param order: The order the columns are supposed to be in.
+        :type order: list of int
+
+        :returns: True if successful (status code of 200)
+        :rtype: bool
+        """
+        # Convert iterable sequence to comma-separated string
+        if is_multivalued(order):
+            order = ",".join([str(topic_id) for topic_id in order])
+
+        # Check if is a string with commas
+        if not isinstance(order, str) or "," not in order:
+            raise ValueError("Param `order` must be a list, tuple, or string.")
+
+        response = self._requester.request(
+            "POST",
+            "courses/{}/custom_gradebook_columns/reorder".format(self.course_id),
+            _kwargs=combine_kwargs(**kwargs),
+            order=order,
+        )
+
+        return response.json().get("reorder")
+
+    def update_custom_column(self, **kwargs):
+        """
+        Update a CustomColumn object.
+
+        :calls: `PUT /api/v1/courses/:course_id/custom_gradebook_columns/:id \
+            <https://canvas.instructure.com/doc/api/custom_gradebook_columns.html#method.custom_gradebook_columns_api.update>`_
+
+        :rtype: :class:`canvasapi.custom_gradebook_columns.CustomGradebookColumn`
+        """
+
+        response = self._requester.request(
+            "PUT",
+            "courses/{}/custom_gradebook_columns/{}".format(self.course_id, self.id),
+            _kwargs=combine_kwargs(**kwargs),
+        )
+
+        if response.json().get("title"):
+            super(CustomGradebookColumn, self).set_attributes(response.json())
+
+        return self
+
+
+class ColumnData(CanvasObject):
+    def __str__(self):
+        return "{} ({})".format(self.user_id, self.content)
+
+    def update_column_data(self, column_data, **kwargs):
+        """
+        Sets the content of a custom column.
+
+        :calls: `PUT /api/v1/courses/:course_id/custom_gradebook_columns/:id/data/:user_id \
+            <https://canvas.instructure.com/doc/api/custom_gradebook_columns.html#method.custom_gradebook_column_data_api.update>`_
+
+        :param column_data: The content in the column.
+        :type column_data: str
+
+        :rtype: :class:`canvasapi.custom_gradebook_columns.ColumnData`
+        """
+
+        kwargs["column_data"] = column_data
+
+        response = self._requester.request(
+            "PUT",
+            "courses/{}/custom_gradebook_columns/{}/data/{}".format(
+                self.course_id, self.gradebook_column_id, self.user_id
+            ),
+            _kwargs=combine_kwargs(**kwargs),
+        )
+
+        if response.json().get("content"):
+            super(ColumnData, self).set_attributes(response.json())
+
+        return self
diff --git a/canvasapi/discussion_topic.py b/canvasapi/discussion_topic.py
index c8c840c..2a7dd50 100644
--- a/canvasapi/discussion_topic.py
+++ b/canvasapi/discussion_topic.py
@@ -35,7 +35,7 @@ class DiscussionTopic(CanvasObject):
         else:
             raise ValueError("Discussion Topic does not have a course_id or group_id")
 
-    def delete(self):
+    def delete(self, **kwargs):
         """
         Deletes the discussion topic. This will also delete the assignment.
 
@@ -53,6 +53,7 @@ class DiscussionTopic(CanvasObject):
             "{}s/{}/discussion_topics/{}".format(
                 self._parent_type, self._parent_id, self.id
             ),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return "deleted_at" in response.json()
 
@@ -91,7 +92,7 @@ class DiscussionTopic(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def get_parent(self):
+    def get_parent(self, **kwargs):
         """
         Return the object that spawned this discussion topic.
 
@@ -101,7 +102,9 @@ class DiscussionTopic(CanvasObject):
         from canvasapi.course import Course
 
         response = self._requester.request(
-            "GET", "{}s/{}".format(self._parent_type, self._parent_id)
+            "GET",
+            "{}s/{}".format(self._parent_type, self._parent_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
 
         if self._parent_type == "group":
@@ -136,7 +139,7 @@ class DiscussionTopic(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def mark_as_read(self):
+    def mark_as_read(self, **kwargs):
         """
         Mark the initial text of the discussion topic as read.
 
@@ -153,10 +156,11 @@ class DiscussionTopic(CanvasObject):
             "{}s/{}/discussion_topics/{}/read".format(
                 self._parent_type, self._parent_id, self.id
             ),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return response.status_code == 204
 
-    def mark_as_unread(self):
+    def mark_as_unread(self, **kwargs):
         """
         Mark the initial text of the discussion topic as unread.
 
@@ -173,6 +177,7 @@ class DiscussionTopic(CanvasObject):
             "{}s/{}/discussion_topics/{}/read".format(
                 self._parent_type, self._parent_id, self.id
             ),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return response.status_code == 204
 
@@ -246,7 +251,7 @@ class DiscussionTopic(CanvasObject):
         )
         return DiscussionEntry(self._requester, response_json)
 
-    def subscribe(self):
+    def subscribe(self, **kwargs):
         """
         Subscribe to a topic to receive notifications about new entries.
 
@@ -263,10 +268,11 @@ class DiscussionTopic(CanvasObject):
             "{}s/{}/discussion_topics/{}/subscribed".format(
                 self._parent_type, self._parent_id, self.id
             ),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return response.status_code == 204
 
-    def unsubscribe(self):
+    def unsubscribe(self, **kwargs):
         """
         Unsubscribe from a topic to stop receiving notifications about new entries.
 
@@ -283,6 +289,7 @@ class DiscussionTopic(CanvasObject):
             "{}s/{}/discussion_topics/{}/subscribed".format(
                 self._parent_type, self._parent_id, self.id
             ),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return response.status_code == 204
 
@@ -364,7 +371,7 @@ class DiscussionEntry(CanvasObject):
         )
         return "deleted_at" in response.json()
 
-    def get_discussion(self):
+    def get_discussion(self, **kwargs):
         """
         Return the discussion topic object this entry is related to
 
@@ -378,6 +385,7 @@ class DiscussionEntry(CanvasObject):
                 self._discussion_parent_id,
                 self.discussion_id,
             ),
+            _kwargs=combine_kwargs(**kwargs),
         )
 
         response_json = response.json()
@@ -421,7 +429,7 @@ class DiscussionEntry(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def mark_as_read(self):
+    def mark_as_read(self, **kwargs):
         """
         Mark a discussion entry as read.
 
@@ -441,10 +449,11 @@ class DiscussionEntry(CanvasObject):
                 self.discussion_id,
                 self.id,
             ),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return response.status_code == 204
 
-    def mark_as_unread(self):
+    def mark_as_unread(self, **kwargs):
         """
         Mark a discussion entry as unread.
 
@@ -466,6 +475,7 @@ class DiscussionEntry(CanvasObject):
                 self.discussion_id,
                 self.id,
             ),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return response.status_code == 204
 
diff --git a/canvasapi/file.py b/canvasapi/file.py
index b13537e..ea68a52 100644
--- a/canvasapi/file.py
+++ b/canvasapi/file.py
@@ -1,11 +1,12 @@
 from canvasapi.canvas_object import CanvasObject
+from canvasapi.util import combine_kwargs
 
 
 class File(CanvasObject):
     def __str__(self):
         return "{}".format(self.display_name)
 
-    def delete(self):
+    def delete(self, **kwargs):
         """
         Delete this file.
 
@@ -14,7 +15,9 @@ class File(CanvasObject):
 
         :rtype: :class:`canvasapi.file.File`
         """
-        response = self._requester.request("DELETE", "files/{}".format(self.id))
+        response = self._requester.request(
+            "DELETE", "files/{}".format(self.id), _kwargs=combine_kwargs(**kwargs)
+        )
         return File(self._requester, response.json())
 
     def download(self, location):
diff --git a/canvasapi/group.py b/canvasapi/group.py
index 558fa75..0309c09 100644
--- a/canvasapi/group.py
+++ b/canvasapi/group.py
@@ -159,7 +159,7 @@ class Group(CanvasObject):
 
         return Page(self._requester, page_json)
 
-    def delete(self):
+    def delete(self, **kwargs):
         """
         Delete a group.
 
@@ -168,10 +168,12 @@ class Group(CanvasObject):
 
         :rtype: :class:`canvasapi.group.Group`
         """
-        response = self._requester.request("DELETE", "groups/{}".format(self.id))
+        response = self._requester.request(
+            "DELETE", "groups/{}".format(self.id), _kwargs=combine_kwargs(**kwargs)
+        )
         return Group(self._requester, response.json())
 
-    def delete_external_feed(self, feed):
+    def delete_external_feed(self, feed, **kwargs):
         """
         Deletes the external feed.
 
@@ -188,7 +190,9 @@ class Group(CanvasObject):
         feed_id = obj_or_id(feed, "feed", (ExternalFeed,))
 
         response = self._requester.request(
-            "DELETE", "groups/{}/external_feeds/{}".format(self.id, feed_id)
+            "DELETE",
+            "groups/{}/external_feeds/{}".format(self.id, feed_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return ExternalFeed(self._requester, response.json())
 
@@ -250,7 +254,7 @@ class Group(CanvasObject):
         )
         return ContentExport(self._requester, response.json())
 
-    def get_activity_stream_summary(self):
+    def get_activity_stream_summary(self, **kwargs):
         """
         Return a summary of the current user's global activity stream.
 
@@ -260,7 +264,9 @@ class Group(CanvasObject):
         :rtype: dict
         """
         response = self._requester.request(
-            "GET", "groups/{}/activity_stream/summary".format(self.id)
+            "GET",
+            "groups/{}/activity_stream/summary".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return response.json()
 
@@ -400,7 +406,7 @@ class Group(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def get_discussion_topic(self, topic):
+    def get_discussion_topic(self, topic, **kwargs):
         """
         Return data on an individual discussion topic.
 
@@ -415,7 +421,9 @@ class Group(CanvasObject):
         topic_id = obj_or_id(topic, "topic", (DiscussionTopic,))
 
         response = self._requester.request(
-            "GET", "groups/{}/discussion_topics/{}".format(self.id, topic_id)
+            "GET",
+            "groups/{}/discussion_topics/{}".format(self.id, topic_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
 
         response_json = response.json()
@@ -505,7 +513,7 @@ class Group(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def get_folder(self, folder):
+    def get_folder(self, folder, **kwargs):
         """
         Returns the details for a group's folder
 
@@ -520,7 +528,9 @@ class Group(CanvasObject):
         folder_id = obj_or_id(folder, "folder", (Folder,))
 
         response = self._requester.request(
-            "GET", "groups/{}/folders/{}".format(self.id, folder_id)
+            "GET",
+            "groups/{}/folders/{}".format(self.id, folder_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return Folder(self._requester, response.json())
 
@@ -539,7 +549,7 @@ class Group(CanvasObject):
             Folder, self._requester, "GET", "groups/{}/folders".format(self.id)
         )
 
-    def get_full_discussion_topic(self, topic):
+    def get_full_discussion_topic(self, topic, **kwargs):
         """
         Return a cached structure of the discussion topic.
 
@@ -554,7 +564,9 @@ class Group(CanvasObject):
         topic_id = obj_or_id(topic, "topic", (DiscussionTopic,))
 
         response = self._requester.request(
-            "GET", "groups/{}/discussion_topics/{}/view".format(self.id, topic_id)
+            "GET",
+            "groups/{}/discussion_topics/{}/view".format(self.id, topic_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return response.json()
 
@@ -578,7 +590,7 @@ class Group(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def get_membership(self, user, membership_type):
+    def get_membership(self, user, membership_type, **kwargs):
         """
         List users in a group.
 
@@ -598,7 +610,9 @@ class Group(CanvasObject):
         user_id = obj_or_id(user, "user", (User,))
 
         response = self._requester.request(
-            "GET", "groups/{}/{}/{}".format(self.id, membership_type, user_id)
+            "GET",
+            "groups/{}/{}/{}".format(self.id, membership_type, user_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return GroupMembership(self._requester, response.json())
 
@@ -640,7 +654,7 @@ class Group(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def get_page(self, url):
+    def get_page(self, url, **kwargs):
         """
         Retrieve the contents of a wiki page.
 
@@ -655,7 +669,9 @@ class Group(CanvasObject):
         from canvasapi.course import Page
 
         response = self._requester.request(
-            "GET", "groups/{}/pages/{}".format(self.id, url)
+            "GET",
+            "groups/{}/pages/{}".format(self.id, url),
+            _kwargs=combine_kwargs(**kwargs),
         )
         page_json = response.json()
         page_json.update({"group_id": self.id})
@@ -723,7 +739,7 @@ class Group(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def invite(self, invitees):
+    def invite(self, invitees, **kwargs):
         """
         Invite users to group.
 
@@ -736,15 +752,16 @@ class Group(CanvasObject):
         :rtype: :class:`canvasapi.paginated_list.PaginatedList` of
             :class:`canvasapi.group.GroupMembership`
         """
+        kwargs["invitees"] = invitees
         return PaginatedList(
             GroupMembership,
             self._requester,
             "POST",
             "groups/{}/invite".format(self.id),
-            invitees=invitees,
+            _kwargs=combine_kwargs(**kwargs),
         )
 
-    def preview_html(self, html):
+    def preview_html(self, html, **kwargs):
         """
         Preview HTML content processed for this course.
 
@@ -756,7 +773,10 @@ class Group(CanvasObject):
         :rtype: str
         """
         response = self._requester.request(
-            "POST", "groups/{}/preview_html".format(self.id), html=html
+            "POST",
+            "groups/{}/preview_html".format(self.id),
+            html=html,
+            _kwargs=combine_kwargs(**kwargs),
         )
         return response.json().get("html", "")
 
@@ -777,7 +797,7 @@ class Group(CanvasObject):
 
         return response.json()
 
-    def remove_user(self, user):
+    def remove_user(self, user, **kwargs):
         """
         Leave a group if allowed.
 
@@ -794,11 +814,13 @@ class Group(CanvasObject):
         user_id = obj_or_id(user, "user", (User,))
 
         response = self._requester.request(
-            "DELETE", "groups/{}/users/{}".format(self.id, user_id)
+            "DELETE",
+            "groups/{}/users/{}".format(self.id, user_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return User(self._requester, response.json())
 
-    def reorder_pinned_topics(self, order):
+    def reorder_pinned_topics(self, order, **kwargs):
         """
         Puts the pinned discussion topics in the specified order.
         All pinned topics should be included.
@@ -821,12 +843,49 @@ class Group(CanvasObject):
         if not isinstance(order, str) or "," not in order:
             raise ValueError("Param `order` must be a list, tuple, or string.")
 
+        kwargs["order"] = order
+
         response = self._requester.request(
-            "POST", "groups/{}/discussion_topics/reorder".format(self.id), order=order
+            "POST",
+            "groups/{}/discussion_topics/reorder".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
         )
 
         return response.json().get("reorder")
 
+    def resolve_path(self, full_path=None, **kwargs):
+        """
+        Returns the paginated list of all of the folders in the given
+        path starting at the group root folder. Returns root folder if called
+        with no arguments.
+
+        :calls: `GET /api/v1/groups/group_id/folders/by_path/*full_path \
+        <https://canvas.instructure.com/doc/api/files.html#method.folders.resolve_path>`_
+
+        :param full_path: Full path to resolve, relative to group root.
+        :type full_path: string
+
+        :rtype: :class:`canvasapi.paginated_list.PaginatedList` of
+            :class:`canvasapi.folder.Folder`
+        """
+
+        if full_path:
+            return PaginatedList(
+                Folder,
+                self._requester,
+                "GET",
+                "groups/{0}/folders/by_path/{1}".format(self.id, full_path),
+                _kwargs=combine_kwargs(**kwargs),
+            )
+        else:
+            return PaginatedList(
+                Folder,
+                self._requester,
+                "GET",
+                "groups/{0}/folders/by_path".format(self.id),
+                _kwargs=combine_kwargs(**kwargs),
+            )
+
     def set_usage_rights(self, **kwargs):
         """
         Changes the usage rights for specified files that are under the current group scope
@@ -845,7 +904,7 @@ class Group(CanvasObject):
 
         return UsageRights(self._requester, response.json())
 
-    def show_front_page(self):
+    def show_front_page(self, **kwargs):
         """
         Retrieve the content of the front page.
 
@@ -857,7 +916,9 @@ class Group(CanvasObject):
         from canvasapi.course import Page
 
         response = self._requester.request(
-            "GET", "groups/{}/front_page".format(self.id)
+            "GET",
+            "groups/{}/front_page".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         page_json = response.json()
         page_json.update({"group_id": self.id})
@@ -915,7 +976,7 @@ class GroupMembership(CanvasObject):
     def __str__(self):
         return "{} - {} ({})".format(self.user_id, self.group_id, self.id)
 
-    def remove_self(self):
+    def remove_self(self, **kwargs):
         """
         Leave a group if allowed.
 
@@ -926,11 +987,13 @@ class GroupMembership(CanvasObject):
         :rtype: dict
         """
         response = self._requester.request(
-            "DELETE", "groups/{}/memberships/self".format(self.id)
+            "DELETE",
+            "groups/{}/memberships/self".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return response.json()
 
-    def remove_user(self, user):
+    def remove_user(self, user, **kwargs):
         """
         Remove user from membership.
 
@@ -948,7 +1011,9 @@ class GroupMembership(CanvasObject):
         user_id = obj_or_id(user, "user", (User,))
 
         response = self._requester.request(
-            "DELETE", "groups/{}/users/{}".format(self.id, user_id)
+            "DELETE",
+            "groups/{}/users/{}".format(self.id, user_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return response.json()
 
@@ -974,7 +1039,7 @@ class GroupCategory(CanvasObject):
     def __str__(self):
         return "{} ({})".format(self.name, self.id)
 
-    def assign_members(self, sync=False):
+    def assign_members(self, sync=False, **kwargs):
         """
         Assign unassigned members.
 
@@ -993,10 +1058,13 @@ class GroupCategory(CanvasObject):
                 self._requester,
                 "POST",
                 "group_categories/{}/assign_unassigned_members".format(self.id),
+                _kwargs=combine_kwargs(**kwargs),
             )
         else:
             response = self._requester.request(
-                "POST", "group_categories/{}/assign_unassigned_members".format(self.id)
+                "POST",
+                "group_categories/{}/assign_unassigned_members".format(self.id),
+                _kwargs=combine_kwargs(**kwargs),
             )
             return Progress(self._requester, response.json())
 
@@ -1016,7 +1084,7 @@ class GroupCategory(CanvasObject):
         )
         return Group(self._requester, response.json())
 
-    def delete(self):
+    def delete(self, **kwargs):
         """
         Delete a group category.
 
@@ -1026,7 +1094,9 @@ class GroupCategory(CanvasObject):
         :rtype: empty dict
         """
         response = self._requester.request(
-            "DELETE", "group_categories/{}".format(self.id)
+            "DELETE",
+            "group_categories/{}".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return response.json()
 
diff --git a/canvasapi/module.py b/canvasapi/module.py
index f235acb..fe6b3b2 100644
--- a/canvasapi/module.py
+++ b/canvasapi/module.py
@@ -122,8 +122,12 @@ class Module(CanvasObject):
         :returns: The created module item.
         :rtype: :class:`canvasapi.module.ModuleItem`
         """
+
+        unrequired_types = ["ExternalUrl", "Page", "SubHeader"]
+
         if isinstance(module_item, dict) and "type" in module_item:
-            if "content_id" in module_item:
+            # content_id is not required for unrequired_types
+            if module_item["type"] in unrequired_types or "content_id" in module_item:
                 kwargs["module_item"] = module_item
             else:
                 raise RequiredFieldMissing(
diff --git a/canvasapi/page.py b/canvasapi/page.py
index c26e180..b362cd0 100644
--- a/canvasapi/page.py
+++ b/canvasapi/page.py
@@ -7,7 +7,7 @@ class Page(CanvasObject):
     def __str__(self):
         return "{} ({})".format(self.title, self.url)
 
-    def delete(self):
+    def delete(self, **kwargs):
         """
         Delete this page.
 
@@ -17,7 +17,9 @@ class Page(CanvasObject):
         :rtype: :class:`canvasapi.page.Page`
         """
         response = self._requester.request(
-            "DELETE", "courses/{}/pages/{}".format(self.course_id, self.url)
+            "DELETE",
+            "courses/{}/pages/{}".format(self.course_id, self.url),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return Page(self._requester, response.json())
 
@@ -43,7 +45,7 @@ class Page(CanvasObject):
 
         return self
 
-    def get_parent(self):
+    def get_parent(self, **kwargs):
         """
         Return the object that spawned this page.
 
@@ -58,7 +60,9 @@ class Page(CanvasObject):
         from canvasapi.course import Course
 
         response = self._requester.request(
-            "GET", "{}s/{}".format(self.parent_type, self.parent_id)
+            "GET",
+            "{}s/{}".format(self.parent_type, self.parent_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
 
         if self.parent_type == "group":
@@ -144,7 +148,7 @@ class Page(CanvasObject):
         else:
             raise ValueError("ExternalTool does not have a course_id or group_id")
 
-    def revert_to_revision(self, revision):
+    def revert_to_revision(self, revision, **kwargs):
         """
         Revert the page back to a specified revision.
 
@@ -163,12 +167,10 @@ class Page(CanvasObject):
             "{}s/{}/pages/{}/revisions/{}".format(
                 self.parent_type, self.parent_id, self.url, revision_id
             ),
+            _kwargs=combine_kwargs(**kwargs),
         )
         pagerev_json = response.json()
-        if self.parent_type == "group":
-            pagerev_json.update({"group_id": self.id})
-        elif self.parent_type == "course":
-            pagerev_json.update({"group_id": self.id})
+        pagerev_json.update({"{self.parent_type}_id": self.parent_id})
 
         return PageRevision(self._requester, pagerev_json)
 
@@ -195,7 +197,7 @@ class PageRevision(CanvasObject):
     def __str__(self):
         return "{} ({})".format(self.updated_at, self.revision_id)
 
-    def get_parent(self):
+    def get_parent(self, **kwargs):
         """
         Return the object that spawned this page.
 
@@ -210,7 +212,9 @@ class PageRevision(CanvasObject):
         from canvasapi.course import Course
 
         response = self._requester.request(
-            "GET", "{}s/{}".format(self.parent_type, self.parent_id)
+            "GET",
+            "{}s/{}".format(self.parent_type, self.parent_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
 
         if self.parent_type == "group":
diff --git a/canvasapi/progress.py b/canvasapi/progress.py
index 04f327d..5dafc93 100644
--- a/canvasapi/progress.py
+++ b/canvasapi/progress.py
@@ -1,11 +1,12 @@
 from canvasapi.canvas_object import CanvasObject
+from canvasapi.util import combine_kwargs
 
 
 class Progress(CanvasObject):
     def __str__(self):
         return "{} - {} ({})".format(self.tag, self.workflow_state, self.id)
 
-    def query(self):
+    def query(self, **kwargs):
         """
         Return completion and status information about an asynchronous job.
 
@@ -14,7 +15,9 @@ class Progress(CanvasObject):
 
         :rtype: :class:`canvasapi.progress.Progress`
         """
-        response = self._requester.request("GET", "progress/{}".format(self.id))
+        response = self._requester.request(
+            "GET", "progress/{}".format(self.id), _kwargs=combine_kwargs(**kwargs),
+        )
         response_json = response.json()
 
         super(Progress, self).set_attributes(response_json)
diff --git a/canvasapi/quiz.py b/canvasapi/quiz.py
index 272bbab..6b93253 100644
--- a/canvasapi/quiz.py
+++ b/canvasapi/quiz.py
@@ -292,7 +292,7 @@ class Quiz(CanvasObject):
         )
 
         response_json = response.json()
-        response_json.update({"course_id": self.id})
+        response_json.update({"course_id": self.course_id})
 
         return QuizGroup(self._requester, response_json)
 
diff --git a/canvasapi/quiz_group.py b/canvasapi/quiz_group.py
index 304f490..0792406 100644
--- a/canvasapi/quiz_group.py
+++ b/canvasapi/quiz_group.py
@@ -7,7 +7,7 @@ class QuizGroup(CanvasObject):
     def __str__(self):
         return "{} ({})".format(self.name, self.id)
 
-    def delete(self, id):
+    def delete(self, id, **kwargs):
         """
         Get details of the quiz group with the given id.
 
@@ -23,6 +23,7 @@ class QuizGroup(CanvasObject):
         response = self._requester.request(
             "DELETE",
             "courses/{}/quizzes/{}/groups/{}".format(self.course_id, self.quiz_id, id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return response.status_code == 204
 
@@ -37,7 +38,7 @@ class QuizGroup(CanvasObject):
         :type id: int
         :param order: A list of dictionaries containing the key 'id' of
             the question to be placed at order's index.
-        :type id: list[dict]
+        :type order: list[dict]
 
         :returns: True if the result was successful (Status code of 204)
         :rtype: bool
diff --git a/canvasapi/section.py b/canvasapi/section.py
index e154d89..a74a25b 100644
--- a/canvasapi/section.py
+++ b/canvasapi/section.py
@@ -9,7 +9,7 @@ class Section(CanvasObject):
     def __str__(self):
         return "{} - {} ({})".format(self.name, self.course_id, self.id)
 
-    def cross_list_section(self, new_course):
+    def cross_list_section(self, new_course, **kwargs):
         """
         Move the Section to another course.
 
@@ -26,11 +26,13 @@ class Section(CanvasObject):
         new_course_id = obj_or_id(new_course, "new_course", (Course,))
 
         response = self._requester.request(
-            "POST", "sections/{}/crosslist/{}".format(self.id, new_course_id)
+            "POST",
+            "sections/{}/crosslist/{}".format(self.id, new_course_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return Section(self._requester, response.json())
 
-    def decross_list_section(self):
+    def decross_list_section(self, **kwargs):
         """
         Undo cross-listing of a section.
 
@@ -40,11 +42,13 @@ class Section(CanvasObject):
         :rtype: :class:`canvasapi.section.Section`
         """
         response = self._requester.request(
-            "DELETE", "sections/{}/crosslist".format(self.id)
+            "DELETE",
+            "sections/{}/crosslist".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return Section(self._requester, response.json())
 
-    def delete(self):
+    def delete(self, **kwargs):
         """
         Delete a target section.
 
@@ -53,7 +57,9 @@ class Section(CanvasObject):
 
         :rtype: :class:`canvasapi.section.Section`
         """
-        response = self._requester.request("DELETE", "sections/{}".format(self.id))
+        response = self._requester.request(
+            "DELETE", "sections/{}".format(self.id), _kwargs=combine_kwargs(**kwargs)
+        )
         return Section(self._requester, response.json())
 
     def edit(self, **kwargs):
diff --git a/canvasapi/user.py b/canvasapi/user.py
index 78f5a62..a45fd27 100644
--- a/canvasapi/user.py
+++ b/canvasapi/user.py
@@ -14,7 +14,7 @@ class User(CanvasObject):
     def __str__(self):
         return "{} ({})".format(self.name, self.id)
 
-    def add_observee(self, observee_id):
+    def add_observee(self, observee_id, **kwargs):
         """
         Registers a user as being observed by the given user.
 
@@ -27,7 +27,9 @@ class User(CanvasObject):
         """
 
         response = self._requester.request(
-            "PUT", "users/{}/observees/{}".format(self.id, observee_id)
+            "PUT",
+            "users/{}/observees/{}".format(self.id, observee_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return User(self._requester, response.json())
 
@@ -201,7 +203,7 @@ class User(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def get_avatars(self):
+    def get_avatars(self, **kwargs):
         """
         Retrieve the possible user avatar options that can be set with the user update endpoint.
 
@@ -214,7 +216,11 @@ class User(CanvasObject):
         from canvasapi.avatar import Avatar
 
         return PaginatedList(
-            Avatar, self._requester, "GET", "users/{}/avatars".format(self.id)
+            Avatar,
+            self._requester,
+            "GET",
+            "users/{}/avatars".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
         )
 
     def get_calendar_events_for_user(self, **kwargs):
@@ -256,7 +262,7 @@ class User(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def get_color(self, asset_string):
+    def get_color(self, asset_string, **kwargs):
         """
         Return the custom colors that have been saved by this user for a given context.
 
@@ -270,11 +276,13 @@ class User(CanvasObject):
         :rtype: dict
         """
         response = self._requester.request(
-            "GET", "users/{}/colors/{}".format(self.id, asset_string)
+            "GET",
+            "users/{}/colors/{}".format(self.id, asset_string),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return response.json()
 
-    def get_colors(self):
+    def get_colors(self, **kwargs):
         """
         Return all custom colors that have been saved by this user.
 
@@ -283,7 +291,9 @@ class User(CanvasObject):
 
         :rtype: dict
         """
-        response = self._requester.request("GET", "users/{}/colors".format(self.id))
+        response = self._requester.request(
+            "GET", "users/{}/colors".format(self.id), _kwargs=combine_kwargs(**kwargs),
+        )
         return response.json()
 
     def get_communication_channels(self, **kwargs):
@@ -541,7 +551,7 @@ class User(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def get_folder(self, folder):
+    def get_folder(self, folder, **kwargs):
         """
         Returns the details for a user's folder
 
@@ -558,7 +568,9 @@ class User(CanvasObject):
         folder_id = obj_or_id(folder, "folder", (Folder,))
 
         response = self._requester.request(
-            "GET", "users/{}/folders/{}".format(self.id, folder_id)
+            "GET",
+            "users/{}/folders/{}".format(self.id, folder_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return Folder(self._requester, response.json())
 
@@ -621,7 +633,7 @@ class User(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def get_missing_submissions(self):
+    def get_missing_submissions(self, **kwargs):
         """
         Retrieve all past-due assignments for which the student does not
         have a submission.
@@ -639,6 +651,7 @@ class User(CanvasObject):
             self._requester,
             "GET",
             "users/{}/missing_submissions".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
         )
 
     def get_observees(self, **kwargs):
@@ -733,7 +746,7 @@ class User(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
-    def merge_into(self, destination_user):
+    def merge_into(self, destination_user, **kwargs):
         """
         Merge this user into another user.
 
@@ -748,12 +761,14 @@ class User(CanvasObject):
         dest_user_id = obj_or_id(destination_user, "destination_user", (User,))
 
         response = self._requester.request(
-            "PUT", "users/{}/merge_into/{}".format(self.id, dest_user_id)
+            "PUT",
+            "users/{}/merge_into/{}".format(self.id, dest_user_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         super(User, self).set_attributes(response.json())
         return self
 
-    def remove_observee(self, observee_id):
+    def remove_observee(self, observee_id, **kwargs):
         """
         Unregisters a user as being observed by the given user.
 
@@ -766,7 +781,9 @@ class User(CanvasObject):
         """
 
         response = self._requester.request(
-            "DELETE", "users/{}/observees/{}".format(self.id, observee_id)
+            "DELETE",
+            "users/{}/observees/{}".format(self.id, observee_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return User(self._requester, response.json())
 
@@ -788,6 +805,39 @@ class User(CanvasObject):
 
         return response.json()
 
+    def resolve_path(self, full_path=None, **kwargs):
+        """
+        Returns the paginated list of all of the folders in the given
+        path starting at the user root folder. Returns root folder if called
+        with no arguments.
+
+        :calls: `GET /api/v1/users/:user_id/folders/by_path/*full_path \
+        <https://canvas.instructure.com/doc/api/files.html#method.folders.resolve_path>`_
+
+        :param full_path: Full path to resolve, relative to user root.
+        :type full_path: string
+
+        :rtype: :class:`canvasapi.paginated_list.PaginatedList` of
+            :class:`canvasapi.folder.Folder`
+        """
+
+        if full_path:
+            return PaginatedList(
+                Folder,
+                self._requester,
+                "GET",
+                "users/{0}/folders/by_path/{1}".format(self.id, full_path),
+                _kwargs=combine_kwargs(**kwargs),
+            )
+        else:
+            return PaginatedList(
+                Folder,
+                self._requester,
+                "GET",
+                "users/{0}/folders/by_path".format(self.id),
+                _kwargs=combine_kwargs(**kwargs),
+            )
+
     def set_usage_rights(self, **kwargs):
         """
         Changes the usage rights for specified files that are under the user scope
@@ -806,7 +856,7 @@ class User(CanvasObject):
 
         return UsageRights(self._requester, response.json())
 
-    def show_observee(self, observee_id):
+    def show_observee(self, observee_id, **kwargs):
         """
         Gets information about an observed user.
 
@@ -819,11 +869,13 @@ class User(CanvasObject):
         """
 
         response = self._requester.request(
-            "GET", "users/{}/observees/{}".format(self.id, observee_id)
+            "GET",
+            "users/{}/observees/{}".format(self.id, observee_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return User(self._requester, response.json())
 
-    def update_color(self, asset_string, hexcode):
+    def update_color(self, asset_string, hexcode, **kwargs):
         """
         Update a custom color for this user for a given context.
 
@@ -841,8 +893,11 @@ class User(CanvasObject):
         :type hexcode: str
         :rtype: dict
         """
+        kwargs["hexcode"] = hexcode
         response = self._requester.request(
-            "PUT", "users/{}/colors/{}".format(self.id, asset_string), hexcode=hexcode
+            "PUT",
+            "users/{}/colors/{}".format(self.id, asset_string),
+            _kwargs=combine_kwargs(**kwargs),
         )
         return response.json()
 
diff --git a/dev_requirements.txt b/dev_requirements.txt
index b500ff2..e89a263 100644
--- a/dev_requirements.txt
+++ b/dev_requirements.txt
@@ -4,3 +4,4 @@ docutils==0.15.2
 pre-commit; python_version >= '3.6'
 Sphinx
 sphinx-rtd-theme
+sphinx-version-warning
\ No newline at end of file
diff --git a/docs/class-reference.rst b/docs/class-reference.rst
index 4da3343..e95326d 100644
--- a/docs/class-reference.rst
+++ b/docs/class-reference.rst
@@ -22,6 +22,7 @@ Class Reference
     course-ref
     course-epub-export-ref
     current-user-ref
+    custom-gradebook-columns-ref
     discussion-entry-ref
     discussion-topic-ref
     enrollment-ref
diff --git a/docs/conf.py b/docs/conf.py
index 66f4dbc..9d54463 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -12,15 +12,20 @@
 # All configuration values have a default; values that are commented out
 # serve to show the default.
 
-import sys
 import os
-
+import sys
 
 # If extensions (or modules to document with autodoc) are in another directory,
 # add these directories to sys.path here. If the directory is relative to the
 # documentation root, use os.path.abspath to make it absolute, like shown here.
 sys.path.insert(0, os.path.abspath('../'))
 
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+from canvasapi import __version__  # isort:skip
+
 # -- General configuration ------------------------------------------------
 # If your documentation needs a minimal Sphinx version, state it here.
 #needs_sphinx = '1.0'
@@ -33,6 +38,7 @@ autoclass_content = 'both'
 # ones.
 extensions = [
     'sphinx.ext.autodoc',
+    'versionwarning.extension'
 ]
 
 # Add any paths that contain templates here, relative to this directory.
@@ -54,16 +60,18 @@ project = u'canvasapi'
 copyright = u'2017, University of Central Florida (Center for Distributed Learning)'
 author = u'University of Central Florida (Center for Distributed Learning)'
 
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-from canvasapi import __version__
+
 # The short X.Y version.
 version = __version__
 # The full version, including alpha/beta/rc tags.
 release = __version__
 
+# sphinx-version-warning configuration
+versionwarning_messages = {
+    'develop': 'You are viewing a development build of the canvasapi docs. <a href="/en/stable">Visit the stable release docs here.</a>',
+}
+versionwarning_body_selector = "ul.wy-breadcrumbs"
+
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
 #
diff --git a/docs/custom-gradebook-columns-ref.rst b/docs/custom-gradebook-columns-ref.rst
new file mode 100644
index 0000000..faade27
--- /dev/null
+++ b/docs/custom-gradebook-columns-ref.rst
@@ -0,0 +1,13 @@
+=====================
+CustomGradebookColumn
+=====================
+
+.. autoclass:: canvasapi.custom_gradebook_columns.CustomGradebookColumn
+    :members:
+
+==========
+ColumnData
+==========
+
+.. autoclass:: canvasapi.custom_gradebook_columns.ColumnData
+    :members:
\ No newline at end of file
diff --git a/scripts/alphabetic.py b/scripts/alphabetic.py
index 7d76566..e00d56e 100644
--- a/scripts/alphabetic.py
+++ b/scripts/alphabetic.py
@@ -80,10 +80,10 @@ if __name__ == "__main__":
     error_count = main()
     if error_count:
         print(
-            "\nFound {error_count} method(s) not in alphabetical order.".format(
+            "\nFound {error_count} method(s) not in alphabetical order. 💥".format(
                 error_count=error_count
             )
         )
     else:
-        print("All methods are alphabetical!")
+        print("All methods are alphabetical! 👍")
     sys.exit(error_count > 0)
diff --git a/scripts/find_missing_kwargs.py b/scripts/find_missing_kwargs.py
new file mode 100644
index 0000000..4132594
--- /dev/null
+++ b/scripts/find_missing_kwargs.py
@@ -0,0 +1,69 @@
+import inspect
+import operator
+import os
+import sys
+
+sys.path.append(os.path.join(sys.path[0], ".."))
+
+import canvasapi  # noqa
+
+# Qualfied names of functions that are exempt from requiring kwargs
+WHITELIST = (
+    "Canvas.get_current_user",
+    "CanvasObject.set_attributes",
+    "File.download",
+    "File.get_contents",
+    "Uploader.request_upload_token",
+    "Uploader.start",
+    "Uploader.upload",
+)
+
+
+def find_missing_kwargs():
+    missing_count = 0
+
+    for _, module in inspect.getmembers(canvasapi, inspect.ismodule):
+        for class_name, theclass in inspect.getmembers(module, inspect.isclass):
+            # Only process classes in this module
+            if inspect.getmodule(theclass).__name__ != module.__name__:
+                continue
+
+            for func_name, func in inspect.getmembers(theclass, inspect.isfunction):
+                # Only process function if it is part of this class.
+                # Get function's class name from qualified name.
+                if func.__qualname__.split(".")[0] == class_name:
+                    # ignore "private" and dunder methods
+                    if func_name.startswith("_"):
+                        continue
+
+                    # ignore functions in whitelist
+                    if func.__qualname__ in WHITELIST:
+                        continue
+
+                    if not accepts_kwargs(func):
+                        print(f"{func.__qualname__} is missing **kwargs")
+                        missing_count += 1
+
+    return missing_count
+
+
+def accepts_kwargs(function):
+    """
+    Determine whether or not the provided function accepts arbritrary keyword arguments.
+
+    :param function: The function to look for **kwargs in
+    :type function: <class 'function'>
+
+    :rtype: bool
+    :returns: True if the function signature contains **kwargs. False otherwise.
+    """
+    return "**kwargs" in str(inspect.signature(function))
+
+
+if __name__ == "__main__":
+    missing_count = find_missing_kwargs()
+    if missing_count:
+        print(f"---\nFound {missing_count} functions missing **kwargs. 💥")
+    else:
+        print("All functions have **kwargs! 👍")
+    sys.exit(missing_count > 0)
diff --git a/scripts/run_black.sh b/scripts/run_black.sh
deleted file mode 100755
index de5ab37..0000000
--- a/scripts/run_black.sh
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/bash
-set -e
-
-PYTHON_VERSION=`python -c 'import sys; version=sys.version_info[:3]; print("{0}.{1}".format(*version))'`
-
-major_ver=${PYTHON_VERSION:0:1}
-minor_ver=${PYTHON_VERSION:2:3}
-
-if (( "$major_ver" >= 3 && "$minor_ver" >= 6 )); then
-	black --check canvasapi tests
-fi


Test patch:
diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh
index 60e5284..8d02b98 100755
--- a/scripts/run_tests.sh
+++ b/scripts/run_tests.sh
@@ -3,7 +3,8 @@
 coverage run -m unittest discover
 coverage report
 coverage html
+black --check canvasapi tests
 flake8 canvasapi tests
 mdl . .github
-./scripts/run_black.sh
 python scripts/alphabetic.py
+python scripts/find_missing_kwargs.py
diff --git a/tests/fixtures/course.json b/tests/fixtures/course.json
index 45b03aa..8242170 100644
--- a/tests/fixtures/course.json
+++ b/tests/fixtures/course.json
@@ -1,4 +1,54 @@
 {
+	"get_custom_columns": {
+		"method": "GET",
+		"endpoint": "courses/1/custom_gradebook_columns",
+		"data": [
+			{
+				"id": 1,
+				"title": "Column One",
+				"position": 1,
+				"hidden": false,
+				"read_only": true
+			},
+			{
+				"id": 2,
+				"title": "Column Two",
+				"position": 2,
+				"hidden": false,
+				"read_only": true
+			}
+		],
+		"status_code": 200
+	},
+	"column_data_bulk_update": {
+		"method": "PUT",
+		"endpoint": "courses/1/custom_gradebook_column_data",
+		"data": {
+			"id": 3,
+			"context_id": 1,
+			"context_type": "Course",
+			"user_id": null,
+			"tag": "columns_update",
+			"completion": null,
+			"workflow_state": "queued",
+			"updated_at": "2013-01-15T15:04:00Z",
+			"message": null,
+			"url": "https://canvas.example.edu/api/v1/progress/3"
+		},
+		"status_code": 200
+	},
+	"create_custom_column": {
+		"method": "POST",
+		"endpoint": "courses/1/custom_gradebook_columns",
+		"data": {
+			"id": 1,
+			"title": "Test Title",
+			"position": 1,
+			"hidden": false,
+			"read_only": true
+		},
+		"status_code": 200
+	},
 	"conclude": {
 		"method": "DELETE",
 		"endpoint": "courses/1",
@@ -2161,13 +2211,12 @@
 				"name": "course_files",
 				"full_name": "course_files"
 			},
-
 			{
 				"id": 3,
 				"files_count": 0,
 				"folders_count": 1,
 				"name": "Folder_Level_1",
-				"full_name": "course_files"
+				"full_name": "course_files/Folder_Level_1"
 			},
 			{
 				"id": 4,
@@ -2185,5 +2234,19 @@
 			}
 		],
 		"status_code": 200
+	},
+    "resolve_path_null": {
+		"method": "GET",
+		"endpoint": "courses/1/folders/by_path",
+		"data": [
+            {
+				"id": 2,
+				"files_count": 0,
+				"folders_count": 1,
+				"name": "course_files",
+				"full_name": "course_files"
+			}
+        ],
+		"status_code": 200
 	}
 }
diff --git a/tests/fixtures/custom_gradebook_columns.json b/tests/fixtures/custom_gradebook_columns.json
new file mode 100644
index 0000000..98dcd41
--- /dev/null
+++ b/tests/fixtures/custom_gradebook_columns.json
@@ -0,0 +1,61 @@
+{
+	"delete": {
+		"method": "DELETE",
+		"endpoint": "courses/1/custom_gradebook_columns/2",
+		"data": {
+			"id": 2,
+			"teacher_notes": false,
+			"title": "Deleted Column",
+			"position": 1,
+			"hidden": false,
+			"read_only": true
+		},
+		"status_code": 200
+	},
+	"get_column_entries": {
+		"method": "GET",
+		"endpoint": "courses/1/custom_gradebook_columns/2/data",
+		"data": [
+			{
+				"content": "Example content",
+				"user_id": 1
+			},
+			{
+				"content": "More example content",
+				"user_id": 1
+			}
+		],
+		"status_code": 200
+	},
+	"reorder_custom_columns": {
+		"method": "POST",
+		"endpoint": "courses/1/custom_gradebook_columns/reorder",
+		"data": {
+			"reorder": true,
+			"order": [1, 2, 3]
+		},
+		"status_code": 200
+	},
+	"update_custom_column": {
+		"method": "PUT",
+		"endpoint": "courses/1/custom_gradebook_columns/2",
+		"data": {
+			"id": 2,
+			"teacher_notes": false,
+			"title": "Example title",
+			"position": 1,
+			"hidden": false,
+			"read_only": true
+		},
+		"status_code": 200
+	}, 
+	"update_column_data": {
+		"method": "PUT",
+		"endpoint": "courses/1/custom_gradebook_columns/2/data/1",
+		"data": {
+			"content": "Updated content",
+			"user_id": 1
+		},
+		"status_code": 200
+	}
+}
diff --git a/tests/fixtures/group.json b/tests/fixtures/group.json
index 57af19e..c115946 100644
--- a/tests/fixtures/group.json
+++ b/tests/fixtures/group.json
@@ -983,5 +983,54 @@
 			}
 		],
 		"status_code": 200
+	},
+    "resolve_path": {
+		"method": "GET",
+		"endpoint": "groups/1/folders/by_path/Folder_Level_1/Folder_Level_2/Folder_Level_3",
+		"data": [
+            {
+				"id": 2,
+				"files_count": 0,
+				"folders_count": 1,
+				"name": "files",
+				"full_name": "files"
+			},
+			{
+				"id": 3,
+				"files_count": 0,
+				"folders_count": 1,
+				"name": "Folder_Level_1",
+				"full_name": "files/Folder_Level_1"
+			},
+			{
+				"id": 4,
+				"files_count": 0,
+				"folders_count": 1,
+				"name": "Folder_Level_2",
+				"full_name": "files/Folder_Level_1/Folder_Level_2"
+			},
+			{
+				"id": 5,
+				"files_count": 0,
+				"folders_count": 0,
+				"name": "Folder_Level_3",
+				"full_name": "files/Folder_Level_1/Folder_Level_2/Folder_Level_3"
+			}
+		],
+		"status_code": 200
+	},
+    "resolve_path_null": {
+		"method": "GET",
+		"endpoint": "groups/1/folders/by_path",
+		"data": [
+            {
+				"id": 2,
+				"files_count": 0,
+				"folders_count": 1,
+				"name": "files",
+				"full_name": "files"
+			}
+        ],
+		"status_code": 200
 	}
 }
diff --git a/tests/fixtures/quiz.json b/tests/fixtures/quiz.json
index 253e05c..5cd8f14 100644
--- a/tests/fixtures/quiz.json
+++ b/tests/fixtures/quiz.json
@@ -68,12 +68,21 @@
 		},
 		"status_code": 200
 	},
+	"get_by_id_5": {
+		"method": "GET",
+		"endpoint": "courses/1/quizzes/5",
+		"data": {
+			"id": 5,
+			"title": "Quiz Numbah Five"
+		},
+		"status_code": 200
+	},
 	"get_quiz_group": {
 		"method": "GET",
-		"endpoint": "courses/1/quizzes/1/groups/1",
+		"endpoint": "courses/1/quizzes/5/groups/10",
 		"data": {
-			"id": 1,
-			"quiz_id": 1,
+			"id": 10,
+			"quiz_id": 5,
 			"name": "Test Group",
 			"pick_count": 1,
 			"question_points": 2,
diff --git a/tests/fixtures/quiz_group.json b/tests/fixtures/quiz_group.json
index d831460..bfebccd 100644
--- a/tests/fixtures/quiz_group.json
+++ b/tests/fixtures/quiz_group.json
@@ -1,11 +1,11 @@
 {
 	"update": {
 		"method": "PUT",
-		"endpoint": "courses/1/quizzes/1/groups/1",
+		"endpoint": "courses/1/quizzes/5/groups/10",
 		"data": {"quiz_groups": [
 			{
-				"id": 1,
-				"quiz_id": 1,
+				"id": 10,
+				"quiz_id": 5,
 				"name": "Test Group",
 				"pick_count": 1,
 				"question_points": 2,
@@ -16,12 +16,12 @@
 	},
 	"delete": {
 		"method": "DELETE",
-		"endpoint": "courses/1/quizzes/1/groups/1",
+		"endpoint": "courses/1/quizzes/5/groups/10",
 		"status_code": 204
 	},
 	"reorder_question_group": {
 		"method": "POST",
-		"endpoint": "courses/1/quizzes/1/groups/1/reorder",
+		"endpoint": "courses/1/quizzes/5/groups/10/reorder",
 		"status_code": 204
 	}
 }
diff --git a/tests/fixtures/user.json b/tests/fixtures/user.json
index 2a41536..b180fc6 100644
--- a/tests/fixtures/user.json
+++ b/tests/fixtures/user.json
@@ -972,5 +972,54 @@
 			}
 		],
 		"status_code": 200
+	},
+    "resolve_path": {
+		"method": "GET",
+		"endpoint": "users/1/folders/by_path/Folder_Level_1/Folder_Level_2/Folder_Level_3",
+		"data": [
+            {
+				"id": 2,
+				"files_count": 0,
+				"folders_count": 1,
+				"name": "my_files",
+				"full_name": "my_files"
+			},
+			{
+				"id": 3,
+				"files_count": 0,
+				"folders_count": 1,
+				"name": "Folder_Level_1",
+				"full_name": "my_files/Folder_Level_1"
+			},
+			{
+				"id": 4,
+				"files_count": 0,
+				"folders_count": 1,
+				"name": "Folder_Level_2",
+				"full_name": "my_files/Folder_Level_1/Folder_Level_2"
+			},
+			{
+				"id": 5,
+				"files_count": 0,
+				"folders_count": 0,
+				"name": "Folder_Level_3",
+				"full_name": "my_files/Folder_Level_1/Folder_Level_2/Folder_Level_3"
+			}
+		],
+		"status_code": 200
+	},
+    "resolve_path_null": {
+		"method": "GET",
+		"endpoint": "users/1/folders/by_path",
+		"data": [
+            {
+				"id": 2,
+				"files_count": 0,
+				"folders_count": 1,
+				"name": "my_files",
+				"full_name": "my_files"
+			}
+        ],
+		"status_code": 200
 	}
 }
diff --git a/tests/test_canvas.py b/tests/test_canvas.py
index c21c56d..d27aa03 100644
--- a/tests/test_canvas.py
+++ b/tests/test_canvas.py
@@ -469,18 +469,18 @@ class TestCanvas(unittest.TestCase):
     def test_conversations_batch_updated_fail_on_event(self, m):
         conversation_ids = [1, 2]
         this_event = "this doesn't work"
-        result = self.canvas.conversations_batch_update(
-            event=this_event, conversation_ids=conversation_ids
-        )
-        self.assertIsInstance(result, ValueError)
+        with self.assertRaises(ValueError):
+            self.canvas.conversations_batch_update(
+                event=this_event, conversation_ids=conversation_ids
+            )
 
     def test_conversations_batch_updated_fail_on_ids(self, m):
         conversation_ids = [None] * 501
         this_event = "mark_as_read"
-        result = self.canvas.conversations_batch_update(
-            event=this_event, conversation_ids=conversation_ids
-        )
-        self.assertIsInstance(result, ValueError)
+        with self.assertRaises(ValueError):
+            self.canvas.conversations_batch_update(
+                event=this_event, conversation_ids=conversation_ids
+            )
 
     # create_calendar_event()
     def test_create_calendar_event(self, m):
diff --git a/tests/test_course.py b/tests/test_course.py
index 78027cb..8c30035 100644
--- a/tests/test_course.py
+++ b/tests/test_course.py
@@ -43,6 +43,7 @@ from canvasapi.user import User
 from canvasapi.usage_rights import UsageRights
 from canvasapi.content_migration import ContentMigration, Migrator
 from canvasapi.content_export import ContentExport
+from canvasapi.custom_gradebook_columns import CustomGradebookColumn
 from tests import settings
 from tests.util import cleanup_file, register_uris
 
@@ -71,6 +72,22 @@ class TestCourse(unittest.TestCase):
         string = str(self.course)
         self.assertIsInstance(string, str)
 
+    # column_data_bulk_update()
+    def test_column_data_bulk_update(self, m):
+        register_uris(
+            {"course": ["column_data_bulk_update"], "progress": ["course_progress"]}, m,
+        )
+        progress = self.course.column_data_bulk_update(
+            column_data=[
+                {"column_id": 1, "user_id": 1, "content": "Test Content One"},
+                {"column_id": 2, "user_id": 2, "content": "Test Content Two"},
+            ]
+        )
+        self.assertIsInstance(progress, Progress)
+        self.assertTrue(progress.context_type == "Course")
+        progress = progress.query()
+        self.assertTrue(progress.context_type == "Course")
+
     # conclude()
     def test_conclude(self, m):
         register_uris({"course": ["conclude"]}, m)
@@ -677,6 +694,15 @@ class TestCourse(unittest.TestCase):
         category_list = [category for category in response]
         self.assertIsInstance(category_list[0], GroupCategory)
 
+    # get_custom_columns()
+    def test_get_custom_columns(self, m):
+        register_uris({"course": ["get_custom_columns"]}, m)
+
+        custom_columns = self.course.get_custom_columns()
+
+        self.assertIsInstance(custom_columns, PaginatedList)
+        self.assertIsInstance(custom_columns[0], CustomGradebookColumn)
+
     # get_discussion_topic()
     def test_get_discussion_topic(self, m):
         register_uris({"course": ["get_discussion_topic"]}, m)
@@ -738,6 +764,23 @@ class TestCourse(unittest.TestCase):
         self.assertTrue(hasattr(discussion_list[0], "course_id"))
         self.assertEqual(2, len(discussion_list))
 
+    # create_custom_column()
+    def test_create_column(self, m):
+        register_uris({"course": ["create_custom_column"]}, m)
+
+        title_str = "Test Title"
+        new_column = self.course.create_custom_column(column={"title": title_str})
+
+        self.assertIsInstance(new_column, CustomGradebookColumn)
+        self.assertTrue(hasattr(new_column, "title"))
+        self.assertEqual(new_column.title, title_str)
+        self.assertTrue(hasattr(new_column, "course_id"))
+        self.assertEqual(new_column.course_id, self.course.id)
+
+    def test_create_column_fail(self, m):
+        with self.assertRaises(RequiredFieldMissing):
+            self.course.create_custom_column(column={})
+
     # create_discussion_topic()
     def test_create_discussion_topic(self, m):
         register_uris({"course": ["create_discussion_topic"]}, m)
@@ -883,6 +926,25 @@ class TestCourse(unittest.TestCase):
         self.assertTrue(hasattr(response, "id"))
         self.assertEqual(response.id, 20)
 
+    def test_create_external_tool_client_id(self, m):
+        register_uris({"external_tool": ["create_tool_course"]}, m)
+
+        response = self.course.create_external_tool(client_id="10000000000001")
+
+        self.assertIsInstance(response, ExternalTool)
+        self.assertTrue(hasattr(response, "id"))
+        self.assertEqual(response.id, 20)
+
+    def test_create_external_tool_no_params(self, m):
+        with self.assertRaises(RequiredFieldMissing):
+            self.course.create_external_tool()
+
+    def test_create_external_tool_missing_params(self, m):
+        with self.assertRaises(RequiredFieldMissing):
+            self.course.create_external_tool(
+                consumer_key="key", shared_secret="secret",
+            )
+
     # get_collaborations
     def test_get_collaborations(self, m):
         register_uris({"course": ["get_collaborations"]}, m)
@@ -1639,11 +1701,21 @@ class TestCourse(unittest.TestCase):
         folder_list = [folder for folder in folders]
         self.assertEqual(len(folder_list), 4)
         self.assertIsInstance(folder_list[0], Folder)
-        for folder_name, folder in zip(
-            ("course_files/" + full_path).split("/"), folders
-        ):
+        folder_names = ("course_files/" + full_path).split("/")
+        for folder_name, folder in zip(folder_names, folders):
             self.assertEqual(folder_name, folder.name)
 
+    # resolve_path() with null input
+    def test_resolve_path_null(self, m):
+        register_uris({"course": ["resolve_path_null"]}, m)
+
+        # test with null input
+        root_folder = self.course.resolve_path()
+        root_folder_list = [folder for folder in root_folder]
+        self.assertEqual(len(root_folder_list), 1)
+        self.assertIsInstance(root_folder_list[0], Folder)
+        self.assertEqual("course_files", root_folder_list[0].name)
+
 
 @requests_mock.Mocker()
 class TestCourseNickname(unittest.TestCase):
diff --git a/tests/test_custom_gradebook_columns.py b/tests/test_custom_gradebook_columns.py
new file mode 100644
index 0000000..6692056
--- /dev/null
+++ b/tests/test_custom_gradebook_columns.py
@@ -0,0 +1,121 @@
+import unittest
+from urllib.parse import quote
+
+import requests
+import requests_mock
+
+from canvasapi import Canvas
+from canvasapi.custom_gradebook_columns import ColumnData
+from canvasapi.paginated_list import PaginatedList
+from tests import settings
+from tests.util import register_uris
+
+
+@requests_mock.Mocker()
+class TestCustomGradebookColumn(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", "get_custom_columns"]}, m)
+
+            self.course = self.canvas.get_course(1)
+            self.gradebook_column = self.course.get_custom_columns()[1]
+
+    # __str__()
+    def test__str__(self, m):
+        string = str(self.gradebook_column)
+        self.assertIsInstance(string, str)
+
+    # delete()
+    def test_delete(self, m):
+        register_uris({"custom_gradebook_columns": ["delete"]}, m)
+
+        success = self.gradebook_column.delete()
+        self.assertTrue(success)
+
+    # get_column_entries()
+    def test_get_column_entries(self, m):
+        register_uris({"custom_gradebook_columns": ["get_column_entries"]}, m)
+
+        column_entries = self.gradebook_column.get_column_entries()
+
+        self.assertIsInstance(column_entries, PaginatedList)
+        self.assertIsInstance(column_entries[0], ColumnData)
+        self.assertTrue(hasattr(column_entries[0], "gradebook_column_id"))
+        self.assertEqual(
+            column_entries[0].gradebook_column_id, self.gradebook_column.id
+        )
+
+    # reorder_custom_columns()
+    def test_reorder_custom_columns(self, m):
+        def custom_matcher(request):
+            match_text = "1,2,3"
+            if request.text == "order={}".format(quote(match_text)):
+                resp = requests.Response()
+                resp._content = b'{"reorder": true, "order": [1, 2, 3]}'
+                resp.status_code = 200
+                return resp
+
+        m.add_matcher(custom_matcher)
+
+        order = [1, 2, 3]
+        columns = self.gradebook_column.reorder_custom_columns(order=order)
+        self.assertTrue(columns)
+
+    def test_reorder_custom_columns_tuple(self, m):
+        register_uris({"custom_gradebook_columns": ["reorder_custom_columns"]}, m)
+
+        order = (1, 2, 3)
+        columns = self.gradebook_column.reorder_custom_columns(order=order)
+        self.assertTrue(columns)
+
+    def test_reorder_custom_columns_comma_separated_string(self, m):
+        register_uris({"custom_gradebook_columns": ["reorder_custom_columns"]}, m)
+
+        order = "1,2,3"
+        gradebook = self.gradebook_column.reorder_custom_columns(order=order)
+        self.assertTrue(gradebook)
+
+    def test_reorder_custom_columns_invalid_input(self, m):
+        order = "invalid string"
+        with self.assertRaises(ValueError):
+            self.gradebook_column.reorder_custom_columns(order=order)
+
+    # update_custom_column()
+    def test_update_custom_column(self, m):
+        register_uris({"custom_gradebook_columns": ["update_custom_column"]}, m)
+
+        new_title = "Example title"
+        self.gradebook_column.update_custom_column(column={"title": new_title})
+        self.assertEqual(self.gradebook_column.title, new_title)
+
+
+@requests_mock.Mocker()
+class TestColumnData(unittest.TestCase):
+    def setUp(self):
+        self.canvas = Canvas(settings.BASE_URL, settings.API_KEY)
+
+        with requests_mock.Mocker() as m:
+            requires = {
+                "course": ["get_by_id", "get_custom_columns"],
+                "custom_gradebook_columns": ["get_column_entries"],
+            }
+            register_uris(requires, m)
+
+            self.course = self.canvas.get_course(1)
+            self.gradebook_column = self.course.get_custom_columns()[1]
+            self.data = self.gradebook_column.get_column_entries()[1]
+
+    # __str__()
+    def test__str__(self, m):
+        string = str(self.data)
+        self.assertIsInstance(string, str)
+
+    # update_column_data()
+    def test_update_column_data(self, m):
+        register_uris({"custom_gradebook_columns": ["update_column_data"]}, m)
+
+        new_content = "Updated content"
+        self.data.update_column_data(column_data={"content": new_content})
+        self.assertEqual(self.data.content, new_content)
diff --git a/tests/test_group.py b/tests/test_group.py
index 5fa398a..705dff9 100644
--- a/tests/test_group.py
+++ b/tests/test_group.py
@@ -578,6 +578,30 @@ class TestGroup(unittest.TestCase):
 
         self.assertEqual(2, len(licenses))
 
+    # resolve_path()
+    def test_resolve_path(self, m):
+        register_uris({"group": ["resolve_path"]}, m)
+
+        full_path = "Folder_Level_1/Folder_Level_2/Folder_Level_3"
+        folders = self.group.resolve_path(full_path)
+        folder_list = [folder for folder in folders]
+        self.assertEqual(len(folder_list), 4)
+        self.assertIsInstance(folder_list[0], Folder)
+        folder_names = ("files/" + full_path).split("/")
+        for folder_name, folder in zip(folder_names, folders):
+            self.assertEqual(folder_name, folder.name)
+
+    # resolve_path() with null input
+    def test_resolve_path_null(self, m):
+        register_uris({"group": ["resolve_path_null"]}, m)
+
+        # test with null input
+        root_folder = self.group.resolve_path()
+        root_folder_list = [folder for folder in root_folder]
+        self.assertEqual(len(root_folder_list), 1)
+        self.assertIsInstance(root_folder_list[0], Folder)
+        self.assertEqual("files", root_folder_list[0].name)
+
 
 @requests_mock.Mocker()
 class TestGroupMembership(unittest.TestCase):
diff --git a/tests/test_module.py b/tests/test_module.py
index 9f434d1..010c057 100644
--- a/tests/test_module.py
+++ b/tests/test_module.py
@@ -86,8 +86,9 @@ class TestModule(unittest.TestCase):
         register_uris({"module": ["create_module_item"]}, m)
 
         module_item = self.module.create_module_item(
-            module_item={"type": "Page", "content_id": 1}
+            module_item={"type": "Assignment", "content_id": 1}
         )
+
         self.assertIsInstance(module_item, ModuleItem)
         self.assertTrue(hasattr(module_item, "course_id"))
         self.assertEqual(module_item.course_id, self.course.id)
@@ -98,7 +99,13 @@ class TestModule(unittest.TestCase):
 
     def test_create_module_item_fail2(self, m):
         with self.assertRaises(RequiredFieldMissing):
-            self.module.create_module_item(module_item={"type": "Page"})
+            self.module.create_module_item(module_item={"type": "Assignment"})
+
+    def test_create_module_item_unrequired_success(self, m):
+        register_uris({"module": ["create_module_item"]}, m)
+
+        module_item = self.module.create_module_item(module_item={"type": "SubHeader"})
+        self.assertIsInstance(module_item, ModuleItem)
 
     # __str__
     def test__str__(self, m):
diff --git a/tests/test_quiz.py b/tests/test_quiz.py
index 1c77972..b2929eb 100644
--- a/tests/test_quiz.py
+++ b/tests/test_quiz.py
@@ -87,12 +87,16 @@ class TestQuiz(unittest.TestCase):
 
     # get_quiz_group()
     def test_get_quiz_group(self, m):
-        register_uris({"quiz": ["get_quiz_group"]}, m)
+        register_uris({"quiz": ["get_by_id_5", "get_quiz_group"]}, m)
 
-        result = self.quiz.get_quiz_group(1)
+        quiz = self.course.get_quiz(5)
+
+        result = quiz.get_quiz_group(10)
         self.assertIsInstance(result, QuizGroup)
-        self.assertEqual(result.id, 1)
-        self.assertEqual(result.quiz_id, 1)
+        self.assertEqual(result.id, 10)
+        self.assertEqual(result.quiz_id, 5)
+        self.assertTrue(hasattr(result, "course_id"))
+        self.assertEqual(result.course_id, 1)
 
     # create_question_group()
     def test_create_question_group(self, m):
diff --git a/tests/test_quiz_group.py b/tests/test_quiz_group.py
index fe13846..f43e65e 100644
--- a/tests/test_quiz_group.py
+++ b/tests/test_quiz_group.py
@@ -15,11 +15,11 @@ class TestQuizGroup(unittest.TestCase):
 
         with requests_mock.Mocker() as m:
             register_uris(
-                {"course": ["get_by_id"], "quiz": ["get_by_id", "get_quiz_group"]}, m
+                {"course": ["get_by_id"], "quiz": ["get_by_id_5", "get_quiz_group"]}, m
             )
 
             self.course = self.canvas.get_course(1)
-            self.quiz_group = self.course.get_quiz(1).get_quiz_group(1)
+            self.quiz_group = self.course.get_quiz(5).get_quiz_group(10)
 
     # __str__()
     def test__str__(self, m):
@@ -31,13 +31,13 @@ class TestQuizGroup(unittest.TestCase):
         register_uris({"quiz_group": ["update"]}, m)
 
         quiz_group = [{"name": "Test Group", "pick_count": 1, "question_points": 2}]
-        result = self.quiz_group.update(1, quiz_group)
+        result = self.quiz_group.update(10, quiz_group)
 
         self.assertIsInstance(result, bool)
         self.assertTrue(result)
 
-        self.assertEqual(self.quiz_group.id, 1)
-        self.assertEqual(self.quiz_group.quiz_id, 1)
+        self.assertEqual(self.quiz_group.id, 10)
+        self.assertEqual(self.quiz_group.quiz_id, 5)
         self.assertEqual(self.quiz_group.name, quiz_group[0].get("name"))
         self.assertEqual(self.quiz_group.pick_count, quiz_group[0].get("pick_count"))
         self.assertEqual(
@@ -50,7 +50,7 @@ class TestQuizGroup(unittest.TestCase):
         quiz_group = []
 
         with self.assertRaises(ValueError):
-            self.quiz_group.update(1, quiz_group)
+            self.quiz_group.update(10, quiz_group)
 
     def test_update_incorrect_param(self, m):
         register_uris({"quiz_group": ["update"]}, m)
@@ -58,7 +58,7 @@ class TestQuizGroup(unittest.TestCase):
         quiz_group = [1]
 
         with self.assertRaises(ValueError):
-            self.quiz_group.update(1, quiz_group)
+            self.quiz_group.update(10, quiz_group)
 
     def test_update_incorrect_dict(self, m):
         register_uris({"quiz_group": ["update"]}, m)
@@ -66,13 +66,13 @@ class TestQuizGroup(unittest.TestCase):
         quiz_group = [{}]
 
         with self.assertRaises(RequiredFieldMissing):
-            self.quiz_group.update(1, quiz_group)
+            self.quiz_group.update(10, quiz_group)
 
     # delete_question_group()
     def test_delete(self, m):
         register_uris({"quiz_group": ["delete"]}, m)
 
-        result = self.quiz_group.delete(1)
+        result = self.quiz_group.delete(10)
 
         self.assertTrue(result)
 
@@ -81,7 +81,7 @@ class TestQuizGroup(unittest.TestCase):
         register_uris({"quiz_group": ["reorder_question_group"]}, m)
 
         newOrdering = [{"id": 2}, {"id": 1, "type": "question"}]
-        result = self.quiz_group.reorder_question_group(1, newOrdering)
+        result = self.quiz_group.reorder_question_group(10, newOrdering)
 
         self.assertTrue(result)
 
@@ -91,7 +91,7 @@ class TestQuizGroup(unittest.TestCase):
         order = []
 
         with self.assertRaises(ValueError):
-            self.quiz_group.reorder_question_group(1, order)
+            self.quiz_group.reorder_question_group(10, order)
 
     def test_reorderquestion_group_incorrect_param(self, m):
         register_uris({"quiz_group": ["reorder_question_group"]}, m)
@@ -99,7 +99,7 @@ class TestQuizGroup(unittest.TestCase):
         order = [1]
 
         with self.assertRaises(ValueError):
-            self.quiz_group.reorder_question_group(1, order)
+            self.quiz_group.reorder_question_group(10, order)
 
     def test_reorderquestion_group_incorrect_dict(self, m):
         register_uris({"quiz_group": ["reorder_question_group"]}, m)
@@ -107,4 +107,4 @@ class TestQuizGroup(unittest.TestCase):
         order = [{"something": 2}]
 
         with self.assertRaises(ValueError):
-            self.quiz_group.reorder_question_group(1, order)
+            self.quiz_group.reorder_question_group(10, order)
diff --git a/tests/test_user.py b/tests/test_user.py
index 90e4aea..d46e26f 100644
--- a/tests/test_user.py
+++ b/tests/test_user.py
@@ -540,6 +540,31 @@ class TestUser(unittest.TestCase):
 
         self.assertEqual(2, len(licenses))
 
+    # resolve_path()
+    def test_resolve_path(self, m):
+        register_uris({"user": ["resolve_path"]}, m)
+
+        full_path = "Folder_Level_1/Folder_Level_2/Folder_Level_3"
+        folders = self.user.resolve_path(full_path)
+        folder_list = [folder for folder in folders]
+        self.assertEqual(len(folder_list), 4)
+        self.assertIsInstance(folder_list[0], Folder)
+
+        folder_names = ("my_files/" + full_path).split("/")
+        for folder_name, folder in zip(folder_names, folders):
+            self.assertEqual(folder_name, folder.name)
+
+    # resolve_path() with null input
+    def test_resolve_path_null(self, m):
+        register_uris({"user": ["resolve_path_null"]}, m)
+
+        # test with null input
+        root_folder = self.user.resolve_path()
+        root_folder_list = [folder for folder in root_folder]
+        self.assertEqual(len(root_folder_list), 1)
+        self.assertIsInstance(root_folder_list[0], Folder)
+        self.assertEqual("my_files", root_folder_list[0].name)
+
 
 @requests_mock.Mocker()
 class TestUserDisplay(unittest.TestCase):
diff --git a/tests/util.py b/tests/util.py
index c72d009..32280a1 100644
--- a/tests/util.py
+++ b/tests/util.py
@@ -21,7 +21,7 @@ def register_uris(requirements, requests_mocker, base_url=None):
         try:
             with open("tests/fixtures/{}.json".format(fixture)) as file:
                 data = json.loads(file.read())
-        except IOError:
+        except (IOError, ValueError):
             raise ValueError("Fixture {}.json contains invalid JSON.".format(fixture))
 
         if not isinstance(objects, list):