Repository: ucfopen/canvasapi

Issue:
Various endpoints
**API Token scopes**
- [x] List Scopes endpoint: https://canvas.instructure.com/doc/api/api_token_scopes.html#method.scopes_api.index

**Account Notifications**
- [x] Show a global notification: https://canvas.instructure.com/doc/api/account_notifications.html#method.account_notifications.show

- [x] Update a global notification: https://canvas.instructure.com/doc/api/account_notifications.html#method.account_notifications.update

**Account Reports**
- [x] Start A report: https://canvas.instructure.com/doc/api/account_reports.html#method.account_reports.create

- [x]  Status of a Report: https://canvas.instructure.com/doc/api/account_reports.html#method.account_reports.show

- [x] Delete a Report: https://canvas.instructure.com/doc/api/account_reports.html#method.account_reports.destroy



Discussion:

Patch:
diff --git a/canvasapi/account.py b/canvasapi/account.py
index 2f91d6b..d9b5524 100644
--- a/canvasapi/account.py
+++ b/canvasapi/account.py
@@ -343,7 +343,35 @@ class Account(CanvasObject):
             "accounts/{}/account_notifications".format(self.id),
             _kwargs=combine_kwargs(**kwargs),
         )
-        return AccountNotification(self._requester, response.json())
+
+        response_json = response.json()
+        response_json.update({"account_id": self.id})
+
+        return AccountNotification(self._requester, response_json)
+
+    def create_report(self, report_type, **kwargs):
+        """
+        Generates a report of a specific type for the account.
+
+        :calls: `POST /api/v1/accounts/:account_id/reports/:report \
+        <https://canvas.instructure.com/doc/api/account_reports.html#method.account_reports.create>`_
+
+        :param report_type: The type of report.
+        :type report_type: str
+
+        :rtype: :class:`canvasapi.account.AccountReport`
+        """
+
+        response = self._requester.request(
+            "POST",
+            "accounts/{}/reports/{}".format(self.id, report_type),
+            _kwargs=combine_kwargs(**kwargs),
+        )
+
+        response_json = response.json()
+        response_json.update({"account_id": self.id})
+
+        return AccountReport(self._requester, response_json)
 
     def create_role(self, label, **kwargs):
         """
@@ -1025,6 +1053,30 @@ class Account(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
+    def get_global_notification(self, notification_id, **kwargs):
+        """
+        Returns a global notification for the current user.
+
+        :calls: `GET /api/v1/accounts/:account_id/account_notifications/:id \
+        <https://canvas.instructure.com/doc/api/account_notifications.html#method.account_notifications.show>`_
+
+        :param notification_id: The notification ID of the desired notification.
+        :type notification_id: `int`
+
+        :rtype: :class:`canvasapi.account.AccountNotification`
+        """
+
+        response = self._requester.request(
+            "GET",
+            "accounts/{}/account_notifications/{}".format(self.id, notification_id),
+            _kwargs=combine_kwargs(**kwargs),
+        )
+
+        response_json = response.json()
+        response_json.update({"account_id": self.id})
+
+        return AccountNotification(self._requester, response_json)
+
     def get_grading_periods(self, **kwargs):
         """
         Return a list of grading periods for the associated account.
@@ -1121,6 +1173,7 @@ class Account(CanvasObject):
             self._requester,
             "GET",
             "accounts/{}/reports/{}".format(self.id, report_type),
+            {"account_id": self.id},
         )
 
     def get_migration_systems(self, **kwargs):
@@ -1218,6 +1271,32 @@ class Account(CanvasObject):
 
         return OutcomeImport(self._requester, response_json)
 
+    def get_report(self, report_type, report_id, **kwargs):
+        """
+        Return a report which corresponds to the given report type and ID.
+
+        :calls: `GET /api/v1/accounts/:account_id/reports/:report/:id \
+        <https://canvas.instructure.com/doc/api/account_reports.html#method.account_reports.show>`_
+
+        :param report_type: The type of the report which is being looked up.
+        :type report_type: `string`
+
+        :param report_id: The id for the report which is being looked up.
+        :type report_id: `int`
+
+        :rtype: :class:`canvasapi.account.AccountReport`
+        """
+        response = self._requester.request(
+            "GET",
+            "accounts/{}/reports/{}/{}".format(self.id, report_type, report_id),
+            _kwargs=combine_kwargs(**kwargs),
+        )
+
+        response_json = response.json()
+        response_json.update({"account_id": self.id})
+
+        return AccountReport(self._requester, response_json)
+
     def get_reports(self):
         """
         Return a list of reports for the current context.
@@ -1229,7 +1308,11 @@ class Account(CanvasObject):
             :class:`canvasapi.account.AccountReport`
         """
         return PaginatedList(
-            AccountReport, self._requester, "GET", "accounts/{}/reports".format(self.id)
+            AccountReport,
+            self._requester,
+            "GET",
+            "accounts/{}/reports".format(self.id),
+            {"account_id": self.id},
         )
 
     def get_role(self, role):
@@ -1324,6 +1407,25 @@ class Account(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
+    def get_scopes(self, **kwargs):
+        """
+        Retrieve a paginated list of scopes.
+
+        :calls: `GET /api/v1/accounts/:account_id/scopes \
+        <https://canvas.instructure.com/doc/api/api_token_scopes.html#method.scopes_api.index>`_
+
+        :rtype: :class:`canvasapi.paginated_list.PaginatedList` of :class:`canvasapi.scope.Scope`
+        """
+        from canvasapi.scope import Scope
+
+        return PaginatedList(
+            Scope,
+            self._requester,
+            "GET",
+            "accounts/{}/scopes".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
+        )
+
     def get_single_grading_standard(self, grading_standard_id, **kwargs):
         """
         Get a single grading standard from the account.
@@ -1761,15 +1863,67 @@ class Account(CanvasObject):
 
 @python_2_unicode_compatible
 class AccountNotification(CanvasObject):
-    def __str__(self):  # pragma: no cover
-        return "{}".format(self.subject)
+    def __str__(self):
+        return "{} ({})".format(self.subject, self.id)
+
+    def update_global_notification(self, account_notification, **kwargs):
+        """
+        Updates a global notification.
+
+        :calls: `PUT /api/v1/accounts/:account_id/account_notifications/:id \
+        <https://canvas.instructure.com/doc/api/account_notifications.html#method.account_notifications.update>`_
+
+        :param account_notification: The notification to update with.
+        :type account_notification: dict
+
+        :rtype: :class:`canvasapi.account.AccountNotification`
+        """
+        required_key_list = ["subject", "message", "start_at", "end_at"]
+        required_keys_present = all(
+            x in account_notification for x in required_key_list
+        )
+
+        if isinstance(account_notification, dict) and required_keys_present:
+            kwargs["account_notification"] = account_notification
+        else:
+            raise RequiredFieldMissing(
+                (
+                    "account_notification must be a dictionary with keys "
+                    "'subject', 'message', 'start_at', and 'end_at'."
+                )
+            )
+
+        response = self._requester.request(
+            "PUT",
+            "accounts/{}/account_notifications/{}".format(self.account_id, self.id),
+            _kwargs=combine_kwargs(**kwargs),
+        )
+
+        return AccountNotification(self._requester, response.json())
 
 
 @python_2_unicode_compatible
 class AccountReport(CanvasObject):
-    def __str__(self):  # pragma: no cover
+    def __str__(self):
         return "{} ({})".format(self.report, self.id)
 
+    def delete_report(self, **kwargs):
+        """
+        Delete this report.
+
+        :calls: `DELETE /api/v1/accounts/:account_id/reports/:report/:id \
+        <https://canvas.instructure.com/doc/api/account_reports.html#method.account_reports.destroy>`_
+
+        :rtype: :class:`canvasapi.account.AccountReport`
+        """
+        response = self._requester.request(
+            "DELETE",
+            "accounts/{}/reports/{}/{}".format(self.account_id, self.report, self.id),
+            _kwargs=combine_kwargs(**kwargs),
+        )
+
+        return AccountReport(self._requester, response.json())
+
 
 @python_2_unicode_compatible
 class Role(CanvasObject):
diff --git a/canvasapi/scope.py b/canvasapi/scope.py
new file mode 100644
index 0000000..4b410fa
--- /dev/null
+++ b/canvasapi/scope.py
@@ -0,0 +1,11 @@
+from __future__ import absolute_import, division, print_function, unicode_literals
+
+from six import python_2_unicode_compatible
+
+from canvasapi.canvas_object import CanvasObject
+
+
+@python_2_unicode_compatible
+class Scope(CanvasObject):
+    def __str__(self):
+        return "{}".format(self.resource)
diff --git a/docs/class-reference.rst b/docs/class-reference.rst
index 938a518..9252a1d 100644
--- a/docs/class-reference.rst
+++ b/docs/class-reference.rst
@@ -44,6 +44,7 @@ Class Reference
     quiz-ref
     quiz-group-ref
     rubric-ref
+    scope-ref
     section-ref
     submission-ref
     tab-ref
diff --git a/docs/scope-ref.rst b/docs/scope-ref.rst
new file mode 100644
index 0000000..ec8d23b
--- /dev/null
+++ b/docs/scope-ref.rst
@@ -0,0 +1,6 @@
+=====
+Scope
+=====
+
+.. autoclass:: canvasapi.scope.Scope
+    :members:


Test patch:
diff --git a/tests/fixtures/account.json b/tests/fixtures/account.json
index f2b3441..1c4302f 100644
--- a/tests/fixtures/account.json
+++ b/tests/fixtures/account.json
@@ -321,6 +321,20 @@
 		},
 		"status_code": 200
 	},
+	"get_notification": {
+		"method": "GET",
+		"endpoint": "accounts/1/account_notifications/1",
+		"data":{
+  			"subject": "Attention Students",
+  			"message": "This is a test of the notification system.",
+  			"start_at": "2013-08-28T23:59:00-06:00",
+  			"end_at": "2013-08-29T23:59:00-06:00",
+  			"icon": "information",
+  			"roles": ["StudentEnrollment"],
+  			"role_ids": [1]
+		},
+		"status_code": 200
+	},
 	"get_grading_periods": {
         "method": "GET",
         "endpoint": "accounts/1/grading_periods",
@@ -459,6 +473,96 @@
 		},
 		"status_code": 200
 	},
+	"create_report":{
+		"method": "POST",
+		"endpoint": "accounts/1/reports/zero_activity_csv",
+		"data": {
+        	"title": "Zero Activity",
+        	"parameters": {
+            	"enrollment_term_id": {
+                	"required": false,
+                	"description": "The canvas id of the term to get grades from"
+            	},
+            	"start_at": {
+                	"required": false,
+                	"description": "The first date in the date range, the second date is the time the report is run."
+            	},
+            	"course_id": {
+                	"required": false,
+                	"description": "The course to report on"
+            	}
+        },
+        "report": "zero_activity_csv",
+        "last_run": null
+		},
+		"status_code": 200
+	},
+	"delete_report":{
+		"method": "DELETE",
+		"endpoint": "accounts/1/reports/zero_activity_csv/1",
+		"data": {
+    		"id": 1,
+    		"progress": 100,
+    		"parameters": {
+    		    "extra_text": "Term: All Terms;"
+    		},
+    		"current_line": 1,
+    		"status": "deleted",
+    		"report": "zero_activity_csv",
+    		"created_at": "2019-08-13T15:23:15-04:00",
+    		"started_at": "2019-08-13T15:23:17-04:00",
+    		"ended_at": "2019-08-13T15:23:17-04:00",
+    		"file_url": "xxxxxxxxxxxxxxxxxxxxxxx",
+    		"attachment": {
+    		    "id": 1,
+    		    "uuid": "CCVh71fQEPsDqc4VOoGBr5llCbzAeLEpBcWcAe88",
+    		    "folder_id": 1,
+    		    "display_name": "zero_activity_csv_xxxxxxxxxxxxxxxxxxxxxxx.csv",
+    		    "filename": "zero_activity_csv_xxxxxxxxxxxxxxxxxxxxxxx.csv",
+    		    "workflow_state": "processed",
+    		    "upload_status": "success",
+    		    "content-type": "text/csv",
+    		    "url": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+    		    "size": 100,
+    		    "created_at": "2019-08-13T19:23:17Z",
+    		    "updated_at": "2019-08-13T19:23:17Z",
+    		    "unlock_at": null,
+    		    "locked": false,
+    		    "hidden": false,
+    		    "lock_at": null,
+    		    "hidden_for_user": false,
+    		    "thumbnail_url": null,
+    		    "modified_at": "2019-08-13T19:23:17Z",
+    		    "mime_class": "file",
+    		    "media_entry_id": null,
+    		    "locked_for_user": false
+    			}
+		}
+	},
+	"get_report":{
+		"method": "GET",
+		"endpoint": "accounts/1/reports/zero_activity_csv/1",
+		"data":{
+        	"title": "Zero Activity",
+        	"parameters": {
+            	"enrollment_term_id": {
+                	"required": false,
+                	"description": "The canvas id of the term to get grades from"
+            	},
+            	"start_at": {
+                	"required": false,
+                	"description": "The first date in the date range, the second date is the time the report is run."
+            	},
+            	"course_id": {
+                	"required": false,
+                	"description": "The course to report on"
+            	}
+        },
+        "report": "zero_activity_csv",
+        "last_run": null
+		},
+		"status_code": 200
+	},
 	"reports": {
 		"method": "GET",
 		"endpoint": "accounts/1/reports",
@@ -651,6 +755,17 @@
 		"data": {},
 		"status_code": 200
 	},
+	"update_notification": {
+		"method": "PUT",
+		"endpoint": "accounts/1/account_notifications/1",
+		"data": {
+            "subject": "subject",
+            "message": "Message",
+            "start_at": "2015-04-01T00:00:00Z",
+            "end_at": "2018-04-01T00:00:00Z"
+		},
+		"status_code": 200
+	},
 	"create_group_category": {
 		"method": "POST",
 		"endpoint": "accounts/1/group_categories",
@@ -1195,6 +1310,31 @@
 		  },
 		"status_code": 200
 	},
+	"get_scopes": {
+	"method": "GET",
+	"endpoint": "accounts/1/scopes",
+	"data": [
+		{
+        	"controller": "users",
+        	"action": "settings",
+        	"verb": "PUT",
+        	"path": "/api/v1/users/:id/settings",
+        	"scope": "url:PUT|/api/v1/users/:id/settings",
+        	"resource": "users",
+        	"resource_name": "Users"
+		},
+		{
+        	"controller": "course_nicknames",
+        	"action": "show",
+        	"verb": "GET",
+        	"path": "/api/v1/users/self/course_nicknames/:course_id",
+        	"scope": "url:GET|/api/v1/users/self/course_nicknames/:course_id",
+        	"resource": "users",
+        	"resource_name": "Users"	
+		}
+	],
+	"status_code": 200
+	},
 	"create_content_migration": {
 		"method": "POST",
 		"endpoint": "accounts/1/content_migrations",
diff --git a/tests/test_account.py b/tests/test_account.py
index 072213e..098d6e5 100644
--- a/tests/test_account.py
+++ b/tests/test_account.py
@@ -30,6 +30,7 @@ from canvasapi.outcome import OutcomeGroup, OutcomeLink
 from canvasapi.outcome_import import OutcomeImport
 from canvasapi.paginated_list import PaginatedList
 from canvasapi.rubric import Rubric
+from canvasapi.scope import Scope
 from canvasapi.sis_import import SisImport
 from canvasapi.user import User
 from canvasapi.content_migration import ContentMigration, Migrator
@@ -55,22 +56,16 @@ class TestAccount(unittest.TestCase):
         string = str(self.account)
         self.assertIsInstance(string, str)
 
-    # close_notification_for_user()
-    def test_close_notification_for_user_id(self, m):
-        register_uris({"account": ["close_notification"]}, m)
+    # get_global_notification()
+    def test_get_global_notification(self, m):
+        register_uris({"account": ["get_notification"]}, m)
 
-        user_id = self.user.id
         notif_id = 1
-        closed_notif = self.account.close_notification_for_user(user_id, notif_id)
+        notification = self.account.get_global_notification(notif_id)
 
-        self.assertIsInstance(closed_notif, AccountNotification)
-        self.assertTrue(hasattr(closed_notif, "subject"))
-
-    def test_close_notification_for_user_obj(self, m):
-        register_uris({"account": ["close_notification"]}, m)
-
-        notif_id = 1
-        self.account.close_notification_for_user(self.user, notif_id)
+        self.assertIsInstance(notification, AccountNotification)
+        self.assertTrue(hasattr(notification, "subject"))
+        self.assertEqual(notification.subject, "Attention Students")
 
     # create_account()
     def test_create_account(self, m):
@@ -211,6 +206,86 @@ class TestAccount(unittest.TestCase):
         self.assertIsInstance(tool_list[0], ExternalTool)
         self.assertEqual(len(tool_list), 4)
 
+    # create_report
+    def test_create_report(self, m):
+        required = {"account": ["create_report"]}
+        register_uris(required, m)
+
+        report_template = {
+            "title": "Zero Activity",
+            "parameters": {
+                "enrollment_term_id": {
+                    "required": False,
+                    "description": "The canvas id of the term to get grades from",
+                },
+                "start_at": {
+                    "required": False,
+                    "description": "The first date in the date range",
+                },
+                "course_id": {
+                    "required": False,
+                    "description": "The course to report on",
+                },
+            },
+            "report": "zero_activity_csv",
+            "last_run": "null",
+        }
+
+        report = self.account.create_report(
+            "zero_activity_csv", parameters=report_template
+        )
+
+        self.assertIsInstance(report, AccountReport)
+        self.assertTrue(hasattr(report, "title"))
+        self.assertEqual(report.title, "Zero Activity")
+
+    # delete_report
+    def test_delete_report(self, m):
+        required = {"account": ["delete_report"]}
+        register_uris(required, m)
+
+        self.account_report = AccountReport(
+            self.canvas._Canvas__requester,
+            {
+                "title": "Zero Activity",
+                "parameters": {
+                    "enrollment_term_id": {
+                        "required": False,
+                        "description": "The canvas id of the term to get grades from",
+                    },
+                    "start_at": {
+                        "required": False,
+                        "description": "The first date in the date range",
+                    },
+                    "course_id": {
+                        "required": False,
+                        "description": "The course to report on",
+                    },
+                },
+                "report": "zero_activity_csv",
+                "last_run": "null",
+                "account_id": 1,
+                "id": 1,
+            },
+        )
+
+        report = self.account_report.delete_report()
+
+        self.assertIsInstance(report, AccountReport)
+        self.assertTrue(hasattr(report, "status"))
+        self.assertEqual(report.status, "deleted")
+
+    # get_report
+    def test_get_report(self, m):
+        required = {"account": ["get_report"]}
+        register_uris(required, m)
+
+        report = self.account.get_report("zero_activity_csv", 1)
+
+        self.assertIsInstance(report, AccountReport)
+        self.assertTrue(hasattr(report, "title"))
+        self.assertEqual(report.title, "Zero Activity")
+
     # get_index_of_reports()
     def test_get_index_of_reports(self, m):
         required = {"account": ["report_index", "report_index_page_2"]}
@@ -1178,6 +1253,144 @@ class TestAccount(unittest.TestCase):
         self.assertTrue(self.account.delete_grading_period(1))
         self.assertTrue(self.account.delete_grading_period(self.grading_period))
 
+    # get_scopes()
+    def test_get_scopes(self, m):
+        register_uris({"account": ["get_scopes"]}, m)
+
+        scopes = self.account.get_scopes()
+        scope_list = [scope for scope in scopes]
+
+        self.assertEqual(len(list(scopes)), 2)
+        self.assertTrue(isinstance(scopes, PaginatedList))
+        self.assertTrue(isinstance(scope_list[0], Scope))
+
+        self.assertEqual(scope_list[0].resource, "users")
+        self.assertEqual(scope_list[1].resource, "users")
+
+        self.assertEqual(scope_list[0].verb, "PUT")
+        self.assertEqual(scope_list[1].verb, "GET")
+
+    # close_notification
+    def test_close_notification_for_user_id(self, m):
+        register_uris({"account": ["close_notification"]}, m)
+
+        user_id = self.user.id
+        notif_id = 1
+        closed_notif = self.account.close_notification_for_user(user_id, notif_id)
+
+        self.assertIsInstance(closed_notif, AccountNotification)
+        self.assertTrue(hasattr(closed_notif, "subject"))
+
+    # update_global_notification()
+    def test_update_global_notification(self, m):
+        register_uris({"account": ["update_notification"]}, m)
+
+        self.AccountNotification = AccountNotification(
+            self.canvas._Canvas__requester,
+            {
+                "subject": "",
+                "message": "",
+                "start_at": "",
+                "end_at": "",
+                "id": 1,
+                "account_id": 1,
+            },
+        )
+
+        notif = {
+            "subject": "subject",
+            "message": "Message",
+            "start_at": "2015-04-01T00:00:00Z",
+            "end_at": "2018-04-01T00:00:00Z",
+            "id": 1,
+            "account_id": 1,
+        }
+
+        updated_notif = self.AccountNotification.update_global_notification(notif)
+
+        self.assertIsInstance(updated_notif, AccountNotification)
+        self.assertTrue(hasattr(updated_notif, "subject"))
+        self.assertEqual(updated_notif.subject, "subject")
+
+    def test_update_global_notification_missing_field(self, m):
+        register_uris({"account": ["update_notification"]}, m)
+
+        self.AccountNotification = AccountNotification(
+            self.canvas._Canvas__requester,
+            {
+                "subject": "",
+                "message": "",
+                "start_at": "",
+                "end_at": "",
+                "id": 1,
+                "account_id": 1,
+            },
+        )
+
+        notif = {}
+
+        with self.assertRaises(RequiredFieldMissing):
+            self.AccountNotification.update_global_notification(notif)
+
+
+@requests_mock.Mocker()
+class TestAccountNotification(unittest.TestCase):
+    def setUp(self):
+        self.canvas = Canvas(settings.BASE_URL, settings.API_KEY)
+
+        self.AccountNotification = AccountNotification(
+            self.canvas._Canvas__requester,
+            {
+                "subject": "subject",
+                "message": "Message",
+                "start_at": "2015-04-01T00:00:00Z",
+                "end_at": "2018-04-01T00:00:00Z",
+                "id": 1,
+                "account_id": 1,
+            },
+        )
+
+    # __str__()
+    def test__str__(self, m):
+        string = str(self.AccountNotification)
+        self.assertIsInstance(string, str)
+
+
+@requests_mock.Mocker()
+class TestAccountReport(unittest.TestCase):
+    def setUp(self):
+        self.canvas = Canvas(settings.BASE_URL, settings.API_KEY)
+        self.account = Account(self.canvas._Canvas__requester, {"id": 1})
+
+        self.AccountReport = AccountReport(
+            self.canvas._Canvas__requester,
+            {
+                "id": 1,
+                "title": "Zero Activity",
+                "parameters": {
+                    "enrollment_term_id": {
+                        "required": False,
+                        "description": "The canvas id of the term to get grades from",
+                    },
+                    "start_at": {
+                        "required": False,
+                        "description": "The first date in the date range",
+                    },
+                    "course_id": {
+                        "required": False,
+                        "description": "The course to report on",
+                    },
+                },
+                "report": "zero_activity_csv",
+                "last_run": "null",
+            },
+        )
+
+    # __str__()
+    def test__str__(self, m):
+        string = str(self.AccountReport)
+        self.assertIsInstance(string, str)
+
     # get_features()
     def test_get_features(self, m):
         register_uris({"account": ["get_features"]}, m)
diff --git a/tests/test_scope.py b/tests/test_scope.py
new file mode 100644
index 0000000..83fd798
--- /dev/null
+++ b/tests/test_scope.py
@@ -0,0 +1,23 @@
+from __future__ import absolute_import, division, print_function, unicode_literals
+import unittest
+
+import requests_mock
+
+from canvasapi import Canvas
+from canvasapi.scope import Scope
+from tests import settings
+
+
+@requests_mock.Mocker()
+class TestGradingPeriod(unittest.TestCase):
+    def setUp(self):
+        self.canvas = Canvas(settings.BASE_URL, settings.API_KEY)
+
+        self.scope = Scope(
+            self.canvas._Canvas__requester, {"resource": "users", "verb": "PUT"}
+        )
+
+    def test_str(self, m):
+
+        test_str = str(self.scope)
+        self.assertIsInstance(test_str, str)